aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2022-06-07 04:21:04 +0200
committerPřemysl Eric Janouch <p@janouch.name>2022-06-08 02:51:54 +0200
commit8dfbd0dee2a76d4cc3c8b1a339ed6d904b45fd9f (patch)
treee8b3e939086891abd39571767df1cfde5cd8e36a
parent930744e1652c9ec5ad6447c0a2a1e486cae9a2a9 (diff)
downloadfiv-8dfbd0dee2a76d4cc3c8b1a339ed6d904b45fd9f.tar.gz
fiv-8dfbd0dee2a76d4cc3c8b1a339ed6d904b45fd9f.tar.xz
fiv-8dfbd0dee2a76d4cc3c8b1a339ed6d904b45fd9f.zip
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.
-rw-r--r--fiv-browser.c3
-rw-r--r--fiv-thumbnail.c154
-rw-r--r--fiv-thumbnail.h9
-rw-r--r--fiv.c66
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 <libraw.h>
+#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;
}