From 3b520d62918dc21129bd0672ea0b90b1edbfd9aa Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 23 Aug 2024 17:53:03 +0200 Subject: [PATCH] Added UltraHDR export plugin to generate UltraHDR JPEG images. --- contrib/ultrahdr.lua | 270 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 270 insertions(+) create mode 100644 contrib/ultrahdr.lua diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua new file mode 100644 index 0000000..f3b4d19 --- /dev/null +++ b/contrib/ultrahdr.lua @@ -0,0 +1,270 @@ +--[[ + + UltraHDR storage for darktable + + copyright (c) 2024 Krzysztof Kotowicz + + darktable 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. + + darktable 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 darktable. If not, see . + +]] +--[[ + +ULTRAHDR +Add a new storage option to generate UltraHDR JPG images. + +https://developer.android.com/media/platform/hdr-image-format + +Of all exported files, the storage detects pairs of files generated from the same source image, +assuming the first one encountered is the base SDR image, and the second one is the gainmap +(alternatively, you can tag the gainmaps with a "gainmap" tag). + +The images are merged using libultrahdr example application (ultrahdr_app). + +ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT +* ultrahdr_app (from https://github.com/google/libultrahdr example dir) +* exiftool + +USAGE +* require this file from your main luarc config file +* set exiftool and libultrahdr_app tool paths + +This plugin will add a new storage option. + +]] +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local log = require "lib/dtutils.log" +local dtsys = require "lib/dtutils.system" +local gettext = dt.gettext.gettext + +local namespace = 'module_ultrahdr' + +-- works with darktable API version from 5.0.0 on +du.check_min_api_version("7.0.0", "ultrahdr") + +dt.gettext.bindtextdomain("ultrahdr", dt.configuration.config_dir .. "/lua/locale/") + +local function _(msgid) + return gettext(msgid) +end + +-- return data structure for script_manager + +local script_data = {} + +script_data.metadata = { + name = "ultrahdr", + purpose = _("generate UltraHDR images"), + author = "Krzysztof Kotowicz" +} + +script_data.destroy = nil -- function to destory the script +script_data.destroy_method = nil -- set to hide for libs since we can't destroy them commpletely yet, otherwise leave as nil +script_data.restart = nil -- how to restart the (lib) script after it's been hidden - i.e. make it visible again +script_data.show = nil -- only required for libs since the destroy_method only hides them + +local function image_path(image) + return image.path .. "/" .. image.filename +end + +local function merge_ultrahdr(base, gainmap, ultrahdr_app, exiftool, metadata, output) + local base_tmp = df.chop_filetype(base) .. ".tmp" + local hdr = df.chop_filetype(base) .. "_hdr." .. df.get_filetype(base) + dtsys.external_command(exiftool .. " -all= " .. df.sanitize_filename(base) .. " -o " .. + df.sanitize_filename(base_tmp)) + dtsys.external_command(exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. " -overwrite_original") + dtsys.external_command(ultrahdr_app .. " -m 0 -i " .. df.sanitize_filename(base_tmp) .. " -g " .. + df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(metadata) .. " -z " .. + df.sanitize_filename(hdr)) + dtsys.external_command(exiftool .. " -tagsfromfile " .. df.sanitize_filename(base) .. " -all>all " .. + df.sanitize_filename(hdr) .. " -overwrite_original") + df.file_move(hdr, df.create_unique_filename(output .. "/" .. df.get_filename(hdr))) + os.remove(base) + os.remove(base_tmp) + os.remove(gainmap) +end + +local function assert_settings_correct() + local ultrahdr_app = df.check_if_bin_exists("ultrahdr_app") + log.msg(log.debug, "ultrahdr_app set to ", ultrahdr_app) + local exiftool = df.check_if_bin_exists("exiftool") + log.msg(log.debug, "exiftool set to ", exiftool) + local metadata = dt.preferences.read("ultrahdr", "metadata path", "string") + log.msg(log.debug, "metadata set to ", metadata) + local output = dt.preferences.read("ultrahdr", "output dir", "string") + log.msg(log.debug, "output dir set to ", output) + + if not ultrahdr_app then + dt.print(_("ultrahdr_app is not found, did you set the path?")) + log.msg(log.error, "ultrahdr_app executable not found. Check if the executable is installed.") + log.msg(log.error, "If the executable is installed, check that the path is set correctly.") + return + end + + if not exiftool then + dt.print(_("exiftool is not found, did you set the path?")) + log.msg(log.error, "exiftool executable not found. Check if the executable is installed.") + log.msg(log.error, "If the executable is installed, check that the path is set correctly.") + return + end + + if not df.check_if_file_exists(metadata) then + dt.print(_("metadata file not found, did you set the path?")) + log.msg(log.error, "metadata file not found.") + return + end + + return ultrahdr_app, exiftool, metadata, output +end + +local function create_hdr(storage, image_table, extra_data) -- finalize + local saved_log_level = log.log_level() + log.log_level(log.info) + + local ultrahdr_app, exiftool, metadata, output = assert_settings_correct() + if not ultrahdr_app or not exiftool then + return + end + local merged = 0 + for ignore, v in pairs(extra_data) do + if not v then + goto continue + end + local msg = string.format(_("Merging %s and %s"), df.get_filename(image_table[v["base"]]), + df.get_filename(image_table[v["gainmap"]])) + log.msg(log.info, msg) + dt.print(msg) + merge_ultrahdr(image_table[v["base"]], image_table[v["gainmap"]], ultrahdr_app, exiftool, metadata, output) + merged = merged + 1 + ::continue:: + end + for ignore, v in pairs(image_table) do + if df.check_if_file_exists(v) then os.remove(v) end + end + dt.print(string.format(_("Created %d UltraHDR image(s) in %s"), merged, output)) + log.log_level(saved_log_level) +end + +local function destroy() + dt.destroy_storage(namespace) +end + +local function is_supported(storage, format) + if format.extension == "jpg" then + return true + end + return false +end + +local function initialize(storage, format, images, high_quality, extra_data) + local saved_log_level = log.log_level() + log.log_level(log.info) + local tags = nil + -- Group images into base, gainmap pairs based on their original filename. + -- Assume that the first encountered image from each filename is a base one, unless it has a "gainmap" tag. + for k, v in pairs(images) do + local has_gainmap_tag = false + tags = dt.tags.get_tags(v) + for ignore, tag in pairs(tags) do + if tag.name == "gainmap" then + has_gainmap_tag = true + end + end + local key = image_path(v) + if extra_data[key] == nil then + extra_data[key] = {} + end + if extra_data[key]["base"] or has_gainmap_tag then + extra_data[key]["gainmap"] = v + else + extra_data[key]["base"] = v + end + end + -- remove incomplete entries + for k, v in pairs(extra_data) do + if not v["base"] or not v["gainmap"] then + extra_data[k] = nil + end + end + log.log_level(saved_log_level) + return nil +end + +local function metadata_file_widget() + local box_widgets = {} + table.insert(box_widgets, dt.new_widget("label") { + label = "libultrahdr metadata file" + }) + local path = dt.preferences.read("ultrahdr", "metadata path", "string") + if not path then + path = "" + end + table.insert(box_widgets, dt.new_widget("file_chooser_button") { + title = "select libultrahdr metadata path", + value = path, + is_directory = false, + changed_callback = function(self) + if df.check_if_file_exists(self.value) then + dt.preferences.write("ultrahdr", "metadata path", "string", self.value) + end + end + }) + + local box = dt.new_widget("box") { + orientation = "vertical", + table.unpack(box_widgets) + } + return box +end + +local function output_directory_widget() + local box_widgets = {} + table.insert(box_widgets, dt.new_widget("label") { + label = "output directory" + }) + local path = dt.preferences.read("ultrahdr", "output dir", "string") + if not path then + path = "" + end + table.insert(box_widgets, dt.new_widget("file_chooser_button") { + title = "select libultrahdr metadata path", + value = path, + is_directory = true, + changed_callback = function(self) + dt.preferences.write("ultrahdr", "output dir", "string", self.value) + end + }) + + local box = dt.new_widget("box") { + orientation = "vertical", + table.unpack(box_widgets) + } + return box +end + +local ultrahdr_widget = dt.new_widget("box") { + orientation = "vertical", + metadata_file_widget(), + output_directory_widget(), + df.executable_path_widget({"ultrahdr_app", "exiftool"}) +} + +-- Register + +dt.register_storage(namespace, _("UltraHDR JPEG"), nil, create_hdr, is_supported, initialize, ultrahdr_widget) + +script_data.destroy = destroy + +return script_data