1 /*
2 * Copyright (C) 2003,2004 Bastien Nocera <hadess@hadess.net>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 *
18 * The Totem project hereby grant permission for non-gpl compatible GStreamer
19 * plugins to be used and distributed together with GStreamer and Totem. This
20 * permission are above and beyond the permissions granted by the GPL license
21 * Totem is covered by.
22 *
23 * Monday 7th February 2005: Christian Schaller: Add exception clause.
24 * See license_change file for details.
25 *
26 */
27
28 #include "config.h"
29
30 #define GST_USE_UNSTABLE_API 1
31
32 #include <glib/gstdio.h>
33 #include <glib/gi18n.h>
34 #include <gst/gst.h>
35 #include <totem-pl-parser.h>
36
37 #include <locale.h>
38 #include <errno.h>
39 #include <unistd.h>
40 #include <string.h>
41 #include <math.h>
42 #include <stdlib.h>
43 #include <fcntl.h>
44 #include <sys/types.h>
45 #include <sys/stat.h>
46
47 #include "gst/totem-gst-helpers.h"
48 #include "gst/totem-gst-pixbuf-helpers.h"
49 #include "totem-resources.h"
50
51 #ifdef G_HAVE_ISO_VARARGS
52 #define PROGRESS_DEBUG(...) { if (verbose != FALSE) g_message (__VA_ARGS__); }
53 #elif defined(G_HAVE_GNUC_VARARGS)
54 #define PROGRESS_DEBUG(format...) { if (verbose != FALSE) g_message (format); }
55 #endif
56
57 /* The main() function controls progress in the first and last 10% */
58 #define PRINT_PROGRESS(p) { if (print_progress) g_printf ("%f%% complete\n", p); }
59 #define MIN_PROGRESS 10.0
60 #define MAX_PROGRESS 90.0
61
62 #define BORING_IMAGE_VARIANCE 256.0 /* Tweak this if necessary */
63 #define DEFAULT_OUTPUT_SIZE 256
64
65 static gboolean raw_output = FALSE;
66 static int output_size = -1;
67 static gboolean time_limit = TRUE;
68 static gboolean verbose = FALSE;
69 static gboolean print_progress = FALSE;
70 static gint64 second_index = -1;
71 static char **filenames = NULL;
72
73 typedef struct {
74 const char *output;
75 const char *input;
76 GstElement *play;
77 gint64 duration;
78 } ThumbApp;
79
80 static void save_pixbuf (GdkPixbuf *pixbuf, const char *path,
81 const char *video_path, int size, gboolean is_still);
82
83 static void
entry_parsed_cb(TotemPlParser * parser,const char * uri,GHashTable * metadata,char ** new_url)84 entry_parsed_cb (TotemPlParser *parser,
85 const char *uri,
86 GHashTable *metadata,
87 char **new_url)
88 {
89 *new_url = g_strdup (uri);
90 }
91
92 static char *
get_special_url(GFile * file)93 get_special_url (GFile *file)
94 {
95 char *path, *orig_uri, *uri, *mime_type;
96 TotemPlParser *parser;
97 TotemPlParserResult res;
98
99 path = g_file_get_path (file);
100
101 mime_type = g_content_type_guess (path, NULL, 0, NULL);
102 g_free (path);
103 if (g_strcmp0 (mime_type, "application/x-cd-image") != 0) {
104 g_free (mime_type);
105 return NULL;
106 }
107 g_free (mime_type);
108
109 uri = NULL;
110 orig_uri = g_file_get_uri (file);
111
112 parser = totem_pl_parser_new ();
113 g_signal_connect (parser, "entry-parsed",
114 G_CALLBACK (entry_parsed_cb), &uri);
115
116 res = totem_pl_parser_parse (parser, orig_uri, FALSE);
117
118 g_free (orig_uri);
119 g_object_unref (parser);
120
121 if (res == TOTEM_PL_PARSER_RESULT_SUCCESS)
122 return uri;
123
124 g_free (uri);
125
126 return NULL;
127 }
128
129 static gboolean
is_special_uri(const char * uri)130 is_special_uri (const char *uri)
131 {
132 if (g_str_has_prefix (uri, "dvd://") ||
133 g_str_has_prefix (uri, "vcd://"))
134 return TRUE;
135
136 return FALSE;
137 }
138
139 static void
thumb_app_set_filename(ThumbApp * app)140 thumb_app_set_filename (ThumbApp *app)
141 {
142 GFile *file;
143 char *uri;
144
145 if (is_special_uri (app->input)) {
146 g_object_set (app->play, "uri", app->input, NULL);
147 return;
148 }
149
150 file = g_file_new_for_commandline_arg (app->input);
151 uri = get_special_url (file);
152 if (uri == NULL)
153 uri = g_file_get_uri (file);
154 g_object_unref (file);
155
156 PROGRESS_DEBUG("setting URI %s", uri);
157
158 g_object_set (app->play, "uri", uri, NULL);
159 g_free (uri);
160 }
161
162 static GstBusSyncReply
error_handler(GstBus * bus,GstMessage * message,GstElement * play)163 error_handler (GstBus *bus,
164 GstMessage *message,
165 GstElement *play)
166 {
167 GstMessageType msg_type;
168
169 msg_type = GST_MESSAGE_TYPE (message);
170 switch (msg_type) {
171 case GST_MESSAGE_ERROR:
172 totem_gst_message_print (message, play, "totem-video-thumbnailer-error");
173 exit (1);
174 case GST_MESSAGE_EOS:
175 exit (0);
176
177 case GST_MESSAGE_ASYNC_DONE:
178 case GST_MESSAGE_UNKNOWN:
179 case GST_MESSAGE_WARNING:
180 case GST_MESSAGE_INFO:
181 case GST_MESSAGE_TAG:
182 case GST_MESSAGE_BUFFERING:
183 case GST_MESSAGE_STATE_CHANGED:
184 case GST_MESSAGE_STATE_DIRTY:
185 case GST_MESSAGE_STEP_DONE:
186 case GST_MESSAGE_CLOCK_PROVIDE:
187 case GST_MESSAGE_CLOCK_LOST:
188 case GST_MESSAGE_NEW_CLOCK:
189 case GST_MESSAGE_STRUCTURE_CHANGE:
190 case GST_MESSAGE_STREAM_STATUS:
191 case GST_MESSAGE_APPLICATION:
192 case GST_MESSAGE_ELEMENT:
193 case GST_MESSAGE_SEGMENT_START:
194 case GST_MESSAGE_SEGMENT_DONE:
195 case GST_MESSAGE_DURATION_CHANGED:
196 case GST_MESSAGE_LATENCY:
197 case GST_MESSAGE_ASYNC_START:
198 case GST_MESSAGE_REQUEST_STATE:
199 case GST_MESSAGE_STEP_START:
200 case GST_MESSAGE_QOS:
201 case GST_MESSAGE_PROGRESS:
202 case GST_MESSAGE_TOC:
203 case GST_MESSAGE_RESET_TIME:
204 case GST_MESSAGE_STREAM_START:
205 case GST_MESSAGE_ANY:
206 case GST_MESSAGE_NEED_CONTEXT:
207 case GST_MESSAGE_HAVE_CONTEXT:
208 default:
209 /* Ignored */
210 ;;
211 }
212
213 return GST_BUS_PASS;
214 }
215
216 static void
thumb_app_cleanup(ThumbApp * app)217 thumb_app_cleanup (ThumbApp *app)
218 {
219 gst_element_set_state (app->play, GST_STATE_NULL);
220 g_clear_object (&app->play);
221 }
222
223 static void
thumb_app_set_error_handler(ThumbApp * app)224 thumb_app_set_error_handler (ThumbApp *app)
225 {
226 GstBus *bus;
227
228 bus = gst_element_get_bus (app->play);
229 gst_bus_set_sync_handler (bus, (GstBusSyncHandler) error_handler, app->play, NULL);
230 g_object_unref (bus);
231 }
232
233 static void
check_cover_for_stream(ThumbApp * app,const char * signal_name)234 check_cover_for_stream (ThumbApp *app,
235 const char *signal_name)
236 {
237 GdkPixbuf *pixbuf;
238 GstTagList *tags = NULL;
239
240 g_signal_emit_by_name (G_OBJECT (app->play), signal_name, 0, &tags);
241
242 if (!tags)
243 return;
244
245 pixbuf = totem_gst_tag_list_get_cover (tags);
246 if (!pixbuf) {
247 gst_tag_list_unref (tags);
248 return;
249 }
250
251 PROGRESS_DEBUG("Saving cover image to %s", app->output);
252 thumb_app_cleanup (app);
253 save_pixbuf (pixbuf, app->output, app->input, output_size, TRUE);
254 g_object_unref (pixbuf);
255
256 exit (0);
257 }
258
259 static void
thumb_app_check_for_cover(ThumbApp * app)260 thumb_app_check_for_cover (ThumbApp *app)
261 {
262 PROGRESS_DEBUG ("Checking whether file has cover");
263 check_cover_for_stream (app, "get-audio-tags");
264 check_cover_for_stream (app, "get-video-tags");
265 }
266
267 static gboolean
thumb_app_set_duration(ThumbApp * app)268 thumb_app_set_duration (ThumbApp *app)
269 {
270 gint64 len = -1;
271
272 if (gst_element_query_duration (app->play, GST_FORMAT_TIME, &len) && len != -1) {
273 app->duration = len / GST_MSECOND;
274 return TRUE;
275 }
276 app->duration = -1;
277 return FALSE;
278 }
279
280 static void
assert_duration(ThumbApp * app)281 assert_duration (ThumbApp *app)
282 {
283 if (app->duration != -1)
284 return;
285 g_print ("totem-video-thumbnailer couldn't get the duration of file '%s'\n", app->input);
286 exit (1);
287 }
288
289 static gboolean
thumb_app_get_has_video(ThumbApp * app)290 thumb_app_get_has_video (ThumbApp *app)
291 {
292 guint n_video;
293 g_object_get (app->play, "n-video", &n_video, NULL);
294 return n_video > 0;
295 }
296
297 static gboolean
thumb_app_start(ThumbApp * app)298 thumb_app_start (ThumbApp *app)
299 {
300 GstBus *bus;
301 GstMessageType events;
302 gboolean terminate = FALSE;
303 gboolean async_received = FALSE;
304
305 gst_element_set_state (app->play, GST_STATE_PAUSED);
306 bus = gst_element_get_bus (app->play);
307 events = GST_MESSAGE_ASYNC_DONE | GST_MESSAGE_ERROR;
308
309 while (terminate == FALSE) {
310 GstMessage *message;
311 GstElement *src;
312
313 message = gst_bus_timed_pop_filtered (bus,
314 GST_CLOCK_TIME_NONE,
315 events);
316
317 src = (GstElement*)GST_MESSAGE_SRC (message);
318
319 switch (GST_MESSAGE_TYPE (message)) {
320 case GST_MESSAGE_ASYNC_DONE:
321 if (src == app->play) {
322 async_received = TRUE;
323 terminate = TRUE;
324 }
325 break;
326 case GST_MESSAGE_ERROR:
327 totem_gst_message_print (message, app->play, "totem-video-thumbnailer-error");
328 terminate = TRUE;
329 break;
330
331 case GST_MESSAGE_UNKNOWN:
332 case GST_MESSAGE_EOS:
333 case GST_MESSAGE_WARNING:
334 case GST_MESSAGE_INFO:
335 case GST_MESSAGE_TAG:
336 case GST_MESSAGE_BUFFERING:
337 case GST_MESSAGE_STATE_CHANGED:
338 case GST_MESSAGE_STATE_DIRTY:
339 case GST_MESSAGE_STEP_DONE:
340 case GST_MESSAGE_CLOCK_PROVIDE:
341 case GST_MESSAGE_CLOCK_LOST:
342 case GST_MESSAGE_NEW_CLOCK:
343 case GST_MESSAGE_STRUCTURE_CHANGE:
344 case GST_MESSAGE_STREAM_STATUS:
345 case GST_MESSAGE_APPLICATION:
346 case GST_MESSAGE_ELEMENT:
347 case GST_MESSAGE_SEGMENT_START:
348 case GST_MESSAGE_SEGMENT_DONE:
349 case GST_MESSAGE_DURATION_CHANGED:
350 case GST_MESSAGE_LATENCY:
351 case GST_MESSAGE_ASYNC_START:
352 case GST_MESSAGE_REQUEST_STATE:
353 case GST_MESSAGE_STEP_START:
354 case GST_MESSAGE_QOS:
355 case GST_MESSAGE_PROGRESS:
356 case GST_MESSAGE_TOC:
357 case GST_MESSAGE_RESET_TIME:
358 case GST_MESSAGE_STREAM_START:
359 case GST_MESSAGE_ANY:
360 case GST_MESSAGE_NEED_CONTEXT:
361 case GST_MESSAGE_HAVE_CONTEXT:
362 default:
363 /* Ignore */
364 ;;
365 }
366
367 gst_message_unref (message);
368 }
369
370 gst_object_unref (bus);
371
372 if (async_received) {
373 /* state change succeeded */
374 GST_DEBUG ("state change to %s succeeded", gst_element_state_get_name (GST_STATE_PAUSED));
375 }
376
377 return async_received;
378 }
379
380 static void
thumb_app_setup_play(ThumbApp * app)381 thumb_app_setup_play (ThumbApp *app)
382 {
383 GstElement *play;
384 GstElement *audio_sink, *video_sink;
385
386 play = gst_element_factory_make ("playbin", "play");
387 audio_sink = gst_element_factory_make ("fakesink", "audio-fake-sink");
388 video_sink = gst_element_factory_make ("fakesink", "video-fake-sink");
389 g_object_set (video_sink, "sync", TRUE, NULL);
390
391 g_object_set (play,
392 "audio-sink", audio_sink,
393 "video-sink", video_sink,
394 "flags", GST_PLAY_FLAG_VIDEO | GST_PLAY_FLAG_AUDIO,
395 NULL);
396
397 app->play = play;
398
399 totem_gst_disable_display_decoders ();
400 }
401
402 static void
thumb_app_seek(ThumbApp * app,gint64 _time)403 thumb_app_seek (ThumbApp *app,
404 gint64 _time)
405 {
406 gst_element_seek (app->play, 1.0,
407 GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT,
408 GST_SEEK_TYPE_SET, _time * GST_MSECOND,
409 GST_SEEK_TYPE_NONE, GST_CLOCK_TIME_NONE);
410 /* And wait for this seek to complete */
411 gst_element_get_state (app->play, NULL, NULL, GST_CLOCK_TIME_NONE);
412 }
413
414 /* This function attempts to detect images that are mostly solid images
415 * It does this by calculating the statistical variance of the
416 * black-and-white image */
417 static gboolean
is_image_interesting(GdkPixbuf * pixbuf)418 is_image_interesting (GdkPixbuf *pixbuf)
419 {
420 /* We're gonna assume 8-bit samples. If anyone uses anything different,
421 * it doesn't really matter cause it's gonna be ugly anyways */
422 int rowstride = gdk_pixbuf_get_rowstride(pixbuf);
423 int height = gdk_pixbuf_get_height(pixbuf);
424 guchar* buffer = gdk_pixbuf_get_pixels(pixbuf);
425 int num_samples = (rowstride * height);
426 int i;
427 float x_bar = 0.0f;
428 float variance = 0.0f;
429
430 /* FIXME: If this proves to be a performance issue, this function
431 * can be modified to perhaps only check 3 rows. I doubt this'll
432 * be a problem though. */
433
434 /* Iterate through the image to calculate x-bar */
435 for (i = 0; i < num_samples; i++) {
436 x_bar += (float) buffer[i];
437 }
438 x_bar /= ((float) num_samples);
439
440 /* Calculate the variance */
441 for (i = 0; i < num_samples; i++) {
442 float tmp = ((float) buffer[i] - x_bar);
443 variance += tmp * tmp;
444 }
445 variance /= ((float) (num_samples - 1));
446
447 return (variance > BORING_IMAGE_VARIANCE);
448 }
449
450 static GdkPixbuf *
scale_pixbuf(GdkPixbuf * pixbuf,int size,gboolean is_still)451 scale_pixbuf (GdkPixbuf *pixbuf, int size, gboolean is_still)
452 {
453 GdkPixbuf *result;
454 int width, height;
455 int d_width, d_height;
456
457 if (size != -1) {
458 height = gdk_pixbuf_get_height (pixbuf);
459 width = gdk_pixbuf_get_width (pixbuf);
460
461 if (width > height) {
462 d_width = size;
463 d_height = size * height / width;
464 } else {
465 d_height = size;
466 d_width = size * width / height;
467 }
468 } else {
469 d_width = d_height = -1;
470 }
471
472 if (size <= 256) {
473 GdkPixbuf *small;
474
475 small = gdk_pixbuf_scale_simple (pixbuf, d_width, d_height, GDK_INTERP_BILINEAR);
476 result = small;
477 } else {
478 if (size > 0)
479 result = gdk_pixbuf_scale_simple (pixbuf, d_width, d_height, GDK_INTERP_BILINEAR);
480 else
481 result = g_object_ref (pixbuf);
482 }
483
484 return result;
485 }
486
487 static void
save_pixbuf(GdkPixbuf * pixbuf,const char * path,const char * video_path,int size,gboolean is_still)488 save_pixbuf (GdkPixbuf *pixbuf, const char *path,
489 const char *video_path, int size, gboolean is_still)
490 {
491 int width, height;
492 char *a_width, *a_height;
493 GdkPixbuf *with_holes;
494 GError *err = NULL;
495 gboolean ret;
496
497 height = gdk_pixbuf_get_height (pixbuf);
498 width = gdk_pixbuf_get_width (pixbuf);
499
500 /* If we're outputting a raw image without a size,
501 * don't scale the pixbuf or add borders */
502 if (raw_output != FALSE && size == -1)
503 with_holes = g_object_ref (pixbuf);
504 else if (raw_output != FALSE)
505 with_holes = scale_pixbuf (pixbuf, size, TRUE);
506 else
507 with_holes = scale_pixbuf (pixbuf, size, is_still);
508
509 a_width = g_strdup_printf ("%d", width);
510 a_height = g_strdup_printf ("%d", height);
511
512 ret = gdk_pixbuf_save (with_holes, path, "png", &err,
513 "tEXt::Thumb::Image::Width", a_width,
514 "tEXt::Thumb::Image::Height", a_height,
515 NULL);
516
517 if (ret == FALSE) {
518 if (err != NULL) {
519 g_print ("totem-video-thumbnailer couldn't write the thumbnail '%s' for video '%s': %s\n", path, video_path, err->message);
520 g_error_free (err);
521 } else {
522 g_print ("totem-video-thumbnailer couldn't write the thumbnail '%s' for video '%s'\n", path, video_path);
523 }
524
525 g_object_unref (with_holes);
526 return;
527 }
528
529 g_object_unref (with_holes);
530 }
531
532 static GdkPixbuf *
capture_frame_at_time(ThumbApp * app,gint64 milliseconds)533 capture_frame_at_time (ThumbApp *app,
534 gint64 milliseconds)
535 {
536 if (milliseconds != 0)
537 thumb_app_seek (app, milliseconds);
538
539 return totem_gst_playbin_get_frame (app->play);
540 }
541
542 static GdkPixbuf *
capture_interesting_frame(ThumbApp * app)543 capture_interesting_frame (ThumbApp *app)
544 {
545 GdkPixbuf* pixbuf;
546 guint current;
547 const double frame_locations[] = {
548 1.0 / 3.0,
549 2.0 / 3.0,
550 0.1,
551 0.9,
552 0.5
553 };
554
555 if (app->duration == -1) {
556 PROGRESS_DEBUG("Video has no duration, so capture 1st frame");
557 return capture_frame_at_time (app, 0);
558 }
559
560 /* Test at multiple points in the file to see if we can get an
561 * interesting frame */
562 for (current = 0; current < G_N_ELEMENTS(frame_locations); current++)
563 {
564 PROGRESS_DEBUG("About to seek to %f", frame_locations[current]);
565 thumb_app_seek (app, frame_locations[current] * app->duration);
566
567 /* Pull the frame, if it's interesting we bail early */
568 PROGRESS_DEBUG("About to get frame for iter %d", current);
569 pixbuf = totem_gst_playbin_get_frame (app->play);
570 if (pixbuf != NULL && is_image_interesting (pixbuf) != FALSE) {
571 PROGRESS_DEBUG("Frame for iter %d is interesting", current);
572 break;
573 }
574
575 /* If we get to the end of this loop, we'll end up using
576 * the last image we pulled */
577 if (current + 1 < G_N_ELEMENTS(frame_locations))
578 g_clear_object (&pixbuf);
579 PROGRESS_DEBUG("Frame for iter %d was not interesting", current);
580 }
581 return pixbuf;
582 }
583
584 static const GOptionEntry entries[] = {
585 { "size", 's', 0, G_OPTION_ARG_INT, &output_size, "Size of the thumbnail in pixels", NULL },
586 { "raw", 'r', 0, G_OPTION_ARG_NONE, &raw_output, "Output the raw picture of the video without scaling or adding borders", NULL },
587 { "no-limit", 'l', G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &time_limit, "Don't limit the thumbnailing time to 30 seconds", NULL },
588 { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Output debug information", NULL },
589 { "time", 't', 0, G_OPTION_ARG_INT64, &second_index, "Choose this time (in seconds) as the thumbnail", NULL },
590 { "print-progress", 'p', 0, G_OPTION_ARG_NONE, &print_progress, "Only print progress updates (can't be used with --verbose)", NULL },
591 { G_OPTION_REMAINING, '\0', 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames, NULL, "[INPUT FILE] [OUTPUT FILE]" },
592 { NULL }
593 };
594
main(int argc,char * argv[])595 int main (int argc, char *argv[])
596 {
597 GOptionGroup *options;
598 GOptionContext *context;
599 GError *err = NULL;
600 GdkPixbuf *pixbuf;
601 const char *input, *output;
602 ThumbApp app;
603
604 setlocale (LC_ALL, "");
605 bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
606 bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
607 textdomain (GETTEXT_PACKAGE);
608
609 /* Call before the global thread pool is setup */
610 errno = 0;
611 if (nice (20) != 20 && errno != 0)
612 g_warning ("Couldn't change nice value of process.");
613
614 context = g_option_context_new ("Thumbnail movies");
615 options = gst_init_get_option_group ();
616 g_option_context_add_main_entries (context, entries, GETTEXT_PACKAGE);
617 g_option_context_add_group (context, options);
618
619 if (g_option_context_parse (context, &argc, &argv, &err) == FALSE) {
620 g_print ("couldn't parse command-line options: %s\n", err->message);
621 g_error_free (err);
622 return 1;
623 }
624
625 if (print_progress) {
626 fcntl (fileno (stdout), F_SETFL, O_NONBLOCK);
627 setbuf (stdout, NULL);
628 }
629
630 if (raw_output == FALSE && output_size == -1)
631 output_size = DEFAULT_OUTPUT_SIZE;
632
633 if (filenames == NULL || g_strv_length (filenames) != 2 ||
634 (print_progress == TRUE && verbose == TRUE)) {
635 char *help;
636 help = g_option_context_get_help (context, FALSE, NULL);
637 g_print ("%s", help);
638 g_free (help);
639 return 1;
640 }
641 input = filenames[0];
642 output = filenames[1];
643
644 PROGRESS_DEBUG("Initialised libraries, about to create video widget");
645 PRINT_PROGRESS (2.0);
646
647 app.input = input;
648 app.output = output;
649
650 thumb_app_setup_play (&app);
651 thumb_app_set_filename (&app);
652
653 PROGRESS_DEBUG("Video widget created");
654 PRINT_PROGRESS (6.0);
655
656 if (time_limit != FALSE)
657 totem_resources_monitor_start (input, 0);
658
659 PROGRESS_DEBUG("About to open video file");
660
661 if (thumb_app_start (&app) == FALSE) {
662 g_print ("totem-video-thumbnailer couldn't open file '%s'\n", input);
663 exit (1);
664 }
665 thumb_app_set_error_handler (&app);
666
667 thumb_app_check_for_cover (&app);
668 if (thumb_app_get_has_video (&app) == FALSE) {
669 PROGRESS_DEBUG ("totem-video-thumbnailer couldn't find a video track in '%s'\n", input);
670 exit (1);
671 }
672 thumb_app_set_duration (&app);
673
674 PROGRESS_DEBUG("Opened video file: '%s'", input);
675 PRINT_PROGRESS (10.0);
676
677 /* If the user has told us to use a frame at a specific second
678 * into the video, just use that frame no matter how boring it
679 * is */
680 if (second_index != -1) {
681 assert_duration (&app);
682 pixbuf = capture_frame_at_time (&app, second_index * 1000);
683 } else {
684 pixbuf = capture_interesting_frame (&app);
685 }
686 PRINT_PROGRESS (90.0);
687
688 /* Cleanup */
689 totem_resources_monitor_stop ();
690 thumb_app_cleanup (&app);
691 PRINT_PROGRESS (92.0);
692
693 if (pixbuf == NULL) {
694 g_print ("totem-video-thumbnailer couldn't get a picture from '%s'\n", input);
695 exit (1);
696 }
697
698 PROGRESS_DEBUG("Saving captured screenshot to %s", output);
699 save_pixbuf (pixbuf, output, input, output_size, FALSE);
700 g_object_unref (pixbuf);
701 PRINT_PROGRESS (100.0);
702
703 return 0;
704 }
705
706