From http://www.jwz.org/xscreensaver/xscreensaver-5.35.tar.gz
[xscreensaver] / hacks / recanim.c
1 /* recanim, Copyright (c) 2014-2015 Jamie Zawinski <jwz@jwz.org>
2  * Record animation frames of the running screenhack.
3  *
4  * Permission to use, copy, modify, distribute, and sell this software and its
5  * documentation for any purpose is hereby granted without fee, provided that
6  * the above copyright notice appear in all copies and that both that
7  * copyright notice and this permission notice appear in supporting
8  * documentation.  No representations are made about the suitability of this
9  * software for any purpose.  It is provided "as is" without express or 
10  * implied warranty.
11  */
12
13 #ifdef HAVE_CONFIG_H
14 # include "config.h"
15 #endif /* HAVE_CONFIG_H */
16
17 #ifdef USE_GL
18 # ifdef HAVE_JWXYZ
19 #  include "jwxyz.h"
20 # else /* !HAVE_JWXYZ -- real Xlib */
21 #  include <GL/glx.h>
22 #  include <GL/glu.h>
23 # endif /* !HAVE_JWXYZ */
24 # ifdef HAVE_JWZGLES
25 #  include "jwzgles.h"
26 # endif /* HAVE_JWZGLES */
27 #endif /* USE_GL */
28
29 #ifdef HAVE_GDK_PIXBUF
30 # ifdef HAVE_GTK2
31 #  include <gdk-pixbuf-xlib/gdk-pixbuf-xlib.h>
32 # else  /* !HAVE_GTK2 */
33 #  include <gdk-pixbuf/gdk-pixbuf-xlib.h>
34 # endif /* !HAVE_GTK2 */
35 #endif /* HAVE_GDK_PIXBUF */
36
37 #include <sys/stat.h>
38 #include <sys/types.h>
39
40 #include "screenhackI.h"
41 #include "recanim.h"
42
43 struct record_anim_state {
44   Screen *screen;
45   Window window;
46   int frame_count;
47   int target_frames;
48   XWindowAttributes xgwa;
49   char *title;
50   int pct;
51   int fade_frames;
52 # ifdef USE_GL
53   char *data, *data2;
54 # else  /* !USE_GL */
55   XImage *img;
56   Pixmap p;
57   GC gc;
58 # endif /* !USE_GL */
59 };
60
61 record_anim_state *
62 screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
63 {
64   Display *dpy = DisplayOfScreen (screen);
65   record_anim_state *st;
66
67 # ifndef USE_GL
68   XGCValues gcv;
69 # endif /* !USE_GL */
70
71   if (target_frames <= 0) return 0;
72
73   st = (record_anim_state *) calloc (1, sizeof(*st));
74
75   st->screen = screen;
76   st->window = window;
77   st->target_frames = target_frames;
78   st->frame_count = 0;
79   st->fade_frames = 30 * 1.5;
80
81   if (st->fade_frames >= (st->target_frames / 2) - 30)
82     st->fade_frames = 0;
83
84 # ifdef HAVE_GDK_PIXBUF
85   {
86     Window root;
87     int x, y;
88     unsigned int w, h, d, bw;
89     XGetGeometry (dpy, window, &root, &x, &y, &w, &h, &bw, &d);
90     gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), d);
91
92 #  ifdef HAVE_GTK2
93 #   if !GLIB_CHECK_VERSION(2, 36 ,0)
94     g_type_init();
95 #   endif
96 #  else  /* !HAVE_GTK2 */
97     xlib_rgb_init (dpy, screen);
98 #  endif /* !HAVE_GTK2 */
99   }
100 # else  /* !HAVE_GDK_PIXBUF */
101 #  error GDK_PIXBUF is required
102 # endif /* !HAVE_GDK_PIXBUF */
103
104   XGetWindowAttributes (dpy, st->window, &st->xgwa);
105
106 # ifdef USE_GL
107
108   st->data  = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
109   st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
110
111 # else    /* !USE_GL */
112
113   st->gc = XCreateGC (dpy, st->window, 0, &gcv);
114   st->p = XCreatePixmap (dpy, st->window,
115                          st->xgwa.width, st->xgwa.height, st->xgwa.depth);
116   st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth, ZPixmap,
117                           0, 0, st->xgwa.width, st->xgwa.height, 8, 0);
118   st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line);
119 # endif /* !USE_GL */
120
121
122 # ifndef HAVE_JWXYZ
123   XFetchName (dpy, st->window, &st->title);
124 # endif /* !HAVE_JWXYZ */
125
126   return st;
127 }
128
129
130 /* Fade to black. Assumes data is 3-byte packed.
131  */
132 static void
133 fade_frame (record_anim_state *st, unsigned char *data, double ratio)
134 {
135   int x, y, i;
136   int w = st->xgwa.width;
137   int h = st->xgwa.height;
138   unsigned char *s = data;
139   for (y = 0; y < h; y++)
140     for (x = 0; x < w; x++)
141       for (i = 0; i < 3; i++)
142         *s++ *= ratio;
143 }
144
145
146 void
147 screenhack_record_anim (record_anim_state *st)
148 {
149   int bytes_per_line = st->xgwa.width * 3;
150
151 # ifndef USE_GL
152
153   Display *dpy = DisplayOfScreen (st->screen);
154   char *data = (char *) st->img->data;
155
156   /* Under XQuartz we can't just do XGetImage on the Window, we have to
157      go through an intermediate Pixmap first.  I don't understand why.
158      Also, the fucking resize handle shows up as black.  God dammit.
159      A workaround for that is to temporarily remove /opt/X11/bin/quartz-wm
160    */
161   XCopyArea (dpy, st->window, st->p, st->gc, 0, 0,
162              st->xgwa.width, st->xgwa.height, 0, 0);
163   XGetSubImage (dpy, st->p, 0, 0, st->xgwa.width, st->xgwa.height,
164                 ~0L, ZPixmap, st->img, 0, 0);
165
166   /* Convert BGRA to RGB */
167   {
168     const char *in = st->img->data;
169     char *out = st->img->data;
170     int x, y;
171     int w = st->img->width;
172     int h = st->img->height;
173     for (y = 0; y < h; y++)
174       {
175         const char *in2 = in;
176         for (x = 0; x < w; x++)
177           {
178             *out++ = in2[2];
179             *out++ = in2[1];
180             *out++ = in2[0];
181             in2 += 4;
182           }
183         in += st->img->bytes_per_line;
184       }
185   }
186 # else  /* USE_GL */
187
188   char *data = st->data2;
189   int y;
190
191 # ifdef HAVE_JWZGLES
192 #  undef glReadPixels /* Kludge -- unimplemented in the GLES compat layer */
193 # endif
194
195   /* First OpenGL frame tends to be random data like a shot of my desktop,
196      since it is the front buffer when we were drawing in the back buffer.
197      Leave it black. */
198   /* glDrawBuffer (GL_BACK); */
199   if (st->frame_count != 0)
200     glReadPixels (0, 0, st->xgwa.width, st->xgwa.height,
201                   GL_RGB, GL_UNSIGNED_BYTE, st->data);
202
203   /* Flip vertically */
204   for (y = 0; y < st->xgwa.height; y++)
205     memcpy (data      + bytes_per_line * y,
206             st->data  + bytes_per_line * (st->xgwa.height - y - 1),
207             bytes_per_line);
208
209 # endif /* USE_GL */
210
211
212   if (st->frame_count < st->fade_frames)
213     fade_frame (st, (unsigned char *) data,
214                 (double) st->frame_count / st->fade_frames);
215   else if (st->frame_count >= st->target_frames - st->fade_frames)
216     fade_frame (st, (unsigned char *) data,
217                 (double) (st->target_frames - st->frame_count - 1) /
218                 st->fade_frames);
219
220 # ifdef HAVE_GDK_PIXBUF
221   {
222     const char *type = "png";
223     char fn[1024];
224     GError *error = 0;
225     GdkPixbuf *pixbuf;
226
227     pixbuf = gdk_pixbuf_new_from_data ((guchar *) data,
228                                        GDK_COLORSPACE_RGB, False, /* alpha */
229                                        8, /* bits per sample */
230                                        st->xgwa.width, st->xgwa.height,
231                                        bytes_per_line,
232                                        0, 0);
233
234     sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type);
235     gdk_pixbuf_save (pixbuf, fn, type, &error, NULL);
236
237     if (error)
238       {
239         fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message);
240         exit (1);
241       }
242
243     g_object_unref (pixbuf);
244   }
245 # else  /* !HAVE_GDK_PIXBUF */
246 #  error GDK_PIXBUF is required
247 # endif /* !HAVE_GDK_PIXBUF */
248
249 # ifndef HAVE_JWXYZ
250   {  /* Put percent done in window title */
251     int pct = 100 * (st->frame_count + 1) / st->target_frames;
252     if (pct != st->pct && st->title)
253       {
254         Display *dpy = DisplayOfScreen (st->screen);
255         char *t2 = (char *) malloc (strlen(st->title) + 20);
256         sprintf (t2, "%s: %d%%", st->title, pct);
257         XStoreName (dpy, st->window, t2);
258         XSync (dpy, 0);
259         free (t2);
260         st->pct = pct;
261       }
262   }
263 # endif /* !HAVE_JWXYZ */
264
265   if (++st->frame_count >= st->target_frames)
266     screenhack_record_anim_free (st);
267 }
268
269
270 void
271 screenhack_record_anim_free (record_anim_state *st)
272 {
273 # ifndef USE_GL
274   Display *dpy = DisplayOfScreen (st->screen);
275 # endif /* !USE_GL */
276
277   struct stat s;
278   int i;
279   const char *type = "png";
280   char cmd[1024];
281   char fn[1024];
282   const char *soundtrack = 0;
283
284   fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count);
285
286 # ifdef USE_GL
287   free (st->data);
288   free (st->data2);
289 # else  /* !USE_GL */
290   free (st->img->data);
291   st->img->data = 0;
292   XDestroyImage (st->img);
293   XFreeGC (dpy, st->gc);
294   XFreePixmap (dpy, st->p);
295 # endif /* !USE_GL */
296
297   sprintf (fn, "%s.%s", progname, "mp4");
298   unlink (fn);
299
300 # define ST "images/drives-200.mp3"
301   soundtrack = ST;
302   if (stat (soundtrack, &s)) soundtrack = 0;
303   if (! soundtrack) soundtrack = "../" ST;
304   if (stat (soundtrack, &s)) soundtrack = 0;
305   if (! soundtrack) soundtrack = "../../" ST;
306   if (stat (soundtrack, &s)) soundtrack = 0;
307
308   sprintf (cmd,
309            "ffmpeg"
310            " -framerate 30"     /* rate of input: must be before -i */
311            " -i '%s-%%06d.%s'"
312            " -r 30",            /* rate of output: must be after -i */
313            progname, type);
314   if (soundtrack)
315     sprintf (cmd + strlen(cmd),
316              " -i '%s' -map 0:v:0 -map 1:a:0 -acodec libfaac",
317              soundtrack);
318   sprintf (cmd + strlen(cmd),
319            " -c:v libx264"
320            " -profile:v high"
321            " -crf 18"
322            " -pix_fmt yuv420p"
323            " '%s'"
324            " 2>&-",
325            fn);
326   fprintf (stderr, "%s: exec: %s\n", progname, cmd);
327   system (cmd);
328
329   if (stat (fn, &s))
330     {
331       fprintf (stderr, "%s: %s was not created\n", progname, fn);
332       exit (1);
333     }
334
335   fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn,
336            s.st_size / (float) (1024 * 1024));
337
338   for (i = 0; i < st->frame_count; i++)
339     {
340       sprintf (fn, "%s-%06d.%s", progname, i, type);
341       unlink (fn);
342     }
343
344   if (st->title)
345     free (st->title);
346   free (st);
347   exit (0);
348 }