]> git.hungrycats.org Git - xscreensaver/blobdiff - hacks/recanim.c
From https://www.jwz.org/xscreensaver/xscreensaver-6.09.tar.gz
[xscreensaver] / hacks / recanim.c
index feeb91335ca6e2a9e569fe1b817bff00b16dd74c..7800b5d48b0675f95b34334eae341c5ee6145aa9 100644 (file)
@@ -1,4 +1,4 @@
-/* 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)
 {
@@ -72,40 +143,23 @@ 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 */
@@ -113,15 +167,43 @@ screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
   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;
 }
@@ -146,12 +228,13 @@ fade_frame (record_anim_state *st, unsigned char *data, double ratio)
 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.
@@ -163,29 +246,33 @@ screenhack_record_anim (record_anim_state *st)
   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
@@ -198,72 +285,86 @@ screenhack_record_anim (record_anim_state *st)
   /* 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;
 }
 
 
@@ -273,18 +374,11 @@ screenhack_record_anim_free (record_anim_state *st)
 # 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);
@@ -294,55 +388,20 @@ screenhack_record_anim_free (record_anim_state *st)
   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);
 }