summaryrefslogtreecommitdiffstats
path: root/hacks/analogtv-cli.c
diff options
context:
space:
mode:
Diffstat (limited to 'hacks/analogtv-cli.c')
-rw-r--r--hacks/analogtv-cli.c967
1 files changed, 967 insertions, 0 deletions
diff --git a/hacks/analogtv-cli.c b/hacks/analogtv-cli.c
new file mode 100644
index 0000000..d70db1b
--- /dev/null
+++ b/hacks/analogtv-cli.c
@@ -0,0 +1,967 @@
+/* xanalogtv-cli, Copyright (c) 2018 Jamie Zawinski <jwz@jwz.org>
+ *
+ * Permission to use, copy, modify, distribute, and sell this software and its
+ * documentation for any purpose is hereby granted without fee, provided that
+ * the above copyright notice appear in all copies and that both that
+ * copyright notice and this permission notice appear in supporting
+ * documentation. No representations are made about the suitability of this
+ * software for any purpose. It is provided "as is" without express or
+ * implied warranty.
+ *
+ * Performs the "Analog TV" transform on an image file, converting it to
+ * an MP4. The MP4 file will have the same dimensions as the input image.
+ * Requires 'ffmpeg' on $PATH.
+ *
+ * --duration Length in seconds of MP4.
+ * --powerup Do the power-on animation at the beginning.
+ * --logo FILE Small image overlayed onto the colorbars image.
+ * --audio FILE Add a soundtrack. Must be as long or longer.
+ *
+ * Created: 10-Dec-2018 by jwz.
+ */
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <signal.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xos.h>
+
+#include "resources.h"
+#include "visual.h"
+#include "yarandom.h"
+#include "font-retry.h"
+#include "ximage-loader.h"
+#include "thread_util.h"
+#include "xshm.h"
+#include "analogtv.h"
+
+#ifdef HAVE_LIBPNG
+# include <png.h>
+#endif
+
+const char *progname;
+const char *progclass;
+int mono_p = 0;
+static Bool verbose_p = 0;
+
+#define RANDSIGN() ((random() & 1) ? 1 : -1)
+
+enum {
+ N_CHANNELS=12,
+ MAX_MULTICHAN=2,
+ MAX_STATIONS=11
+};
+
+typedef struct chansetting_s {
+ analogtv_reception recs[MAX_MULTICHAN];
+ double noise_level;
+} chansetting;
+
+struct state {
+ XImage *output_frame;
+ int frames_written;
+ char *framefile_fmt;
+
+ Display *dpy;
+ Window window;
+ analogtv *tv;
+ analogtv_font ugly_font;
+
+ int n_stations;
+ analogtv_input *stations[MAX_STATIONS];
+ Bool image_loading_p;
+ XImage *logo, *logo_mask;
+
+ int curinputi;
+ chansetting chansettings[N_CHANNELS];
+ chansetting *cs;
+};
+
+static struct state global_state;
+
+
+/* Since this program does not connect to an X server, or in fact link
+ with Xlib, we need stubs for the few X11 routines that analogtv.c calls.
+ Most are unused. It seems like I am forever implementing subsets of X11.
+ */
+
+Status
+XAllocColor (Display *dpy, Colormap cmap, XColor *c)
+{
+ abort();
+}
+
+int
+XClearArea (Display *dpy, Window win, int x, int y,
+ unsigned int w, unsigned int h, Bool exp)
+{
+ return 0;
+}
+
+int
+XClearWindow (Display *dpy, Window window)
+{
+ return 0;
+}
+
+GC
+XCreateGC(Display *dpy, Drawable d, unsigned long mask, XGCValues *gcv)
+{
+ return 0;
+}
+
+XImage *
+XCreateImage (Display *dpy, Visual *v, unsigned int depth,
+ int format, int offset, char *data,
+ unsigned int width, unsigned int height,
+ int bitmap_pad, int bytes_per_line)
+{
+ XImage *ximage = (XImage *) calloc (1, sizeof(*ximage));
+ unsigned long r, g, b;
+
+ if (depth == 0) depth = 32;
+
+ ximage->width = width;
+ ximage->height = height;
+ ximage->format = format;
+ ximage->data = data;
+ ximage->bitmap_unit = 8;
+ ximage->byte_order = LSBFirst;
+ ximage->bitmap_bit_order = ximage->byte_order;
+ ximage->bitmap_pad = bitmap_pad;
+ ximage->depth = depth;
+ visual_rgb_masks (0, v, &r, &g, &b);
+ ximage->red_mask = (depth == 1 ? 0 : r);
+ ximage->green_mask = (depth == 1 ? 0 : g);
+ ximage->blue_mask = (depth == 1 ? 0 : b);
+ ximage->bits_per_pixel = (depth == 1 ? 1 : visual_pixmap_depth (0, v));
+ ximage->bytes_per_line = bytes_per_line;
+
+ XInitImage (ximage);
+ if (! ximage->f.put_pixel) abort();
+ return ximage;
+}
+
+Pixmap
+XCreatePixmap (Display *dpy, Drawable d, unsigned int width,
+ unsigned int height, unsigned int depth)
+{
+ abort();
+}
+
+Pixmap
+XCreatePixmapFromBitmapData (Display *dpy, Drawable d, char *data,
+ unsigned int w, unsigned int h,
+ unsigned long fg, unsigned long bg,
+ unsigned int depth)
+{
+ abort();
+}
+
+int
+XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, const char *s,
+ int len)
+{
+ abort();
+}
+
+int
+XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y,
+ unsigned int width, unsigned int height)
+{
+ abort();
+}
+
+int
+XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int n,
+ unsigned long planes)
+{
+ abort();
+}
+
+int
+XFreeGC (Display *dpy, GC gc)
+{
+ abort();
+}
+
+int
+XFreePixmap (Display *dpy, Pixmap p)
+{
+ abort();
+}
+
+XImage *
+XGetImage (Display *dpy, Drawable d, int x, int y,
+ unsigned int w, unsigned int h,
+ unsigned long pm, int fmt)
+{
+ abort();
+}
+
+Status
+XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
+{
+ struct state *st = &global_state;
+ memset (xgwa, 0, sizeof(*xgwa));
+ xgwa->width = st->output_frame->width;
+ xgwa->height = st->output_frame->height;
+ return True;
+}
+
+int
+XPutImage (Display *dpy, Drawable d, GC gc, XImage *image,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int w, unsigned int h)
+{
+ struct state *st = &global_state;
+ XImage *out = st->output_frame;
+ int x, y;
+ for (y = 0; y < h; y++) {
+ int iy = src_y + y;
+ int oy = dest_y + y;
+ if (iy >= 0 &&
+ oy >= 0 &&
+ iy < image->height &&
+ oy < out->height)
+ for (x = 0; x < w; x++) {
+ int ix = src_x + x;
+ int ox = dest_x + x;
+ if (ix >= 0 &&
+ ox >= 0 &&
+ ix < image->width &&
+ ox < out->width) {
+ XPutPixel (out, ox, oy, XGetPixel (image, ix, iy));
+ }
+ }
+ }
+ return 0;
+}
+
+int
+XQueryColor (Display *dpy, Colormap cmap, XColor *color)
+{
+ uint16_t r = (color->pixel & 0x00FF0000L) >> 16;
+ uint16_t g = (color->pixel & 0x0000FF00L) >> 8;
+ uint16_t b = (color->pixel & 0x000000FFL);
+ color->red = r | (r<<8);
+ color->green = g | (g<<8);
+ color->blue = b | (b<<8);
+ color->flags = DoRed|DoGreen|DoBlue;
+ return 0;
+}
+
+int
+XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n)
+{
+ int i;
+ for (i = 0; i < n; i++)
+ XQueryColor (dpy, cmap, &c[i]);
+ return 0;
+}
+
+int
+XSetForeground (Display *dpy, GC gc, unsigned long fg)
+{
+ abort();
+}
+
+int
+XSetWindowBackground (Display *dpy, Window win, unsigned long bg)
+{
+ return 0;
+}
+
+XImage *
+create_xshm_image (Display *dpy, Visual *visual,
+ unsigned int depth,
+ int format, XShmSegmentInfo *shm_info,
+ unsigned int width, unsigned int height)
+{
+# undef BitmapPad
+# define BitmapPad(dpy) 8
+ XImage *image = XCreateImage (dpy, visual, depth, format, 0, NULL,
+ width, height, BitmapPad(dpy), 0);
+ int error = thread_malloc ((void **)&image->data, dpy,
+ image->height * image->bytes_per_line);
+ if (error) {
+ XDestroyImage (image);
+ image = NULL;
+ } else {
+ memset (image->data, 0, image->height * image->bytes_per_line);
+ }
+
+ return image;
+}
+
+void
+destroy_xshm_image (Display *dpy, XImage *image, XShmSegmentInfo *shm_info)
+{
+ thread_free (image->data);
+ image->data = NULL;
+ XDestroyImage (image);
+}
+
+Bool
+get_boolean_resource (Display *dpy, char *name, char *class)
+{
+ if (!strcmp(name, "useThreads")) return True;
+ abort();
+}
+
+double
+get_float_resource (Display *dpy, char *name, char *class)
+{
+ if (!strcmp(name, "TVTint")) return 5;
+ if (!strcmp(name, "TVColor")) return 70;
+ if (!strcmp(name, "TVBrightness")) return -15;
+ if (!strcmp(name, "TVContrast")) return 150;
+ abort();
+}
+
+int
+get_integer_resource (Display *dpy, char *name, char *class)
+{
+ if (!strcmp(name, "use_cmap")) return 0;
+ abort();
+}
+
+unsigned int
+get_pixel_resource (Display *dpy, Colormap cmap, char *name, char *class)
+{
+ if (!strcmp(name, "background")) return 0;
+ abort();
+}
+
+XFontStruct *
+load_font_retry (Display *dpy, const char *xlfd)
+{
+ abort();
+}
+
+Bool
+put_xshm_image (Display *dpy, Drawable d, GC gc, XImage *image,
+ int src_x, int src_y, int dest_x, int dest_y,
+ unsigned int width, unsigned int height,
+ XShmSegmentInfo *shm_info)
+{
+ return XPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
+ width, height);
+}
+
+int
+visual_class (Screen *s, Visual *v)
+{
+ return TrueColor;
+}
+
+int
+visual_pixmap_depth (Screen *s, Visual *v)
+{
+ return 32;
+}
+
+void
+visual_rgb_masks (Screen *screen, Visual *visual,
+ unsigned long *red_mask,
+ unsigned long *green_mask,
+ unsigned long *blue_mask)
+{
+ *red_mask = 0x00FF0000L;
+ *green_mask = 0x0000FF00L;
+ *blue_mask = 0x000000FFL;
+}
+
+
+static void
+update_smpte_colorbars(analogtv_input *input)
+{
+ struct state *st = (struct state *) input->client_data;
+ int col;
+ int black_ntsc[4];
+
+ /*
+ SMPTE is the society of motion picture and television engineers, and
+ these are the standard color bars in the US. Following the partial spec
+ at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
+ These are luma, chroma, and phase numbers for each of the 7 bars.
+ */
+ double top_cb_table[7][3]={
+ {75, 0, 0.0}, /* gray */
+ {69, 31, 167.0}, /* yellow */
+ {56, 44, 283.5}, /* cyan */
+ {48, 41, 240.5}, /* green */
+ {36, 41, 60.5}, /* magenta */
+ {28, 44, 103.5}, /* red */
+ {15, 31, 347.0} /* blue */
+ };
+ double mid_cb_table[7][3]={
+ {15, 31, 347.0}, /* blue */
+ {7, 0, 0}, /* black */
+ {36, 41, 60.5}, /* magenta */
+ {7, 0, 0}, /* black */
+ {56, 44, 283.5}, /* cyan */
+ {7, 0, 0}, /* black */
+ {75, 0, 0.0} /* gray */
+ };
+
+ analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
+
+ analogtv_setup_sync(input, 1, 0);
+
+ for (col=0; col<7; col++) {
+ analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68,
+ top_cb_table[col][0],
+ top_cb_table[col][1], top_cb_table[col][2]);
+
+ analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75,
+ mid_cb_table[col][0],
+ mid_cb_table[col][1], mid_cb_table[col][2]);
+ }
+
+ analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
+ 0.75, 1.00, 7, 40, 303); /* -I */
+ analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
+ 0.75, 1.00, 100, 0, 0); /* white */
+ analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
+ 0.75, 1.00, 7, 40, 33); /* +Q */
+ analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+ analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
+ 0.75, 1.00, 3, 0, 0); /* black -4 */
+ analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+ analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
+ 0.75, 1.00, 11, 0, 0); /* black +4 */
+ analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
+ 0.75, 1.00, 7, 0, 0); /* black */
+ if (st->logo)
+ {
+ double aspect = (double)
+ st->output_frame->width / st->output_frame->height;
+ int w2 = st->tv->xgwa.width * 0.35;
+ int h2 = st->tv->xgwa.height * 0.35 * aspect;
+ analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask,
+ (st->tv->xgwa.width - w2) / 2,
+ st->tv->xgwa.height * 0.20,
+ w2, h2);
+ }
+
+ input->next_update_time += 1.0;
+}
+
+
+
+static void
+analogtv_save_frame (struct state *st, const char *outfile,
+ unsigned long frame)
+{
+ char *pngfile = malloc (strlen (st->framefile_fmt) + 40);
+ FILE *f;
+
+ sprintf (pngfile, st->framefile_fmt, (int) frame);
+ f = fopen (pngfile, "wb");
+ if (! f) {
+ fprintf (stderr, "%s: unable to write %s\n", progname, pngfile);
+ exit (1);
+ }
+
+# ifdef HAVE_LIBPNG
+ {
+ png_structp png_ptr;
+ png_infop info_ptr;
+ png_bytep row;
+ XImage *img = st->output_frame;
+ int x, y;
+
+ png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0);
+ if (!png_ptr) abort();
+ info_ptr = png_create_info_struct (png_ptr);
+ if (!info_ptr) abort();
+ if (setjmp (png_jmpbuf (png_ptr))) abort();
+
+ png_init_io (png_ptr, f);
+
+ png_set_IHDR (png_ptr, info_ptr, img->width, img->height, 8,
+ PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
+ png_write_info (png_ptr, info_ptr);
+
+ row = (png_bytep) malloc (3 * img->width * sizeof(png_byte));
+ if (!row) abort();
+ for (y = 0 ; y < img->height ; y++) {
+ for (x = 0 ; x < img->width ; x++) {
+ unsigned long p = XGetPixel (img, x, y);
+ row[x*3+0] = (p & 0x000000FFL);
+ row[x*3+1] = (p & 0x0000FF00L) >> 8;
+ row[x*3+2] = (p & 0x00FF0000L) >> 16;
+ }
+ png_write_row (png_ptr, row);
+ }
+
+ png_write_end (png_ptr, 0);
+
+ png_free_data (png_ptr, info_ptr, PNG_FREE_ALL, -1);
+ png_destroy_write_struct (&png_ptr, 0);
+ free (row);
+ }
+#else /* ! HAVE_LIBPNG */
+# error libpng required
+# endif /* ! HAVE_LIBPNG */
+
+ fclose (f);
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: wrote %s\n", progname, pngfile);
+ free (pngfile);
+}
+
+
+static void
+delete_tmp_files(void)
+{
+ struct state *st = &global_state;
+ char outfile[2048];
+ int i;
+ for (i = 0; i <= st->frames_written; i++)
+ {
+ sprintf (outfile, st->framefile_fmt, i);
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: rm %s\n", progname, outfile);
+ unlink (outfile);
+ }
+}
+
+
+static RETSIGTYPE
+analogtv_signal (int sig)
+{
+ signal (sig, SIG_DFL);
+ delete_tmp_files();
+ kill (getpid (), sig);
+}
+
+
+static void
+analogtv_write_mp4 (struct state *st, const char *outfile,
+ const char *audiofile,
+ unsigned long frames)
+{
+ char cmd[1024];
+ struct stat ss;
+
+ sprintf (cmd,
+ "ffmpeg"
+ " -hide_banner"
+ " -v 16"
+ " -framerate 30" /* rate of input: must be before -i */
+ " -i '%s'"
+ " -r 30", /* rate of output: must be after -i */
+ st->framefile_fmt);
+ if (audiofile)
+ sprintf (cmd + strlen(cmd),
+ " -i '%s'"
+ " -map 0:v:0"
+ " -map 1:a:0"
+ " -acodec aac"
+ " -shortest",
+ audiofile);
+ sprintf (cmd + strlen(cmd),
+ " -c:v libx264"
+ " -profile:v high"
+ " -crf 24" /* 18 is very high; 24 is good enough */
+ " -pix_fmt yuv420p"
+ " '%s'"
+ " </dev/null"
+ /*" 2>&-"*/,
+ outfile);
+
+ if (verbose_p > 1)
+ fprintf (stderr, "%s: exec: %s\n", progname, cmd);
+ unlink (outfile);
+ system (cmd);
+
+ delete_tmp_files();
+
+ if (stat (outfile, &ss))
+ {
+ fprintf (stderr, "%s: %s was not created\n", progname, outfile);
+ exit (1);
+ }
+
+ if (verbose_p)
+ fprintf (stderr, "%s: wrote %s (%dx%d, %lu sec, %.0f MB)\n",
+ progname, outfile,
+ st->output_frame->width, st->output_frame->height,
+ frames/30,
+ (float) ss.st_size / (1024*1024));
+}
+
+
+static void
+flip_ximage (XImage *ximage)
+{
+ char *data2, *in, *out;
+ int y;
+
+ if (!ximage) return;
+ data2 = malloc (ximage->bytes_per_line * ximage->height);
+ if (!data2) abort();
+ in = ximage->data;
+ out = data2 + ximage->bytes_per_line * (ximage->height - 1);
+ for (y = 0; y < ximage->height; y++)
+ {
+ memcpy (out, in, ximage->bytes_per_line);
+ in += ximage->bytes_per_line;
+ out -= ximage->bytes_per_line;
+ }
+ free (ximage->data);
+ ximage->data = data2;
+}
+
+
+static void
+analogtv_convert (const char *infile, const char *outfile,
+ const char *audiofile, const char *logofile,
+ int duration, Bool powerp)
+{
+ struct state *st = &global_state;
+ XImage *ximage = file_to_ximage (0, 0, infile);
+ Display *dpy = 0;
+ Window window = 0;
+ int i;
+ unsigned long curticks = 0;
+ time_t lastlog = time((time_t *)0);
+ int frames_left;
+ int fps = 30;
+
+ if (verbose_p)
+ fprintf (stderr, "%s: progname: loaded %s %dx%d\n",
+ progname, infile, ximage->width, ximage->height);
+
+ flip_ximage (ximage);
+
+ memset (st, 0, sizeof(*st));
+ st->dpy = dpy;
+ st->window = window;
+
+ st->output_frame = XCreateImage (dpy, 0, ximage->depth, ximage->format, 0,
+ NULL,
+ ximage->width & ~1, /* can't be odd */
+ ximage->height & ~1,
+ ximage->bitmap_pad, 0);
+ st->output_frame->data = (char *)
+ calloc (st->output_frame->height, st->output_frame->bytes_per_line);
+
+ {
+ char *s1, *s2;
+ st->framefile_fmt = malloc (strlen(outfile) + 100);
+ strcpy (st->framefile_fmt, outfile);
+ s1 = strrchr (st->framefile_fmt, '/');
+ s2 = strrchr (st->framefile_fmt, '.');
+ if (s2 && s2 > s1) *s2 = 0;
+ sprintf (st->framefile_fmt + strlen(st->framefile_fmt),
+ ".%08x.%%06d.png", (random() % 0xFFFFFFFF));
+ }
+
+ if (logofile) {
+ int x, y;
+ st->logo = file_to_ximage (0, 0, logofile);
+ if (verbose_p)
+ fprintf (stderr, "%s: progname: loaded %s %dx%d\n",
+ progname, logofile, st->logo->width, st->logo->height);
+ flip_ximage (st->logo);
+ /* Pull the alpha out of the logo and make a separate mask ximage. */
+ st->logo_mask = XCreateImage (dpy, 0, st->logo->depth, st->logo->format, 0,
+ NULL, st->logo->width, st->logo->height,
+ st->logo->bitmap_pad, 0);
+ st->logo_mask->data = (char *)
+ calloc (st->logo_mask->height, st->logo_mask->bytes_per_line);
+
+ for (y = 0; y < st->logo->height; y++)
+ for (x = 0; x < st->logo->width; x++) {
+ unsigned long p = XGetPixel (st->logo, x, y);
+ uint8_t a = (p & 0xFF000000L) >> 24;
+ XPutPixel (st->logo, x, y, (p & 0x00FFFFFFL));
+ XPutPixel (st->logo_mask, x, y, (a ? 0x00FFFFFFL : 0));
+ }
+ }
+
+
+ if (audiofile) {
+ struct stat ss;
+ if (stat (audiofile, &ss))
+ {
+ fprintf (stderr, "%s: %s does not exist\n", progname, audiofile);
+ exit (1);
+ }
+ }
+
+ /* Catch signals to delete tmp files before we start writing them. */
+
+ signal (SIGHUP, analogtv_signal);
+ signal (SIGINT, analogtv_signal);
+ signal (SIGQUIT, analogtv_signal);
+ signal (SIGILL, analogtv_signal);
+ signal (SIGTRAP, analogtv_signal);
+# ifdef SIGIOT
+ signal (SIGIOT, analogtv_signal);
+# endif
+ signal (SIGABRT, analogtv_signal);
+# ifdef SIGEMT
+ signal (SIGEMT, analogtv_signal);
+# endif
+ signal (SIGFPE, analogtv_signal);
+ signal (SIGBUS, analogtv_signal);
+ signal (SIGSEGV, analogtv_signal);
+# ifdef SIGSYS
+ signal (SIGSYS, analogtv_signal);
+# endif
+ signal (SIGTERM, analogtv_signal);
+# ifdef SIGXCPU
+ signal (SIGXCPU, analogtv_signal);
+# endif
+# ifdef SIGXFSZ
+ signal (SIGXFSZ, analogtv_signal);
+# endif
+# ifdef SIGDANGER
+ signal (SIGDANGER, analogtv_signal);
+# endif
+
+ st->tv=analogtv_allocate(dpy, window);
+
+ while (st->n_stations < MAX_STATIONS) {
+ analogtv_input *input=analogtv_input_allocate();
+ st->stations[st->n_stations++]=input;
+ input->client_data = st;
+ }
+
+ analogtv_set_defaults(st->tv, "");
+ st->tv->need_clear=1;
+
+ if (random()%4==0) {
+ st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
+ }
+ if (1) {
+ st->tv->color_control += frand(0.3) * RANDSIGN();
+ }
+ if (random()%4==0) {
+ st->tv->brightness_control += frand(0.15);
+ }
+ if (random()%4==0) {
+ st->tv->contrast_control += frand(0.2) * RANDSIGN();
+ }
+
+ for (i=0; i<N_CHANNELS; i++) {
+ memset(&st->chansettings[i], 0, sizeof(chansetting));
+
+ st->chansettings[i].noise_level = 0.06;
+ {
+ int last_station=42;
+ int stati;
+ for (stati=0; stati<MAX_MULTICHAN; stati++) {
+ analogtv_reception *rec=&st->chansettings[i].recs[stati];
+ int station;
+ while (1) {
+ station=random()%st->n_stations;
+ if (station!=last_station) break;
+ if ((random()%10)==0) break;
+ }
+ last_station=station;
+ rec->input = st->stations[station];
+ rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
+ rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
+ if (random()%3) {
+ rec->multipath = frand(1.0);
+ } else {
+ rec->multipath=0.0;
+ }
+ if (stati) {
+ /* We only set a frequency error for ghosting stations,
+ because it doesn't matter otherwise */
+ rec->freqerr = (frand(2.0)-1.0) * 3.0;
+ }
+
+ if (rec->level > 0.3) break;
+ if (random()%4) break;
+ }
+ }
+ }
+
+ st->curinputi=0;
+ st->cs = &st->chansettings[st->curinputi];
+ frames_left = fps * (2 + frand(1.5));
+
+ st->tv->powerup=0.0;
+
+ /* load_station_images() */
+
+ for (i = 0; i < MAX_STATIONS; i++) {
+ analogtv_input *input = st->stations[i];
+
+ if (i == 1) { /* station 0 is the unadulterated image.
+ station 1 is colorbars. */
+ input->updater = update_smpte_colorbars;
+ } else {
+ int w = ximage->width * 0.815; /* underscan */
+ int h = ximage->height * 0.970;
+ int x = (ximage->width - w) / 2;
+ int y = (ximage->height - h) / 2;
+ analogtv_input *input = st->stations[i];
+ analogtv_setup_sync(input, 1, (random()%20)==0);
+ analogtv_load_ximage (st->tv, input, ximage, 0, x, y, w, h);
+ }
+ }
+
+
+ /* xanalogtv_draw() */
+
+ while (1) {
+ const analogtv_reception *recs[MAX_MULTICHAN];
+ unsigned rec_count = 0;
+ double curtime=curticks*0.001;
+
+ frames_left--;
+ if (frames_left <= 0) {
+ frames_left = fps * (0.5 + frand(2.5));
+
+ if (st->curinputi != 0 && !(random() % 3)) {
+ st->curinputi = 0; /* unadulterated image */
+ } else {
+ st->curinputi = 1 + (random() % (N_CHANNELS - 1));
+ }
+
+ st->cs = &st->chansettings[st->curinputi];
+ /* Set channel change noise flag */
+ st->tv->channel_change_cycles=200000;
+ }
+
+ for (i=0; i<MAX_MULTICHAN; i++) {
+ analogtv_reception *rec = &st->cs->recs[i];
+ analogtv_input *inp=rec->input;
+ if (!inp) continue;
+
+ if (inp->updater) {
+ inp->next_update_time = curtime;
+ (inp->updater)(inp);
+ }
+ rec->ofs += rec->freqerr;
+ }
+
+ st->tv->powerup=(powerp ? curtime : 9999);
+
+ if (st->curinputi == 0) {
+ XPutImage (dpy, 0, 0, ximage, 0, 0, 0, 0,
+ ximage->width, ximage->height);
+ } else {
+ for (i=0; i<MAX_MULTICHAN; i++) {
+ analogtv_reception *rec = &st->cs->recs[i];
+ if (rec->input) {
+ analogtv_reception_update(rec);
+ recs[rec_count] = rec;
+ ++rec_count;
+ }
+ }
+ analogtv_draw (st->tv, st->cs->noise_level, recs, rec_count);
+ }
+
+ analogtv_save_frame (st, outfile, st->frames_written);
+
+ if (curtime >= duration) break;
+
+ curticks += 1000/fps;
+ st->frames_written++;
+
+ if (verbose_p) {
+ unsigned long now = time((time_t *)0);
+ if (now > lastlog + 5) {
+ fprintf (stderr, "%s: %2d%%...\n", progname,
+ (int) (curtime * 100 / duration));
+ lastlog = now;
+ }
+ }
+ }
+
+ analogtv_write_mp4 (st, outfile, audiofile, st->frames_written);
+}
+
+
+static void
+usage(const char *err)
+{
+ if (err) fprintf (stderr, "%s: %s unknown\n", progname, err);
+ fprintf (stderr, "usage: %s [--verbose] [--duration secs]"
+ " [--audio mp3-file] [--no-powerup] infile.png outfile.mp4\n",
+ progname);
+ exit (1);
+}
+
+int
+main (int argc, char **argv)
+{
+ int i;
+ const char *infile = 0;
+ const char *outfile = 0;
+ int duration = 30;
+ Bool powerp = False;
+ char *audio = 0;
+ char *logo = 0;
+
+ char *s = strrchr (argv[0], '/');
+ progname = s ? s+1 : argv[0];
+ progclass = progname;
+
+ for (i = 1; i < argc; i++)
+ {
+ if (argv[i][0] == '-' && argv[i][1] == '-')
+ argv[i]++;
+ if (!strcmp(argv[i], "-v") ||
+ !strcmp(argv[i], "-verbose"))
+ verbose_p++;
+ else if (!strcmp(argv[i], "-vv")) verbose_p += 2;
+ else if (!strcmp(argv[i], "-vvv")) verbose_p += 3;
+ else if (!strcmp(argv[i], "-vvvv")) verbose_p += 4;
+ else if (!strcmp(argv[i], "-vvvvv")) verbose_p += 5;
+ else if (!strcmp(argv[i], "-duration") && argv[i+1])
+ {
+ char dummy;
+ i++;
+ if (1 != sscanf (argv[i], " %d %c", &duration, &dummy))
+ usage(argv[i]);
+ }
+ else if (!strcmp(argv[i], "-audio") && argv[i+1])
+ audio = argv[++i];
+ else if (!strcmp(argv[i], "-logo") && argv[i+1])
+ logo = argv[++i];
+ else if (!strcmp(argv[i], "-powerup") ||
+ !strcmp(argv[i], "-power"))
+ powerp = True;
+ else if (!strcmp(argv[i], "-no-powerup") ||
+ !strcmp(argv[i], "-no-power"))
+ powerp = False;
+ else if (argv[i][0] == '-')
+ usage(argv[i]);
+ else if (!infile)
+ infile = argv[i];
+ else if (!outfile)
+ outfile = argv[i];
+ else
+ usage(argv[i]);
+ }
+ if (!infile) usage("input file");
+ if (!outfile) usage("output file");
+
+# undef ya_rand_init
+ ya_rand_init (0);
+ analogtv_convert (infile, outfile, audio, logo, duration, powerp);
+ exit (0);
+}