From http://www.jwz.org/xscreensaver/xscreensaver-5.31.tar.gz
[xscreensaver] / hacks / recanim.c
1 /* recanim, Copyright (c) 2014 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_COCOA
19 #  include "jwxyz.h"
20 # else /* !HAVE_COCOA -- real Xlib */
21 #  include <GL/glx.h>
22 #  include <GL/glu.h>
23 # endif /* !HAVE_COCOA */
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 # ifdef USE_GL
52   char *data, *data2;
53 # else  /* !USE_GL */
54   XImage *img;
55   Pixmap p;
56   GC gc;
57 # endif /* !USE_GL */
58 };
59
60 record_anim_state *
61 screenhack_record_anim_init (Screen *screen, Window window, int target_frames)
62 {
63   Display *dpy = DisplayOfScreen (screen);
64   record_anim_state *st;
65
66 # ifndef USE_GL
67   XGCValues gcv;
68 # endif /* !USE_GL */
69
70   if (target_frames <= 0) return 0;
71
72   st = (record_anim_state *) calloc (1, sizeof(*st));
73
74   st->screen = screen;
75   st->window = window;
76   st->target_frames = target_frames;
77   st->frame_count = 0;
78
79 # ifdef HAVE_GDK_PIXBUF
80   {
81     Window root;
82     int x, y;
83     unsigned int w, h, d, bw;
84     XGetGeometry (dpy, window, &root, &x, &y, &w, &h, &bw, &d);
85     gdk_pixbuf_xlib_init_with_depth (dpy, screen_number (screen), d);
86
87 #  ifdef HAVE_GTK2
88 #   if !GLIB_CHECK_VERSION(2, 36 ,0)
89     g_type_init();
90 #   endif
91 #  else  /* !HAVE_GTK2 */
92     xlib_rgb_init (dpy, screen);
93 #  endif /* !HAVE_GTK2 */
94   }
95 # else  /* !HAVE_GDK_PIXBUF */
96 #  error GDK_PIXBUF is required
97 # endif /* !HAVE_GDK_PIXBUF */
98
99   XGetWindowAttributes (dpy, st->window, &st->xgwa);
100
101 # ifdef USE_GL
102
103   st->data  = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
104   st->data2 = (char *) calloc (st->xgwa.width, st->xgwa.height * 3);
105
106 # else    /* !USE_GL */
107
108   st->gc = XCreateGC (dpy, st->window, 0, &gcv);
109   st->p = XCreatePixmap (dpy, st->window,
110                          st->xgwa.width, st->xgwa.height, st->xgwa.depth);
111   st->img = XCreateImage (dpy, st->xgwa.visual, st->xgwa.depth, ZPixmap,
112                           0, 0, st->xgwa.width, st->xgwa.height, 8, 0);
113   st->img->data = (char *) calloc (st->img->height, st->img->bytes_per_line);
114 # endif /* !USE_GL */
115
116
117 # ifndef HAVE_COCOA
118   XFetchName (dpy, st->window, &st->title);
119 # endif /* !HAVE_COCOA */
120
121   return st;
122 }
123
124
125 void
126 screenhack_record_anim (record_anim_state *st)
127 {
128   int bytes_per_line = st->xgwa.width * 3;
129
130 # ifndef USE_GL
131
132   Display *dpy = DisplayOfScreen (st->screen);
133   char *data = (char *) st->img->data;
134
135   /* Under XQuartz we can't just do XGetImage on the Window, we have to
136      go through an intermediate Pixmap first.  I don't understand why.
137      Also, the fucking resize handle shows up as black.  God dammit.
138      A workaround for that is to temporarily remove /opt/X11/bin/quartz-wm
139    */
140   XCopyArea (dpy, st->window, st->p, st->gc, 0, 0,
141              st->xgwa.width, st->xgwa.height, 0, 0);
142   XGetSubImage (dpy, st->p, 0, 0, st->xgwa.width, st->xgwa.height,
143                 ~0L, ZPixmap, st->img, 0, 0);
144
145   /* Convert BGRA to RGB */
146   {
147     const char *in = st->img->data;
148     char *out = st->img->data;
149     int x, y;
150     int w = st->img->width;
151     int h = st->img->height;
152     for (y = 0; y < h; y++)
153       {
154         const char *in2 = in;
155         for (x = 0; x < w; x++)
156           {
157             *out++ = in2[2];
158             *out++ = in2[1];
159             *out++ = in2[0];
160             in2 += 4;
161           }
162         in += st->img->bytes_per_line;
163       }
164   }
165 # else  /* USE_GL */
166
167   char *data = st->data2;
168   int y;
169
170 # ifdef HAVE_JWZGLES
171 #  undef glReadPixels /* Kludge -- unimplemented in the GLES compat layer */
172 # endif
173
174   /* First OpenGL frame tends to be random data like a shot of my desktop,
175      since it is the front buffer when we were drawing in the back buffer.
176      Leave it black. */
177   /* glDrawBuffer (GL_BACK); */
178   if (st->frame_count != 0)
179     glReadPixels (0, 0, st->xgwa.width, st->xgwa.height,
180                   GL_RGB, GL_UNSIGNED_BYTE, st->data);
181
182   /* Flip vertically */
183   for (y = 0; y < st->xgwa.height; y++)
184     memcpy (data      + bytes_per_line * y,
185             st->data  + bytes_per_line * (st->xgwa.height - y - 1),
186             bytes_per_line);
187
188 # endif /* USE_GL */
189
190
191 # ifdef HAVE_GDK_PIXBUF
192   {
193     const char *type = "png";
194     char fn[1024];
195     GError *error = 0;
196     GdkPixbuf *pixbuf;
197
198     pixbuf = gdk_pixbuf_new_from_data ((guchar *) data,
199                                        GDK_COLORSPACE_RGB, False, /* alpha */
200                                        8, /* bits per sample */
201                                        st->xgwa.width, st->xgwa.height,
202                                        bytes_per_line,
203                                        0, 0);
204
205     sprintf (fn, "%s-%06d.%s", progname, st->frame_count, type);
206     gdk_pixbuf_save (pixbuf, fn, type, &error, NULL);
207
208     if (error)
209       {
210         fprintf (stderr, "%s: %s: %s\n", progname, fn, error->message);
211         exit (1);
212       }
213
214     g_object_unref (pixbuf);
215   }
216 # else  /* !HAVE_GDK_PIXBUF */
217 #  error GDK_PIXBUF is required
218 # endif /* !HAVE_GDK_PIXBUF */
219
220 # ifndef HAVE_COCOA
221   {  /* Put percent done in window title */
222     int pct = 100 * (st->frame_count + 1) / st->target_frames;
223     if (pct != st->pct && st->title)
224       {
225         Display *dpy = DisplayOfScreen (st->screen);
226         char *t2 = (char *) malloc (strlen(st->title) + 20);
227         sprintf (t2, "%s: %d%%", st->title, pct);
228         XStoreName (dpy, st->window, t2);
229         XSync (dpy, 0);
230         free (t2);
231         st->pct = pct;
232       }
233   }
234 # endif /* !HAVE_COCOA */
235
236   if (++st->frame_count >= st->target_frames)
237     screenhack_record_anim_free (st);
238 }
239
240
241 void
242 screenhack_record_anim_free (record_anim_state *st)
243 {
244 # ifndef USE_GL
245   Display *dpy = DisplayOfScreen (st->screen);
246 # endif /* !USE_GL */
247
248   struct stat s;
249   int i;
250   const char *type = "png";
251   char cmd[1024];
252   char fn[1024];
253   const char *soundtrack = 0;
254
255   fprintf (stderr, "%s: wrote %d frames\n", progname, st->frame_count);
256
257 # ifdef USE_GL
258   free (st->data);
259   free (st->data2);
260 # else  /* !USE_GL */
261   free (st->img->data);
262   st->img->data = 0;
263   XDestroyImage (st->img);
264   XFreeGC (dpy, st->gc);
265   XFreePixmap (dpy, st->p);
266 # endif /* !USE_GL */
267
268   sprintf (fn, "%s.%s", progname, "mp4");
269   unlink (fn);
270
271 # define ST "images/drives-200.mp3"
272   soundtrack = ST;
273   if (stat (soundtrack, &s)) soundtrack = 0;
274   if (! soundtrack) soundtrack = "../" ST;
275   if (stat (soundtrack, &s)) soundtrack = 0;
276   if (! soundtrack) soundtrack = "../../" ST;
277   if (stat (soundtrack, &s)) soundtrack = 0;
278
279   sprintf (cmd,
280            "ffmpeg"
281            " -r 30"   /* Must be before -i */
282            " -framerate 30"
283            " -i '%s-%%06d.%s'",
284            progname, type);
285   if (soundtrack)
286     sprintf (cmd + strlen(cmd),
287              " -i '%s' -map 0:v:0 -map 1:a:0 -acodec libfaac",
288              soundtrack);
289   sprintf (cmd + strlen(cmd),
290            " -c:v libx264"
291            " -profile:v high"
292            " -crf 18"
293            " -pix_fmt yuv420p"
294            " '%s'"
295            " 2>&-",
296            fn);
297   fprintf (stderr, "%s: exec: %s\n", progname, cmd);
298   system (cmd);
299
300   if (stat (fn, &s))
301     {
302       fprintf (stderr, "%s: %s was not created\n", progname, fn);
303       exit (1);
304     }
305
306   fprintf (stderr, "%s: wrote %s (%.1f MB)\n", progname, fn,
307            s.st_size / (float) (1024 * 1024));
308
309   for (i = 0; i < st->frame_count; i++)
310     {
311       sprintf (fn, "%s-%06d.%s", progname, i, type);
312       unlink (fn);
313     }
314
315   if (st->title)
316     free (st->title);
317   free (st);
318   exit (0);
319 }