Major overhaul of the plugin. Added SDR + HDR (JPEG-XL) support.
This commit is contained in:
@@ -17,8 +17,7 @@
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with darktable. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
]]
|
||||
--[[
|
||||
]] --[[
|
||||
|
||||
ULTRAHDR
|
||||
Add a new storage option to generate UltraHDR JPG images.
|
||||
@@ -32,35 +31,59 @@ assuming the first one encountered is the base SDR image, and the second one is
|
||||
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)
|
||||
* ultrahdr_app (built using https://github.com/google/libultrahdr/blob/main/docs/building.md instructions)
|
||||
* exiftool
|
||||
* ffmpeg
|
||||
|
||||
USAGE
|
||||
* require this file from your main luarc config file
|
||||
* set exiftool and libultrahdr_app tool paths
|
||||
* set binary tool paths
|
||||
|
||||
This plugin will add a new storage option.
|
||||
|
||||
]]
|
||||
local dt = require "darktable"
|
||||
]] 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 dd = require "lib/dtutils.debug"
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
local namespace = 'module_ultrahdr'
|
||||
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/")
|
||||
dt.gettext.bindtextdomain(namespace, dt.configuration.config_dir .. "/lua/locale/")
|
||||
|
||||
local function _(msgid)
|
||||
return gettext(msgid)
|
||||
end
|
||||
|
||||
-- return data structure for script_manager
|
||||
local job
|
||||
|
||||
local GUI = {
|
||||
optionwidgets = {
|
||||
settings_label = {},
|
||||
encoding_variant_combo = {},
|
||||
encoding_settings_box = {},
|
||||
output_settings_label = {},
|
||||
output_settings_box = {},
|
||||
use_original_directory = {},
|
||||
output_directory_widget = {},
|
||||
copy_exif = {},
|
||||
import_to_darktable = {},
|
||||
metadata_path_label = {},
|
||||
metadata_path_widget = {},
|
||||
metadata_path_box = {},
|
||||
edit_executables_button = {},
|
||||
executable_path_widget = {}
|
||||
},
|
||||
options = {},
|
||||
run = {}
|
||||
}
|
||||
|
||||
local flags = {}
|
||||
flags.event_registered = false -- keep track of whether we've added an event callback or not
|
||||
flags.module_installed = false -- keep track of whether the module is module_installed
|
||||
|
||||
local script_data = {}
|
||||
|
||||
@@ -70,247 +93,449 @@ script_data.metadata = {
|
||||
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 PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
local ENCODING_VARIANT_SDR_AND_GAINMAP = 1
|
||||
local ENCODING_VARIANT_SDR_AND_HDR = 2
|
||||
|
||||
local function image_path(image)
|
||||
return image.path .. "/" .. image.filename
|
||||
local function save_preferences()
|
||||
dt.preferences.write(namespace, "encoding_variant", "integer", GUI.optionwidgets.encoding_variant_combo.selected)
|
||||
if GUI.optionwidgets.metadata_path_widget.value then
|
||||
dt.preferences.write(namespace, "metadata_path", "string", GUI.optionwidgets.metadata_path_widget.value)
|
||||
end
|
||||
dt.preferences.write(namespace, "use_original_directory", "bool", GUI.optionwidgets.use_original_directory.value)
|
||||
dt.preferences.write(namespace, "output_directory", "string", GUI.optionwidgets.output_directory_widget.value)
|
||||
dt.preferences.write(namespace, "import_to_darktable", "bool", GUI.optionwidgets.import_to_darktable.value)
|
||||
dt.preferences.write(namespace, "copy_exif", "bool", GUI.optionwidgets.copy_exif.value)
|
||||
end
|
||||
|
||||
local ENCODING_VARIANT_API_4 = 1
|
||||
local function load_preferences()
|
||||
GUI.optionwidgets.encoding_variant_combo.selected = dt.preferences.read(namespace, "encoding_variant", "integer") or
|
||||
ENCODING_VARIANT_SDR_AND_GAINMAP
|
||||
GUI.optionwidgets.metadata_path_widget.value = dt.preferences.read(namespace, "metadata_path", "string")
|
||||
GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool")
|
||||
GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string")
|
||||
GUI.optionwidgets.import_to_darktable.value = dt.preferences.read(namespace, "import_to_darktable", "bool")
|
||||
GUI.optionwidgets.copy_exif.value = dt.preferences.read(namespace, "copy_exif", "bool")
|
||||
end
|
||||
|
||||
local function get_encoding_variant()
|
||||
local encoding_variant = dt.preferences.read("ultrahdr", "encoding variant", "integer")
|
||||
if not encoding_variant then
|
||||
encoding_variant = ENCODING_VARIANT_API_4
|
||||
end
|
||||
return encoding_variant
|
||||
return GUI.optionwidgets.encoding_variant_combo.selected
|
||||
end
|
||||
|
||||
local function merge_ultrahdr_api4(base, gainmap, ultrahdr_app, exiftool, output)
|
||||
local metadata = dt.preferences.read("ultrahdr", "metadata path", "string")
|
||||
if not df.check_if_file_exists(metadata) then
|
||||
dt.print(_("metadata file not found, did you set the correct path?"))
|
||||
log.msg(log.error, "metadata file not found.")
|
||||
return
|
||||
end
|
||||
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(encoding_variant)
|
||||
local settings = {
|
||||
bin = {
|
||||
ultrahdr_app = df.check_if_bin_exists("ultrahdr_app"),
|
||||
exiftool = df.check_if_bin_exists("exiftool"),
|
||||
ffmpeg = df.check_if_bin_exists("ffmpeg")
|
||||
},
|
||||
output = GUI.optionwidgets.output_directory_widget.value,
|
||||
use_original_dir = GUI.optionwidgets.use_original_directory.value,
|
||||
import_to_darktable = GUI.optionwidgets.import_to_darktable.value,
|
||||
copy_exif = GUI.optionwidgets.copy_exif.value,
|
||||
metadata = GUI.optionwidgets.metadata_path_widget.value,
|
||||
tmpdir = dt.configuration.tmp_dir
|
||||
}
|
||||
|
||||
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 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.")
|
||||
if not settings.use_original_dir and not df.check_if_file_exists(settings.output) then
|
||||
dt.print(string.format(_("output directory (%s) not found, did you set the correct path?"), settings.output))
|
||||
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
|
||||
for k, v in pairs(settings.bin) do
|
||||
if not v then
|
||||
dt.print(string.format(_("%s is not found, did you set the path?"), k))
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return ultrahdr_app, exiftool, output
|
||||
if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then
|
||||
if not df.check_if_file_exists(settings.metadata) then
|
||||
dt.print(_("metadata file not found, did you set the correct path?"))
|
||||
log.msg(log.error, "metadata file not found.")
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
return settings
|
||||
end
|
||||
|
||||
local function create_hdr(storage, image_table, extra_data) -- finalize
|
||||
local function get_stacks(images, encoding_variant)
|
||||
local stacks = {}
|
||||
local extra_image_content_type, extra_image_extension
|
||||
if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then
|
||||
extra_image_content_type = "gainmap"
|
||||
elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then
|
||||
extra_image_extension = "jxl"
|
||||
extra_image_content_type = "hdr"
|
||||
end
|
||||
|
||||
local tags = nil
|
||||
-- Group images into sdr, extra pairs based on their original filename, ignoring the extension
|
||||
-- Assume that the first encountered image from each filename is an sdr one, unless it has a tag matching the expected extra_image_type, or has the expected extension
|
||||
for k, v in pairs(images) do
|
||||
local is_extra = false
|
||||
tags = dt.tags.get_tags(v)
|
||||
for ignore, tag in pairs(tags) do
|
||||
if tag.name == extra_image_content_type then
|
||||
is_extra = true
|
||||
end
|
||||
end
|
||||
if extra_image_extension and df.get_filetype(v.filename) == extra_image_extension then
|
||||
is_extra = true
|
||||
end
|
||||
-- we assume every image in the stack is generated from the same source image file
|
||||
local key = df.chop_filetype(v.path .. PS .. v.filename)
|
||||
if stacks[key] == nil then
|
||||
stacks[key] = {}
|
||||
end
|
||||
if stacks[key]["sdr"] or is_extra then
|
||||
stacks[key][extra_image_content_type] = v
|
||||
else
|
||||
stacks[key]["sdr"] = v
|
||||
end
|
||||
end
|
||||
-- remove invalid stacks
|
||||
local count = 0
|
||||
for k, v in pairs(stacks) do
|
||||
if not v["sdr"] or not v[extra_image_content_type] then
|
||||
stacks[k] = nil
|
||||
elseif (v["sdr"].final_width ~= v[extra_image_content_type].final_width) or
|
||||
(v["sdr"].final_height ~= v[extra_image_content_type].final_height) then
|
||||
stacks[k] = nil
|
||||
elseif extra_image_extension and df.get_filetype(v[extra_image_content_type].filename) ~= extra_image_extension then
|
||||
stacks[k] = nil
|
||||
else
|
||||
count = count + 1
|
||||
end
|
||||
end
|
||||
return stacks, count
|
||||
end
|
||||
|
||||
local function stop_job(job)
|
||||
job.valid = false
|
||||
end
|
||||
|
||||
local function execute_cmd(cmd)
|
||||
log.msg(log.debug, cmd)
|
||||
return dtsys.external_command(cmd)
|
||||
end
|
||||
|
||||
local function generate_ultrahdr(encoding_variant, images, settings, step, total_steps)
|
||||
local total_substeps
|
||||
local substep = 0
|
||||
local uhdr
|
||||
|
||||
function update_job_progress()
|
||||
substep = substep + 1
|
||||
if substep > total_substeps then
|
||||
log.msg(log.debug,
|
||||
string.format("total_substeps count is too low for encoding_variant %d", encoding_variant))
|
||||
end
|
||||
job.percent = (total_substeps * step + substep) / (total_steps * total_substeps)
|
||||
end
|
||||
|
||||
if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then
|
||||
total_substeps = 6
|
||||
local msg = string.format(_("Stacking %s"), images["sdr"].filename)
|
||||
log.msg(log.info, msg)
|
||||
dt.print(msg)
|
||||
-- Export both SDR and gainmap to JPEGs
|
||||
local exporter = dt.new_format("jpeg")
|
||||
exporter.quality = 95
|
||||
local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) ..
|
||||
".jpg")
|
||||
exporter:write_image(images["sdr"], sdr)
|
||||
local gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg")
|
||||
exporter:write_image(images["gainmap"], gainmap)
|
||||
|
||||
log.msg(log.debug, string.format(_("Exported files: %s, %s"), sdr, gainmap))
|
||||
update_job_progress()
|
||||
-- Strip EXIFs
|
||||
execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(sdr) .. " -o " ..
|
||||
df.sanitize_filename(sdr .. ".noexif"))
|
||||
execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. " -overwrite_original")
|
||||
update_job_progress()
|
||||
-- Merge files
|
||||
uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg"
|
||||
|
||||
execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -M 0 -i " .. df.sanitize_filename(sdr .. ".noexif") .. " -g " ..
|
||||
df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(settings.metadata) .. " -z " ..
|
||||
df.sanitize_filename(uhdr))
|
||||
update_job_progress()
|
||||
-- Copy SDR's EXIF to UltraHDR file
|
||||
if settings.copy_exif then
|
||||
execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -all>all " ..
|
||||
df.sanitize_filename(uhdr) .. " -overwrite_original -preserve")
|
||||
end
|
||||
update_job_progress()
|
||||
-- Cleanup
|
||||
os.remove(sdr)
|
||||
os.remove(sdr .. ".noexif")
|
||||
os.remove(gainmap)
|
||||
update_job_progress()
|
||||
elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then
|
||||
total_substeps = 5
|
||||
-- https://discuss.pixls.us/t/manual-creation-of-ultrahdr-images/45004/20
|
||||
-- Step 1: Export SDR to PNG (HDR is already a JPEG-XL)
|
||||
local exporter = dt.new_format("png")
|
||||
exporter.bpp = 8
|
||||
local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) ..
|
||||
".png")
|
||||
exporter:write_image(images["sdr"], sdr)
|
||||
uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg"
|
||||
|
||||
update_job_progress()
|
||||
local extra = df.create_unique_filename(settings.tmpdir .. PS .. images["hdr"].filename .. ".raw")
|
||||
|
||||
-- Step 3: Generate libultrahdr RAW images
|
||||
execute_cmd(settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(sdr) .. " -pix_fmt rgba -f rawvideo " ..
|
||||
df.sanitize_filename(sdr .. ".raw"))
|
||||
execute_cmd(settings.bin.ffmpeg .. " -i " ..
|
||||
df.sanitize_filename(images["hdr"].path .. PS .. images["hdr"].filename) ..
|
||||
" -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(extra))
|
||||
update_job_progress()
|
||||
execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr .. ".raw") .. " -p " ..
|
||||
df.sanitize_filename(extra) .. " -a 0 -b 3 -c 1 -C 1 -t 2 -M 1 -s 1 -q 100 -Q 100 -D 1 " ..
|
||||
" -w " .. tostring(images["sdr"].final_width) .. " -h " .. tostring(images["sdr"].final_height) ..
|
||||
" -z " .. df.sanitize_filename(uhdr))
|
||||
update_job_progress()
|
||||
if settings.copy_exif then
|
||||
execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -all>all " ..
|
||||
df.sanitize_filename(uhdr) .. " -overwrite_original -preserve")
|
||||
end
|
||||
-- Cleanup
|
||||
os.remove(sdr)
|
||||
os.remove(sdr .. ".raw")
|
||||
os.remove(extra)
|
||||
update_job_progress()
|
||||
end
|
||||
|
||||
local output_dir = settings.use_original_dir and images["sdr"].path or settings.output
|
||||
local output_file = df.create_unique_filename(output_dir .. PS .. df.get_filename(uhdr))
|
||||
df.file_move(uhdr, output_file)
|
||||
if settings.import_to_darktable then
|
||||
local img = dt.database.import(output_file)
|
||||
-- Add "ultrahdr" tag to the imported image
|
||||
local tagnr = dt.tags.find("ultrahdr")
|
||||
if tagnr == nil then
|
||||
dt.tags.create("ultrahdr")
|
||||
tagnr = dt.tags.find("ultrahdr")
|
||||
end
|
||||
dt.tags.attach(tagnr, img)
|
||||
end
|
||||
update_job_progress()
|
||||
end
|
||||
|
||||
local function main()
|
||||
local saved_log_level = log.log_level()
|
||||
log.log_level(log.info)
|
||||
|
||||
local ultrahdr_app, exiftool, output = assert_settings_correct()
|
||||
if not ultrahdr_app or not exiftool then
|
||||
return
|
||||
end
|
||||
save_preferences()
|
||||
|
||||
local encoding_variant = get_encoding_variant()
|
||||
log.msg(log.info, string.format("using encoding variant %d", encoding_variant))
|
||||
if encoding_variant == ENCODING_VARIANT_API_4 then
|
||||
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_api4(image_table[v["base"]], image_table[v["gainmap"]], ultrahdr_app, exiftool, 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))
|
||||
else
|
||||
local msg = string.format(_("Unknown encoding variant: %d"), encoding_variant)
|
||||
dt.print_error(msg)
|
||||
dt.print(msg)
|
||||
|
||||
local settings = assert_settings_correct(encoding_variant)
|
||||
if not settings then
|
||||
dt.print(_("Export settings are incorrect, exiting..."))
|
||||
log.log_level(saved_log_level)
|
||||
return
|
||||
end
|
||||
|
||||
local images = dt.gui.selection() -- get selected images
|
||||
if #images < 2 then
|
||||
dt.print(_("Select at least 2 images to generate UltraHDR image"))
|
||||
log.log_level(saved_log_level)
|
||||
return
|
||||
end
|
||||
|
||||
local stacks, stack_count = get_stacks(images, encoding_variant)
|
||||
dt.print(string.format(_("Detected %d image stack(s)"), stack_count))
|
||||
if stack_count == 0 then
|
||||
log.log_level(saved_log_level)
|
||||
return
|
||||
end
|
||||
job = dt.gui.create_job(_("Generating UltraHDR images"), true, stop_job)
|
||||
local count = 0
|
||||
for i, v in pairs(stacks) do
|
||||
generate_ultrahdr(encoding_variant, v, settings, count, stack_count)
|
||||
count = count + 1
|
||||
-- sleep for a short moment to give stop_job callback function a chance to run
|
||||
dt.control.sleep(10)
|
||||
end
|
||||
-- stop job and remove progress_bar from ui, but only if not alreay canceled
|
||||
if (job.valid) then
|
||||
job.valid = false
|
||||
end
|
||||
|
||||
log.log_level(saved_log_level)
|
||||
end
|
||||
|
||||
GUI.optionwidgets.settings_label = dt.new_widget("section_label") {
|
||||
label = _("UltraHDR settings")
|
||||
}
|
||||
|
||||
GUI.optionwidgets.output_settings_label = dt.new_widget("section_label") {
|
||||
label = _("Output")
|
||||
}
|
||||
|
||||
GUI.optionwidgets.output_directory_widget = dt.new_widget("file_chooser_button") {
|
||||
title = _("Select directory to write UltraHDR image files to"),
|
||||
is_directory = true
|
||||
}
|
||||
|
||||
GUI.optionwidgets.use_original_directory = dt.new_widget("check_button") {
|
||||
label = _("Export to original directory"),
|
||||
tooltip = _("Write UltraHDR images to the same directory as their original images"),
|
||||
clicked_callback = function(self)
|
||||
GUI.optionwidgets.output_directory_widget.sensitive = not self.value
|
||||
end
|
||||
}
|
||||
|
||||
GUI.optionwidgets.import_to_darktable = dt.new_widget("check_button") {
|
||||
label = _("Import UltraHDRs to Darktable"),
|
||||
tooltip = _("Import UltraHDR images to Darktable library after generating, with an 'ultrahdr' tag attached.")
|
||||
}
|
||||
|
||||
GUI.optionwidgets.copy_exif = dt.new_widget("check_button") {
|
||||
label = _("Copy EXIF data from SDR file(s)"),
|
||||
tooltip = _("Copy EXIF data into UltraHDR file(s) from their SDR sources.")
|
||||
}
|
||||
|
||||
GUI.optionwidgets.output_settings_box = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
GUI.optionwidgets.output_settings_label,
|
||||
GUI.optionwidgets.use_original_directory,
|
||||
GUI.optionwidgets.output_directory_widget,
|
||||
GUI.optionwidgets.import_to_darktable,
|
||||
GUI.optionwidgets.copy_exif
|
||||
}
|
||||
|
||||
GUI.optionwidgets.metadata_path_label = dt.new_widget("label") {
|
||||
label = _("ultrahdr_app metadata.cfg file")
|
||||
}
|
||||
|
||||
GUI.optionwidgets.metadata_path_widget = dt.new_widget("file_chooser_button") {
|
||||
title = "select libultrahdr metadata path",
|
||||
is_directory = false
|
||||
}
|
||||
|
||||
GUI.optionwidgets.metadata_path_box = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
GUI.optionwidgets.metadata_path_label,
|
||||
GUI.optionwidgets.metadata_path_widget
|
||||
}
|
||||
|
||||
GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") {
|
||||
label = _("Source images"),
|
||||
tooltip = string.format(_([[Select types of images in the selection.
|
||||
|
||||
%s: SDR image paired with a monochromatic gain map image
|
||||
%s: SDR image paired with a JPEG-XL HDR image (10-bit, 'PQ P3 RGB' profile recommended)
|
||||
|
||||
UltraHDR image will be created for each pair of images that:
|
||||
- have the same underlying image path + filename (ignoring file extension)
|
||||
- have the same dimensions
|
||||
|
||||
It is assumed that the first image in a pair is the SDR , unless it has a "hdr" / "gainmap" tag.
|
||||
]]), _("SDR + monochrome gainmap"), _("SDR + JPEG-XL HDR")),
|
||||
selected = 0,
|
||||
changed_callback = function(self)
|
||||
if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP then
|
||||
GUI.optionwidgets.metadata_path_box.visible = true
|
||||
else
|
||||
GUI.optionwidgets.metadata_path_box.visible = false
|
||||
end
|
||||
end,
|
||||
_("SDR + monochrome gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP,
|
||||
_("SDR + JPEG-XL HDR") -- ENCODING_VARIANT_SDR_AND_HDR,
|
||||
}
|
||||
|
||||
GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
GUI.optionwidgets.encoding_variant_combo,
|
||||
GUI.optionwidgets.metadata_path_box
|
||||
}
|
||||
|
||||
GUI.optionwidgets.executable_path_widget = df.executable_path_widget({"ultrahdr_app", "exiftool", "ffmpeg"})
|
||||
GUI.optionwidgets.executable_path_widget.visible = false
|
||||
|
||||
GUI.optionwidgets.edit_executables_button = dt.new_widget("button") {
|
||||
label = _("Show / hide executables"),
|
||||
tooltip = _("Show / hide settings for executable files required for the plugin functionality"),
|
||||
clicked_callback = function()
|
||||
GUI.optionwidgets.executable_path_widget.visible = not GUI.optionwidgets.executable_path_widget.visible
|
||||
end
|
||||
}
|
||||
|
||||
GUI.options = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
GUI.optionwidgets.settings_label,
|
||||
GUI.optionwidgets.encoding_settings_box,
|
||||
GUI.optionwidgets.edit_executables_button,
|
||||
GUI.optionwidgets.executable_path_widget,
|
||||
GUI.optionwidgets.output_settings_box
|
||||
}
|
||||
|
||||
GUI.run = dt.new_widget("button") {
|
||||
label = _("Generate UltraHDR"),
|
||||
tooltip = _([[Generate UltraHDR image(s) from selection
|
||||
|
||||
Global options in the export module apply to the SDR image. Make sure that a proper color 'profile' setting is used (e.g. Display P3)
|
||||
]]),
|
||||
clicked_callback = main
|
||||
}
|
||||
|
||||
load_preferences()
|
||||
|
||||
local function install_module()
|
||||
if flags.module_installed then
|
||||
return
|
||||
end
|
||||
dt.register_lib( -- register module
|
||||
namespace, -- Module name
|
||||
_("UltraHDR"), -- name
|
||||
true, -- expandable
|
||||
true, -- resetable
|
||||
{
|
||||
[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}
|
||||
}, -- containers
|
||||
dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
GUI.options,
|
||||
GUI.run
|
||||
}, nil, -- view_enter
|
||||
nil -- view_leave
|
||||
)
|
||||
end
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_storage(namespace)
|
||||
dt.gui.libs[namespace].visible = false
|
||||
end
|
||||
|
||||
|
||||
|
||||
local function is_supported(storage, format)
|
||||
local encoding_variant = get_encoding_variant()
|
||||
if encoding_variant == ENCODING_VARIANT_API_4 then
|
||||
-- API-4 expects compressed base and gainmap
|
||||
-- https://github.com/google/libultrahdr/tree/main?tab=readme-ov-file#encoding-api-outline
|
||||
return format.extension == "jpg"
|
||||
end
|
||||
return false
|
||||
local function restart()
|
||||
dt.gui.libs[namespace].visible = true
|
||||
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
|
||||
if dt.gui.current_view().id == "lighttable" then -- make sure we are in lighttable view
|
||||
install_module() -- register the lib
|
||||
else
|
||||
if not flags.event_registered then -- if we are not in lighttable view then register an event to signal when we might be
|
||||
-- https://www.darktable.org/lua-api/index.html#darktable_register_event
|
||||
dt.register_event(namespace, "view-changed", -- we want to be informed when the view changes
|
||||
function(event, old_view, new_view)
|
||||
if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable
|
||||
install_module() -- register the lib
|
||||
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)
|
||||
flags.event_registered = true -- keep track of whether we have an event handler installed
|
||||
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 function encoding_variant_widget()
|
||||
local metadata_widget = metadata_file_widget()
|
||||
local encoding_variant = get_encoding_variant()
|
||||
local combobox = dt.new_widget("combobox"){
|
||||
label = _("Generate HDR from"),
|
||||
selected = encoding_variant,
|
||||
changed_callback=function(self)
|
||||
dt.preferences.write("ultrahdr", "encoding variant", "integer", self.selected)
|
||||
if self.selected == ENCODING_VARIANT_API_4 then
|
||||
metadata_widget.visible = true
|
||||
else
|
||||
metadata_widget.visible = false
|
||||
end
|
||||
end,
|
||||
_("SDR + gainmap (API-4)") -- ENCODING_VARIANT_API_4,
|
||||
}
|
||||
|
||||
combobox.changed_callback(combobox)
|
||||
return dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
combobox,
|
||||
metadata_widget
|
||||
}
|
||||
end
|
||||
|
||||
local ultrahdr_widget = dt.new_widget("box") {
|
||||
orientation = "vertical",
|
||||
output_directory_widget(),
|
||||
encoding_variant_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
|
||||
script_data.restart = restart
|
||||
script_data.destroy_method = "hide"
|
||||
script_data.show = restart
|
||||
|
||||
return script_data
|
||||
|
||||
Reference in New Issue
Block a user