[simple-scan] initial support for executing postprocessing scripts
- From: Bartosz <bkosiorek src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [simple-scan] initial support for executing postprocessing scripts
- Date: Thu, 27 Jan 2022 14:26:28 +0000 (UTC)
commit db90d5b188267b6377305d36b6199f196902e263
Author: Alex Vogt <a vogt linexus de>
Date: Thu Jan 6 20:06:09 2022 +0100
initial support for executing postprocessing scripts
data/org.gnome.SimpleScan.gschema.xml | 20 +++++++
data/ui/preferences-dialog.ui | 98 +++++++++++++++++++++++++++++++++++
src/app-window.vala | 10 +++-
src/book.vala | 30 +++++++++--
src/meson.build | 1 +
src/postprocessor.vala | 44 ++++++++++++++++
src/preferences-dialog.vala | 35 +++++++++++++
src/simple-scan-postprocessing.sh | 83 +++++++++++++++++++++++++++++
8 files changed, 316 insertions(+), 5 deletions(-)
---
diff --git a/data/org.gnome.SimpleScan.gschema.xml b/data/org.gnome.SimpleScan.gschema.xml
index 290add1f..03f19227 100644
--- a/data/org.gnome.SimpleScan.gschema.xml
+++ b/data/org.gnome.SimpleScan.gschema.xml
@@ -77,5 +77,25 @@
<summary>Delay in millisecond between pages</summary>
<description>Delay in millisecond between pages.</description>
</key>
+ <key name="postproc-enabled" type="b">
+ <default>false</default>
+ <summary>Whether or not postprocessing is enabled</summary>
+ <description>Whether or not postprocessing is enabled.</description>
+ </key>
+ <key name="postproc-script" type="s">
+ <default>''</default>
+ <summary>The path to the postprocessing script</summary>
+ <description>The path to the postprocessing script.</description>
+ </key>
+ <key name="postproc-arguments" type="s">
+ <default>''</default>
+ <summary>Additional arguments for the postprocessing script</summary>
+ <description>Additional arguments for the postprocessing script.</description>
+ </key>
+ <key name="postproc-keep-original" type="b">
+ <default>false</default>
+ <summary>Whether or not to keep the original, unprocessed file</summary>
+ <description>Whether or not to keep the original, unprocessed file. The "_orig" filename will be added
to the filename immediately before the file extension. </description>
+ </key>
</schema>
</schemalist>
diff --git a/data/ui/preferences-dialog.ui b/data/ui/preferences-dialog.ui
index 4bc42d62..fe63150a 100644
--- a/data/ui/preferences-dialog.ui
+++ b/data/ui/preferences-dialog.ui
@@ -261,6 +261,104 @@
</child>
</object>
</child>
+ <child>
+ <object class="HdyPreferencesGroup">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" comments="Preferences Dialog: Section label for
postprocessing settings">Postprocessing</property>
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" comments="Switch to enable postprocessing">_Enable
Postprocessing</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">postproc_enable_box</property>
+ <child>
+ <object class="GtkBox" id="postproc_enable_box">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkSwitch" id="postproc_enable_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" comments="Label beside postprocesing script name
entry">_Script</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">postproc_script_entry</property>
+ <child>
+ <object class="GtkBox" id="postproc_script_box">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="homogeneous">True</property>
+
+ <child>
+ <object class="GtkEntry" id="postproc_script_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" comments="Label beside postprocesing arguments
entry">_Script arguments</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">postproc_args_entry</property>
+ <child>
+ <object class="GtkBox" id="postproc_args_box">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="homogeneous">True</property>
+
+ <child>
+ <object class="GtkEntry" id="postproc_args_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="HdyActionRow">
+ <property name="visible">True</property>
+ <property name="title" translatable="yes" comments="Label beside keep keep original file
radio">_Keep original file</property>
+ <property name="use_underline">True</property>
+ <property name="activatable_widget">postproc_keep_original_box</property>
+ <child>
+ <object class="GtkBox" id="postproc_keep_original_box">
+ <property name="visible">True</property>
+ <property name="valign">center</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkSwitch" id="postproc_keep_original_switch">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="hexpand">False</property>
+ <property name="active">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
</object>
</child>
</template>
diff --git a/src/app-window.vala b/src/app-window.vala
index 8c0995a7..cea6837d 100644
--- a/src/app-window.vala
+++ b/src/app-window.vala
@@ -720,7 +720,10 @@ public class AppWindow : Hdy.ApplicationWindow
save_button.sensitive = false;
try
{
- yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file, (fraction) =>
+ yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file,
+ settings.get_boolean ("postproc-enabled"), settings.get_string ("postproc-script"),
+ settings.get_string ("postproc-arguments"), settings.get_boolean ("postproc-keep-original"),
+ (fraction) =>
{
progress_bar.set_fraction (fraction);
}, cancellable);
@@ -1473,7 +1476,10 @@ public class AppWindow : Hdy.ApplicationWindow
filename = "scan.jpg";
}
var file = File.new_for_path (Path.build_filename (dir, filename));
- yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file, null, null);
+ yield book.save_async (mime_type, settings.get_int ("jpeg-quality"), file,
+ settings.get_boolean ("postproc-enabled"), settings.get_string ("postproc-script"),
+ settings.get_string ("postproc-arguments"), settings.get_boolean ("postproc-keep-original"),
+ null, null);
var command_line = "xdg-email";
if (mime_type == "application/pdf")
command_line += " --attach %s".printf (file.get_path ());
diff --git a/src/book.vala b/src/book.vala
index 798fe980..d2aa54da 100644
--- a/src/book.vala
+++ b/src/book.vala
@@ -136,10 +136,14 @@ public class Book : Object
return pages.index (page);
}
- public async void save_async (string mime_type, int quality, File file, ProgressionCallback?
progress_cb, Cancellable? cancellable = null) throws Error
+ public async void save_async (string mime_type, int quality, File file,
+ bool postproc_enabled, string postproc_script, string postproc_arguments, bool
postproc_keep_original,
+ ProgressionCallback? progress_cb, Cancellable? cancellable = null) throws Error
{
var book_saver = new BookSaver ();
- yield book_saver.save_async (this, mime_type, quality, file, progress_cb, cancellable);
+ yield book_saver.save_async (this, mime_type, quality, file,
+ postproc_enabled, postproc_script, postproc_arguments, postproc_keep_original,
+ progress_cb, cancellable);
}
}
@@ -155,12 +159,15 @@ private class BookSaver
private AsyncQueue<WriteTask> write_queue;
private ThreadPool<EncodeTask> encoder;
private SourceFunc save_async_callback;
+ private Postprocessor postprocessor = new Postprocessor();
/* save_async get called in the main thread to start saving. It
* distributes all encode tasks to other threads then yield so
* the ui can continue operating. The method then return once saving
* is completed, cancelled, or failed */
- public async void save_async (Book book, string mime_type, int quality, File file, ProgressionCallback?
progression_callback, Cancellable? cancellable) throws Error
+ public async void save_async (Book book, string mime_type, int quality, File file,
+ bool postproc_enabled, string postproc_script, string postproc_arguments, bool
postproc_keep_original,
+ ProgressionCallback? progression_callback, Cancellable? cancellable) throws Error
{
var timer = new Timer ();
@@ -239,6 +246,23 @@ private class BookSaver
timer.stop ();
debug ("Save time: %f seconds", timer.elapsed (null));
+
+ if ( postproc_enabled ) {
+ /* Perform post-processing */
+ timer = new Timer ();
+ var return_code = postprocessor.process(postproc_script,
+ mime_type, // MIME Type
+ postproc_keep_original, // Keep Original
+ file.get_path(), // Filename
+ postproc_arguments // Arguments
+ );
+ if ( return_code != 0 ) {
+ warning ("Postprocessing script execution failed. ");
+ }
+ timer.stop ();
+ debug ("Postprocessing time: %f seconds", timer.elapsed (null));
+ }
+
}
/* Those methods are run in the encoder threads pool. It process
diff --git a/src/meson.build b/src/meson.build
index 3f699ebc..240b56d0 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -22,6 +22,7 @@ simple_scan = executable ('simple-scan',
'page.vala',
'page-icon.vala',
'page-view.vala',
+ 'postprocessor.vala',
'preferences-dialog.vala',
'simple-scan.vala',
'scanner.vala',
diff --git a/src/postprocessor.vala b/src/postprocessor.vala
new file mode 100644
index 00000000..2d036c91
--- /dev/null
+++ b/src/postprocessor.vala
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+/*
+ * Copyright (C) 2022 Alexander Vogt
+ * Author: Alexander Vogt <a vogt fulguritus com>
+ *
+ * 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 3 of the License, or (at your option) any later
+ * version. See http://www.gnu.org/copyleft/gpl.html the full text of the
+ * license.
+ */
+
+public class Postprocessor {
+
+ public Postprocessor(){
+
+ }
+
+ public int process(string script, string mime_type, bool keep_original, string source_file, string
arguments) throws Error {
+ // Code copied and adapted from https://valadoc.org/glib-2.0/GLib.Process.spawn_sync.html
+ string[] spawn_args = {script, mime_type, keep_original ? "true" : "false", source_file, arguments };
+ string[] spawn_env = Environ.get ();
+ string process_stdout;
+ string process_stderr;
+ int process_status;
+
+ print ("Executing script%s\n", script);
+ Process.spawn_sync (null, // inherit parent's working dir
+ spawn_args,
+ spawn_env,
+ SpawnFlags.SEARCH_PATH,
+ null,
+ out process_stdout,
+ out process_stderr,
+ out process_status);
+ debug ("status: %d\n", process_status);
+ debug ("STDOUT: \n");
+ debug ("process_stdout");
+ debug ("STDERR: \n");
+ debug ("process_stderr");
+
+ return process_status;
+ }
+}
diff --git a/src/preferences-dialog.vala b/src/preferences-dialog.vala
index 8d992abb..02bbaf8d 100644
--- a/src/preferences-dialog.vala
+++ b/src/preferences-dialog.vala
@@ -51,6 +51,14 @@ private class PreferencesDialog : Hdy.PreferencesWindow
private unowned Gtk.Adjustment brightness_adjustment;
[GtkChild]
private unowned Gtk.Adjustment contrast_adjustment;
+ [GtkChild]
+ private unowned Gtk.Switch postproc_enable_switch;
+ [GtkChild]
+ private unowned Gtk.Entry postproc_script_entry;
+ [GtkChild]
+ private unowned Gtk.Entry postproc_args_entry;
+ [GtkChild]
+ private unowned Gtk.Switch postproc_keep_original_switch;
public PreferencesDialog (Settings settings)
{
@@ -133,6 +141,33 @@ private class PreferencesDialog : Hdy.PreferencesWindow
page_delay_6s_button.toggled.connect ((button) => { if (button.active) settings.set_int
("page-delay", 6000); });
page_delay_10s_button.toggled.connect ((button) => { if (button.active) settings.set_int
("page-delay", 10000); });
page_delay_15s_button.toggled.connect ((button) => { if (button.active) settings.set_int
("page-delay", 15000); });
+
+ // Postprocessing settings
+ var postproc_enabled = settings.get_boolean ("postproc-enabled");
+ postproc_enable_switch.set_state(postproc_enabled);
+ toggle_postproc_visibility (postproc_enabled);
+ postproc_enable_switch.state_set.connect ((is_active) => { toggle_postproc_visibility (is_active);
+ settings.set_boolean("postproc-enabled",
is_active);
+ return true; });
+
+ var postproc_script = settings.get_string("postproc-script");
+ postproc_script_entry.set_text(postproc_script);
+ postproc_script_entry.changed.connect (() => { settings.set_string("postproc-script",
postproc_script_entry.get_text()); });
+
+ var postproc_arguments = settings.get_string("postproc-arguments");
+ postproc_args_entry.set_text(postproc_arguments);
+ postproc_args_entry.changed.connect (() => { settings.set_string("postproc-arguments",
postproc_args_entry.get_text()); });
+
+ var postproc_keep_original = settings.get_boolean ("postproc-keep-original");
+ postproc_keep_original_switch.set_state(postproc_keep_original);
+ postproc_keep_original_switch.state_set.connect ((is_active) => {
settings.set_boolean("postproc-keep-original", is_active);
+ return true; });
+ }
+
+ private void toggle_postproc_visibility(bool enabled) {
+ postproc_script_entry.get_parent ().get_parent ().get_parent ().get_parent ().set_visible(enabled);
+ postproc_args_entry.get_parent ().get_parent ().get_parent ().get_parent ().set_visible(enabled);
+ postproc_keep_original_switch.get_parent ().get_parent ().get_parent ().get_parent
().set_visible(enabled);
}
private void set_page_side (ScanSide page_side)
diff --git a/src/simple-scan-postprocessing.sh b/src/simple-scan-postprocessing.sh
new file mode 100755
index 00000000..39fb4612
--- /dev/null
+++ b/src/simple-scan-postprocessing.sh
@@ -0,0 +1,83 @@
+#!/bin/bash
+# SPDX-License-Identifier: GPL-3.0-or-later
+# Copyright (C) 2022 Alexander Vogt
+# Author: Alexander Vogt <a vogt fulguritus com>
+#
+# Sample postprocessing script for gnome-simple-scan for OCR in PDFs
+#
+# This script first identifies a suitable instance of ocrmypdf
+# (https://github.com/ocrmypdf/OCRmyPDF) and then applies this as a
+# postprocessing step to PDFs generated by simple-scan.
+#
+# Usage:
+# =====
+# simple-scan-postprocessing mime-type keep-origin input-file args
+#
+# Currently, only mime-type "application/pdf" is supported, the script will
+# exit without an error if "image/jpeg", "image/png", or "image/webp" is
+# provided. Any other mime-type results in an error.
+# All args are provided to ocrmypdf.
+# If keep-origin is set to "true", a copy of the source file is kept.
+#
+# Example:
+# =======
+# simple-scan-postprocessing application/pdf true scan.pdf -l eng+deu
+# simple-scan-postprocessing application/pdf true scan.pdf -rcd --jbig2-lossy -l deu
+#
+set -e +m
+
+# Arguments
+mime_type="$1"
+keep_original="$2"
+target="$3"
+remainder="${@:4}"
+# Globals
+_ocrmypdfcontainer="jbarlow83/ocrmypdf"
+
+source="${target%.*}_orig.${target##*.}"
+
+# Helper functions
+function findOcrMyPdf() {
+ # Determines the path of ocrmypdf in the following order:
+ # 1. ocrmypdf from the $PATH (local installation)
+ # 2. ocrmypdf through podman (if podman in $PATH)
+ # 3. ocrmypdf through docker (if podman in $PATH)
+ _ocrmypdf=$(which ocrmypdf) && return
+ _ocrmypdf="$(which podman) run --rm -i ${_ocrmypdfcontainer} " && return
+ _ocrmypdf="$(which docker) run --rm -i ${_ocrmypdfcontainer} "
+ if [ $? -ne 0 ]; then
+ echo "No suitable instance of ocrmypdf found. Please check your setup. "
+ exit 1
+ fi
+}
+
+case ${mime_type} in
+ "application/pdf")
+ mv "$target" "$source" # create a backup
+
+ # Determine the version of ocrmypdf to use
+ findOcrMyPdf
+ # Execute OCR
+ ${_ocrmypdf} ${remainder} - - <"$source" >"$target"
+ ;;
+ "image/jpeg")
+ exit 0 # Nothing implemented
+ ;;
+ "image/png")
+ exit 0 # Nothing implemented
+ ;;
+ "image/webp")
+ exit 0 # Nothing implemented
+ ;;
+ *)
+ echo "Unsupported mime-type \"${mime_type}\""
+ exit 1
+ ;;
+esac
+
+# Clean up
+if [ "$keep_original" == "true" ]; then
+ exit 0
+else
+ rm "$source"
+fi
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]