static void jpeg_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer, GCancellable *cancellable, TumblerFileInfo *info) { TumblerThumbnailFlavor *flavor; TumblerImageData data; TumblerThumbnail *thumbnail; struct stat statb; const gchar *uri; GdkPixbuf *pixbuf = NULL; gboolean streaming_needed = TRUE; JOCTET *content; GError *error = NULL; GFile *file; gchar *path; gsize length; gint fd; gint height; gint width; gint size; g_return_if_fail (IS_JPEG_THUMBNAILER (thumbnailer)); g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable)); g_return_if_fail (TUMBLER_IS_FILE_INFO (info)); /* do nothing if cancelled */ if (g_cancellable_is_cancelled (cancellable)) return; uri = tumbler_file_info_get_uri (info); /* try to open the source file for reading */ file = g_file_new_for_uri (uri); thumbnail = tumbler_file_info_get_thumbnail (info); g_assert (thumbnail != NULL); flavor = tumbler_thumbnail_get_flavor (thumbnail); g_assert (flavor != NULL); tumbler_thumbnail_flavor_get_size (flavor, &width, &height); size = MIN (width, height); #ifdef HAVE_MMAP if (g_file_is_native (file)) { path = g_file_get_path (file); /* try to open the file at the given path */ fd = open (path, O_RDONLY); if (G_LIKELY (fd >= 0)) { /* determine the status of the file */ if (G_LIKELY (fstat (fd, &statb) == 0 && statb.st_size > 0)) { /* try to mmap the file */ content = (JOCTET *) mmap (NULL, statb.st_size, PROT_READ, MAP_SHARED, fd, 0); /* verify whether the mmap was successful */ if (G_LIKELY (content != (JOCTET *) MAP_FAILED)) { /* try to load the embedded thumbnail first */ pixbuf = tvtj_jpeg_load_thumbnail (content, statb.st_size, size); if (pixbuf == NULL) { /* fall back to loading and scaling the image itself */ pixbuf = tvtj_jpeg_load (content, statb.st_size, size); if (pixbuf == NULL) { g_set_error (&error, TUMBLER_ERROR, TUMBLER_ERROR_INVALID_FORMAT, _("Thumbnail could not be inferred from file contents")); } } /* we have successfully mmapped the file. we may not have * a thumbnail but trying to read the image from a stream * won't help us here, so we don't need to attempt streaming * as a fallback */ streaming_needed = FALSE; } /* unmap the file content */ munmap ((void *) content, statb.st_size); } /* close the file */ close (fd); } g_free (path); } #endif if (streaming_needed) { g_file_load_contents (file, cancellable, (gchar **)&content, &length, NULL, &error); if (error == NULL) { pixbuf = tvtj_jpeg_load_thumbnail (content, length, size); if (pixbuf == NULL) { pixbuf = tvtj_jpeg_load (content, length, size); if (pixbuf == NULL) { g_set_error (&error, TUMBLER_ERROR, TUMBLER_ERROR_INVALID_FORMAT, _("Thumbnail could not be inferred from file contents")); } } } } /* either we have an error now or we have a valid thumbnail pixbuf */ g_assert (error != NULL || pixbuf != NULL); if (pixbuf != NULL) { data.data = gdk_pixbuf_get_pixels (pixbuf); data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf); data.width = gdk_pixbuf_get_width (pixbuf); data.height = gdk_pixbuf_get_height (pixbuf); data.rowstride = gdk_pixbuf_get_rowstride (pixbuf); data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf); tumbler_thumbnail_save_image_data (thumbnail, &data, tumbler_file_info_get_mtime (info), NULL, &error); g_object_unref (pixbuf); } if (error != NULL) { g_signal_emit_by_name (thumbnailer, "error", uri, error->code, error->message); g_error_free (error); } else { g_signal_emit_by_name (thumbnailer, "ready", uri); } g_object_unref (flavor); g_object_unref (thumbnail); g_object_unref (file); }
/* * 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)); } } }
static void cover_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer, GCancellable *cancellable, TumblerFileInfo *info) { CoverThumbnailer *cover = COVER_THUMBNAILER (thumbnailer); const gchar *uri; TumblerThumbnail *thumbnail; gchar *year; gchar *title; GdkPixbuf *pixbuf = NULL; gchar *poster_url; GError *error = NULL; TumblerImageData data; TumblerThumbnailFlavor *flavor; GFile *gfile; /* source file */ uri = tumbler_file_info_get_uri (info); gfile = g_file_new_for_uri (uri); /* target data */ thumbnail = tumbler_file_info_get_thumbnail (info); flavor = tumbler_thumbnail_get_flavor (thumbnail); /* extract title from filename */ if (cover_thumbnailer_get_title (cover, gfile, &title, &year)) { /* request online metadata and return the poster url */ poster_url = cover_thumbnailer_poster_url (cover, title, year, flavor, cancellable, &error); g_free (title); g_free (year); if (poster_url != NULL) { /* download poster and load it in a pixbuf */ pixbuf = cover_thumbnailer_load_pixbuf (cover, poster_url, flavor, cancellable, &error); g_free (poster_url); } } else { g_set_error_literal (&error, G_IO_ERROR, G_IO_ERROR_INVALID_FILENAME, _("Movie title is too short")); } if (pixbuf != NULL) { /* prepare the image data */ data.data = gdk_pixbuf_get_pixels (pixbuf); data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf); data.width = gdk_pixbuf_get_width (pixbuf); data.height = gdk_pixbuf_get_height (pixbuf); data.rowstride = gdk_pixbuf_get_rowstride (pixbuf); data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf); tumbler_thumbnail_save_image_data (thumbnail, &data, tumbler_file_info_get_mtime (info), cancellable, &error); g_object_unref (pixbuf); } /* return the status */ if (error != NULL) { g_signal_emit_by_name (thumbnailer, "error", uri, error->code, error->message); g_error_free (error); } else { g_signal_emit_by_name (thumbnailer, "ready", uri); } g_object_unref (thumbnail); g_object_unref (flavor); g_object_unref (gfile); }
static void gst_thumbnailer_create (TumblerAbstractThumbnailer *thumbnailer, GCancellable *cancellable, TumblerFileInfo *info) { GstElement *play; GdkPixbuf *pixbuf = NULL; gint64 duration; TumblerImageData data; GError *error = NULL; TumblerThumbnail *thumbnail; gint width, height; TumblerThumbnailFlavor *flavor; GdkPixbuf *scaled; /* check for early cancellation */ if (g_cancellable_is_cancelled (cancellable)) return; /* get size of dest thumb */ thumbnail = tumbler_file_info_get_thumbnail (info); flavor = tumbler_thumbnail_get_flavor (thumbnail); tumbler_thumbnail_flavor_get_size (flavor, &width, &height); /* prepare factory */ play = gst_thumbnailer_play_init (info); if (gst_thumbnailer_play_start (play, cancellable)) { /* check for covers in the file */ pixbuf = gst_thumbnailer_cover (play, cancellable); /* extract cover from video stream */ if (pixbuf == NULL && gst_thumbnailer_has_video (play)) { /* get the length of the video track */ if (gst_element_query_duration (play, GST_FORMAT_TIME, &duration) && duration != -1) duration /= GST_MSECOND; else duration = -1; pixbuf = gst_thumbnailer_capture_interesting_frame (play, duration, width, cancellable); } } /* stop factory */ gst_element_set_state (play, GST_STATE_NULL); g_object_unref (play); if (G_LIKELY (pixbuf != NULL)) { /* scale to correct size if required */ scaled = gst_thumbnailer_scale_pixbuf (pixbuf, width, height); g_object_unref (pixbuf); pixbuf = scaled; data.data = gdk_pixbuf_get_pixels (pixbuf); data.has_alpha = gdk_pixbuf_get_has_alpha (pixbuf); data.bits_per_sample = gdk_pixbuf_get_bits_per_sample (pixbuf); data.width = gdk_pixbuf_get_width (pixbuf); data.height = gdk_pixbuf_get_height (pixbuf); data.rowstride = gdk_pixbuf_get_rowstride (pixbuf); data.colorspace = (TumblerColorspace) gdk_pixbuf_get_colorspace (pixbuf); tumbler_thumbnail_save_image_data (thumbnail, &data, tumbler_file_info_get_mtime (info), NULL, &error); g_object_unref (pixbuf); 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)); } } g_object_unref (thumbnail); }