GdkPixbuf * gst_video_thumbnailer_get_shot (const gchar *location, GCancellable *cancellable) { GstElement *playbin, *audio_sink, *video_sink; GstStateChangeReturn state; GdkPixbuf *shot = NULL; int count = 0; gchar *uri = g_strconcat ("file://", location, NULL); GMainContext *context = g_main_context_new (); g_main_context_push_thread_default (context); playbin = gst_element_factory_make ("playbin", "playbin"); audio_sink = gst_element_factory_make ("fakesink", "audiosink"); video_sink = gst_element_factory_make ("fakesink", "videosink"); g_object_set (playbin, "uri", uri, "audio-sink", audio_sink, "video-sink", video_sink, NULL); g_object_set (video_sink, "sync", TRUE, NULL); state = gst_element_set_state (playbin, GST_STATE_PAUSED); while (state == GST_STATE_CHANGE_ASYNC && count < 5 && !g_cancellable_is_cancelled (cancellable)) { state = gst_element_get_state (playbin, NULL, 0, 1 * GST_SECOND); count++; /* Spin mainloop so we can pick up the cancels */ while (g_main_context_pending (context)) { g_main_context_iteration (context, FALSE); } } if (g_cancellable_is_cancelled (cancellable)) { g_print ("Video %s was cancelled\n", uri); state = GST_STATE_CHANGE_FAILURE; } if (state != GST_STATE_CHANGE_FAILURE && state != GST_STATE_CHANGE_ASYNC) { GstFormat format = GST_FORMAT_TIME; gint64 duration; if (gst_element_query_duration (playbin, &format, &duration)) { gint64 seekpos; GstBuffer *frame; if (duration > 0) { if (duration / (3 * GST_SECOND) > 90) { seekpos = (rand () % (duration / (3 * GST_SECOND))) * GST_SECOND; } else { seekpos = (rand () % (duration / (GST_SECOND))) * GST_SECOND; } } else { seekpos = 5 * GST_SECOND; } gst_element_seek_simple (playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_ACCURATE, seekpos); /* Wait for seek to complete */ count = 0; state = gst_element_get_state (playbin, NULL, 0, 0.2 * GST_SECOND); while (state == GST_STATE_CHANGE_ASYNC && count < 3) { state = gst_element_get_state (playbin, NULL, 0, 1 * GST_SECOND); count++; } g_object_get (playbin, "frame", &frame, NULL); if (frame == NULL) { g_warning ("No frame for %s", uri); shot = NULL; goto finish; } shot = convert_buffer_to_pixbuf (frame, cancellable); } } gst_element_set_state (playbin, GST_STATE_NULL); g_object_unref (playbin); g_free (uri); finish: g_main_context_pop_thread_default (context); g_main_context_unref (context); return shot; }
/* * get uri * create playbin * check that we have video * for each potential location * check for cancel * grab snapshot * interesting? * return if so, other spin loop * * for future, check metadata for artwork and don't reject non-video streams (see totem) * * TODO: all error paths leak */ static void gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer, GCancellable *cancellable, TumblerFileInfo *info) { /* These positions are taken from Totem */ const double positions[] = { 1.0 / 3.0, 2.0 / 3.0, 0.1, 0.9, 0.5 }; GstElement *playbin; gint64 duration; unsigned int i; GstBuffer *frame; GdkPixbuf *shot; TumblerThumbnail *thumbnail; TumblerThumbnailFlavor *flavour; TumblerImageData data; GError *error = NULL; g_return_if_fail (IS_GST_THUMBNAILER (thumbnailer)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (TUMBLER_IS_FILE_INFO (info)); /* Check for early cancellation */ if (g_cancellable_is_cancelled (cancellable)) return; playbin = make_pipeline (info, cancellable); if (playbin == NULL) { /* TODO: emit an error, but the specification won't let me. */ return; } duration = get_duration (playbin); /* Now we have a pipeline that we know has video and is paused, ready for seeking */ for (i = 0; i < G_N_ELEMENTS (positions); i++) { /* Check if we've been cancelled */ if (g_cancellable_is_cancelled (cancellable)) { gst_element_set_state (playbin, GST_STATE_NULL); g_object_unref (playbin); return; } LOG ("trying position %f", positions[i]); gst_element_seek_simple (playbin, GST_FORMAT_TIME, GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT, (gint64)(positions[i] * duration)); if (gst_element_get_state (playbin, NULL, NULL, 1 * GST_SECOND) == GST_STATE_CHANGE_FAILURE) { LOG ("Could not seek"); return; } g_object_get (playbin, "frame", &frame, NULL); if (frame == NULL) { LOG ("No frame found!"); continue; } thumbnail = tumbler_file_info_get_thumbnail (info); flavour = tumbler_thumbnail_get_flavor (thumbnail); /* This frees the buffer for us */ shot = convert_buffer_to_pixbuf (frame, cancellable, flavour); g_object_unref (flavour); /* If it's not interesting, throw it away and try again*/ if (is_interesting (shot)) { /* Got an interesting image, break out */ LOG ("Found an interesting image"); break; } /* * If we've still got positions to try, free the current uninteresting * shot. Otherwise we'll make do with what we have. */ if (i + 1 < G_N_ELEMENTS (positions) && shot) { g_object_unref (shot); shot = NULL; } /* Spin mainloop so we can pick up the cancels */ while (g_main_context_pending (NULL)) { g_main_context_iteration (NULL, FALSE); } } gst_element_set_state (playbin, GST_STATE_NULL); g_object_unref (playbin); if (shot) { data.data = gdk_pixbuf_get_pixels (shot); data.has_alpha = gdk_pixbuf_get_has_alpha (shot); data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (shot); data.width = gdk_pixbuf_get_width (shot); data.height = gdk_pixbuf_get_height (shot); data.rowstride = gdk_pixbuf_get_rowstride (shot); data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (shot); tumbler_thumbnail_save_image_data (thumbnail, &data, tumbler_file_info_get_mtime (info), NULL, &error); g_object_unref (shot); if (error != NULL) { g_signal_emit_by_name (thumbnailer, "error", tumbler_file_info_get_uri (info), error->code, error->message); g_error_free (error); } else { g_signal_emit_by_name (thumbnailer, "ready", tumbler_file_info_get_uri (info)); } } }