External preview image extractor/decoder
authorColin Clark <colin.clark@cclark.uk>
Sun, 6 Jun 2021 14:09:25 +0000 (15:09 +0100)
committerColin Clark <colin.clark@cclark.uk>
Sun, 6 Jun 2021 14:09:25 +0000 (15:09 +0100)
Include a new tab in Preferences - Advanced.
This provides the possibility to use an external preview image decoder
or extractor.
Usage is described in the Help file.

doc/docbook/GuideOptionsAdvanced.xml [new file with mode: 0644]
doc/docbook/GuideOptionsMain.xml
src/Makefile.am
src/image-load.c
src/image_load_external.c [new file with mode: 0644]
src/image_load_external.h [new file with mode: 0644]
src/options.h
src/preferences.c
src/rcfile.c

diff --git a/doc/docbook/GuideOptionsAdvanced.xml b/doc/docbook/GuideOptionsAdvanced.xml
new file mode 100644 (file)
index 0000000..e02a562
--- /dev/null
@@ -0,0 +1,88 @@
+<?xml version="1.0" encoding="utf-8"?>\r
+<section id="GuideOptionsAdvanced">\r
+  <title>External preview extraction</title>\r
+  <para>\r
+    The intention of this feature is to give the user the possibility to display image previews of files\r
+    that the standard libraries cannot decode.\r
+    <para />\r
+    An example is the .dng files produced by LG V30 cameras. Neither\r
+    <code>exiv2</code>\r
+    nor\r
+    <code>libraw</code>\r
+    can\r
+    extract a preview, but the command line program\r
+    <code>dcraw</code>\r
+    can.\r
+    <para />\r
+    This feature allows a work-around until the standard libraries provide a solution.\r
+    <para />\r
+    Two command files are required: one to identify which files to process, and one to extract or decode the preview image.\r
+    <para />\r
+    The format for the identification tool is:\r
+    <para />\r
+    <pre>\r
+      <programlisting xml:space="preserve">\r
+        Parameter 1: (input) full path name to the current image.\r
+        <para />\r
+        Returns: 0 for file match, any other value for no match.\r
+      </programlisting>\r
+    </pre>\r
+    <para />\r
+    The format for the extraction tool is:\r
+    <para />\r
+    <pre>\r
+      <programlisting xml:space="preserve">\r
+        Parameter 1: (input) full path name to the current image.\r
+        <para />\r
+        Parameter 2: (output) a temporary file name generated by Geeqie. The tool should load this file with the decoded image.\r
+        <para />\r
+        Returns: not used.\r
+      </programlisting>\r
+    </pre>\r
+    <para />\r
+    This is an example of an identification tool using a shell script:\r
+    <para />\r
+    <pre>\r
+      <programlisting xml:space="preserve">#! /bin/bash\r
+\r
+        filename=$(basename -- "$1")\r
+        extension="${filename##*.}"\r
+\r
+        shopt -s nocasematch\r
+        if [[ $extension == "DNG" ]]\r
+        then\r
+            cameramodel=$(exiv2 -K Exif.Image.UniqueCameraModel -Pt "$1" )\r
+            if [[ $cameramodel  == "LG-H930" ]]\r
+            then\r
+                exit 0\r
+            else\r
+                exit 1\r
+            fi\r
+        else\r
+            exit 1\r
+        fi</programlisting>\r
+    </pre>\r
+  </para>\r
+  <para>\r
+    This is an example of an extraction/decode tool using a shell script:\r
+    <pre>\r
+      <programlisting xml:space="preserve">#! /bin/bash\r
+        dcraw -e -c   "$1" > "$2"</programlisting>\r
+    </pre>\r
+    <para />\r
+    Alternatively:\r
+    <pre>\r
+      <programlisting xml:space="preserve">#! /bin/bash\r
+        gm convert "$1" "$2"</programlisting>\r
+    </pre>\r
+  </para>\r
+  <para>\r
+    If the decode tool requires an output file with a particular extension, use this method:\r
+    <pre>\r
+      <programlisting xml:space="preserve">#! /bin/bash\r
+        tmpfile=$(mktemp --tmpdir=$tempdir geeqie_tmp_XXXXXX.jpg)\r
+        gm convert "$1" $tmpfile\r
+        mv $tmpfile "$2"</programlisting>\r
+    </pre>\r
+  </para>\r
+</section>\r
index 9b927dd..3f7feb6 100644 (file)
@@ -33,6 +33,7 @@
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsStereo.xml" />\r
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsBehavior.xml" />\r
     <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsToolbar.xml" />\r
+    <xi:include xmlns:xi="http://www.w3.org/2001/XInclude" href="GuideOptionsAdvanced.xml" />\r
     <para />\r
 \r
 </chapter>\r
index bff64e1..339af63 100644 (file)
@@ -191,6 +191,8 @@ geeqie_SOURCES = \
        image_load_tiff.h\
        image_load_dds.c\
        image_load_dds.h\
+       image_load_external.c\
+       image_load_external.h\
        image_load_collection.c\
        image_load_collection.h\
        image_load_pdf.c\
index 1250318..07588cb 100644 (file)
@@ -27,6 +27,7 @@
 #include "image_load_tiff.h"
 #include "image_load_dds.h"
 #include "image_load_djvu.h"
+#include "image_load_external.h"
 #include "image_load_pdf.h"
 #include "image_load_psd.h"
 #include "image_load_heif.h"
@@ -36,6 +37,7 @@
 #include "image_load_j2k.h"
 #include "image_load_libraw.h"
 #include "image_load_svgz.h"
+#include "misc.h"
 
 #include "exif.h"
 #include "filedata.h"
@@ -690,7 +692,31 @@ static void image_loader_setup_loader(ImageLoader *il)
        gchar *format;
 #endif
 
+       gint external_preview = 1;
+
        g_mutex_lock(il->data_mutex);
+
+       if (options->external_preview.enable)
+               {
+               gchar *cmd_line;
+               gchar *tilde_filename;
+
+               tilde_filename = expand_tilde(options->external_preview.select);
+
+               cmd_line = g_strdup_printf("\"%s\" \"%s\"" , tilde_filename, il->fd->path);
+
+               external_preview = runcmd(cmd_line);
+               g_free(cmd_line);
+               g_free(tilde_filename);
+               }
+
+       if (external_preview == 0)
+               {
+               DEBUG_1("Using custom external loader");
+               image_loader_backend_set_external(&il->backend);
+               }
+       else
+
 #ifdef HAVE_FFMPEGTHUMBNAILER
        if (il->fd->format_class == FORMAT_CLASS_VIDEO)
                {
diff --git a/src/image_load_external.c b/src/image_load_external.c
new file mode 100644 (file)
index 0000000..7eb1770
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2021 - The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "main.h"
+
+#include "image-load.h"
+#include "image_load_external.h"
+
+#include "misc.h"
+#include "ui_fileops.h"
+
+typedef struct _ImageLoaderExternal ImageLoaderExternal;
+struct _ImageLoaderExternal {
+       ImageLoaderBackendCbAreaUpdated area_updated_cb;
+       ImageLoaderBackendCbSize size_cb;
+       ImageLoaderBackendCbAreaPrepared area_prepared_cb;
+       gpointer data;
+       GdkPixbuf *pixbuf;
+       guint requested_width;
+       guint requested_height;
+       gboolean abort;
+};
+
+static gboolean image_loader_external_load(gpointer loader, const guchar *buf, gsize count, GError **error)
+{
+       ImageLoaderExternal *ld = (ImageLoaderExternal *) loader;
+       ImageLoader *il = ld->data;
+       gchar *cmd_line;
+       gchar *randname;
+       gchar *tilde_filename;
+
+       tilde_filename = expand_tilde(options->external_preview.extract);
+
+       randname = g_strdup("/tmp/geeqie_external_preview_XXXXXX");
+       g_mkstemp(randname);
+
+       cmd_line = g_strdup_printf("\"%s\" \"%s\" \"%s\"" , tilde_filename, il->fd->path, randname);
+
+       runcmd(cmd_line);
+
+       ld->pixbuf = gdk_pixbuf_new_from_file(randname, NULL);
+
+       ld->area_updated_cb(loader, 0, 0, gdk_pixbuf_get_width(ld->pixbuf), gdk_pixbuf_get_height(ld->pixbuf), ld->data);
+
+       g_free(cmd_line);
+       unlink_file(randname);
+       g_free(randname);
+       g_free(tilde_filename);
+
+       return TRUE;
+}
+
+static gpointer image_loader_external_new(ImageLoaderBackendCbAreaUpdated area_updated_cb, ImageLoaderBackendCbSize size_cb, ImageLoaderBackendCbAreaPrepared area_prepared_cb, gpointer data)
+{
+       ImageLoaderExternal *loader = g_new0(ImageLoaderExternal, 1);
+       loader->area_updated_cb = area_updated_cb;
+       loader->size_cb = size_cb;
+       loader->area_prepared_cb = area_prepared_cb;
+       loader->data = data;
+       return (gpointer) loader;
+}
+
+static void image_loader_external_set_size(gpointer loader, int width, int height)
+{
+       ImageLoaderExternal *ld = (ImageLoaderExternal *) loader;
+       ld->requested_width = width;
+       ld->requested_height = height;
+}
+
+static GdkPixbuf* image_loader_external_get_pixbuf(gpointer loader)
+{
+       ImageLoaderExternal *ld = (ImageLoaderExternal *) loader;
+       return ld->pixbuf;
+}
+
+static gchar* image_loader_external_get_format_name(gpointer loader)
+{
+       return g_strdup("external");
+}
+
+static gchar** image_loader_external_get_format_mime_types(gpointer loader)
+{
+       static gchar *mime[] = {"application/octet-stream", NULL};
+       return g_strdupv(mime);
+}
+
+static gboolean image_loader_external_close(gpointer loader, GError **error)
+{
+       return TRUE;
+}
+
+static void image_loader_external_abort(gpointer loader)
+{
+       ImageLoaderExternal *ld = (ImageLoaderExternal *) loader;
+       ld->abort = TRUE;
+}
+
+static void image_loader_external_free(gpointer loader)
+{
+       ImageLoaderExternal *ld = (ImageLoaderExternal *) loader;
+       if (ld->pixbuf) g_object_unref(ld->pixbuf);
+       g_free(ld);
+}
+
+void image_loader_backend_set_external(ImageLoaderBackend *funcs)
+{
+       funcs->loader_new = image_loader_external_new;
+       funcs->set_size = image_loader_external_set_size;
+       funcs->load = image_loader_external_load;
+       funcs->write = NULL;
+       funcs->get_pixbuf = image_loader_external_get_pixbuf;
+       funcs->close = image_loader_external_close;
+       funcs->abort = image_loader_external_abort;
+       funcs->free = image_loader_external_free;
+       funcs->get_format_name = image_loader_external_get_format_name;
+       funcs->get_format_mime_types = image_loader_external_get_format_mime_types;
+}
+
+
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
diff --git a/src/image_load_external.h b/src/image_load_external.h
new file mode 100644 (file)
index 0000000..a6929d8
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2021 - The Geeqie Team
+ *
+ * Author: Colin Clark
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef IMAGE_LOAD_EXTERNAL_H
+#define IMAGE_LOAD_EXTERNAL_H
+
+void image_loader_backend_set_external(ImageLoaderBackend *funcs);
+
+#endif
+/* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 702889b..c1f9256 100644 (file)
@@ -310,6 +310,13 @@ struct _ConfOptions
                } tmp;
        } stereo;
 
+       /* External preview extraction */
+       struct {
+               gboolean enable;
+               gchar *select; /**< path to executable */
+               gchar *extract; /**< path to executable */
+       } external_preview;
+
        /**
         * @struct cp_mv_rn
         * copy move rename
index ad86037..c2d7c0e 100644 (file)
@@ -137,6 +137,8 @@ static GtkWidget *safe_delete_path_entry;
 static GtkWidget *color_profile_input_file_entry[COLOR_PROFILE_INPUTS];
 static GtkWidget *color_profile_input_name_entry[COLOR_PROFILE_INPUTS];
 static GtkWidget *color_profile_screen_file_entry;
+static GtkWidget *external_preview_select_entry;
+static GtkWidget *external_preview_extract_entry;
 
 static GtkWidget *sidecar_ext_entry;
 static GtkWidget *help_search_engine_entry;
@@ -434,6 +436,10 @@ static void config_window_apply(void)
        options->hide_window_in_fullscreen = c_options->hide_window_in_fullscreen;
        config_entry_to_option(help_search_engine_entry, &options->help_search_engine, NULL);
 
+       options->external_preview.enable = c_options->external_preview.enable;
+       config_entry_to_option(external_preview_select_entry, &options->external_preview.select, NULL);
+       config_entry_to_option(external_preview_extract_entry, &options->external_preview.extract, NULL);
+
        options->read_metadata_in_idle = c_options->read_metadata_in_idle;
 
        options->star_rating.star = c_options->star_rating.star;
@@ -510,7 +516,8 @@ static void config_window_help_cb(GtkWidget *widget, gpointer data)
        "GuideOptionsStereo.html",
        "GuideOptionsBehavior.html",
        "GuideOptionsToolbar.html",
-       "GuideOptionsToolbar.html"
+       "GuideOptionsToolbar.html",
+       "GuideOptionsAdvanced.html"
        };
 
        i = gtk_notebook_get_current_page(GTK_NOTEBOOK(notebook));
@@ -3587,6 +3594,92 @@ static void config_tab_toolbar_status(GtkWidget *notebook)
        gtk_widget_show(vbox);
 }
 
+/* advanced tab */
+static gint extension_sort_cb(gconstpointer a, gconstpointer b)
+{
+       return g_strcmp0((gchar *)a, (gchar *)b);
+}
+
+static void config_tab_advanced(GtkWidget *notebook)
+{
+       GtkWidget *vbox;
+       GtkWidget *group;
+       GSList *formats_list;
+       GList *extensions_list = NULL;
+       gchar **extensions;
+       GtkWidget *tabcomp;
+       GdkPixbufFormat *fm;
+       gint i;
+       GString *types_string = g_string_new(NULL);
+
+       vbox = scrolled_notebook_page(notebook, _("Advanced"));
+       group = pref_group_new(vbox, FALSE, _("External preview extraction"), GTK_ORIENTATION_VERTICAL);
+
+       pref_checkbox_new_int(group, _("Use external preview extraction -  Requires restart"), options->external_preview.enable, &c_options->external_preview.enable);
+
+       pref_spacer(group, PREF_PAD_GROUP);
+
+       formats_list = gdk_pixbuf_get_formats();
+
+       while (formats_list)
+               {
+               fm = formats_list->data;
+               extensions = gdk_pixbuf_format_get_extensions(fm);
+
+               i = 0;
+               while (extensions[i])
+                       {
+                       extensions_list = g_list_insert_sorted(extensions_list, g_strdup(extensions[i]), extension_sort_cb);
+                       i++;
+                       }
+
+               g_strfreev(extensions);
+               formats_list = formats_list->next;
+               }
+
+       while (extensions_list)
+               {
+               if (types_string->len == 0)
+                       {
+                       types_string = g_string_append(types_string, extensions_list->data);
+                       }
+               else
+                       {
+                       types_string = g_string_append(types_string, ", ");
+                       types_string = g_string_append(types_string, extensions_list->data);
+                       }
+
+               extensions_list = extensions_list->next;
+               }
+
+       types_string = g_string_prepend(types_string, _("Usable file types:\n"));
+       pref_label_new(group, types_string->str);
+       GtkWidget *types_string_label = gtk_label_new(types_string->str);
+       gtk_label_set_line_wrap(GTK_LABEL(types_string_label), TRUE);
+
+       pref_spacer(group, PREF_PAD_GROUP);
+
+       group = pref_group_new(vbox, FALSE, _("File identification tool"), GTK_ORIENTATION_VERTICAL);
+       external_preview_select_entry = gtk_entry_new();
+       tabcomp = tab_completion_new(&external_preview_select_entry, options->external_preview.select, NULL, NULL, NULL, NULL);
+       tab_completion_add_select_button(external_preview_select_entry, _("Select file identification tool"), FALSE);
+       gtk_box_pack_start(GTK_BOX(group), tabcomp, TRUE, TRUE, 0);
+       gtk_widget_show(tabcomp);
+
+       group = pref_group_new(vbox, FALSE, _("Preview extraction tool"), GTK_ORIENTATION_VERTICAL);
+       external_preview_extract_entry = gtk_entry_new();
+       tabcomp = tab_completion_new(&external_preview_extract_entry, options->external_preview.extract, NULL, NULL, NULL, NULL);
+       tab_completion_add_select_button(external_preview_extract_entry, _("Select preview extraction tool"), FALSE);
+       gtk_box_pack_start(GTK_BOX(group), tabcomp, TRUE, TRUE, 0);
+       gtk_widget_show(tabcomp);
+
+       gtk_widget_show(vbox);
+
+       g_slist_free(formats_list);
+       string_list_free(extensions_list);
+       g_string_free(types_string, TRUE);
+}
+
 /* stereo tab */
 static void config_tab_stereo(GtkWidget *notebook)
 {
@@ -3714,6 +3807,7 @@ static void config_window_create(LayoutWindow *lw)
        config_tab_behavior(notebook);
        config_tab_toolbar_main(notebook);
        config_tab_toolbar_status(notebook);
+       config_tab_advanced(notebook);
 
        gtk_notebook_set_current_page(GTK_NOTEBOOK(notebook), lw->options.preferences_window.page_number);
 
index cac00af..7f3d384 100644 (file)
@@ -353,6 +353,10 @@ static void write_global_attributes(GString *outstr, gint indent)
        WRITE_NL(); WRITE_BOOL(*options, marks_save);
        WRITE_NL(); WRITE_CHAR(*options, help_search_engine);
 
+       WRITE_NL(); WRITE_BOOL(*options, external_preview.enable);
+       WRITE_NL(); WRITE_CHAR(*options, external_preview.select);
+       WRITE_NL(); WRITE_CHAR(*options, external_preview.extract);
+
        WRITE_NL(); WRITE_BOOL(*options, with_rename);
        WRITE_NL(); WRITE_BOOL(*options, collections_on_top);
        WRITE_NL(); WRITE_BOOL(*options, hide_window_in_fullscreen);
@@ -786,6 +790,10 @@ static gboolean load_global_params(const gchar **attribute_names, const gchar **
                if (READ_BOOL(*options, marks_save)) continue;
                if (READ_CHAR(*options, help_search_engine)) continue;
 
+               if (READ_BOOL(*options, external_preview.enable)) continue;
+               if (READ_CHAR(*options, external_preview.select)) continue;
+               if (READ_CHAR(*options, external_preview.extract)) continue;
+
                if (READ_BOOL(*options, collections_on_top)) continue;
                if (READ_BOOL(*options, hide_window_in_fullscreen)) continue;