From 8dfbd0dee2a76d4cc3c8b1a339ed6d904b45fd9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Tue, 7 Jun 2022 04:21:04 +0200 Subject: Add a command line option to extract thumbnails Only use LibRaw for now, which probably has the most impact using the least amount of effort. --- fiv-browser.c | 3 +- fiv-thumbnail.c | 154 ++++++++++++++++++++++++++++++++++++++++---------------- fiv-thumbnail.h | 9 ++-- fiv.c | 66 +++++++++++++++--------- 4 files changed, 161 insertions(+), 71 deletions(-) diff --git a/fiv-browser.c b/fiv-browser.c index 4c4dab6..111c291 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -555,8 +555,9 @@ thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry) { g_clear_object(&entry->icon); g_clear_pointer(&entry->thumbnail, cairo_surface_destroy); + guint64 dummy; if (!output || !(entry->thumbnail = rescale_thumbnail( - fiv_io_deserialize(output), self->item_height))) { + fiv_io_deserialize(output, &dummy), self->item_height))) { entry_add_thumbnail(entry, self); materialize_icon(self, entry); } else { diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 18be4eb..aee3d49 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -30,6 +30,10 @@ #include "fiv-thumbnail.h" #include "xdg.h" +#ifdef HAVE_LIBRAW +#include +#endif // HAVE_LIBRAW + #ifndef __linux__ #define st_mtim st_mtimespec #endif // ! __linux__ @@ -104,6 +108,91 @@ fiv_thumbnail_get_root(void) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +static cairo_surface_t * +render(GFile *target, GBytes *data, gboolean *color_managed, GError **error) +{ + FivIoOpenContext ctx = { + .uri = g_file_get_uri(target), + .screen_profile = fiv_io_profile_new_sRGB(), + .screen_dpi = 96, + .first_frame_only = TRUE, + // Only using this array as a redirect. + .warnings = g_ptr_array_new_with_free_func(g_free), + }; + + cairo_surface_t *surface = fiv_io_open_from_data( + g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error); + g_free((gchar *) ctx.uri); + g_ptr_array_free(ctx.warnings, TRUE); + if ((*color_managed = !!ctx.screen_profile)) + fiv_io_profile_free(ctx.screen_profile); + g_bytes_unref(data); + return surface; +} + +cairo_surface_t * +fiv_thumbnail_extract(GFile *target, GError **error) +{ + const char *path = g_file_peek_path(target); + if (!path) { + set_error(error, "thumbnails will only be extracted from local files"); + return NULL; + } + + GMappedFile *mf = g_mapped_file_new(path, FALSE, error); + if (!mf) + return NULL; + + cairo_surface_t *surface = NULL; +#ifndef HAVE_LIBRAW + // TODO(p): Implement our own thumbnail extractors. + set_error(error, "unsupported file"); +#else // HAVE_LIBRAW + libraw_data_t *iprc = libraw_init( + LIBRAW_OPIONS_NO_MEMERR_CALLBACK | LIBRAW_OPIONS_NO_DATAERR_CALLBACK); + if (!iprc) { + set_error(error, "failed to obtain a LibRaw handle"); + goto fail; + } + + int err = 0; + if ((err = libraw_open_buffer(iprc, (void *) g_mapped_file_get_contents(mf), + g_mapped_file_get_length(mf))) || + (err = libraw_unpack_thumb(iprc))) { + set_error(error, libraw_strerror(err)); + goto fail_libraw; + } + + libraw_processed_image_t *image = libraw_dcraw_make_mem_thumb(iprc, &err); + if (!image) { + set_error(error, libraw_strerror(err)); + goto fail_libraw; + } + + gboolean dummy = FALSE; + switch (image->type) { + case LIBRAW_IMAGE_JPEG: + surface = render( + target, g_bytes_new(image->data, image->data_size), &dummy, error); + break; + case LIBRAW_IMAGE_BITMAP: + // TODO(p): Implement this one as well. + default: + set_error(error, "unsupported embedded thumbnail"); + } + + libraw_dcraw_clear_mem(image); +fail_libraw: + libraw_close(iprc); +#endif // HAVE_LIBRAW + +fail: + g_mapped_file_unref(mf); + return surface; +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // In principle similar to rescale_thumbnail() from fiv-browser.c. static cairo_surface_t * adjust_thumbnail(cairo_surface_t *thumbnail, double row_height) @@ -238,30 +327,7 @@ save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum) } static cairo_surface_t * -render(GFile *target, GBytes *data, gboolean *color_managed, GError **error) -{ - FivIoOpenContext ctx = { - .uri = g_file_get_uri(target), - .screen_profile = fiv_io_profile_new_sRGB(), - .screen_dpi = 96, - .first_frame_only = TRUE, - // Only using this array as a redirect. - .warnings = g_ptr_array_new_with_free_func(g_free), - }; - - cairo_surface_t *surface = fiv_io_open_from_data( - g_bytes_get_data(data, NULL), g_bytes_get_size(data), &ctx, error); - g_free((gchar *) ctx.uri); - g_ptr_array_free(ctx.warnings, TRUE); - if ((*color_managed = !!ctx.screen_profile)) - fiv_io_profile_free(ctx.screen_profile); - g_bytes_unref(data); - return surface; -} - -static gboolean -produce_fallback(GFile *target, FivThumbnailSize size, - cairo_surface_t **surface, GError **error) +produce_fallback(GFile *target, FivThumbnailSize size, GError **error) { goffset filesize = 0; GFileInfo *info = g_file_query_info(target, @@ -276,40 +342,39 @@ produce_fallback(GFile *target, FivThumbnailSize size, // For example, we can employ magic checks. if (filesize > 10 << 20) { set_error(error, "oversize, not thumbnailing"); - return FALSE; + return NULL; } GBytes *data = g_file_load_bytes(target, NULL, NULL, error); if (!data) - return FALSE; + return NULL; gboolean color_managed = FALSE; - cairo_surface_t *result = render(target, data, &color_managed, error); - if (!result) - return FALSE; - - if (!*surface) - *surface = adjust_thumbnail(result, fiv_thumbnail_sizes[size].size); - cairo_surface_destroy(result); - return TRUE; + cairo_surface_t *surface = render(target, data, &color_managed, error); + if (!surface) + return NULL; + + cairo_surface_t *result = + adjust_thumbnail(surface, fiv_thumbnail_sizes[size].size); + cairo_surface_destroy(surface); + return result; } -gboolean -fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, - cairo_surface_t **max_size_surface, GError **error) +cairo_surface_t * +fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error) { g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN && max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE); const gchar *path = g_file_peek_path(target); if (!path || !g_file_is_native(target) /* Don't save sftp://. */) - return produce_fallback(target, max_size, max_size_surface, error); + return produce_fallback(target, max_size, error); // Make the TOCTTOU issue favour unnecessary reloading. GStatBuf st = {}; if (g_stat(path, &st)) { set_error(error, g_strerror(errno)); - return FALSE; + return NULL; } GError *e = NULL; @@ -317,7 +382,7 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, if (!mf) { g_debug("%s: %s", path, e->message); g_error_free(e); - return produce_fallback(target, max_size, max_size_surface, error); + return produce_fallback(target, max_size, error); } gsize filesize = g_mapped_file_get_length(mf); @@ -326,7 +391,7 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, render(target, g_mapped_file_get_bytes(mf), &color_managed, error); g_mapped_file_unref(mf); if (!surface) - return FALSE; + return NULL; // Boilerplate copied from fiv_thumbnail_lookup(). gchar *uri = g_file_get_uri(target); @@ -354,6 +419,7 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, thum, "%s%c%s%c", THUMB_COLORSPACE, 0, THUMB_COLORSPACE_SRGB, 0); } + cairo_surface_t *max_size_surface = NULL; for (int use = max_size; use >= FIV_THUMBNAIL_SIZE_MIN; use--) { cairo_surface_t *scaled = adjust_thumbnail(surface, fiv_thumbnail_sizes[use].size); @@ -362,8 +428,8 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, save_thumbnail(scaled, path, thum); g_free(path); - if (!*max_size_surface) - *max_size_surface = scaled; + if (!max_size_surface) + max_size_surface = scaled; else cairo_surface_destroy(scaled); } @@ -374,7 +440,7 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, g_free(sum); g_free(uri); cairo_surface_destroy(surface); - return TRUE; + return max_size_surface; } static bool diff --git a/fiv-thumbnail.h b/fiv-thumbnail.h index 35b93fe..301a641 100644 --- a/fiv-thumbnail.h +++ b/fiv-thumbnail.h @@ -55,10 +55,13 @@ extern cairo_user_data_key_t fiv_thumbnail_key_lq; /// Returns this user's root thumbnail directory. gchar *fiv_thumbnail_get_root(void); +/// Attempts to extract any low-quality thumbnail from fast targets. +cairo_surface_t *fiv_thumbnail_extract(GFile *target, GError **error); + /// Generates wide thumbnails of up to the specified size, saves them in cache. -/// Returns the surface used for the maximum size (if the pointer was NULL). -gboolean fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, - cairo_surface_t **max_size_surface, GError **error); +/// Returns the surface used for the maximum size, or an error. +cairo_surface_t *fiv_thumbnail_produce( + GFile *target, FivThumbnailSize max_size, GError **error); /// Retrieves a thumbnail of the most appropriate quality and resolution /// for the target file. diff --git a/fiv.c b/fiv.c index 9486f06..9ec976c 100644 --- a/fiv.c +++ b/fiv.c @@ -1767,11 +1767,47 @@ static const char stylesheet[] = "@define-color fiv-tile @content_view_bg; \ } \ .fiv-information label { padding: 0 4px; }"; +static void +output_thumbnail(const char *path_arg, gboolean extract, const char *size_arg) +{ + if (!path_arg) + exit_fatal("no path given"); + + FivThumbnailSize size = 0; + if (size_arg) { + for (; size < FIV_THUMBNAIL_SIZE_COUNT; size++) { + if (!strcmp( + fiv_thumbnail_sizes[size].thumbnail_spec_name, size_arg)) + break; + } + if (size >= FIV_THUMBNAIL_SIZE_COUNT) + exit_fatal("unknown thumbnail size: %s", size_arg); + } + + GError *error = NULL; + GFile *file = g_file_new_for_commandline_arg(path_arg); + cairo_surface_t *surface = NULL; + if (extract && (surface = fiv_thumbnail_extract(file, &error))) + fiv_io_serialize_to_stdout(surface, FIV_IO_SERIALIZE_LOW_QUALITY); + else if (size_arg && + (g_clear_error(&error), + (surface = fiv_thumbnail_produce(file, size, &error)))) + fiv_io_serialize_to_stdout(surface, 0); + else + g_assert(error != NULL); + + g_object_unref(file); + if (error) + exit_fatal("%s", error->message); + + cairo_surface_destroy(surface); +} + int main(int argc, char *argv[]) { gboolean show_version = FALSE, show_supported_media_types = FALSE, - invalidate_cache = FALSE, browse = FALSE; + invalidate_cache = FALSE, browse = FALSE, extract_thumbnail = FALSE; gchar **path_args = NULL, *thumbnail_size = NULL; const GOptionEntry options[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &path_args, @@ -1782,9 +1818,12 @@ main(int argc, char *argv[]) {"browse", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &browse, "Start in filesystem browsing mode", NULL}, + {"extract-thumbnail", 0, G_OPTION_FLAG_IN_MAIN, + G_OPTION_ARG_NONE, &extract_thumbnail, + "Output any embedded thumbnail (superseding --thumbnail)", NULL}, {"thumbnail", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_STRING, &thumbnail_size, - "Generate thumbnails for an image, up to the given size", "SIZE"}, + "Generate thumbnails, up to SIZE, and output that size", "SIZE"}, {"invalidate-cache", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &invalidate_cache, "Invalidate the wide thumbnail cache", NULL}, @@ -1820,27 +1859,8 @@ main(int argc, char *argv[]) // TODO(p): Complain to the user if there's more than one argument. // Best show the help message, if we can figure that out. const gchar *path_arg = path_args ? path_args[0] : NULL; - if (thumbnail_size) { - if (!path_arg) - exit_fatal("no path given"); - - FivThumbnailSize size = 0; - for (; size < FIV_THUMBNAIL_SIZE_COUNT; size++) - if (!strcmp(fiv_thumbnail_sizes[size].thumbnail_spec_name, - thumbnail_size)) - break; - if (size >= FIV_THUMBNAIL_SIZE_COUNT) - exit_fatal("unknown thumbnail size: %s", thumbnail_size); - - GFile *target = g_file_new_for_commandline_arg(path_arg); - cairo_surface_t *surface = NULL; - if (!fiv_thumbnail_produce(target, size, &surface, &error)) - exit_fatal("%s", error->message); - g_object_unref(target); - if (surface) { - fiv_io_serialize_to_stdout(surface); - cairo_surface_destroy(surface); - } + if (extract_thumbnail || thumbnail_size) { + output_thumbnail(path_arg, extract_thumbnail, thumbnail_size); return 0; } -- cgit v1.2.3