/* 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);
}