-/* recanim, Copyright (c) 2014-2015 Jamie Zawinski <jwz@jwz.org>
+/* recanim, Copyright © 2014-2023 Jamie Zawinski <jwz@jwz.org>
* Record animation frames of the running screenhack.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* implied warranty.
*/
-#ifdef HAVE_CONFIG_H
-# include "config.h"
-#endif /* HAVE_CONFIG_H */
+#include "screenhackI.h"
+#include "recanim.h"
+
+#ifndef HAVE_FFMPEG
+# error HAVE_FFMPEG is required
+#endif
+
+#include "ffmpeg-out.h"
+
+#if (__GNUC__ >= 4) /* Ignore useless warnings generated by gtk.h */
+# undef inline
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wstrict-prototypes"
+# pragma GCC diagnostic ignored "-Wlong-long"
+# pragma GCC diagnostic ignored "-Wvariadic-macros"
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+
+#if (__GNUC__ >= 4)
+# pragma GCC diagnostic pop
+#endif
#ifdef USE_GL
-# ifdef HAVE_COCOA
+# ifdef HAVE_JWXYZ
# include "jwxyz.h"
-# else /* !HAVE_COCOA -- real Xlib */
+# else /* !HAVE_JWXYZ -- real Xlib */
# include <GL/glx.h>
# include <GL/glu.h>
-# endif /* !HAVE_COCOA */
+# endif /* !HAVE_JWXYZ */
# ifdef HAVE_JWZGLES
# include "jwzgles.h"
# endif /* HAVE_JWZGLES */
#endif /* USE_GL */
-#ifdef HAVE_GDK_PIXBUF
-# ifdef HAVE_GTK2
-# include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
-# else /* !HAVE_GTK2 */
-# include <gdk-pixbuf/gdk-pixbuf-xlib.h>
-# endif /* !HAVE_GTK2 */
-#endif /* HAVE_GDK_PIXBUF */
-
#include <sys/stat.h>
#include <sys/types.h>
-#include "screenhackI.h"
-#include "recanim.h"
+#undef gettimeofday /* wrapped by recanim.h */
+#undef time
struct record_anim_state {
Screen *screen;
Window window;
int frame_count;
int target_frames;
+ int fps;
XWindowAttributes xgwa;
char *title;
- int pct;
+ int secs_elapsed;
int fade_frames;
+ double start_time;
+ XImage *img;
# ifdef USE_GL
- char *data, *data2;
+ char *data2;
# else /* !USE_GL */
- XImage *img;
Pixmap p;
GC gc;
# endif /* !USE_GL */
+
+ char *outfile;
+ ffmpeg_out_state *ffst;
};
+
+static double
+double_time (void)
+{
+ struct timeval now;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tzp;
+ gettimeofday(&now, &tzp);
+# else
+ gettimeofday(&now);
+# endif
+
+ return (now.tv_sec + ((double) now.tv_usec * 0.000001));
+}
+
+
+/* Some of the hacks set their timing based on the real-world wall clock,
+ so to make the animations record at a sensible speed, we need to slow
+ down that clock by discounting the time taken up by snapshotting and
+ saving the frame.
+ */
+static double recanim_time_warp = 0;
+
+void
+screenhack_record_anim_gettimeofday (struct timeval *tv
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ , struct timezone *tz
+# endif
+ )
+{
+ gettimeofday (tv
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ , tz
+# endif
+ );
+ tv->tv_sec -= (time_t) recanim_time_warp;
+ tv->tv_usec -= 1000000 * (recanim_time_warp - (time_t) recanim_time_warp);
+}
+
+time_t
+screenhack_record_anim_time (time_t *o)
+{
+ struct timeval tv;
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ struct timezone tz;
+# endif
+ screenhack_record_anim_gettimeofday (&tv
+# ifdef GETTIMEOFDAY_TWO_ARGS
+ , &tz
+# endif
+ );
+ if (o) *o = tv.tv_sec;
+ return tv.tv_sec;
+}
+
+
record_anim_state *
screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
{
st = (record_anim_state *) calloc (1, sizeof(*st));
+ st->fps = 30;
st->screen = screen;
st->window = window;
st->target_frames = target_frames;
+ st->start_time = double_time();
st->frame_count = 0;
- st->fade_frames = 30 * 1.5;
+ st->fade_frames = st->fps * 1.5;
- if (st->fade_frames >= (st->target_frames / 2) - 30)
+ if (st->fade_frames >= (st->target_frames / 2) - st->fps)
st->fade_frames = 0;
-# ifdef HAVE_GDK_PIXBUF
- {
- Window root;
- int x, y;
- unsigned int w, h, d, bw;
- XGetGeometry (dpy, window, &root, &x, &y, &w, &h, &bw, &d);
- gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), d);
-
-# ifdef HAVE_GTK2
-# if !GLIB_CHECK_VERSION(2, 36 ,0)
- g_type_init();
-# endif
-# else /* !HAVE_GTK2 */
- xlib_rgb_init (dpy, screen);
-# endif /* !HAVE_GTK2 */
- }
-# else /* !HAVE_GDK_PIXBUF */
-# error GDK_PIXBUF is required
-# endif /* !HAVE_GDK_PIXBUF */
-
XGetWindowAttributes (dpy, st->window, &st->xgwa);
# ifdef USE_GL
-
- st->data = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
+ st->img = XCreateImage (dpy, st->xgwa.visual, 24,
+ ZPixmap, 0, 0, st->xgwa.width, st->xgwa.height,
+ 32, 0);
st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
# else /* !USE_GL */
st->gc = XCreateGC (dpy, st->window, 0, &gcv);
st->p = XCreatePixmap (dpy, st->window,
st->xgwa.width, st->xgwa.height, st->xgwa.depth);
- st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth, ZPixmap,
- 0, 0, st->xgwa.width, st->xgwa.height, 8, 0);
- st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line);
+ st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth,
+ ZPixmap, 0, 0, st->xgwa.width, st->xgwa.height,
+ 8, 0);
# endif /* !USE_GL */
+ st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line);
-# ifndef HAVE_COCOA
+
+# ifndef HAVE_JWXYZ
XFetchName (dpy, st->window, &st->title);
-# endif /* !HAVE_COCOA */
+ {
+ char *s = strchr(st->title, ':');
+ if (s) *s = 0;
+ }
+# endif /* !HAVE_JWXYZ */
+
+ {
+ char fn[1024];
+ struct stat s;
+
+ const char *soundtrack = 0;
+# define ST "images/drives-200.mp3"
+ soundtrack = ST;
+ if (stat (soundtrack, &s)) soundtrack = 0;
+ if (! soundtrack) soundtrack = "../" ST;
+ if (stat (soundtrack, &s)) soundtrack = 0;
+ if (! soundtrack) soundtrack = "../../" ST;
+ if (stat (soundtrack, &s)) soundtrack = 0;
+
+ sprintf (fn, "%s.%s", progname, "mp4");
+ unlink (fn);
+
+ st->outfile = strdup (fn);
+ st->ffst = ffmpeg_out_init (st->outfile, soundtrack,
+ st->xgwa.width, st->xgwa.height,
+ 3, False);
+ }
return st;
}
void
screenhack_record_anim (record_anim_state *st)
{
- int bytes_per_line = st->xgwa.width * 3;
+ double start_time = double_time();
+ int obpl = st->img->bytes_per_line;
+ char *odata = st->img->data;
# ifndef USE_GL
Display *dpy = DisplayOfScreen (st->screen);
- char *data = (char *) st->img->data;
/* Under XQuartz we can't just do XGetImage on the Window, we have to
go through an intermediate Pixmap first. I don't understand why.
XGetSubImage (dpy, st->p, 0, 0, st->xgwa.width, st->xgwa.height,
~0L, ZPixmap, st->img, 0, 0);
- /* Convert BGRA to RGB */
- {
- const char *in = st->img->data;
- char *out = st->img->data;
- int x, y;
- int w = st->img->width;
- int h = st->img->height;
- for (y = 0; y < h; y++)
- {
- const char *in2 = in;
- for (x = 0; x < w; x++)
- {
- *out++ = in2[2];
- *out++ = in2[1];
- *out++ = in2[0];
- in2 += 4;
- }
- in += st->img->bytes_per_line;
- }
- }
+ /* Convert BGRA to BGR */
+ if (st->img->bytes_per_line == st->img->width * 4)
+ {
+ const char *in = st->img->data;
+ char *out = st->img->data;
+ int x, y;
+ int w = st->img->width;
+ int h = st->img->height;
+ for (y = 0; y < h; y++)
+ {
+ const char *in2 = in;
+ for (x = 0; x < w; x++)
+ {
+ *out++ = in2[0];
+ *out++ = in2[1];
+ *out++ = in2[2];
+ in2 += 4;
+ }
+ in += st->img->bytes_per_line;
+ }
+ st->img->bytes_per_line = w * 3;
+ }
+ else if (st->img->bytes_per_line != st->img->width * 3)
+ abort();
+
# else /* USE_GL */
- char *data = st->data2;
int y;
# ifdef HAVE_JWZGLES
/* glDrawBuffer (GL_BACK); */
if (st->frame_count != 0)
glReadPixels (0, 0, st->xgwa.width, st->xgwa.height,
- GL_RGB, GL_UNSIGNED_BYTE, st->data);
+ GL_BGR, GL_UNSIGNED_BYTE, st->img->data);
/* Flip vertically */
- for (y = 0; y < st->xgwa.height; y++)
- memcpy (data + bytes_per_line * y,
- st->data + bytes_per_line * (st->xgwa.height - y - 1),
- bytes_per_line);
+ {
+ int bpl = st->xgwa.width * 3;
+ for (y = 0; y < st->xgwa.height; y++)
+ memcpy (st->data2 + bpl * y,
+ st->img->data + bpl * (st->xgwa.height - y - 1),
+ bpl);
+ st->img->data = st->data2;
+ st->img->bytes_per_line = bpl;
+ }
# endif /* USE_GL */
-
if (st->frame_count < st->fade_frames)
- fade_frame (st, (unsigned char *) data,
+ fade_frame (st, (unsigned char *) st->img->data,
(double) st->frame_count / st->fade_frames);
else if (st->frame_count >= st->target_frames - st->fade_frames)
- fade_frame (st, (unsigned char *) data,
+ fade_frame (st, (unsigned char *) st->img->data,
(double) (st->target_frames - st->frame_count - 1) /
st->fade_frames);
-# ifdef HAVE_GDK_PIXBUF
- {
- const char *type = "png";
- char fn[1024];
- GError *error = 0;
- GdkPixbuf *pixbuf;
-
- pixbuf = gdk_pixbuf_new_from_data ((guchar *) data,
- GDK_COLORSPACE_RGB, False, /* alpha */
- 8, /* bits per sample */
- st->xgwa.width, st->xgwa.height,
- bytes_per_line,
- 0, 0);
-
- sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type);
- gdk_pixbuf_save (pixbuf, fn, type, &error, NULL);
-
- if (error)
- {
- fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message);
- exit (1);
- }
+ ffmpeg_out_add_frame (st->ffst, st->img);
+ st->img->bytes_per_line = obpl;
+ st->img->data = odata;
- g_object_unref (pixbuf);
- }
-# else /* !HAVE_GDK_PIXBUF */
-# error GDK_PIXBUF is required
-# endif /* !HAVE_GDK_PIXBUF */
-
-# ifndef HAVE_COCOA
- { /* Put percent done in window title */
- int pct = 100 * (st->frame_count + 1) / st->target_frames;
- if (pct != st->pct && st->title)
+# ifndef HAVE_JWXYZ /* Put percent done in window title */
+ {
+ double now = double_time();
+ double dur = st->target_frames / (double) st->fps;
+ double ratio = (st->frame_count + 1) / (double) st->target_frames;
+ double encoded = dur * ratio;
+ double elapsed = now - st->start_time;
+ double rate = encoded / elapsed;
+ double remain = (elapsed / ratio) - elapsed;
+
+ if (st->title && st->secs_elapsed != (int) elapsed)
{
Display *dpy = DisplayOfScreen (st->screen);
- char *t2 = (char *) malloc (strlen(st->title) + 20);
- sprintf (t2, "%s: %d%%", st->title, pct);
+ char *t2 = (char *) malloc (strlen(st->title) + 100);
+ sprintf (t2,
+ "%s: encoded"
+ " %d:%02d:%02d of"
+ " %d:%02d:%02d at"
+ " %d%% speed;"
+ " %d:%02d:%02d elapsed,"
+ " %d:%02d:%02d remaining",
+ st->title,
+
+ ((int) encoded) / (60*60),
+ (((int) encoded) / 60) % 60,
+ ((int) encoded) % 60,
+
+ ((int) dur) / (60*60),
+ (((int) dur) / 60) % 60,
+ ((int) dur) % 60,
+
+ (int) (100 * rate),
+
+ ((int) elapsed) / (60*60),
+ (((int) elapsed) / 60) % 60,
+ ((int) elapsed) % 60,
+
+ ((int) remain) / (60*60),
+ (((int) remain) / 60) % 60,
+ ((int) remain) % 60
+ );
XStoreName (dpy, st->window, t2);
XSync (dpy, 0);
free (t2);
- st->pct = pct;
+ st->secs_elapsed = elapsed;
}
}
-# endif /* !HAVE_COCOA */
+# endif /* !HAVE_JWXYZ */
if (++st->frame_count >= st->target_frames)
screenhack_record_anim_free (st);
+
+ recanim_time_warp += double_time() - start_time;
}
# ifndef USE_GL
Display *dpy = DisplayOfScreen (st->screen);
# endif /* !USE_GL */
-
struct stat s;
- int i;
- const char *type = "png";
- char cmd[1024];
- char fn[1024];
- const char *soundtrack = 0;
fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count);
# ifdef USE_GL
- free (st->data);
free (st->data2);
# else /* !USE_GL */
free (st->img->data);
XFreePixmap (dpy, st->p);
# endif /* !USE_GL */
- sprintf (fn, "%s.%s", progname, "mp4");
- unlink (fn);
-
-# define ST "images/drives-200.mp3"
- soundtrack = ST;
- if (stat (soundtrack, &s)) soundtrack = 0;
- if (! soundtrack) soundtrack = "../" ST;
- if (stat (soundtrack, &s)) soundtrack = 0;
- if (! soundtrack) soundtrack = "../../" ST;
- if (stat (soundtrack, &s)) soundtrack = 0;
-
- sprintf (cmd,
- "ffmpeg"
- " -framerate 30" /* rate of input: must be before -i */
- " -i '%s-%%06d.%s'"
- " -r 30", /* rate of output: must be after -i */
- progname, type);
- if (soundtrack)
- sprintf (cmd + strlen(cmd),
- " -i '%s' -map 0:v:0 -map 1:a:0 -acodec libfaac",
- soundtrack);
- sprintf (cmd + strlen(cmd),
- " -c:v libx264"
- " -profile:v high"
- " -crf 18"
- " -pix_fmt yuv420p"
- " '%s'"
- " 2>&-",
- fn);
- fprintf (stderr, "%s: exec: %s\n", progname, cmd);
- system (cmd);
-
- if (stat (fn, &s))
+ ffmpeg_out_close (st->ffst);
+
+ if (stat (st->outfile, &s))
{
- fprintf (stderr, "%s: %s was not created\n", progname, fn);
+ fprintf (stderr, "%s: %s was not created\n", progname, st->outfile);
exit (1);
}
- fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn,
+ fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, st->outfile,
s.st_size / (float) (1024 * 1024));
- for (i = 0; i < st->frame_count; i++)
- {
- sprintf (fn, "%s-%06d.%s", progname, i, type);
- unlink (fn);
- }
-
if (st->title)
free (st->title);
+ free (st->outfile);
free (st);
exit (0);
}