+--[[
+ photo-what-what.lua - pww plugin for Darktable
+
+ Copyright (C) 2025 Trevor Bentley <pww@x.mrmekon.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.
+
+ 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, see <http://www.gnu.org/licenses/>.
+]]
+--[[
+ photo-what-what - use pww to automatically tag selected image(s)
+
+ This launches pww, a utility for automatic image tagging, which
+ analyzes the image and attaches new tags based on its content.
+ These new tags are then imported into Darktable.
+
+ ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
+ * photo-what-what
+ * libexiv2
+ * libgexiv2
+ * libopenimageio
+ * any application that outputs a list of image tags
+
+ USAGE
+ * install & configure pww so it works with `photo-what-what <img>`
+ - or change default paths in darktable settings
+ * copy this script into darktable's lua/contrib/ directory
+ * require this script in darktable's luarc config file
+ * select one or more images
+ * click the "auto-tag images (pww)" button
+
+ BUGS, COMMENTS, SUGGESTIONS
+ * Send to Trevor Bentley: pww@x.mrmekon.com
+
+ CHANGES
+ * 2025-01-13: initial implementation
+]]
+
+local dt = require "darktable"
+local du = require "lib/dtutils"
+local dlog = require "lib/dtutils.log"
+local dsys = require 'lib/dtutils.system'
+
+local namespace <const> = "photo-what-what"
+local LOG_LEVEL <const> = dlog.info
+
+local gettext = dt.gettext.gettext
+
+local function _(msgid)
+ return gettext(msgid)
+end
+
+-- return data structure for script_manager
+local script_data = {}
+
+script_data.metadata = {
+ name = _("photo-what-what"),
+ purpose = _("automatically tag image(s) with photo-what-what analysis tool"),
+ author = "Trevor Bentley <pww@x.mmekon.com>",
+ help = ""
+}
+
+script_data.destroy = nil
+script_data.destroy_method = nil
+script_data.restart = nil
+script_data.show = nil
+
+du.check_min_api_version("9.0.0", "photo-what-what")
+
+local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
+
+local settings = {
+ pww_bin_path = {},
+ pww_script_path = {},
+}
+
+local function default_to(value, default)
+ if value == 0 or value == "" then
+ return default
+ end
+ return value
+end
+
+local function load_preferences()
+ settings.pww_bin_path = default_to(dt.preferences.read(namespace, "pww_bin_path", "string"), "photo-what-what")
+ settings.pww_script_path = default_to(dt.preferences.read(namespace, "pww_script_path", "string"), "")
+end
+
+
+local job
+local function stop_job(job)
+ if job then
+ job.valid = false
+ end
+end
+
+local function call_pww(images)
+ load_preferences()
+ local selection = dt.gui.selection()
+
+ -- process each selected image
+ job = dt.gui.create_job(_("pww auto-tagging"), true, stop_job)
+ for idx, image in ipairs(images) do
+ if not job.valid then
+ break
+ end
+ job.percent = ((idx-1) / #images)
+
+ -- I can't believe DT doesn't do this for us
+ local file = image.path .. PS .. image.filename
+
+ -- build up command from strings. yucky.
+ local cmd = settings.pww_bin_path
+ cmd = string.format("%s -e", cmd)
+ if settings.pww_script_path ~= "" then
+ cmd = string.format("%s -b \"%s\"", cmd, settings.pww_script_path)
+ end
+ cmd = string.format("%s \"%s\"", cmd, file)
+
+ -- run pww
+ local resp = dsys.external_command(cmd)
+ if resp ~= 0 then
+ dt.print("error running photo-what-what")
+ if job.valid then
+ job.valid = false
+ end
+ break
+ end
+
+ -- re-import file to read the updated metadata, since it was changed outside of darktable.
+ -- This does not change the image ID.
+ dt.database.import(file)
+ -- re-select the same selection, since importing clears it
+ dt.gui.selection(selection)
+ end
+
+ if job.valid then
+ job.valid = false
+ end
+end
+
+local function destroy()
+ dt.destroy_event("photo-what-what", "shortcut")
+ dt.gui.libs.image.destroy_action("photo-what-what")
+end
+
+script_data.destroy = destroy
+
+load_preferences()
+
+-- register preferences in darktable settings
+dt.preferences.register(namespace, "pww_script_path", "string", "pww script path", "Full path to identifier application/script used by pww (optional)", "")
+dt.preferences.register(namespace, "pww_bin_path", "string", "pww bin path", "Full path to the photo-what-what executable", "photo-what-what")
+
+-- register button in action menu
+dt.gui.libs.image.register_action(
+ namespace, _("auto-tag images (pww)"),
+ function(event, images) call_pww(images) end,
+ _("auto-tag image(s) with photo-what-what analysis tool")
+)
+
+-- register keyboard shortcut handler
+dt.register_event(
+ namespace, "shortcut",
+ function(event, shortcut) call_pww(dt.gui.action_images) end,
+ _("auto-tag image(s) with photo-what-what analysis tool")
+)
+
+return script_data