"Fossies" - the Fresh Open Source Software Archive

Member "xscreensaver-6.01/hacks/analogtv-cli.c" (27 Apr 2021, 30080 Bytes) of package /linux/misc/xscreensaver-6.01.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "analogtv-cli.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 6.00_vs_6.01.

    1 /* xanalogtv-cli, Copyright © 2018-2021 Jamie Zawinski <jwz@jwz.org>
    2  *
    3  * Permission to use, copy, modify, distribute, and sell this software and its
    4  * documentation for any purpose is hereby granted without fee, provided that
    5  * the above copyright notice appear in all copies and that both that
    6  * copyright notice and this permission notice appear in supporting
    7  * documentation.  No representations are made about the suitability of this
    8  * software for any purpose.  It is provided "as is" without express or 
    9  * implied warranty.
   10  *
   11  * Performs the "Analog TV" transform on an image file, converting it to
   12  * an MP4.  The MP4 file will have the same dimensions as the input image.
   13  * Requires 'ffmpeg' on $PATH.
   14  *
   15  *    --duration     Length in seconds of MP4.
   16  *    --powerup      Do the power-on animation at the beginning.
   17  *    --logo FILE    Small image overlayed onto the colorbars image.
   18  *    --audio FILE   Add a soundtrack.
   19  *
   20  *  Created: 10-Dec-2018 by jwz.
   21  */
   22 
   23 #ifdef HAVE_CONFIG_H
   24 # include "config.h"
   25 #endif
   26 
   27 #include <stdio.h>
   28 #include <stdlib.h>
   29 #include <string.h>
   30 #include <math.h>
   31 #include <sys/stat.h>
   32 #include <sys/types.h>
   33 #include <signal.h>
   34 
   35 #include <X11/Xlib.h>
   36 #include <X11/Xutil.h>
   37 #include <X11/Xos.h>
   38 
   39 #include "screenhackI.h"
   40 #include "resources.h"
   41 #include "visual.h"
   42 #include "yarandom.h"
   43 #include "font-retry.h"
   44 #include "ximage-loader.h"
   45 #include "thread_util.h"
   46 #include "xshm.h"
   47 #include "analogtv.h"
   48 
   49 #ifdef HAVE_LIBPNG
   50 # include <png.h>
   51 #endif
   52 
   53 const char *progname;
   54 const char *progclass;
   55 int mono_p = 0;
   56 static Bool verbose_p = 0;
   57 
   58 #define MAX_MULTICHAN 2
   59 static int N_CHANNELS=12;
   60 static int MAX_STATIONS=6;
   61 
   62 typedef struct chansetting_s {
   63   analogtv_reception recs[MAX_MULTICHAN];
   64   double noise_level;
   65 } chansetting;
   66 
   67 struct state {
   68   XImage *output_frame;
   69   int frames_written;
   70   char *framefile_fmt;
   71 
   72   Display *dpy;
   73   Window window;
   74   analogtv *tv;
   75   analogtv_font ugly_font;
   76 
   77   int n_stations;
   78   analogtv_input **stations;
   79   Bool image_loading_p;
   80   XImage *logo, *logo_mask;
   81 
   82   int curinputi;
   83   chansetting *chansettings;
   84   chansetting *cs;
   85 };
   86 
   87 static struct state global_state;
   88 
   89 
   90 /* Since this program does not connect to an X server, or in fact link
   91    with Xlib, we need stubs for the few X11 routines that analogtv.c calls.
   92    Most are unused. It seems like I am forever implementing subsets of X11.
   93  */
   94 
   95 Status
   96 XAllocColor (Display *dpy, Colormap cmap, XColor *c)
   97 {
   98   abort();
   99 }
  100 
  101 int
  102 XClearArea (Display *dpy, Window win, int x, int y,
  103             unsigned int w, unsigned int h, Bool exp)
  104 {
  105   return 0;
  106 }
  107 
  108 int
  109 XClearWindow (Display *dpy, Window window)
  110 {
  111   return 0;
  112 }
  113 
  114 GC
  115 XCreateGC(Display *dpy, Drawable d, unsigned long mask, XGCValues *gcv)
  116 {
  117   return 0;
  118 }
  119 
  120 int screen_number (Screen *screen) { return 0; }
  121 
  122 
  123 XImage *
  124 XCreateImage (Display *dpy, Visual *v, unsigned int depth,
  125               int format, int offset, char *data,
  126               unsigned int width, unsigned int height,
  127               int bitmap_pad, int bytes_per_line)
  128 {
  129   XImage *ximage = (XImage *) calloc (1, sizeof(*ximage));
  130   unsigned long r, g, b;
  131 
  132   if (depth == 0) depth = 32;
  133 
  134   ximage->width = width;
  135   ximage->height = height;
  136   ximage->format = format;
  137   ximage->data = data;
  138   ximage->bitmap_unit = 8;
  139   ximage->byte_order = LSBFirst;
  140   ximage->bitmap_bit_order = ximage->byte_order;
  141   ximage->bitmap_pad = bitmap_pad;
  142   ximage->depth = depth;
  143   visual_rgb_masks (0, v, &r, &g, &b);
  144   ximage->red_mask   = (depth == 1 ? 0 : r);
  145   ximage->green_mask = (depth == 1 ? 0 : g);
  146   ximage->blue_mask  = (depth == 1 ? 0 : b);
  147   ximage->bits_per_pixel = (depth == 1 ? 1 : visual_pixmap_depth (0, v));
  148   ximage->bytes_per_line = bytes_per_line;
  149 
  150   XInitImage (ximage);
  151   if (! ximage->f.put_pixel) abort();
  152   return ximage;
  153 }
  154 
  155 Pixmap
  156 XCreatePixmap (Display *dpy, Drawable d, unsigned int width,
  157                unsigned int height, unsigned int depth)
  158 {
  159   abort();
  160 }
  161 
  162 Pixmap
  163 XCreatePixmapFromBitmapData (Display *dpy, Drawable d, char *data,
  164                              unsigned int w, unsigned int h,
  165                              unsigned long fg, unsigned long bg,
  166                              unsigned int depth)
  167 {
  168   abort();
  169 }
  170 
  171 int
  172 XDrawString (Display *dpy, Drawable d, GC gc, int x, int y, const char *s,
  173              int len)
  174 {
  175   abort();
  176 }
  177 
  178 int
  179 XFillRectangle (Display *dpy, Drawable d, GC gc, int x, int y, 
  180                 unsigned int width, unsigned int height)
  181 {
  182   abort();
  183 }
  184 
  185 int
  186 XFreeColors (Display *dpy, Colormap cmap, unsigned long *px, int n,
  187              unsigned long planes)
  188 {
  189   abort();
  190 }
  191 
  192 int
  193 XFreeGC (Display *dpy, GC gc)
  194 {
  195   abort();
  196 }
  197 
  198 int
  199 XFreePixmap (Display *dpy, Pixmap p)
  200 {
  201   abort();
  202 }
  203 
  204 XImage *
  205 XGetImage (Display *dpy, Drawable d, int x, int y,
  206            unsigned int w, unsigned int h,
  207            unsigned long pm, int fmt)
  208 {
  209   abort();
  210 }
  211 
  212 Status
  213 XGetWindowAttributes (Display *dpy, Window w, XWindowAttributes *xgwa)
  214 {
  215   struct state *st = &global_state;
  216   memset (xgwa, 0, sizeof(*xgwa));
  217   xgwa->width = st->output_frame->width;
  218   xgwa->height = st->output_frame->height;
  219   return True;
  220 }
  221 
  222 int
  223 XPutImage (Display *dpy, Drawable d, GC gc, XImage *image, 
  224            int src_x, int src_y, int dest_x, int dest_y,
  225            unsigned int w, unsigned int h)
  226 {
  227   struct state *st = &global_state;
  228   XImage *out = st->output_frame;
  229   int x, y;
  230   for (y = 0; y < h; y++) {
  231     int iy = src_y + y;
  232     int oy = dest_y + y;
  233     if (iy >= 0 &&
  234         oy >= 0 &&
  235         iy < image->height &&
  236         oy < out->height)
  237       for (x = 0; x < w; x++) {
  238         int ix = src_x + x;
  239         int ox = dest_x + x;
  240         if (ix >= 0 &&
  241             ox >= 0 &&
  242             ix < image->width &&
  243             ox < out->width) {
  244           XPutPixel (out, ox, oy, XGetPixel (image, ix, iy));
  245         }
  246       }
  247   }
  248   return 0;
  249 }
  250 
  251 int
  252 XQueryColor (Display *dpy, Colormap cmap, XColor *color)
  253 {
  254   uint16_t r = (color->pixel & 0x00FF0000L) >> 16;
  255   uint16_t g = (color->pixel & 0x0000FF00L) >> 8;
  256   uint16_t b = (color->pixel & 0x000000FFL);
  257   color->red   = r | (r<<8);
  258   color->green = g | (g<<8);
  259   color->blue  = b | (b<<8);
  260   color->flags = DoRed|DoGreen|DoBlue;
  261   return 0;
  262 }
  263 
  264 int
  265 XQueryColors (Display *dpy, Colormap cmap, XColor *c, int n)
  266 {
  267   int i;
  268   for (i = 0; i < n; i++)
  269     XQueryColor (dpy, cmap, &c[i]);
  270   return 0;
  271 }
  272 
  273 int
  274 XSetForeground (Display *dpy, GC gc, unsigned long fg)
  275 {
  276   abort();
  277 }
  278 
  279 int
  280 XSetWindowBackground (Display *dpy, Window win, unsigned long bg)
  281 {
  282   return 0;
  283 }
  284 
  285 XImage *
  286 create_xshm_image (Display *dpy, Visual *visual,
  287            unsigned int depth,
  288            int format, XShmSegmentInfo *shm_info,
  289            unsigned int width, unsigned int height)
  290 {
  291 # undef BitmapPad
  292 # define BitmapPad(dpy) 8
  293   XImage *image = XCreateImage (dpy, visual, depth, format, 0, NULL,
  294                                 width, height, BitmapPad(dpy), 0);
  295   int error = thread_malloc ((void **)&image->data, dpy,
  296                              image->height * image->bytes_per_line);
  297   if (error) {
  298     XDestroyImage (image);
  299     image = NULL;
  300   } else {
  301     memset (image->data, 0, image->height * image->bytes_per_line);
  302   }
  303 
  304   return image;
  305 }
  306 
  307 void
  308 destroy_xshm_image (Display *dpy, XImage *image, XShmSegmentInfo *shm_info)
  309 {
  310   thread_free (image->data);
  311   image->data = NULL;
  312   XDestroyImage (image);
  313 }
  314 
  315 Bool
  316 get_boolean_resource (Display *dpy, char *name, char *class)
  317 {
  318   if (!strcmp(name, "useThreads")) return True;
  319   abort();
  320 }
  321 
  322 static int darkp = 0;
  323 double
  324 get_float_resource (Display *dpy, char *name, char *class)
  325 {
  326   if (!strcmp(name, "TVTint")) return 5;        /* default 5   */
  327   if (!strcmp(name, "TVColor")) return 70;      /* default 70  */
  328   if (!strcmp(name, "TVBrightness"))
  329     return (darkp ? -15 : 2);               /* default 2   */
  330   if (!strcmp(name, "TVContrast")) return 150;      /* default 150 */
  331   abort();
  332 }
  333 
  334 int
  335 get_integer_resource (Display *dpy, char *name, char *class)
  336 {
  337   if (!strcmp(name, "use_cmap")) return 0;
  338   abort();
  339 }
  340 
  341 unsigned int
  342 get_pixel_resource (Display *dpy, Colormap cmap, char *name, char *class)
  343 {
  344   if (!strcmp(name, "background")) return 0;
  345   abort();
  346 }
  347 
  348 Bool
  349 put_xshm_image (Display *dpy, Drawable d, GC gc, XImage *image,
  350                 int src_x, int src_y, int dest_x, int dest_y,
  351                 unsigned int width, unsigned int height,
  352                 XShmSegmentInfo *shm_info)
  353 {
  354   return XPutImage (dpy, d, gc, image, src_x, src_y, dest_x, dest_y,
  355                     width, height);
  356 }
  357 
  358 int
  359 visual_class (Screen *s, Visual *v)
  360 {
  361   return TrueColor;
  362 }
  363 
  364 int
  365 visual_pixmap_depth (Screen *s, Visual *v)
  366 {
  367   return 32;
  368 }
  369 
  370 void
  371 visual_rgb_masks (Screen *screen, Visual *visual,
  372                   unsigned long *red_mask,
  373                   unsigned long *green_mask,
  374                   unsigned long *blue_mask)
  375 {
  376   *red_mask   = 0x00FF0000L;
  377   *green_mask = 0x0000FF00L;
  378   *blue_mask  = 0x000000FFL;
  379 }
  380 
  381 
  382 static void
  383 update_smpte_colorbars(analogtv_input *input)
  384 {
  385   struct state *st = (struct state *) input->client_data;
  386   int col;
  387   int black_ntsc[4];
  388 
  389   /* 
  390      SMPTE is the society of motion picture and television engineers, and
  391      these are the standard color bars in the US. Following the partial spec
  392      at http://broadcastengineering.com/ar/broadcasting_inside_color_bars/
  393      These are luma, chroma, and phase numbers for each of the 7 bars.
  394   */
  395   double top_cb_table[7][3]={
  396     {75, 0, 0.0},    /* gray */
  397     {69, 31, 167.0}, /* yellow */
  398     {56, 44, 283.5}, /* cyan */
  399     {48, 41, 240.5}, /* green */
  400     {36, 41, 60.5},  /* magenta */
  401     {28, 44, 103.5}, /* red */
  402     {15, 31, 347.0}  /* blue */
  403   };
  404   double mid_cb_table[7][3]={
  405     {15, 31, 347.0}, /* blue */
  406     {7, 0, 0},       /* black */
  407     {36, 41, 60.5},  /* magenta */
  408     {7, 0, 0},       /* black */
  409     {56, 44, 283.5}, /* cyan */
  410     {7, 0, 0},       /* black */
  411     {75, 0, 0.0}     /* gray */
  412   };
  413 
  414   analogtv_lcp_to_ntsc(0.0, 0.0, 0.0, black_ntsc);
  415 
  416   analogtv_setup_sync(input, 1, 0);
  417 
  418   for (col=0; col<7; col++) {
  419     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.00, 0.68, 
  420                                 top_cb_table[col][0], 
  421                                 top_cb_table[col][1], top_cb_table[col][2]);
  422     
  423     analogtv_draw_solid_rel_lcp(input, col*(1.0/7.0), (col+1)*(1.0/7.0), 0.68, 0.75, 
  424                                 mid_cb_table[col][0], 
  425                                 mid_cb_table[col][1], mid_cb_table[col][2]);
  426   }
  427 
  428   analogtv_draw_solid_rel_lcp(input, 0.0, 1.0/6.0,
  429                               0.75, 1.00, 7, 40, 303);   /* -I */
  430   analogtv_draw_solid_rel_lcp(input, 1.0/6.0, 2.0/6.0,
  431                               0.75, 1.00, 100, 0, 0);    /* white */
  432   analogtv_draw_solid_rel_lcp(input, 2.0/6.0, 3.0/6.0,
  433                               0.75, 1.00, 7, 40, 33);    /* +Q */
  434   analogtv_draw_solid_rel_lcp(input, 3.0/6.0, 4.0/6.0,
  435                               0.75, 1.00, 7, 0, 0);      /* black */
  436   analogtv_draw_solid_rel_lcp(input, 12.0/18.0, 13.0/18.0,
  437                               0.75, 1.00, 3, 0, 0);      /* black -4 */
  438   analogtv_draw_solid_rel_lcp(input, 13.0/18.0, 14.0/18.0,
  439                               0.75, 1.00, 7, 0, 0);      /* black */
  440   analogtv_draw_solid_rel_lcp(input, 14.0/18.0, 15.0/18.0,
  441                               0.75, 1.00, 11, 0, 0);     /* black +4 */
  442   analogtv_draw_solid_rel_lcp(input, 5.0/6.0, 6.0/6.0,
  443                               0.75, 1.00, 7, 0, 0);      /* black */
  444   if (st->logo)
  445     {
  446       double aspect = (double)
  447         st->output_frame->width / st->output_frame->height;
  448       int w2 = st->tv->xgwa.width  * 0.35;
  449       int h2 = st->tv->xgwa.height * 0.35 * aspect;
  450       analogtv_load_ximage (st->tv, input, st->logo, st->logo_mask,
  451                             (st->tv->xgwa.width - w2) / 2,
  452                             st->tv->xgwa.height * 0.20,
  453                             w2, h2);
  454     }
  455 
  456   input->next_update_time += 1.0;
  457 }
  458 
  459 
  460 
  461 static void
  462 analogtv_save_frame (struct state *st, const char *outfile, 
  463                      unsigned long frame)
  464 {
  465   char *pngfile = malloc (strlen (st->framefile_fmt) + 40);
  466   FILE *f;
  467 
  468   sprintf (pngfile, st->framefile_fmt, (int) frame);
  469   f = fopen (pngfile, "wb");
  470   if (! f) {
  471     fprintf (stderr, "%s: unable to write %s\n", progname, pngfile);
  472     exit (1);
  473   }
  474 
  475 # ifdef HAVE_LIBPNG
  476   {
  477     png_structp png_ptr;
  478     png_infop info_ptr;
  479     png_bytep row;
  480     XImage *img = st->output_frame;
  481     int x, y;
  482 
  483     png_ptr = png_create_write_struct (PNG_LIBPNG_VER_STRING, 0, 0, 0);
  484     if (!png_ptr) abort();
  485     info_ptr = png_create_info_struct (png_ptr);
  486     if (!info_ptr) abort();
  487     if (setjmp (png_jmpbuf (png_ptr))) abort();
  488 
  489     png_init_io (png_ptr, f);
  490 
  491     png_set_IHDR (png_ptr, info_ptr, img->width, img->height, 8,
  492                   PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
  493                   PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
  494     png_write_info (png_ptr, info_ptr);
  495 
  496     row = (png_bytep) malloc (3 * img->width * sizeof(png_byte));
  497     if (!row) abort();
  498     for (y = 0 ; y < img->height ; y++) {
  499       for (x = 0 ; x < img->width ; x++) {
  500         unsigned long p = XGetPixel (img, x, y);
  501         row[x*3+0] = (p & 0x000000FFL);
  502         row[x*3+1] = (p & 0x0000FF00L) >> 8;
  503         row[x*3+2] = (p & 0x00FF0000L) >> 16;
  504       }
  505       png_write_row (png_ptr, row);
  506     }
  507 
  508     png_write_end (png_ptr, 0);
  509 
  510     png_free_data (png_ptr, info_ptr, PNG_FREE_ALL, -1);
  511     png_destroy_write_struct (&png_ptr, 0);
  512     free (row);
  513   }
  514 #else   /* ! HAVE_LIBPNG */
  515 # error libpng required
  516 # endif /* ! HAVE_LIBPNG */
  517 
  518   fclose (f);
  519   if (verbose_p > 1)
  520     fprintf (stderr, "%s: wrote %s\n", progname, pngfile);
  521   free (pngfile);
  522 }
  523 
  524 
  525 static void
  526 delete_tmp_files(void)
  527 {
  528   struct state *st = &global_state;
  529   char outfile[2048];
  530   int i;
  531   for (i = 0; i <= st->frames_written; i++)
  532     {
  533       sprintf (outfile, st->framefile_fmt, i);
  534       if (verbose_p > 2)
  535         fprintf (stderr, "%s: rm %s\n", progname, outfile);
  536       unlink (outfile);
  537     }
  538 }
  539 
  540 
  541 static RETSIGTYPE
  542 analogtv_signal (int sig)
  543 {
  544   signal (sig, SIG_DFL);
  545   delete_tmp_files();
  546   kill (getpid (), sig);
  547 }
  548 
  549 
  550 static char *
  551 quote (const char *s)
  552 {
  553   char *s2, *o;
  554   if (!s) return 0;
  555 
  556   s2 = malloc (strlen(s) * 2 + 2);
  557   o = s2;
  558   while (*s)
  559     {
  560       if (*s == '"' || *s == '\\')
  561         *o++ = '\\';
  562       *o++ = *s++;
  563     }
  564   *o = 0;
  565   return s2;
  566 }
  567 
  568 
  569 static void
  570 analogtv_write_mp4 (struct state *st, const char *outfile,
  571                     const char *audiofile,
  572                     unsigned long frames)
  573 {
  574   char cmd[1024];
  575   struct stat ss;
  576   char *qout   = quote (outfile);
  577   char *qaudio = quote (audiofile);
  578 
  579   sprintf (cmd,
  580            "ffmpeg"
  581            " -hide_banner"
  582            " -loglevel error"
  583            " -framerate 30"     /* rate of input: must be before -i */
  584            " -thread_queue_size 4096"
  585            " -i \"%s\""
  586            " -r 30",            /* rate of output: must be after -i */
  587            st->framefile_fmt);
  588   if (audiofile)
  589     sprintf (cmd + strlen(cmd),
  590              " -i \"%s\""
  591              " -map 0:v:0"
  592              " -map 1:a:0"
  593              " -acodec aac"
  594              " -b:a 96k"
  595              /* Truncate or pad audio to length of video */
  596              " -filter_complex '[1:0] apad' -shortest",
  597              qaudio);
  598   sprintf (cmd + strlen(cmd),
  599            " -c:v libx264"
  600            " -profile:v high"
  601            " -pix_fmt yuv420p"
  602            " -preset veryfast"
  603            " -crf 24"             /* 18 is very high; 24 is good enough */
  604            " '%s'"
  605            " </dev/null",
  606            qout);
  607 
  608   if (verbose_p > 1)
  609     fprintf (stderr, "%s: exec: %s\n", progname, cmd);
  610   unlink (outfile);
  611   system (cmd);
  612 
  613   delete_tmp_files();
  614 
  615   if (stat (outfile, &ss))
  616     {
  617       fprintf (stderr, "%s: %s was not created\n", progname, outfile);
  618       exit (1);
  619     }
  620 
  621   if (verbose_p)
  622     fprintf (stderr, "%s: wrote %s (%dx%d, %lu sec, %.0f MB)\n",
  623              progname, outfile, 
  624              st->output_frame->width, st->output_frame->height,
  625              frames/30,
  626              (float) ss.st_size / (1024*1024));
  627 }
  628 
  629 
  630 static void
  631 flip_ximage (XImage *ximage)
  632 {
  633   char *data2, *in, *out;
  634   int y;
  635 
  636   if (!ximage) return;
  637   data2 = malloc (ximage->bytes_per_line * ximage->height);
  638   if (!data2) abort();
  639   in = ximage->data;
  640   out = data2 + ximage->bytes_per_line * (ximage->height - 1);
  641   for (y = 0; y < ximage->height; y++)
  642     {
  643       memcpy (out, in, ximage->bytes_per_line);
  644       in  += ximage->bytes_per_line;
  645       out -= ximage->bytes_per_line;
  646     }
  647   free (ximage->data);
  648   ximage->data = data2;
  649 }
  650 
  651 
  652 static void
  653 analogtv_convert (const char **infiles, const char *outfile,
  654                   const char *audiofile, const char *logofile,
  655                   int duration, Bool powerp)
  656 {
  657   unsigned long start_time = time((time_t *)0);
  658   struct state *st = &global_state;
  659   Display *dpy = 0;
  660   Window window = 0;
  661   int i, n;
  662   unsigned long curticks = 0;
  663   time_t lastlog = time((time_t *)0);
  664   int frames_left = 0;
  665   int channel_changes = 0;
  666   int fps = 30;
  667   XImage **ximages;
  668   int singlep;
  669   int *stats;
  670 
  671   stats = (int *) calloc(N_CHANNELS, sizeof(*stats));
  672   ximages = calloc (MAX_STATIONS, sizeof(*ximages));
  673   i = 0;
  674   while (infiles[i])
  675     {
  676       ximages[i] = file_to_ximage (0, 0, infiles[i]);
  677       if (verbose_p > 1)
  678         fprintf (stderr, "%s: loaded %s %dx%d\n", 
  679                  progname, infiles[i], ximages[i]->width, ximages[i]->height);
  680       flip_ximage (ximages[i]);
  681       i++;
  682     }
  683 
  684   singlep = !infiles[1];
  685 
  686   if (singlep) powerp = 0;  /* #### These don't work together but should */
  687 
  688   memset (st, 0, sizeof(*st));
  689   st->dpy = dpy;
  690   st->window = window;
  691 
  692   st->output_frame = XCreateImage (dpy, 0, ximages[0]->depth,
  693                                    ximages[0]->format, 0,
  694                                    NULL,
  695                                    ximages[0]->width  & ~1, /* can't be odd */
  696                                    ximages[0]->height & ~1,
  697                                    ximages[0]->bitmap_pad, 0);
  698   st->output_frame->data = (char *)
  699     calloc (st->output_frame->height, st->output_frame->bytes_per_line);
  700 
  701   {
  702     char *s0, *slash, *dot;
  703     st->framefile_fmt = calloc (1, strlen(outfile) + 100);
  704 
  705     s0 = st->framefile_fmt;
  706     strcpy (st->framefile_fmt, outfile);
  707 
  708     slash = strrchr (st->framefile_fmt, '/');
  709     dot = strrchr (st->framefile_fmt, '.');
  710     if (dot && dot > slash) *dot = 0;
  711 
  712     /* Make tmp files be dotfiles */
  713     if (slash) {
  714       memmove (slash+1, slash, strlen(slash)+1);
  715       slash[1] = '.';
  716     } else {
  717       memmove (s0+1, s0, strlen(s0)+1);
  718       s0[0] = '.';
  719     }
  720 
  721     /* Can't have percents in the tmp file names */
  722     for (s0 = (slash ? slash : s0); *s0; s0++) {
  723       if (*s0 == '%') *s0 = '_';
  724     }
  725 
  726     sprintf (st->framefile_fmt + strlen(st->framefile_fmt),
  727              ".%08x.%%06d.png", (random() % 0xFFFFFFFF));
  728   }
  729 
  730   if (logofile) {
  731     int x, y;
  732     st->logo = file_to_ximage (0, 0, logofile);
  733     if (verbose_p)
  734       fprintf (stderr, "%s: loaded %s %dx%d\n", 
  735                progname, logofile, st->logo->width, st->logo->height);
  736     flip_ximage (st->logo);
  737     /* Pull the alpha out of the logo and make a separate mask ximage. */
  738     st->logo_mask = XCreateImage (dpy, 0, st->logo->depth, st->logo->format, 0,
  739                                   NULL, st->logo->width, st->logo->height,
  740                                   st->logo->bitmap_pad, 0);
  741     st->logo_mask->data = (char *)
  742     calloc (st->logo_mask->height, st->logo_mask->bytes_per_line);
  743 
  744     for (y = 0; y < st->logo->height; y++)
  745       for (x = 0; x < st->logo->width; x++) {
  746         unsigned long p = XGetPixel (st->logo, x, y);
  747         uint8_t a =                     (p & 0xFF000000L) >> 24;
  748         XPutPixel (st->logo,      x, y, (p & 0x00FFFFFFL));
  749         XPutPixel (st->logo_mask, x, y, (a ? 0x00FFFFFFL : 0));
  750       }
  751   }
  752 
  753 
  754   if (audiofile) {
  755     struct stat ss;
  756     if (stat (audiofile, &ss))
  757       {
  758         fprintf (stderr, "%s: %s does not exist\n", progname, audiofile);
  759         exit (1);
  760       }
  761   }
  762 
  763   /* Catch signals to delete tmp files before we start writing them. */
  764 
  765   signal (SIGHUP,  analogtv_signal);
  766   signal (SIGINT,  analogtv_signal);
  767   signal (SIGQUIT, analogtv_signal);
  768   signal (SIGILL,  analogtv_signal);
  769   signal (SIGTRAP, analogtv_signal);
  770 # ifdef SIGIOT
  771   signal (SIGIOT,  analogtv_signal);
  772 # endif
  773   signal (SIGABRT, analogtv_signal);
  774 # ifdef SIGEMT
  775   signal (SIGEMT,  analogtv_signal);
  776 # endif
  777   signal (SIGFPE,  analogtv_signal);
  778   signal (SIGBUS,  analogtv_signal);
  779   signal (SIGSEGV, analogtv_signal);
  780 # ifdef SIGSYS
  781   signal (SIGSYS,  analogtv_signal);
  782 # endif
  783   signal (SIGTERM, analogtv_signal);
  784 # ifdef SIGXCPU
  785   signal (SIGXCPU, analogtv_signal);
  786 # endif
  787 # ifdef SIGXFSZ
  788   signal (SIGXFSZ, analogtv_signal);
  789 # endif
  790 # ifdef SIGDANGER
  791   signal (SIGDANGER, analogtv_signal);
  792 # endif
  793 
  794   st->tv=analogtv_allocate(dpy, window);
  795 
  796   st->stations = (analogtv_input **)
  797     calloc (MAX_STATIONS, sizeof(*st->stations));
  798   while (st->n_stations < MAX_STATIONS) {
  799     analogtv_input *input=analogtv_input_allocate();
  800     st->stations[st->n_stations++]=input;
  801     input->client_data = st;
  802   }
  803 
  804   analogtv_set_defaults(st->tv, "");
  805   st->tv->need_clear=1;
  806 
  807   if (random()%4==0) {
  808     st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0;
  809   }
  810   if (1) {
  811     st->tv->color_control += frand(0.3) * RANDSIGN();
  812   }
  813   if (darkp) {
  814     if (random()%4==0) {
  815       st->tv->brightness_control += frand(0.15);
  816     }
  817     if (random()%4==0) {
  818       st->tv->contrast_control += frand(0.2) * RANDSIGN();
  819     }
  820   }
  821 
  822   st->chansettings = calloc (N_CHANNELS, sizeof (*st->chansettings));
  823   for (i=0; i<N_CHANNELS; i++) {
  824     st->chansettings[i].noise_level = 0.06;
  825     {
  826       int last_station=42;
  827       int stati;
  828       for (stati=0; stati<MAX_MULTICHAN; stati++) {
  829         analogtv_reception *rec=&st->chansettings[i].recs[stati];
  830         int station;
  831         while (1) {
  832           station=random()%st->n_stations;
  833           if (station!=last_station) break;
  834           if ((random()%10)==0) break;
  835         }
  836         last_station=station;
  837         rec->input = st->stations[station];
  838         rec->level = pow(frand(1.0), 3.0) * 2.0 + 0.05;
  839         rec->ofs=random()%ANALOGTV_SIGNAL_LEN;
  840         if (random()%3) {
  841           rec->multipath = frand(1.0);
  842         } else {
  843           rec->multipath=0.0;
  844         }
  845         if (stati) {
  846           /* We only set a frequency error for ghosting stations,
  847              because it doesn't matter otherwise */
  848           rec->freqerr = (frand(2.0)-1.0) * 3.0;
  849         }
  850 
  851         if (rec->level > 0.3) break;
  852         if (random()%4) break;
  853       }
  854     }
  855   }
  856 
  857   st->curinputi=0;
  858   st->cs = &st->chansettings[st->curinputi];
  859 
  860   if (singlep)
  861     /* First channel (initial unadulterated image) stays for this long */
  862     frames_left = fps * (2 + frand(1.5));
  863 
  864   st->tv->powerup=0.0;
  865 
  866   /* load_station_images() */
  867 
  868   n = 0;
  869   for (i = 0; i < MAX_STATIONS; i++) {
  870     analogtv_input *input = st->stations[i];
  871 
  872     if (i == 1) {   /* station 0 is the unadulterated image.
  873                        station 1 is colorbars. */
  874       input->updater = update_smpte_colorbars;
  875     } else {
  876       XImage *ximage = ximages[n++];
  877       if (!ximage) {
  878         n = 0;
  879         ximage = ximages[n++];
  880       }
  881 
  882       {
  883       int w = ximage->width  * 0.815;  /* underscan */
  884       int h = ximage->height * 0.970;
  885       int x = (ximage->width - w) / 2;
  886       int y = (ximage->height - h) / 2;
  887       analogtv_input *input = st->stations[i];
  888       analogtv_setup_sync(input, 1, (random()%20)==0);
  889       analogtv_load_ximage (st->tv, input, ximage, 0, x, y, w, h);
  890       }
  891     }
  892   }
  893 
  894 
  895   /* xanalogtv_draw() */
  896 
  897   while (1) {
  898     const analogtv_reception *recs[MAX_MULTICHAN];
  899     unsigned rec_count = 0;
  900     double curtime=curticks*0.001;
  901 
  902     frames_left--;
  903     if (frames_left <= 0) {
  904 
  905       channel_changes++;
  906 
  907       if (singlep && channel_changes == 1) {
  908         /* Second channel has short duration, 0.25 to 0.75 sec. */
  909         frames_left = fps * (0.25 + frand(0.5));
  910       } else if (singlep) {
  911         /* 0.5 - 2.0 sec (was 0.5 - 3.0 sec) */
  912         frames_left = fps * (0.5 + frand(1.5));
  913       } else {
  914         /* 1 - 7 sec */
  915         frames_left = fps * (1 + frand(6));
  916       }
  917 
  918       if (singlep && channel_changes == 2) {
  919         /* Always use the unadulterated image for the third channel:
  920            So the effect is, plain, brief blip, plain, then random. */
  921         st->curinputi = 0;
  922         frames_left += fps * (0.1 + frand(0.5));
  923 
  924       } else if (singlep && st->curinputi != 0 && ((random() % 100) < 75)) {
  925         /* Use the unadulterated image 75% of the time (was 33%) */
  926         st->curinputi = 0;
  927       } else {
  928         /* Otherwise random */
  929         st->curinputi = 1 + (random() % (N_CHANNELS - 1));
  930       }
  931 
  932       stats[st->curinputi]++;
  933       st->cs = &st->chansettings[st->curinputi];
  934       /* Set channel change noise flag */
  935       st->tv->channel_change_cycles=200000;
  936 
  937       if (verbose_p > 1)
  938         fprintf (stderr, "%s: %.1f: channel %d\n",
  939                  progname, curticks/1000.0, st->curinputi);
  940 
  941       /* Turn the knobs every now and then */
  942       if (! (random() % 5)) {
  943         if (random()%4==0) {
  944           st->tv->tint_control += pow(frand(2.0)-1.0, 7) * 180.0 * RANDSIGN();
  945         }
  946         if (1) {
  947           st->tv->color_control += frand(0.3) * RANDSIGN();
  948         }
  949         if (darkp) {
  950           if (random()%4==0) {
  951             st->tv->brightness_control += frand(0.15);
  952           }
  953           if (random()%4==0) {
  954             st->tv->contrast_control += frand(0.2) * RANDSIGN();
  955           }
  956         }
  957       }
  958     }
  959 
  960     for (i=0; i<MAX_MULTICHAN; i++) {
  961       analogtv_reception *rec = &st->cs->recs[i];
  962       analogtv_input *inp=rec->input;
  963       if (!inp) continue;
  964 
  965       if (inp->updater) {
  966         inp->next_update_time = curtime;
  967         (inp->updater)(inp);
  968       }
  969       rec->ofs += rec->freqerr;
  970     }
  971 
  972     st->tv->powerup=(powerp ? curtime : 9999);
  973 
  974     if (st->curinputi == 0 && singlep) {
  975       XPutImage (dpy, 0, 0, ximages[0], 0, 0, 0, 0,
  976                  ximages[0]->width, ximages[0]->height);
  977     } else {
  978       for (i=0; i<MAX_MULTICHAN; i++) {
  979         analogtv_reception *rec = &st->cs->recs[i];
  980         if (rec->input) {
  981           analogtv_reception_update(rec);
  982           recs[rec_count] = rec;
  983           ++rec_count;
  984         }
  985       }
  986       analogtv_draw (st->tv, st->cs->noise_level, recs, rec_count);
  987     }
  988 
  989     analogtv_save_frame (st, outfile, st->frames_written);
  990 
  991     if (curtime >= duration) break;
  992 
  993     curticks += 1000/fps;
  994     st->frames_written++;
  995 
  996     if (verbose_p) {
  997       unsigned long now = time((time_t *)0);
  998       if (now > (verbose_p == 1 ? lastlog : lastlog + 10)) {
  999         unsigned long elapsed = now - start_time;
 1000         double ratio = curtime / (double) duration;
 1001         int remaining = (ratio ? (elapsed / ratio) - elapsed : 0);
 1002         int pct = 100 * ratio;
 1003         int cols = 47;
 1004         char dots[80];
 1005         int ii;
 1006         for (ii = 0; ii < cols * ratio; ii++)
 1007           dots[ii] = '.';
 1008         dots[ii] = 0;
 1009         fprintf (stderr, "%s%s: %s %2d%%, %d:%02d:%02d ETA%s",
 1010                  (verbose_p == 1 ? "\r" : ""),
 1011                  progname,
 1012                  dots, pct, 
 1013                  (remaining/60/60),
 1014                  (remaining/60)%60,
 1015                  remaining%60,
 1016                  (verbose_p == 1 ? "" : "\n"));
 1017         lastlog = now;
 1018       }
 1019     }
 1020   }
 1021 
 1022   if (verbose_p == 1) fprintf(stderr, "\n");
 1023 
 1024   if (verbose_p > 1) {
 1025     if (channel_changes == 0) channel_changes++;
 1026     fprintf(stderr, "%s: channels shown: %d\n", progname, channel_changes);
 1027     for (i = 0; i < N_CHANNELS; i++)
 1028       fprintf(stderr, "%s:   %2d: %3d%%\n", progname,
 1029               i+1, stats[i] * 100 / channel_changes);
 1030   }
 1031 
 1032   free (stats);
 1033   analogtv_write_mp4 (st, outfile, audiofile, st->frames_written);
 1034 }
 1035 
 1036 
 1037 static void
 1038 usage(const char *err)
 1039 {
 1040   if (err) fprintf (stderr, "%s: %s unknown\n", progname, err);
 1041   fprintf (stderr, "usage: %s [--verbose] [--duration secs]"
 1042            " [--audio mp3-file] [--no-powerup] infile.png ... outfile.mp4\n",
 1043            progname);
 1044   exit (1);
 1045 }
 1046 
 1047 int
 1048 main (int argc, char **argv)
 1049 {
 1050   int i;
 1051   const char *infiles[1000];
 1052   const char *outfile = 0;
 1053   int duration = 30;
 1054   Bool powerp = False;
 1055   char *audio = 0;
 1056   char *logo = 0;
 1057   int nfiles = 0;
 1058 
 1059   char *s = strrchr (argv[0], '/');
 1060   progname = s ? s+1 : argv[0];
 1061   progclass = progname;
 1062 
 1063   memset (infiles, 0, sizeof(infiles));
 1064 
 1065   for (i = 1; i < argc; i++)
 1066     {
 1067       if (argv[i][0] == '-' && argv[i][1] == '-')
 1068         argv[i]++;
 1069        if (!strcmp(argv[i], "-v") ||
 1070            !strcmp(argv[i], "-verbose"))
 1071         verbose_p++;
 1072        else if (!strcmp(argv[i], "-vv")) verbose_p += 2;
 1073        else if (!strcmp(argv[i], "-vvv")) verbose_p += 3;
 1074        else if (!strcmp(argv[i], "-vvvv")) verbose_p += 4;
 1075        else if (!strcmp(argv[i], "-vvvvv")) verbose_p += 5;
 1076        else if (!strcmp(argv[i], "-duration") && argv[i+1])
 1077          {
 1078            char dummy;
 1079            i++;
 1080            if (1 != sscanf (argv[i], " %d %c", &duration, &dummy))
 1081              usage(argv[i]);
 1082          }
 1083        else if (!strcmp(argv[i], "-audio") && argv[i+1])
 1084          audio = argv[++i];
 1085        else if (!strcmp(argv[i], "-logo") && argv[i+1])
 1086          logo = argv[++i];
 1087        else if (!strcmp(argv[i], "-powerup") ||
 1088                 !strcmp(argv[i], "-power"))
 1089          powerp = True;
 1090        else if (!strcmp(argv[i], "-no-powerup") ||
 1091                 !strcmp(argv[i], "-no-power"))
 1092          powerp = False;
 1093       else if (argv[i][0] == '-')
 1094         usage(argv[i]);
 1095       else if (nfiles >= countof(infiles)-1)
 1096         usage("too many files");
 1097       else
 1098         infiles[nfiles++] = argv[i];
 1099     }
 1100 
 1101   if (nfiles < 2)
 1102     usage("");
 1103 
 1104   outfile = infiles[nfiles-1];
 1105   infiles[--nfiles] = 0;
 1106 
 1107   /* stations should be a multiple of files, but >= 6.
 1108      channels should be double that. */
 1109   MAX_STATIONS = 0;
 1110   while (MAX_STATIONS < 6)
 1111     MAX_STATIONS += nfiles;
 1112   N_CHANNELS = MAX_STATIONS * 2;
 1113 
 1114   darkp = (nfiles == 1);
 1115 
 1116 # undef ya_rand_init
 1117   ya_rand_init (0);
 1118   analogtv_convert (infiles, outfile, audio, logo, duration, powerp);
 1119   exit (0);
 1120 }