From c9943a206519352b156b7312b22a3707d87cb3dc Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 25 Mar 2023 17:02:48 -0400 Subject: [PATCH 001/169] lib/dtutils/string - Added logging capability with the ability to set library logging level from a user script --- lib/dtutils/string.lua | 117 +++++++++++++++++++++++++++++++---------- 1 file changed, 88 insertions(+), 29 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index f7159a2f..6b716929 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -1,6 +1,12 @@ local dtutils_string = {} local dt = require "darktable" +local du = require "lib/dtutils" +local log = require "lib/dtutils.log" + +local DEFAULT_LOG_LEVEL = log.error + +dtutils_string.log_level = DEFAULT_LOG_LEVEL dtutils_string.libdoc = { Name = [[dtutils.string]], @@ -48,6 +54,8 @@ dtutils_string.libdoc.functions["strip_accents"] = { } function dtutils_string.strip_accents( str ) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) local tableAccents = {} tableAccents["à"] = "a" tableAccents["á"] = "a" @@ -111,6 +119,7 @@ function dtutils_string.strip_accents( str ) end end + log.log_level(old_log_level) return normalizedString end @@ -136,6 +145,8 @@ dtutils_string.libdoc.functions["escape_xml_characters"] = { -- Keep & first, otherwise it will double escape other characters function dtutils_string.escape_xml_characters( str ) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) str = string.gsub(str,"&", "&") str = string.gsub(str,"\"", """) @@ -143,6 +154,7 @@ function dtutils_string.escape_xml_characters( str ) str = string.gsub(str,"<", "<") str = string.gsub(str,">", ">") + log.log_level(old_log_level) return str end @@ -165,11 +177,14 @@ dtutils_string.libdoc.functions["urlencode"] = { } function dtutils_string.urlencode(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) if (str) then str = string.gsub (str, "\n", "\r\n") str = string.gsub (str, "([^%w ])", function (c) return string.format ("%%%02X", string.byte(c)) end) str = string.gsub (str, " ", "+") end + log.log_level(old_log_level) return str end @@ -192,38 +207,52 @@ dtutils_string.libdoc.functions["is_not_sanitized"] = { } local function _is_not_sanitized_posix(str) - -- A sanitized string must be quoted. - if not string.match(str, "^'.*'$") then - return true + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + -- A sanitized string must be quoted. + if not string.match(str, "^'.*'$") then + log.log_level(old_log_level) + return true -- A quoted string containing no quote characters within is sanitized. - elseif string.match(str, "^'[^']*'$") then - return false - end + elseif string.match(str, "^'[^']*'$") then + log.log_level(old_log_level) + return false + end - -- Any quote characters within a sanitized string must be properly - -- escaped. - local quotesStripped = string.sub(str, 2, -2) - local escapedQuotesRemoved = string.gsub(quotesStripped, "'\\''", "") - if string.find(escapedQuotesRemoved, "'") then - return true - else - return false - end + -- Any quote characters within a sanitized string must be properly + -- escaped. + local quotesStripped = string.sub(str, 2, -2) + local escapedQuotesRemoved = string.gsub(quotesStripped, "'\\''", "") + if string.find(escapedQuotesRemoved, "'") then + log.log_level(old_log_level) + return true + else + log.log_level(old_log_level) + return false + end end local function _is_not_sanitized_windows(str) - if not string.match(str, "^\".*\"$") then - return true - else - return false - end + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + if not string.match(str, "^\".*\"$") then + log.log_level(old_log_level) + return true + else + log.log_level(old_log_level) + return false + end end function dtutils_string.is_not_sanitized(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) if dt.configuration.running_os == "windows" then - return _is_not_sanitized_windows(str) + log.log_level(old_log_level) + return _is_not_sanitized_windows(str) else - return _is_not_sanitized_posix(str) + log.log_level(old_log_level) + return _is_not_sanitized_posix(str) end end @@ -246,26 +275,38 @@ dtutils_string.libdoc.functions["sanitize"] = { } local function _sanitize_posix(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) if _is_not_sanitized_posix(str) then - return "'" .. string.gsub(str, "'", "'\\''") .. "'" + log.log_level(old_log_level) + return "'" .. string.gsub(str, "'", "'\\''") .. "'" else - return str + log.log_level(old_log_level) + return str end end local function _sanitize_windows(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) if _is_not_sanitized_windows(str) then - return "\"" .. string.gsub(str, "\"", "\"^\"\"") .. "\"" + log.log_level(old_log_level) + return "\"" .. string.gsub(str, "\"", "\"^\"\"") .. "\"" else - return str + log.log_level(old_log_level) + return str end end function dtutils_string.sanitize(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) if dt.configuration.running_os == "windows" then - return _sanitize_windows(str) + log.log_level(old_log_level) + return _sanitize_windows(str) else - return _sanitize_posix(str) + log.log_level(old_log_level) + return _sanitize_posix(str) end end @@ -288,9 +329,12 @@ dtutils_string.libdoc.functions["sanitize_lua"] = { } function dtutils_string.sanitize_lua(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) str = string.gsub(str, "%-", "%%-") str = string.gsub(str, "%(", "%%(") str = string.gsub(str, "%)", "%%)") + log.log_level(old_log_level) return str end @@ -313,7 +357,9 @@ dtutils_string.libdoc.functions["split_filepath"] = { } function dtutils_string.split_filepath(str) - -- strip out single quotes from quoted pathnames + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + -- strip out single quotes from quoted pathnames str = string.gsub(str, "'", "") str = string.gsub(str, '"', '') local result = {} @@ -323,6 +369,7 @@ function dtutils_string.split_filepath(str) result["basename"] = result["filetype"] result["filetype"] = "" end + log.log_level(old_log_level) return result end @@ -344,7 +391,10 @@ dtutils_string.libdoc.functions["get_path"] = { } function dtutils_string.get_path(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) local parts = dtutils_string.split_filepath(str) + log.log_level(old_log_level) return parts["path"] end @@ -366,7 +416,10 @@ dtutils_string.libdoc.functions["get_filename"] = { } function dtutils_string.get_filename(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) local parts = dtutils_string.split_filepath(str) + log.log_level(old_log_level) return parts["filename"] end @@ -389,7 +442,10 @@ dtutils_string.libdoc.functions["get_basename"] = { } function dtutils_string.get_basename(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) local parts = dtutils_string.split_filepath(str) + log.log_level(old_log_level) return parts["basename"] end @@ -411,7 +467,10 @@ dtutils_string.libdoc.functions["get_filetype"] = { } function dtutils_string.get_filetype(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) local parts = dtutils_string.split_filepath(str) + log.log_level(old_log_level) return parts["filetype"] end From 5331a38f10fdbbd85894ec6b9cd096e242370aa4 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 25 Mar 2023 17:05:11 -0400 Subject: [PATCH 002/169] lib/dtutils/string - added % and + to sanitize_lua() to prevent pattern matching problems --- lib/dtutils/string.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 6b716929..b988731e 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -331,9 +331,11 @@ dtutils_string.libdoc.functions["sanitize_lua"] = { function dtutils_string.sanitize_lua(str) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) + str = string.gsub(str, "%%", "%%%%") str = string.gsub(str, "%-", "%%-") str = string.gsub(str, "%(", "%%(") str = string.gsub(str, "%)", "%%)") + str = string.gsub(str, "+", "%%+") log.log_level(old_log_level) return str end From ad10542b3025a2a4e32d87cc3bd1ca61351a0fdb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 26 Mar 2023 14:55:40 -0400 Subject: [PATCH 003/169] lib/dtutils/string - Added variable substitution functions that are equivalent to the darktable variable substitution functions. --- lib/dtutils/string.lua | 411 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 411 insertions(+) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index b988731e..e315edaa 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -476,6 +476,417 @@ function dtutils_string.get_filetype(str) return parts["filetype"] end +local substitutes = {} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- C O N S T A N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local PLACEHOLDERS = {"ROLL.NAME", + "FILE.FOLDER", + "FILE.NAME", + "FILE.EXTENSION", + "ID", + "VERSION", + "VERSION.IF.MULTI", -- Not Implemented + "VERSION.NAME", + "DARKTABLE.VERSION", + "DARKTABLE.NAME", -- Not Implemented + "SEQUENCE", + "WIDTH.SENSOR", + "HEIGHT.SENSOR", + "WIDTH.RAW", + "HEIGHT.RAW", + "WIDTH.CROP", + "HEIGHT.CROP", + "WIDTH.EXPORT", + "HEIGHT.EXPORT", + "WIDTH.MAX", -- Not Implemented + "HEIGHT.MAX", -- Not Implemented + "YEAR", + "MONTH", + "DAY", + "HOUR", + "MINUTE", + "SECOND", + "MSEC", + "EXIF.YEAR", + "EXIF.MONTH", + "EXIF.DAY", + "EXIF.HOUR", + "EXIF.MINUTE", + "EXIF.SECOND", + "EXIF.MSEC", + "EXIF.DATE.REGIONAL", -- Not Implemented + "EXIF.TIME.REGIONAL", -- Not Implemented + "EXIF.ISO", + "EXIF.EXPOSURE", + "EXIF.EXPOSURE.BIAS", + "EXIF.APERTURE", + "EXIF.FOCAL.LENGTH", + "EXIF.FOCUS.DISTANCE", + "LONGITUDE", + "LATITUDE", + "ALTITUDE", + "STARS", + "RATING.ICONS", -- Not Implemented + "LABELS", + "LABELS.ICONS", -- Not Implemented + "MAKER", + "MODEL", + "TITLE", + "DESCRIPTION", + "CREATOR", + "PUBLISHER", + "RIGHTS", + "TAGS", -- Not Implemented + "CATEGORY", -- Not Implemented + "SIDECAR.TXT", -- Not Implemented + "FOLDER.PICTURES", + "FOLDER.HOME", + "FOLDER.DESKTOP", + "OPENCL.ACTIVATED", -- Not Implemented + "USERNAME", + "NL", + "JOBCODE" -- Not Implemented +} + +local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local USER = os.getenv("USERNAME") +local HOME = dt.configuration.running_os == "windows" and os.getenv("HOMEPATH") or os.getenv("HOME") +local PICTURES = HOME .. PS .. (dt.configuration.running_os == "windows" and "My Pictures" or "Pictures") +local DESKTOP = HOME .. PS .. "Desktop" + +local function get_colorlabels(image) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + local colorlabels = {} + if image.red then table.insert(colorlabels, "red") end + if image.yellow then table.insert(colorlabels, "yellow") end + if image.green then table.insert(colorlabels, "green") end + if image.blue then table.insert(colorlabels, "blue") end + if image.purple then table.insert(colorlabels, "purple") end + local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",") + log.log_level(old_log_level) + return labels +end + +dtutils_string.libdoc.functions["build_substitution_list"] = { + Name = [[build_substitution_list]], + Synopsis = [[build a list of variable substitutions]], + Usage = [[local ds = require "lib/dtutils.string" + ds.build_substitution_list(image, sequence, [username], [pic_folder], [home], [desktop]) + image - dt_lua_image_t - the image being processed + sequence - integer - the sequence number of the image + [username] - string - optional - user name. Will be determined if not supplied + [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied + [home] - string - optional - home directory. Will be determined if not supplied + [desktop] - string - optional - desktop directory. Will be determined if not supplied]], + Description = [[build_substitution_list populates variables with values from the arguments + and determined from the system and darktable.]], + Return_Value = [[]], + Limitations = [[If the value for a variable can not be determined, or if it is not supported, + then an empty string is used for the value.]], + Example = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.2/en/special-topics/variables/]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.build_substition_list(image, sequence, username, pic_folder, home, desktop) + -- build the argument substitution list from each image + + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + + local is_api_9_1 = true + if dt.configuration.api_version_string < "9.1.0" then + is_api_9_1 = false + end + + local datetime = os.date("*t") + local user_name = username or USER + local pictures_folder = pic_folder or PICTURES + local home_folder = home or HOME + log.msg(log.info, "home is " .. tostring(home) .. " and HOME is " .. tostring(HOME) .. " and home_folder is " .. tostring(home_folder)) + local desktop_folder = desktop or DESKTOP + + local labels = get_colorlabels(image) + log.msg(log.info, "image date time taken is " .. image.exif_datetime_taken) + local eyear, emon, eday, ehour, emin, esec, emsec + if dt.preferences.read("darktable", "lighttable/ui/milliseconds", "bool") and is_api_9_1 then + eyear, emon, eday, ehour, emin, esec, emsec = + string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)%.(%d+)$") + else + emsec = "0" + eyear, emon, eday, ehour, emin, esec = + string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$") + end + local replacements = {image.film, -- ROLL.NAME + image.path, -- FILE.FOLDER + image.filename, -- FILE.NAME + dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION + image.id, -- ID + image.duplicate_index, -- VERSION + "", -- VERSION.IF_MULTI + image.version_name, -- VERSION.NAME + dt.configuration.version, -- DARKTABLE.VERSION + "", -- DARKTABLE.NAME + sequence, -- SEQUENCE + image.width, -- WIDTH.SENSOR + image.height, -- HEIGHT.SENSOR + is_api_9_1 and image.p_width or "", -- WIDTH.RAW + is_api_9_1 and image.p_height or "", -- HEIGHT.RAW + is_api_9_1 and image.final_width or "", -- WIDTH.CROP + is_api_9_1 and image.final_height or "", -- HEIGHT.CROP + is_api_9_1 and image.final_width or "", -- WIDTH.EXPORT + is_api_9_1 and image.final_height or "", -- HEIGHT.EXPORT + "", -- WIDTH.MAX + "", -- HEIGHT.MAX + string.format("%4d", datetime.year), -- YEAR + string.format("%02d", datetime.month), -- MONTH + string.format("%02d", datetime.day), -- DAY + string.format("%02d", datetime.hour), -- HOUR + string.format("%02d", datetime.min), -- MINUTE + string.format("%02d", datetime.sec), -- SECOND + "", -- MSEC + eyear, -- EXIF.YEAR + emon, -- EXIF.MONTH + eday, -- EXIF.DAY + ehour, -- EXIF.HOUR + emin, -- EXIF.MINUTE + esec, -- EXIF.SECOND + emsec, -- EXIF.MSEC + "", -- EXIF.DATE.REGIONAL + "", -- EXIF.TIME.REGIONAL + string.format("%d", image.exif_iso), -- EXIF.ISO + string.format("1/%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE + image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS + string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE + string.format("%.0f", image.exif_focal_length), -- EXIF.FOCAL.LENGTH + image.exif_focus_distance, -- EXIF.FOCUS.DISTANCE + image.longitude or "", -- LONGITUDE + image.latitude or "", -- LATITUDE + image.elevation or "", -- ALTITUDE + image.rating, -- STARS + "", -- RATING.ICONS + labels, -- LABELS + "", -- LABELS.ICONS + image.exif_maker, -- MAKER + image.exif_model, -- MODEL + image.title, -- TITLE + image.description, -- DESCRIPTION + image.creator, -- CREATOR + image.publisher, -- PUBLISHER + image.rights, -- RIGHTS + "", -- TAGS + "", -- CATEGORYn + "", -- SIDECAR.TXT + pictures_folder, -- FOLDER.PICTURES + home_folder, -- FOLDER.HOME + desktop_folder, -- FOLDER.DESKTOP + "", -- OPENCL.ACTIVATED + user_name, -- USERNAME + "\n", -- NL + "" -- JOBCODE + } + + for i = 1, #PLACEHOLDERS, 1 do + substitutes[PLACEHOLDERS[i]] = replacements[i] + log.msg(log.info, "setting " .. PLACEHOLDERS[i] .. " to " .. tostring(replacements[i])) + end + log.log_level(old_log_level) +end + +local function check_legacy_vars(var_name) + local var = var_name + if string.match(var, "_") then + var = string.gsub(var, "_", ".") + end + if string.match(var, "^HOME$") then var = "FOLDER.HOME" end + if string.match(var, "^PICTURES.FOLDER$") then var = "FOLDER.PICTURES" end + if string.match(var, "^DESKTOP$") then var = "FOLDER.DESKTOP" end + return var +end + +local function treat(var_string) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + local ret_val = "" + -- remove the var from the string + local var = string.match(var_string, "[%a%._]+") + var = check_legacy_vars(var) + log.msg(log.info, "var_string is " .. tostring(var_string) .. " and var is " .. tostring(var)) + ret_val = substitutes[var] + if not ret_val then + log.msg(log.error, "variable " .. var .. " is not an allowed variable, returning empty value") + log.log_level(old_log_level) + return "" + end + local args = string.gsub(var_string, var, "") + log.msg(log.info, "args is " .. tostring(args)) + if string.len(args) > 0 then + if string.match(args, '^%^%^') then + ret_val = string.upper(ret_val) + elseif string.match(args, "^%^") then + ret_val = string.gsub(ret_val, "^%a", string.upper, 1) + elseif string.match(args, "^,,") then + ret_val = string.lower(ret_val) + elseif string.match(args, "^,") then + ret_val = string.gsub(ret_val, "^%a", string.lower, 1) + elseif string.match(args, "^:%-?%d+:%-?%d+") then + local soffset, slen = string.match(args, ":(%-?%d+):(%-?%d+)") + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + if tonumber(soffset) >= 0 then + soffset = soffset + 1 + end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + if tonumber(soffset) < 0 and tonumber(slen) < 0 then + local temp = soffset + soffset = slen + slen = temp + end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + ret_val = string.sub(ret_val, soffset, slen) + log.msg(log.info, "ret_val is " .. ret_val) + elseif string.match(args, "^:%-?%d+") then + local soffset= string.match(args, ":(%-?%d+)") + if tonumber(soffset) >= 0 then + soffset = soffset + 1 + end + ret_val = string.sub(ret_val, soffset, -1) + elseif string.match(args, "^-%$%(.-%)") then + local replacement = string.match(args, "-%$%(([%a%._]+)%)") + replacement = check_legacy_vars(replacement) + if string.len(ret_val) == 0 then + ret_val = substitutes[replacement] + end + elseif string.match(args, "^-.+$") then + local replacement = string.match(args, "-(.+)$") + if string.len(ret_val) == 0 then + ret_val = replacement + end + elseif string.match(args, "^+.+") then + local replacement = string.match(args, "+(.+)") + if string.len(ret_val) > 0 then + ret_val = replacement + end + elseif string.match(args, "^#.+") then + local pattern = string.match(args, "#(.+)") + log.msg(log.info, "pattern to remove is " .. tostring(pattern)) + ret_val = string.gsub(ret_val, "^" .. dtutils_string.sanitize_lua(pattern), "") + elseif string.match(args, "^%%.+") then + local pattern = string.match(args, "%%(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", "") + elseif string.match(args, "^//.-/.+") then + local pattern, replacement = string.match(args, "//(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement) + elseif string.match(args, "^/#.+/.+") then + local pattern, replacement = string.match(args, "/#(.+)/(.+)") + ret_val = string.gsub(ret_val, "^" .. pattern, replacement, 1) + elseif string.match(args, "^/%%.-/.+") then + local pattern, replacement = string.match(args, "/%%(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", replacement) + elseif string.match(args, "^/.-/.+") then + log.msg(log.info, "took replacement branch") + local pattern, replacement = string.match(args, "/(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement, 1) + end + end + log.log_level(old_log_level) + return ret_val +end + +dtutils_string.libdoc.functions["substitute_list"] = { + Name = [[substitute_list]], + Synopsis = [[Replace variables in a string with their computed values]], + Usage = [[local ds = require "lib/dtutils.string" + local result = ds.substitute_list(str) + str - string - the string containing the variables to be substituted for]], + Description = [[substitute_list replaces the variables in the supplied string with + values computed in build_substitution_list().]], + Return_Value = [[result - string - the input string with values substituted for the variables]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.substitute_list(str) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + -- replace the substitution variables in a string + for match in string.gmatch(str, "%$%(.-%)?%)") do + local var = string.match(match, "%$%((.-%)?)%)") + local treated_var = treat(var) + log.msg(log.info, "var is " .. var .. " and treated var is " .. tostring(treated_var)) + str = string.gsub(str, "%$%(".. dtutils_string.sanitize_lua(var) .."%)", tostring(treated_var)) + log.msg(log.info, "str after replacement is " .. str) + end + log.log_level(old_log_level) + return str +end + +dtutils_string.libdoc.functions["clear_substitute_list"] = { + Name = [[clear_substitute_list]], + Synopsis = [[Clear the computed list of variable substitution values]], + Usage = [[local ds = require "lib/dtutils.string" + ds.clear_substitute_list()]], + Description = [[clear_substitute_list resets the list of variable replacement values]], + Return_Value = [[]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.clear_substitute_list() + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + for i = 1, #PLACEHOLDERS, 1 do + substitutes[PLACEHOLDERS[i]] = nil + end + log.log_level(old_log_level) +end + +dtutils_string.libdoc.functions["substitute"] = { + Name = [[substitute]], + Synopsis = [[Check if a string has been sanitized]], + Usage = [[local ds = require "lib/dtutils.string" + ds.substitute(image, sequence, [username], [pic_folder], [home], [desktop]) + image - dt_lua_image_t - the image being processed + sequence - integer - the sequence number of the image + [username] - string - optional - user name. Will be determined if not supplied + [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied + [home] - string - optional - home directory. Will be determined if not supplied + [desktop] - string - optional - desktop directory. Will be determined if not supplied]], + Description = [[substitute initializes the substitution list by calling clear_substitute_list(), + then builds the substitutions by calling build_substitution_list() and finally does the + substitution by calling substitute_list(), then returns the result string.]], + Return_Value = [[result - string - the input string with values substituted for the variables]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.substitute(image, sequence, variable_string, username, pic_folder, home, desktop) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + dtutils_string.clear_substitute_list() + dtutils_string.build_substition_list(image, sequence, username, pic_folder, home, desktop) + local str = dtutils_string.substitute_list(variable_string) + log.log_level(old_log_level) + return str +end + return dtutils_string From 86ee8adbce3a73660c4c441b796b7628af901106 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 1 Apr 2024 18:26:41 -0400 Subject: [PATCH 004/169] tools/script_manager - changed category/categories to folder/folders to accurately refrect reality and to open the possibility of actually categorizing scripts. --- tools/script_manager.lua | 170 +++++++++++++++++++-------------------- 1 file changed, 85 insertions(+), 85 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 7376ed43..f8e6c73b 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -22,9 +22,9 @@ manage the lua scripts. On startup script_manager scans the lua scripts directory to see what scripts are present. - Scripts are sorted by 'category' based on what sub-directory they are in. With no - additional script repositories iinstalled, the categories are contrib, examples, official - and tools. When a category is selected the buttons show the script name and whether the + Scripts are sorted by 'folder' based on what sub-directory they are in. With no + additional script repositories iinstalled, the folders are contrib, examples, official + and tools. When a folder is selected the buttons show the script name and whether the script is started or stopped. The button is a toggle, so if the script is stopped click the button to start it and vice versa. @@ -128,7 +128,7 @@ sm.event_registered = false -- set up tables to contain all the widgets and choices sm.widgets = {} -sm.categories = {} +sm.folders = {} -- set log level for functions @@ -137,14 +137,14 @@ sm.log_level = DEFAULT_LOG_LEVEL --[[ sm.scripts is a table of tables for containing the scripts - It is organized as into category (folder) subtables containing + It is organized as into folder (folder) subtables containing each script definition, which is a table sm.scripts- | - - category------------| + - folder------------| | - script - - category----| | + - folder----| | - script| | - script - script| @@ -152,7 +152,7 @@ sm.log_level = DEFAULT_LOG_LEVEL and a script table looks like name the name of the script file without the lua extension - path category (folder), path separator, path, name without the lua extension + path folder (folder), path separator, path, name without the lua extension doc the header comments from the script to be used as a tooltip script_name the folder, path separator, and name without the lua extension running true if running, false if not, hidden if running but the @@ -178,7 +178,7 @@ sm.page_status = {} sm.page_status.num_buttons = DEFAULT_BUTTONS_PER_PAGE sm.page_status.buttons_created = 0 sm.page_status.current_page = 0 -sm.page_status.category = "" +sm.page_status.folder = "" -- use color in the interface? sm.use_color = false @@ -348,12 +348,12 @@ end -- script handling ------------------ -local function add_script_category(category) +local function add_script_folder(folder) local old_log_level = set_log_level(sm.log_level) - if #sm.categories == 0 or not string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then - table.insert(sm.categories, category) - sm.scripts[category] = {} - log.msg(log.debug, "created category " .. category) + if #sm.folders == 0 or not string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then + table.insert(sm.folders, folder) + sm.scripts[folder] = {} + log.msg(log.debug, "created folder " .. folder) end restore_log_level(old_log_level) end @@ -452,19 +452,19 @@ local function deactivate(script) restore_log_level(old_log_level) end -local function add_script_name(name, path, category) +local function add_script_name(name, path, folder) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "category is " .. category) + log.msg(log.debug, "folder is " .. folder) log.msg(log.debug, "name is " .. name) local script = { name = name, - path = category .. "/" .. path .. name, + path = folder .. "/" .. path .. name, running = false, - doc = get_script_doc(category .. "/" .. path .. name), - script_name = category .. "/" .. name, + doc = get_script_doc(folder .. "/" .. path .. name), + script_name = folder .. "/" .. name, data = nil } - table.insert(sm.scripts[category], script) + table.insert(sm.scripts[folder], script) if pref_read(script.script_name, "bool") then activate(script) else @@ -476,10 +476,10 @@ end local function process_script_data(script_file) local old_log_level = set_log_level(sm.log_level) - -- the script file supplied is category/filename.filetype - -- the following pattern splits the string into category, path, name, fileename, and filetype + -- the script file supplied is folder/filename.filetype + -- the following pattern splits the string into folder, path, name, fileename, and filetype -- for example contrib/gimp.lua becomes - -- category - contrib + -- folder - contrib -- path - -- name - gimp.lua -- filename - gimp @@ -496,14 +496,14 @@ local function process_script_data(script_file) log.msg(log.info, "processing " .. script_file) -- add the script data - local category,path,name,filename,filetype = string.match(script_file, pattern) + local folder,path,name,filename,filetype = string.match(script_file, pattern) - if category and name and path then - log.msg(log.debug, "category is " .. category) + if folder and name and path then + log.msg(log.debug, "folder is " .. folder) log.msg(log.debug, "name is " .. name) - add_script_category(category) - add_script_name(name, path, category) + add_script_folder(folder) + add_script_name(name, path, folder) end restore_log_level(old_log_level) @@ -594,25 +594,25 @@ local function scan_repositories() local output = io.popen(find_cmd) for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off - local category = string.match(l, "(.-)" .. PS) -- get everything to teh first / - if category then -- if we have a category (.git doesn't) - log.msg(log.debug, "found category " .. category) - if not string.match(category, "plugins") and not string.match(category, "%.git") then -- skip plugins + local folder = string.match(l, "(.-)" .. PS) -- get everything to teh first / + if folder then -- if we have a folder (.git doesn't) + log.msg(log.debug, "found folder " .. folder) + if not string.match(folder, "plugins") and not string.match(folder, "%.git") then -- skip plugins if #sm.installed_repositories == 1 then - log.msg(log.debug, "only 1 repo, adding " .. category) - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + log.msg(log.debug, "only 1 repo, adding " .. folder) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) else log.msg(log.debug, "more than 1 repo, we have to search the repos to make sure it's not there") local found = nil for _, repo in ipairs(sm.installed_repositories) do - if string.match(repo.name, ds.sanitize_lua(category)) then + if string.match(repo.name, ds.sanitize_lua(folder)) then log.msg(log.debug, "matched " .. repo.name) found = true break end end if not found then - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) end end end @@ -625,11 +625,11 @@ end local function install_scripts() local old_log_level = set_log_level(sm.log_level) local url = sm.widgets.script_url.text - local category = sm.widgets.new_category.text + local folder = sm.widgets.new_folder.text - if string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then - log.msg(log.screen, _("category ") .. category .. _(" is already in use. Please specify a different category name.")) - log.msg(log.error, "category " .. category .. " already exists, returning...") + if string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then + log.msg(log.screen, _("folder ") .. folder .. _(" is already in use. Please specify a different folder name.")) + log.msg(log.error, "folder " .. folder .. " already exists, returning...") restore_log_level(old_log_level) return end @@ -644,7 +644,7 @@ local function install_scripts() return end - local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. category + local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. folder log.msg(log.debug, "update git command is " .. git_command) if dt.configuration.running_os == "windows" then @@ -656,27 +656,27 @@ local function install_scripts() log.msg(log.info, "result from import is " .. result) if result == 0 then - local count = scan_scripts(LUA_DIR .. PS .. category) + local count = scan_scripts(LUA_DIR .. PS .. folder) if count > 0 then - update_combobox_choices(sm.widgets.category_selector, sm.categories, sm.widgets.category_selector.selected) - dt.print(_("scripts successfully installed into category ") .. category) - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + update_combobox_choices(sm.widgets.folder_selector, sm.folders, sm.widgets.folder_selector.selected) + dt.print(_("scripts successfully installed into folder ") .. folder) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) update_script_update_choices() - for i = 1, #sm.widgets.category_selector do - if string.match(sm.widgets.category_selector[i], ds.sanitize_lua(category)) then - log.msg(log.debug, "setting category selector to " .. i) - sm.widgets.category_selector.selected = i + for i = 1, #sm.widgets.folder_selector do + if string.match(sm.widgets.folder_selector[i], ds.sanitize_lua(folder)) then + log.msg(log.debug, "setting folder selector to " .. i) + sm.widgets.folder_selector.selected = i break end i = i + 1 end log.msg(log.debug, "clearing text fields") sm.widgets.script_url.text = "" - sm.widgets.new_category.text = "" + sm.widgets.new_folder.text = "" sm.widgets.main_menu.selected = 3 else dt.print(_("No scripts found to install")) - log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to category_selector") + log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") end else dt.print(_("failed to download scripts")) @@ -696,10 +696,10 @@ local function clear_button(number) restore_log_level(old_log_level) end -local function find_script(category, name) +local function find_script(folder, name) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "looking for script " .. name .. " in category " .. category) - for _, script in ipairs(sm.scripts[category]) do + log.msg(log.debug, "looking for script " .. name .. " in folder " .. folder) + for _, script in ipairs(sm.scripts[folder]) do if string.match(script.name, "^" .. ds.sanitize_lua(name) .. "$") then return script end @@ -708,12 +708,12 @@ local function find_script(category, name) return nil end -local function populate_buttons(category, first, last) +local function populate_buttons(folder, first, last) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "category is " .. category .. " and first is " .. first .. " and last is " .. last) + log.msg(log.debug, "folder is " .. folder .. " and first is " .. first .. " and last is " .. last) local button_num = 1 for i = first, last do - script = sm.scripts[category][i] + script = sm.scripts[folder][i] button = sm.widgets.buttons[button_num] if script.running == true then if sm.use_color then @@ -741,7 +741,7 @@ local function populate_buttons(category, first, last) else script_name, state = string.match(this.label, "(.-) (.+)") end - local script = find_script(sm.widgets.category_selector.value, script_name) + local script = find_script(sm.widgets.folder_selector.value, script_name) if script then log.msg(log.debug, "found script " .. script.name .. " with path " .. script.path) if script.running == true then @@ -779,9 +779,9 @@ end local function paginate(direction) local old_log_level = set_log_level(sm.log_level) - local category = sm.page_status.category - log.msg(log.debug, "category is " .. category) - local num_scripts = #sm.scripts[category] + local folder = sm.page_status.folder + log.msg(log.debug, "folder is " .. folder) + local num_scripts = #sm.scripts[folder] log.msg(log.debug, "num_scripts is " .. num_scripts) local max_pages = math.ceil(num_scripts / sm.page_status.num_buttons) local cur_page = sm.page_status.current_page @@ -829,18 +829,18 @@ local function paginate(direction) end sm.widgets.page_status.label = _("Page ") .. cur_page .. _(" of ") .. max_pages - populate_buttons(category, first, last) + populate_buttons(folder, first, last) restore_log_level(old_log_level) end -local function change_category(category) +local function change_folder(folder) local old_log_level = set_log_level(sm.log_level) - if not category then - log.msg(log.debug "setting category to selector value " .. sm.widgets.category_selector.value) - sm.page_status.category = sm.widgets.category_selector.value + if not folder then + log.msg(log.debug "setting folder to selector value " .. sm.widgets.folder_selector.value) + sm.page_status.folder = sm.widgets.folder_selector.value else - log.msg(log.debug, "setting catgory to argument " .. category) - sm.page_status.category = category + log.msg(log.debug, "setting catgory to argument " .. folder) + sm.page_status.folder = folder end paginate(2) @@ -898,14 +898,14 @@ local function load_preferences() end update_script_update_choices() log.msg(log.debug, "updated installed scripts") - -- category selector - local val = pref_read("category_selector", "integer") + -- folder selector + local val = pref_read("folder_selector", "integer") if val == 0 then val = 1 end - sm.widgets.category_selector.selected = val - sm.page_status.category = sm.widgets.category_selector.value - log.msg(log.debug, "updated category selector and set it to " .. sm.widgets.category_selector.value) + sm.widgets.folder_selector.selected = val + sm.page_status.folder = sm.widgets.folder_selector.value + log.msg(log.debug, "updated folder selector and set it to " .. sm.widgets.folder_selector.value) -- num_buttons local val = pref_read("num_buttons", "integer") if val == 0 then @@ -1069,18 +1069,18 @@ sm.widgets.script_url = dt.new_widget("entry"){ tooltip = _("enter the URL of the git repository containing the scripts you wish to add") } -sm.widgets.new_category = dt.new_widget("entry"){ +sm.widgets.new_folder = dt.new_widget("entry"){ text = "", - placeholder = _("name of new category"), - tooltip = _("enter a category name for the additional scripts") + placeholder = _("name of new folder"), + tooltip = _("enter a folder name for the additional scripts") } sm.widgets.add_scripts = dt.new_widget("box"){ orientation = vertical, dt.new_widget("label"){label = _("URL to download additional scripts from")}, sm.widgets.script_url, - dt.new_widget("label"){label = _("new category to place scripts in")}, - sm.widgets.new_category, + dt.new_widget("label"){label = _("new folder to place scripts in")}, + sm.widgets.new_folder, dt.new_widget("button"){ label = _("install additional scripts"), clicked_callback = function(this) @@ -1124,17 +1124,17 @@ sm.widgets.install_update = dt.new_widget("box"){ -- manage the scripts -sm.widgets.category_selector = dt.new_widget("combobox"){ - label = _("category"), - tooltip = _( "select the script category"), +sm.widgets.folder_selector = dt.new_widget("combobox"){ + label = _("folder"), + tooltip = _( "select the script folder"), selected = 1, changed_callback = function(self) if sm.run then - pref_write("category_selector", "integer", self.selected) - change_category(self.value) + pref_write("folder_selector", "integer", self.selected) + change_folder(self.value) end end, - table.unpack(sm.categories), + table.unpack(sm.folders), } sm.widgets.buttons ={} @@ -1175,7 +1175,7 @@ sm.widgets.page_control = dt.new_widget("box"){ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, dt.new_widget("label"){label = _("Scripts")}, - sm.widgets.category_selector, + sm.widgets.folder_selector, sm.widgets.page_control, table.unpack(sm.widgets.buttons) } From a11726889d42bcb7fc9f3dcc0705855fd601b22b Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 2 Apr 2024 19:09:31 -0400 Subject: [PATCH 005/169] tools/script_manager - add support for "local" libraries by adding entries in the package.path search path if required --- contrib/micharambou | 1 + data/icons/power.png | Bin 0 -> 426 bytes data/icons/poweroff.png | Bin 0 -> 599 bytes development | 1 + tools/script_manager.lua | 21 +++++++++++++++++++++ 5 files changed, 23 insertions(+) create mode 120000 contrib/micharambou create mode 100644 data/icons/power.png create mode 100644 data/icons/poweroff.png create mode 120000 development diff --git a/contrib/micharambou b/contrib/micharambou new file mode 120000 index 00000000..bbe4f2d2 --- /dev/null +++ b/contrib/micharambou @@ -0,0 +1 @@ +/home/bill/src/dtluadev/gitannex/micharambou/ \ No newline at end of file diff --git a/data/icons/power.png b/data/icons/power.png new file mode 100644 index 0000000000000000000000000000000000000000..f9d1fe578f888f3ecf937287e3d94e05a112229d GIT binary patch literal 426 zcmV;b0agBqP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10XIoR zK~yM_jgmhvL{SjM&%9Oo7YTMLGz%ws)~H1CJIeN?*DO=dPQvs*J86J2)w%92d49tXMh`uaSc>rGa1kT+Q2jL<*^NH0T%jy08L>r U|26I@$^ZZW07*qoM6N<$g6#{bzW@LL literal 0 HcmV?d00001 diff --git a/data/icons/poweroff.png b/data/icons/poweroff.png new file mode 100644 index 0000000000000000000000000000000000000000..1fb1e75c828d38f907b7ff7556dc73de0455ff10 GIT binary patch literal 599 zcmV-d0;v6oP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10pv+U zK~yM_jgY--lTjGOf6sm2bnzpxR7tEc5sm0#Qi^uaKR~1y-av7%Aa!zZut@(0QOV+0 zce_YYQ$HZ6xL7QeYLh__ba_pn6&(~EY~SWS4sTLZNs4E=mwV57&N+`m7o>9eQ_=Y0 zg;?TH^~dsuPn|fXn^=)!z;;!Jx^djUPTqu~7c9=ndgcDde~JBB@1P@E7WB4XnYq61 zoXqCO6uk7yg@&HC=i4)?1t4xlh) z>hB(w5ncj)HVB?{8;}|-B#gL^fnMN6ty+5K0vI=Pwh;vL0)sXP-Y0Wj(LtXQi7e~t zN&;hswPDAB9f0qw+0k&BO(X|S?-n)pfD>JF3$MhOGydBLOB=+k?^M?FtS~C*5YPvF lQ)J$#z3W$JN@3j)e*jLFrNd8l&ny4{002ovPDHLkV1h8c408Yg literal 0 HcmV?d00001 diff --git a/development b/development new file mode 120000 index 00000000..1c69f2fb --- /dev/null +++ b/development @@ -0,0 +1 @@ +/home/bill/src/lua-scripts.local/development/ \ No newline at end of file diff --git a/tools/script_manager.lua b/tools/script_manager.lua index f8e6c73b..d54f9e6f 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -509,6 +509,25 @@ local function process_script_data(script_file) restore_log_level(old_log_level) end +local function ensure_lib_in_search_path(line) + local old_log_level = set_log_level(sm.log_level) + set_log_level(log.debug) + log.msg(log.debug, "line is " .. line) + if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua/lib")) then + log.msg(log.debug, line .. " is already in search path, returning...") + return + end + local path = string.match(line, "(.+)/lib/.+lua") + log.msg(log.debug, "extracted path is " .. path) + log.msg(log.debug, "package.path is " .. package.path) + if not string.match(package.path, ds.sanitize_lua(path)) then + log.msg(log.debug, "path isn't in package.path, adding...") + package.path = package.path .. ";" .. path .. "/?.lua" + log.msg(log.debug, "new package.path is " .. package.path) + end + restore_log_level(old_log_level) +end + local function scan_scripts(script_dir) local old_log_level = set_log_level(sm.log_level) local script_count = 0 @@ -529,6 +548,8 @@ local function scan_scripts(script_dir) process_script_data(script_file) script_count = script_count + 1 end + else + ensure_lib_in_search_path(line) -- but let's make sure libraries can be found end end end From 9c4e906033df371c66e17b8c9d8c070837ce0784 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 5 Apr 2024 20:37:34 -0400 Subject: [PATCH 006/169] data/icons - added 20px icons --- data/icons/blank20.png | Bin 0 -> 5678 bytes data/icons/path20.png | Bin 0 -> 6265 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 data/icons/blank20.png create mode 100644 data/icons/path20.png diff --git a/data/icons/blank20.png b/data/icons/blank20.png new file mode 100644 index 0000000000000000000000000000000000000000..81ed51574807dde06248d05f94fe17aad24b193a GIT binary patch literal 5678 zcmeHKd010d7Eff8SY$^*g%A|$60$=Q2*bWqK$g^iqLAdhK!7YHfh4SoUw6r(JBzi;}VN#4u5=brOB=lt%u_uZT6 z@8_w5Fhjs#Fdc6%w?OFM2)c~tYeILA?(3B>nC627daxvrDMboJ0uDC@L`vjB5DCh- z92iV?_i6B^eGjd_c^6?EGSAB^ZD`zKrG5JgamHraEfOi$Z)Mm4(f(Mti3`Jvv8*R7 zb!=SAkzSj=c0=t6k0ep&UNiIWnjD|r3%WL(@~(X{IHBJn|DIa9jYr3?T}LLTJyR15 zg(RKzFZPz=$F|L+l=iI&k{d`cZKw4InSE)0pm#ntlU4Se=DAm0ZtimlVQ7`o?McUi@UT6Mc^jjkx>L~$$P z?(Wf~U#p#GYQ9r*;KlV;kG-0D>l6lzyFjR11V8dXQF5S9;vI3v%#(c5_>_ZTd0^Vv zbcSP6MsD-jdW($0`@E#{8WSiH+f~Q&`m-WUw_lobR$o!%j=p(t9vUY;sS1LiNM}z(onbsZ!Gm;Az`--*8O6X(;k>+xd0aBzf5ZI;M&Z~+H0ID;irl=%}*)aHYhjvxmANR!jZ`5tVTzG9e z;Ao|5N{uND@cbuHj!N-4WZQA7rmA+}yET}QpJVoZ&-hJP*U214Z>oQKZj>*mEPLDQ z7-r#w{=M?1^VOAimqd+_5_fu;`?cZJ!i_}p?myB~9R4>hd&Q8??qasdnnC(}XYbf+ z98ufW@GY4@g;xAF8@FW*>jzENwp_}YStYu7_$^Y@fB!Y2?lCuVY1`?b5s#c*w|;p3 zz1b^Md0EsYT`cNgVdnm*aSJcAM4i+>Zk~AJ8_l{vlTIqs-UU|)xGBB$9WNS|++V#V zrl`R)GJzUrn_@SwE@^(%&-syI3uH|ej%x;kJy~*C`qL*V+?#y=NDZ-Jf2eZay<~T`xwisXyIyKG(zi zxy8=&%hS8Eq1z8T$=ixsJ)$4#%qq4?CcI^)yABr|`_(sD+P5g2d}M*`eZ1nr){RlJ zGn9~+Dc8it{tLFd?L*mqvR(1dfeEvl7YI8W zgFL*RA+3)`TIZ}WDKUrrEQT-dBuf{Y!u6jY-hN81pD)j{gS^K z<1%J<`efz&=CSuOqsZ{(pDv7N?LBLgMHY9=tmx9J7)u_S*3Co4)b4nsZLVhilRsXc zLGOLo(=M=lXV06ula8S<^{+M;EcWPZDp=&wwC#4mT`e+nX_os)N4o=dX!UWs_Tt9SGS?7-TrRC7pn6UX3T$S|!xN@7 z-oRk+bgrwbzqhOFhu0nSk}FQ$L-V@iZwLlV2 z`qa2+JKf?D3ckx~m_E__HcKxV{tap^p`)na7%^#dw1j$m?7l-s%d@UO7z<3)=AmNK zOv`VoONu8mkDgk4vTeA{(0W?d3Mc)s+!l7H#$}t$>4#}T8}-gsC`K1^fc<~%2to!8R{w%eC>}D z&vVbGWQHOdR);?fcG58ee$}1PY>9|AuCTsv%*$Xz9$TD?E0r#aBrP3&+Z(%arPH|S z`Csg3HX0VaTYll@O_rxZVJnRN{bnI;K%})e1rG$pmm$%IgVp=GMh?7C+(7GXdHJZP z@5*Dfyt0ro!Pf1r;Z1ILMja=|MoWHcp*?-`4)#h{Bzd%q*8*)z%(&3@BiPr6$`J^U^Qnbc;vNryybtJqT8Qb;E)Ek2iUn~ZHs~G?@+DTELICWK_QE((j4B;~ zjR9jo9;7OUyy8A{>FMq3|ItE8K?Ij4R9Qh}f2Jwna{eOgv)Gh9s&qaL1Tz1K`O~8&*hUe`@Ln-}J0Rfu}P*smO4iT^=67A4LTNa2WQa~n}$!25G910c$ zNH`pc$YFm1<;@pMn0z*LC#En9y)?dCUk9BjiV@I+TP{o&CLOD7+2!FNuE)Q^J7^Xeb{pKTh^n2c63UgCtBP zn>afh9!DV%30OSIj)0wuS%tR&6p5iqRAS<=HiS7`<*-npWFTUh$~uJrRIpGkR96wm zln6w0fgpy4QbHh=ma6hbI?N4<7gr2v$dwiUzUG6#*tyo+6o}!fx{yd!*+MMOIT17C zK|tjQvYYE-M>6>lAT+<<7t{wi_kRo)heM%Y32a+58^?sk8(?4{yOl0d}tRWg^n|0uSeRTqmU+Rou?aY zR{1TxP*DI$w1r+FVi;`UCFKRbBB%r)p_atk*IjEwT^+8En0a<&5E7YqyE)Uz9qjPM j8b`y8cBKVi5t^$Yz%m%T)O4pCqyh7G_j5b#5}x)S(L1|$ literal 0 HcmV?d00001 diff --git a/data/icons/path20.png b/data/icons/path20.png new file mode 100644 index 0000000000000000000000000000000000000000..1021d86405b81bbfa10a78e4ea7e8de8651462dd GIT binary patch literal 6265 zcmeH~c|26@`^Se0i3}x0dZrh=5n)9dy7{@2W$nK}1;UGMw4KKFI*bDsofCmVTLRapoG zB5!AF=?eZX2M=W#De&9+aZ@e?BGnh>?#*{431A#9o5lzPVEj-H00V>!8U!MII^w;x zL_=F{ny&04X=@wZKW?nOq2aAt*0YT3YnpYF!-39rM>W5-?&dmOS7yKP;!Vdhv(`}y zvpn#<$pHneJ`tCL9 zFZEGjmmT`89^8bMS;xkQE)kW&&Ii263@a;9E?zMezGTn#k(pUQDm>=-ZQ@nR4X@oZ zXXM*XM>}cx9%6N#VlV2bG~(1+Y!rME>qfeDQ?}<2g7c)cG9pIWite?%Pz_btE`ZC+K`}m%ZhqnK730>TM zI?Q(@P|$p2UHEIIvuW0~Ua*=bk052?he+K{_DfRiOI7ESP=~$r z&g5yvjz;cXs1I9fxKk=n#~?Dbs47BFWzu=saXq+>mX!zx+}9EL51AFN+PYNhJk9fx zukJRkVx6OIPU|Bb594@n{fYdmL;h+7B}Q+`f#H(vAzdR0zBdv_hCeLJ%~8V> zZ9iM?&f2%5D1QIT9?RAG5bL$tFxFCu)oH7Xyj`1KHW<+gm7L6i%rI3|>B8aVH(WIO-jl*pt@l{($a^}z z;Lv_8pf^W#+J^irVA3to6JYx;8y>k{CcS2`-hYq%yV{EF$D6yLUK8ZnMicXvmb{{g zc*Wd)3%|#ymQy>b`ew#2O0F4;XUlYSL2rpZxpW(^Jl)oDL)h%Fi7^qXapYm*;aK^% zoabc=(wB})6fm!O7iQFJss-?N#uRJ4_S|hyN?L!%Vb#QieVNyI)}Bs2;{&+f4f%lw z2+ODPJA$_^ZOC~jGIn?&)p9qAAI(dbmvi0m&s5rj74&Gq7D!oQ#X#iFCo7>@<5O2Q zBuRaZ6ty*W8Y|Csz3^c*X2~YV?~Zb7-FvC!gYT$Z^>I41`s<-7zn6+%qxRdIPX;TV zh#iM2YA3mM_#H0q`O7fr$i~H}7!3c|`x|*WIMl77x)=I(tOOM;H`_%2lzIsnTh(<=rmDWWy~I9qX=T)?jKskE z&PuW$-M;%%zzEx{a?#9+_s^F|CR)T=20mZ(q2{E|2~e3+4NopQevsVuZZt*l(T4qY zI%#%&2jVVBM~-fT*y%T^TU&3k3?GCA?1-gikZo197*rFkke`BX(is}e&t8A)_?>BJ zQq5`Gb!XS{g16}0GThpg<)+%u*m1X^?zA0kRYRQ=aV!VAR^4Tv2paV!jk>+Z6rN1( zD{%oPlwZ}AS3G#U;VRz#?@pVN+cJ;0>u$B(YQH5|O-3z0Gc>wBG9HoQP^YX+xQ>6T zpB<4S|NQj$dTl-PCjzC~+*5+LS@|;j&J6(ywsL2kV=xPfVLC@@NF#l^psV?ij{s8y+H1ZY-FZ% zI7BWd&AG{J*`$5*^<>%H-N=&}k(`osD4B@r%}75brPp^eZ}&fGzkd7z8U1y!)>{7~ zri?QOg_4^%ni8LHrEw$wIu|LMT=2yCu4>%Ax6HE@$flzNThaX@r$+VRU8j1D<>T%Z z%iRe*fcW&x)EMJ6THREIzOh=9E`S!)n_>4a8-%Sy$kIP0W=~3(yZ2!14`{n2nR8bt zy;bP@Izp&>9zClNU6pb3iyz0pWaG)c^oJCJs8`nzhK$iE7!1DZNFP$1exI`QY~p@b z8LsdIG)%ERLCtT-+R>=ZHSX-9S36e-O?$#uKP`367T!xgGrfi&DsY&(QPK0f@PW3O z^V)~z_Yf+)Zl#PO&0t}v8+`t)IZKs-C@TU zX-Cy#%T;Wui(kBGCO@ve<>MvoWd@y=2nyl2@{cNw*~=ox4uZkd*P(&W+M1NH25d*l*heSez%yE+$gVe75UDn(cG6t^s^ zcXzxHzILGSZs@@x&-|i~MxW(17VQB}4gX_(v<7}0W)>Sw)XA-}PM;;NUxEGS@Angl z*Y+lClfAC9>!tT5c~xq=!mLy!JwW+_*3E2NrNPjk^hC^Afr=k~^}tj|kgxWp@m1Fz zZJ704c5+7Z=EH|%o2n`TC#dUTipdME^vboVD|KcixGhm-fZGpmM+YK>%|w!@ zY%+ipGCAP(1OhQO6>>yK$=ML`TP_6)v9n7TwRv`e3ukka2O8Mc>3FZdQr9-8l zfIxr=n)1L=F~1CHW9R7n!$T|qox$YHd4Xd8V##OF{uS$&*u*_^>HHiB=>CKIi}m;1 z=ZrxsM@OP1n-VMz&(6{WE*_sqWm6bb;{0C#N5&W$8Y1vm8VP~J7@!d(l0F7OGs55~ zcnk?o!5jR9vSac1Bo+k_qd+*40dmNO1^|^r!XS){NFV}FAs~zl0StmdBVcg)hGd*R z4fhjb3zq>_B`NUdsKh8Lh|)I#a0Ubt9)ZSE&|oxF0>Y3+Fhl@o10zEM8K4o!ggF$I zLbPUcnIv#J8B7u#Kyg^~xel@5L^EeQ6F3%${us95EKIS=|C&1-_l_+Ln&`AAxu6SCAU^1YvBypXBfH^Ff3(=LQ1(=DR3P*}(p1PRjMaS_}LDu})|#4uJ+g`tDc^5kny2 zvEV5YjfSJXC5#eZ)!#EVM*Sa6jOPq~S_VMB`8IHQ0aq*3kLBu{W?~uti?45U@n0MP zME@D&xAgrZ*B`llOM%}q{!?9lA{5yC7?L1)FVqE5J{4q zrJ1|oz0}rw7d?UHQsd9dD!c9k_`0vT;$VJNiUiY)J9oqrhC7yPmz?%q1_jPq{liOhS5q)pEPGKbjdP_!P_dBr=6E z23K4?>ThJfH(sha9+Msxy1C1_Kc_DIfV-N=cm^obM~gJ_@IT^*eYlEg#jmmeyOynJ!{do)8eGidEWAN3)SPXYZklWq)3{VUBUHS6Vb&?OZ;D_z;!%DI>+7Hr9y>-(&z{uGP{Y+J zb=Rz&?ksH+iJE#jDe857E)$lwJN{nVxpF~>;x<-Vnx;r(zj$P`@9uLUz0+~IY0bgmF+l+%ZJpC Date: Fri, 5 Apr 2024 20:38:10 -0400 Subject: [PATCH 007/169] tools/script_manager added parser for script_data.metadata blocks in scripts. Parsed data block is used as the tooltip. Fallback is the comment block at the top of the script. --- tools/script_manager.lua | 158 +++++++++++++++++++++++++++------------ 1 file changed, 109 insertions(+), 49 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index d54f9e6f..39072052 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -68,7 +68,7 @@ end -- api check -du.check_min_api_version("5.0.0", "script_manager") +du.check_min_api_version("9.3.0", "script_manager") -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S @@ -93,6 +93,10 @@ local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" local LUA_API_VER = "API-" .. dt.configuration.api_version_string +-- local POWER_ICON = dt.configuration.config_dir .. "/lua/data/data/icons/power.png" +local POWER_ICON = dt.configuration.config_dir .. "/lua/data/icons/path20.png" +local BLANK_ICON = dt.configuration.config_dir .. "/lua/data/icons/blank20.png" + -- - - - - - - - - - - - - - - - - - - - - - - - -- P R E F E R E N C E S -- - - - - - - - - - - - - - - - - - - - - - - - @@ -344,6 +348,10 @@ local function string_dequote(str) return string.gsub(str, "['\"]", "") end +local function string_dei18n(str) + return string.match(str, "%_%((.+)%)") +end + ------------------ -- script handling ------------------ @@ -358,6 +366,51 @@ local function add_script_folder(folder) restore_log_level(old_log_level) end +local function get_script_metadata(script) + local old_log_level = set_log_level(sm.log_level) + -- set_log_level(log.debug) + + log.msg(log.debug, "processing metatdata for " .. script) + + local description = nil + local metadata = nil + + f = io.open(LUA_DIR .. PS .. script .. ".lua") + if f then + -- slurp the file + local content = f:read("*all") + f:close() + -- grab the script_data.metadata table + description = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") + else + log.msg(log.error, _("Cant read from " .. script)) + end + + if description then + metadata = "" + -- format it into a string block for display + local lines = du.split(description, "\n") + log.msg(log.debug, "got " .. #lines .. " lines") + local first = 1 + for i = 1, #lines do + log.msg(log.debug, "splitting line " .. lines[i]) + local parts = du.split(lines[i], " = ") + log.msg(log.debug, "got value " .. parts[1] .. " and data " .. parts[2]) + if string.match(parts[2], "%_%(") then + parts[2] = _(string_dequote(string_dei18n(parts[2]))) + else + parts[2] = string_dequote(parts[2]) + end + metadata = metadata .. string.format("%s%-10s\t%s", first and "" or "\n", parts[1], parts[2]) + first = nil + end + log.msg(log.debug, "script data is \n" .. metadata) + end + + restore_log_level(old_log_level) + return metadata +end + local function get_script_doc(script) local old_log_level = set_log_level(sm.log_level) local description = nil @@ -461,6 +514,7 @@ local function add_script_name(name, path, folder) path = folder .. "/" .. path .. name, running = false, doc = get_script_doc(folder .. "/" .. path .. name), + metadata = get_script_metadata(folder .. "/" .. path .. name), script_name = folder .. "/" .. name, data = nil } @@ -511,7 +565,7 @@ end local function ensure_lib_in_search_path(line) local old_log_level = set_log_level(sm.log_level) - set_log_level(log.debug) + -- set_log_level(log.debug) log.msg(log.debug, "line is " .. line) if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua/lib")) then log.msg(log.debug, line .. " is already in search path, returning...") @@ -710,10 +764,12 @@ end local function clear_button(number) local old_log_level = set_log_level(sm.log_level) local button = sm.widgets.buttons[number] - button.label = "" + local label = sm.widgets.labels[number] + button.image = BLANK_ICON button.tooltip = "" button.sensitive = false - --button.name = "" + label.label = "" + button.name = "" restore_log_level(old_log_level) end @@ -734,58 +790,36 @@ local function populate_buttons(folder, first, last) log.msg(log.debug, "folder is " .. folder .. " and first is " .. first .. " and last is " .. last) local button_num = 1 for i = first, last do - script = sm.scripts[folder][i] - button = sm.widgets.buttons[button_num] + local script = sm.scripts[folder][i] + local button = sm.widgets.buttons[button_num] + local label = sm.widgets.labels[button_num] if script.running == true then - if sm.use_color then - button.label = script.name - button.name = "sm_started" - else - button.label = script.name .. _(" started") - end + button.name = "pb_on" else - if sm.use_color then - button.label = script.name - button.name = "sm_stopped" - else - button.label = script.name .. _(" stopped") - end + button.name = "pb_off" end - button.ellipsize = "middle" + button.image = POWER_ICON + label.label = script.name + label.name = "pb_label" + button.ellipsize = "end" button.sensitive = true - button.tooltip = script.doc + label.tooltip = script.metadata and script.metadata or script.doc button.clicked_callback = function (this) - local script_name = nil + local cb_script = script local state = nil - if sm.use_color then - script_name = string.match(this.label, "(.+)") - else - script_name, state = string.match(this.label, "(.-) (.+)") - end - local script = find_script(sm.widgets.folder_selector.value, script_name) - if script then - log.msg(log.debug, "found script " .. script.name .. " with path " .. script.path) - if script.running == true then - log.msg(log.debug, "deactivating " .. script.name .. " on " .. script.path .. " for button " .. this.label) - deactivate(script) - if sm.use_color then - this.name = "sm_stopped" - else - this.label = script.name .. _(" stopped") - end + if cb_script then + log.msg(log.debug, "found script " .. cb_script.name .. " with path " .. cb_script.path) + if cb_script.running == true then + log.msg(log.debug, "deactivating " .. cb_script.name .. " on " .. cb_script.path) + deactivate(cb_script) + this.name = "pb_off" else - log.msg(log.debug, "activating " .. script.name .. " on " .. script.path .. " for button " .. this.label) - local result = activate(script) + log.msg(log.debug, "activating " .. cb_script.name .. " on " .. script.path) + local result = activate(cb_script) if result then - if sm.use_color then - this.name = "sm_started" - else - this.label = script.name .. " started" - end + this.name = "pb_on" end end - else - log.msg(log.error, "script " .. script_name .. " not found") end end button_num = button_num + 1 @@ -870,18 +904,34 @@ end local function change_num_buttons() local old_log_level = set_log_level(sm.log_level) + -- set_log_level(log.debug) cur_buttons = sm.page_status.num_buttons new_buttons = sm.widgets.num_buttons.value pref_write("num_buttons", "integer", new_buttons) if new_buttons < cur_buttons then + log.msg(log.debug, "took new is less than current branch") for i = 1, cur_buttons - new_buttons do table.remove(sm.widgets.scripts) end log.msg(log.debug, "finished removing widgets, now there are " .. #sm.widgets.buttons) elseif new_buttons > cur_buttons then + log.msg(log.debug, "took new is greater than current branch") + log.msg(log.debug, "number of scripts is " .. #sm.widgets.scripts) + log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) + log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) + log.msg(log.debug, "number of boxes is " .. #sm.widgets.boxes) if new_buttons > sm.page_status.buttons_created then for i = sm.page_status.buttons_created + 1, new_buttons do + log.msg(log.debug, "i is " .. i) table.insert(sm.widgets.buttons, dt.new_widget("button"){}) + log.msg(log.debug, "inserted new button") + log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) + table.insert(sm.widgets.labels, dt.new_widget("label"){}) + log.msg(log.debug, "inserted new label") + log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) + table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, + sm.widgets.buttons[i], sm.widgets.labels[i]}) + log.msg(log.debug, "inserted new box") sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end end @@ -889,7 +939,7 @@ local function change_num_buttons() log.msg(log.debug, #sm.widgets.buttons .. " buttons are available") for i = cur_buttons + 1, new_buttons do log.msg(log.debug, "inserting button " .. i .. " into scripts widget") - table.insert(sm.widgets.scripts, sm.widgets.buttons[i]) + table.insert(sm.widgets.scripts, sm.widgets.boxes[i]) end log.msg(log.debug, "finished adding widgets, now there are " .. #sm.widgets.buttons) else -- no change @@ -1158,10 +1208,20 @@ sm.widgets.folder_selector = dt.new_widget("combobox"){ table.unpack(sm.folders), } +-- a script "button" consists of: +-- a button to start and stop the script +-- a label that contains the name of the script +-- a horizontal box that contains the button and the label + sm.widgets.buttons ={} +sm.widgets.labels = {} +sm.widgets.boxes = {} for i =1, DEFAULT_BUTTONS_PER_PAGE do table.insert(sm.widgets.buttons, dt.new_widget("button"){}) - sm.page_status.buttons_create = sm.page_status.buttons_created + 1 + table.insert(sm.widgets.labels, dt.new_widget("label"){}) + table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, + sm.widgets.buttons[i], sm.widgets.labels[i]}) + sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end local page_back = "<" @@ -1198,7 +1258,7 @@ sm.widgets.scripts = dt.new_widget("box"){ dt.new_widget("label"){label = _("Scripts")}, sm.widgets.folder_selector, sm.widgets.page_control, - table.unpack(sm.widgets.buttons) + table.unpack(sm.widgets.boxes) } -- configure options From 3b0ff3475a13dca20df22c706d95bc10a60f58ea Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 5 Apr 2024 21:12:45 -0400 Subject: [PATCH 008/169] tools/script_manager added blank lines to improve code readability --- tools/script_manager.lua | 177 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 174 insertions(+), 3 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 39072052..435768c3 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -216,17 +216,24 @@ end local function pref_read(name, pref_type) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type) + local val = dt.preferences.read(MODULE, name, pref_type) + log.msg(log.debug, "read value " .. tostring(val)) + restore_log_level(old_log_level) return val end local function pref_write(name, pref_type, value) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name) + dt.preferences.write(MODULE, name, pref_type, value) + restore_log_level(old_log_level) end @@ -236,12 +243,15 @@ end local function get_repo_status(repo) local old_log_level = set_log_level(sm.log_level) + local p = io.popen("cd " .. repo .. CS .. "git status") + if p then local data = p:read("*a") p:close() return data end + log.msg(log.error, "unable to get status of " .. repo) restore_log_level(old_log_level) return nil @@ -249,8 +259,11 @@ end local function get_current_repo_branch(repo) local old_log_level = set_log_level(sm.log_level) + local branch = nil + local p = io.popen("cd " .. repo .. CS .. "git branch --all") + if p then local data = p:read("*a") p:close() @@ -258,23 +271,29 @@ local function get_current_repo_branch(repo) for _, b in ipairs(branches) do log.msg(log.debug, "branch for testing is " .. b) branch = string.match(b, "^%* (.-)$") + if branch then log.msg(log.info, "current repo branch is " .. branch) return branch end + end end + if not branch then log.msg(log.error, "no current branch detected in repo_data") end + restore_log_level(old_log_level) return nil end local function get_repo_branches(repo) local old_log_level = set_log_level(sm.log_level) + local branches = {} local p = io.popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") + if p then local data = p:read("*a") p:close() @@ -289,12 +308,14 @@ local function get_repo_branches(repo) end end end + restore_log_level(old_log_level) return branches end local function is_repo_clean(repo_data) local old_log_level = set_log_level(sm.log_level) + if string.match(repo_data, "\n%s-%a.-%a:%s-%a%g-\n") then log.msg(log.info, "repo is dirty") return false @@ -302,13 +323,17 @@ local function is_repo_clean(repo_data) log.msg(log.info, "repo is clean") return true end + restore_log_level(old_log_level) end local function checkout_repo_branch(repo, branch) local old_log_level = set_log_level(sm.log_level) + log.msg(log.info, "checkout out branch " .. branch .. " from repository " .. repo) + os.execute("cd " .. repo .. CS .. "git checkout " .. branch) + restore_log_level(old_log_level) end @@ -318,16 +343,20 @@ end local function update_combobox_choices(combobox, choice_table, selected) local old_log_level = set_log_level(sm.log_level) + local items = #combobox local choices = #choice_table + for i, name in ipairs(choice_table) do combobox[i] = name end + if choices < items then for j = items, choices + 1, -1 do combobox[j] = nil end end + if not selected then selected = 1 end @@ -338,8 +367,10 @@ end local function string_trim(str) local old_log_level = set_log_level(sm.log_level) + local result = string.gsub(str, "^%s+", "") result = string.gsub(result, "%s+$", "") + restore_log_level(old_log_level) return result end @@ -358,11 +389,13 @@ end local function add_script_folder(folder) local old_log_level = set_log_level(sm.log_level) + if #sm.folders == 0 or not string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then table.insert(sm.folders, folder) sm.scripts[folder] = {} log.msg(log.debug, "created folder " .. folder) end + restore_log_level(old_log_level) end @@ -435,18 +468,26 @@ end local function activate(script) local old_log_level = set_log_level(sm.log_level) + local status = nil -- status of start function local err = nil -- error message returned if module doesn't start + log.msg(log.info, "activating " .. script.name) + if script.running == false then + script_manager_running_script = script.name + status, err = du.prequire(script.path) log.msg(log.debug, "prequire returned " .. tostring(status) .. " and for err " .. tostring(err)) + script_manager_running_script = nil + if status then pref_write(script.script_name, "bool", true) log.msg(log.screen, _("Loaded ") .. script.script_name) script.running = true + if err ~= true then log.msg(log.debug, "got lib data") script.data = err @@ -456,17 +497,20 @@ local function activate(script) else script.data = nil end + else log.msg(log.screen, script.script_name .. _(" failed to load")) log.msg(log.error, "Error loading " .. script.script_name) log.msg(log.error, "Error message: " .. err) end + else -- script is a lib and loaded but hidden and the user wants to reload script.data.restart() script.running = true status = true pref_write(script.script_name, "bool", true) end + restore_log_level(old_log_level) return status end @@ -481,9 +525,13 @@ local function deactivate(script) -- deactivate it.... local old_log_level = set_log_level(sm.log_level) + pref_write(script.script_name, "bool", false) + if script.data then + script.data.destroy() + if script.data.destroy_method then if string.match(script.data.destroy_method, "hide") then script.running = "hidden" @@ -495,20 +543,26 @@ local function deactivate(script) package.loaded[script.script_name] = nil script.running = false end + log.msg(log.info, "turned off " .. script.script_name) log.msg(log.screen, script.name .. _(" stopped")) + else script.running = false + log.msg(log.info, "setting " .. script.script_name .. " to not start") log.msg(log.screen, script.name .. _(" will not start when darktable is restarted")) end + restore_log_level(old_log_level) end local function add_script_name(name, path, folder) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "folder is " .. folder) log.msg(log.debug, "name is " .. name) + local script = { name = name, path = folder .. "/" .. path .. name, @@ -518,12 +572,15 @@ local function add_script_name(name, path, folder) script_name = folder .. "/" .. name, data = nil } + table.insert(sm.scripts[folder], script) + if pref_read(script.script_name, "bool") then activate(script) else pref_write(script.script_name, "bool", false) end + restore_log_level(old_log_level) end @@ -565,31 +622,43 @@ end local function ensure_lib_in_search_path(line) local old_log_level = set_log_level(sm.log_level) - -- set_log_level(log.debug) + log.msg(log.debug, "line is " .. line) + if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua/lib")) then log.msg(log.debug, line .. " is already in search path, returning...") return end + local path = string.match(line, "(.+)/lib/.+lua") + log.msg(log.debug, "extracted path is " .. path) log.msg(log.debug, "package.path is " .. package.path) + if not string.match(package.path, ds.sanitize_lua(path)) then + log.msg(log.debug, "path isn't in package.path, adding...") + package.path = package.path .. ";" .. path .. "/?.lua" + log.msg(log.debug, "new package.path is " .. package.path) end + restore_log_level(old_log_level) end local function scan_scripts(script_dir) local old_log_level = set_log_level(sm.log_level) + local script_count = 0 local find_cmd = "find -L " .. script_dir .. " -name \\*.lua -print | sort" + if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s \"" .. script_dir .. "\\*.lua\" | sort" end + log.msg(log.debug, _("find command is ") .. find_cmd) + -- scan the scripts local output = io.popen(find_cmd) for line in output:lines() do @@ -608,6 +677,7 @@ local function scan_scripts(script_dir) end end end + restore_log_level(old_log_level) return script_count end @@ -646,39 +716,54 @@ end local function update_script_update_choices() local old_log_level = set_log_level(sm.log_level) + local installs = {} local pref_string = "" + for i, repo in ipairs(sm.installed_repositories) do table.insert(installs, repo.name) pref_string = pref_string .. i .. "," .. repo.name .. "," .. repo.directory .. "," end + update_combobox_choices(sm.widgets.update_script_choices, installs, 1) + log.msg(log.debug, "repo pref string is " .. pref_string) pref_write("installed_repos", "string", pref_string) + restore_log_level(old_log_level) end local function scan_repositories() local old_log_level = set_log_level(sm.log_level) + local script_count = 0 local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.git -print | sort" + if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s /a:d " .. LUA_DIR .. PS .. "*.git | sort" end + log.msg(log.debug, _("find command is ") .. find_cmd) + local output = io.popen(find_cmd) + for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off - local folder = string.match(l, "(.-)" .. PS) -- get everything to teh first / + local folder = string.match(l, "(.-)" .. PS) -- get everything to the first / + if folder then -- if we have a folder (.git doesn't) + log.msg(log.debug, "found folder " .. folder) + if not string.match(folder, "plugins") and not string.match(folder, "%.git") then -- skip plugins + if #sm.installed_repositories == 1 then log.msg(log.debug, "only 1 repo, adding " .. folder) table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) else log.msg(log.debug, "more than 1 repo, we have to search the repos to make sure it's not there") local found = nil + for _, repo in ipairs(sm.installed_repositories) do if string.match(repo.name, ds.sanitize_lua(folder)) then log.msg(log.debug, "matched " .. repo.name) @@ -686,19 +771,24 @@ local function scan_repositories() break end end + if not found then table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) end + end end end end + update_script_update_choices() + restore_log_level(old_log_level) end local function install_scripts() local old_log_level = set_log_level(sm.log_level) + local url = sm.widgets.script_url.text local folder = sm.widgets.new_folder.text @@ -732,11 +822,13 @@ local function install_scripts() if result == 0 then local count = scan_scripts(LUA_DIR .. PS .. folder) + if count > 0 then update_combobox_choices(sm.widgets.folder_selector, sm.folders, sm.widgets.folder_selector.selected) dt.print(_("scripts successfully installed into folder ") .. folder) table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) update_script_update_choices() + for i = 1, #sm.widgets.folder_selector do if string.match(sm.widgets.folder_selector[i], ds.sanitize_lua(folder)) then log.msg(log.debug, "setting folder selector to " .. i) @@ -745,6 +837,7 @@ local function install_scripts() end i = i + 1 end + log.msg(log.debug, "clearing text fields") sm.widgets.script_url.text = "" sm.widgets.new_folder.text = "" @@ -753,6 +846,7 @@ local function install_scripts() dt.print(_("No scripts found to install")) log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") end + else dt.print(_("failed to download scripts")) end @@ -763,47 +857,59 @@ end local function clear_button(number) local old_log_level = set_log_level(sm.log_level) + local button = sm.widgets.buttons[number] local label = sm.widgets.labels[number] + button.image = BLANK_ICON button.tooltip = "" button.sensitive = false label.label = "" button.name = "" + restore_log_level(old_log_level) end local function find_script(folder, name) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "looking for script " .. name .. " in folder " .. folder) + for _, script in ipairs(sm.scripts[folder]) do if string.match(script.name, "^" .. ds.sanitize_lua(name) .. "$") then return script end end + restore_log_level(old_log_level) return nil end local function populate_buttons(folder, first, last) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "folder is " .. folder .. " and first is " .. first .. " and last is " .. last) + local button_num = 1 + for i = first, last do local script = sm.scripts[folder][i] local button = sm.widgets.buttons[button_num] local label = sm.widgets.labels[button_num] + if script.running == true then button.name = "pb_on" else button.name = "pb_off" end + button.image = POWER_ICON label.label = script.name label.name = "pb_label" button.ellipsize = "end" button.sensitive = true label.tooltip = script.metadata and script.metadata or script.doc + button.clicked_callback = function (this) local cb_script = script local state = nil @@ -822,23 +928,30 @@ local function populate_buttons(folder, first, last) end end end + button_num = button_num + 1 end + if button_num <= sm.page_status.num_buttons then for i = button_num, sm.page_status.num_buttons do clear_button(i) end end + restore_log_level(old_log_level) end local function paginate(direction) local old_log_level = set_log_level(sm.log_level) + local folder = sm.page_status.folder log.msg(log.debug, "folder is " .. folder) + local num_scripts = #sm.scripts[folder] log.msg(log.debug, "num_scripts is " .. num_scripts) + local max_pages = math.ceil(num_scripts / sm.page_status.num_buttons) + local cur_page = sm.page_status.current_page log.msg(log.debug, "max pages is " .. max_pages) @@ -860,7 +973,9 @@ local function paginate(direction) log.msg(log.debug, "took path 2") cur_page = 1 end + log.msg(log.debug, "cur_page is " .. cur_page .. " and max_pages is " .. max_pages) + if cur_page == max_pages and cur_page == 1 then sm.widgets.page_forward.sensitive = false sm.widgets.page_back.sensitive = false @@ -876,20 +991,25 @@ local function paginate(direction) end sm.page_status.current_page = cur_page + first = (cur_page * sm.page_status.num_buttons) - (sm.page_status.num_buttons - 1) + if first + sm.page_status.num_buttons > num_scripts then last = num_scripts else last = first + sm.page_status.num_buttons - 1 end + sm.widgets.page_status.label = _("Page ") .. cur_page .. _(" of ") .. max_pages populate_buttons(folder, first, last) + restore_log_level(old_log_level) end local function change_folder(folder) local old_log_level = set_log_level(sm.log_level) + if not folder then log.msg(log.debug "setting folder to selector value " .. sm.widgets.folder_selector.value) sm.page_status.folder = sm.widgets.folder_selector.value @@ -899,20 +1019,25 @@ local function change_folder(folder) end paginate(2) + restore_log_level(old_log_level) end local function change_num_buttons() local old_log_level = set_log_level(sm.log_level) - -- set_log_level(log.debug) + cur_buttons = sm.page_status.num_buttons new_buttons = sm.widgets.num_buttons.value + pref_write("num_buttons", "integer", new_buttons) + if new_buttons < cur_buttons then log.msg(log.debug, "took new is less than current branch") + for i = 1, cur_buttons - new_buttons do table.remove(sm.widgets.scripts) end + log.msg(log.debug, "finished removing widgets, now there are " .. #sm.widgets.buttons) elseif new_buttons > cur_buttons then log.msg(log.debug, "took new is greater than current branch") @@ -920,7 +1045,9 @@ local function change_num_buttons() log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) log.msg(log.debug, "number of boxes is " .. #sm.widgets.boxes) + if new_buttons > sm.page_status.buttons_created then + for i = sm.page_status.buttons_created + 1, new_buttons do log.msg(log.debug, "i is " .. i) table.insert(sm.widgets.buttons, dt.new_widget("button"){}) @@ -934,73 +1061,97 @@ local function change_num_buttons() log.msg(log.debug, "inserted new box") sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end + end + log.msg(log.debug, "cur_buttons is " .. cur_buttons .. " and new_buttons is " .. new_buttons) log.msg(log.debug, #sm.widgets.buttons .. " buttons are available") + for i = cur_buttons + 1, new_buttons do log.msg(log.debug, "inserting button " .. i .. " into scripts widget") table.insert(sm.widgets.scripts, sm.widgets.boxes[i]) end + log.msg(log.debug, "finished adding widgets, now there are " .. #sm.widgets.buttons) else -- no change log.msg(log.debug, "no change, just returning") return end + sm.page_status.num_buttons = new_buttons log.msg(log.debug, "num_buttons set to " .. sm.page_status.num_buttons) paginate(2) -- force the buttons to repopulate sm.widgets.main_menu.selected = 3 -- jump back to start/stop scripts + restore_log_level(old_log_level) end local function load_preferences() local old_log_level = set_log_level(sm.log_level) + -- load the prefs and update settings -- update_script_choices + local pref_string = pref_read("installed_repos", "string") local entries = du.split(pref_string, ",") + while #entries > 2 do local num = table.remove(entries, 1) local name = table.remove(entries, 1) local directory = table.remove(entries, 1) + if not string.match(sm.installed_repositories[1].name, "^" .. ds.sanitize_lua(name) .. "$") then table.insert(sm.installed_repositories, {name = name, directory = directory}) end + end + update_script_update_choices() log.msg(log.debug, "updated installed scripts") + -- folder selector local val = pref_read("folder_selector", "integer") + if val == 0 then val = 1 end + sm.widgets.folder_selector.selected = val sm.page_status.folder = sm.widgets.folder_selector.value log.msg(log.debug, "updated folder selector and set it to " .. sm.widgets.folder_selector.value) + -- num_buttons local val = pref_read("num_buttons", "integer") + if val == 0 then val = DEFAULT_BUTTONS_PER_PAGE end + sm.widgets.num_buttons.value = val log.msg(log.debug, "set page buttons to " .. val) + change_num_buttons() log.msg(log.debug, "paginated") + -- main menu local val = pref_read("main_menu_action", "integer") log.msg(log.debug, "read " .. val .. " for main menu") + if val == 0 then val = 3 end + sm.widgets.main_menu.selected = val log.msg(log.debug, "set main menu to val " .. val .. " which is " .. sm.widgets.main_menu.value) log.msg(log.debug, "set main menu to " .. sm.widgets.main_menu.value) + restore_log_level(old_log_level) end local function install_module() local old_log_level = set_log_level(sm.log_level) + if not sm.module_installed then dt.register_lib( "script_manager", -- Module name @@ -1014,6 +1165,7 @@ local function install_module() ) sm.module_installed = true end + sm.run = true sm.use_color = pref_read("use_color", "bool") log.msg(log.debug, "set run to true, loading preferences") @@ -1030,6 +1182,7 @@ local function install_module() dt.control.sleep(5000) dt.print_log("setting sm expanded true") dt.gui.libs["script_manager"].expanded = true]] + restore_log_level(old_log_level) end @@ -1048,22 +1201,28 @@ if check_for_updates then local repo = LUA_DIR if current_branch then + if sm.executables.git and clean and + (current_branch == "master" or string.match(current_branch, "^API%-")) then -- only make changes to clean branches local branches = get_repo_branches(LUA_DIR) + if current_branch ~= LUA_API_VER and current_branch ~= "master" then -- probably upgraded from an earlier api version so get back to master -- to use the latest version of script_manager to get the proper API checkout_repo_branch(repo, "master") log.msg(log.screen, "lua API version reset, please restart darktable") + elseif LUA_API_VER == current_branch then -- do nothing, we are fine log.msg(log.debug, "took equal branch, doing nothing") + elseif string.match(LUA_API_VER, "dev") then -- we are on a dev API version, so checkout the dev -- api version or checkout/stay on master log.msg(log.debug, "took the dev branch") local match = false + for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) if LUA_API_VER == branch then @@ -1072,6 +1231,7 @@ if check_for_updates then checkout_repo_branch(repo, branch) end end + if not match then if current_branch == "master" then log.msg(log.info, "staying on master, no dev branch yet") @@ -1080,25 +1240,33 @@ if check_for_updates then checkout_repo_branch(repo, "master") end end + elseif #branches > 0 and LUA_API_VER > branches[#branches] then log.msg(log.info, "no newer branches, staying on master") -- stay on master + else -- checkout the appropriate branch for API version if it exists log.msg(log.info, "checking out the appropriate API branch") + local match = false + for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) + if LUA_API_VER == branch then match = true log.msg(log.info, "checking out repo branch " .. branch) checkout_repo_branch(repo, branch) log.msg(log.screen, "you must restart darktable to use the correct version of the lua") end + end + if not match then log.msg(log.warn, "no matching branch found for " .. LUA_API_VER) end + end end end @@ -1216,6 +1384,7 @@ sm.widgets.folder_selector = dt.new_widget("combobox"){ sm.widgets.buttons ={} sm.widgets.labels = {} sm.widgets.boxes = {} + for i =1, DEFAULT_BUTTONS_PER_PAGE do table.insert(sm.widgets.buttons, dt.new_widget("button"){}) table.insert(sm.widgets.labels, dt.new_widget("label"){}) @@ -1228,6 +1397,7 @@ local page_back = "<" local page_forward = ">" sm.widgets.page_status = dt.new_widget("label"){label = _("Page:")} + sm.widgets.page_back = dt.new_widget("button"){ label = page_back, clicked_callback = function(this) @@ -1297,6 +1467,7 @@ sm.widgets.color = dt.new_widget("check_button"){ sm.use_color = this.value end } + table.insert(sm.widgets.configure, sm.widgets.color) -- stack for the options From ddf0c8940208bfe79c5a67aa2aa43ac71ad87ac1 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 6 Apr 2024 21:28:58 -0400 Subject: [PATCH 009/169] tools/script_manager - cleaned up layout of panels and added some spacing so what is being operated on is more appearent --- tools/script_manager.lua | 44 +++++++++++++++++++++++++++++----------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 435768c3..6e60af3b 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1351,14 +1351,25 @@ sm.widgets.disable_scripts = dt.new_widget("button"){ sm.widgets.install_update = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = _("update scripts")}, + dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("update scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.update_script_choices, sm.widgets.update, - dt.new_widget("section_label"){label = _("add more scripts")}, + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("add more scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.add_scripts, - dt.new_widget("section_label"){label = _("disable scripts")}, + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("disable scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.allow_disable, - sm.widgets.disable_scripts + sm.widgets.disable_scripts, + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, } -- manage the scripts @@ -1425,10 +1436,12 @@ sm.widgets.page_control = dt.new_widget("box"){ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, + dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("Scripts")}, sm.widgets.folder_selector, sm.widgets.page_control, - table.unpack(sm.widgets.boxes) + table.unpack(sm.widgets.boxes), } -- configure options @@ -1452,13 +1465,6 @@ sm.widgets.change_buttons = dt.new_widget("button"){ end } -sm.widgets.configure = dt.new_widget("box"){ - orientation = "vertical", - dt.new_widget("label"){label = _("Configuration")}, - sm.widgets.num_buttons, - sm.widgets.change_buttons, -} - sm.widgets.color = dt.new_widget("check_button"){ label = _("use color interface?"), value = pref_read("use_color", "bool"), @@ -1468,7 +1474,21 @@ sm.widgets.color = dt.new_widget("check_button"){ end } +sm.widgets.configure = dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("Configuration")}, + dt.new_widget("label"){label = " "}, + sm.widgets.num_buttons, + dt.new_widget("label"){label = " "}, + sm.widgets.change_buttons, + dt.new_widget("label"){label = " "}, +} + table.insert(sm.widgets.configure, sm.widgets.color) +table.insert(sm.widgets.configure, dt.new_widget("section_label"){label = " "}) +table.insert(sm.widgets.configure, dt.new_widget("label"){label = " "}) -- stack for the options From b1b3392b92ae816502ca7fd97528d2ab7ce4e9a0 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 9 Apr 2024 21:41:42 -0400 Subject: [PATCH 010/169] tools/script_manager removed color interface check button. Color can be specified in the CSS using the tags that are present if desired. Cleaned up formatting of configuration items. Added a few debugging messages. --- tools/script_manager.lua | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 6e60af3b..2313e309 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -184,9 +184,6 @@ sm.page_status.buttons_created = 0 sm.page_status.current_page = 0 sm.page_status.folder = "" --- use color in the interface? -sm.use_color = false - -- installed script repositories sm.installed_repositories = { {name = "lua-scripts", directory = LUA_DIR}, @@ -630,7 +627,8 @@ local function ensure_lib_in_search_path(line) return end - local path = string.match(line, "(.+)/lib/.+lua") + local pattern = dt.configuration.running_os == "windows" and "(.+)\\lib\\.+lua" or "(.+)/lib/.+lua" + local path = string.match(line, pattern) log.msg(log.debug, "extracted path is " .. path) log.msg(log.debug, "package.path is " .. package.path) @@ -657,11 +655,12 @@ local function scan_scripts(script_dir) find_cmd = "dir /b/s \"" .. script_dir .. "\\*.lua\" | sort" end - log.msg(log.debug, _("find command is ") .. find_cmd) + log.msg(log.debug, "find command is " .. find_cmd) -- scan the scripts local output = io.popen(find_cmd) for line in output:lines() do + log.msg(log.debug, "line is " .. line) local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off local script_file = l:sub(1,-5) -- strip off .lua\n if not string.match(script_file, "script_manager") then -- let's not include ourself @@ -1334,6 +1333,8 @@ sm.widgets.allow_disable = dt.new_widget("check_button"){ clicked_callback = function(this) if this.value == true then sm.widgets.disable_scripts.sensitive = true + else + sm.widgets.disable_scripts.sensitive = false end end, } @@ -1368,7 +1369,6 @@ sm.widgets.install_update = dt.new_widget("box"){ dt.new_widget("label"){label = " "}, sm.widgets.allow_disable, sm.widgets.disable_scripts, - dt.new_widget("section_label"){label = " "}, dt.new_widget("label"){label = " "}, } @@ -1465,15 +1465,6 @@ sm.widgets.change_buttons = dt.new_widget("button"){ end } -sm.widgets.color = dt.new_widget("check_button"){ - label = _("use color interface?"), - value = pref_read("use_color", "bool"), - clicked_callback = function(this) - pref_write("use_color", "bool", this.value) - sm.use_color = this.value - end -} - sm.widgets.configure = dt.new_widget("box"){ orientation = "vertical", dt.new_widget("section_label"){label = " "}, @@ -1486,10 +1477,6 @@ sm.widgets.configure = dt.new_widget("box"){ dt.new_widget("label"){label = " "}, } -table.insert(sm.widgets.configure, sm.widgets.color) -table.insert(sm.widgets.configure, dt.new_widget("section_label"){label = " "}) -table.insert(sm.widgets.configure, dt.new_widget("label"){label = " "}) - -- stack for the options sm.widgets.main_stack = dt.new_widget("stack"){ From f6916c3a15f0b57721501934dbc48a063787ecd6 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 15 May 2024 14:51:14 -0400 Subject: [PATCH 011/169] tools/script_manager - lower cased mixed case buttons and headings --- tools/script_manager.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 2313e309..28bd1d98 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1328,7 +1328,7 @@ sm.widgets.add_scripts = dt.new_widget("box"){ } sm.widgets.allow_disable = dt.new_widget("check_button"){ - label = _('Enable "Disable Scripts" button'), + label = _('enable "disable scripts" button'), value = false, clicked_callback = function(this) if this.value == true then @@ -1340,7 +1340,7 @@ sm.widgets.allow_disable = dt.new_widget("check_button"){ } sm.widgets.disable_scripts = dt.new_widget("button"){ - label = _("Disable Scripts"), + label = _("disable scripts"), sensitive = false, clicked_callback = function(this) local LUARC = dt.configuration.config_dir .. PS .. "luarc" @@ -1469,7 +1469,7 @@ sm.widgets.configure = dt.new_widget("box"){ orientation = "vertical", dt.new_widget("section_label"){label = " "}, dt.new_widget("label"){label = " "}, - dt.new_widget("label"){label = _("Configuration")}, + dt.new_widget("label"){label = _("configuration")}, dt.new_widget("label"){label = " "}, sm.widgets.num_buttons, dt.new_widget("label"){label = " "}, From 284c0f971484a367a1ed22bc69587deea4eafcef Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 15 May 2024 21:33:29 -0400 Subject: [PATCH 012/169] tools/script_manager - fixed metadata decoding to get rid of spurious commas --- tools/script_manager.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 28bd1d98..b79dbe30 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -60,7 +60,7 @@ local gettext = dt.gettext -- Tell gettext where to find the .mo file translating messages for a particular domain -gettext.bindtextdomain("script_manager",dt.configuration.config_dir.."/lua/locale/") +dt.gettext.bindtextdomain("script_manager",dt.configuration.config_dir.."/lua/locale/") local function _(msgid) return gettext.dgettext("script_manager", msgid) @@ -380,6 +380,10 @@ local function string_dei18n(str) return string.match(str, "%_%((.+)%)") end +local function string_chop(str) + return str:sub(1, -2) +end + ------------------ -- script handling ------------------ @@ -431,6 +435,9 @@ local function get_script_metadata(script) else parts[2] = string_dequote(parts[2]) end + if string.match(parts[2], ",$") then + parts[2] = string_chop(parts[2]) + end metadata = metadata .. string.format("%s%-10s\t%s", first and "" or "\n", parts[1], parts[2]) first = nil end @@ -438,6 +445,7 @@ local function get_script_metadata(script) end restore_log_level(old_log_level) + dt.print_log(metadata) return metadata end From bf138abeb2d21e52839f7c7aa1ef459c3898e46d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 15 May 2024 21:36:15 -0400 Subject: [PATCH 013/169] tools/script_manager - removed debugging statement --- tools/script_manager.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index b79dbe30..daaa824e 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -445,7 +445,6 @@ local function get_script_metadata(script) end restore_log_level(old_log_level) - dt.print_log(metadata) return metadata end From 3e2afffed82ff9488c0805bcfd389d8361269c7c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 18 May 2024 21:59:11 -0400 Subject: [PATCH 014/169] lib/dtutils/string - Added categories to substitution functionality. This makes it feature complete. Some substitutions aren't implemented since the current use case for this is creating export directories. The results from the not implemented substitutions wouldn't work well in path/file names (such as comma separated lists or newlines). --- lib/dtutils/string.lua | 291 +++++++++++++++++++++++++++-------------- 1 file changed, 195 insertions(+), 96 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index e315edaa..1cece46a 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -477,6 +477,7 @@ function dtutils_string.get_filetype(str) end local substitutes = {} +local category_substitutes = {} -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S @@ -488,7 +489,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "FILE.EXTENSION", "ID", "VERSION", - "VERSION.IF.MULTI", -- Not Implemented + "VERSION.IF.MULTI", "VERSION.NAME", "DARKTABLE.VERSION", "DARKTABLE.NAME", -- Not Implemented @@ -504,14 +505,20 @@ local PLACEHOLDERS = {"ROLL.NAME", "WIDTH.MAX", -- Not Implemented "HEIGHT.MAX", -- Not Implemented "YEAR", + "YEAR.SHORT", "MONTH", + "MONTH.LONG", + "MONTH.SHORT", "DAY", "HOUR", "MINUTE", "SECOND", "MSEC", "EXIF.YEAR", + "EXIF.YEAR.SHORT", "EXIF.MONTH", + "EXIF.MONTH.LONG", + "EXIF.MONTH.SHORT", "EXIF.DAY", "EXIF.HOUR", "EXIF.MINUTE", @@ -523,17 +530,22 @@ local PLACEHOLDERS = {"ROLL.NAME", "EXIF.EXPOSURE", "EXIF.EXPOSURE.BIAS", "EXIF.APERTURE", + "EXIF.CROP.FACTOR", "EXIF.FOCAL.LENGTH", + "EXIF.FOCAL.LENGTH.EQUIV", -- Not Implemented "EXIF.FOCUS.DISTANCE", + "IMAGE.EXIF", -- Not Implemented "LONGITUDE", "LATITUDE", - "ALTITUDE", + "ELEVATION", + "GPS.LOCATION", -- Not Implemented "STARS", "RATING.ICONS", -- Not Implemented "LABELS", "LABELS.ICONS", -- Not Implemented "MAKER", "MODEL", + "LENS", "TITLE", "DESCRIPTION", "CREATOR", @@ -547,7 +559,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "FOLDER.DESKTOP", "OPENCL.ACTIVATED", -- Not Implemented "USERNAME", - "NL", + "NL", -- Not Implemented "JOBCODE" -- Not Implemented } @@ -560,14 +572,19 @@ local DESKTOP = HOME .. PS .. "Desktop" local function get_colorlabels(image) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) + local colorlabels = {} + if image.red then table.insert(colorlabels, "red") end if image.yellow then table.insert(colorlabels, "yellow") end if image.green then table.insert(colorlabels, "green") end if image.blue then table.insert(colorlabels, "blue") end if image.purple then table.insert(colorlabels, "purple") end - local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",") + + local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, "_") + log.log_level(old_log_level) + return labels end @@ -575,9 +592,10 @@ dtutils_string.libdoc.functions["build_substitution_list"] = { Name = [[build_substitution_list]], Synopsis = [[build a list of variable substitutions]], Usage = [[local ds = require "lib/dtutils.string" - ds.build_substitution_list(image, sequence, [username], [pic_folder], [home], [desktop]) + ds.build_substitution_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed sequence - integer - the sequence number of the image + variable_string - string - the substitution variable string [username] - string - optional - user name. Will be determined if not supplied [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied [home] - string - optional - home directory. Will be determined if not supplied @@ -594,7 +612,46 @@ dtutils_string.libdoc.functions["build_substitution_list"] = { Copyright = [[]], } -function dtutils_string.build_substition_list(image, sequence, username, pic_folder, home, desktop) +local function build_category_substitution_list(image, variable_string) + -- scan the variable string for CATEGORYn(tag name) entries and build the substitutions + local old_log_level = log.log_level() + -- log.log_level(dtutils_string.log_level) + --log.log_level(log.debug) + + for match in string.gmatch(variable_string, "%$%(.-%)?%)") do + log.msg(log.info, "match is " .. match) + local var = string.match(match, "%$%((.-%)?)%)") + log.msg(log.info, "var is " .. var) + if string.match(var, "CATEGORY%d") then + local element, tag = string.match(var, "CATEGORY(%d)%((.-)%)") + element = element + 1 + log.msg(log.debug, "element is " .. element .. " and tag is " .. tag) + local tags = image:get_tags() + log.msg(log.debug, "got " .. #tags .. " from image " .. image.filename) + for _, image_tag in ipairs(tags) do + log.msg(log.debug, "checking tag " .. image_tag.name) + if string.match(image_tag.name, tag) then + parts = du.split(image_tag.name, "|") + substitutes[var] = parts[element] + log.msg(log.info, "set substitute for " .. var .. " to " .. parts[element]) + log.msg(log.info, "double check substitutes[" .. var .. "] is " .. substitutes[var]) + end + end + end + end + -- scan the variable string looking for CATEGORYn + -- retrieve the tag and compute the replacement string + -- add the found string as a PLACEHOLDER + -- add the replacement to the corresponding spot in the replacement table + log.log_level(old_log_level) +end + +local function exiftime2systime(exiftime) + local yr,mo,dy,h,m,s = string.match(exiftime, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d+)") + return(os.time{year=yr, month=mo, day=dy, hour=h, min=m, sec=s}) +end + +function dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) -- build the argument substitution list from each image local old_log_level = log.log_level() @@ -606,6 +663,8 @@ function dtutils_string.build_substition_list(image, sequence, username, pic_fol end local datetime = os.date("*t") + local long_month = os.date("%B") + local short_month = os.date("%b") local user_name = username or USER local pictures_folder = pic_folder or PICTURES local home_folder = home or HOME @@ -629,7 +688,8 @@ function dtutils_string.build_substition_list(image, sequence, username, pic_fol dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION image.id, -- ID image.duplicate_index, -- VERSION - "", -- VERSION.IF_MULTI + -- #image:get_group_members() > 1 and image.duplicate_index or "", -- VERSION.IF_MULTI + "", -- VERSION.IF_MULTI image.version_name, -- VERSION.NAME dt.configuration.version, -- DARKTABLE.VERSION "", -- DARKTABLE.NAME @@ -645,57 +705,71 @@ function dtutils_string.build_substition_list(image, sequence, username, pic_fol "", -- WIDTH.MAX "", -- HEIGHT.MAX string.format("%4d", datetime.year), -- YEAR + string.sub(datetime.year, 3), -- YEAR.SHORT string.format("%02d", datetime.month), -- MONTH + long_month, -- MONTH.LONG + short_month, -- MONTH.SHORT string.format("%02d", datetime.day), -- DAY string.format("%02d", datetime.hour), -- HOUR string.format("%02d", datetime.min), -- MINUTE string.format("%02d", datetime.sec), -- SECOND - "", -- MSEC + 0, -- MSEC eyear, -- EXIF.YEAR + string.sub(eyear, 3), -- EXIF.YEAR.SHORT emon, -- EXIF.MONTH + os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG + os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT eday, -- EXIF.DAY ehour, -- EXIF.HOUR emin, -- EXIF.MINUTE esec, -- EXIF.SECOND emsec, -- EXIF.MSEC - "", -- EXIF.DATE.REGIONAL - "", -- EXIF.TIME.REGIONAL + "", -- EXIF.DATE.REGIONAL - wont be implemented + "", -- EXIF.TIME.REGIONAL - wont be implemented string.format("%d", image.exif_iso), -- EXIF.ISO - string.format("1/%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE + string.format("%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE + string.format("%.01f", image.exif_crop),-- EXIF.CROP_FACTOR string.format("%.0f", image.exif_focal_length), -- EXIF.FOCAL.LENGTH + string.format("%.0f", image.exif_focal_length * image.exif_crop), -- EXIF.FOCAL.LENGTH.EQUIV image.exif_focus_distance, -- EXIF.FOCUS.DISTANCE + "", -- IMAGE.EXIF image.longitude or "", -- LONGITUDE image.latitude or "", -- LATITUDE - image.elevation or "", -- ALTITUDE + image.elevation or "", -- ELEVATION + "", -- GPS.LOCATION - wont be implemented image.rating, -- STARS - "", -- RATING.ICONS + "", -- RATING.ICONS - wont be implemented labels, -- LABELS - "", -- LABELS.ICONS + "", -- LABELS.ICONS - wont be implemented image.exif_maker, -- MAKER image.exif_model, -- MODEL + image.exif_lens, -- LENS image.title, -- TITLE image.description, -- DESCRIPTION image.creator, -- CREATOR image.publisher, -- PUBLISHER image.rights, -- RIGHTS - "", -- TAGS - "", -- CATEGORYn - "", -- SIDECAR.TXT + "", -- TAGS - wont be implemented + "", -- CATEGORY + "", -- SIDECAR.TXT - wont be implemented pictures_folder, -- FOLDER.PICTURES home_folder, -- FOLDER.HOME desktop_folder, -- FOLDER.DESKTOP - "", -- OPENCL.ACTIVATED + "", -- OPENCL.ACTIVATED - wont be implemented user_name, -- USERNAME - "\n", -- NL - "" -- JOBCODE + "", -- NL - wont be implemented + "" -- JOBCODE - wont be implemented } for i = 1, #PLACEHOLDERS, 1 do substitutes[PLACEHOLDERS[i]] = replacements[i] log.msg(log.info, "setting " .. PLACEHOLDERS[i] .. " to " .. tostring(replacements[i])) end + + build_category_substitution_list(image, variable_string) + log.log_level(old_log_level) end @@ -712,86 +786,105 @@ end local function treat(var_string) local old_log_level = log.log_level() - log.log_level(dtutils_string.log_level) + --log.log_level(dtutils_string.log_level) + --log.log_level(log.info) local ret_val = "" -- remove the var from the string local var = string.match(var_string, "[%a%._]+") var = check_legacy_vars(var) log.msg(log.info, "var_string is " .. tostring(var_string) .. " and var is " .. tostring(var)) - ret_val = substitutes[var] - if not ret_val then + if string.match(var_string, "CATEGORY%d") then + log.msg(log.info, "substituting for " .. var_string) + ret_val = substitutes[var_string] + log.msg(log.info, "ret_val is " .. ret_val) + else + ret_val = substitutes[var] + end + local valid_var = false + + if ret_val then + valid_var = true + --elseif string.match(var, "CATEGORY%d") then + --valid_var = true + end + + if not valid_var then log.msg(log.error, "variable " .. var .. " is not an allowed variable, returning empty value") log.log_level(old_log_level) return "" end - local args = string.gsub(var_string, var, "") - log.msg(log.info, "args is " .. tostring(args)) - if string.len(args) > 0 then - if string.match(args, '^%^%^') then - ret_val = string.upper(ret_val) - elseif string.match(args, "^%^") then - ret_val = string.gsub(ret_val, "^%a", string.upper, 1) - elseif string.match(args, "^,,") then - ret_val = string.lower(ret_val) - elseif string.match(args, "^,") then - ret_val = string.gsub(ret_val, "^%a", string.lower, 1) - elseif string.match(args, "^:%-?%d+:%-?%d+") then - local soffset, slen = string.match(args, ":(%-?%d+):(%-?%d+)") - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - if tonumber(soffset) >= 0 then - soffset = soffset + 1 - end - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - if tonumber(soffset) < 0 and tonumber(slen) < 0 then - local temp = soffset - soffset = slen - slen = temp - end - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - ret_val = string.sub(ret_val, soffset, slen) - log.msg(log.info, "ret_val is " .. ret_val) - elseif string.match(args, "^:%-?%d+") then - local soffset= string.match(args, ":(%-?%d+)") - if tonumber(soffset) >= 0 then - soffset = soffset + 1 - end - ret_val = string.sub(ret_val, soffset, -1) - elseif string.match(args, "^-%$%(.-%)") then - local replacement = string.match(args, "-%$%(([%a%._]+)%)") - replacement = check_legacy_vars(replacement) - if string.len(ret_val) == 0 then - ret_val = substitutes[replacement] - end - elseif string.match(args, "^-.+$") then - local replacement = string.match(args, "-(.+)$") - if string.len(ret_val) == 0 then - ret_val = replacement - end - elseif string.match(args, "^+.+") then - local replacement = string.match(args, "+(.+)") - if string.len(ret_val) > 0 then - ret_val = replacement + if string.match(var, "CATEGORY%d") then + ret_val = process_category(image, var, var_string) + else + local args = string.gsub(var_string, var, "") + log.msg(log.info, "args is " .. tostring(args)) + if string.len(args) > 0 then + if string.match(args, '^%^%^') then + ret_val = string.upper(ret_val) + elseif string.match(args, "^%^") then + ret_val = string.gsub(ret_val, "^%a", string.upper, 1) + elseif string.match(args, "^,,") then + ret_val = string.lower(ret_val) + elseif string.match(args, "^,") then + ret_val = string.gsub(ret_val, "^%a", string.lower, 1) + elseif string.match(args, "^:%-?%d+:%-?%d+") then + local soffset, slen = string.match(args, ":(%-?%d+):(%-?%d+)") + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + if tonumber(soffset) >= 0 then + soffset = soffset + 1 + end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + if tonumber(soffset) < 0 and tonumber(slen) < 0 then + local temp = soffset + soffset = slen + slen = temp + end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + ret_val = string.sub(ret_val, soffset, slen) + log.msg(log.info, "ret_val is " .. ret_val) + elseif string.match(args, "^:%-?%d+") then + local soffset= string.match(args, ":(%-?%d+)") + if tonumber(soffset) >= 0 then + soffset = soffset + 1 + end + ret_val = string.sub(ret_val, soffset, -1) + elseif string.match(args, "^-%$%(.-%)") then + local replacement = string.match(args, "-%$%(([%a%._]+)%)") + replacement = check_legacy_vars(replacement) + if string.len(ret_val) == 0 then + ret_val = substitutes[replacement] + end + elseif string.match(args, "^-.+$") then + local replacement = string.match(args, "-(.+)$") + if string.len(ret_val) == 0 then + ret_val = replacement + end + elseif string.match(args, "^+.+") then + local replacement = string.match(args, "+(.+)") + if string.len(ret_val) > 0 then + ret_val = replacement + end + elseif string.match(args, "^#.+") then + local pattern = string.match(args, "#(.+)") + log.msg(log.info, "pattern to remove is " .. tostring(pattern)) + ret_val = string.gsub(ret_val, "^" .. dtutils_string.sanitize_lua(pattern), "") + elseif string.match(args, "^%%.+") then + local pattern = string.match(args, "%%(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", "") + elseif string.match(args, "^//.-/.+") then + local pattern, replacement = string.match(args, "//(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement) + elseif string.match(args, "^/#.+/.+") then + local pattern, replacement = string.match(args, "/#(.+)/(.+)") + ret_val = string.gsub(ret_val, "^" .. pattern, replacement, 1) + elseif string.match(args, "^/%%.-/.+") then + local pattern, replacement = string.match(args, "/%%(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", replacement) + elseif string.match(args, "^/.-/.+") then + log.msg(log.info, "took replacement branch") + local pattern, replacement = string.match(args, "/(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement, 1) end - elseif string.match(args, "^#.+") then - local pattern = string.match(args, "#(.+)") - log.msg(log.info, "pattern to remove is " .. tostring(pattern)) - ret_val = string.gsub(ret_val, "^" .. dtutils_string.sanitize_lua(pattern), "") - elseif string.match(args, "^%%.+") then - local pattern = string.match(args, "%%(.+)") - ret_val = string.gsub(ret_val, pattern .. "$", "") - elseif string.match(args, "^//.-/.+") then - local pattern, replacement = string.match(args, "//(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern, replacement) - elseif string.match(args, "^/#.+/.+") then - local pattern, replacement = string.match(args, "/#(.+)/(.+)") - ret_val = string.gsub(ret_val, "^" .. pattern, replacement, 1) - elseif string.match(args, "^/%%.-/.+") then - local pattern, replacement = string.match(args, "/%%(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern .. "$", replacement) - elseif string.match(args, "^/.-/.+") then - log.msg(log.info, "took replacement branch") - local pattern, replacement = string.match(args, "/(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern, replacement, 1) end end log.log_level(old_log_level) @@ -848,9 +941,10 @@ dtutils_string.libdoc.functions["clear_substitute_list"] = { function dtutils_string.clear_substitute_list() local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) - for i = 1, #PLACEHOLDERS, 1 do - substitutes[PLACEHOLDERS[i]] = nil - end + + substitutes = {} + category_substitutes = {} + log.log_level(old_log_level) end @@ -858,9 +952,10 @@ dtutils_string.libdoc.functions["substitute"] = { Name = [[substitute]], Synopsis = [[Check if a string has been sanitized]], Usage = [[local ds = require "lib/dtutils.string" - ds.substitute(image, sequence, [username], [pic_folder], [home], [desktop]) + ds.substitute(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed sequence - integer - the sequence number of the image + variable_string - string - the substitution variable string [username] - string - optional - user name. Will be determined if not supplied [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied [home] - string - optional - home directory. Will be determined if not supplied @@ -880,13 +975,17 @@ dtutils_string.libdoc.functions["substitute"] = { function dtutils_string.substitute(image, sequence, variable_string, username, pic_folder, home, desktop) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) + dtutils_string.clear_substitute_list() - dtutils_string.build_substition_list(image, sequence, username, pic_folder, home, desktop) + + dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) + local str = dtutils_string.substitute_list(variable_string) + log.log_level(old_log_level) + return str end - return dtutils_string From 7b2018412a7180c09ab502062dac2d8939c8dec0 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 18 May 2024 22:01:10 -0400 Subject: [PATCH 015/169] lib/dtutils/string - Cleaned up code to make it more readable. Fixed VERSION_IF_MULTI. Added safety check in category to make sure we don't try and read a tag field that doesn't exist. --- lib/dtutils/string.lua | 353 ++++++++++++++++++++++++----------------- 1 file changed, 208 insertions(+), 145 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 1cece46a..dad0d558 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -476,6 +476,30 @@ function dtutils_string.get_filetype(str) return parts["filetype"] end +dtutils_string.libdoc.functions["build_substitution_list"] = { + Name = [[build_substitution_list]], + Synopsis = [[build a list of variable substitutions]], + Usage = [[local ds = require "lib/dtutils.string" + ds.build_substitution_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) + image - dt_lua_image_t - the image being processed + sequence - integer - the sequence number of the image + variable_string - string - the substitution variable string + [username] - string - optional - user name. Will be determined if not supplied + [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied + [home] - string - optional - home directory. Will be determined if not supplied + [desktop] - string - optional - desktop directory. Will be determined if not supplied]], + Description = [[build_substitution_list populates variables with values from the arguments + and determined from the system and darktable.]], + Return_Value = [[]], + Limitations = [[If the value for a variable can not be determined, or if it is not supported, + then an empty string is used for the value.]], + Example = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.2/en/special-topics/variables/]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + local substitutes = {} local category_substitutes = {} @@ -492,7 +516,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "VERSION.IF.MULTI", "VERSION.NAME", "DARKTABLE.VERSION", - "DARKTABLE.NAME", -- Not Implemented + "DARKTABLE.NAME", -- Not Implemented "SEQUENCE", "WIDTH.SENSOR", "HEIGHT.SENSOR", @@ -502,8 +526,8 @@ local PLACEHOLDERS = {"ROLL.NAME", "HEIGHT.CROP", "WIDTH.EXPORT", "HEIGHT.EXPORT", - "WIDTH.MAX", -- Not Implemented - "HEIGHT.MAX", -- Not Implemented + "WIDTH.MAX", -- Not Implemented + "HEIGHT.MAX", -- Not Implemented "YEAR", "YEAR.SHORT", "MONTH", @@ -524,8 +548,8 @@ local PLACEHOLDERS = {"ROLL.NAME", "EXIF.MINUTE", "EXIF.SECOND", "EXIF.MSEC", - "EXIF.DATE.REGIONAL", -- Not Implemented - "EXIF.TIME.REGIONAL", -- Not Implemented + "EXIF.DATE.REGIONAL", -- Not Implemented + "EXIF.TIME.REGIONAL", -- Not Implemented "EXIF.ISO", "EXIF.EXPOSURE", "EXIF.EXPOSURE.BIAS", @@ -534,15 +558,15 @@ local PLACEHOLDERS = {"ROLL.NAME", "EXIF.FOCAL.LENGTH", "EXIF.FOCAL.LENGTH.EQUIV", -- Not Implemented "EXIF.FOCUS.DISTANCE", - "IMAGE.EXIF", -- Not Implemented + "IMAGE.EXIF", -- Not Implemented "LONGITUDE", "LATITUDE", "ELEVATION", - "GPS.LOCATION", -- Not Implemented + "GPS.LOCATION", -- Not Implemented "STARS", - "RATING.ICONS", -- Not Implemented + "RATING.ICONS", -- Not Implemented "LABELS", - "LABELS.ICONS", -- Not Implemented + "LABELS.ICONS", -- Not Implemented "MAKER", "MODEL", "LENS", @@ -551,23 +575,22 @@ local PLACEHOLDERS = {"ROLL.NAME", "CREATOR", "PUBLISHER", "RIGHTS", - "TAGS", -- Not Implemented - "CATEGORY", -- Not Implemented - "SIDECAR.TXT", -- Not Implemented + "TAGS", -- Not Implemented + "SIDECAR.TXT", -- Not Implemented "FOLDER.PICTURES", "FOLDER.HOME", "FOLDER.DESKTOP", - "OPENCL.ACTIVATED", -- Not Implemented + "OPENCL.ACTIVATED", -- Not Implemented "USERNAME", - "NL", -- Not Implemented - "JOBCODE" -- Not Implemented + "NL", -- Not Implemented + "JOBCODE" -- Not Implemented } -local PS = dt.configuration.running_os == "windows" and "\\" or "/" -local USER = os.getenv("USERNAME") -local HOME = dt.configuration.running_os == "windows" and os.getenv("HOMEPATH") or os.getenv("HOME") -local PICTURES = HOME .. PS .. (dt.configuration.running_os == "windows" and "My Pictures" or "Pictures") -local DESKTOP = HOME .. PS .. "Desktop" +local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local USER = os.getenv("USERNAME") +local HOME = dt.configuration.running_os == "windows" and os.getenv("HOMEPATH") or os.getenv("HOME") +local PICTURES = HOME .. PS .. (dt.configuration.running_os == "windows" and "My Pictures" or "Pictures") +local DESKTOP = HOME .. PS .. "Desktop" local function get_colorlabels(image) local old_log_level = log.log_level() @@ -588,75 +611,64 @@ local function get_colorlabels(image) return labels end -dtutils_string.libdoc.functions["build_substitution_list"] = { - Name = [[build_substitution_list]], - Synopsis = [[build a list of variable substitutions]], - Usage = [[local ds = require "lib/dtutils.string" - ds.build_substitution_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) - image - dt_lua_image_t - the image being processed - sequence - integer - the sequence number of the image - variable_string - string - the substitution variable string - [username] - string - optional - user name. Will be determined if not supplied - [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied - [home] - string - optional - home directory. Will be determined if not supplied - [desktop] - string - optional - desktop directory. Will be determined if not supplied]], - Description = [[build_substitution_list populates variables with values from the arguments - and determined from the system and darktable.]], - Return_Value = [[]], - Limitations = [[If the value for a variable can not be determined, or if it is not supported, - then an empty string is used for the value.]], - Example = [[]], - See_Also = [[https://docs.darktable.org/usermanual/4.2/en/special-topics/variables/]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} +-- find the $CATEGORYn requests and add them to the substitute list local function build_category_substitution_list(image, variable_string) - -- scan the variable string for CATEGORYn(tag name) entries and build the substitutions local old_log_level = log.log_level() - -- log.log_level(dtutils_string.log_level) - --log.log_level(log.debug) + log.log_level(dtutils_string.log_level) - for match in string.gmatch(variable_string, "%$%(.-%)?%)") do + for match in string.gmatch(variable_string, "%$%(.-%)?%)") do -- grab each complete variable log.msg(log.info, "match is " .. match) - local var = string.match(match, "%$%((.-%)?)%)") + + local var = string.match(match, "%$%((.-%)?)%)") -- strip of the leading $( and trailing ) log.msg(log.info, "var is " .. var) + if string.match(var, "CATEGORY%d") then - local element, tag = string.match(var, "CATEGORY(%d)%((.-)%)") - element = element + 1 + local element, tag = string.match(var, "CATEGORY(%d)%((.-)%)") -- get the element number and the tag to match + + element = element + 1 -- add one to element since lua arrays are 1 based log.msg(log.debug, "element is " .. element .. " and tag is " .. tag) + local tags = image:get_tags() log.msg(log.debug, "got " .. #tags .. " from image " .. image.filename) + for _, image_tag in ipairs(tags) do log.msg(log.debug, "checking tag " .. image_tag.name) + if string.match(image_tag.name, tag) then - parts = du.split(image_tag.name, "|") - substitutes[var] = parts[element] - log.msg(log.info, "set substitute for " .. var .. " to " .. parts[element]) - log.msg(log.info, "double check substitutes[" .. var .. "] is " .. substitutes[var]) + fields = du.split(image_tag.name, "|") + + if element <= #fields then + substitutes[var] = fields[element] + else + substitutes[var] = "" + log.msg(log.warn, "requested field for tag " .. tag .. " doesn't exist") + end + + log.msg(log.info, "set substitute for " .. var .. " to " .. fields[element]) + end end end end - -- scan the variable string looking for CATEGORYn - -- retrieve the tag and compute the replacement string - -- add the found string as a PLACEHOLDER - -- add the replacement to the corresponding spot in the replacement table log.log_level(old_log_level) end +-- convert image.exif_datetime_taken to system time + local function exiftime2systime(exiftime) local yr,mo,dy,h,m,s = string.match(exiftime, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d+)") return(os.time{year=yr, month=mo, day=dy, hour=h, min=m, sec=s}) end -function dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) - -- build the argument substitution list from each image +-- build the argument substitution list from each image +function dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) + -- is time millisecond aware? Implemented in API 9.1.0 + local is_api_9_1 = true if dt.configuration.api_version_string < "9.1.0" then is_api_9_1 = false @@ -668,11 +680,10 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, local user_name = username or USER local pictures_folder = pic_folder or PICTURES local home_folder = home or HOME - log.msg(log.info, "home is " .. tostring(home) .. " and HOME is " .. tostring(HOME) .. " and home_folder is " .. tostring(home_folder)) local desktop_folder = desktop or DESKTOP local labels = get_colorlabels(image) - log.msg(log.info, "image date time taken is " .. image.exif_datetime_taken) + local eyear, emon, eday, ehour, emin, esec, emsec if dt.preferences.read("darktable", "lighttable/ui/milliseconds", "bool") and is_api_9_1 then eyear, emon, eday, ehour, emin, esec, emsec = @@ -682,14 +693,16 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, eyear, emon, eday, ehour, emin, esec = string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$") end + + local version_multi = #image:get_group_members() > 1 and image.version or "" + local replacements = {image.film, -- ROLL.NAME image.path, -- FILE.FOLDER image.filename, -- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION image.id, -- ID image.duplicate_index, -- VERSION - -- #image:get_group_members() > 1 and image.duplicate_index or "", -- VERSION.IF_MULTI - "", -- VERSION.IF_MULTI + version_multi, -- VERSION.IF_MULTI image.version_name, -- VERSION.NAME dt.configuration.version, -- DARKTABLE.VERSION "", -- DARKTABLE.NAME @@ -698,12 +711,12 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, image.height, -- HEIGHT.SENSOR is_api_9_1 and image.p_width or "", -- WIDTH.RAW is_api_9_1 and image.p_height or "", -- HEIGHT.RAW - is_api_9_1 and image.final_width or "", -- WIDTH.CROP + is_api_9_1 and image.final_width or "", -- WIDTH.CROP is_api_9_1 and image.final_height or "", -- HEIGHT.CROP is_api_9_1 and image.final_width or "", -- WIDTH.EXPORT is_api_9_1 and image.final_height or "", -- HEIGHT.EXPORT - "", -- WIDTH.MAX - "", -- HEIGHT.MAX + "", -- WIDTH.MAX -- from export module + "", -- HEIGHT.MAX -- from export module string.format("%4d", datetime.year), -- YEAR string.sub(datetime.year, 3), -- YEAR.SHORT string.format("%02d", datetime.month), -- MONTH @@ -717,8 +730,8 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, eyear, -- EXIF.YEAR string.sub(eyear, 3), -- EXIF.YEAR.SHORT emon, -- EXIF.MONTH - os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG - os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT + os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG + os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT eday, -- EXIF.DAY ehour, -- EXIF.HOUR emin, -- EXIF.MINUTE @@ -752,7 +765,6 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, image.publisher, -- PUBLISHER image.rights, -- RIGHTS "", -- TAGS - wont be implemented - "", -- CATEGORY "", -- SIDECAR.TXT - wont be implemented pictures_folder, -- FOLDER.PICTURES home_folder, -- FOLDER.HOME @@ -763,36 +775,50 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, "" -- JOBCODE - wont be implemented } + -- populate the substitution list + for i = 1, #PLACEHOLDERS, 1 do substitutes[PLACEHOLDERS[i]] = replacements[i] log.msg(log.info, "setting " .. PLACEHOLDERS[i] .. " to " .. tostring(replacements[i])) end + -- do category substitutions separately + build_category_substitution_list(image, variable_string) log.log_level(old_log_level) end +-- handle different versions of names + local function check_legacy_vars(var_name) local var = var_name + if string.match(var, "_") then var = string.gsub(var, "_", ".") end + if string.match(var, "^HOME$") then var = "FOLDER.HOME" end if string.match(var, "^PICTURES.FOLDER$") then var = "FOLDER.PICTURES" end if string.match(var, "^DESKTOP$") then var = "FOLDER.DESKTOP" end + return var end +-- get the substitution and do any string manipulations requested + local function treat(var_string) local old_log_level = log.log_level() - --log.log_level(dtutils_string.log_level) - --log.log_level(log.info) + log.log_level(dtutils_string.log_level) + local ret_val = "" + -- remove the var from the string local var = string.match(var_string, "[%a%._]+") + var = check_legacy_vars(var) log.msg(log.info, "var_string is " .. tostring(var_string) .. " and var is " .. tostring(var)) + if string.match(var_string, "CATEGORY%d") then log.msg(log.info, "substituting for " .. var_string) ret_val = substitutes[var_string] @@ -800,12 +826,11 @@ local function treat(var_string) else ret_val = substitutes[var] end + local valid_var = false if ret_val then valid_var = true - --elseif string.match(var, "CATEGORY%d") then - --valid_var = true end if not valid_var then @@ -813,79 +838,109 @@ local function treat(var_string) log.log_level(old_log_level) return "" end - if string.match(var, "CATEGORY%d") then - ret_val = process_category(image, var, var_string) - else - local args = string.gsub(var_string, var, "") - log.msg(log.info, "args is " .. tostring(args)) - if string.len(args) > 0 then - if string.match(args, '^%^%^') then - ret_val = string.upper(ret_val) - elseif string.match(args, "^%^") then - ret_val = string.gsub(ret_val, "^%a", string.upper, 1) - elseif string.match(args, "^,,") then - ret_val = string.lower(ret_val) - elseif string.match(args, "^,") then - ret_val = string.gsub(ret_val, "^%a", string.lower, 1) - elseif string.match(args, "^:%-?%d+:%-?%d+") then - local soffset, slen = string.match(args, ":(%-?%d+):(%-?%d+)") - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - if tonumber(soffset) >= 0 then - soffset = soffset + 1 - end - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - if tonumber(soffset) < 0 and tonumber(slen) < 0 then - local temp = soffset - soffset = slen - slen = temp - end - log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) - ret_val = string.sub(ret_val, soffset, slen) - log.msg(log.info, "ret_val is " .. ret_val) - elseif string.match(args, "^:%-?%d+") then - local soffset= string.match(args, ":(%-?%d+)") - if tonumber(soffset) >= 0 then - soffset = soffset + 1 - end - ret_val = string.sub(ret_val, soffset, -1) - elseif string.match(args, "^-%$%(.-%)") then - local replacement = string.match(args, "-%$%(([%a%._]+)%)") - replacement = check_legacy_vars(replacement) - if string.len(ret_val) == 0 then - ret_val = substitutes[replacement] - end - elseif string.match(args, "^-.+$") then - local replacement = string.match(args, "-(.+)$") - if string.len(ret_val) == 0 then - ret_val = replacement - end - elseif string.match(args, "^+.+") then - local replacement = string.match(args, "+(.+)") - if string.len(ret_val) > 0 then - ret_val = replacement - end - elseif string.match(args, "^#.+") then - local pattern = string.match(args, "#(.+)") - log.msg(log.info, "pattern to remove is " .. tostring(pattern)) - ret_val = string.gsub(ret_val, "^" .. dtutils_string.sanitize_lua(pattern), "") - elseif string.match(args, "^%%.+") then - local pattern = string.match(args, "%%(.+)") - ret_val = string.gsub(ret_val, pattern .. "$", "") - elseif string.match(args, "^//.-/.+") then - local pattern, replacement = string.match(args, "//(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern, replacement) - elseif string.match(args, "^/#.+/.+") then - local pattern, replacement = string.match(args, "/#(.+)/(.+)") - ret_val = string.gsub(ret_val, "^" .. pattern, replacement, 1) - elseif string.match(args, "^/%%.-/.+") then - local pattern, replacement = string.match(args, "/%%(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern .. "$", replacement) - elseif string.match(args, "^/.-/.+") then - log.msg(log.info, "took replacement branch") - local pattern, replacement = string.match(args, "/(.-)/(.+)") - ret_val = string.gsub(ret_val, pattern, replacement, 1) + + -- string modifications + + local args = string.gsub(var_string, var, "") + log.msg(log.info, "args is " .. tostring(args)) + + if string.len(args) > 0 then + + if string.match(args, '^%^%^') then + ret_val = string.upper(ret_val) + + elseif string.match(args, "^%^") then + ret_val = string.gsub(ret_val, "^%a", string.upper, 1) + + elseif string.match(args, "^,,") then + ret_val = string.lower(ret_val) + + elseif string.match(args, "^,") then + ret_val = string.gsub(ret_val, "^%a", string.lower, 1) + + elseif string.match(args, "^:%-?%d+:%-?%d+") then + + local soffset, slen = string.match(args, ":(%-?%d+):(%-?%d+)") + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + + if tonumber(soffset) >= 0 then + soffset = soffset + 1 end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + + if tonumber(soffset) < 0 and tonumber(slen) < 0 then + local temp = soffset + soffset = slen + slen = temp + end + log.msg(log.info, "soffset is " .. soffset .. " and slen is " .. slen) + + ret_val = string.sub(ret_val, soffset, slen) + log.msg(log.info, "ret_val is " .. ret_val) + + elseif string.match(args, "^:%-?%d+") then + + local soffset= string.match(args, ":(%-?%d+)") + if tonumber(soffset) >= 0 then + soffset = soffset + 1 + end + ret_val = string.sub(ret_val, soffset, -1) + + elseif string.match(args, "^-%$%(.-%)") then + + local replacement = string.match(args, "-%$%(([%a%._]+)%)") + replacement = check_legacy_vars(replacement) + if string.len(ret_val) == 0 then + ret_val = substitutes[replacement] + end + + elseif string.match(args, "^-.+$") then + + local replacement = string.match(args, "-(.+)$") + if string.len(ret_val) == 0 then + ret_val = replacement + end + + elseif string.match(args, "^+.+") then + + local replacement = string.match(args, "+(.+)") + if string.len(ret_val) > 0 then + ret_val = replacement + end + + elseif string.match(args, "^#.+") then + + local pattern = string.match(args, "#(.+)") + log.msg(log.info, "pattern to remove is " .. tostring(pattern)) + ret_val = string.gsub(ret_val, "^" .. dtutils_string.sanitize_lua(pattern), "") + + elseif string.match(args, "^%%.+") then + + local pattern = string.match(args, "%%(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", "") + + elseif string.match(args, "^//.-/.+") then + + local pattern, replacement = string.match(args, "//(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement) + + elseif string.match(args, "^/#.+/.+") then + + local pattern, replacement = string.match(args, "/#(.+)/(.+)") + ret_val = string.gsub(ret_val, "^" .. pattern, replacement, 1) + + elseif string.match(args, "^/%%.-/.+") then + + local pattern, replacement = string.match(args, "/%%(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern .. "$", replacement) + + elseif string.match(args, "^/.-/.+") then + + log.msg(log.info, "took replacement branch") + local pattern, replacement = string.match(args, "/(.-)/(.+)") + ret_val = string.gsub(ret_val, pattern, replacement, 1) end + end log.log_level(old_log_level) return ret_val @@ -911,15 +966,22 @@ dtutils_string.libdoc.functions["substitute_list"] = { function dtutils_string.substitute_list(str) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) + -- replace the substitution variables in a string for match in string.gmatch(str, "%$%(.-%)?%)") do + local var = string.match(match, "%$%((.-%)?)%)") + local treated_var = treat(var) log.msg(log.info, "var is " .. var .. " and treated var is " .. tostring(treated_var)) + str = string.gsub(str, "%$%(".. dtutils_string.sanitize_lua(var) .."%)", tostring(treated_var)) log.msg(log.info, "str after replacement is " .. str) + end + log.log_level(old_log_level) + return str end @@ -988,4 +1050,5 @@ function dtutils_string.substitute(image, sequence, variable_string, username, p end + return dtutils_string From 97e33672ea79d29073aef37364c6c523bd6d5bad Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 19 May 2024 23:04:01 -0400 Subject: [PATCH 016/169] lib/dtutils/string - fixed default formatting of sequence --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index dad0d558..c172ff66 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -706,7 +706,7 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, image.version_name, -- VERSION.NAME dt.configuration.version, -- DARKTABLE.VERSION "", -- DARKTABLE.NAME - sequence, -- SEQUENCE + string.format("%04d", sequence), -- SEQUENCE image.width, -- WIDTH.SENSOR image.height, -- HEIGHT.SENSOR is_api_9_1 and image.p_width or "", -- WIDTH.RAW From 3ed4219e6c2f92cdc01eabb9d88227b972c83948 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 20 May 2024 00:56:01 -0400 Subject: [PATCH 017/169] lib/dtutils/string - added [] to sanitize_lua(). added 4.8 syntax to CATEGORY added 4.8 SEQUENCE changes (syntax, start, width) --- lib/dtutils/string.lua | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index c172ff66..2e4b7d86 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -335,6 +335,8 @@ function dtutils_string.sanitize_lua(str) str = string.gsub(str, "%-", "%%-") str = string.gsub(str, "%(", "%%(") str = string.gsub(str, "%)", "%%)") + str = string.gsub(str, "%[", "%%[") + str = string.gsub(str, "%]", "%%]") str = string.gsub(str, "+", "%%+") log.log_level(old_log_level) return str @@ -623,8 +625,15 @@ local function build_category_substitution_list(image, variable_string) local var = string.match(match, "%$%((.-%)?)%)") -- strip of the leading $( and trailing ) log.msg(log.info, "var is " .. var) - if string.match(var, "CATEGORY%d") then - local element, tag = string.match(var, "CATEGORY(%d)%((.-)%)") -- get the element number and the tag to match + if string.match(var, "CATEGORY%d") or string.match(var, "CATEGORY%[") then + local element + local tag + + if string.match(var, "CATEGORY%d") then + element, tag = string.match(var, "CATEGORY(%d)%((.-)%)") -- get the element number and the tag to match + else + element, tag = string.match(var, "%[(%d),(.-)%]") -- new syntax + end element = element + 1 -- add one to element since lua arrays are 1 based log.msg(log.debug, "element is " .. element .. " and tag is " .. tag) @@ -696,7 +705,7 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, local version_multi = #image:get_group_members() > 1 and image.version or "" - local replacements = {image.film, -- ROLL.NAME + local replacements = {image.film.path, -- ROLL.NAME image.path, -- FILE.FOLDER image.filename, -- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION @@ -819,10 +828,18 @@ local function treat(var_string) var = check_legacy_vars(var) log.msg(log.info, "var_string is " .. tostring(var_string) .. " and var is " .. tostring(var)) - if string.match(var_string, "CATEGORY%d") then + if string.match(var_string, "CATEGORY%d") or string.match(var_string, "CATEGORY%[") then log.msg(log.info, "substituting for " .. var_string) ret_val = substitutes[var_string] log.msg(log.info, "ret_val is " .. ret_val) + + elseif string.match(var_string, "SEQUENCE%[") then + local start, width = string.match(var_string, "(%d+),(%d)") + local seq_val = tonumber(substitutes[var]) + local pat = "%0" .. width .. "d" + substitutes[var_string] = string.format(pat, start + (seq_val - 1)) + ret_val = substitutes[var_string] + else ret_val = substitutes[var] end @@ -943,6 +960,7 @@ local function treat(var_string) end log.log_level(old_log_level) + dt.print_log("returning ret_val of " .. ret_val) return ret_val end @@ -965,7 +983,8 @@ dtutils_string.libdoc.functions["substitute_list"] = { function dtutils_string.substitute_list(str) local old_log_level = log.log_level() - log.log_level(dtutils_string.log_level) + -- log.log_level(dtutils_string.log_level) + log.log_level(log.info) -- replace the substitution variables in a string for match in string.gmatch(str, "%$%(.-%)?%)") do @@ -975,6 +994,8 @@ function dtutils_string.substitute_list(str) local treated_var = treat(var) log.msg(log.info, "var is " .. var .. " and treated var is " .. tostring(treated_var)) + dt.print_log("str is " .. str) + str = string.gsub(str, "%$%(".. dtutils_string.sanitize_lua(var) .."%)", tostring(treated_var)) log.msg(log.info, "str after replacement is " .. str) From 232baab86bd559684d97aab3c154da6c240d5deb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 20 May 2024 10:07:13 -0400 Subject: [PATCH 018/169] lib/dtutils/string - removed debugging statements --- lib/dtutils/string.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 2e4b7d86..7690d59c 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -960,7 +960,6 @@ local function treat(var_string) end log.log_level(old_log_level) - dt.print_log("returning ret_val of " .. ret_val) return ret_val end @@ -983,8 +982,7 @@ dtutils_string.libdoc.functions["substitute_list"] = { function dtutils_string.substitute_list(str) local old_log_level = log.log_level() - -- log.log_level(dtutils_string.log_level) - log.log_level(log.info) + log.log_level(dtutils_string.log_level) -- replace the substitution variables in a string for match in string.gmatch(str, "%$%(.-%)?%)") do @@ -994,8 +992,6 @@ function dtutils_string.substitute_list(str) local treated_var = treat(var) log.msg(log.info, "var is " .. var .. " and treated var is " .. tostring(treated_var)) - dt.print_log("str is " .. str) - str = string.gsub(str, "%$%(".. dtutils_string.sanitize_lua(var) .."%)", tostring(treated_var)) log.msg(log.info, "str after replacement is " .. str) From 417c165d16dc7d65ccfd2be5906ab05e0bb12c85 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 20 May 2024 18:18:46 -0400 Subject: [PATCH 019/169] lib/dtutils/string - updated documentation --- lib/dtutils/string.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 7690d59c..f3236fc8 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -484,7 +484,7 @@ dtutils_string.libdoc.functions["build_substitution_list"] = { Usage = [[local ds = require "lib/dtutils.string" ds.build_substitution_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed - sequence - integer - the sequence number of the image + sequence - integer - the sequence number of the image being processed (exported) variable_string - string - the substitution variable string [username] - string - optional - user name. Will be determined if not supplied [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied @@ -496,7 +496,7 @@ dtutils_string.libdoc.functions["build_substitution_list"] = { Limitations = [[If the value for a variable can not be determined, or if it is not supported, then an empty string is used for the value.]], Example = [[]], - See_Also = [[https://docs.darktable.org/usermanual/4.2/en/special-topics/variables/]], + See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]], Reference = [[]], License = [[]], Copyright = [[]], @@ -613,7 +613,7 @@ local function get_colorlabels(image) return labels end --- find the $CATEGORYn requests and add them to the substitute list +-- find the $CATEGORYn and $CATEGORY[n,m] requests and add them to the substitute list local function build_category_substitution_list(image, variable_string) local old_log_level = log.log_level() @@ -974,7 +974,7 @@ dtutils_string.libdoc.functions["substitute_list"] = { Return_Value = [[result - string - the input string with values substituted for the variables]], Limitations = [[]], Example = [[]], - See_Also = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]], Reference = [[]], License = [[]], Copyright = [[]], @@ -1033,7 +1033,7 @@ dtutils_string.libdoc.functions["substitute"] = { Usage = [[local ds = require "lib/dtutils.string" ds.substitute(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed - sequence - integer - the sequence number of the image + sequence - integer - the number of the image being processed (exported) variable_string - string - the substitution variable string [username] - string - optional - user name. Will be determined if not supplied [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied @@ -1045,7 +1045,7 @@ dtutils_string.libdoc.functions["substitute"] = { Return_Value = [[result - string - the input string with values substituted for the variables]], Limitations = [[]], Example = [[]], - See_Also = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]], Reference = [[]], License = [[]], Copyright = [[]], From dcbe575e9c50665209c429842edbe309a8191192 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 20 May 2024 22:13:29 -0400 Subject: [PATCH 020/169] lib/dtutils/string - updated embedded documentation --- lib/dtutils/string.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index f3236fc8..342a5763 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -478,11 +478,11 @@ function dtutils_string.get_filetype(str) return parts["filetype"] end -dtutils_string.libdoc.functions["build_substitution_list"] = { - Name = [[build_substitution_list]], +dtutils_string.libdoc.functions["build_substitute_list"] = { + Name = [[build_substitute_list]], Synopsis = [[build a list of variable substitutions]], Usage = [[local ds = require "lib/dtutils.string" - ds.build_substitution_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) + ds.build_substitute_list(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed sequence - integer - the sequence number of the image being processed (exported) variable_string - string - the substitution variable string @@ -490,7 +490,7 @@ dtutils_string.libdoc.functions["build_substitution_list"] = { [pic_folder] - string - optional - pictures folder name. Will be determined if not supplied [home] - string - optional - home directory. Will be determined if not supplied [desktop] - string - optional - desktop directory. Will be determined if not supplied]], - Description = [[build_substitution_list populates variables with values from the arguments + Description = [[build_substitute_list populates variables with values from the arguments and determined from the system and darktable.]], Return_Value = [[]], Limitations = [[If the value for a variable can not be determined, or if it is not supported, @@ -1057,7 +1057,7 @@ function dtutils_string.substitute(image, sequence, variable_string, username, p dtutils_string.clear_substitute_list() - dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) + dtutils_string.build_substitute_list(image, sequence, variable_string, username, pic_folder, home, desktop) local str = dtutils_string.substitute_list(variable_string) From fecbb39347eb315d61f8532ecc3b741a44d58935 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 20 May 2024 23:43:47 -0400 Subject: [PATCH 021/169] lib/dtutils/string - more embedded doc updates --- lib/dtutils/string.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 342a5763..926c6673 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -1029,7 +1029,7 @@ end dtutils_string.libdoc.functions["substitute"] = { Name = [[substitute]], - Synopsis = [[Check if a string has been sanitized]], + Synopsis = [[Perform all the variable substitution steps with one function call]], Usage = [[local ds = require "lib/dtutils.string" ds.substitute(image, sequence, variable_string, [username], [pic_folder], [home], [desktop]) image - dt_lua_image_t - the image being processed @@ -1040,7 +1040,7 @@ dtutils_string.libdoc.functions["substitute"] = { [home] - string - optional - home directory. Will be determined if not supplied [desktop] - string - optional - desktop directory. Will be determined if not supplied]], Description = [[substitute initializes the substitution list by calling clear_substitute_list(), - then builds the substitutions by calling build_substitution_list() and finally does the + then builds the substitutions by calling build_substitute_list() and finally does the substitution by calling substitute_list(), then returns the result string.]], Return_Value = [[result - string - the input string with values substituted for the variables]], Limitations = [[]], From a3b295112f1ac8afe9f2454ec865cac81a8dcd07 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 27 May 2024 13:03:00 +0200 Subject: [PATCH 022/169] Update OpenInExplorer.lua In OpenInExplorer, use AppleScript instead of `open -R` for OSX. That way multiple files can be selected. --- contrib/OpenInExplorer.lua | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index dba04d02..64d478ac 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -118,9 +118,26 @@ open_dir.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesk local open_files = {} open_files.windows = "explorer.exe /select, %s" -open_files.macos = "open -Rn %s" +open_files.macos = "osascript -e 'tell application \"Finder\" to (reveal {%s}) activate'" open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]] +reveal_file_osx_cmd = "\"%s\" as POSIX file" + +--Call the osx Finder with each selected image selected. +--For images in multiple folders, Finder will open a separate window for each +local function call_list_of_files_osx(selected_images) + local cmds = {} + for _, image in pairs(selected_images) do + current_image = image.path..PS..image.filename + -- AppleScript needs double quoted strings, and the whole command is wrapped in single quotes. + table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "'", "'\\''"), "\"", "\\\"") )) + end + reveal_cmd = table.concat(cmds, ",") + run_cmd = string.format(open_files.macos, reveal_cmd) + dt.print_log("OSX run_cmd = "..run_cmd) + dsys.external_command(run_cmd) +end + --Call the file mangager for each selected image on Linux. --There is one call to busctl containing a list of all the image file names. local function call_list_of_files(selected_images) @@ -188,6 +205,8 @@ local function open_in_fmanager() else if act_os == "linux" then call_list_of_files(images) + elseif act_os == "macos" then + call_list_of_files_osx(images) else call_file_by_file(images) end From f1d8ad7fce66aa04a539ef6eab4e1daae531ea4a Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 27 May 2024 18:26:35 +0200 Subject: [PATCH 023/169] Fix quoting. --- contrib/OpenInExplorer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 64d478ac..1633a1a5 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -130,7 +130,7 @@ local function call_list_of_files_osx(selected_images) for _, image in pairs(selected_images) do current_image = image.path..PS..image.filename -- AppleScript needs double quoted strings, and the whole command is wrapped in single quotes. - table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "'", "'\\''"), "\"", "\\\"") )) + table.insert(cmds, string.format(reveal_file_osx_cmd, string.gsub(string.gsub(current_image, "\"", "\\\""), "'", "'\"'\"'"))) end reveal_cmd = table.concat(cmds, ",") run_cmd = string.format(open_files.macos, reveal_cmd) From 9088bc91441ae4c1a65bd7a4b8a76793fac95465 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 3 Jun 2024 14:48:44 -0400 Subject: [PATCH 024/169] tools/script_manager - make the constants to ensure they stay that way --- tools/script_manager.lua | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index daaa824e..ed93b963 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -75,27 +75,27 @@ du.check_min_api_version("9.3.0", "script_manager") -- - - - - - - - - - - - - - - - - - - - - - - - -- path separator -local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- command separator -local CS = dt.configuration.running_os == "windows" and "&" or ";" +local CS = dt.configuration.running_os == "windows" and "&" or ";" -local MODULE = "script_manager" +local MODULE = "script_manager" -local MIN_BUTTONS_PER_PAGE = 5 -local MAX_BUTTONS_PER_PAGE = 20 -local DEFAULT_BUTTONS_PER_PAGE = 10 +local MIN_BUTTONS_PER_PAGE = 5 +local MAX_BUTTONS_PER_PAGE = 20 +local DEFAULT_BUTTONS_PER_PAGE = 10 -local DEFAULT_LOG_LEVEL = log.error +local DEFAULT_LOG_LEVEL = log.error -local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" -local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" +local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" +local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" -local LUA_API_VER = "API-" .. dt.configuration.api_version_string +local LUA_API_VER = "API-" .. dt.configuration.api_version_string -- local POWER_ICON = dt.configuration.config_dir .. "/lua/data/data/icons/power.png" -local POWER_ICON = dt.configuration.config_dir .. "/lua/data/icons/path20.png" -local BLANK_ICON = dt.configuration.config_dir .. "/lua/data/icons/blank20.png" +local POWER_ICON = dt.configuration.config_dir .. "/lua/data/icons/path20.png" +local BLANK_ICON = dt.configuration.config_dir .. "/lua/data/icons/blank20.png" -- - - - - - - - - - - - - - - - - - - - - - - - -- P R E F E R E N C E S From 81b0f1fb484a9c293663191b99393eb9f322fc1c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 3 Jun 2024 18:08:30 -0400 Subject: [PATCH 025/169] contrib/dbmaint - remove database records for missing film rolls and images. --- contrib/dbmaint.lua | 314 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 314 insertions(+) create mode 100644 contrib/dbmaint.lua diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua new file mode 100644 index 00000000..7f2f21e0 --- /dev/null +++ b/contrib/dbmaint.lua @@ -0,0 +1,314 @@ +--[[ + + dbmaint.lua - perform database maintenance + + Copyright (C) 2024 Bill Ferguson . + + 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 . +]] +--[[ + dbmaint - perform database maintenance + + Perform database maintenance to clean up missing images and filmstrips. + + ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT + None + + USAGE + * start dbmaint from script_manager + * scan for missing film rolls or missing images + * look at the results and choose to delete or not + + BUGS, COMMENTS, SUGGESTIONS + Bill Ferguson + + CHANGES +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local log = require "lib/dtutils.log" + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- C O N S T A N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local MODULE = "dbmaint" +local DEFAULT_LOG_LEVEL = log.error +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A P I C H E C K +-- - - - - - - - - - - - - - - - - - - - - - - - + +du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- I 1 8 N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local gettext = dt.gettext.gettext + +dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/") + +local function _(msgid) + return gettext(MODULE, msgid) +end + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- S C R I P T M A N A G E R I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local script_data = {} + +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 +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 + +script_data.metadata = { + name = "dbmaint", + purpose = _("perform database maintenance"), + author = "Bill Ferguson ", + help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/" +} + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- L O G L E V E L +-- - - - - - - - - - - - - - - - - - - - - - - - + +log.log_level(DEFAULT_LOG_LEVEL) + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- N A M E S P A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +local dbmaint = {} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- G L O B A L V A R I A B L E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dbmaint.main_widget = nil + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A L I A S E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local namespace = dbmaint + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- F U N C T I O N S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local function scan_film_rolls() + local missing_films = {} + + for _, filmroll in ipairs(dt.films) do + if not df.check_if_file_exists(filmroll.path) then + table.insert(missing_films, filmroll) + end + end + + return missing_films +end + +local function scan_images(film) + local missing_images = {} + + if film then + for i = 1, #film do + local image = film[i] + log.log_msg(log.debug, "checking " .. image.filename) + if not df.check_if_file_exists(image.path .. PS .. image.filename) then + log.log_msg(log.info, image.filename .. " not found") + table.insert(missing_images, image) + end + end + end + + return missing_images +end + +local function remove_missing_film_rolls(list) + for _, filmroll in ipairs(list) do + filmroll:delete(true) + end +end + +-- force the lighttable to reload + +local function refresh_lighttable(film) + local rules = dt.gui.libs.collect.filter() + dt.gui.libs.collect.filter(rules) +end + +local function remove_missing_images(list) + local film = list[1].film + for _, image in ipairs(list) do + image:delete() + end + refresh_lighttable(film) +end + +local function install_module() + if not namespace.module_installed then + dt.register_lib( + MODULE, -- Module name + _("DB maintenance"), -- Visible name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers + namespace.main_widget, + nil,-- view_enter + nil -- view_leave + ) + namespace.module_installed = true + end +end + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- U S E R I N T E R F A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +dbmaint.list_widget = dt.new_widget("text_view"){ + editable = false, + reset_callback = function(this) + this.text = "" + end +} + +dbmaint.chooser = dt.new_widget("combobox"){ + label = "scan for", + selected = 1, + "film rolls", "images", + reset_callback = function(this) + this.selected = 1 + end +} + +dbmaint.scan_button = dt.new_widget("button"){ + label = "scan", + tooltip = "click to scan for missing film rolls/files", + clicked_callback = function(this) + local found = nil + local found_text = "" + if dbmaint.chooser.value == "film rolls" then + found = scan_film_rolls() + if #found > 0 then + for _, film in ipairs(found) do + local dir_name = du.split(film.path, PS) + found_text = found_text .. dir_name[#dir_name] .. "\n" + end + end + else + log.log_msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files") + found = scan_images(dt.collection[1].film) + if #found > 0 then + for _, image in ipairs(found) do + found_text = found_text .. image.filename .. "\n" + end + end + end + if #found > 0 then + dbmaint.list_widget.text = found_text + dbmaint.found = found + dbmaint.remove_button.sensitive = true + end + end, + reset_callback = function(this) + dbmaint.found = nil + end +} + +dbmaint.remove_button = dt.new_widget("button"){ + label = "remove", + tooltip = "remove missing film rolls/images", + sensitive = false, + clicked_callback = function(this) + if dbmaint.chooser.value == "film rolls" then + remove_missing_film_rolls(dbmaint.found) + else + remove_missing_images(dbmaint.found) + end + dbmaint.found = nil + dbmaint.list_widget.text = "" + this.sensitive = false + end, + reset_callback = function(this) + this.sensitive = false + end +} + +dbmaint.main_widget = dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("section_label"){label = "missing items"}, + dbmaint.list_widget, + dt.new_widget("label"){label = ""}, + dbmaint.chooser, + dt.new_widget("label"){label = ""}, + dbmaint.scan_button, + dbmaint.remove_button +} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- D A R K T A B L E I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - + +local function destroy() + dt.gui.libs[MODULE].visible = false + + if namespace.event_registered then + dt.destroy_event(MODULE, "view-changed") + end + + return +end + +local function restart() + dt.gui.libs[MODULE].visible = true + + return +end + +script_data.destroy = destroy +script_data.restart = restart +script_data.destroy_method = "hide" +script_data.show = restart + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- E V E N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not namespace.event_registered then + dt.register_event(MODULE, "view-changed", + function(event, old_view, new_view) + if new_view.name == "lighttable" and old_view.name == "darkroom" then + install_module() + end + end + ) + namespace.event_registered = true + end +end + +return script_data \ No newline at end of file From 2f330ea9cc6afe8795bd290697637d4c68f8662a Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 4 Jun 2024 23:16:19 -0400 Subject: [PATCH 026/169] lib/dtutils/system - added wrapper functions io_popen and os_execute to wrap io.popen and os.execute system calls respectively. These wrapper functions provide the necessary quoting on windows to get handle strings with spaces and special characters. --- lib/dtutils/system.lua | 51 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index f41f821c..8f794f45 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -129,4 +129,55 @@ function dtutils_system.launch_default_app(path) end +dtutils_system.libdoc.functions["os_execute"] = { + Name = [[os_execute]], + Synopsis = [[wrapper around the lua os.execute function]], + Usage = [[local dsys = require "lib/dtutils.file" + + result = dsys.os_execute(cmd) + cmd - string - a command to execute on the operating system]], + Description = [[os_execute wraps the lua os.execute system call to provide + correct sanitization of windows commands]], + Return_Value = [[see the lua os.execute documentation]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_system.os_execute(cmd) + if _scripts_install.dt.configuration.running_os == "windows" then + cmd = "\"" .. cmd .. "\"" + end + return os.execute(cmd) +end + +dtutils_system.libdoc.functions["io_popen"] = { + Name = [[io_popen]], + Synopsis = [[wrapper around the lua io.popen function]], + Usage = [[local dsys = require "lib/dtutils.file" + + result = dsys.io_popen(cmd) + cmd - string - a command to execute and attach to]], + Description = [[io_popen wraps the lua io.popen system call to provide + correct sanitization of windows commands]], + Return_Value = [[see the lua io.popen documentation]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_system.io_popen(cmd) + if _scripts_install.dt.configuration.running_os == "windows" then + cmd = "\"" .. cmd .. "\"" + end + return io.popen(cmd) +end + + return dtutils_system From fe48d7498c8d40bd222c62104e454aa1f92dd39b Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 4 Jun 2024 23:41:07 -0400 Subject: [PATCH 027/169] lib/dtutils/file - replaced os.execute and io.popen with wrapper functions to sanitize windows calls --- lib/dtutils/file.lua | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index cd898e71..b8717116 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -42,7 +42,7 @@ end local function _win_os_execute(cmd) local result = nil - local p = io.popen(cmd) + local p = dsys.io_popen(cmd) local output = p:read("*a") p:close() if string.match(output, "true") then @@ -94,7 +94,7 @@ dtutils_file.libdoc.functions["test_file"] = { function dtutils_file.test_file(path, test) local cmd = "test -" - local engine = os.execute + local engine = dsys.os_execute local cmdstring = "" if dt.configuration.running_os == "windows" then @@ -167,7 +167,7 @@ local function _search_for_bin_windows(bin) for _,arg in ipairs(args) do local cmd = "where " .. arg .. " " .. ds.sanitize(bin) - local p = io.popen(cmd) + local p = dsys.io_popen(cmd) local output = p:read("*a") p:close() local lines = du.split(output, "\n") @@ -191,7 +191,7 @@ end local function _search_for_bin_nix(bin) local result = false - local p = io.popen("command -v " .. bin) + local p = dsys.io_popen("command -v " .. bin) local output = p:read("*a") p:close() if string.len(output) > 0 then @@ -220,7 +220,7 @@ local function _search_for_bin_macos(bin) search_start = "/Applications/" .. bin .. ".app" end - local p = io.popen("find " .. search_start .. " -type f -name " .. bin .. " -print") + local p = dsys.io_popen("find " .. search_start .. " -type f -name " .. bin .. " -print") local output = p:read("*a") p:close() local lines = du.split(output, "\n") @@ -445,7 +445,7 @@ function dtutils_file.check_if_file_exists(filepath) local result = false if (dt.configuration.running_os == 'windows') then filepath = string.gsub(filepath, '[\\/]+', '\\') - local p = io.popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')") + local p = dsys.io_popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')") local ans = p:read("*all") p:close() if string.match(ans, "yes") then @@ -456,7 +456,7 @@ function dtutils_file.check_if_file_exists(filepath) -- result = false -- end elseif (dt.configuration.running_os == "linux") then - result = os.execute('test -e ' .. dtutils_file.sanitize_filename(filepath)) + result = dsys.os_execute('test -e ' .. dtutils_file.sanitize_filename(filepath)) if not result then result = false end @@ -522,9 +522,9 @@ function dtutils_file.file_copy(fromFile, toFile) local result = nil -- if cp exists, use it if dt.configuration.running_os == "windows" then - result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') + result = dsys.os_execute('copy "' .. fromFile .. '" "' .. toFile .. '"') elseif dtutils_file.check_if_bin_exists("cp") then - result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") + result = dsys.os_execute("cp '" .. fromFile .. "' '" .. toFile .. "'") end -- if cp was not present, or if cp failed, then a pure lua solution @@ -575,7 +575,7 @@ function dtutils_file.file_move(fromFile, toFile) if not success then -- an error occurred, so let's try using the operating system function if dtutils_file.check_if_bin_exists("mv") then - success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'") + success = dsys.os_execute("mv '" .. fromFile .. "' '" .. toFile .. "'") end -- if the mv didn't exist or succeed, then... if not success then From 038418b89fac9e99a309a536eee6aa4f13c19428 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 4 Jun 2024 23:43:21 -0400 Subject: [PATCH 028/169] contrib/autostyle replaced io.popen functions with wrapper contrib/color_profile_manager to properly quote windows commands so contrib/fujifilm_dynamic_range that they are sanitized for special contrib/fujifilm_ratings characters and spaces contrib/geoJSON_export contrib/geoToolbox contrib/image_stack contrib/image_time contrib/kml_export official/enfuse tools/executable_manager tools/get_lib_manpages tools/get_libdoc --- contrib/autostyle.lua | 3 ++- contrib/color_profile_manager.lua | 3 ++- contrib/fujifilm_dynamic_range.lua | 3 ++- contrib/fujifilm_ratings.lua | 5 +++-- contrib/geoJSON_export.lua | 3 ++- contrib/geoToolbox.lua | 3 ++- contrib/image_stack.lua | 2 +- contrib/image_time.lua | 7 ++++--- contrib/kml_export.lua | 2 +- official/enfuse.lua | 2 +- tools/executable_manager.lua | 5 +++-- tools/get_lib_manpages.lua | 5 +++-- tools/get_libdoc.lua | 3 ++- 13 files changed, 28 insertions(+), 18 deletions(-) diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index a12cd0ac..319b8f3a 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -39,6 +39,7 @@ GPLv2 local darktable = require "darktable" local du = require "lib/dtutils" local filelib = require "lib/dtutils.file" +local syslib = require "lib/dtutils.system" du.check_min_api_version("7.0.0", "autostyle") @@ -68,7 +69,7 @@ script_data.show = nil -- only required for libs since the destroy_method only h -- run command and retrieve stdout local function get_stdout(cmd) -- Open the command, for reading - local fd = assert(io.popen(cmd, 'r')) + local fd = assert(syslib.io_popen(cmd, 'r')) darktable.control.read(fd) -- slurp the whole file local data = assert(fd:read('*a')) diff --git a/contrib/color_profile_manager.lua b/contrib/color_profile_manager.lua index ed083178..d87d9a6a 100644 --- a/contrib/color_profile_manager.lua +++ b/contrib/color_profile_manager.lua @@ -45,6 +45,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" du.check_min_api_version("7.0.0", "color_profile_manager") @@ -106,7 +107,7 @@ end local function list_profiles(dir) local files = {} - local p = io.popen(DIR_CMD .. " " .. dir) + local p = dtsys.io_popen(DIR_CMD .. " " .. dir) if p then for line in p:lines() do table.insert(files, line) diff --git a/contrib/fujifilm_dynamic_range.lua b/contrib/fujifilm_dynamic_range.lua index d373d130..1034411d 100644 --- a/contrib/fujifilm_dynamic_range.lua +++ b/contrib/fujifilm_dynamic_range.lua @@ -60,6 +60,7 @@ cameras may behave in other ways. local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "fujifilm_dynamic_range") @@ -105,7 +106,7 @@ local function detect_dynamic_range(event, image) -- without -n flag, exiftool will round to the nearest tenth command = command .. " -RawExposureBias -n -t " .. RAF_filename dt.print_log(command) - output = io.popen(command) + output = dtsys.io_popen(command) local raf_result = output:read("*all") output:close() if #raf_result == 0 then diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index f9bb1c86..ef935139 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -26,6 +26,7 @@ Dependencies: local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "fujifilm_ratings") @@ -61,7 +62,7 @@ local function detect_rating(event, image) local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG") local command = "exiftool -Rating " .. JPEG_filename dt.print_error(command) - local output = io.popen(command) + local output = dtsys.io_popen(command) local jpeg_result = output:read("*all") output:close() if string.len(jpeg_result) > 0 then @@ -72,7 +73,7 @@ local function detect_rating(event, image) end command = "exiftool -Rating " .. RAF_filename dt.print_error(command) - output = io.popen(command) + output = dtsys.io_popen(command) local raf_result = output:read("*all") output:close() if string.len(raf_result) > 0 then diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 695976a6..406c37c2 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -35,6 +35,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "geoJSON_export") @@ -330,7 +331,7 @@ dt.preferences.register("geoJSON_export", _("opens the geoJSON file after the export with the standard program for geoJSON files"), false ) -local handle = io.popen("xdg-user-dir DESKTOP") +local handle = dtsys.io_popen("xdg-user-dir DESKTOP") local result = handle:read() handle:close() if (result == nil) then diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 83caa130..d654eeba 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -28,6 +28,7 @@ require "geoToolbox" local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "geoToolbox") @@ -411,7 +412,7 @@ local function reverse_geocode() -- jq could be replaced with a Lua JSON parser startCommand = string.format("curl --silent \"https://api.mapbox.com/geocoding/v5/mapbox.places/%s,%s.json?types=%s&access_token=%s\" | jq '.features | .[0] | '.text''", lon1, lat1, types, tokan) - local handle = io.popen(startCommand) + local handle = dtsys.io_popen(startCommand) local result = trim12(handle:read("*a")) handle:close() diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index c63ff166..ba0909b1 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -348,7 +348,7 @@ local function list_files(search_string) search_string = string.gsub(search_string, "/", "\\\\") end - local f = io.popen(ls .. search_string) + local f = dtsys.io_popen(ls .. search_string) if f then local found_file = f:read() while found_file do diff --git a/contrib/image_time.lua b/contrib/image_time.lua index f20e5000..fa00b576 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -107,6 +107,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" local ds = require "lib/dtutils.string" +local dtsys = require "lib/dtutils.system" local gettext = dt.gettext.gettext local img_time = {} @@ -225,7 +226,7 @@ local function get_image_taken_time(image) local exiv2 = df.check_if_bin_exists("exiv2") if exiv2 then - p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename) + p = dtsys.io_popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, "Exif.Image.DateTime") then @@ -243,7 +244,7 @@ end local function _get_windows_image_file_creation_time(image) local datetime = nil - local p = io.popen("dir " .. image.path .. PS .. image.filename) + local p = dtsys.io_popen("dir " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, ds.sanitize_lua(image.filename)) then @@ -264,7 +265,7 @@ end local function _get_nix_image_file_creation_time(image) local datetime = nil - local p = io.popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename) + local p = dtsys.io_popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, ds.sanitize_lua(image.filename)) then diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index ec1aa04a..ec7218ca 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -343,7 +343,7 @@ if dt.configuration.running_os == "windows" then elseif dt.configuration.running_os == "macos" then defaultDir = os.getenv("HOME") else - local handle = io.popen("xdg-user-dir DESKTOP") + local handle = dsys.io_popen("xdg-user-dir DESKTOP") defaultDir = handle:read() handle:close() end diff --git a/official/enfuse.lua b/official/enfuse.lua index c8df51b1..c193c35e 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -116,7 +116,7 @@ if enfuse_installed then local version = nil - local p = io.popen(enfuse_installed .. " --version") + local p = dtsys.io_popen(enfuse_installed .. " --version") local f = p:read("all") p:close() version = string.match(f, "enfuse (%d.%d)") diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 58a055ab..0ac35ad4 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -31,6 +31,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" du.check_min_api_version("7.0.0", "executable_manager") @@ -75,7 +76,7 @@ local function grep(file, pattern) if dt.configuration.running_os == "windows" then -- use find to get the matches local command = "\\windows\\system32\\find.exe " .. "\"" .. pattern .. "\"" .. " " .. file - local f = io.popen(command) + local f = dtsys.io_popen(command) local output = f:read("all") f:close() -- strip out the first line @@ -84,7 +85,7 @@ local function grep(file, pattern) else -- use grep and just return the answers local command = "grep " .. pattern .. " " .. file - local f = io.popen(command) + local f = dtsys.io_popen(command) local output = f:read("all") f:close() result = du.split(output, "\n") diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index a3c39157..33d2d031 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -7,6 +7,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" local log = require "lib/dtutils.log" local libname = nil @@ -33,7 +34,7 @@ local function output_man(d) libname = name end local fname = "/tmp/" .. name .. ".3" - local mf = io.open(fname, "w") + local mf = dtsys.io_open(fname, "w") if mf then mf:write(".TH " .. string.upper(name) .. " 3 \"\" \"\" \"Darktable " .. libname .. " functions\"\n") for _,section in ipairs(keys) do @@ -59,7 +60,7 @@ end -- find the libraries -local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") +local output = dtsys.io_popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") -- loop through the libraries diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 5cb08122..2328014a 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -6,6 +6,7 @@ local dt = require "darktable" local du = require "lib/dtutils" +local dtsys = require "lib/dtutils.system" du.check_min_api_version("3.0.0", "get_libdoc") @@ -36,7 +37,7 @@ end -- find the libraries -local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") +local output = dtsys.io_popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") -- loop through the libraries From 96471ae7e77e9146989f40ce3f7a670b04b3225b Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 00:00:43 -0400 Subject: [PATCH 029/169] lib/dtutils - fixed documentation error --- lib/dtutils.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index c17ce334..c8331cbd 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -82,8 +82,7 @@ dtutils.libdoc.functions["check_max_api_version"] = { run against the current api version. This function is used when a part of the Lua API that the script relies on is removed. If the maximum api version is not met, then an error message is printed saying the script_name failed to load, then an error is thrown causing the - program to stop executing. - + program to stop executing.]], Return_Value = [[result - true if the maximum api version is available, false if not.]], Limitations = [[When using the default handler on a script being executed from the luarc file, the error thrown will stop the luarc file from executing any remaining statements. This limitation does not apply to script_manger.]], From 67538a4de2efb78b9c96134e963166f55ed23848 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 00:01:17 -0400 Subject: [PATCH 030/169] lib/dtutils/system code cleanup --- lib/dtutils/system.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 8f794f45..87a1f230 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -148,7 +148,7 @@ dtutils_system.libdoc.functions["os_execute"] = { } function dtutils_system.os_execute(cmd) - if _scripts_install.dt.configuration.running_os == "windows" then + if dt.configuration.running_os == "windows" then cmd = "\"" .. cmd .. "\"" end return os.execute(cmd) @@ -173,7 +173,7 @@ dtutils_system.libdoc.functions["io_popen"] = { } function dtutils_system.io_popen(cmd) - if _scripts_install.dt.configuration.running_os == "windows" then + if dt.configuration.running_os == "windows" then cmd = "\"" .. cmd .. "\"" end return io.popen(cmd) From 32e966f8d9b8135fcabc5bab26e1b1a80708b2de Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 00:01:47 -0400 Subject: [PATCH 031/169] tools/git_lib_manpages code cleanup --- tools/get_lib_manpages.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index 33d2d031..fbc38b86 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -34,7 +34,7 @@ local function output_man(d) libname = name end local fname = "/tmp/" .. name .. ".3" - local mf = dtsys.io_open(fname, "w") + local mf = dtsys.io_popen(fname, "w") if mf then mf:write(".TH " .. string.upper(name) .. " 3 \"\" \"\" \"Darktable " .. libname .. " functions\"\n") for _,section in ipairs(keys) do @@ -46,7 +46,7 @@ local function output_man(d) mf:close() if df.check_if_bin_exists("groff") then if df.check_if_bin_exists("ps2pdf") then - os.execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf") + dtsys.os_execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf") else log.msg(log.error, "Missing ps2pdf. Can't generate pdf man pages.") end From 2abb83b485922892ec1f7318481dd11c38d83fda Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 00:09:18 -0400 Subject: [PATCH 032/169] tools/get_lib_manpages - more code cleanup --- tools/get_lib_manpages.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index fbc38b86..91d5dbb3 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -34,7 +34,7 @@ local function output_man(d) libname = name end local fname = "/tmp/" .. name .. ".3" - local mf = dtsys.io_popen(fname, "w") + local mf = io.open(fname, "w") if mf then mf:write(".TH " .. string.upper(name) .. " 3 \"\" \"\" \"Darktable " .. libname .. " functions\"\n") for _,section in ipairs(keys) do From 9f2dbef4f09f00c7043eba5fc16d26180805ccfe Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 12:15:01 -0400 Subject: [PATCH 033/169] lib/dtutils/system added local helper function quote_windows_command() to wrap a windows command in quotes. Added a sanitize step to windows_external_command() plus quote_windows_command() to deal with all the windows username possibilities --- lib/dtutils/system.lua | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 87a1f230..e8ce9af0 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -1,6 +1,7 @@ local dtutils_system = {} local dt = require "darktable" +local ds = require "lib/dtutils.string" dtutils_system.libdoc = { Name = [[dtutils.system]], @@ -50,9 +51,9 @@ function dtutils_system.external_command(command) local result = nil if dt.configuration.running_os == "windows" then - result = dtutils_system.windows_command(command) + result = dtutils_system.windows_command(ds.sanitize(command)) else - result = dt.control.execute(command) + result = dt.control.execute(ds.sanitize(command)) end return result @@ -77,15 +78,20 @@ dtutils_system.libdoc.functions["windows_command"] = { Copyright = [[]], } +local function quote_windows_command(command) + return "\"" .. command .. "\"" +end + function dtutils_system.windows_command(command) local result = 1 - local fname = dt.configuration.tmp_dir .. "/run_command.bat" + local fname = ds.sanitize(dt.configuration.tmp_dir .. "/run_command.bat") local file = io.open(fname, "w") if file then dt.print_log("opened file") command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell + command = quote_windows-command(command) file:write(command) file:close() @@ -149,7 +155,7 @@ dtutils_system.libdoc.functions["os_execute"] = { function dtutils_system.os_execute(cmd) if dt.configuration.running_os == "windows" then - cmd = "\"" .. cmd .. "\"" + cmd = quote_windows_command(cmd) end return os.execute(cmd) end @@ -174,7 +180,7 @@ dtutils_system.libdoc.functions["io_popen"] = { function dtutils_system.io_popen(cmd) if dt.configuration.running_os == "windows" then - cmd = "\"" .. cmd .. "\"" + cmd = quote_windows_command(cmd) end return io.popen(cmd) end From 3e032616a038cb4317c6ed897aadb5976dfac5cc Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 14:49:06 -0400 Subject: [PATCH 034/169] tools/executable_manager - fix position not visible on windows --- tools/executable_manager.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 58a055ab..cc98cd9b 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -107,13 +107,19 @@ local function update_combobox_choices(combobox, choice_table, selected) end local function install_module() + local panel = "DT_UI_CONTAINER_PANEL_LEFT_BOTTOM" + local panel_pos = 600 + if dt.configuration.running_os == "windows" then + panel = "DT_UI_CONTAINER_PANEL_LEFT_CENTER" + panel_pos = 100 + end if not exec_man.module_installed then dt.register_lib( "executable_manager", -- Module name "executable manager", -- Visible name true, -- expandable false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 600}}, -- containers + {[dt.gui.views.lighttable] = {panel, panel_pos}}, -- containers dt.new_widget("box") -- widget { orientation = "vertical", From 539f25517bbf0a4fcc58a3408d13d9eafe3ef1b4 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 5 Jun 2024 22:16:40 -0400 Subject: [PATCH 035/169] contrib/fujifilm_ratings - added check to ensure the image is a fujifilm file. --- contrib/fujifilm_ratings.lua | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index ef935139..b2f11fe3 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -54,6 +54,9 @@ script_data.restart = nil -- how to restart the (lib) script after it's been hid script_data.show = nil -- only required for libs since the destroy_method only hides them local function detect_rating(event, image) + if not string.match(image.filename, "%.RAF$") and not string.match(image.filename, "%.raf$") then + return + end if not df.check_if_bin_exists("exiftool") then dt.print_error(_("exiftool not found")) return @@ -61,25 +64,25 @@ local function detect_rating(event, image) local RAF_filename = df.sanitize_filename(tostring(image)) local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG") local command = "exiftool -Rating " .. JPEG_filename - dt.print_error(command) + dt.print_log(command) local output = dtsys.io_popen(command) local jpeg_result = output:read("*all") output:close() if string.len(jpeg_result) > 0 then jpeg_result = string.gsub(jpeg_result, "^Rating.*(%d)", "%1") image.rating = tonumber(jpeg_result) - dt.print_error(string.format(_("using JPEG rating: %d"), jpeg_result)) + dt.print_log("using JPEG rating: " .. jpeg_result) return end command = "exiftool -Rating " .. RAF_filename - dt.print_error(command) + dt.print_log(command) output = dtsys.io_popen(command) local raf_result = output:read("*all") output:close() if string.len(raf_result) > 0 then raf_result = string.gsub(raf_result, "^Rating.*(%d)", "%1") image.rating = tonumber(raf_result) - dt.print_error(string.format(_("using RAF rating: %d"), raf_result)) + dt.print_log("using RAF rating: " .. raf_result) end end From 4ff111134141cbdd0adfdacee4643695847465d0 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 7 Jun 2024 15:23:06 -0400 Subject: [PATCH 036/169] tools/script_manaager added sanitized io.popen and os.execute functions for windows character and space issues. Cleaned up translatable strings to make future translation easier. --- tools/script_manager.lua | 69 +++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 40 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index ed93b963..955f4839 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1,6 +1,6 @@ --[[ This file is part of darktable, - copyright (c) 2018, 2020, 2023 Bill Ferguson + copyright (c) 2018, 2020, 2023, 2024 Bill Ferguson darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,7 +60,7 @@ local gettext = dt.gettext -- Tell gettext where to find the .mo file translating messages for a particular domain -dt.gettext.bindtextdomain("script_manager",dt.configuration.config_dir.."/lua/locale/") +dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .. "/lua/locale/") local function _(msgid) return gettext.dgettext("script_manager", msgid) @@ -241,7 +241,7 @@ end local function get_repo_status(repo) local old_log_level = set_log_level(sm.log_level) - local p = io.popen("cd " .. repo .. CS .. "git status") + local p = dtsys.io_popen("cd " .. repo .. CS .. "git status") if p then local data = p:read("*a") @@ -259,7 +259,7 @@ local function get_current_repo_branch(repo) local branch = nil - local p = io.popen("cd " .. repo .. CS .. "git branch --all") + local p = dtsys.io_popen("cd " .. repo .. CS .. "git branch --all") if p then local data = p:read("*a") @@ -289,7 +289,7 @@ local function get_repo_branches(repo) local old_log_level = set_log_level(sm.log_level) local branches = {} - local p = io.popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") + local p = dtsys.io_popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") if p then local data = p:read("*a") @@ -329,7 +329,7 @@ local function checkout_repo_branch(repo, branch) log.msg(log.info, "checkout out branch " .. branch .. " from repository " .. repo) - os.execute("cd " .. repo .. CS .. "git checkout " .. branch) + dtsys.os_execute("cd " .. repo .. CS .. "git checkout " .. branch) restore_log_level(old_log_level) end @@ -417,7 +417,7 @@ local function get_script_metadata(script) -- grab the script_data.metadata table description = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") else - log.msg(log.error, _("Cant read from " .. script)) + log.msg(log.error, "cant read from " .. script) end if description then @@ -459,7 +459,7 @@ local function get_script_doc(script) -- assume that the second block comment is the documentation description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]") else - log.msg(log.error, _("Cant read from " .. script)) + log.msg(log.error, "can't read from " .. script) end if description then restore_log_level(old_log_level) @@ -489,7 +489,7 @@ local function activate(script) if status then pref_write(script.script_name, "bool", true) - log.msg(log.screen, _("Loaded ") .. script.script_name) + log.msg(log.screen, _(string.format("loaded %s", script.script_name))) script.running = true if err ~= true then @@ -503,9 +503,9 @@ local function activate(script) end else - log.msg(log.screen, script.script_name .. _(" failed to load")) - log.msg(log.error, "Error loading " .. script.script_name) - log.msg(log.error, "Error message: " .. err) + log.msg(log.screen, _(string.format("%s failed to load", script.script_name))) + log.msg(log.error, "error loading " .. script.script_name) + log.msg(log.error, "error message: " .. err) end else -- script is a lib and loaded but hidden and the user wants to reload @@ -549,13 +549,13 @@ local function deactivate(script) end log.msg(log.info, "turned off " .. script.script_name) - log.msg(log.screen, script.name .. _(" stopped")) + log.msg(log.screen, _(string.format("%s stopped", script.name))) else script.running = false log.msg(log.info, "setting " .. script.script_name .. " to not start") - log.msg(log.screen, script.name .. _(" will not start when darktable is restarted")) + log.msg(log.screen, _(string.format("%s will not start when darktable is restarted", script.name))) end restore_log_level(old_log_level) @@ -665,7 +665,7 @@ local function scan_scripts(script_dir) log.msg(log.debug, "find command is " .. find_cmd) -- scan the scripts - local output = io.popen(find_cmd) + local output = dtsys.io_popen(find_cmd) for line in output:lines() do log.msg(log.debug, "line is " .. line) local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off @@ -695,7 +695,7 @@ local function update_scripts() local git = sm.executables.git if not git then - dt.print(_("ERROR: git not found. Install or specify the location of the git executable.")) + log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) return end @@ -709,7 +709,7 @@ local function update_scripts() end if result == 0 then - dt.print(_("lua scripts successfully updated")) + log.msg(log.screen, _("lua scripts successfully updated")) end restore_log_level(old_log_level) @@ -749,9 +749,9 @@ local function scan_repositories() find_cmd = "dir /b/s /a:d " .. LUA_DIR .. PS .. "*.git | sort" end - log.msg(log.debug, _("find command is ") .. find_cmd) + log.msg(log.debug, "find command is " .. find_cmd) - local output = io.popen(find_cmd) + local output = dtsys.io_popen(find_cmd) for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off @@ -799,7 +799,7 @@ local function install_scripts() local folder = sm.widgets.new_folder.text if string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then - log.msg(log.screen, _("folder ") .. folder .. _(" is already in use. Please specify a different folder name.")) + log.msg(log.screen, _(string.format("folder %s is already in use. Please specify a different folder name.", folder))) log.msg(log.error, "folder " .. folder .. " already exists, returning...") restore_log_level(old_log_level) return @@ -810,7 +810,7 @@ local function install_scripts() local git = sm.executables.git if not git then - dt.print(_("ERROR: git not found. Install or specify the location of the git executable.")) + log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) restore_log_level(old_log_level) return end @@ -849,12 +849,12 @@ local function install_scripts() sm.widgets.new_folder.text = "" sm.widgets.main_menu.selected = 3 else - dt.print(_("No scripts found to install")) + log.msg(log.screen, _("No scripts found to install")) log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") end else - dt.print(_("failed to download scripts")) + log.msg(log.screen, _("failed to download scripts")) end restore_log_level(old_log_level) @@ -1006,7 +1006,7 @@ local function paginate(direction) last = first + sm.page_status.num_buttons - 1 end - sm.widgets.page_status.label = _("Page ") .. cur_page .. _(" of ") .. max_pages + sm.widgets.page_status.label = _(string.format("page %d of %d", cur_page, max_pages)) populate_buttons(folder, first, last) @@ -1177,17 +1177,6 @@ local function install_module() log.msg(log.debug, "set run to true, loading preferences") load_preferences() scan_repositories() - --[[dt.print_log("\n\nsetting sm visible false\n\n") - dt.gui.libs["script_manager"].visible = false - dt.control.sleep(5000) - dt.print_log("setting sm visible true") - dt.gui.libs["script_manager"].visible = true - --[[dt.control.sleep(5000) - dt.print_log("setting sm expanded false") - dt.gui.libs["script_manager"].expanded = false - dt.control.sleep(5000) - dt.print_log("setting sm expanded true") - dt.gui.libs["script_manager"].expanded = true]] restore_log_level(old_log_level) end @@ -1217,7 +1206,7 @@ if check_for_updates then -- probably upgraded from an earlier api version so get back to master -- to use the latest version of script_manager to get the proper API checkout_repo_branch(repo, "master") - log.msg(log.screen, "lua API version reset, please restart darktable") + log.msg(log.screen, _("lua API version reset, please restart darktable")) elseif LUA_API_VER == current_branch then -- do nothing, we are fine @@ -1353,7 +1342,7 @@ sm.widgets.disable_scripts = dt.new_widget("button"){ local LUARC = dt.configuration.config_dir .. PS .. "luarc" df.file_move(LUARC, LUARC .. ".disabled") log.msg(log.info, "lua scripts disabled") - dt.print(_("lua scripts will not run the next time darktable is started")) + log.msg(log.screen, _("lua scripts will not run the next time darktable is started")) end } @@ -1414,7 +1403,7 @@ end local page_back = "<" local page_forward = ">" -sm.widgets.page_status = dt.new_widget("label"){label = _("Page:")} +sm.widgets.page_status = dt.new_widget("label"){label = _("page:")} sm.widgets.page_back = dt.new_widget("button"){ label = page_back, @@ -1445,7 +1434,7 @@ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, dt.new_widget("section_label"){label = _(" ")}, dt.new_widget("label"){label = " "}, - dt.new_widget("label"){label = _("Scripts")}, + dt.new_widget("label"){label = _("scripts")}, sm.widgets.folder_selector, sm.widgets.page_control, table.unpack(sm.widgets.boxes), @@ -1529,7 +1518,7 @@ else function(event, old_view, new_view) if new_view.name == "lighttable" and old_view.name == "darkroom" then install_module() - end + end end ) sm.event_registered = true From 5571e2f2dc3e9a709aef3cee20e58e787ba67d78 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 8 Jun 2024 21:51:37 -0400 Subject: [PATCH 037/169] ChangeLog - Updated with the latest repository changes README - Updated the script additions --- ChangeLog.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 9 +++++++ 2 files changed, 82 insertions(+) diff --git a/ChangeLog.md b/ChangeLog.md index d935ab16..33b27bd2 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,5 +1,78 @@ ## Changes from most recent to oldest +**06 Jun 2024 - wpferguson** +* fix fujifilm_ratings running on all images +* added string library functions to sanitize windows io.popen and os.execute functions +* added database maintenance script + +**05 Jun 2024 - wpferguson** +* added fix for executable_manager not being visible on windows + +**30 May 2024 - kkotowicz** +* open in explorer now uses applescript on macos to open multiple files + +**20 May 2024 - wpferguson** +* added string variable substitution to the string library + +**16 May 2024 - wpferguson** +* fix crash in script_manager + +**15 May 2024 - wpferguson** +* added metadata to scripts (name, author, purpose, help url) + +**06 May 2024 - christian.sueltrop** +* added passport_guide_germany script + +**08 Apr 2024 - wpferguson** +* made script_manager aware of library modules in other directories besides lib + +**29 Mar 2024 - wpferguson** +* updated examples/gui_action to use NaN instead of nan + +**29 Mar 2024 - dterrahe** +* add lua action script example + +**28 Jan 2024 - wpferguson** +* fix script_manager crash when script existed in top level lua directory + +**24 Jan 2024 - wpferguson** +* don't set lib visibility unless we are in lighttable mode + +**15 Jan 2024 - MStraeten** +* update x-touch.lua with support for primaries slider + +**14 Jan 2024 - wpferguson** +* added cycle_group_leader script +* added hif_group_leader script +* added jpg_group_leader script + +**28 Oct 2023 - ddittmar** +* Added select non existing image script + +**17 Oct 2023 - wpferguson** +* script_manager wrap username in quotes to handle spaces in username + +**20 Sep 2023 - wpferguson** +* script_manager explicitly set stopped scripts to false + +**18 Aug 2023 - wpferguson** +* swap the position of executable_manager and script_manager due to windows gtk bug + +**17 Jul 2023 - wpferguson** +* update check for update instructions +* update flatpak readme + +**16 Jul 2023 - wpferguson** +* added script_data.show function to contrib/gpx_export + +**15 Jul 2023 - wpferguson** +* script_manager updates +* added check_max_api_version for the case where the API no longer supports the required + functions + +**14 Jul 2023 - spaceChRis** +* add tooltip to filter manager + **25 Mar 2023 - wpferguson** * Added script_manager darktable shortcut integration * Moved filename/path/extension string functions to string library diff --git a/README.md b/README.md index f371e8d5..80ead1b9 100644 --- a/README.md +++ b/README.md @@ -39,8 +39,11 @@ Name|Standalone|OS |Purpose change_group_leader|Yes|LMW|Change which image leads group [clear_GPS](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images [CollectHelper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection +color_profile_manager|Yes|LMW|Manage darktable input and output color profiles [copy_attach_detach_tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images [cr2hdr](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO +cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn +dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images [enfuseAdvanced](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images [exportLUT](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it [ext_editor](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors @@ -51,14 +54,18 @@ change_group_leader|Yes|LMW|Change which image leads group [geoToolbox](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions [gimp](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result [gpx_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data +harmonic_armature|Yes|LMW|provide a harmonic armature guide +hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image [HDRMerge](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result [hugin](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result [image_stack](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects [image_time](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time +jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image [kml_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth [LabelsToTags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings [OpenInExplorer](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager [passport_guide](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool +passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool [pdf_slideshow](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow [photils](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images [quicktag](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags @@ -81,6 +88,7 @@ Name|Standalone|OS |Purpose [api_version](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version [darkroom_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom [gettextExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation +gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls [hello_world](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts [lighttable_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering [moduleExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module @@ -89,6 +97,7 @@ Name|Standalone|OS |Purpose [preferenceExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script [printExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script [running_os](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system +x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI ### Tools From 28622e620e676e98b3f01f45b6e08e709080c982 Mon Sep 17 00:00:00 2001 From: wpferguson Date: Sun, 9 Jun 2024 16:42:05 -0400 Subject: [PATCH 038/169] Revert "script_manager new UI" --- contrib/micharambou | 1 - data/icons/blank20.png | Bin 5678 -> 0 bytes data/icons/path20.png | Bin 6265 -> 0 bytes data/icons/power.png | Bin 426 -> 0 bytes data/icons/poweroff.png | Bin 599 -> 0 bytes development | 1 - tools/script_manager.lua | 652 ++++++++++++--------------------------- 7 files changed, 194 insertions(+), 460 deletions(-) delete mode 120000 contrib/micharambou delete mode 100644 data/icons/blank20.png delete mode 100644 data/icons/path20.png delete mode 100644 data/icons/power.png delete mode 100644 data/icons/poweroff.png delete mode 120000 development diff --git a/contrib/micharambou b/contrib/micharambou deleted file mode 120000 index bbe4f2d2..00000000 --- a/contrib/micharambou +++ /dev/null @@ -1 +0,0 @@ -/home/bill/src/dtluadev/gitannex/micharambou/ \ No newline at end of file diff --git a/data/icons/blank20.png b/data/icons/blank20.png deleted file mode 100644 index 81ed51574807dde06248d05f94fe17aad24b193a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5678 zcmeHKd010d7Eff8SY$^*g%A|$60$=Q2*bWqK$g^iqLAdhK!7YHfh4SoUw6r(JBzi;}VN#4u5=brOB=lt%u_uZT6 z@8_w5Fhjs#Fdc6%w?OFM2)c~tYeILA?(3B>nC627daxvrDMboJ0uDC@L`vjB5DCh- z92iV?_i6B^eGjd_c^6?EGSAB^ZD`zKrG5JgamHraEfOi$Z)Mm4(f(Mti3`Jvv8*R7 zb!=SAkzSj=c0=t6k0ep&UNiIWnjD|r3%WL(@~(X{IHBJn|DIa9jYr3?T}LLTJyR15 zg(RKzFZPz=$F|L+l=iI&k{d`cZKw4InSE)0pm#ntlU4Se=DAm0ZtimlVQ7`o?McUi@UT6Mc^jjkx>L~$$P z?(Wf~U#p#GYQ9r*;KlV;kG-0D>l6lzyFjR11V8dXQF5S9;vI3v%#(c5_>_ZTd0^Vv zbcSP6MsD-jdW($0`@E#{8WSiH+f~Q&`m-WUw_lobR$o!%j=p(t9vUY;sS1LiNM}z(onbsZ!Gm;Az`--*8O6X(;k>+xd0aBzf5ZI;M&Z~+H0ID;irl=%}*)aHYhjvxmANR!jZ`5tVTzG9e z;Ao|5N{uND@cbuHj!N-4WZQA7rmA+}yET}QpJVoZ&-hJP*U214Z>oQKZj>*mEPLDQ z7-r#w{=M?1^VOAimqd+_5_fu;`?cZJ!i_}p?myB~9R4>hd&Q8??qasdnnC(}XYbf+ z98ufW@GY4@g;xAF8@FW*>jzENwp_}YStYu7_$^Y@fB!Y2?lCuVY1`?b5s#c*w|;p3 zz1b^Md0EsYT`cNgVdnm*aSJcAM4i+>Zk~AJ8_l{vlTIqs-UU|)xGBB$9WNS|++V#V zrl`R)GJzUrn_@SwE@^(%&-syI3uH|ej%x;kJy~*C`qL*V+?#y=NDZ-Jf2eZay<~T`xwisXyIyKG(zi zxy8=&%hS8Eq1z8T$=ixsJ)$4#%qq4?CcI^)yABr|`_(sD+P5g2d}M*`eZ1nr){RlJ zGn9~+Dc8it{tLFd?L*mqvR(1dfeEvl7YI8W zgFL*RA+3)`TIZ}WDKUrrEQT-dBuf{Y!u6jY-hN81pD)j{gS^K z<1%J<`efz&=CSuOqsZ{(pDv7N?LBLgMHY9=tmx9J7)u_S*3Co4)b4nsZLVhilRsXc zLGOLo(=M=lXV06ula8S<^{+M;EcWPZDp=&wwC#4mT`e+nX_os)N4o=dX!UWs_Tt9SGS?7-TrRC7pn6UX3T$S|!xN@7 z-oRk+bgrwbzqhOFhu0nSk}FQ$L-V@iZwLlV2 z`qa2+JKf?D3ckx~m_E__HcKxV{tap^p`)na7%^#dw1j$m?7l-s%d@UO7z<3)=AmNK zOv`VoONu8mkDgk4vTeA{(0W?d3Mc)s+!l7H#$}t$>4#}T8}-gsC`K1^fc<~%2to!8R{w%eC>}D z&vVbGWQHOdR);?fcG58ee$}1PY>9|AuCTsv%*$Xz9$TD?E0r#aBrP3&+Z(%arPH|S z`Csg3HX0VaTYll@O_rxZVJnRN{bnI;K%})e1rG$pmm$%IgVp=GMh?7C+(7GXdHJZP z@5*Dfyt0ro!Pf1r;Z1ILMja=|MoWHcp*?-`4)#h{Bzd%q*8*)z%(&3@BiPr6$`J^U^Qnbc;vNryybtJqT8Qb;E)Ek2iUn~ZHs~G?@+DTELICWK_QE((j4B;~ zjR9jo9;7OUyy8A{>FMq3|ItE8K?Ij4R9Qh}f2Jwna{eOgv)Gh9s&qaL1Tz1K`O~8&*hUe`@Ln-}J0Rfu}P*smO4iT^=67A4LTNa2WQa~n}$!25G910c$ zNH`pc$YFm1<;@pMn0z*LC#En9y)?dCUk9BjiV@I+TP{o&CLOD7+2!FNuE)Q^J7^Xeb{pKTh^n2c63UgCtBP zn>afh9!DV%30OSIj)0wuS%tR&6p5iqRAS<=HiS7`<*-npWFTUh$~uJrRIpGkR96wm zln6w0fgpy4QbHh=ma6hbI?N4<7gr2v$dwiUzUG6#*tyo+6o}!fx{yd!*+MMOIT17C zK|tjQvYYE-M>6>lAT+<<7t{wi_kRo)heM%Y32a+58^?sk8(?4{yOl0d}tRWg^n|0uSeRTqmU+Rou?aY zR{1TxP*DI$w1r+FVi;`UCFKRbBB%r)p_atk*IjEwT^+8En0a<&5E7YqyE)Uz9qjPM j8b`y8cBKVi5t^$Yz%m%T)O4pCqyh7G_j5b#5}x)S(L1|$ diff --git a/data/icons/path20.png b/data/icons/path20.png deleted file mode 100644 index 1021d86405b81bbfa10a78e4ea7e8de8651462dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6265 zcmeH~c|26@`^Se0i3}x0dZrh=5n)9dy7{@2W$nK}1;UGMw4KKFI*bDsofCmVTLRapoG zB5!AF=?eZX2M=W#De&9+aZ@e?BGnh>?#*{431A#9o5lzPVEj-H00V>!8U!MII^w;x zL_=F{ny&04X=@wZKW?nOq2aAt*0YT3YnpYF!-39rM>W5-?&dmOS7yKP;!Vdhv(`}y zvpn#<$pHneJ`tCL9 zFZEGjmmT`89^8bMS;xkQE)kW&&Ii263@a;9E?zMezGTn#k(pUQDm>=-ZQ@nR4X@oZ zXXM*XM>}cx9%6N#VlV2bG~(1+Y!rME>qfeDQ?}<2g7c)cG9pIWite?%Pz_btE`ZC+K`}m%ZhqnK730>TM zI?Q(@P|$p2UHEIIvuW0~Ua*=bk052?he+K{_DfRiOI7ESP=~$r z&g5yvjz;cXs1I9fxKk=n#~?Dbs47BFWzu=saXq+>mX!zx+}9EL51AFN+PYNhJk9fx zukJRkVx6OIPU|Bb594@n{fYdmL;h+7B}Q+`f#H(vAzdR0zBdv_hCeLJ%~8V> zZ9iM?&f2%5D1QIT9?RAG5bL$tFxFCu)oH7Xyj`1KHW<+gm7L6i%rI3|>B8aVH(WIO-jl*pt@l{($a^}z z;Lv_8pf^W#+J^irVA3to6JYx;8y>k{CcS2`-hYq%yV{EF$D6yLUK8ZnMicXvmb{{g zc*Wd)3%|#ymQy>b`ew#2O0F4;XUlYSL2rpZxpW(^Jl)oDL)h%Fi7^qXapYm*;aK^% zoabc=(wB})6fm!O7iQFJss-?N#uRJ4_S|hyN?L!%Vb#QieVNyI)}Bs2;{&+f4f%lw z2+ODPJA$_^ZOC~jGIn?&)p9qAAI(dbmvi0m&s5rj74&Gq7D!oQ#X#iFCo7>@<5O2Q zBuRaZ6ty*W8Y|Csz3^c*X2~YV?~Zb7-FvC!gYT$Z^>I41`s<-7zn6+%qxRdIPX;TV zh#iM2YA3mM_#H0q`O7fr$i~H}7!3c|`x|*WIMl77x)=I(tOOM;H`_%2lzIsnTh(<=rmDWWy~I9qX=T)?jKskE z&PuW$-M;%%zzEx{a?#9+_s^F|CR)T=20mZ(q2{E|2~e3+4NopQevsVuZZt*l(T4qY zI%#%&2jVVBM~-fT*y%T^TU&3k3?GCA?1-gikZo197*rFkke`BX(is}e&t8A)_?>BJ zQq5`Gb!XS{g16}0GThpg<)+%u*m1X^?zA0kRYRQ=aV!VAR^4Tv2paV!jk>+Z6rN1( zD{%oPlwZ}AS3G#U;VRz#?@pVN+cJ;0>u$B(YQH5|O-3z0Gc>wBG9HoQP^YX+xQ>6T zpB<4S|NQj$dTl-PCjzC~+*5+LS@|;j&J6(ywsL2kV=xPfVLC@@NF#l^psV?ij{s8y+H1ZY-FZ% zI7BWd&AG{J*`$5*^<>%H-N=&}k(`osD4B@r%}75brPp^eZ}&fGzkd7z8U1y!)>{7~ zri?QOg_4^%ni8LHrEw$wIu|LMT=2yCu4>%Ax6HE@$flzNThaX@r$+VRU8j1D<>T%Z z%iRe*fcW&x)EMJ6THREIzOh=9E`S!)n_>4a8-%Sy$kIP0W=~3(yZ2!14`{n2nR8bt zy;bP@Izp&>9zClNU6pb3iyz0pWaG)c^oJCJs8`nzhK$iE7!1DZNFP$1exI`QY~p@b z8LsdIG)%ERLCtT-+R>=ZHSX-9S36e-O?$#uKP`367T!xgGrfi&DsY&(QPK0f@PW3O z^V)~z_Yf+)Zl#PO&0t}v8+`t)IZKs-C@TU zX-Cy#%T;Wui(kBGCO@ve<>MvoWd@y=2nyl2@{cNw*~=ox4uZkd*P(&W+M1NH25d*l*heSez%yE+$gVe75UDn(cG6t^s^ zcXzxHzILGSZs@@x&-|i~MxW(17VQB}4gX_(v<7}0W)>Sw)XA-}PM;;NUxEGS@Angl z*Y+lClfAC9>!tT5c~xq=!mLy!JwW+_*3E2NrNPjk^hC^Afr=k~^}tj|kgxWp@m1Fz zZJ704c5+7Z=EH|%o2n`TC#dUTipdME^vboVD|KcixGhm-fZGpmM+YK>%|w!@ zY%+ipGCAP(1OhQO6>>yK$=ML`TP_6)v9n7TwRv`e3ukka2O8Mc>3FZdQr9-8l zfIxr=n)1L=F~1CHW9R7n!$T|qox$YHd4Xd8V##OF{uS$&*u*_^>HHiB=>CKIi}m;1 z=ZrxsM@OP1n-VMz&(6{WE*_sqWm6bb;{0C#N5&W$8Y1vm8VP~J7@!d(l0F7OGs55~ zcnk?o!5jR9vSac1Bo+k_qd+*40dmNO1^|^r!XS){NFV}FAs~zl0StmdBVcg)hGd*R z4fhjb3zq>_B`NUdsKh8Lh|)I#a0Ubt9)ZSE&|oxF0>Y3+Fhl@o10zEM8K4o!ggF$I zLbPUcnIv#J8B7u#Kyg^~xel@5L^EeQ6F3%${us95EKIS=|C&1-_l_+Ln&`AAxu6SCAU^1YvBypXBfH^Ff3(=LQ1(=DR3P*}(p1PRjMaS_}LDu})|#4uJ+g`tDc^5kny2 zvEV5YjfSJXC5#eZ)!#EVM*Sa6jOPq~S_VMB`8IHQ0aq*3kLBu{W?~uti?45U@n0MP zME@D&xAgrZ*B`llOM%}q{!?9lA{5yC7?L1)FVqE5J{4q zrJ1|oz0}rw7d?UHQsd9dD!c9k_`0vT;$VJNiUiY)J9oqrhC7yPmz?%q1_jPq{liOhS5q)pEPGKbjdP_!P_dBr=6E z23K4?>ThJfH(sha9+Msxy1C1_Kc_DIfV-N=cm^obM~gJ_@IT^*eYlEg#jmmeyOynJ!{do)8eGidEWAN3)SPXYZklWq)3{VUBUHS6Vb&?OZ;D_z;!%DI>+7Hr9y>-(&z{uGP{Y+J zb=Rz&?ksH+iJE#jDe857E)$lwJN{nVxpF~>;x<-Vnx;r(zj$P`@9uLUz0+~IY0bgmF+l+%ZJpCpF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10XIoR zK~yM_jgmhvL{SjM&%9Oo7YTMLGz%ws)~H1CJIeN?*DO=dPQvs*J86J2)w%92d49tXMh`uaSc>rGa1kT+Q2jL<*^NH0T%jy08L>r U|26I@$^ZZW07*qoM6N<$g6#{bzW@LL diff --git a/data/icons/poweroff.png b/data/icons/poweroff.png deleted file mode 100644 index 1fb1e75c828d38f907b7ff7556dc73de0455ff10..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 599 zcmV-d0;v6oP)pF8FWQhbW?9;ba!ELWdL_~cP?peYja~^aAhuUa%Y?FJQ@H10pv+U zK~yM_jgY--lTjGOf6sm2bnzpxR7tEc5sm0#Qi^uaKR~1y-av7%Aa!zZut@(0QOV+0 zce_YYQ$HZ6xL7QeYLh__ba_pn6&(~EY~SWS4sTLZNs4E=mwV57&N+`m7o>9eQ_=Y0 zg;?TH^~dsuPn|fXn^=)!z;;!Jx^djUPTqu~7c9=ndgcDde~JBB@1P@E7WB4XnYq61 zoXqCO6uk7yg@&HC=i4)?1t4xlh) z>hB(w5ncj)HVB?{8;}|-B#gL^fnMN6ty+5K0vI=Pwh;vL0)sXP-Y0Wj(LtXQi7e~t zN&;hswPDAB9f0qw+0k&BO(X|S?-n)pfD>JF3$MhOGydBLOB=+k?^M?FtS~C*5YPvF lQ)J$#z3W$JN@3j)e*jLFrNd8l&ny4{002ovPDHLkV1h8c408Yg diff --git a/development b/development deleted file mode 120000 index 1c69f2fb..00000000 --- a/development +++ /dev/null @@ -1 +0,0 @@ -/home/bill/src/lua-scripts.local/development/ \ No newline at end of file diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 8beed26a..71609321 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1,6 +1,6 @@ --[[ This file is part of darktable, - copyright (c) 2018, 2020, 2023, 2024 Bill Ferguson + copyright (c) 2018, 2020, 2023 Bill Ferguson darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,9 +22,9 @@ manage the lua scripts. On startup script_manager scans the lua scripts directory to see what scripts are present. - Scripts are sorted by 'folder' based on what sub-directory they are in. With no - additional script repositories iinstalled, the folders are contrib, examples, official - and tools. When a folder is selected the buttons show the script name and whether the + Scripts are sorted by 'category' based on what sub-directory they are in. With no + additional script repositories iinstalled, the categories are contrib, examples, official + and tools. When a category is selected the buttons show the script name and whether the script is started or stopped. The button is a toggle, so if the script is stopped click the button to start it and vice versa. @@ -60,9 +60,7 @@ local debug = require "darktable.debug" local gettext = dt.gettext.gettext --- Tell gettext where to find the .mo file translating messages for a particular domain -dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .. "/lua/locale/") - +dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) @@ -70,34 +68,30 @@ end -- api check -du.check_min_api_version("9.3.0", "script_manager") +du.check_min_api_version("5.0.0", "script_manager") -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S -- - - - - - - - - - - - - - - - - - - - - - - - -- path separator -local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- command separator -local CS = dt.configuration.running_os == "windows" and "&" or ";" - -local MODULE = "script_manager" +local CS = dt.configuration.running_os == "windows" and "&" or ";" -local MIN_BUTTONS_PER_PAGE = 5 -local MAX_BUTTONS_PER_PAGE = 20 -local DEFAULT_BUTTONS_PER_PAGE = 10 +local MODULE = "script_manager" -local DEFAULT_LOG_LEVEL = log.error +local MIN_BUTTONS_PER_PAGE = 5 +local MAX_BUTTONS_PER_PAGE = 20 +local DEFAULT_BUTTONS_PER_PAGE = 10 -local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" -local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" +local DEFAULT_LOG_LEVEL = log.error -local LUA_API_VER = "API-" .. dt.configuration.api_version_string +local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" +local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" --- local POWER_ICON = dt.configuration.config_dir .. "/lua/data/data/icons/power.png" -local POWER_ICON = dt.configuration.config_dir .. "/lua/data/icons/path20.png" -local BLANK_ICON = dt.configuration.config_dir .. "/lua/data/icons/blank20.png" +local LUA_API_VER = "API-" .. dt.configuration.api_version_string -- - - - - - - - - - - - - - - - - - - - - - - - -- P R E F E R E N C E S @@ -134,7 +128,7 @@ sm.event_registered = false -- set up tables to contain all the widgets and choices sm.widgets = {} -sm.folders = {} +sm.categories = {} -- set log level for functions @@ -143,14 +137,14 @@ sm.log_level = DEFAULT_LOG_LEVEL --[[ sm.scripts is a table of tables for containing the scripts - It is organized as into folder (folder) subtables containing + It is organized as into category (folder) subtables containing each script definition, which is a table sm.scripts- | - - folder------------| + - category------------| | - script - - folder----| | + - category----| | - script| | - script - script| @@ -158,7 +152,7 @@ sm.log_level = DEFAULT_LOG_LEVEL and a script table looks like name the name of the script file without the lua extension - path folder (folder), path separator, path, name without the lua extension + path category (folder), path separator, path, name without the lua extension doc the header comments from the script to be used as a tooltip script_name the folder, path separator, and name without the lua extension running true if running, false if not, hidden if running but the @@ -184,7 +178,10 @@ sm.page_status = {} sm.page_status.num_buttons = DEFAULT_BUTTONS_PER_PAGE sm.page_status.buttons_created = 0 sm.page_status.current_page = 0 -sm.page_status.folder = "" +sm.page_status.category = "" + +-- use color in the interface? +sm.use_color = false -- installed script repositories sm.installed_repositories = { @@ -215,24 +212,17 @@ end local function pref_read(name, pref_type) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type) - local val = dt.preferences.read(MODULE, name, pref_type) - log.msg(log.debug, "read value " .. tostring(val)) - restore_log_level(old_log_level) return val end local function pref_write(name, pref_type, value) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name) - dt.preferences.write(MODULE, name, pref_type, value) - restore_log_level(old_log_level) end @@ -242,15 +232,12 @@ end local function get_repo_status(repo) local old_log_level = set_log_level(sm.log_level) - - local p = dtsys.io_popen("cd " .. repo .. CS .. "git status") - + local p = io.popen("cd " .. repo .. CS .. "git status") if p then local data = p:read("*a") p:close() return data end - log.msg(log.error, "unable to get status of " .. repo) restore_log_level(old_log_level) return nil @@ -258,11 +245,8 @@ end local function get_current_repo_branch(repo) local old_log_level = set_log_level(sm.log_level) - local branch = nil - - local p = dtsys.io_popen("cd " .. repo .. CS .. "git branch --all") - + local p = io.popen("cd " .. repo .. CS .. "git branch --all") if p then local data = p:read("*a") p:close() @@ -270,29 +254,23 @@ local function get_current_repo_branch(repo) for _, b in ipairs(branches) do log.msg(log.debug, "branch for testing is " .. b) branch = string.match(b, "^%* (.-)$") - if branch then log.msg(log.info, "current repo branch is " .. branch) return branch end - end end - if not branch then log.msg(log.error, "no current branch detected in repo_data") end - restore_log_level(old_log_level) return nil end local function get_repo_branches(repo) local old_log_level = set_log_level(sm.log_level) - local branches = {} - local p = dtsys.io_popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") - + local p = io.popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") if p then local data = p:read("*a") p:close() @@ -307,14 +285,12 @@ local function get_repo_branches(repo) end end end - restore_log_level(old_log_level) return branches end local function is_repo_clean(repo_data) local old_log_level = set_log_level(sm.log_level) - if string.match(repo_data, "\n%s-%a.-%a:%s-%a%g-\n") then log.msg(log.info, "repo is dirty") return false @@ -322,17 +298,13 @@ local function is_repo_clean(repo_data) log.msg(log.info, "repo is clean") return true end - restore_log_level(old_log_level) end local function checkout_repo_branch(repo, branch) local old_log_level = set_log_level(sm.log_level) - log.msg(log.info, "checkout out branch " .. branch .. " from repository " .. repo) - - dtsys.os_execute("cd " .. repo .. CS .. "git checkout " .. branch) - + os.execute("cd " .. repo .. CS .. "git checkout " .. branch) restore_log_level(old_log_level) end @@ -342,20 +314,16 @@ end local function update_combobox_choices(combobox, choice_table, selected) local old_log_level = set_log_level(sm.log_level) - local items = #combobox local choices = #choice_table - for i, name in ipairs(choice_table) do combobox[i] = name end - if choices < items then for j = items, choices + 1, -1 do combobox[j] = nil end end - if not selected then selected = 1 end @@ -366,10 +334,8 @@ end local function string_trim(str) local old_log_level = set_log_level(sm.log_level) - local result = string.gsub(str, "^%s+", "") result = string.gsub(result, "%s+$", "") - restore_log_level(old_log_level) return result end @@ -378,76 +344,18 @@ local function string_dequote(str) return string.gsub(str, "['\"]", "") end -local function string_dei18n(str) - return string.match(str, "%_%((.+)%)") -end - -local function string_chop(str) - return str:sub(1, -2) -end - ------------------ -- script handling ------------------ -local function add_script_folder(folder) - local old_log_level = set_log_level(sm.log_level) - - if #sm.folders == 0 or not string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then - table.insert(sm.folders, folder) - sm.scripts[folder] = {} - log.msg(log.debug, "created folder " .. folder) - end - - restore_log_level(old_log_level) -end - -local function get_script_metadata(script) +local function add_script_category(category) local old_log_level = set_log_level(sm.log_level) - -- set_log_level(log.debug) - - log.msg(log.debug, "processing metatdata for " .. script) - - local description = nil - local metadata = nil - - f = io.open(LUA_DIR .. PS .. script .. ".lua") - if f then - -- slurp the file - local content = f:read("*all") - f:close() - -- grab the script_data.metadata table - description = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") - else - log.msg(log.error, "cant read from " .. script) - end - - if description then - metadata = "" - -- format it into a string block for display - local lines = du.split(description, "\n") - log.msg(log.debug, "got " .. #lines .. " lines") - local first = 1 - for i = 1, #lines do - log.msg(log.debug, "splitting line " .. lines[i]) - local parts = du.split(lines[i], " = ") - log.msg(log.debug, "got value " .. parts[1] .. " and data " .. parts[2]) - if string.match(parts[2], "%_%(") then - parts[2] = _(string_dequote(string_dei18n(parts[2]))) - else - parts[2] = string_dequote(parts[2]) - end - if string.match(parts[2], ",$") then - parts[2] = string_chop(parts[2]) - end - metadata = metadata .. string.format("%s%-10s\t%s", first and "" or "\n", parts[1], parts[2]) - first = nil - end - log.msg(log.debug, "script data is \n" .. metadata) + if #sm.categories == 0 or not string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then + table.insert(sm.categories, category) + sm.scripts[category] = {} + log.msg(log.debug, "created category " .. category) end - restore_log_level(old_log_level) - return metadata end local function get_script_doc(script) @@ -461,9 +369,7 @@ local function get_script_doc(script) -- assume that the second block comment is the documentation description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]") else - - log.msg(log.error, "can't read from " .. script) - + log.msg(log.error, "Cant read from " .. script) end if description then restore_log_level(old_log_level) @@ -476,28 +382,18 @@ end local function activate(script) local old_log_level = set_log_level(sm.log_level) - local status = nil -- status of start function local err = nil -- error message returned if module doesn't start - log.msg(log.info, "activating " .. script.name) - if script.running == false then - script_manager_running_script = script.name - status, err = du.prequire(script.path) log.msg(log.debug, "prequire returned " .. tostring(status) .. " and for err " .. tostring(err)) - script_manager_running_script = nil - if status then pref_write(script.script_name, "bool", true) - - log.msg(log.screen, _(string.format("loaded %s", script.script_name))) - + log.msg(log.screen, string.format(_("loaded %s"), script.script_name)) script.running = true - if err ~= true then log.msg(log.debug, "got lib data") script.data = err @@ -507,20 +403,17 @@ local function activate(script) else script.data = nil end - else - - log.msg(log.screen, _(string.format("%s failed to load", script.script_name))) - log.msg(log.error, "error loading " .. script.script_name) - log.msg(log.error, "error message: " .. err) - + log.msg(log.screen, string.format(_("%s failed to load"), script.script_name)) + log.msg(log.error, "Error loading " .. script.script_name) + log.msg(log.error, "Error message: " .. err) + end else -- script is a lib and loaded but hidden and the user wants to reload script.data.restart() script.running = true status = true pref_write(script.script_name, "bool", true) end - restore_log_level(old_log_level) return status end @@ -535,13 +428,9 @@ local function deactivate(script) -- deactivate it.... local old_log_level = set_log_level(sm.log_level) - pref_write(script.script_name, "bool", false) - if script.data then - script.data.destroy() - if script.data.destroy_method then if string.match(script.data.destroy_method, "hide") then script.running = "hidden" @@ -553,57 +442,44 @@ local function deactivate(script) package.loaded[script.script_name] = nil script.running = false end - log.msg(log.info, "turned off " .. script.script_name) - - log.msg(log.screen, _(string.format("%s stopped", script.name))) - + log.msg(log.screen, string.format(_("%s stopped"), script.name)) else script.running = false - log.msg(log.info, "setting " .. script.script_name .. " to not start") - - log.msg(log.screen, _(string.format("%s will not start when darktable is restarted", script.name))) - + log.msg(log.screen, string.format(_("%s will not start when darktable is restarted"), script.name)) end - restore_log_level(old_log_level) end -local function add_script_name(name, path, folder) +local function add_script_name(name, path, category) local old_log_level = set_log_level(sm.log_level) - - log.msg(log.debug, "folder is " .. folder) + log.msg(log.debug, "category is " .. category) log.msg(log.debug, "name is " .. name) - local script = { name = name, - path = folder .. "/" .. path .. name, + path = category .. "/" .. path .. name, running = false, - doc = get_script_doc(folder .. "/" .. path .. name), - metadata = get_script_metadata(folder .. "/" .. path .. name), - script_name = folder .. "/" .. name, + doc = get_script_doc(category .. "/" .. path .. name), + script_name = category .. "/" .. name, data = nil } - - table.insert(sm.scripts[folder], script) - + table.insert(sm.scripts[category], script) if pref_read(script.script_name, "bool") then activate(script) else pref_write(script.script_name, "bool", false) end - restore_log_level(old_log_level) end local function process_script_data(script_file) local old_log_level = set_log_level(sm.log_level) - -- the script file supplied is folder/filename.filetype - -- the following pattern splits the string into folder, path, name, fileename, and filetype + -- the script file supplied is category/filename.filetype + -- the following pattern splits the string into category, path, name, fileename, and filetype -- for example contrib/gimp.lua becomes - -- folder - contrib + -- category - contrib -- path - -- name - gimp.lua -- filename - gimp @@ -620,42 +496,14 @@ local function process_script_data(script_file) log.msg(log.info, "processing " .. script_file) -- add the script data - local folder,path,name,filename,filetype = string.match(script_file, pattern) + local category,path,name,filename,filetype = string.match(script_file, pattern) - if folder and name and path then - log.msg(log.debug, "folder is " .. folder) + if category and name and path then + log.msg(log.debug, "category is " .. category) log.msg(log.debug, "name is " .. name) - add_script_folder(folder) - add_script_name(name, path, folder) - end - - restore_log_level(old_log_level) -end - -local function ensure_lib_in_search_path(line) - local old_log_level = set_log_level(sm.log_level) - - log.msg(log.debug, "line is " .. line) - - if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua/lib")) then - log.msg(log.debug, line .. " is already in search path, returning...") - return - end - - local pattern = dt.configuration.running_os == "windows" and "(.+)\\lib\\.+lua" or "(.+)/lib/.+lua" - local path = string.match(line, pattern) - - log.msg(log.debug, "extracted path is " .. path) - log.msg(log.debug, "package.path is " .. package.path) - - if not string.match(package.path, ds.sanitize_lua(path)) then - - log.msg(log.debug, "path isn't in package.path, adding...") - - package.path = package.path .. ";" .. path .. "/?.lua" - - log.msg(log.debug, "new package.path is " .. package.path) + add_script_category(category) + add_script_name(name, path, category) end restore_log_level(old_log_level) @@ -692,20 +540,15 @@ end local function scan_scripts(script_dir) local old_log_level = set_log_level(sm.log_level) - local script_count = 0 local find_cmd = "find -L " .. script_dir .. " -name \\*.lua -print | sort" - if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s \"" .. script_dir .. "\\*.lua\" | sort" end - log.msg(log.debug, "find command is " .. find_cmd) - -- scan the scripts - local output = dtsys.io_popen(find_cmd) + local output = io.popen(find_cmd) for line in output:lines() do - log.msg(log.debug, "line is " .. line) local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off local script_file = l:sub(1,-5) -- strip off .lua\n if not string.match(script_file, "script_manager") then -- let's not include ourself @@ -721,7 +564,6 @@ local function scan_scripts(script_dir) end end end - restore_log_level(old_log_level) return script_count end @@ -733,9 +575,7 @@ local function update_scripts() local git = sm.executables.git if not git then - - log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) - + dt.print(_("ERROR: git not found, install or specify the location of the git executable.")) return end @@ -749,7 +589,7 @@ local function update_scripts() end if result == 0 then - log.msg(log.screen, _("lua scripts successfully updated")) + dt.print(_("lua scripts successfully updated")) end restore_log_level(old_log_level) @@ -762,85 +602,65 @@ end local function update_script_update_choices() local old_log_level = set_log_level(sm.log_level) - local installs = {} local pref_string = "" - for i, repo in ipairs(sm.installed_repositories) do table.insert(installs, repo.name) pref_string = pref_string .. i .. "," .. repo.name .. "," .. repo.directory .. "," end - update_combobox_choices(sm.widgets.update_script_choices, installs, 1) - log.msg(log.debug, "repo pref string is " .. pref_string) pref_write("installed_repos", "string", pref_string) - restore_log_level(old_log_level) end local function scan_repositories() local old_log_level = set_log_level(sm.log_level) - local script_count = 0 local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.git -print | sort" - if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s /a:d " .. LUA_DIR .. PS .. "*.git | sort" end - log.msg(log.debug, "find command is " .. find_cmd) - - local output = dtsys.io_popen(find_cmd) - + local output = io.popen(find_cmd) for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off - local folder = string.match(l, "(.-)" .. PS) -- get everything to the first / - - if folder then -- if we have a folder (.git doesn't) - - log.msg(log.debug, "found folder " .. folder) - - if not string.match(folder, "plugins") and not string.match(folder, "%.git") then -- skip plugins - + local category = string.match(l, "(.-)" .. PS) -- get everything to teh first / + if category then -- if we have a category (.git doesn't) + log.msg(log.debug, "found category " .. category) + if not string.match(category, "plugins") and not string.match(category, "%.git") then -- skip plugins if #sm.installed_repositories == 1 then - log.msg(log.debug, "only 1 repo, adding " .. folder) - table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) + log.msg(log.debug, "only 1 repo, adding " .. category) + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) else log.msg(log.debug, "more than 1 repo, we have to search the repos to make sure it's not there") local found = nil - for _, repo in ipairs(sm.installed_repositories) do - if string.match(repo.name, ds.sanitize_lua(folder)) then + if string.match(repo.name, ds.sanitize_lua(category)) then log.msg(log.debug, "matched " .. repo.name) found = true break end end - if not found then - table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) end - end end end end - update_script_update_choices() - restore_log_level(old_log_level) end local function install_scripts() local old_log_level = set_log_level(sm.log_level) - local url = sm.widgets.script_url.text - local folder = sm.widgets.new_folder.text + local category = sm.widgets.new_category.text - if string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then - log.msg(log.screen, _(string.format("folder %s is already in use. Please specify a different folder name.", folder))) - log.msg(log.error, "folder " .. folder .. " already exists, returning...") + if string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then + log.msg(log.screen, string.format(_("category %s is already in use, please specify a different category name."), category)) + log.msg(log.error, "category " .. category .. " already exists, returning...") restore_log_level(old_log_level) return end @@ -850,12 +670,12 @@ local function install_scripts() local git = sm.executables.git if not git then - log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) + dt.print(_("ERROR: git not found, install or specify the location of the git executable.")) restore_log_level(old_log_level) return end - local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. folder + local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. category log.msg(log.debug, "update git command is " .. git_command) if dt.configuration.running_os == "windows" then @@ -867,34 +687,30 @@ local function install_scripts() log.msg(log.info, "result from import is " .. result) if result == 0 then - local count = scan_scripts(LUA_DIR .. PS .. folder) - + local count = scan_scripts(LUA_DIR .. PS .. category) if count > 0 then - update_combobox_choices(sm.widgets.folder_selector, sm.folders, sm.widgets.folder_selector.selected) - dt.print(_("scripts successfully installed into folder ") .. folder) - table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) + update_combobox_choices(sm.widgets.category_selector, sm.categories, sm.widgets.category_selector.selected) + dt.print(string.format(_("scripts successfully installed into category "), category)) + table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) update_script_update_choices() - - for i = 1, #sm.widgets.folder_selector do - if string.match(sm.widgets.folder_selector[i], ds.sanitize_lua(folder)) then - log.msg(log.debug, "setting folder selector to " .. i) - sm.widgets.folder_selector.selected = i + for i = 1, #sm.widgets.category_selector do + if string.match(sm.widgets.category_selector[i], ds.sanitize_lua(category)) then + log.msg(log.debug, "setting category selector to " .. i) + sm.widgets.category_selector.selected = i break end i = i + 1 end - log.msg(log.debug, "clearing text fields") sm.widgets.script_url.text = "" - sm.widgets.new_folder.text = "" + sm.widgets.new_category.text = "" sm.widgets.main_menu.selected = 3 else - log.msg(log.screen, _("No scripts found to install")) - log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") + dt.print(_("no scripts found to install")) + log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to category_selector") end - else - log.msg(log.screen, _("failed to download scripts")) + dt.print(_("failed to download scripts")) end restore_log_level(old_log_level) @@ -903,101 +719,102 @@ end local function clear_button(number) local old_log_level = set_log_level(sm.log_level) - local button = sm.widgets.buttons[number] - local label = sm.widgets.labels[number] - - button.image = BLANK_ICON + button.label = "" button.tooltip = "" button.sensitive = false - label.label = "" - button.name = "" - + --button.name = "" restore_log_level(old_log_level) end -local function find_script(folder, name) +local function find_script(category, name) local old_log_level = set_log_level(sm.log_level) - - log.msg(log.debug, "looking for script " .. name .. " in folder " .. folder) - - for _, script in ipairs(sm.scripts[folder]) do + log.msg(log.debug, "looking for script " .. name .. " in category " .. category) + for _, script in ipairs(sm.scripts[category]) do if string.match(script.name, "^" .. ds.sanitize_lua(name) .. "$") then return script end end - restore_log_level(old_log_level) return nil end -local function populate_buttons(folder, first, last) +local function populate_buttons(category, first, last) local old_log_level = set_log_level(sm.log_level) - - log.msg(log.debug, "folder is " .. folder .. " and first is " .. first .. " and last is " .. last) - + log.msg(log.debug, "category is " .. category .. " and first is " .. first .. " and last is " .. last) local button_num = 1 - for i = first, last do - local script = sm.scripts[folder][i] - local button = sm.widgets.buttons[button_num] - local label = sm.widgets.labels[button_num] - + script = sm.scripts[category][i] + button = sm.widgets.buttons[button_num] if script.running == true then - button.name = "pb_on" + if sm.use_color then + button.label = script.name + button.name = "sm_started" + else + button.label = string.format(_("%s started"), script.name) + end else - button.name = "pb_off" + if sm.use_color then + button.label = script.name + button.name = "sm_stopped" + else + button.label = string.format(_("%s stopped"), script.name) + end end - - button.image = POWER_ICON - label.label = script.name - label.name = "pb_label" - button.ellipsize = "end" + button.ellipsize = "middle" button.sensitive = true - label.tooltip = script.metadata and script.metadata or script.doc - + button.tooltip = script.doc button.clicked_callback = function (this) - local cb_script = script + local script_name = nil local state = nil - if cb_script then - log.msg(log.debug, "found script " .. cb_script.name .. " with path " .. cb_script.path) - if cb_script.running == true then - log.msg(log.debug, "deactivating " .. cb_script.name .. " on " .. cb_script.path) - deactivate(cb_script) - this.name = "pb_off" + if sm.use_color then + script_name = string.match(this.label, "(.+)") + else + script_name, state = string.match(this.label, "(.-) (.+)") + end + local script = find_script(sm.widgets.category_selector.value, script_name) + if script then + log.msg(log.debug, "found script " .. script.name .. " with path " .. script.path) + if script.running == true then + log.msg(log.debug, "deactivating " .. script.name .. " on " .. script.path .. " for button " .. this.label) + deactivate(script) + if sm.use_color then + this.name = "sm_stopped" + else + this.label = string.format(_("%s stopped"), script.name) + end else - log.msg(log.debug, "activating " .. cb_script.name .. " on " .. script.path) - local result = activate(cb_script) + log.msg(log.debug, "activating " .. script.name .. " on " .. script.path .. " for button " .. this.label) + local result = activate(script) if result then - this.name = "pb_on" + if sm.use_color then + this.name = "sm_started" + else + this.label = string.format(_("%s started"), script.name) + end end end + else + log.msg(log.error, "script " .. script_name .. " not found") end end - button_num = button_num + 1 end - if button_num <= sm.page_status.num_buttons then for i = button_num, sm.page_status.num_buttons do clear_button(i) end end - restore_log_level(old_log_level) end local function paginate(direction) local old_log_level = set_log_level(sm.log_level) - - local folder = sm.page_status.folder - log.msg(log.debug, "folder is " .. folder) - - local num_scripts = #sm.scripts[folder] + local category = sm.page_status.category + log.msg(log.debug, "category is " .. category) + local num_scripts = #sm.scripts[category] log.msg(log.debug, "num_scripts is " .. num_scripts) - local max_pages = math.ceil(num_scripts / sm.page_status.num_buttons) - local cur_page = sm.page_status.current_page log.msg(log.debug, "max pages is " .. max_pages) @@ -1019,9 +836,7 @@ local function paginate(direction) log.msg(log.debug, "took path 2") cur_page = 1 end - log.msg(log.debug, "cur_page is " .. cur_page .. " and max_pages is " .. max_pages) - if cur_page == max_pages and cur_page == 1 then sm.widgets.page_forward.sensitive = false sm.widgets.page_back.sensitive = false @@ -1037,167 +852,115 @@ local function paginate(direction) end sm.page_status.current_page = cur_page - first = (cur_page * sm.page_status.num_buttons) - (sm.page_status.num_buttons - 1) - if first + sm.page_status.num_buttons > num_scripts then last = num_scripts else last = first + sm.page_status.num_buttons - 1 end + sm.widgets.page_status.label = string.format(_("page %d of %d"), cur_page, max_pages) - sm.widgets.page_status.label = _(string.format("page %d of %d", cur_page, max_pages)) - - populate_buttons(folder, first, last) - + populate_buttons(category, first, last) restore_log_level(old_log_level) end -local function change_folder(folder) +local function change_category(category) local old_log_level = set_log_level(sm.log_level) - - if not folder then - log.msg(log.debug "setting folder to selector value " .. sm.widgets.folder_selector.value) - sm.page_status.folder = sm.widgets.folder_selector.value + if not category then + log.msg(log.debug "setting category to selector value " .. sm.widgets.category_selector.value) + sm.page_status.category = sm.widgets.category_selector.value else - log.msg(log.debug, "setting catgory to argument " .. folder) - sm.page_status.folder = folder + log.msg(log.debug, "setting catgory to argument " .. category) + sm.page_status.category = category end paginate(2) - restore_log_level(old_log_level) end local function change_num_buttons() local old_log_level = set_log_level(sm.log_level) - cur_buttons = sm.page_status.num_buttons new_buttons = sm.widgets.num_buttons.value - pref_write("num_buttons", "integer", new_buttons) - if new_buttons < cur_buttons then - log.msg(log.debug, "took new is less than current branch") - for i = 1, cur_buttons - new_buttons do table.remove(sm.widgets.scripts) end - log.msg(log.debug, "finished removing widgets, now there are " .. #sm.widgets.buttons) elseif new_buttons > cur_buttons then - log.msg(log.debug, "took new is greater than current branch") - log.msg(log.debug, "number of scripts is " .. #sm.widgets.scripts) - log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) - log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) - log.msg(log.debug, "number of boxes is " .. #sm.widgets.boxes) - if new_buttons > sm.page_status.buttons_created then - for i = sm.page_status.buttons_created + 1, new_buttons do - log.msg(log.debug, "i is " .. i) table.insert(sm.widgets.buttons, dt.new_widget("button"){}) - log.msg(log.debug, "inserted new button") - log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) - table.insert(sm.widgets.labels, dt.new_widget("label"){}) - log.msg(log.debug, "inserted new label") - log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) - table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, - sm.widgets.buttons[i], sm.widgets.labels[i]}) - log.msg(log.debug, "inserted new box") sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end - end - log.msg(log.debug, "cur_buttons is " .. cur_buttons .. " and new_buttons is " .. new_buttons) log.msg(log.debug, #sm.widgets.buttons .. " buttons are available") - for i = cur_buttons + 1, new_buttons do log.msg(log.debug, "inserting button " .. i .. " into scripts widget") - table.insert(sm.widgets.scripts, sm.widgets.boxes[i]) + table.insert(sm.widgets.scripts, sm.widgets.buttons[i]) end - log.msg(log.debug, "finished adding widgets, now there are " .. #sm.widgets.buttons) else -- no change log.msg(log.debug, "no change, just returning") return end - sm.page_status.num_buttons = new_buttons log.msg(log.debug, "num_buttons set to " .. sm.page_status.num_buttons) paginate(2) -- force the buttons to repopulate sm.widgets.main_menu.selected = 3 -- jump back to start/stop scripts - restore_log_level(old_log_level) end local function load_preferences() local old_log_level = set_log_level(sm.log_level) - -- load the prefs and update settings -- update_script_choices - local pref_string = pref_read("installed_repos", "string") local entries = du.split(pref_string, ",") - while #entries > 2 do local num = table.remove(entries, 1) local name = table.remove(entries, 1) local directory = table.remove(entries, 1) - if not string.match(sm.installed_repositories[1].name, "^" .. ds.sanitize_lua(name) .. "$") then table.insert(sm.installed_repositories, {name = name, directory = directory}) end - end - update_script_update_choices() log.msg(log.debug, "updated installed scripts") - - -- folder selector - local val = pref_read("folder_selector", "integer") - + -- category selector + local val = pref_read("category_selector", "integer") if val == 0 then val = 1 end - - sm.widgets.folder_selector.selected = val - sm.page_status.folder = sm.widgets.folder_selector.value - log.msg(log.debug, "updated folder selector and set it to " .. sm.widgets.folder_selector.value) - + sm.widgets.category_selector.selected = val + sm.page_status.category = sm.widgets.category_selector.value + log.msg(log.debug, "updated category selector and set it to " .. sm.widgets.category_selector.value) -- num_buttons local val = pref_read("num_buttons", "integer") - if val == 0 then val = DEFAULT_BUTTONS_PER_PAGE end - sm.widgets.num_buttons.value = val log.msg(log.debug, "set page buttons to " .. val) - change_num_buttons() log.msg(log.debug, "paginated") - -- main menu local val = pref_read("main_menu_action", "integer") log.msg(log.debug, "read " .. val .. " for main menu") - if val == 0 then val = 3 end - sm.widgets.main_menu.selected = val log.msg(log.debug, "set main menu to val " .. val .. " which is " .. sm.widgets.main_menu.value) log.msg(log.debug, "set main menu to " .. sm.widgets.main_menu.value) - restore_log_level(old_log_level) end local function install_module() local old_log_level = set_log_level(sm.log_level) - if not sm.module_installed then dt.register_lib( "script_manager", -- Module name @@ -1211,13 +974,22 @@ local function install_module() ) sm.module_installed = true end - sm.run = true sm.use_color = pref_read("use_color", "bool") log.msg(log.debug, "set run to true, loading preferences") load_preferences() scan_repositories() - + --[[dt.print_log("\n\nsetting sm visible false\n\n") + dt.gui.libs["script_manager"].visible = false + dt.control.sleep(5000) + dt.print_log("setting sm visible true") + dt.gui.libs["script_manager"].visible = true + --[[dt.control.sleep(5000) + dt.print_log("setting sm expanded false") + dt.gui.libs["script_manager"].expanded = false + dt.control.sleep(5000) + dt.print_log("setting sm expanded true") + dt.gui.libs["script_manager"].expanded = true]] restore_log_level(old_log_level) end @@ -1236,28 +1008,22 @@ if check_for_updates then local repo = LUA_DIR if current_branch then - if sm.executables.git and clean and - (current_branch == "master" or string.match(current_branch, "^API%-")) then -- only make changes to clean branches local branches = get_repo_branches(LUA_DIR) - if current_branch ~= LUA_API_VER and current_branch ~= "master" then -- probably upgraded from an earlier api version so get back to master -- to use the latest version of script_manager to get the proper API checkout_repo_branch(repo, "master") - log.msg(log.screen, _("lua API version reset, please restart darktable")) - + log.msg(log.screen, "lua API version reset, please restart darktable") elseif LUA_API_VER == current_branch then -- do nothing, we are fine log.msg(log.debug, "took equal branch, doing nothing") - elseif string.match(LUA_API_VER, "dev") then -- we are on a dev API version, so checkout the dev -- api version or checkout/stay on master log.msg(log.debug, "took the dev branch") local match = false - for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) if LUA_API_VER == branch then @@ -1266,7 +1032,6 @@ if check_for_updates then checkout_repo_branch(repo, branch) end end - if not match then if current_branch == "master" then log.msg(log.info, "staying on master, no dev branch yet") @@ -1275,33 +1040,25 @@ if check_for_updates then checkout_repo_branch(repo, "master") end end - elseif #branches > 0 and LUA_API_VER > branches[#branches] then log.msg(log.info, "no newer branches, staying on master") -- stay on master - else -- checkout the appropriate branch for API version if it exists log.msg(log.info, "checking out the appropriate API branch") - local match = false - for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) - if LUA_API_VER == branch then match = true log.msg(log.info, "checking out repo branch " .. branch) checkout_repo_branch(repo, branch) log.msg(log.screen, "you must restart darktable to use the correct version of the lua") end - end - if not match then log.msg(log.warn, "no matching branch found for " .. LUA_API_VER) end - end end end @@ -1343,18 +1100,18 @@ sm.widgets.script_url = dt.new_widget("entry"){ tooltip = _("enter the URL of the git repository containing the scripts you wish to add") } -sm.widgets.new_folder = dt.new_widget("entry"){ +sm.widgets.new_category = dt.new_widget("entry"){ text = "", - placeholder = _("name of new folder"), - tooltip = _("enter a folder name for the additional scripts") + placeholder = _("name of new category"), + tooltip = _("enter a category name for the additional scripts") } sm.widgets.add_scripts = dt.new_widget("box"){ orientation = vertical, dt.new_widget("label"){label = _("URL to download additional scripts from")}, sm.widgets.script_url, - dt.new_widget("label"){label = _("new folder to place scripts in")}, - sm.widgets.new_folder, + dt.new_widget("label"){label = _("new category to place scripts in")}, + sm.widgets.new_category, dt.new_widget("button"){ label = _("install additional scripts"), clicked_callback = function(this) @@ -1369,8 +1126,6 @@ sm.widgets.allow_disable = dt.new_widget("check_button"){ clicked_callback = function(this) if this.value == true then sm.widgets.disable_scripts.sensitive = true - else - sm.widgets.disable_scripts.sensitive = false end end, } @@ -1382,62 +1137,41 @@ sm.widgets.disable_scripts = dt.new_widget("button"){ local LUARC = dt.configuration.config_dir .. PS .. "luarc" df.file_move(LUARC, LUARC .. ".disabled") log.msg(log.info, "lua scripts disabled") - log.msg(log.screen, _("lua scripts will not run the next time darktable is started")) + dt.print(_("lua scripts will not run the next time darktable is started")) end } sm.widgets.install_update = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = _(" ")}, - dt.new_widget("label"){label = " "}, - dt.new_widget("label"){label = _("update scripts")}, - dt.new_widget("label"){label = " "}, + dt.new_widget("section_label"){label = _("update scripts")}, sm.widgets.update_script_choices, sm.widgets.update, - dt.new_widget("section_label"){label = " "}, - dt.new_widget("label"){label = " "}, - dt.new_widget("label"){label = _("add more scripts")}, - dt.new_widget("label"){label = " "}, + dt.new_widget("section_label"){label = _("add more scripts")}, sm.widgets.add_scripts, - dt.new_widget("section_label"){label = " "}, - dt.new_widget("label"){label = " "}, - dt.new_widget("label"){label = _("disable scripts")}, - dt.new_widget("label"){label = " "}, + dt.new_widget("section_label"){label = _("disable scripts")}, sm.widgets.allow_disable, - sm.widgets.disable_scripts, - dt.new_widget("label"){label = " "}, + sm.widgets.disable_scripts } -- manage the scripts -sm.widgets.folder_selector = dt.new_widget("combobox"){ - label = _("folder"), - tooltip = _( "select the script folder"), +sm.widgets.category_selector = dt.new_widget("combobox"){ + label = _("category"), + tooltip = _("select the script category"), selected = 1, changed_callback = function(self) if sm.run then - pref_write("folder_selector", "integer", self.selected) - change_folder(self.value) + pref_write("category_selector", "integer", self.selected) + change_category(self.value) end end, - table.unpack(sm.folders), + table.unpack(sm.categories), } --- a script "button" consists of: --- a button to start and stop the script --- a label that contains the name of the script --- a horizontal box that contains the button and the label - sm.widgets.buttons ={} -sm.widgets.labels = {} -sm.widgets.boxes = {} - for i =1, DEFAULT_BUTTONS_PER_PAGE do table.insert(sm.widgets.buttons, dt.new_widget("button"){}) - table.insert(sm.widgets.labels, dt.new_widget("label"){}) - table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, - sm.widgets.buttons[i], sm.widgets.labels[i]}) - sm.page_status.buttons_created = sm.page_status.buttons_created + 1 + sm.page_status.buttons_create = sm.page_status.buttons_created + 1 end local page_back = "<" @@ -1471,13 +1205,10 @@ sm.widgets.page_control = dt.new_widget("box"){ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, - - dt.new_widget("section_label"){label = _(" ")}, - dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("scripts")}, - sm.widgets.folder_selector, + sm.widgets.category_selector, sm.widgets.page_control, - table.unpack(sm.widgets.boxes), + table.unpack(sm.widgets.buttons) } -- configure options @@ -1503,16 +1234,21 @@ sm.widgets.change_buttons = dt.new_widget("button"){ sm.widgets.configure = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = " "}, - dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("configuration")}, - dt.new_widget("label"){label = " "}, sm.widgets.num_buttons, - dt.new_widget("label"){label = " "}, sm.widgets.change_buttons, - dt.new_widget("label"){label = " "}, } +sm.widgets.color = dt.new_widget("check_button"){ + label = _("use color interface?"), + value = pref_read("use_color", "bool"), + clicked_callback = function(this) + pref_write("use_color", "bool", this.value) + sm.use_color = this.value + end +} +table.insert(sm.widgets.configure, sm.widgets.color) + -- stack for the options sm.widgets.main_stack = dt.new_widget("stack"){ @@ -1558,7 +1294,7 @@ else function(event, old_view, new_view) if new_view.name == "lighttable" and old_view.name == "darkroom" then install_module() - end + end end ) sm.event_registered = true From 3efc1805ca6637aca3094ed4825234ac9c58b91c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 9 Jun 2024 17:33:06 -0400 Subject: [PATCH 039/169] tools/script_manager - new user interface for darktable 4.8 wrappers added for io.popen and os.execute to handle windows special characters and spaces --- data/icons/blank20.png | Bin 0 -> 5678 bytes data/icons/path20.png | Bin 0 -> 6265 bytes tools/script_manager.lua | 620 ++++++++++++++++++++++++++------------- 3 files changed, 422 insertions(+), 198 deletions(-) create mode 100644 data/icons/blank20.png create mode 100644 data/icons/path20.png diff --git a/data/icons/blank20.png b/data/icons/blank20.png new file mode 100644 index 0000000000000000000000000000000000000000..81ed51574807dde06248d05f94fe17aad24b193a GIT binary patch literal 5678 zcmeHKd010d7Eff8SY$^*g%A|$60$=Q2*bWqK$g^iqLAdhK!7YHfh4SoUw6r(JBzi;}VN#4u5=brOB=lt%u_uZT6 z@8_w5Fhjs#Fdc6%w?OFM2)c~tYeILA?(3B>nC627daxvrDMboJ0uDC@L`vjB5DCh- z92iV?_i6B^eGjd_c^6?EGSAB^ZD`zKrG5JgamHraEfOi$Z)Mm4(f(Mti3`Jvv8*R7 zb!=SAkzSj=c0=t6k0ep&UNiIWnjD|r3%WL(@~(X{IHBJn|DIa9jYr3?T}LLTJyR15 zg(RKzFZPz=$F|L+l=iI&k{d`cZKw4InSE)0pm#ntlU4Se=DAm0ZtimlVQ7`o?McUi@UT6Mc^jjkx>L~$$P z?(Wf~U#p#GYQ9r*;KlV;kG-0D>l6lzyFjR11V8dXQF5S9;vI3v%#(c5_>_ZTd0^Vv zbcSP6MsD-jdW($0`@E#{8WSiH+f~Q&`m-WUw_lobR$o!%j=p(t9vUY;sS1LiNM}z(onbsZ!Gm;Az`--*8O6X(;k>+xd0aBzf5ZI;M&Z~+H0ID;irl=%}*)aHYhjvxmANR!jZ`5tVTzG9e z;Ao|5N{uND@cbuHj!N-4WZQA7rmA+}yET}QpJVoZ&-hJP*U214Z>oQKZj>*mEPLDQ z7-r#w{=M?1^VOAimqd+_5_fu;`?cZJ!i_}p?myB~9R4>hd&Q8??qasdnnC(}XYbf+ z98ufW@GY4@g;xAF8@FW*>jzENwp_}YStYu7_$^Y@fB!Y2?lCuVY1`?b5s#c*w|;p3 zz1b^Md0EsYT`cNgVdnm*aSJcAM4i+>Zk~AJ8_l{vlTIqs-UU|)xGBB$9WNS|++V#V zrl`R)GJzUrn_@SwE@^(%&-syI3uH|ej%x;kJy~*C`qL*V+?#y=NDZ-Jf2eZay<~T`xwisXyIyKG(zi zxy8=&%hS8Eq1z8T$=ixsJ)$4#%qq4?CcI^)yABr|`_(sD+P5g2d}M*`eZ1nr){RlJ zGn9~+Dc8it{tLFd?L*mqvR(1dfeEvl7YI8W zgFL*RA+3)`TIZ}WDKUrrEQT-dBuf{Y!u6jY-hN81pD)j{gS^K z<1%J<`efz&=CSuOqsZ{(pDv7N?LBLgMHY9=tmx9J7)u_S*3Co4)b4nsZLVhilRsXc zLGOLo(=M=lXV06ula8S<^{+M;EcWPZDp=&wwC#4mT`e+nX_os)N4o=dX!UWs_Tt9SGS?7-TrRC7pn6UX3T$S|!xN@7 z-oRk+bgrwbzqhOFhu0nSk}FQ$L-V@iZwLlV2 z`qa2+JKf?D3ckx~m_E__HcKxV{tap^p`)na7%^#dw1j$m?7l-s%d@UO7z<3)=AmNK zOv`VoONu8mkDgk4vTeA{(0W?d3Mc)s+!l7H#$}t$>4#}T8}-gsC`K1^fc<~%2to!8R{w%eC>}D z&vVbGWQHOdR);?fcG58ee$}1PY>9|AuCTsv%*$Xz9$TD?E0r#aBrP3&+Z(%arPH|S z`Csg3HX0VaTYll@O_rxZVJnRN{bnI;K%})e1rG$pmm$%IgVp=GMh?7C+(7GXdHJZP z@5*Dfyt0ro!Pf1r;Z1ILMja=|MoWHcp*?-`4)#h{Bzd%q*8*)z%(&3@BiPr6$`J^U^Qnbc;vNryybtJqT8Qb;E)Ek2iUn~ZHs~G?@+DTELICWK_QE((j4B;~ zjR9jo9;7OUyy8A{>FMq3|ItE8K?Ij4R9Qh}f2Jwna{eOgv)Gh9s&qaL1Tz1K`O~8&*hUe`@Ln-}J0Rfu}P*smO4iT^=67A4LTNa2WQa~n}$!25G910c$ zNH`pc$YFm1<;@pMn0z*LC#En9y)?dCUk9BjiV@I+TP{o&CLOD7+2!FNuE)Q^J7^Xeb{pKTh^n2c63UgCtBP zn>afh9!DV%30OSIj)0wuS%tR&6p5iqRAS<=HiS7`<*-npWFTUh$~uJrRIpGkR96wm zln6w0fgpy4QbHh=ma6hbI?N4<7gr2v$dwiUzUG6#*tyo+6o}!fx{yd!*+MMOIT17C zK|tjQvYYE-M>6>lAT+<<7t{wi_kRo)heM%Y32a+58^?sk8(?4{yOl0d}tRWg^n|0uSeRTqmU+Rou?aY zR{1TxP*DI$w1r+FVi;`UCFKRbBB%r)p_atk*IjEwT^+8En0a<&5E7YqyE)Uz9qjPM j8b`y8cBKVi5t^$Yz%m%T)O4pCqyh7G_j5b#5}x)S(L1|$ literal 0 HcmV?d00001 diff --git a/data/icons/path20.png b/data/icons/path20.png new file mode 100644 index 0000000000000000000000000000000000000000..1021d86405b81bbfa10a78e4ea7e8de8651462dd GIT binary patch literal 6265 zcmeH~c|26@`^Se0i3}x0dZrh=5n)9dy7{@2W$nK}1;UGMw4KKFI*bDsofCmVTLRapoG zB5!AF=?eZX2M=W#De&9+aZ@e?BGnh>?#*{431A#9o5lzPVEj-H00V>!8U!MII^w;x zL_=F{ny&04X=@wZKW?nOq2aAt*0YT3YnpYF!-39rM>W5-?&dmOS7yKP;!Vdhv(`}y zvpn#<$pHneJ`tCL9 zFZEGjmmT`89^8bMS;xkQE)kW&&Ii263@a;9E?zMezGTn#k(pUQDm>=-ZQ@nR4X@oZ zXXM*XM>}cx9%6N#VlV2bG~(1+Y!rME>qfeDQ?}<2g7c)cG9pIWite?%Pz_btE`ZC+K`}m%ZhqnK730>TM zI?Q(@P|$p2UHEIIvuW0~Ua*=bk052?he+K{_DfRiOI7ESP=~$r z&g5yvjz;cXs1I9fxKk=n#~?Dbs47BFWzu=saXq+>mX!zx+}9EL51AFN+PYNhJk9fx zukJRkVx6OIPU|Bb594@n{fYdmL;h+7B}Q+`f#H(vAzdR0zBdv_hCeLJ%~8V> zZ9iM?&f2%5D1QIT9?RAG5bL$tFxFCu)oH7Xyj`1KHW<+gm7L6i%rI3|>B8aVH(WIO-jl*pt@l{($a^}z z;Lv_8pf^W#+J^irVA3to6JYx;8y>k{CcS2`-hYq%yV{EF$D6yLUK8ZnMicXvmb{{g zc*Wd)3%|#ymQy>b`ew#2O0F4;XUlYSL2rpZxpW(^Jl)oDL)h%Fi7^qXapYm*;aK^% zoabc=(wB})6fm!O7iQFJss-?N#uRJ4_S|hyN?L!%Vb#QieVNyI)}Bs2;{&+f4f%lw z2+ODPJA$_^ZOC~jGIn?&)p9qAAI(dbmvi0m&s5rj74&Gq7D!oQ#X#iFCo7>@<5O2Q zBuRaZ6ty*W8Y|Csz3^c*X2~YV?~Zb7-FvC!gYT$Z^>I41`s<-7zn6+%qxRdIPX;TV zh#iM2YA3mM_#H0q`O7fr$i~H}7!3c|`x|*WIMl77x)=I(tOOM;H`_%2lzIsnTh(<=rmDWWy~I9qX=T)?jKskE z&PuW$-M;%%zzEx{a?#9+_s^F|CR)T=20mZ(q2{E|2~e3+4NopQevsVuZZt*l(T4qY zI%#%&2jVVBM~-fT*y%T^TU&3k3?GCA?1-gikZo197*rFkke`BX(is}e&t8A)_?>BJ zQq5`Gb!XS{g16}0GThpg<)+%u*m1X^?zA0kRYRQ=aV!VAR^4Tv2paV!jk>+Z6rN1( zD{%oPlwZ}AS3G#U;VRz#?@pVN+cJ;0>u$B(YQH5|O-3z0Gc>wBG9HoQP^YX+xQ>6T zpB<4S|NQj$dTl-PCjzC~+*5+LS@|;j&J6(ywsL2kV=xPfVLC@@NF#l^psV?ij{s8y+H1ZY-FZ% zI7BWd&AG{J*`$5*^<>%H-N=&}k(`osD4B@r%}75brPp^eZ}&fGzkd7z8U1y!)>{7~ zri?QOg_4^%ni8LHrEw$wIu|LMT=2yCu4>%Ax6HE@$flzNThaX@r$+VRU8j1D<>T%Z z%iRe*fcW&x)EMJ6THREIzOh=9E`S!)n_>4a8-%Sy$kIP0W=~3(yZ2!14`{n2nR8bt zy;bP@Izp&>9zClNU6pb3iyz0pWaG)c^oJCJs8`nzhK$iE7!1DZNFP$1exI`QY~p@b z8LsdIG)%ERLCtT-+R>=ZHSX-9S36e-O?$#uKP`367T!xgGrfi&DsY&(QPK0f@PW3O z^V)~z_Yf+)Zl#PO&0t}v8+`t)IZKs-C@TU zX-Cy#%T;Wui(kBGCO@ve<>MvoWd@y=2nyl2@{cNw*~=ox4uZkd*P(&W+M1NH25d*l*heSez%yE+$gVe75UDn(cG6t^s^ zcXzxHzILGSZs@@x&-|i~MxW(17VQB}4gX_(v<7}0W)>Sw)XA-}PM;;NUxEGS@Angl z*Y+lClfAC9>!tT5c~xq=!mLy!JwW+_*3E2NrNPjk^hC^Afr=k~^}tj|kgxWp@m1Fz zZJ704c5+7Z=EH|%o2n`TC#dUTipdME^vboVD|KcixGhm-fZGpmM+YK>%|w!@ zY%+ipGCAP(1OhQO6>>yK$=ML`TP_6)v9n7TwRv`e3ukka2O8Mc>3FZdQr9-8l zfIxr=n)1L=F~1CHW9R7n!$T|qox$YHd4Xd8V##OF{uS$&*u*_^>HHiB=>CKIi}m;1 z=ZrxsM@OP1n-VMz&(6{WE*_sqWm6bb;{0C#N5&W$8Y1vm8VP~J7@!d(l0F7OGs55~ zcnk?o!5jR9vSac1Bo+k_qd+*40dmNO1^|^r!XS){NFV}FAs~zl0StmdBVcg)hGd*R z4fhjb3zq>_B`NUdsKh8Lh|)I#a0Ubt9)ZSE&|oxF0>Y3+Fhl@o10zEM8K4o!ggF$I zLbPUcnIv#J8B7u#Kyg^~xel@5L^EeQ6F3%${us95EKIS=|C&1-_l_+Ln&`AAxu6SCAU^1YvBypXBfH^Ff3(=LQ1(=DR3P*}(p1PRjMaS_}LDu})|#4uJ+g`tDc^5kny2 zvEV5YjfSJXC5#eZ)!#EVM*Sa6jOPq~S_VMB`8IHQ0aq*3kLBu{W?~uti?45U@n0MP zME@D&xAgrZ*B`llOM%}q{!?9lA{5yC7?L1)FVqE5J{4q zrJ1|oz0}rw7d?UHQsd9dD!c9k_`0vT;$VJNiUiY)J9oqrhC7yPmz?%q1_jPq{liOhS5q)pEPGKbjdP_!P_dBr=6E z23K4?>ThJfH(sha9+Msxy1C1_Kc_DIfV-N=cm^obM~gJ_@IT^*eYlEg#jmmeyOynJ!{do)8eGidEWAN3)SPXYZklWq)3{VUBUHS6Vb&?OZ;D_z;!%DI>+7Hr9y>-(&z{uGP{Y+J zb=Rz&?ksH+iJE#jDe857E)$lwJN{nVxpF~>;x<-Vnx;r(zj$P`@9uLUz0+~IY0bgmF+l+%ZJpC + copyright (c) 2018, 2020, 2023, 2024 Bill Ferguson darktable is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -22,9 +22,9 @@ manage the lua scripts. On startup script_manager scans the lua scripts directory to see what scripts are present. - Scripts are sorted by 'category' based on what sub-directory they are in. With no - additional script repositories iinstalled, the categories are contrib, examples, official - and tools. When a category is selected the buttons show the script name and whether the + Scripts are sorted by 'folder' based on what sub-directory they are in. With no + additional script repositories iinstalled, the folders are contrib, examples, official + and tools. When a folder is selected the buttons show the script name and whether the script is started or stopped. The button is a toggle, so if the script is stopped click the button to start it and vice versa. @@ -56,42 +56,46 @@ local dtsys = require "lib/dtutils.system" local log = require "lib/dtutils.log" local debug = require "darktable.debug" --- set up translation +local gettext = dt.gettext -local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .."/lua/locale/") +-- Tell gettext where to find the .mo file translating messages for a particular domain +dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .. "/lua/locale/") local function _(msgid) - return gettext(msgid) + return gettext.dgettext("script_manager", msgid) end -- api check -du.check_min_api_version("5.0.0", "script_manager") +du.check_min_api_version("9.3.0", "script_manager") -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S -- - - - - - - - - - - - - - - - - - - - - - - - -- path separator -local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- command separator -local CS = dt.configuration.running_os == "windows" and "&" or ";" +local CS = dt.configuration.running_os == "windows" and "&" or ";" -local MODULE = "script_manager" +local MODULE = "script_manager" -local MIN_BUTTONS_PER_PAGE = 5 -local MAX_BUTTONS_PER_PAGE = 20 -local DEFAULT_BUTTONS_PER_PAGE = 10 +local MIN_BUTTONS_PER_PAGE = 5 +local MAX_BUTTONS_PER_PAGE = 20 +local DEFAULT_BUTTONS_PER_PAGE = 10 -local DEFAULT_LOG_LEVEL = log.error +local DEFAULT_LOG_LEVEL = log.error -local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" -local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" +local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" +local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" -local LUA_API_VER = "API-" .. dt.configuration.api_version_string +local LUA_API_VER = "API-" .. dt.configuration.api_version_string + +-- local POWER_ICON = dt.configuration.config_dir .. "/lua/data/data/icons/power.png" +local POWER_ICON = dt.configuration.config_dir .. "/lua/data/icons/path20.png" +local BLANK_ICON = dt.configuration.config_dir .. "/lua/data/icons/blank20.png" -- - - - - - - - - - - - - - - - - - - - - - - - -- P R E F E R E N C E S @@ -128,7 +132,7 @@ sm.event_registered = false -- set up tables to contain all the widgets and choices sm.widgets = {} -sm.categories = {} +sm.folders = {} -- set log level for functions @@ -137,14 +141,14 @@ sm.log_level = DEFAULT_LOG_LEVEL --[[ sm.scripts is a table of tables for containing the scripts - It is organized as into category (folder) subtables containing + It is organized as into folder (folder) subtables containing each script definition, which is a table sm.scripts- | - - category------------| + - folder------------| | - script - - category----| | + - folder----| | - script| | - script - script| @@ -152,7 +156,7 @@ sm.log_level = DEFAULT_LOG_LEVEL and a script table looks like name the name of the script file without the lua extension - path category (folder), path separator, path, name without the lua extension + path folder (folder), path separator, path, name without the lua extension doc the header comments from the script to be used as a tooltip script_name the folder, path separator, and name without the lua extension running true if running, false if not, hidden if running but the @@ -178,10 +182,7 @@ sm.page_status = {} sm.page_status.num_buttons = DEFAULT_BUTTONS_PER_PAGE sm.page_status.buttons_created = 0 sm.page_status.current_page = 0 -sm.page_status.category = "" - --- use color in the interface? -sm.use_color = false +sm.page_status.folder = "" -- installed script repositories sm.installed_repositories = { @@ -212,17 +213,24 @@ end local function pref_read(name, pref_type) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type) + local val = dt.preferences.read(MODULE, name, pref_type) + log.msg(log.debug, "read value " .. tostring(val)) + restore_log_level(old_log_level) return val end local function pref_write(name, pref_type, value) local old_log_level = set_log_level(sm.log_level) + log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name) + dt.preferences.write(MODULE, name, pref_type, value) + restore_log_level(old_log_level) end @@ -232,12 +240,15 @@ end local function get_repo_status(repo) local old_log_level = set_log_level(sm.log_level) - local p = io.popen("cd " .. repo .. CS .. "git status") + + local p = dtsys.io_popen("cd " .. repo .. CS .. "git status") + if p then local data = p:read("*a") p:close() return data end + log.msg(log.error, "unable to get status of " .. repo) restore_log_level(old_log_level) return nil @@ -245,8 +256,11 @@ end local function get_current_repo_branch(repo) local old_log_level = set_log_level(sm.log_level) + local branch = nil - local p = io.popen("cd " .. repo .. CS .. "git branch --all") + + local p = dtsys.io_popen("cd " .. repo .. CS .. "git branch --all") + if p then local data = p:read("*a") p:close() @@ -254,23 +268,29 @@ local function get_current_repo_branch(repo) for _, b in ipairs(branches) do log.msg(log.debug, "branch for testing is " .. b) branch = string.match(b, "^%* (.-)$") + if branch then log.msg(log.info, "current repo branch is " .. branch) return branch end + end end + if not branch then log.msg(log.error, "no current branch detected in repo_data") end + restore_log_level(old_log_level) return nil end local function get_repo_branches(repo) local old_log_level = set_log_level(sm.log_level) + local branches = {} - local p = io.popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") + local p = dtsys.io_popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") + if p then local data = p:read("*a") p:close() @@ -285,12 +305,14 @@ local function get_repo_branches(repo) end end end + restore_log_level(old_log_level) return branches end local function is_repo_clean(repo_data) local old_log_level = set_log_level(sm.log_level) + if string.match(repo_data, "\n%s-%a.-%a:%s-%a%g-\n") then log.msg(log.info, "repo is dirty") return false @@ -298,13 +320,17 @@ local function is_repo_clean(repo_data) log.msg(log.info, "repo is clean") return true end + restore_log_level(old_log_level) end local function checkout_repo_branch(repo, branch) local old_log_level = set_log_level(sm.log_level) + log.msg(log.info, "checkout out branch " .. branch .. " from repository " .. repo) - os.execute("cd " .. repo .. CS .. "git checkout " .. branch) + + dtsys.os_execute("cd " .. repo .. CS .. "git checkout " .. branch) + restore_log_level(old_log_level) end @@ -314,16 +340,20 @@ end local function update_combobox_choices(combobox, choice_table, selected) local old_log_level = set_log_level(sm.log_level) + local items = #combobox local choices = #choice_table + for i, name in ipairs(choice_table) do combobox[i] = name end + if choices < items then for j = items, choices + 1, -1 do combobox[j] = nil end end + if not selected then selected = 1 end @@ -334,8 +364,10 @@ end local function string_trim(str) local old_log_level = set_log_level(sm.log_level) + local result = string.gsub(str, "^%s+", "") result = string.gsub(result, "%s+$", "") + restore_log_level(old_log_level) return result end @@ -344,20 +376,78 @@ local function string_dequote(str) return string.gsub(str, "['\"]", "") end +local function string_dei18n(str) + return string.match(str, "%_%((.+)%)") +end + +local function string_chop(str) + return str:sub(1, -2) +end + ------------------ -- script handling ------------------ -local function add_script_category(category) +local function add_script_folder(folder) local old_log_level = set_log_level(sm.log_level) - if #sm.categories == 0 or not string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then - table.insert(sm.categories, category) - sm.scripts[category] = {} - log.msg(log.debug, "created category " .. category) + + if #sm.folders == 0 or not string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then + table.insert(sm.folders, folder) + sm.scripts[folder] = {} + log.msg(log.debug, "created folder " .. folder) end + restore_log_level(old_log_level) end +local function get_script_metadata(script) + local old_log_level = set_log_level(sm.log_level) + -- set_log_level(log.debug) + + log.msg(log.debug, "processing metatdata for " .. script) + + local description = nil + local metadata = nil + + f = io.open(LUA_DIR .. PS .. script .. ".lua") + if f then + -- slurp the file + local content = f:read("*all") + f:close() + -- grab the script_data.metadata table + description = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") + else + log.msg(log.error, "cant read from " .. script) + end + + if description then + metadata = "" + -- format it into a string block for display + local lines = du.split(description, "\n") + log.msg(log.debug, "got " .. #lines .. " lines") + local first = 1 + for i = 1, #lines do + log.msg(log.debug, "splitting line " .. lines[i]) + local parts = du.split(lines[i], " = ") + log.msg(log.debug, "got value " .. parts[1] .. " and data " .. parts[2]) + if string.match(parts[2], "%_%(") then + parts[2] = _(string_dequote(string_dei18n(parts[2]))) + else + parts[2] = string_dequote(parts[2]) + end + if string.match(parts[2], ",$") then + parts[2] = string_chop(parts[2]) + end + metadata = metadata .. string.format("%s%-10s\t%s", first and "" or "\n", parts[1], parts[2]) + first = nil + end + log.msg(log.debug, "script data is \n" .. metadata) + end + + restore_log_level(old_log_level) + return metadata +end + local function get_script_doc(script) local old_log_level = set_log_level(sm.log_level) local description = nil @@ -369,7 +459,7 @@ local function get_script_doc(script) -- assume that the second block comment is the documentation description = string.match(content, "%-%-%[%[.-%]%].-%-%-%[%[(.-)%]%]") else - log.msg(log.error, "Cant read from " .. script) + log.msg(log.error, "can't read from " .. script) end if description then restore_log_level(old_log_level) @@ -382,18 +472,26 @@ end local function activate(script) local old_log_level = set_log_level(sm.log_level) + local status = nil -- status of start function local err = nil -- error message returned if module doesn't start + log.msg(log.info, "activating " .. script.name) + if script.running == false then + script_manager_running_script = script.name + status, err = du.prequire(script.path) log.msg(log.debug, "prequire returned " .. tostring(status) .. " and for err " .. tostring(err)) + script_manager_running_script = nil + if status then pref_write(script.script_name, "bool", true) - log.msg(log.screen, string.format(_("loaded %s"), script.script_name)) + log.msg(log.screen, _(string.format("loaded %s", script.script_name))) script.running = true + if err ~= true then log.msg(log.debug, "got lib data") script.data = err @@ -403,17 +501,20 @@ local function activate(script) else script.data = nil end + else - log.msg(log.screen, string.format(_("%s failed to load"), script.script_name)) - log.msg(log.error, "Error loading " .. script.script_name) - log.msg(log.error, "Error message: " .. err) + log.msg(log.screen, _(string.format("%s failed to load", script.script_name))) + log.msg(log.error, "error loading " .. script.script_name) + log.msg(log.error, "error message: " .. err) end + else -- script is a lib and loaded but hidden and the user wants to reload script.data.restart() script.running = true status = true pref_write(script.script_name, "bool", true) end + restore_log_level(old_log_level) return status end @@ -428,9 +529,13 @@ local function deactivate(script) -- deactivate it.... local old_log_level = set_log_level(sm.log_level) + pref_write(script.script_name, "bool", false) + if script.data then + script.data.destroy() + if script.data.destroy_method then if string.match(script.data.destroy_method, "hide") then script.running = "hidden" @@ -442,44 +547,54 @@ local function deactivate(script) package.loaded[script.script_name] = nil script.running = false end + log.msg(log.info, "turned off " .. script.script_name) - log.msg(log.screen, string.format(_("%s stopped"), script.name)) + log.msg(log.screen, _(string.format("%s stopped", script.name))) + else script.running = false + log.msg(log.info, "setting " .. script.script_name .. " to not start") - log.msg(log.screen, string.format(_("%s will not start when darktable is restarted"), script.name)) + log.msg(log.screen, _(string.format("%s will not start when darktable is restarted", script.name))) end + restore_log_level(old_log_level) end -local function add_script_name(name, path, category) +local function add_script_name(name, path, folder) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "category is " .. category) + + log.msg(log.debug, "folder is " .. folder) log.msg(log.debug, "name is " .. name) + local script = { name = name, - path = category .. "/" .. path .. name, + path = folder .. "/" .. path .. name, running = false, - doc = get_script_doc(category .. "/" .. path .. name), - script_name = category .. "/" .. name, + doc = get_script_doc(folder .. "/" .. path .. name), + metadata = get_script_metadata(folder .. "/" .. path .. name), + script_name = folder .. "/" .. name, data = nil } - table.insert(sm.scripts[category], script) + + table.insert(sm.scripts[folder], script) + if pref_read(script.script_name, "bool") then activate(script) else pref_write(script.script_name, "bool", false) end + restore_log_level(old_log_level) end local function process_script_data(script_file) local old_log_level = set_log_level(sm.log_level) - -- the script file supplied is category/filename.filetype - -- the following pattern splits the string into category, path, name, fileename, and filetype + -- the script file supplied is folder/filename.filetype + -- the following pattern splits the string into folder, path, name, fileename, and filetype -- for example contrib/gimp.lua becomes - -- category - contrib + -- folder - contrib -- path - -- name - gimp.lua -- filename - gimp @@ -496,14 +611,14 @@ local function process_script_data(script_file) log.msg(log.info, "processing " .. script_file) -- add the script data - local category,path,name,filename,filetype = string.match(script_file, pattern) + local folder,path,name,filename,filetype = string.match(script_file, pattern) - if category and name and path then - log.msg(log.debug, "category is " .. category) + if folder and name and path then + log.msg(log.debug, "folder is " .. folder) log.msg(log.debug, "name is " .. name) - add_script_category(category) - add_script_name(name, path, category) + add_script_folder(folder) + add_script_name(name, path, folder) end restore_log_level(old_log_level) @@ -511,11 +626,10 @@ end local function ensure_lib_in_search_path(line) local old_log_level = set_log_level(sm.log_level) - set_log_level(log.debug) log.msg(log.debug, "line is " .. line) - if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua" .. PS .. "lib")) then + if string.match(line, ds.sanitize_lua(dt.configuration.config_dir .. PS .. "lua/lib")) then log.msg(log.debug, line .. " is already in search path, returning...") return end @@ -540,15 +654,20 @@ end local function scan_scripts(script_dir) local old_log_level = set_log_level(sm.log_level) + local script_count = 0 local find_cmd = "find -L " .. script_dir .. " -name \\*.lua -print | sort" + if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s \"" .. script_dir .. "\\*.lua\" | sort" end + log.msg(log.debug, "find command is " .. find_cmd) + -- scan the scripts - local output = io.popen(find_cmd) + local output = dtsys.io_popen(find_cmd) for line in output:lines() do + log.msg(log.debug, "line is " .. line) local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off local script_file = l:sub(1,-5) -- strip off .lua\n if not string.match(script_file, "script_manager") then -- let's not include ourself @@ -564,6 +683,7 @@ local function scan_scripts(script_dir) end end end + restore_log_level(old_log_level) return script_count end @@ -575,7 +695,7 @@ local function update_scripts() local git = sm.executables.git if not git then - dt.print(_("ERROR: git not found, install or specify the location of the git executable.")) + log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) return end @@ -589,7 +709,7 @@ local function update_scripts() end if result == 0 then - dt.print(_("lua scripts successfully updated")) + log.msg(log.screen, _("lua scripts successfully updated")) end restore_log_level(old_log_level) @@ -602,65 +722,85 @@ end local function update_script_update_choices() local old_log_level = set_log_level(sm.log_level) + local installs = {} local pref_string = "" + for i, repo in ipairs(sm.installed_repositories) do table.insert(installs, repo.name) pref_string = pref_string .. i .. "," .. repo.name .. "," .. repo.directory .. "," end + update_combobox_choices(sm.widgets.update_script_choices, installs, 1) + log.msg(log.debug, "repo pref string is " .. pref_string) pref_write("installed_repos", "string", pref_string) + restore_log_level(old_log_level) end local function scan_repositories() local old_log_level = set_log_level(sm.log_level) + local script_count = 0 local find_cmd = "find -L " .. LUA_DIR .. " -name \\*.git -print | sort" + if dt.configuration.running_os == "windows" then find_cmd = "dir /b/s /a:d " .. LUA_DIR .. PS .. "*.git | sort" end + log.msg(log.debug, "find command is " .. find_cmd) - local output = io.popen(find_cmd) + + local output = dtsys.io_popen(find_cmd) + for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off - local category = string.match(l, "(.-)" .. PS) -- get everything to teh first / - if category then -- if we have a category (.git doesn't) - log.msg(log.debug, "found category " .. category) - if not string.match(category, "plugins") and not string.match(category, "%.git") then -- skip plugins + local folder = string.match(l, "(.-)" .. PS) -- get everything to the first / + + if folder then -- if we have a folder (.git doesn't) + + log.msg(log.debug, "found folder " .. folder) + + if not string.match(folder, "plugins") and not string.match(folder, "%.git") then -- skip plugins + if #sm.installed_repositories == 1 then - log.msg(log.debug, "only 1 repo, adding " .. category) - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + log.msg(log.debug, "only 1 repo, adding " .. folder) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) else log.msg(log.debug, "more than 1 repo, we have to search the repos to make sure it's not there") local found = nil + for _, repo in ipairs(sm.installed_repositories) do - if string.match(repo.name, ds.sanitize_lua(category)) then + if string.match(repo.name, ds.sanitize_lua(folder)) then log.msg(log.debug, "matched " .. repo.name) found = true break end end + if not found then - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) end + end end end end + update_script_update_choices() + restore_log_level(old_log_level) end local function install_scripts() local old_log_level = set_log_level(sm.log_level) + local url = sm.widgets.script_url.text - local category = sm.widgets.new_category.text + local folder = sm.widgets.new_folder.text - if string.match(du.join(sm.categories, " "), ds.sanitize_lua(category)) then - log.msg(log.screen, string.format(_("category %s is already in use, please specify a different category name."), category)) - log.msg(log.error, "category " .. category .. " already exists, returning...") + if string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then + log.msg(log.screen, _(string.format("folder %s is already in use. Please specify a different folder name.", folder))) + log.msg(log.error, "folder " .. folder .. " already exists, returning...") restore_log_level(old_log_level) return end @@ -670,12 +810,12 @@ local function install_scripts() local git = sm.executables.git if not git then - dt.print(_("ERROR: git not found, install or specify the location of the git executable.")) + log.msg(log.screen, _("ERROR: git not found. Install or specify the location of the git executable.")) restore_log_level(old_log_level) return end - local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. category + local git_command = "cd " .. LUA_DIR .. " " .. CS .. " " .. git .. " clone " .. url .. " " .. folder log.msg(log.debug, "update git command is " .. git_command) if dt.configuration.running_os == "windows" then @@ -687,30 +827,34 @@ local function install_scripts() log.msg(log.info, "result from import is " .. result) if result == 0 then - local count = scan_scripts(LUA_DIR .. PS .. category) + local count = scan_scripts(LUA_DIR .. PS .. folder) + if count > 0 then - update_combobox_choices(sm.widgets.category_selector, sm.categories, sm.widgets.category_selector.selected) - dt.print(string.format(_("scripts successfully installed into category "), category)) - table.insert(sm.installed_repositories, {name = category, directory = LUA_DIR .. PS .. category}) + update_combobox_choices(sm.widgets.folder_selector, sm.folders, sm.widgets.folder_selector.selected) + dt.print(_("scripts successfully installed into folder ") .. folder) + table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) update_script_update_choices() - for i = 1, #sm.widgets.category_selector do - if string.match(sm.widgets.category_selector[i], ds.sanitize_lua(category)) then - log.msg(log.debug, "setting category selector to " .. i) - sm.widgets.category_selector.selected = i + + for i = 1, #sm.widgets.folder_selector do + if string.match(sm.widgets.folder_selector[i], ds.sanitize_lua(folder)) then + log.msg(log.debug, "setting folder selector to " .. i) + sm.widgets.folder_selector.selected = i break end i = i + 1 end + log.msg(log.debug, "clearing text fields") sm.widgets.script_url.text = "" - sm.widgets.new_category.text = "" + sm.widgets.new_folder.text = "" sm.widgets.main_menu.selected = 3 else - dt.print(_("no scripts found to install")) - log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to category_selector") + log.msg(log.screen, _("No scripts found to install")) + log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") end + else - dt.print(_("failed to download scripts")) + log.msg(log.screen, _("failed to download scripts")) end restore_log_level(old_log_level) @@ -719,102 +863,101 @@ end local function clear_button(number) local old_log_level = set_log_level(sm.log_level) + local button = sm.widgets.buttons[number] - button.label = "" + local label = sm.widgets.labels[number] + + button.image = BLANK_ICON button.tooltip = "" button.sensitive = false - --button.name = "" + label.label = "" + button.name = "" + restore_log_level(old_log_level) end -local function find_script(category, name) +local function find_script(folder, name) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "looking for script " .. name .. " in category " .. category) - for _, script in ipairs(sm.scripts[category]) do + + log.msg(log.debug, "looking for script " .. name .. " in folder " .. folder) + + for _, script in ipairs(sm.scripts[folder]) do if string.match(script.name, "^" .. ds.sanitize_lua(name) .. "$") then return script end end + restore_log_level(old_log_level) return nil end -local function populate_buttons(category, first, last) +local function populate_buttons(folder, first, last) local old_log_level = set_log_level(sm.log_level) - log.msg(log.debug, "category is " .. category .. " and first is " .. first .. " and last is " .. last) + + log.msg(log.debug, "folder is " .. folder .. " and first is " .. first .. " and last is " .. last) + local button_num = 1 + for i = first, last do - script = sm.scripts[category][i] - button = sm.widgets.buttons[button_num] + local script = sm.scripts[folder][i] + local button = sm.widgets.buttons[button_num] + local label = sm.widgets.labels[button_num] + if script.running == true then - if sm.use_color then - button.label = script.name - button.name = "sm_started" - else - button.label = string.format(_("%s started"), script.name) - end + button.name = "pb_on" else - if sm.use_color then - button.label = script.name - button.name = "sm_stopped" - else - button.label = string.format(_("%s stopped"), script.name) - end + button.name = "pb_off" end - button.ellipsize = "middle" + + button.image = POWER_ICON + label.label = script.name + label.name = "pb_label" + button.ellipsize = "end" button.sensitive = true - button.tooltip = script.doc + label.tooltip = script.metadata and script.metadata or script.doc + button.clicked_callback = function (this) - local script_name = nil + local cb_script = script local state = nil - if sm.use_color then - script_name = string.match(this.label, "(.+)") - else - script_name, state = string.match(this.label, "(.-) (.+)") - end - local script = find_script(sm.widgets.category_selector.value, script_name) - if script then - log.msg(log.debug, "found script " .. script.name .. " with path " .. script.path) - if script.running == true then - log.msg(log.debug, "deactivating " .. script.name .. " on " .. script.path .. " for button " .. this.label) - deactivate(script) - if sm.use_color then - this.name = "sm_stopped" - else - this.label = string.format(_("%s stopped"), script.name) - end + if cb_script then + log.msg(log.debug, "found script " .. cb_script.name .. " with path " .. cb_script.path) + if cb_script.running == true then + log.msg(log.debug, "deactivating " .. cb_script.name .. " on " .. cb_script.path) + deactivate(cb_script) + this.name = "pb_off" else - log.msg(log.debug, "activating " .. script.name .. " on " .. script.path .. " for button " .. this.label) - local result = activate(script) + log.msg(log.debug, "activating " .. cb_script.name .. " on " .. script.path) + local result = activate(cb_script) if result then - if sm.use_color then - this.name = "sm_started" - else - this.label = string.format(_("%s started"), script.name) - end + this.name = "pb_on" end end - else - log.msg(log.error, "script " .. script_name .. " not found") end end + button_num = button_num + 1 end + if button_num <= sm.page_status.num_buttons then for i = button_num, sm.page_status.num_buttons do clear_button(i) end end + restore_log_level(old_log_level) end local function paginate(direction) local old_log_level = set_log_level(sm.log_level) - local category = sm.page_status.category - log.msg(log.debug, "category is " .. category) - local num_scripts = #sm.scripts[category] + + local folder = sm.page_status.folder + log.msg(log.debug, "folder is " .. folder) + + local num_scripts = #sm.scripts[folder] log.msg(log.debug, "num_scripts is " .. num_scripts) + local max_pages = math.ceil(num_scripts / sm.page_status.num_buttons) + local cur_page = sm.page_status.current_page log.msg(log.debug, "max pages is " .. max_pages) @@ -836,7 +979,9 @@ local function paginate(direction) log.msg(log.debug, "took path 2") cur_page = 1 end + log.msg(log.debug, "cur_page is " .. cur_page .. " and max_pages is " .. max_pages) + if cur_page == max_pages and cur_page == 1 then sm.widgets.page_forward.sensitive = false sm.widgets.page_back.sensitive = false @@ -852,115 +997,167 @@ local function paginate(direction) end sm.page_status.current_page = cur_page + first = (cur_page * sm.page_status.num_buttons) - (sm.page_status.num_buttons - 1) + if first + sm.page_status.num_buttons > num_scripts then last = num_scripts else last = first + sm.page_status.num_buttons - 1 end - sm.widgets.page_status.label = string.format(_("page %d of %d"), cur_page, max_pages) - populate_buttons(category, first, last) + sm.widgets.page_status.label = _(string.format("page %d of %d", cur_page, max_pages)) + + populate_buttons(folder, first, last) + restore_log_level(old_log_level) end -local function change_category(category) +local function change_folder(folder) local old_log_level = set_log_level(sm.log_level) - if not category then - log.msg(log.debug "setting category to selector value " .. sm.widgets.category_selector.value) - sm.page_status.category = sm.widgets.category_selector.value + + if not folder then + log.msg(log.debug "setting folder to selector value " .. sm.widgets.folder_selector.value) + sm.page_status.folder = sm.widgets.folder_selector.value else - log.msg(log.debug, "setting catgory to argument " .. category) - sm.page_status.category = category + log.msg(log.debug, "setting catgory to argument " .. folder) + sm.page_status.folder = folder end paginate(2) + restore_log_level(old_log_level) end local function change_num_buttons() local old_log_level = set_log_level(sm.log_level) + cur_buttons = sm.page_status.num_buttons new_buttons = sm.widgets.num_buttons.value + pref_write("num_buttons", "integer", new_buttons) + if new_buttons < cur_buttons then + log.msg(log.debug, "took new is less than current branch") + for i = 1, cur_buttons - new_buttons do table.remove(sm.widgets.scripts) end + log.msg(log.debug, "finished removing widgets, now there are " .. #sm.widgets.buttons) elseif new_buttons > cur_buttons then + log.msg(log.debug, "took new is greater than current branch") + log.msg(log.debug, "number of scripts is " .. #sm.widgets.scripts) + log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) + log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) + log.msg(log.debug, "number of boxes is " .. #sm.widgets.boxes) + if new_buttons > sm.page_status.buttons_created then + for i = sm.page_status.buttons_created + 1, new_buttons do + log.msg(log.debug, "i is " .. i) table.insert(sm.widgets.buttons, dt.new_widget("button"){}) + log.msg(log.debug, "inserted new button") + log.msg(log.debug, "number of buttons is " .. #sm.widgets.buttons) + table.insert(sm.widgets.labels, dt.new_widget("label"){}) + log.msg(log.debug, "inserted new label") + log.msg(log.debug, "number of labels is " .. #sm.widgets.labels) + table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, + sm.widgets.buttons[i], sm.widgets.labels[i]}) + log.msg(log.debug, "inserted new box") sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end + end + log.msg(log.debug, "cur_buttons is " .. cur_buttons .. " and new_buttons is " .. new_buttons) log.msg(log.debug, #sm.widgets.buttons .. " buttons are available") + for i = cur_buttons + 1, new_buttons do log.msg(log.debug, "inserting button " .. i .. " into scripts widget") - table.insert(sm.widgets.scripts, sm.widgets.buttons[i]) + table.insert(sm.widgets.scripts, sm.widgets.boxes[i]) end + log.msg(log.debug, "finished adding widgets, now there are " .. #sm.widgets.buttons) else -- no change log.msg(log.debug, "no change, just returning") return end + sm.page_status.num_buttons = new_buttons log.msg(log.debug, "num_buttons set to " .. sm.page_status.num_buttons) paginate(2) -- force the buttons to repopulate sm.widgets.main_menu.selected = 3 -- jump back to start/stop scripts + restore_log_level(old_log_level) end local function load_preferences() local old_log_level = set_log_level(sm.log_level) + -- load the prefs and update settings -- update_script_choices + local pref_string = pref_read("installed_repos", "string") local entries = du.split(pref_string, ",") + while #entries > 2 do local num = table.remove(entries, 1) local name = table.remove(entries, 1) local directory = table.remove(entries, 1) + if not string.match(sm.installed_repositories[1].name, "^" .. ds.sanitize_lua(name) .. "$") then table.insert(sm.installed_repositories, {name = name, directory = directory}) end + end + update_script_update_choices() log.msg(log.debug, "updated installed scripts") - -- category selector - local val = pref_read("category_selector", "integer") + + -- folder selector + local val = pref_read("folder_selector", "integer") + if val == 0 then val = 1 end - sm.widgets.category_selector.selected = val - sm.page_status.category = sm.widgets.category_selector.value - log.msg(log.debug, "updated category selector and set it to " .. sm.widgets.category_selector.value) + + sm.widgets.folder_selector.selected = val + sm.page_status.folder = sm.widgets.folder_selector.value + log.msg(log.debug, "updated folder selector and set it to " .. sm.widgets.folder_selector.value) + -- num_buttons local val = pref_read("num_buttons", "integer") + if val == 0 then val = DEFAULT_BUTTONS_PER_PAGE end + sm.widgets.num_buttons.value = val log.msg(log.debug, "set page buttons to " .. val) + change_num_buttons() log.msg(log.debug, "paginated") + -- main menu local val = pref_read("main_menu_action", "integer") log.msg(log.debug, "read " .. val .. " for main menu") + if val == 0 then val = 3 end + sm.widgets.main_menu.selected = val log.msg(log.debug, "set main menu to val " .. val .. " which is " .. sm.widgets.main_menu.value) log.msg(log.debug, "set main menu to " .. sm.widgets.main_menu.value) + restore_log_level(old_log_level) end local function install_module() local old_log_level = set_log_level(sm.log_level) + if not sm.module_installed then dt.register_lib( "script_manager", -- Module name @@ -974,22 +1171,13 @@ local function install_module() ) sm.module_installed = true end + sm.run = true sm.use_color = pref_read("use_color", "bool") log.msg(log.debug, "set run to true, loading preferences") load_preferences() scan_repositories() - --[[dt.print_log("\n\nsetting sm visible false\n\n") - dt.gui.libs["script_manager"].visible = false - dt.control.sleep(5000) - dt.print_log("setting sm visible true") - dt.gui.libs["script_manager"].visible = true - --[[dt.control.sleep(5000) - dt.print_log("setting sm expanded false") - dt.gui.libs["script_manager"].expanded = false - dt.control.sleep(5000) - dt.print_log("setting sm expanded true") - dt.gui.libs["script_manager"].expanded = true]] + restore_log_level(old_log_level) end @@ -1008,22 +1196,28 @@ if check_for_updates then local repo = LUA_DIR if current_branch then + if sm.executables.git and clean and + (current_branch == "master" or string.match(current_branch, "^API%-")) then -- only make changes to clean branches local branches = get_repo_branches(LUA_DIR) + if current_branch ~= LUA_API_VER and current_branch ~= "master" then -- probably upgraded from an earlier api version so get back to master -- to use the latest version of script_manager to get the proper API checkout_repo_branch(repo, "master") - log.msg(log.screen, "lua API version reset, please restart darktable") + log.msg(log.screen, _("lua API version reset, please restart darktable")) + elseif LUA_API_VER == current_branch then -- do nothing, we are fine log.msg(log.debug, "took equal branch, doing nothing") + elseif string.match(LUA_API_VER, "dev") then -- we are on a dev API version, so checkout the dev -- api version or checkout/stay on master log.msg(log.debug, "took the dev branch") local match = false + for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) if LUA_API_VER == branch then @@ -1032,6 +1226,7 @@ if check_for_updates then checkout_repo_branch(repo, branch) end end + if not match then if current_branch == "master" then log.msg(log.info, "staying on master, no dev branch yet") @@ -1040,25 +1235,33 @@ if check_for_updates then checkout_repo_branch(repo, "master") end end + elseif #branches > 0 and LUA_API_VER > branches[#branches] then log.msg(log.info, "no newer branches, staying on master") -- stay on master + else -- checkout the appropriate branch for API version if it exists log.msg(log.info, "checking out the appropriate API branch") + local match = false + for _, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) + if LUA_API_VER == branch then match = true log.msg(log.info, "checking out repo branch " .. branch) checkout_repo_branch(repo, branch) log.msg(log.screen, "you must restart darktable to use the correct version of the lua") end + end + if not match then log.msg(log.warn, "no matching branch found for " .. LUA_API_VER) end + end end end @@ -1100,18 +1303,18 @@ sm.widgets.script_url = dt.new_widget("entry"){ tooltip = _("enter the URL of the git repository containing the scripts you wish to add") } -sm.widgets.new_category = dt.new_widget("entry"){ +sm.widgets.new_folder = dt.new_widget("entry"){ text = "", - placeholder = _("name of new category"), - tooltip = _("enter a category name for the additional scripts") + placeholder = _("name of new folder"), + tooltip = _("enter a folder name for the additional scripts") } sm.widgets.add_scripts = dt.new_widget("box"){ orientation = vertical, dt.new_widget("label"){label = _("URL to download additional scripts from")}, sm.widgets.script_url, - dt.new_widget("label"){label = _("new category to place scripts in")}, - sm.widgets.new_category, + dt.new_widget("label"){label = _("new folder to place scripts in")}, + sm.widgets.new_folder, dt.new_widget("button"){ label = _("install additional scripts"), clicked_callback = function(this) @@ -1126,6 +1329,8 @@ sm.widgets.allow_disable = dt.new_widget("check_button"){ clicked_callback = function(this) if this.value == true then sm.widgets.disable_scripts.sensitive = true + else + sm.widgets.disable_scripts.sensitive = false end end, } @@ -1137,47 +1342,69 @@ sm.widgets.disable_scripts = dt.new_widget("button"){ local LUARC = dt.configuration.config_dir .. PS .. "luarc" df.file_move(LUARC, LUARC .. ".disabled") log.msg(log.info, "lua scripts disabled") - dt.print(_("lua scripts will not run the next time darktable is started")) + log.msg(log.screen, _("lua scripts will not run the next time darktable is started")) end } sm.widgets.install_update = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = _("update scripts")}, + dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("update scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.update_script_choices, sm.widgets.update, - dt.new_widget("section_label"){label = _("add more scripts")}, + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("add more scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.add_scripts, - dt.new_widget("section_label"){label = _("disable scripts")}, + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, + dt.new_widget("label"){label = _("disable scripts")}, + dt.new_widget("label"){label = " "}, sm.widgets.allow_disable, - sm.widgets.disable_scripts + sm.widgets.disable_scripts, + dt.new_widget("label"){label = " "}, } -- manage the scripts -sm.widgets.category_selector = dt.new_widget("combobox"){ - label = _("category"), - tooltip = _("select the script category"), +sm.widgets.folder_selector = dt.new_widget("combobox"){ + label = _("folder"), + tooltip = _( "select the script folder"), selected = 1, changed_callback = function(self) if sm.run then - pref_write("category_selector", "integer", self.selected) - change_category(self.value) + pref_write("folder_selector", "integer", self.selected) + change_folder(self.value) end end, - table.unpack(sm.categories), + table.unpack(sm.folders), } +-- a script "button" consists of: +-- a button to start and stop the script +-- a label that contains the name of the script +-- a horizontal box that contains the button and the label + sm.widgets.buttons ={} +sm.widgets.labels = {} +sm.widgets.boxes = {} + for i =1, DEFAULT_BUTTONS_PER_PAGE do table.insert(sm.widgets.buttons, dt.new_widget("button"){}) - sm.page_status.buttons_create = sm.page_status.buttons_created + 1 + table.insert(sm.widgets.labels, dt.new_widget("label"){}) + table.insert(sm.widgets.boxes, dt.new_widget("box"){ orientation = "horizontal", expand = false, fill = false, + sm.widgets.buttons[i], sm.widgets.labels[i]}) + sm.page_status.buttons_created = sm.page_status.buttons_created + 1 end local page_back = "<" local page_forward = ">" sm.widgets.page_status = dt.new_widget("label"){label = _("page:")} + sm.widgets.page_back = dt.new_widget("button"){ label = page_back, clicked_callback = function(this) @@ -1205,10 +1432,12 @@ sm.widgets.page_control = dt.new_widget("box"){ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, + dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("scripts")}, - sm.widgets.category_selector, + sm.widgets.folder_selector, sm.widgets.page_control, - table.unpack(sm.widgets.buttons) + table.unpack(sm.widgets.boxes), } -- configure options @@ -1234,21 +1463,16 @@ sm.widgets.change_buttons = dt.new_widget("button"){ sm.widgets.configure = dt.new_widget("box"){ orientation = "vertical", + dt.new_widget("section_label"){label = " "}, + dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("configuration")}, + dt.new_widget("label"){label = " "}, sm.widgets.num_buttons, + dt.new_widget("label"){label = " "}, sm.widgets.change_buttons, + dt.new_widget("label"){label = " "}, } -sm.widgets.color = dt.new_widget("check_button"){ - label = _("use color interface?"), - value = pref_read("use_color", "bool"), - clicked_callback = function(this) - pref_write("use_color", "bool", this.value) - sm.use_color = this.value - end -} -table.insert(sm.widgets.configure, sm.widgets.color) - -- stack for the options sm.widgets.main_stack = dt.new_widget("stack"){ @@ -1294,7 +1518,7 @@ else function(event, old_view, new_view) if new_view.name == "lighttable" and old_view.name == "darkroom" then install_module() - end + end end ) sm.event_registered = true From ff20098175d99588ee414d2f38fbf4fbb08f5ce3 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 14 Jun 2024 12:33:35 -0400 Subject: [PATCH 040/169] lib/dtuils/system - removed ds.sanitize wrapped around non windows command. --- lib/dtutils/system.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index e8ce9af0..894ff6ea 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -53,7 +53,7 @@ function dtutils_system.external_command(command) if dt.configuration.running_os == "windows" then result = dtutils_system.windows_command(ds.sanitize(command)) else - result = dt.control.execute(ds.sanitize(command)) + result = dt.control.execute(command) end return result From 88e4ac1a64fdb974763a24522490847d268dd516 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 18 Jun 2024 00:29:27 -0400 Subject: [PATCH 041/169] tools/script_manager - changed API check from hard (crash) to soft which allows script_manager to fix tthe problem by checking out the correct version of the scripts for the API version. --- tools/script_manager.lua | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 955f4839..40a69628 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -68,12 +68,15 @@ end -- api check -du.check_min_api_version("9.3.0", "script_manager") +-- du.check_min_api_version("9.3.0", "script_manager") -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S -- - - - - - - - - - - - - - - - - - - - - - - - +-- script_manager required API version +local SM_API_VER_REQD = "9.3.0" + -- path separator local PS = dt.configuration.running_os == "windows" and "\\" or "/" @@ -1189,7 +1192,7 @@ end script_manager_running_script = "script_manager" -if check_for_updates then +if check_for_updates or SM_API_VER_REQD > dt.configuration.api_version_string then local repo_data = get_repo_status(LUA_DIR) local current_branch = get_current_repo_branch(LUA_DIR) local clean = is_repo_clean(repo_data) @@ -1246,14 +1249,15 @@ if check_for_updates then local match = false - for _, branch in ipairs(branches) do + for _x, branch in ipairs(branches) do log.msg(log.debug, "checking branch " .. branch .. " against API " .. LUA_API_VER) if LUA_API_VER == branch then match = true log.msg(log.info, "checking out repo branch " .. branch) checkout_repo_branch(repo, branch) - log.msg(log.screen, "you must restart darktable to use the correct version of the lua") + log.msg(log.screen, _("you must restart darktable to use the correct version of the lua scripts")) + return end end From c95f8a23b2e371c69727ca2dc9a08ffa8b6919aa Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 18 Jun 2024 00:30:34 -0400 Subject: [PATCH 042/169] lib/dtutils/log - fix screen print check --- lib/dtutils/log.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/log.lua b/lib/dtutils/log.lua index 6910c8eb..28dce69f 100644 --- a/lib/dtutils/log.lua +++ b/lib/dtutils/log.lua @@ -199,7 +199,7 @@ function dtutils_log.msg(level, ...) table.remove(args, 1) end local log_msg = level.label - if level.engine ~= dt_screen and call_level ~= 0 then + if level.engine ~= dt_print and call_level ~= 0 then log_msg = log_msg .. dtutils_log.caller(call_level, level.caller_info) .. " " elseif log_msg:len() > 2 then log_msg = log_msg .. " " From 94f41db6fceca0246c92ad5757782a5567d2bb19 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 19 Jun 2024 11:41:54 -0400 Subject: [PATCH 043/169] contrib/rename_images - make sure the renamed image, or first renamed image of a group is visible after renaming. --- contrib/rename_images.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/rename_images.lua b/contrib/rename_images.lua index d3ed9824..769adfac 100644 --- a/contrib/rename_images.lua +++ b/contrib/rename_images.lua @@ -214,6 +214,7 @@ end local function do_rename(images) if #images > 0 then + local first_image = images[1] local pattern = rename.widgets.pattern.text dt.preferences.write(MODULE_NAME, "pattern", "string", pattern) dt.print_log("pattern is " .. pattern) @@ -256,6 +257,7 @@ local function do_rename(images) stop_job(job) local collect_rules = dt.gui.libs.collect.filter() dt.gui.libs.collect.filter(collect_rules) + dt.gui.views.lighttable.set_image_visible(first_image) dt.print(string.format(_("renamed %d images"), #images)) else -- pattern length dt.print_error("no pattern supplied, returning...") From f8c076f3082c6305db28aea9fd32b8a7ac4fded6 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 19 Jun 2024 20:50:54 -0400 Subject: [PATCH 044/169] contrib/fujifilm_dynamic_range - added check for duplicates after an image is processed. If duplicates are found the exposure bias is applied to them. --- contrib/fujifilm_dynamic_range.lua | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/contrib/fujifilm_dynamic_range.lua b/contrib/fujifilm_dynamic_range.lua index 1034411d..ad7b2bb5 100644 --- a/contrib/fujifilm_dynamic_range.lua +++ b/contrib/fujifilm_dynamic_range.lua @@ -126,6 +126,17 @@ local function detect_dynamic_range(event, image) -- note that scene-referred workflow exposure preset also pushes exposure up by 0.5 EV image.exif_exposure_bias = image.exif_exposure_bias + tonumber(raf_result) dt.print_log("[fujifilm_dynamic_range] raw exposure bias " .. tostring(raf_result)) + -- handle any duplicates + if #image:get_group_members() > 1 then + local basename = df.get_basename(image.filename) + local grouped_images = image:get_group_members() + for _, img in ipairs(grouped_images) do + if string.match(img.filename, basename) and img.duplicate_index > 0 then + -- its a duplicate + img.exif_exposure_bias = img.exif_exposure_bias + tonumber(raf_result) + end + end + end end local function destroy() From 3bb4e9c59a422f17eb1850dff4d5bc0c2075fcef Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 20 Jun 2024 17:46:54 -0400 Subject: [PATCH 045/169] contrib/auto_snapshot - take a snapshot automatically when opening an image in darkroom --- contrib/auto_snapshot.lua | 123 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 contrib/auto_snapshot.lua diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua new file mode 100644 index 00000000..e7d39867 --- /dev/null +++ b/contrib/auto_snapshot.lua @@ -0,0 +1,123 @@ +--[[ + + auto_snapshot.lua - automatically take a snapshot when an image is loaded in darkroom + + Copyright (C) 2024 Bill Ferguson . + + 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 . +]] +--[[ + auto_snapshot - + + automatically take a snapshot when an image is loaded in darkroom + + ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT + None + + USAGE + * start the script from script_manager + * open an image in darkroom + + BUGS, COMMENTS, SUGGESTIONS + Bill Ferguson + + CHANGES +]] + +local dt = require "darktable" +local du = require "lib/dtutils" + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- C O N S T A N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local MODULE = "auto_snapshot" +local DEFAULT_LOG_LEVEL = log.error + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A P I C H E C K +-- - - - - - - - - - - - - - - - - - - - - - - - + +du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- I 1 8 N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local gettext = dt.gettext.gettext + +dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/") + +local function _(msgid) + return gettext(MODULE, msgid) +end + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- S C R I P T M A N A G E R I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local script_data = {} + +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 +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 + +script_data.metadata = { + name = "auto_snapshot", -- name of script + purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script + author = "Bill Ferguson ", -- your name and optionally e-mail address + help = "" -- URL to help/documentation +} + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- L O G L E V E L +-- - - - - - - - - - - - - - - - - - - - - - - - + +log.log_level(DEFAULT_LOG_LEVEL) + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- N A M E S P A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +local auto_snapshot = {} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- D A R K T A B L E I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - + +local function destroy() + dt.destroy_event(MODULE, "darkroom-image-loaded") +end + +script_data.destroy = destroy + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- E V E N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dt.register_event(MODULE, "darkroom-image-loaded", + function(event, clean, image) + if clean then + dt.gui.libs.snapshots.take_snapshot() + end + + end +) + + +return script_data \ No newline at end of file From ba94914c0e256aae2d09f2c6fc8877da1c70edfb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 20 Jun 2024 18:09:10 -0400 Subject: [PATCH 046/169] contrib/auto_snapshot - added Lua option to create always create a snapshot when opening an image in darkroom, even if it is altered --- contrib/auto_snapshot.lua | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua index e7d39867..a5eca61a 100644 --- a/contrib/auto_snapshot.lua +++ b/contrib/auto_snapshot.lua @@ -37,6 +37,7 @@ local dt = require "darktable" local du = require "lib/dtutils" +local log = require "lib/dtutils.log" -- - - - - - - - - - - - - - - - - - - - - - - - -- C O N S T A N T S @@ -96,6 +97,13 @@ log.log_level(DEFAULT_LOG_LEVEL) local auto_snapshot = {} +-- - - - - - - - - - - - - - - - - - - - - - - - +-- P R E F E R E N C E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dt.preferences.register(MODULE, "always_create_snapshot", "bool", "always automatically create_snapshot", + "auto_snapshot - create a snapshot even if the image is altered", false) + -- - - - - - - - - - - - - - - - - - - - - - - - -- D A R K T A B L E I N T E G R A T I O N -- - - - - - - - - - - - - - - - - - - - - - - - @@ -112,7 +120,10 @@ script_data.destroy = destroy dt.register_event(MODULE, "darkroom-image-loaded", function(event, clean, image) - if clean then + local always = dt.preferences.read(MODULE, "always_create_snapshot", "bool") + if clean and always then + dt.gui.libs.snapshots.take_snapshot() + elseif clean and not image.is_altered then dt.gui.libs.snapshots.take_snapshot() end From 316a89d6a83fc11ddc25c1868c11608c27fd0ec7 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 30 Jun 2024 12:44:02 -0400 Subject: [PATCH 047/169] contrib/auto_snapshot - fixed email address contrib/dbmain --- contrib/auto_snapshot.lua | 6 +++--- contrib/dbmaint.lua | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua index a5eca61a..f42631f4 100644 --- a/contrib/auto_snapshot.lua +++ b/contrib/auto_snapshot.lua @@ -2,7 +2,7 @@ auto_snapshot.lua - automatically take a snapshot when an image is loaded in darkroom - Copyright (C) 2024 Bill Ferguson . + Copyright (C) 2024 Bill Ferguson . 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 @@ -30,7 +30,7 @@ * open an image in darkroom BUGS, COMMENTS, SUGGESTIONS - Bill Ferguson + Bill Ferguson CHANGES ]] @@ -80,7 +80,7 @@ script_data.show = nil -- only required for libs since the destroy_ script_data.metadata = { name = "auto_snapshot", -- name of script purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script - author = "Bill Ferguson ", -- your name and optionally e-mail address + author = "Bill Ferguson ", -- your name and optionally e-mail address help = "" -- URL to help/documentation } diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua index 7f2f21e0..70454ff6 100644 --- a/contrib/dbmaint.lua +++ b/contrib/dbmaint.lua @@ -2,7 +2,7 @@ dbmaint.lua - perform database maintenance - Copyright (C) 2024 Bill Ferguson . + Copyright (C) 2024 Bill Ferguson . 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 @@ -31,7 +31,7 @@ * look at the results and choose to delete or not BUGS, COMMENTS, SUGGESTIONS - Bill Ferguson + Bill Ferguson CHANGES ]] @@ -84,7 +84,7 @@ script_data.show = nil -- only required for libs since the destroy_ script_data.metadata = { name = "dbmaint", purpose = _("perform database maintenance"), - author = "Bill Ferguson ", + author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/" } From 36023d8d4f5e83bde5d96b249dca6ccaf81e26f0 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 8 Jul 2024 11:43:42 -0400 Subject: [PATCH 048/169] lib/dtutils/system - fixed typo in quote_windows_command call --- lib/dtutils/system.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 894ff6ea..5326018b 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -91,7 +91,7 @@ function dtutils_system.windows_command(command) if file then dt.print_log("opened file") command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell - command = quote_windows-command(command) + command = quote_windows_command(command) file:write(command) file:close() From e8a99c83968c49ec8d849c2d074a015e2ddaa893 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 10 Jul 2024 15:17:35 -0400 Subject: [PATCH 049/169] contrib/RL_out_sharp - add function to preserve the exported images metadata in the sharpened image using exiftool --- contrib/RL_out_sharp.lua | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index aa64b3f8..9e0a1fb0 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -99,7 +99,18 @@ if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then dt.preferences.write(MODULE_NAME, "iterations", "string", "10") dt.preferences.write(MODULE_NAME, "jpg_quality", "string", "95") dt.preferences.write(MODULE_NAME, "initialized", "bool", true) - end +end + +-- preserve original image metadata in the output image ----------------------- +local function preserve_metadata(original, sharpened) + local exiftool = df.check_if_bin_exists("exiftool") + + if exiftool then + dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened) + else + dt.print_log(MODULE .. " exiftool not found, metadata not preserved") + end +end -- setup export --------------------------------------------------------------- @@ -163,7 +174,10 @@ local function export2RL(storage, image_table, extra_data) if result ~= 0 then dt.print(_("sharpening error")) return - end + end + + -- copy metadata from input_file to output_file + preserve_metadata(input_file, output_file) -- delete temp image os.remove(temp_name) From 9f423eeef39db6e26a0123179377648b34e3e2fa Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 13 Jul 2024 12:46:34 -0400 Subject: [PATCH 050/169] lib/dtutils/file - reverted io_popen and os_execute wrappers as lib/dtutils/system they don't work in this situation (no control over the input strings) --- lib/dtutils/file.lua | 20 +++++++------- lib/dtutils/system.lua | 61 ++++-------------------------------------- 2 files changed, 15 insertions(+), 66 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index b8717116..cd898e71 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -42,7 +42,7 @@ end local function _win_os_execute(cmd) local result = nil - local p = dsys.io_popen(cmd) + local p = io.popen(cmd) local output = p:read("*a") p:close() if string.match(output, "true") then @@ -94,7 +94,7 @@ dtutils_file.libdoc.functions["test_file"] = { function dtutils_file.test_file(path, test) local cmd = "test -" - local engine = dsys.os_execute + local engine = os.execute local cmdstring = "" if dt.configuration.running_os == "windows" then @@ -167,7 +167,7 @@ local function _search_for_bin_windows(bin) for _,arg in ipairs(args) do local cmd = "where " .. arg .. " " .. ds.sanitize(bin) - local p = dsys.io_popen(cmd) + local p = io.popen(cmd) local output = p:read("*a") p:close() local lines = du.split(output, "\n") @@ -191,7 +191,7 @@ end local function _search_for_bin_nix(bin) local result = false - local p = dsys.io_popen("command -v " .. bin) + local p = io.popen("command -v " .. bin) local output = p:read("*a") p:close() if string.len(output) > 0 then @@ -220,7 +220,7 @@ local function _search_for_bin_macos(bin) search_start = "/Applications/" .. bin .. ".app" end - local p = dsys.io_popen("find " .. search_start .. " -type f -name " .. bin .. " -print") + local p = io.popen("find " .. search_start .. " -type f -name " .. bin .. " -print") local output = p:read("*a") p:close() local lines = du.split(output, "\n") @@ -445,7 +445,7 @@ function dtutils_file.check_if_file_exists(filepath) local result = false if (dt.configuration.running_os == 'windows') then filepath = string.gsub(filepath, '[\\/]+', '\\') - local p = dsys.io_popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')") + local p = io.popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')") local ans = p:read("*all") p:close() if string.match(ans, "yes") then @@ -456,7 +456,7 @@ function dtutils_file.check_if_file_exists(filepath) -- result = false -- end elseif (dt.configuration.running_os == "linux") then - result = dsys.os_execute('test -e ' .. dtutils_file.sanitize_filename(filepath)) + result = os.execute('test -e ' .. dtutils_file.sanitize_filename(filepath)) if not result then result = false end @@ -522,9 +522,9 @@ function dtutils_file.file_copy(fromFile, toFile) local result = nil -- if cp exists, use it if dt.configuration.running_os == "windows" then - result = dsys.os_execute('copy "' .. fromFile .. '" "' .. toFile .. '"') + result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') elseif dtutils_file.check_if_bin_exists("cp") then - result = dsys.os_execute("cp '" .. fromFile .. "' '" .. toFile .. "'") + result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") end -- if cp was not present, or if cp failed, then a pure lua solution @@ -575,7 +575,7 @@ function dtutils_file.file_move(fromFile, toFile) if not success then -- an error occurred, so let's try using the operating system function if dtutils_file.check_if_bin_exists("mv") then - success = dsys.os_execute("mv '" .. fromFile .. "' '" .. toFile .. "'") + success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'") end -- if the mv didn't exist or succeed, then... if not success then diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 5326018b..2ec2c313 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -51,7 +51,7 @@ function dtutils_system.external_command(command) local result = nil if dt.configuration.running_os == "windows" then - result = dtutils_system.windows_command(ds.sanitize(command)) + result = dtutils_system.windows_command(command) else result = dt.control.execute(command) end @@ -78,20 +78,20 @@ dtutils_system.libdoc.functions["windows_command"] = { Copyright = [[]], } -local function quote_windows_command(command) +local function _quote_windows_command(command) return "\"" .. command .. "\"" end function dtutils_system.windows_command(command) local result = 1 - local fname = ds.sanitize(dt.configuration.tmp_dir .. "/run_command.bat") + local fname = dt.configuration.tmp_dir .. "/run_command.bat" local file = io.open(fname, "w") if file then dt.print_log("opened file") command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell - command = quote_windows_command(command) + command = _quote_windows_command(command) file:write(command) file:close() @@ -124,6 +124,7 @@ dtutils_system.libdoc.functions["launch_default_app"] = { License = [[]], Copyright = [[]], } + function dtutils_system.launch_default_app(path) local open_cmd = "xdg-open " if (dt.configuration.running_os == "windows") then @@ -134,56 +135,4 @@ function dtutils_system.launch_default_app(path) return dtutils_system.external_command(open_cmd .. path) end - -dtutils_system.libdoc.functions["os_execute"] = { - Name = [[os_execute]], - Synopsis = [[wrapper around the lua os.execute function]], - Usage = [[local dsys = require "lib/dtutils.file" - - result = dsys.os_execute(cmd) - cmd - string - a command to execute on the operating system]], - Description = [[os_execute wraps the lua os.execute system call to provide - correct sanitization of windows commands]], - Return_Value = [[see the lua os.execute documentation]], - Limitations = [[]], - Example = [[]], - See_Also = [[]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} - -function dtutils_system.os_execute(cmd) - if dt.configuration.running_os == "windows" then - cmd = quote_windows_command(cmd) - end - return os.execute(cmd) -end - -dtutils_system.libdoc.functions["io_popen"] = { - Name = [[io_popen]], - Synopsis = [[wrapper around the lua io.popen function]], - Usage = [[local dsys = require "lib/dtutils.file" - - result = dsys.io_popen(cmd) - cmd - string - a command to execute and attach to]], - Description = [[io_popen wraps the lua io.popen system call to provide - correct sanitization of windows commands]], - Return_Value = [[see the lua io.popen documentation]], - Limitations = [[]], - Example = [[]], - See_Also = [[]], - Reference = [[]], - License = [[]], - Copyright = [[]], -} - -function dtutils_system.io_popen(cmd) - if dt.configuration.running_os == "windows" then - cmd = quote_windows_command(cmd) - end - return io.popen(cmd) -end - - return dtutils_system From 1b272be5f560ba9d4239d2f47c687a630f6453df Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 13 Jul 2024 12:48:37 -0400 Subject: [PATCH 051/169] various scripts - reverted the calls to io_popen and os_execute back to the respective lua calls. --- contrib/autostyle.lua | 4 ++-- contrib/color_profile_manager.lua | 2 +- contrib/fujifilm_dynamic_range.lua | 2 +- contrib/fujifilm_ratings.lua | 4 ++-- contrib/geoJSON_export.lua | 2 +- contrib/geoToolbox.lua | 2 +- contrib/image_stack.lua | 2 +- contrib/image_time.lua | 6 +++--- contrib/kml_export.lua | 2 +- official/enfuse.lua | 2 +- tools/executable_manager.lua | 4 ++-- tools/get_lib_manpages.lua | 4 ++-- tools/get_libdoc.lua | 2 +- tools/script_manager.lua | 14 +++++++------- 14 files changed, 26 insertions(+), 26 deletions(-) diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 319b8f3a..336e6f78 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -69,7 +69,7 @@ script_data.show = nil -- only required for libs since the destroy_method only h -- run command and retrieve stdout local function get_stdout(cmd) -- Open the command, for reading - local fd = assert(syslib.io_popen(cmd, 'r')) + local fd = assert(io.popen(cmd, 'r')) darktable.control.read(fd) -- slurp the whole file local data = assert(fd:read('*a')) @@ -188,4 +188,4 @@ darktable.register_event("autostyle", "post-import-image", script_data.destroy = destroy -return script_data \ No newline at end of file +return script_data diff --git a/contrib/color_profile_manager.lua b/contrib/color_profile_manager.lua index d87d9a6a..52fc38ce 100644 --- a/contrib/color_profile_manager.lua +++ b/contrib/color_profile_manager.lua @@ -107,7 +107,7 @@ end local function list_profiles(dir) local files = {} - local p = dtsys.io_popen(DIR_CMD .. " " .. dir) + local p = io.popen(DIR_CMD .. " " .. dir) if p then for line in p:lines() do table.insert(files, line) diff --git a/contrib/fujifilm_dynamic_range.lua b/contrib/fujifilm_dynamic_range.lua index ad7b2bb5..6116d352 100644 --- a/contrib/fujifilm_dynamic_range.lua +++ b/contrib/fujifilm_dynamic_range.lua @@ -106,7 +106,7 @@ local function detect_dynamic_range(event, image) -- without -n flag, exiftool will round to the nearest tenth command = command .. " -RawExposureBias -n -t " .. RAF_filename dt.print_log(command) - output = dtsys.io_popen(command) + output = io.popen(command) local raf_result = output:read("*all") output:close() if #raf_result == 0 then diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index b2f11fe3..049e3c20 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -65,7 +65,7 @@ local function detect_rating(event, image) local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG") local command = "exiftool -Rating " .. JPEG_filename dt.print_log(command) - local output = dtsys.io_popen(command) + local output = io.popen(command) local jpeg_result = output:read("*all") output:close() if string.len(jpeg_result) > 0 then @@ -76,7 +76,7 @@ local function detect_rating(event, image) end command = "exiftool -Rating " .. RAF_filename dt.print_log(command) - output = dtsys.io_popen(command) + output = io.popen(command) local raf_result = output:read("*all") output:close() if string.len(raf_result) > 0 then diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 406c37c2..2ff30807 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -331,7 +331,7 @@ dt.preferences.register("geoJSON_export", _("opens the geoJSON file after the export with the standard program for geoJSON files"), false ) -local handle = dtsys.io_popen("xdg-user-dir DESKTOP") +local handle = io.popen("xdg-user-dir DESKTOP") local result = handle:read() handle:close() if (result == nil) then diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index d654eeba..68fdc3c6 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -412,7 +412,7 @@ local function reverse_geocode() -- jq could be replaced with a Lua JSON parser startCommand = string.format("curl --silent \"https://api.mapbox.com/geocoding/v5/mapbox.places/%s,%s.json?types=%s&access_token=%s\" | jq '.features | .[0] | '.text''", lon1, lat1, types, tokan) - local handle = dtsys.io_popen(startCommand) + local handle = io.popen(startCommand) local result = trim12(handle:read("*a")) handle:close() diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index ba0909b1..c63ff166 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -348,7 +348,7 @@ local function list_files(search_string) search_string = string.gsub(search_string, "/", "\\\\") end - local f = dtsys.io_popen(ls .. search_string) + local f = io.popen(ls .. search_string) if f then local found_file = f:read() while found_file do diff --git a/contrib/image_time.lua b/contrib/image_time.lua index fa00b576..a35f751f 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -226,7 +226,7 @@ local function get_image_taken_time(image) local exiv2 = df.check_if_bin_exists("exiv2") if exiv2 then - p = dtsys.io_popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename) + p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, "Exif.Image.DateTime") then @@ -244,7 +244,7 @@ end local function _get_windows_image_file_creation_time(image) local datetime = nil - local p = dtsys.io_popen("dir " .. image.path .. PS .. image.filename) + local p = io.popen("dir " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, ds.sanitize_lua(image.filename)) then @@ -265,7 +265,7 @@ end local function _get_nix_image_file_creation_time(image) local datetime = nil - local p = dtsys.io_popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename) + local p = io.popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename) if p then for line in p:lines() do if string.match(line, ds.sanitize_lua(image.filename)) then diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index ec7218ca..ec1aa04a 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -343,7 +343,7 @@ if dt.configuration.running_os == "windows" then elseif dt.configuration.running_os == "macos" then defaultDir = os.getenv("HOME") else - local handle = dsys.io_popen("xdg-user-dir DESKTOP") + local handle = io.popen("xdg-user-dir DESKTOP") defaultDir = handle:read() handle:close() end diff --git a/official/enfuse.lua b/official/enfuse.lua index c193c35e..c8df51b1 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -116,7 +116,7 @@ if enfuse_installed then local version = nil - local p = dtsys.io_popen(enfuse_installed .. " --version") + local p = io.popen(enfuse_installed .. " --version") local f = p:read("all") p:close() version = string.match(f, "enfuse (%d.%d)") diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 0e286680..ffdf532d 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -76,7 +76,7 @@ local function grep(file, pattern) if dt.configuration.running_os == "windows" then -- use find to get the matches local command = "\\windows\\system32\\find.exe " .. "\"" .. pattern .. "\"" .. " " .. file - local f = dtsys.io_popen(command) + local f = io.popen(command) local output = f:read("all") f:close() -- strip out the first line @@ -85,7 +85,7 @@ local function grep(file, pattern) else -- use grep and just return the answers local command = "grep " .. pattern .. " " .. file - local f = dtsys.io_popen(command) + local f = io.popen(command) local output = f:read("all") f:close() result = du.split(output, "\n") diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index 91d5dbb3..a5a7ab97 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -46,7 +46,7 @@ local function output_man(d) mf:close() if df.check_if_bin_exists("groff") then if df.check_if_bin_exists("ps2pdf") then - dtsys.os_execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf") + os.execute("groff -man " .. fname .. " | ps2pdf - " .. fname .. ".pdf") else log.msg(log.error, "Missing ps2pdf. Can't generate pdf man pages.") end @@ -60,7 +60,7 @@ end -- find the libraries -local output = dtsys.io_popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") +local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") -- loop through the libraries diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 2328014a..3749a683 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -37,7 +37,7 @@ end -- find the libraries -local output = dtsys.io_popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") +local output = io.popen("cd "..dt.configuration.config_dir.."/lua/lib ;find . -name \\*.lua -print | sort") -- loop through the libraries diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 40a69628..b39f9e42 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -89,7 +89,7 @@ local MIN_BUTTONS_PER_PAGE = 5 local MAX_BUTTONS_PER_PAGE = 20 local DEFAULT_BUTTONS_PER_PAGE = 10 -local DEFAULT_LOG_LEVEL = log.error +local DEFAULT_LOG_LEVEL = log.debug local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" @@ -244,7 +244,7 @@ end local function get_repo_status(repo) local old_log_level = set_log_level(sm.log_level) - local p = dtsys.io_popen("cd " .. repo .. CS .. "git status") + local p = io.popen("cd " .. repo .. CS .. "git status") if p then local data = p:read("*a") @@ -262,7 +262,7 @@ local function get_current_repo_branch(repo) local branch = nil - local p = dtsys.io_popen("cd " .. repo .. CS .. "git branch --all") + local p = io.popen("cd " .. repo .. CS .. "git branch --all") if p then local data = p:read("*a") @@ -292,7 +292,7 @@ local function get_repo_branches(repo) local old_log_level = set_log_level(sm.log_level) local branches = {} - local p = dtsys.io_popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") + local p = io.popen("cd " .. repo .. CS .. "git pull --all" .. CS .. "git branch --all") if p then local data = p:read("*a") @@ -332,7 +332,7 @@ local function checkout_repo_branch(repo, branch) log.msg(log.info, "checkout out branch " .. branch .. " from repository " .. repo) - dtsys.os_execute("cd " .. repo .. CS .. "git checkout " .. branch) + os.execute("cd " .. repo .. CS .. "git checkout " .. branch) restore_log_level(old_log_level) end @@ -668,7 +668,7 @@ local function scan_scripts(script_dir) log.msg(log.debug, "find command is " .. find_cmd) -- scan the scripts - local output = dtsys.io_popen(find_cmd) + local output = io.popen(find_cmd) for line in output:lines() do log.msg(log.debug, "line is " .. line) local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off @@ -754,7 +754,7 @@ local function scan_repositories() log.msg(log.debug, "find command is " .. find_cmd) - local output = dtsys.io_popen(find_cmd) + local output = io.popen(find_cmd) for line in output:lines() do local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off From 3e37c06b84e9f07a0f5586af352cefa4ce2ee8f3 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 13 Jul 2024 14:44:01 -0400 Subject: [PATCH 052/169] lib/dtutils/string - Added check to determine if string needs to be sanitized. Check looks for unprintable characters and spaces. --- lib/dtutils/string.lua | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 926c6673..d64a95a2 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -298,16 +298,32 @@ local function _sanitize_windows(str) end end +local function _should_be_sanitized(str) + local old_log_level = log.log_level() + local result = false + log.log_level(dtutils_string.log_level) + if string.match(str, "[^%g]") then + result = true + end + log.log_level(old_log_level) + return result +end + function dtutils_string.sanitize(str) local old_log_level = log.log_level() + local sanitized_str = nil log.log_level(dtutils_string.log_level) - if dt.configuration.running_os == "windows" then - log.log_level(old_log_level) - return _sanitize_windows(str) + if _should_be_sanitized(str) then + if dt.configuration.running_os == "windows" then + sanitized_str = _sanitize_windows(str) + else + sanitized_str = _sanitize_posix(str) + end else - log.log_level(old_log_level) - return _sanitize_posix(str) + sanitized_str = str end + log.log_level(old_log_level) + return sanitized_str end dtutils_string.libdoc.functions["sanitize_lua"] = { From cc11c1c3b89b0b834c6652759a4eaad53a86881e Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 18 Jul 2024 12:42:12 -0400 Subject: [PATCH 053/169] tools/script_manager - set log level to warn instead of debug --- tools/script_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index b39f9e42..b62e0e2c 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -89,7 +89,7 @@ local MIN_BUTTONS_PER_PAGE = 5 local MAX_BUTTONS_PER_PAGE = 20 local DEFAULT_BUTTONS_PER_PAGE = 10 -local DEFAULT_LOG_LEVEL = log.debug +local DEFAULT_LOG_LEVEL = log.warn local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" From 535ba78a06380c6d29e7a307e3b5a4613ebbec58 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 21 Jul 2024 22:43:59 -0400 Subject: [PATCH 054/169] lib/dtutils/system - remove _quote_windows_command --- lib/dtutils/system.lua | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 2ec2c313..0df0dee9 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -78,10 +78,6 @@ dtutils_system.libdoc.functions["windows_command"] = { Copyright = [[]], } -local function _quote_windows_command(command) - return "\"" .. command .. "\"" -end - function dtutils_system.windows_command(command) local result = 1 @@ -91,7 +87,6 @@ function dtutils_system.windows_command(command) if file then dt.print_log("opened file") command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell - command = _quote_windows_command(command) file:write(command) file:close() From 46e729b40e8e8fb0f9e4718679940a78a830bafb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 8 Aug 2024 19:43:33 -0400 Subject: [PATCH 055/169] fixed translatable strings for inclusion into darktable --- contrib/LabelsToTags.lua | 8 ++++---- contrib/change_group_leader.lua | 4 ++-- contrib/geoJSON_export.lua | 2 +- contrib/geoToolbox.lua | 6 +++--- contrib/image_time.lua | 2 +- contrib/rate_group.lua | 14 ++++++++------ contrib/transfer_hierarchy.lua | 4 ++-- examples/multi_os.lua | 2 +- tools/script_manager.lua | 4 ++-- 9 files changed, 24 insertions(+), 22 deletions(-) diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 6046f9c3..1054a8b9 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -130,10 +130,10 @@ local initialAvailableMappings = { ["***+**"] = { _("blue") }, ["****+*"] = { _("purple") } }, [_("single colors")] = { ["+----*"] = { _("red"), _("only red") }, - ["-+---*"] = { _("Yellow"), _("only yellow") }, - ["--+--*"] = { _("Green"), _("only green") }, - ["---+-*"] = { _("Blue"), _("only blue") }, - ["----+*"] = { _("Purple"), _("only purple") } }, + ["-+---*"] = { _("yellow"), _("only yellow") }, + ["--+--*"] = { _("green"), _("only green") }, + ["---+-*"] = { _("blue"), _("only blue") }, + ["----+*"] = { _("purple"), _("only purple") } }, [_("ratings")] = { ["*****0"] = { _("no stars"), _("not rejected") }, ["*****1"] = { _("one star"), _("not rejected") }, ["*****2"] = { _("two stars"), _("not rejected") }, diff --git a/contrib/change_group_leader.lua b/contrib/change_group_leader.lua index 202f3f6e..11522cbd 100644 --- a/contrib/change_group_leader.lua +++ b/contrib/change_group_leader.lua @@ -127,7 +127,7 @@ end local function process_image_groups(images) if #images < 1 then - dt.print(_("o images selected")) + dt.print(_("no images selected")) dt.print_log(MODULE .. "no images seletected, returning...") else local mode = cgl.widgets.mode.value @@ -204,4 +204,4 @@ script_data.restart = restart script_data.destroy_method = "hide" script_data.show = restart -return script_data \ No newline at end of file +return script_data diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 2ff30807..74625120 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -322,7 +322,7 @@ dt.preferences.register("geoJSON_export", "mapBoxKey", "string", _("geoJSON export: MapBox key"), - _("https://www.mapbox.com/studio/account/tokens"), + "https://www.mapbox.com/studio/account/tokens", '' ) dt.preferences.register("geoJSON_export", "OpengeoJSONFile", diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 68fdc3c6..13a7b5bd 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -495,9 +495,9 @@ local function calc_distance() if (distance < 1) then distance = distance * 1000 - distanceUnit = _("m") + distanceUnit = "m" else - distanceUnit = _("km") + distanceUnit = "km" end return string.format(_("distance: %.2f %s"), distance, distanceUnit) @@ -746,7 +746,7 @@ dt.preferences.register("geoToolbox", "mapBoxKey", "string", _("geoToolbox export: MapBox Key"), - _("https://www.mapbox.com/studio/account/tokens"), + "https://www.mapbox.com/studio/account/tokens", '' ) -- Register diff --git a/contrib/image_time.lua b/contrib/image_time.lua index a35f751f..ff5f8c28 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -257,7 +257,7 @@ local function _get_windows_image_file_creation_time(image) end p:close() else - dt.print(string.format(_("unable to get information for $s"), image.filename)) + dt.print(string.format(_("unable to get information for %s"), image.filename)) datetime = ERROR end return datetime diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 6455f6d3..5c1659e7 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -93,6 +93,8 @@ local function destroy() dt.destroy_event("rg5", "shortcut") end +local rate_group = _("rate group") + dt.register_event("rg_reject", "shortcut", function(event, shortcut) apply_rating(-1) @@ -101,32 +103,32 @@ end, _("reject group")) dt.register_event("rg0", "shortcut", function(event, shortcut) apply_rating(0) -end, _("rate group 0")) +end, rate group .. " 0") dt.register_event("rg1", "shortcut", function(event, shortcut) apply_rating(1) -end, _("rate group 1")) +end, rate group .. " 1") dt.register_event("rg2", "shortcut", function(event, shortcut) apply_rating(2) -end, _("rate group 2")) +end, rate group .. " 2") dt.register_event("rg3", "shortcut", function(event, shortcut) apply_rating(3) -end, _("rate group 3")) +end, rate group .. " 3") dt.register_event("rg4", "shortcut", function(event, shortcut) apply_rating(4) -end, _("rate group 4")) +end, rate group .. " 4") dt.register_event("rg5", "shortcut", function(event, shortcut) apply_rating(5) -end, _("rate group 5")) +end, rate group .. " 5") script_data.destroy = destroy diff --git a/contrib/transfer_hierarchy.lua b/contrib/transfer_hierarchy.lua index 96b18e44..b73b38a9 100755 --- a/contrib/transfer_hierarchy.lua +++ b/contrib/transfer_hierarchy.lua @@ -352,7 +352,7 @@ th.transfer_widget = darktable.new_widget("box") { }, darktable.new_widget("button") { label = _("copy"), - tooltip = _("Copy all selected images"), + tooltip = _("copy all selected images"), clicked_callback = doCopy } } @@ -406,4 +406,4 @@ script_data.restart = restart script_data.destroy_method = "hide" script_data.show = restart -return script_data \ No newline at end of file +return script_data diff --git a/examples/multi_os.lua b/examples/multi_os.lua index f27cc9ea..a1eea24b 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -227,7 +227,7 @@ if dt.configuration.running_os ~= "linux" then dt.preferences.register("executable_paths", "ufraw-batch", -- name "file", -- type _('multi_os: ufraw-batch location'), -- label - _('Installed location of ufraw-batch, requires restart to take effect.'), -- tooltip + _('installed location of ufraw-batch, requires restart to take effect.'), -- tooltip "ufraw-batch", -- default ufraw_batch_path_widget ) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index b62e0e2c..ada8579d 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -852,7 +852,7 @@ local function install_scripts() sm.widgets.new_folder.text = "" sm.widgets.main_menu.selected = 3 else - log.msg(log.screen, _("No scripts found to install")) + log.msg(log.screen, _("no scripts found to install")) log.msg(log.error, "scan_scripts returned " .. count .. " scripts found. Not adding to folder_selector") end @@ -1436,7 +1436,7 @@ sm.widgets.page_control = dt.new_widget("box"){ sm.widgets.scripts = dt.new_widget("box"){ orientation = vertical, - dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("section_label"){label = " "}, dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("scripts")}, sm.widgets.folder_selector, From 200fc059c064c3e094321fc1f9a91eb940e0eccc Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 10 Aug 2024 23:38:06 -0400 Subject: [PATCH 056/169] README.md - updated 3rd party scripts --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 80ead1b9..d57c8e68 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,8 @@ The following third-party projects are listed for information only. Think of thi * [nbremond77/darktable](https://github.com/nbremond77/darktable/tree/master/scripts) * [s5k6/dtscripts](https://github.com/s5k6/dtscripts) * [ChristianBirzer/darktable_extra_scripts](https://github.com/ChristianBirzer/darktable_extra_scripts) +* [fjb2020/darktable-scripts](https://github.com//fjb2020/darktable-scripts) +* [bastibe/Darktable-Film-Simulation-Panel](https://github.com/bastibe/Darktable-Film-Simulation-Panel) ## Download and Install From a079860cd8f5074a1ecbd9b8632f24a3cf2950ad Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 13 Aug 2024 17:46:28 -0400 Subject: [PATCH 057/169] lib/dtutils/file - os.tmpname now works correctly on windows, removed check and fix --- lib/dtutils/file.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index cd898e71..e33fa5aa 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -861,9 +861,6 @@ dtutils_file.libdoc.functions["create_tmp_file"] = { function dtutils_file.create_tmp_file() local tmp_file = os.tmpname() - if dt.configuration.running_os == "windows" then - tmp_file = dt.configuration.tmp_dir .. tmp_file -- windows os.tmpname() defaults to root directory - end local f = io.open(tmp_file, "w") if not f then From 6ca8f0034efebc875da46341c3cc13d57eda5541 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 31 Aug 2024 15:53:38 -0400 Subject: [PATCH 058/169] official/apply_camera_style - apply a darktable camera style to an image based on maker and model. Styles may be applied to selections, collections, and on newly imported images. --- official/apply_camera_style.lua | 491 ++++++++++++++++++++++++++++++++ 1 file changed, 491 insertions(+) create mode 100644 official/apply_camera_style.lua diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua new file mode 100644 index 00000000..ada2e45a --- /dev/null +++ b/official/apply_camera_style.lua @@ -0,0 +1,491 @@ +--[[ + + apply_camera_style.lua - apply camera style to matching images + + Copyright (C) 2024 Bill Ferguson . + + 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 . +]] +--[[ + apply_camera_style - apply camera style to matching images + + apply a camera style corresponding to the camera used to + take the image to provide a starting point for editing that + is similar to the SOOC jpeg. + + ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT + none + + USAGE + start the script from script_manager + + BUGS, COMMENTS, SUGGESTIONS + Bill Ferguson + + CHANGES +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +-- local df = require "lib/dtutils.file" +-- local ds = require "lib/dtutils.string" +-- local dtsys = require "lib/dtutils.system" +local log = require "lib/dtutils.log" +-- local debug = require "darktable.debug" + + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- C O N S T A N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local MODULE = "apply_camera_style" +local DEFAULT_LOG_LEVEL = log.info +local TMP_DIR = dt.configuration.tmp_dir +local STYLE_PREFIX = "_l10n_darktable camera styles|" + +-- path separator +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +-- command separator +local CS = dt.configuration.running_os == "windows" and "&" or ";" + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A P I C H E C K +-- - - - - - - - - - - - - - - - - - - - - - - - + +du.check_min_api_version("9.4.0", MODULE) -- styles use filmic V7 which appeared in darktable 4.4 + + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- I 1 8 N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local gettext = dt.gettext.gettext + +local function _(msgid) + return gettext(msgid) +end + +-- - - - - - - - - - - - - - - - - - - - - - - - - - +-- S C R I P T M A N A G E R I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - - - + +local script_data = {} + +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 +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 + +script_data.metadata = { + name = "apply_camera_style", -- name of script + purpose = _("apply camera style to matching images"), -- purpose of script + author = "Bill Ferguson ", -- your name and optionally e-mail address + help = "https://docs.darktable.org/lua/development/lua.scripts.manual/scripts/official/apply_camera_style/" -- URL to help/documentation +} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- L O G L E V E L +-- - - - - - - - - - - - - - - - - - - - - - - - + +log.log_level(DEFAULT_LOG_LEVEL) + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- N A M E S P A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +local apply_camera_style = {} + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- G L O B A L V A R I A B L E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +apply_camera_style.imported_images = {} +apply_camera_style.styles = {} +apply_camera_style.log_level = DEFAULT_LOG_LEVEL + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- P R E F E R E N C E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- A L I A S E S +-- - - - - - - - - - - - - - - - - - - - - - - - + +local namespace = apply_camera_style +local acs = apply_camera_style + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- F U N C T I O N S +-- - - - - - - - - - - - - - - - - - - - - - - - + +------------------- +-- helper functions +------------------- + +local function set_log_level(level) + local old_log_level = log.log_level() + log.log_level(level) + return old_log_level +end + +local function restore_log_level(level) + log.log_level(level) +end + +------------------- +-- script functions +------------------- + +local function process_pattern(pattern) + + local log_level = set_log_level(acs.log_level) + + pattern = string.lower(pattern) + -- strip off series + pattern = string.gsub(pattern, " series$", "?") + -- match a character + if string.match(pattern, "?$") then + -- handle EOS R case + pattern = string.gsub(pattern, "?", ".?") + else + pattern = string.gsub(pattern, "?", ".") + end + -- escape dashes + pattern = string.gsub(pattern, "%-", "%%-") + -- until we end up with a set, I'll defer set processing, i.e. [...] + -- anchor the pattern to ensure we don't short match + pattern = "^" .. pattern .. "$" + + restore_log_level(log_level) + + return pattern +end + +local function process_set(pattern_set) + + local log_level = set_log_level(acs.log_level) + + local to_process = {} + local processed = {} + + local base, set, tail + + if string.match(pattern_set, "]$") then + base, set = string.match(pattern_set, "(.+)%[(.+)%]") + else + base, set, tail = string.match(pattern_set, "(.+)%[(.+)%](.+)") + end + + log.msg(log.debug, "base is " .. base .. " and set is " .. set) + + to_process = du.split(set, ",") + + for _, item in ipairs(to_process) do + local pat = base .. item + if tail then + pat = pat .. tail + end + table.insert(processed, process_pattern(pat)) + end + + restore_log_level(log_level) + + return processed +end + +local function get_camera_styles() + + local log_level = set_log_level(acs.log_level) + + -- separate the styles into + -- + -- acs.styles - + -- maker - + -- styles {} + -- patterns {} + + for _, style in ipairs(dt.styles) do + + if string.match(style.name, STYLE_PREFIX) then + log.msg(log.debug, "got " .. style.name) + + local parts = du.split(style.name, "|") + parts[2] = string.lower(parts[2]) + log.msg(log.debug, "maker is " .. parts[2]) + + if not acs.styles[parts[2]] then + acs.styles[parts[2]] = {} + acs.styles[parts[2]]["styles"] = {} + acs.styles[parts[2]]["patterns"] = {} + end + if parts[3] then + if not string.match(parts[3], "]") then + table.insert(acs.styles[parts[2]].styles, style) + local processed_pattern = process_pattern(parts[#parts]) + table.insert(acs.styles[parts[2]].patterns, processed_pattern) + log.msg(log.debug, "pattern for " .. style.name .. " is " .. processed_pattern) + else + local processed_patterns = process_set(parts[3]) + for _, pat in ipairs(processed_patterns) do + table.insert(acs.styles[parts[2]].styles, style) + table.insert(acs.styles[parts[2]].patterns, pat) + log.msg(log.debug, "pattern for " .. style.name .. " is " .. pat) + end + end + end + end + end + + restore_log_level(log_level) + +end + +local function normalize_model(maker, model) + + local log_level = set_log_level(acs.log_level) + + model = string.lower(model) + + -- strip off the maker name + if maker == "canon" then + model = string.gsub(model, "canon ", "") + elseif maker == "hasselblad" then + model = string.gsub(model, "hasselblad ", "") + elseif maker == "leica" then + model = string.gsub(model, "leica ", "") + elseif maker == "lg" then + model = string.gsub(model, "lg ", "") + elseif maker == "nikon" then + model = string.gsub(model, "nikon ", "") + elseif maker == "nokia" then + model = string.gsub(model, "nokia ", "") + elseif maker == "oneplus" then + model = string.gsub(model, "oneplus ", "") + elseif maker == "pentax" then + model = string.gsub(model, "pentax ", "") + model = string.gsub(model, "ricoh ", "") + end + + restore_log_level(log_level) + + return model +end + +local function normalize_maker(maker) + + local log_level = set_log_level(acs.log_level) + + maker = string.lower(maker) + + if string.match(maker, "^fujifilm") then + maker = "fujifilm" + elseif string.match(maker, "^hmd ") then + maker = "nokia" + elseif string.match(maker, "^leica") then + maker = "leica" + elseif string.match(maker, "^minolta") then + maker = "minolta" + elseif string.match(maker, "^nikon") then + maker = "nikon" + elseif string.match(maker, "^om ") then + maker = "om system" + elseif string.match(maker, "^olympus") then + maker = "olympus" + elseif string.match(maker, "^pentax") or string.match(maker, "^ricoh") then + maker = "pentax" + end + + restore_log_level(log_level) + + return maker +end + +local function has_style_tag(image, tag_name) + + local log_level = set_log_level(acs.log_level) + + local result = false + + log.msg(log.debug, "looking for tag " .. tag_name) + + for _, tag in ipairs(image:get_tags()) do + log.msg(log.debug, "checking against " .. tag.name) + if tag.name == tag_name then + log.msg(log.debug, "matched tag " .. tag_name) + result = true + end + end + + restore_log_level(log_level) + + return result +end + +local function mangle_model(model) + + local log_level = set_log_level(acs.log_level) + + if string.match(model, "eos") then + log.msg(log.debug, "mangle model got " .. model) + model = string.gsub(model, "r6m2", "r6 mark ii") + model = string.gsub(model, "eos 350d digital", "eos kiss digital n") + model = string.gsub(model, "eos 500d", "eos rebel t1") + model = string.gsub(model, "eos 550d", "eos rebel t2") + model = string.gsub(model, "eos 600d", "eos rebel t3i") + model = string.gsub(model, "eos 650d", "eos rebel t4i") + model = string.gsub(model, "eos 700d", "eos rebel t5") + model = string.gsub(model, "eos 750d", "eos rebel t6i") + model = string.gsub(model, "eos 760d", "eos rebel t6s") + model = string.gsub(model, "eos 100d", "eos rebel t6") + model = string.gsub(model, "eos 1100d", "eos rebel t3") + model = string.gsub(model, "eos 1200d", "eos rebel t5") + model = string.gsub(model, "eos 1300d", "eos rebel t6") + model = string.gsub(model, "eos 2000d", "eos rebel t7") + log.msg(log.debug, "mandle model returning " .. model) + end + + restore_log_level(log_level) + + return model +end + +local function stop_job() + if acs.job then + if acs.job.valid then + acs.job.valid = false + end + end +end + +local function apply_style_to_images(images) + + local log_level = set_log_level(acs.log_level) + + acs.job = dt.gui.create_job(_("applying camera styles to images"), true, stop_job) + + for count, image in ipairs(images) do + local maker = normalize_maker(image.exif_maker) + local model = normalize_model(maker, image.exif_model) + model = mangle_model(model) + log.msg(log.debug, "got maker " .. maker .. " and model " .. model .. " from image " .. image.filename) + + if acs.styles[maker] then + local no_match = true + for i, pattern in ipairs(acs.styles[maker].patterns) do + if string.match(model, pattern) or + (i == #acs.styles[maker].patterns and string.match(pattern, "generic")) then + local tag_name = "darktable|style|" .. acs.styles[maker].styles[i].name + if not has_style_tag(image, tag_name) then + image:apply_style(acs.styles[maker].styles[i]) + no_match = false + log.msg(log.info, "applied style " .. acs.styles[maker].styles[i].name .. " to " .. image.filename) + end + log.log_level(loglevel) + break + end + end + if no_match then + log.msg(log.info, "no style found for " .. maker .. " " .. model) + end + else + log.msg(log.info, "no maker found for " .. image.filename) + end + if count % 10 == 0 then + acs.job.percent = count / #images + end + if dt.control.ending then + stop_job() + end + end + + stop_job() + + restore_log_level(log_level) + +end + +local function apply_camera_style(collection) + + local log_level = set_log_level(acs.log_level) + + local images = nil + + if collection == true then + images = dt.collection + log.msg(log.info, "applying camera styles to collection") + elseif collection == false then + images = dt.gui.selection() + if #images == 0 then + images = dt.gui.action_images + end + log.msg(log.info, "applying camera styles to selection") + end + apply_style_to_images(images) + + restore_log_level(log_level) + +end + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- M A I N P R O G R A M +-- - - - - - - - - - - - - - - - - - - - - - - - + +get_camera_styles() + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- U S E R I N T E R F A C E +-- - - - - - - - - - - - - - - - - - - - - - - - + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- D A R K T A B L E I N T E G R A T I O N +-- - - - - - - - - - - - - - - - - - - - - - - - + +local function destroy() + dt.destroy_event(MODULE, "post-import-image") + dt.destroy_event(MODULE, "post-import-film") +end + +script_data.destroy = destroy + +-- - - - - - - - - - - - - - - - - - - - - - - - +-- E V E N T S +-- - - - - - - - - - - - - - - - - - - - - - - - + +dt.register_event(MODULE, "shortcut", + function(event, shortcut) + apply_camera_style(true) + end, _("apply camera styles to collection") +) + +dt.register_event(MODULE, "shortcut", + function(event, shortcut) + apply_camera_style(false) + end, _("apply camera styles to selection") +) + +dt.register_event(MODULE, "post-import-image", + function(event, image) + table.insert(acs.imported_images, image) + end +) + +dt.register_event(MODULE, "post-import-film", + function(event, film) + apply_style_to_images(acs.imported_images) + acs.imported_images = {} + end +) + +return script_data \ No newline at end of file From 6c686128d2ea960496ebd298a8c441c84b938eee Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 31 Aug 2024 16:48:36 -0400 Subject: [PATCH 059/169] official/*.lua - Removed bindtextdoamin so that darktable can handle the translation --- official/check_for_updates.lua | 2 -- official/copy_paste_metadata.lua | 2 -- official/delete_long_tags.lua | 2 -- official/delete_unused_tags.lua | 2 -- official/enfuse.lua | 2 -- official/generate_image_txt.lua | 2 -- official/image_path_in_ui.lua | 2 -- official/import_filter_manager.lua | 2 -- official/import_filters.lua | 2 -- official/save_selection.lua | 2 -- official/selection_to_pdf.lua | 2 -- 11 files changed, 22 deletions(-) diff --git a/official/check_for_updates.lua b/official/check_for_updates.lua index de92c7c9..45ec19b0 100644 --- a/official/check_for_updates.lua +++ b/official/check_for_updates.lua @@ -36,8 +36,6 @@ du.check_min_api_version("2.0.0", "check_for_updates") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("check_for_updates", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/official/copy_paste_metadata.lua b/official/copy_paste_metadata.lua index 0c765ec1..37eb94f4 100644 --- a/official/copy_paste_metadata.lua +++ b/official/copy_paste_metadata.lua @@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "copy_paste_metadata") -dt.gettext.bindtextdomain("copy_paste_metadata", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/official/delete_long_tags.lua b/official/delete_long_tags.lua index 2c807e15..89b192f1 100644 --- a/official/delete_long_tags.lua +++ b/official/delete_long_tags.lua @@ -35,8 +35,6 @@ du.check_min_api_version("2.0.0", "delete_long_tags") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("delete_long_tags", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/official/delete_unused_tags.lua b/official/delete_unused_tags.lua index bcdceaf1..62ebc95e 100644 --- a/official/delete_unused_tags.lua +++ b/official/delete_unused_tags.lua @@ -39,8 +39,6 @@ end local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("delete_unused_tags", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/official/enfuse.lua b/official/enfuse.lua index c8df51b1..317ab73d 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -43,8 +43,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "enfuse") -dt.gettext.bindtextdomain("enfuse", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index deacefaf..42f64987 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -40,8 +40,6 @@ require "darktable.debug" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("generate_image_txt", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index fd9e3fbc..b4440db9 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -35,8 +35,6 @@ du.check_min_api_version("7.0.0", "image_path_in_ui") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("image_path_in_ui", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/official/import_filter_manager.lua b/official/import_filter_manager.lua index 9b16ee3e..11191504 100644 --- a/official/import_filter_manager.lua +++ b/official/import_filter_manager.lua @@ -34,8 +34,6 @@ local dt = require "darktable" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("import_filter_manager", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/official/import_filters.lua b/official/import_filters.lua index 165ec3fd..44633062 100644 --- a/official/import_filters.lua +++ b/official/import_filters.lua @@ -32,8 +32,6 @@ local dt = require "darktable" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("import_filters", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/official/save_selection.lua b/official/save_selection.lua index 865ca265..77e82eb4 100644 --- a/official/save_selection.lua +++ b/official/save_selection.lua @@ -40,8 +40,6 @@ du.check_min_api_version("7.0.0", "save_selection") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("save_selection", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index 18a7be57..1457a1b6 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -40,8 +40,6 @@ du.check_min_api_version("7.0.0", "selection_to_pdf") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("selection_to_pdf", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end From 96fee48ef2afc242db08435e22d3b09d73bd2772 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 31 Aug 2024 21:39:50 -0400 Subject: [PATCH 060/169] tools/script_manager - restore script_manager_running_script to "script_manager" after changing it to start a script --- tools/script_manager.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index ada8579d..0baba484 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -517,6 +517,7 @@ local function activate(script) status = true pref_write(script.script_name, "bool", true) end + script_manager_running_script = "script_manager" restore_log_level(old_log_level) return status From ee98f96a4fe67451e427ea0107c0ce8b4718def8 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 3 Sep 2024 19:14:31 -0400 Subject: [PATCH 061/169] contrib/rate_group - fixed formatting of tooltips --- contrib/rate_group.lua | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 5c1659e7..c75b504d 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -93,8 +93,6 @@ local function destroy() dt.destroy_event("rg5", "shortcut") end -local rate_group = _("rate group") - dt.register_event("rg_reject", "shortcut", function(event, shortcut) apply_rating(-1) @@ -103,32 +101,33 @@ end, _("reject group")) dt.register_event("rg0", "shortcut", function(event, shortcut) apply_rating(0) -end, rate group .. " 0") + end, string.format(_("rate group %d"), 0) +) dt.register_event("rg1", "shortcut", function(event, shortcut) apply_rating(1) -end, rate group .. " 1") +end, string.format(_("rate group %d"), 1)) dt.register_event("rg2", "shortcut", function(event, shortcut) apply_rating(2) -end, rate group .. " 2") +end, string.format(_("rate group %d"), 2)) dt.register_event("rg3", "shortcut", function(event, shortcut) apply_rating(3) -end, rate group .. " 3") +end, string.format(_("rate group %d"), 3)) dt.register_event("rg4", "shortcut", function(event, shortcut) apply_rating(4) -end, rate group .. " 4") +end, string.format(_("rate group %d"), 4)) dt.register_event("rg5", "shortcut", function(event, shortcut) apply_rating(5) -end, rate group .. " 5") +end, string.format(_("rate group %d"), 5)) script_data.destroy = destroy From ea08557a0e37511262240555ffcb79802f68fbdf Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 8 Sep 2024 22:49:50 -0400 Subject: [PATCH 062/169] tools/script_manager - removed translation of empty string --- tools/script_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 0baba484..fe8fa6ab 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1353,7 +1353,7 @@ sm.widgets.disable_scripts = dt.new_widget("button"){ sm.widgets.install_update = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = _(" ")}, + dt.new_widget("section_label"){label = " "}, dt.new_widget("label"){label = " "}, dt.new_widget("label"){label = _("update scripts")}, dt.new_widget("label"){label = " "}, From f0e36278001c04c855898f8b82b4287a892b4ef6 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 8 Sep 2024 22:54:42 -0400 Subject: [PATCH 063/169] contrib/exportLUT - fixed file chooser labels for translation --- contrib/exportLUT.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/exportLUT.lua b/contrib/exportLUT.lua index 0a578a24..be904cdd 100644 --- a/contrib/exportLUT.lua +++ b/contrib/exportLUT.lua @@ -71,13 +71,13 @@ local mkdir_command = 'mkdir -p ' if dt.configuration.running_os == 'windows' then mkdir_command = 'mkdir ' end local file_chooser_button = dt.new_widget("file_chooser_button"){ - title = _("identity_file_chooser"), + title = _("choose the identity file"), value = "", is_directory = false } local export_chooser_button = dt.new_widget("file_chooser_button"){ - title = _("export_location_chooser"), + title = _("choose the export location"), value = "", is_directory = true } From 5be3d6986ef1da570908ac1a5f05ba7e55b5f8fb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 8 Sep 2024 23:24:33 -0400 Subject: [PATCH 064/169] official/selection_to_pdf - dont translate internal module name --- official/selection_to_pdf.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index 1457a1b6..e4f22f5e 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -129,7 +129,7 @@ local function destroy() dt.print_log("done destroying") end -dt.register_storage(_("export_pdf"),_("export thumbnails to pdf"), +dt.register_storage("export_pdf", _("export thumbnails to pdf"), nil, function(storage,image_table) local my_title = title_widget.text From 05c57c02477a12d3682ce5d40f0a511d44ce0e75 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 8 Sep 2024 23:42:45 -0400 Subject: [PATCH 065/169] contrib/geoTooxbox - fix latitude and longitude labels --- contrib/geoToolbox.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 13a7b5bd..677af0a1 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -67,12 +67,12 @@ labelDistance.label = _("distance:") local label_copy_gps_lat = dt.new_widget("check_button") { - label = _("latitude:"), + label = _("latitude: "), value = true } local label_copy_gps_lon = dt.new_widget("check_button") { - label = _("longitude:"), + label = _("longitude: "), value = true } local label_copy_gps_ele = dt.new_widget("check_button") @@ -307,9 +307,9 @@ local function copy_gps() end end - label_copy_gps_lat.label = string.format(_("latitude: "), copy_gps_latitude) - label_copy_gps_lon.label = string.format(_("longitude: "),copy_gps_longitude) - label_copy_gps_ele.label = string.format(_("elevation: "), copy_gps_elevation) + label_copy_gps_lat.label = _("latitude: ") .. copy_gps_latitude + label_copy_gps_lon.label = _("longitude: ") .. copy_gps_longitude + label_copy_gps_ele.label = _("elevation: ") .. copy_gps_elevation return end From c65e070a761c4111bd35107181a0ce58dd26bd8a Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 8 Sep 2024 23:43:39 -0400 Subject: [PATCH 066/169] contrib/geoJSON - don't translate the module name --- contrib/geoJSON_export.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 74625120..dc336b2a 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -315,19 +315,19 @@ end dt.preferences.register("geoJSON_export", "CreateMapBoxHTMLFile", "bool", - _("geoJSON export: Create an additional HTML file"), + "geoJSON export: ".._("Create an additional HTML file"), _("creates an HTML file that loads the geoJSON file. (needs a MapBox key"), false ) dt.preferences.register("geoJSON_export", "mapBoxKey", "string", - _("geoJSON export: MapBox key"), + "geoJSON export: MapBox " .. _("key"), "https://www.mapbox.com/studio/account/tokens", '' ) dt.preferences.register("geoJSON_export", "OpengeoJSONFile", "bool", - _("geoJSON export: open geoJSON file after export"), + "geoJSON export: " .. _("open geoJSON file after export"), _("opens the geoJSON file after the export with the standard program for geoJSON files"), false ) From f0bdc890c1177008c175dd54101db30d54d7b3bb Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 9 Sep 2024 22:50:49 -0400 Subject: [PATCH 067/169] official/apply_camera_styles - updated message to specify the camera styles are darktable camera styles --- official/apply_camera_style.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index ada2e45a..963586af 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -18,7 +18,7 @@ along with this program. If not, see . ]] --[[ - apply_camera_style - apply camera style to matching images + apply_camera_style - apply darktable camera style to matching images apply a camera style corresponding to the camera used to take the image to provide a starting point for editing that @@ -90,7 +90,7 @@ script_data.show = nil -- only required for libs since the destroy_ script_data.metadata = { name = "apply_camera_style", -- name of script - purpose = _("apply camera style to matching images"), -- purpose of script + purpose = _("apply darktable camera style to matching images"), -- purpose of script author = "Bill Ferguson ", -- your name and optionally e-mail address help = "https://docs.darktable.org/lua/development/lua.scripts.manual/scripts/official/apply_camera_style/" -- URL to help/documentation } @@ -466,13 +466,13 @@ script_data.destroy = destroy dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(true) - end, _("apply camera styles to collection") + end, _("apply darktable camera styles to collection") ) dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(false) - end, _("apply camera styles to selection") + end, _("apply darktable camera styles to selection") ) dt.register_event(MODULE, "post-import-image", @@ -488,4 +488,4 @@ dt.register_event(MODULE, "post-import-film", end ) -return script_data \ No newline at end of file +return script_data From 167620fadf8b0baa137023ed6f4233c178e6d18d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 10 Sep 2024 16:34:36 -0400 Subject: [PATCH 068/169] lib/dtutils/string - Added get_substitution_tooltip() function to return the substitution pattern tooltip so it only has to be translated in one place. Spelled substitution properly to that calls to build_substitution_list succeed. --- lib/dtutils/string.lua | 102 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 101 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index d64a95a2..b1718b39 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -6,6 +6,10 @@ local log = require "lib/dtutils.log" local DEFAULT_LOG_LEVEL = log.error +local function _(msg) + return dt.gettext.gettext(msg) +end + dtutils_string.log_level = DEFAULT_LOG_LEVEL dtutils_string.libdoc = { @@ -688,7 +692,7 @@ end -- build the argument substitution list from each image -function dtutils_string.build_substition_list(image, sequence, variable_string, username, pic_folder, home, desktop) +function dtutils_string.build_substitution_list(image, sequence, variable_string, username, pic_folder, home, desktop) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) @@ -814,6 +818,102 @@ function dtutils_string.build_substition_list(image, sequence, variable_string, log.log_level(old_log_level) end +dtutils_string.libdoc.functions["get_substitution_tooltip"] = { + Name = [[get_substitution_tooltip]], + Synopsis = [[get a tooltip that lists the substitution variables]], + Usage = [[local ds = require "lib/dtutils.string" + ds.get_substitution_tooltip() + Description = [[get_substitution_tooltip lists the variables with brief explanations]], + Return_Value = [[string - the tooltip]], + Limitations = [[]], + Example = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.get_substitution_tooltip() + return string.format("$(ROLL.NAME) - %s\n", _("film roll of the input image")) .. + string.format("$(FILE.FOLDER) - %s\n", _("folder containing the input image")) .. + string.format("$(FILE.NAME) - %s\n", _("basename of the input image")) .. + string.format("$(FILE.EXTENSION) - %s\n", _("extension of the input image")) .. + string.format("$(ID) - %s\n", _("the image id")) .. + string.format("$(VERSION) - %s\n", _("the duplicate version number")) .. + string.format("$(VERSION.IF.MULTI) - %s\n", _("same as $(VERSION) but null string if only one version exists")) .. + string.format("$(VERSION.NAME) - %s\n", _("version name from metadata")) .. + string.format("$(DARKTABLE.VERSION) - %s\n", _("the version of the running darktable instance")) .. + --string.format("$(DARKTABLE.NAME) - %s\n", _("")) .. -- Not Implemented + string.format("$(SEQUENCE[n,m]) - %s\n", _("a sequence number within an export job with n digits and starting with m\nparameters are optional, default is [4,1]")) .. + string.format("$(WIDTH.SENSOR) - %s\n", _("width of RAW data in pixels before RAW crop")) .. + string.format("$(HEIGHT.SENSOR) - %s\n", _("height of RAW data in pixels before RAW crop")) .. + string.format("$(WIDTH.RAW) - %s\n", _("width of RAW data in pixels after RAW crop")) .. + string.format("$(HEIGHT.RAW) - %s\n", _("height of RAW data in pixels after RAW crop")) .. + string.format("$(WIDTH.CROP) - %s\n", _("image width in pixels at the end of the pixelpipe, but before export resize")) .. + string.format("$(HEIGHT.CROP) - %s\n", _("image height in pixels at the end of the pixelpipe, but before export resize")) .. + string.format("$(WIDTH.EXPORT) - %s\n", _("image width in pixels at the end of the pixelpipe and after export resize")) .. + string.format("$(HEIGHT.EXPORT) - %s\n", _("image height in pixels at the end of the pixelpipe and after export resize")) .. + --string.format("$(WIDTH.MAX) - %s\n", _("")) .. -- Not Implemented + --string.format("$(HEIGHT.MAX) - %s\n", _("")) .. -- Not Implemented + string.format("$(YEAR) - %s\n", _("current year")) .. + string.format("$(YEAR.SHORT) - %s\n", _("current two digit year")) .. + string.format("$(MONTH) - %s\n", _("current numeric (1-12) month")) .. + string.format("$(MONTH.LONG) - %s\n", _("full current month name")) .. + string.format("$(MONTH.SHORT) - %s\n", _("abbreviated current month name")) .. + string.format("$(DAY) - %s\n", _("current day")) .. + string.format("$(HOUR) - %s\n", _("current hour")) .. + string.format("$(MINUTE) - %s\n", _("current minute")) .. + string.format("$(SECOND) - %s\n", _("current second")) .. + string.format("$(MSEC) - %s\n", _("current millisecond")) .. + string.format("$(EXIF.YEAR) - EXIF %s\n", _("year")) .. + string.format("$(EXIF.YEAR.SHORT) - EXIF %s\n", _("year, two-digit version")) .. + string.format("$(EXIF.MONTH) - EXIF %s\n", _("month, numeric")) .. + string.format("$(EXIF.MONTH.LONG) - EXIF %s\n", _("month, full name")) .. + string.format("$(EXIF.MONTH.SHORT) - EXIF %s\n", _("month, abbreviated name")) .. + string.format("$(EXIF.DAY) - EXIF %s\n", _("day")) .. + string.format("$(EXIF.HOUR) - EXIF %s\n", _("hour")) .. + string.format("$(EXIF.MINUTE) - EXIF %s\n", _("minute")) .. + string.format("$(EXIF.SECOND) - EXIF %s\n", _("second")) .. + string.format("$(EXIF.MSEC) - EXIF %s\n", _("millisecond")) .. + --string.format("$(EXIF.DATE.REGIONAL) - %s\n", _("")) .. -- Not Implemented + --string.format("$(EXIF.TIME.REGIONAL) - %s\n", _("")) .. -- Not Implemented + string.format("$(EXIF.ISO) - EXIF ISO %s\n", _("value")) .. + string.format("$(EXIF.EXPOSURE) - EXIF %s\n", _("exposure")) .. + string.format("$(EXIF.EXPOSURE.BIAS) - EXIF %s\n", _("exposure bias")) .. + string.format("$(EXIF.APERTURE) - EXIF %s\n", _("aperture")) .. + string.format("$(EXIF.CROP.FACTOR) - EXIF %s\n", _("crop factor")) .. + string.format("$(EXIF.FOCAL.LENGTH) - EXIF %s\n", _("focal length")) .. + string.format("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35mm %s\n", _("equivalent focal length")) .. -- Not Implemented + string.format("$(EXIF.FOCUS.DISTANCE) - EXIF %s\n", _("focus distance")) .. + --string.format("$(IMAGE.EXIF) - %s\n", _("")) .. -- Not Implemented + string.format("$(LONGITUDE) - %s\n", _("longitude")) .. + string.format("$(LATITUDE) - %s\n", _("latitude")) .. + string.format("$(ELEVATION) - %s\n", _("elevation")) .. + --string.format("$(GPS.LOCATION) - %s\n", _("")) .. -- Not Implemented + string.format("$(STARS) - %s\n", _("star rating")) .. + --string.format("$(RATING.ICONS) - %s\n", _("")) .. -- Not Implemented + string.format("$(LABELS) - %s\n", _("colorlabels")) .. + --string.format("$(LABELS.ICONS) - %s\n", _("")) .. -- Not Implemented + string.format("$(MAKER) - %s\n", _("camera maker")) .. + string.format("$(MODEL) - %s\n", _("camera model")) .. + string.format("$(LENS) - %s\n", _("lens")) .. + string.format("$(TITLE) - %s\n", _("title from metadata")) .. + string.format("$(DESCRIPTION) - %s\n", _("description from metadata")) .. + string.format("$(CREATOR) - %s\n", _("creator from metadata")) .. + string.format("$(PUBLISHER) - %s\n", _("publisher from metadata")) .. + string.format("$(RIGHTS) - %s\n", _("rights from metadata")) .. + --string.format("$(TAGS) - %s\n", _("")) .. -- Not Implemented + string.format("$(CATEGORY[n,category]) - %s\n", _("tag name of level n [0,9] of selected category (or tag)")) .. + --string.format("$(SIDECAR.TXT) - %s\n", _("")) .. -- Not Implemented + string.format("$(FOLDER.PICTURES) - %s\n", _("pictures folder")) .. + string.format("$(FOLDER.HOME) - %s\n", _("home folder")) .. + string.format("$(FOLDER.DESKTOP) - %s\n", _("desktop folder")) .. + --string.format("$(OPENCL.ACTIVATED) - %s\n", _("")) .. -- Not Implemented + string.format("$(USERNAME) - %s\n", _("user name defined by OS")) + --string.format("$(NL) - %s\n", _("")) .. -- Not Implemented + --string.format("$(JOBCODE) - %s", _("")) -- Not Implemented +end + -- handle different versions of names local function check_legacy_vars(var_name) From 3fb00586c39067b5d56e8ba06a9272586205c181 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 10 Sep 2024 16:36:54 -0400 Subject: [PATCH 069/169] contrib/rename_images - replaced pattern substitution code with the better implementation in the string library. --- contrib/rename_images.lua | 136 ++------------------------------------ 1 file changed, 6 insertions(+), 130 deletions(-) diff --git a/contrib/rename_images.lua b/contrib/rename_images.lua index 769adfac..c0132adb 100644 --- a/contrib/rename_images.lua +++ b/contrib/rename_images.lua @@ -43,6 +43,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local ds = require "lib/dtutils.string" du.check_min_api_version("7.0.0", "rename_images") @@ -57,12 +58,6 @@ end -- namespace variable local rename = { presets = {}, - substitutes = {}, - placeholders = {"ROLL_NAME","FILE_FOLDER","FILE_NAME","FILE_EXTENSION","ID","VERSION","SEQUENCE","YEAR","MONTH","DAY", - "HOUR","MINUTE","SECOND","EXIF_YEAR","EXIF_MONTH","EXIF_DAY","EXIF_HOUR","EXIF_MINUTE","EXIF_SECOND", - "STARS","LABELS","MAKER","MODEL","TITLE","CREATOR","PUBLISHER","RIGHTS","USERNAME","PICTURES_FOLDER", - "HOME","DESKTOP","EXIF_ISO","EXIF_EXPOSURE","EXIF_EXPOSURE_BIAS","EXIF_APERTURE","EXIF_FOCUS_DISTANCE", - "EXIF_FOCAL_LENGTH","LONGITUDE","LATITUDE","ELEVATION","LENS","DESCRIPTION","EXIF_CROP"}, widgets = {}, } rename.module_installed = false @@ -99,83 +94,6 @@ local DESKTOP = HOME .. PS .. "Desktop" -- F U N C T I O N S -- - - - - - - - - - - - - - - - - - - - - - - - -local function build_substitution_list(image, sequence, datetime, username, pic_folder, home, desktop) - -- build the argument substitution list from each image - -- local datetime = os.date("*t") - local colorlabels = {} - if image.red then table.insert(colorlabels, "red") end - if image.yellow then table.insert(colorlabels, "yellow") end - if image.green then table.insert(colorlabels, "green") end - if image.blue then table.insert(colorlabels, "blue") end - if image.purple then table.insert(colorlabels, "purple") end - local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",") - local eyear,emon,eday,ehour,emin,esec = string.match(image.exif_datetime_taken, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d-)$") - local replacements = {image.film, - image.path, - df.get_filename(image.filename), - string.upper(df.get_filetype(image.filename)), - image.id,image.duplicate_index, - string.format("%04d", sequence), - datetime.year, - string.format("%02d", datetime.month), - string.format("%02d", datetime.day), - string.format("%02d", datetime.hour), - string.format("%02d", datetime.min), - string.format("%02d", datetime.sec), - eyear, - emon, - eday, - ehour, - emin, - esec, - image.rating, - labels, - image.exif_maker, - image.exif_model, - image.title, - image.creator, - image.publisher, - image.rights, - username, - pic_folder, - home, - desktop, - image.exif_iso, - image.exif_exposure, - image.exif_exposure_bias, - image.exif_aperture, - image.exif_focus_distance, - image.exif_focal_length, - image.longitude, - image.latitude, - image.elevation, - image.exif_lens, - image.description, - image.exif_crop - } - - for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = replacements[i] end -end - -local function substitute_list(str) - -- replace the substitution variables in a string - for match in string.gmatch(str, "%$%(.-%)") do - local var = string.match(match, "%$%((.-)%)") - if rename.substitutes[var] then - str = string.gsub(str, "%$%("..var.."%)", rename.substitutes[var]) - else - dt.print_error("unrecognized variable " .. var) - dt.print(string.format(_("unknown variable %s, aborting..."), var)) - return -1 - end - end - return str -end - -local function clear_substitute_list() - for i=1,#rename.placeholders,1 do rename.substitutes[rename.placeholders[i]] = nil end -end - local function stop_job(job) job.valid = false end @@ -225,14 +143,14 @@ local function do_rename(images) for i, image in ipairs(images) do if job.valid then job.percent = i / #images - build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP) - local new_name = substitute_list(pattern) + ds.build_substitution_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP) + local new_name = ds.substitute_list(pattern) if new_name == -1 then dt.print(_("unable to do variable substitution, exiting...")) stop_job(job) return end - clear_substitute_list() + ds.clear_substitute_list() local args = {} local path = string.sub(df.get_path(new_name), 1, -2) if string.len(path) == 0 then @@ -278,50 +196,8 @@ end -- - - - - - - - - - - - - - - - - - - - - - - - rename.widgets.pattern = dt.new_widget("entry"){ - tooltip = _("$(ROLL_NAME) - film roll name\n") .. - _("$(FILE_FOLDER) - image file folder\n") .. - _("$(FILE_NAME) - image file name\n") .. - _("$(FILE_EXTENSION) - image file extension\n") .. - _("$(ID) - image id\n") .. - _("$(VERSION) - version number\n") .. - _("$(SEQUENCE) - sequence number of selection\n") .. - _("$(YEAR) - current year\n") .. - _("$(MONTH) - current month\n") .. - _("$(DAY) - current day\n") .. - _("$(HOUR) - current hour\n") .. - _("$(MINUTE) - current minute\n") .. - _("$(SECOND) - current second\n") .. - _("$(EXIF_YEAR) - EXIF year\n") .. - _("$(EXIF_MONTH) - EXIF month\n") .. - _("$(EXIF_DAY) - EXIF day\n") .. - _("$(EXIF_HOUR) - EXIF hour\n") .. - _("$(EXIF_MINUTE) - EXIF minute\n") .. - _("$(EXIF_SECOND) - EXIF seconds\n") .. - _("$(EXIF_ISO) - EXIF ISO\n") .. - _("$(EXIF_EXPOSURE) - EXIF exposure\n") .. - _("$(EXIF_EXPOSURE_BIAS) - EXIF exposure bias\n") .. - _("$(EXIF_APERTURE) - EXIF aperture\n") .. - _("$(EXIF_FOCAL_LENGTH) - EXIF focal length\n") .. - _("$(EXIF_FOCUS_DISTANCE) - EXIF focus distance\n") .. - _("$(EXIF_CROP) - EXIF crop\n") .. - _("$(LONGITUDE) - longitude\n") .. - _("$(LATITUDE) - latitude\n") .. - _("$(ELEVATION) - elevation\n") .. - _("$(STARS) - star rating\n") .. - _("$(LABELS) - color labels\n") .. - _("$(MAKER) - camera maker\n") .. - _("$(MODEL) - camera model\n") .. - _("$(LENS) - lens\n") .. - _("$(TITLE) - title from metadata\n") .. - _("$(DESCRIPTION) - description from metadata\n") .. - _("$(CREATOR) - creator from metadata\n") .. - _("$(PUBLISHER) - publisher from metadata\n") .. - _("$(RIGHTS) - rights from metadata\n") .. - _("$(USERNAME) - username\n") .. - _("$(PICTURES_FOLDER) - pictures folder\n") .. - _("$(HOME) - user's home directory\n") .. - _("$(DESKTOP) - desktop directory"), - placeholder = _("enter pattern $(FILE_FOLDER)/$(FILE_NAME)"), + tooltip = ds.get_substitution_tooltip(), + placeholder = _("enter pattern") .. "$(FILE_FOLDER)/$(FILE_NAME)", text = "" } From ce7ddfa4722d2ecd31aa561f75ed21bc32b81f25 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 10 Sep 2024 21:19:37 -0400 Subject: [PATCH 070/169] examples/*.lua - cleaned up translatable messages --- examples/api_version.lua | 4 +--- examples/darkroom_demo.lua | 4 +--- examples/gettextExample.lua | 2 -- examples/gui_action.lua | 39 ++++++++++++++++----------------- examples/hello_world.lua | 2 -- examples/moduleExample.lua | 14 +++++------- examples/multi_os.lua | 6 ++--- examples/panels_demo.lua | 8 +++---- examples/preferenceExamples.lua | 30 ++++++++++++------------- examples/printExamples.lua | 2 -- examples/running_os.lua | 2 -- examples/x-touch.lua | 1 - 12 files changed, 46 insertions(+), 68 deletions(-) diff --git a/examples/api_version.lua b/examples/api_version.lua index d54b7883..aaf341c7 100644 --- a/examples/api_version.lua +++ b/examples/api_version.lua @@ -27,8 +27,6 @@ local dt = require "darktable" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("api_version", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end @@ -41,7 +39,7 @@ end local result = dt.configuration.api_version_string dt.print_log("API Version: " .. result) -dt.print(string.format(_("API version: %s"), result)) +dt.print("API " .. _("version") .. ": " .. result) -- set the destroy routine so that script_manager can call it when -- it's time to destroy the script and then return the data to diff --git a/examples/darkroom_demo.lua b/examples/darkroom_demo.lua index 802df749..72bf1324 100644 --- a/examples/darkroom_demo.lua +++ b/examples/darkroom_demo.lua @@ -60,8 +60,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- - - - - - - - - - - - - - - - - - - - - - - - local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("darkroom_demo", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -93,7 +91,7 @@ dt.gui.current_view(dt.gui.views.darkroom) local max_images = 10 -dt.print(_("showing images, with a pause in between each")) +dt.print(_("showing images, with a pause between each")) sleep(1500) -- display first 10 images of collection pausing for a second between each diff --git a/examples/gettextExample.lua b/examples/gettextExample.lua index 20d5db43..acffdca2 100644 --- a/examples/gettextExample.lua +++ b/examples/gettextExample.lua @@ -69,8 +69,6 @@ dt.print_error("Hello World!") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("gettextExample", dt.configuration.config_dir .."/lua/locale/") - -- Translate a string using the darktable textdomain dt.print_error(gettext("image")) diff --git a/examples/gui_action.lua b/examples/gui_action.lua index 3bff94b6..d0e707df 100644 --- a/examples/gui_action.lua +++ b/examples/gui_action.lua @@ -5,7 +5,6 @@ local NaN = 0/0 local wg = {} local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) @@ -29,37 +28,37 @@ script_data.show = nil -- only required for libs since the destroy_method only h wg.action = dt.new_widget("entry"){ text = "lib/filter/view", - placeholder = "action path", - tooltip = "enter the full path of an action, for example 'lib/filter/view'" + placeholder = _("action path"), + tooltip = _("enter the full path of an action, for example 'lib/filter/view'") } wg.instance = dt.new_widget("combobox"){ - label = "instance", - tooltip = "the instance of an image processing module to execute action on", + label = _("instance"), + tooltip = _("the instance of an image processing module to execute action on"), "0", "+1", "-1", "+2", "-2", "+3", "-3", "+4", "-4", "+5", "-5", "+6", "-6", "+7", "-7", "+8", "-8", "+9", "-9" } wg.element = dt.new_widget("entry"){ text = "", - placeholder = "action element", - tooltip = "enter the element of an action, for example 'selection', or leave empty for default" + placeholder = _("action element"), + tooltip = _("enter the element of an action, for example 'selection', or leave empty for default") } wg.effect = dt.new_widget("entry"){ text = "next", - placeholder = "action effect", - tooltip = "enter the effect of an action, for example 'next', or leave empty for default" + placeholder = _("action effect"), + tooltip = _("enter the effect of an action, for example 'next', or leave empty for default") } wg.speed = dt.new_widget("entry"){ text = "1", - placeholder = "action speed", - tooltip = "enter the speed to use in action execution, or leave empty to only read state" + placeholder = _("action speed"), + tooltip = _("enter the speed to use in action execution, or leave empty to only read state") } wg.check = dt.new_widget("check_button"){ - label = 'perform action', - tooltip = 'perform action or only read return', + label = _('perform action'), + tooltip = _('perform action or only read return'), clicked_callback = function() wg.speed.sensitive = wg.check.value end, @@ -73,7 +72,7 @@ wg.return_value = dt.new_widget("entry"){ dt.register_lib( "execute_action", -- Module name - "execute gui actions", -- name + _("execute gui actions"), -- name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100}, @@ -85,33 +84,33 @@ dt.register_lib( dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label"){label = "action path", halign = "start"}, + dt.new_widget("label"){label = _("action path"), halign = "start"}, wg.action }, wg.instance, dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label"){label = "element", halign = "start"}, + dt.new_widget("label"){label = _("element"), halign = "start"}, wg.element }, dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label"){label = "effect", halign = "start"}, + dt.new_widget("label"){label = _("effect"), halign = "start"}, wg.effect }, wg.check, dt.new_widget("box") { orientation = "horizontal", - dt.new_widget("label"){label = "speed", halign = "start"}, + dt.new_widget("label"){label = _("speed"), halign = "start"}, wg.speed }, dt.new_widget("button") { - label = "execute action", - tooltip = "execute the action specified in the fields above", + label = _("execute action"), + tooltip = _("execute the action specified in the fields above"), clicked_callback = function(_) local sp = NaN if wg.check.value then sp = wg.speed.text end diff --git a/examples/hello_world.lua b/examples/hello_world.lua index e64c4f9a..d29c99c7 100644 --- a/examples/hello_world.lua +++ b/examples/hello_world.lua @@ -35,8 +35,6 @@ du.check_min_api_version("2.0.0", "hello_world") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("hello_world", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index edf4815a..6996d2a9 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -38,8 +38,6 @@ du.check_min_api_version("7.0.0", "moduleExample") -- https://www.darktable.org/lua-api/index.html#darktable_gettext local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("moduleExample", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -87,7 +85,7 @@ local function install_module() orientation = "vertical", dt.new_widget("button") { - label = _("my button"), + label = _("my ") .. "button", clicked_callback = function (_) dt.print(_("button clicked")) end @@ -112,10 +110,10 @@ local function restart() end -- https://www.darktable.org/lua-api/types_lua_check_button.html -local check_button = dt.new_widget("check_button"){label = _("my check_button"), value = true} +local check_button = dt.new_widget("check_button"){label = _("my ") .. "check_button", value = true} -- https://www.darktable.org/lua-api/types_lua_combobox.html -local combobox = dt.new_widget("combobox"){label = _("my combobox"), value = 2, "8", "16", "32"} +local combobox = dt.new_widget("combobox"){label = _("my ") .. "combobox", value = 2, "8", "16", "32"} -- https://www.darktable.org/lua-api/types_lua_entry.html local entry = dt.new_widget("entry") @@ -131,14 +129,14 @@ local entry = dt.new_widget("entry") -- https://www.darktable.org/lua-api/types_lua_file_chooser_button.html local file_chooser_button = dt.new_widget("file_chooser_button") { - title = _("my file_chooser_button"), -- The title of the window when choosing a file + title = _("my ") .. "file_chooser_button", -- The title of the window when choosing a file value = "", -- The currently selected file is_directory = false -- True if the file chooser button only allows directories to be selecte } -- https://www.darktable.org/lua-api/types_lua_label.html local label = dt.new_widget("label") -label.label = _("my label") -- This is an alternative way to the "{}" syntax to set a property +label.label = _("my ") .. "label" -- This is an alternative way to the "{}" syntax to set a property -- https://www.darktable.org/lua-api/types_lua_separator.html local separator = dt.new_widget("separator"){} @@ -146,7 +144,7 @@ local separator = dt.new_widget("separator"){} -- https://www.darktable.org/lua-api/types_lua_slider.html local slider = dt.new_widget("slider") { - label = _("my slider"), + label = _("my ") .. "slider", soft_min = 10, -- The soft minimum value for the slider, the slider can't go beyond this point soft_max = 100, -- The soft maximum value for the slider, the slider can't go beyond this point hard_min = 0, -- The hard minimum value for the slider, the user can't manually enter a value beyond this point diff --git a/examples/multi_os.lua b/examples/multi_os.lua index a1eea24b..83bfee98 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -72,8 +72,6 @@ local dtsys = require "lib/dtutils.system" -- system utilities local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("multi_os", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -215,7 +213,7 @@ end if dt.configuration.running_os ~= "linux" then local executable = "ufraw-batch" local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){ - title = _("select ufraw-batch[.exe] executable"), + title = string.format(_("select %s executable"), "ufraw-batch[.exe]"), value = df.get_executable_path_preference(executable), is_directory = false, changed_callback = function(self) @@ -226,7 +224,7 @@ if dt.configuration.running_os ~= "linux" then } dt.preferences.register("executable_paths", "ufraw-batch", -- name "file", -- type - _('multi_os: ufraw-batch location'), -- label + 'multi_os: ufraw-batch ' .. _('location'), -- label _('installed location of ufraw-batch, requires restart to take effect.'), -- tooltip "ufraw-batch", -- default ufraw_batch_path_widget diff --git a/examples/panels_demo.lua b/examples/panels_demo.lua index 36e31e3c..6fa82607 100644 --- a/examples/panels_demo.lua +++ b/examples/panels_demo.lua @@ -61,8 +61,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- - - - - - - - - - - - - - - - - - - - - - - - local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("panels_demo", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -95,11 +93,11 @@ dt.gui.panel_show_all() -- hide center_top, center_bottom, left, top, right, bottom in order -dt.print(_("hiding all panels, one at a tme")) +dt.print(_("hiding all panels, one at a time")) sleep(1500) for i = 1, #panels do - dt.print(_("hiding " .. panels[i])) + dt.print(string.format(_("hiding %s"), panels[i])) dt.gui.panel_hide(panels[i]) sleep(1500) end @@ -110,7 +108,7 @@ end sleep(1500) for i = #panels, 1, -1 do - dt.print(_("showing " .. panels[i])) + dt.print(string.format(_("showing %s"), panels[i])) dt.gui.panel_show(panels[i]) sleep(1500) end diff --git a/examples/preferenceExamples.lua b/examples/preferenceExamples.lua index e3721ca6..c53ce163 100644 --- a/examples/preferenceExamples.lua +++ b/examples/preferenceExamples.lua @@ -28,8 +28,6 @@ local du = require "lib/dtutils" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("preferenceExamples", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end @@ -49,22 +47,22 @@ du.check_min_api_version("2.0.1", "preferenceExamples") dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesString", -- name "string", -- type - _("example string"), -- label - _("example string tooltip"), -- tooltip + _("example") .. " string", -- label + _("example") .. " string " .. _("tooltip"), -- tooltip "String") -- default dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesBool", -- name "bool", -- type - _("example boolean"), -- label - _("example boolean tooltip"), -- tooltip + _("example") .. " boolean", -- label + _("example") .. " boolean " .. _("tooltip"), -- tooltip true) -- default dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesInteger", -- name "integer", -- type - _("example integer"), -- label - _("example integer tooltip"), -- tooltip + _("example") .. " integer", -- label + _("example") .. " integer " .. _("tooltip"), -- tooltip 2, -- default 1, -- min 99) -- max @@ -72,8 +70,8 @@ dt.preferences.register("preferenceExamples", -- script: This is a string dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesFloat", -- name "float", -- type - _("example float"), -- label - _("example float tooltip"), -- tooltip + _("example") .. " float", -- label + _("example") .. " float " .. _("tooltip"), -- tooltip 1.3, -- default 1, -- min 99, -- max @@ -82,22 +80,22 @@ dt.preferences.register("preferenceExamples", -- script: This is a string dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesFile", -- name "file", -- type - _("example file"), -- label - _("example file tooltip"), -- tooltip + _("example") .. " file", -- label + _("example") .. " file " .. _("tooltip"), -- tooltip "") -- default dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesDirectory", -- name "directory", -- type - _("example directory"), -- label - _("example directory tooltip"), -- tooltip + _("example") .. " directory", -- label + _("example") .. " directory " .. _("tooltip"), -- tooltip "") -- default dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesEnum", -- name "enum", -- type - _("example enum"), -- label - _("example enum tooltip"), -- tooltip + _("example") .. " enum", -- label + _("example") .. " enum " .. _("tooltip"), -- tooltip "Enum 1", -- default "Enum 1", "Enum 2") -- values diff --git a/examples/printExamples.lua b/examples/printExamples.lua index 23614c5b..4bedaffa 100644 --- a/examples/printExamples.lua +++ b/examples/printExamples.lua @@ -28,8 +28,6 @@ du.check_min_api_version("5.0.0", "printExamples") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("printExamples", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/examples/running_os.lua b/examples/running_os.lua index 1e61cb84..3c45cee8 100644 --- a/examples/running_os.lua +++ b/examples/running_os.lua @@ -34,8 +34,6 @@ du.check_min_api_version("5.0.0", "running_os") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("running_os", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/examples/x-touch.lua b/examples/x-touch.lua index dd8dd2e9..969fd105 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -56,7 +56,6 @@ local du = require "lib/dtutils" du.check_min_api_version("9.2.0", "x-touch") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("gui_action", dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) From 8863f6ade740b97060da2846aa3979c92d7e8c77 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 13 Sep 2024 14:46:47 -0400 Subject: [PATCH 071/169] tools/get*.lua - updated translable stirngs --- tools/get_lib_manpages.lua | 2 -- tools/get_libdoc.lua | 2 -- 2 files changed, 4 deletions(-) diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index a5a7ab97..65c68046 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -15,8 +15,6 @@ du.check_min_api_version("3.0.0", "get_lib_manpages") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("get_lib_manpages", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 3749a683..78bbf760 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -12,8 +12,6 @@ du.check_min_api_version("3.0.0", "get_libdoc") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("get_libdoc", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end From a72abe0ad0a01f422e36bf6dadfd1fd2cf2f5a6f Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 13 Sep 2024 14:47:37 -0400 Subject: [PATCH 072/169] tools/executable_manager - cleaned up translatable strings. Changed displayed name to executables. --- tools/executable_manager.lua | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index ffdf532d..97421e4e 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -37,8 +37,6 @@ du.check_min_api_version("7.0.0", "executable_manager") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("executable_manager", dt.configuration.config_dir .."/lua/locale/") - local function _(msg) return gettext(msg) end @@ -117,7 +115,7 @@ local function install_module() if not exec_man.module_installed then dt.register_lib( "executable_manager", -- Module name - "executable manager", -- Visible name + _("executables"), -- Visible name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {panel, panel_pos}}, -- containers @@ -201,7 +199,7 @@ exec_man.stack = dt.new_widget("stack"){} -- create a combobox to for indexing into the stack of widgets exec_man.selector = dt.new_widget("combobox"){ - label = "executable", + label = _("executable"), tooltip = _("select executable to modify"), value = 1, "placeholder", changed_callback = function(self) From 684178b27e48dbacb82718650468e27eb25ec3ef Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 13 Sep 2024 14:48:30 -0400 Subject: [PATCH 073/169] tools/script_manager - cleaned up translatable strings. Changed displayed name to scripts. --- tools/script_manager.lua | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index fe8fa6ab..52d20825 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -58,10 +58,6 @@ local debug = require "darktable.debug" local gettext = dt.gettext - --- Tell gettext where to find the .mo file translating messages for a particular domain -dt.gettext.bindtextdomain("script_manager", dt.configuration.config_dir .. "/lua/locale/") - local function _(msgid) return gettext.dgettext("script_manager", msgid) end @@ -469,7 +465,7 @@ local function get_script_doc(script) return description else restore_log_level(old_log_level) - return "No documentation available" + return _("No documentation available") end end @@ -835,7 +831,7 @@ local function install_scripts() if count > 0 then update_combobox_choices(sm.widgets.folder_selector, sm.folders, sm.widgets.folder_selector.selected) - dt.print(_("scripts successfully installed into folder ") .. folder) + dt.print(_(string.format("scripts successfully installed into folder %s"), folder)) table.insert(sm.installed_repositories, {name = folder, directory = LUA_DIR .. PS .. folder}) update_script_update_choices() @@ -1165,7 +1161,7 @@ local function install_module() if not sm.module_installed then dt.register_lib( "script_manager", -- Module name - "script manager", -- Visible name + _("scripts"), -- Visible name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 100}}, -- containers @@ -1408,7 +1404,7 @@ end local page_back = "<" local page_forward = ">" -sm.widgets.page_status = dt.new_widget("label"){label = _("page:")} +sm.widgets.page_status = dt.new_widget("label"){label = _("page") .. ":"} sm.widgets.page_back = dt.new_widget("button"){ label = page_back, From 5bed7c914c36a0faae49a5bb4f039dd4a7425ff7 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 13 Sep 2024 19:47:44 -0400 Subject: [PATCH 074/169] contrib/[a-f]*.lua - fixed translatable strings to prepare for darktable translation --- contrib/AutoGrouper.lua | 1 - contrib/CollectHelper.lua | 4 +--- contrib/auto_snapshot.lua | 8 +++----- contrib/autostyle.lua | 1 - contrib/change_group_leader.lua | 6 ++---- contrib/clear_GPS.lua | 3 +-- contrib/color_profile_manager.lua | 2 -- contrib/copy_attach_detach_tags.lua | 2 -- contrib/cr2hdr.lua | 2 -- contrib/cycle_group_leader.lua | 4 +--- contrib/dbmaint.lua | 20 +++++++++----------- contrib/enfuseAdvanced.lua | 12 +++++------- contrib/exportLUT.lua | 2 -- contrib/ext_editor.lua | 2 -- contrib/face_recognition.lua | 2 -- contrib/fujifilm_dynamic_range.lua | 2 -- contrib/fujifilm_ratings.lua | 4 +--- 17 files changed, 23 insertions(+), 54 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 90f65dbc..72654ae2 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -44,7 +44,6 @@ du.check_min_api_version("7.0.0", "AutoGrouper") local MOD = 'autogrouper' local gettext = dt.gettext -gettext.bindtextdomain(MOD, dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext.gettext(msgid) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index ca76d857..f06b14e4 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -53,8 +53,6 @@ local all_active = false du.check_min_api_version("7.0.0", "CollectHelper") -dt.gettext.bindtextdomain("CollectHelper", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -239,7 +237,7 @@ end dt.preferences.register("module_CollectHelper", "all_and", -- name "bool", -- type _('CollectHelper: all'), -- label - _('will create a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip + _('creates a collect parameter set that utilizes all enabled CollectHelper types (and)'), -- tooltip true -- default ) dt.preferences.register("module_CollectHelper", "colors", -- name diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua index f42631f4..3b883412 100644 --- a/contrib/auto_snapshot.lua +++ b/contrib/auto_snapshot.lua @@ -59,8 +59,6 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/") - local function _(msgid) return gettext(MODULE, msgid) end @@ -81,7 +79,7 @@ script_data.metadata = { name = "auto_snapshot", -- name of script purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script author = "Bill Ferguson ", -- your name and optionally e-mail address - help = "" -- URL to help/documentation + help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_snapshot/" -- URL to help/documentation } @@ -101,8 +99,8 @@ local auto_snapshot = {} -- P R E F E R E N C E S -- - - - - - - - - - - - - - - - - - - - - - - - -dt.preferences.register(MODULE, "always_create_snapshot", "bool", "always automatically create_snapshot", - "auto_snapshot - create a snapshot even if the image is altered", false) +dt.preferences.register(MODULE, "always_create_snapshot", "bool", "auto_snapshot - " .. _("always automatically create_snapshot"), + _("create a snapshot even if the image is altered"), false) -- - - - - - - - - - - - - - - - - - - - - - - - -- D A R K T A B L E I N T E G R A T I O N diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 336e6f78..46b14cac 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -44,7 +44,6 @@ local syslib = require "lib/dtutils.system" du.check_min_api_version("7.0.0", "autostyle") local gettext = darktable.gettext.gettext -darktable.gettext.bindtextdomain("autostyle", darktable.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) diff --git a/contrib/change_group_leader.lua b/contrib/change_group_leader.lua index 11522cbd..e7f424b7 100644 --- a/contrib/change_group_leader.lua +++ b/contrib/change_group_leader.lua @@ -40,8 +40,6 @@ local gettext = dt.gettext.gettext local MODULE = "change_group_leader" -dt.gettext.bindtextdomain(MODULE, dt.configuration.config_dir .."/lua/locale/") - du.check_min_api_version("3.0.0", MODULE) local function _(msgid) @@ -65,7 +63,7 @@ script_data.restart = nil -- how to restart the (lib) script after it's been hid script_data.show = nil -- only required for libs since the destroy_method only hides them -- create a namespace to contain persistent data and widgets -chg_grp_ldr = {} +local chg_grp_ldr = {} local cgl = chg_grp_ldr @@ -82,7 +80,7 @@ local function install_module() if not cgl.module_installed then dt.register_lib( MODULE, -- Module name - _("change_group_leader"), -- Visible name + _("change group leader"), -- Visible name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700}}, -- containers diff --git a/contrib/clear_GPS.lua b/contrib/clear_GPS.lua index f90d8b8f..8c591661 100644 --- a/contrib/clear_GPS.lua +++ b/contrib/clear_GPS.lua @@ -40,7 +40,6 @@ local dt = require "darktable" local du = require "lib/dtutils" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("clear_GPS", dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) @@ -92,7 +91,7 @@ dt.gui.libs.image.register_action( dt.register_event( "clear_GPS", "shortcut", function(event, shortcut) clear_GPS(dt.gui.action_images) end, - _("clear GPS data") + _("clear GPS data from selected images") ) return script_data diff --git a/contrib/color_profile_manager.lua b/contrib/color_profile_manager.lua index 52fc38ce..6114992e 100644 --- a/contrib/color_profile_manager.lua +++ b/contrib/color_profile_manager.lua @@ -55,8 +55,6 @@ du.check_min_api_version("7.0.0", "color_profile_manager") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("color_profile_manager", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index 6d6c470c..2b66bc76 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -44,8 +44,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "copy_attach_detach_tags") -dt.gettext.bindtextdomain("copy_attach_detach_tags", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/cr2hdr.lua b/contrib/cr2hdr.lua index a091a8f1..209287cc 100644 --- a/contrib/cr2hdr.lua +++ b/contrib/cr2hdr.lua @@ -39,8 +39,6 @@ du.check_min_api_version("7.0.0", "cr2hdr") local gettext = darktable.gettext.gettext -darktable.gettext.bindtextdomain("cr2hdr", darktable.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/cycle_group_leader.lua b/contrib/cycle_group_leader.lua index 30ed0b8e..665f0c09 100644 --- a/contrib/cycle_group_leader.lua +++ b/contrib/cycle_group_leader.lua @@ -60,8 +60,6 @@ du.check_min_api_version("7.0.0", MODULE) local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("cycle_group_leader", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -106,7 +104,7 @@ end local function cycle_group_leader(image) local group_images = image:get_group_members() if #group_images < 2 then - hinter_msg(_("no images to cycle to in group")) + hinter_msg(_("no images to cycle through in group")) return else local position = nil diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua index 70454ff6..8d24f1d2 100644 --- a/contrib/dbmaint.lua +++ b/contrib/dbmaint.lua @@ -63,8 +63,6 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain(MODULE , dt.configuration.config_dir .. "/lua/locale/") - local function _(msgid) return gettext(MODULE, msgid) end @@ -195,21 +193,21 @@ dbmaint.list_widget = dt.new_widget("text_view"){ } dbmaint.chooser = dt.new_widget("combobox"){ - label = "scan for", + label = _("scan for"), selected = 1, - "film rolls", "images", + _("film rolls"), _("images"), reset_callback = function(this) this.selected = 1 end } dbmaint.scan_button = dt.new_widget("button"){ - label = "scan", - tooltip = "click to scan for missing film rolls/files", + label = _("scan"), + tooltip = _("click to scan for missing film rolls/files"), clicked_callback = function(this) local found = nil local found_text = "" - if dbmaint.chooser.value == "film rolls" then + if dbmaint.chooser.selected == 1 then -- film rolls found = scan_film_rolls() if #found > 0 then for _, film in ipairs(found) do @@ -238,11 +236,11 @@ dbmaint.scan_button = dt.new_widget("button"){ } dbmaint.remove_button = dt.new_widget("button"){ - label = "remove", - tooltip = "remove missing film rolls/images", + label = _("remove"), + tooltip = _("remove missing film rolls/images"), sensitive = false, clicked_callback = function(this) - if dbmaint.chooser.value == "film rolls" then + if dbmaint.chooser.selected == 1 then -- film rolls remove_missing_film_rolls(dbmaint.found) else remove_missing_images(dbmaint.found) @@ -258,7 +256,7 @@ dbmaint.remove_button = dt.new_widget("button"){ dbmaint.main_widget = dt.new_widget("box"){ orientation = "vertical", - dt.new_widget("section_label"){label = "missing items"}, + dt.new_widget("section_label"){label = _("missing items")}, dbmaint.list_widget, dt.new_widget("label"){label = ""}, dbmaint.chooser, diff --git a/contrib/enfuseAdvanced.lua b/contrib/enfuseAdvanced.lua index b5ec586e..b35cb490 100644 --- a/contrib/enfuseAdvanced.lua +++ b/contrib/enfuseAdvanced.lua @@ -70,8 +70,6 @@ du.check_min_api_version("7.0.0", "enfuseAdvanced") -- Tell gettext where to find the .mo file translating messages for a particular domain local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("enfuseAdvanced", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -445,7 +443,7 @@ local function main(storage, image_table, extra_data) dt.print(_('too few images selected, please select at least 2 images')) return elseif extra_data[1] == 2 then - dt.print(_('installation error, please verify binary paths are proper')) + dt.print(_('installation error, please verify binary paths are correct')) return end local images_to_remove = '' @@ -956,7 +954,7 @@ if temp == '' then temp = nil end GUI.Target.add_tags = dt.new_widget('entry'){ tooltip = _('additional tags to be added on import, seperate with commas, all spaces will be removed'), text = temp, - placeholder = _('enter tags, seperated by commas'), + placeholder = _('enter tags, separated by commas'), editable = true } temp = dt.preferences.read(mod, 'active_current_preset_ind', 'integer') @@ -1023,7 +1021,7 @@ GUI.Presets.variants_type = dt.new_widget('combobox'){ GUI.Presets.variants_type.sensitive = GUI.Presets.variants.value temp = df.get_executable_path_preference(AIS.name) GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){ - title = 'AIS binary path', + title = 'align_image_stack ' .. _('binary path'), value = temp, tooltip = temp, is_directory = false, @@ -1031,7 +1029,7 @@ GUI.exes.align_image_stack = dt.new_widget('file_chooser_button'){ } temp = df.get_executable_path_preference(ENF.name) GUI.exes.enfuse = dt.new_widget('file_chooser_button'){ - title = 'enfuse binary path', + title = 'enfuse ' .. _('binary path'), value = temp, tooltip = temp, is_directory = false, @@ -1039,7 +1037,7 @@ GUI.exes.enfuse = dt.new_widget('file_chooser_button'){ } temp = df.get_executable_path_preference(EXF.name) GUI.exes.exiftool = dt.new_widget('file_chooser_button'){ - title = 'Exiftool binary path', + title = 'exiftool ' .. _('binary path'), value = temp, tooltip = temp, is_directory = false, diff --git a/contrib/exportLUT.lua b/contrib/exportLUT.lua index be904cdd..80854567 100644 --- a/contrib/exportLUT.lua +++ b/contrib/exportLUT.lua @@ -35,8 +35,6 @@ du.check_min_api_version("7.0.0", "exportLUT") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("exportLUT", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/ext_editor.lua b/contrib/ext_editor.lua index 592550bc..0cee9454 100644 --- a/contrib/ext_editor.lua +++ b/contrib/ext_editor.lua @@ -78,8 +78,6 @@ du.check_min_api_version("7.0.0", MODULE_NAME) -- translation local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("ext_editor", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/face_recognition.lua b/contrib/face_recognition.lua index 0f6183bd..74827f12 100644 --- a/contrib/face_recognition.lua +++ b/contrib/face_recognition.lua @@ -54,8 +54,6 @@ local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt" du.check_min_api_version("7.0.0", MODULE) -dt.gettext.bindtextdomain("face_recognition", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/fujifilm_dynamic_range.lua b/contrib/fujifilm_dynamic_range.lua index 6116d352..7bb6ab53 100644 --- a/contrib/fujifilm_dynamic_range.lua +++ b/contrib/fujifilm_dynamic_range.lua @@ -65,8 +65,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "fujifilm_dynamic_range") -dt.gettext.bindtextdomain("fujifilm_dynamic_range", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index 049e3c20..c9a034d8 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "fujifilm_ratings") -dt.gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -58,7 +56,7 @@ local function detect_rating(event, image) return end if not df.check_if_bin_exists("exiftool") then - dt.print_error(_("exiftool not found")) + dt.print_error("exiftool not found") return end local RAF_filename = df.sanitize_filename(tostring(image)) From 4165d46b33989f80eb5a3f44adaf730350ff653c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 16 Sep 2024 20:04:29 -0400 Subject: [PATCH 075/169] tools/script_manager - fixed capitalization --- tools/script_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 52d20825..7991672e 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -465,7 +465,7 @@ local function get_script_doc(script) return description else restore_log_level(old_log_level) - return _("No documentation available") + return _("no documentation available") end end From 842f2fca66a1c6f9121454c60d51093a2fe6b163 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 16 Sep 2024 21:16:34 -0400 Subject: [PATCH 076/169] contrib/[g-o]*.lua - clean up translatable strings --- contrib/HDRMerge.lua | 10 ++++------ contrib/LabelsToTags.lua | 8 +++----- contrib/OpenInExplorer.lua | 2 -- contrib/geoJSON_export.lua | 12 +++++------- contrib/geoToolbox.lua | 10 ++++------ contrib/gimp.lua | 2 -- contrib/gpx_export.lua | 4 +--- contrib/harmonic_armature_guide.lua | 2 -- contrib/hif_group_leader.lua | 6 ++---- contrib/hugin.lua | 6 ++---- contrib/image_stack.lua | 10 ++++------ contrib/image_time.lua | 6 ++---- contrib/jpg_group_leader.lua | 6 ++---- contrib/kml_export.lua | 8 +++----- 14 files changed, 32 insertions(+), 60 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 5ab1bee2..76067d53 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -54,8 +54,6 @@ du.check_min_api_version("7.0.0", "HDRmerge") -- Tell gettext where to find the .mo file translating messages for a particular domain local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("HDRMerge", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -218,7 +216,7 @@ local function main() PreCall({HDRM}) --check if furst run then check if install OK if HDRM.install_error then dt.print_error('HDRMerge install issue') - dt.print(_('HDRMerge install issue, please ensure the binary path is proper')) + dt.print(_('HDRMerge install issue, please ensure the binary path is correct')) return end images = dt.gui.selection() --get selected images @@ -416,14 +414,14 @@ GUI.Target.copy_tags = dt.new_widget('check_button'){ temp = dt.preferences.read(mod, 'active_add_tags', 'string') if temp == '' then temp = nil end GUI.Target.add_tags = dt.new_widget('entry'){ - tooltip = _('additional tags to be added on import, seperate with commas, all spaces will be removed'), + tooltip = _('additional tags to be added on import, separate with commas, all spaces will be removed'), text = temp, - placeholder = _('enter tags, seperated by commas'), + placeholder = _('enter tags, separated by commas'), editable = true } GUI.run = dt.new_widget('button'){ label = _('merge'), - tooltip =_('run HDRMerge with the above specified settings'), + tooltip =_('run HDRMerge with the above settings'), clicked_callback = function() main() end } GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){ diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 1054a8b9..0c00c123 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -54,8 +54,6 @@ du.check_min_api_version("7.0.0", "LabelsToTags") local gettext = darktable.gettext.gettext -darktable.gettext.bindtextdomain("LabelsToTags", darktable.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -216,8 +214,8 @@ ltt.my_widget = darktable.new_widget("box") { orientation = "vertical", mappingComboBox, darktable.new_widget("button") { - label = "start", - tooltip = "Tag all selected images", + label = _("start"), + tooltip = _("tag all selected images"), clicked_callback = doTagging } } @@ -247,7 +245,7 @@ end local function install_module() if not ltt.module_installed then - darktable.register_lib(LIB_ID,"labels to tags",true,true,{ + darktable.register_lib(LIB_ID,_("labels to tags"),true,true,{ [darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20}, },ltt.my_widget,nil,nil) ltt.module_installed = true diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 1633a1a5..4ba59690 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -56,8 +56,6 @@ local gettext = dt.gettext.gettext --Check API version du.check_min_api_version("7.0.0", "OpenInExplorer") -dt.gettext.bindtextdomain("OpenInExplorer", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index dc336b2a..aff04b22 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -40,8 +40,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "geoJSON_export") -dt.gettext.bindtextdomain("geoJSON_export", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -92,19 +90,19 @@ end local function create_geoJSON_file(storage, image_table, extra_data) if not df.check_if_bin_exists("mkdir") then - dt.print_error(_("mkdir not found")) + dt.print_error("mkdir not found") return end if not df.check_if_bin_exists("convert") then - dt.print_error(_("convert not found")) + dt.print_error("convert not found") return end if not df.check_if_bin_exists("xdg-open") then - dt.print_error(_("xdg-open not found")) + dt.print_error("xdg-open not found") return end if not df.check_if_bin_exists("xdg-user-dir") then - dt.print_error(_("xdg-user-dir not found")) + dt.print_error("xdg-user-dir not found") return end @@ -295,7 +293,7 @@ local function create_geoJSON_file(storage, image_table, extra_data) file:write(geoJSON_file) file:close() - dt.print("geoJSON file created in "..exportDirectory) + dt.print(string.format(_("%s file created in %s", "geoJSON", exportDirectory))) -- Open the file with the standard programm if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 677af0a1..f2ab6957 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -33,8 +33,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "geoToolbox") -dt.gettext.bindtextdomain("geoToolbox", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -77,7 +75,7 @@ local label_copy_gps_lon = dt.new_widget("check_button") } local label_copy_gps_ele = dt.new_widget("check_button") { - label = _("elevation:"), + label = _("elevation: "), value = true } -- @@ -372,12 +370,12 @@ end local function reverse_geocode() if not df.check_if_bin_exists("curl") then - dt.print_error(_("curl not found")) + dt.print_error("curl not found") return end if not df.check_if_bin_exists("jq") then - dt.print_error(_("jq not found")) + dt.print_error("jq not found") return end @@ -597,7 +595,7 @@ local function install_module() if not gT.module_installed then dt.register_lib( "geoToolbox", -- Module name - "geo toolbox", -- name + _("geo toolbox"), -- name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers diff --git a/contrib/gimp.lua b/contrib/gimp.lua index 9a60cada..8e285517 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -74,8 +74,6 @@ local gimp_widget = nil du.check_min_api_version("7.0.0", "gimp") -dt.gettext.bindtextdomain("gimp", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index b3d93015..84fc7bf9 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -29,8 +29,6 @@ local gettext = dt.gettext dl.check_min_api_version("7.0.0", "gpx_export") -gettext.bindtextdomain("gpx_export", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext.dgettext("gpx_export", msgid) end @@ -150,7 +148,7 @@ local function install_module() if not gpx.module_installed then dt.register_lib( "gpx_exporter", - "gpx export", + _("gpx export"), true, -- expandable true, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers diff --git a/contrib/harmonic_armature_guide.lua b/contrib/harmonic_armature_guide.lua index fd16f2b6..a85195cc 100644 --- a/contrib/harmonic_armature_guide.lua +++ b/contrib/harmonic_armature_guide.lua @@ -36,8 +36,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("2.0.0", "harmonic_armature_guide") -dt.gettext.bindtextdomain("harmonic_armature_guide", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/hif_group_leader.lua b/contrib/hif_group_leader.lua index 26eaf48d..2252f967 100644 --- a/contrib/hif_group_leader.lua +++ b/contrib/hif_group_leader.lua @@ -64,8 +64,6 @@ du.check_min_api_version("7.0.0", MODULE) local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("hif_group_leader", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -188,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_hif_group_leader(images) end, - _("make hif group leader for collection") + string.format(_("make hif group leader for %s", "collection")) ) dt.register_event(MODULE .. "_select", "shortcut", @@ -196,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_hif_group_leader(images) end, - _("make hif group leader for selection") + string.format(_("make hif group leader for %s", "selection")) ) return script_data \ No newline at end of file diff --git a/contrib/hugin.lua b/contrib/hugin.lua index f209c0c5..12849168 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -56,8 +56,6 @@ local PQ = dt.configuration.running_os == "windows" and '"' or "'" -- works with darktable API version from 5.0.0 on du.check_min_api_version("7.0.0", "hugin") -dt.gettext.bindtextdomain("hugin", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -85,7 +83,7 @@ end local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) - dt.print("exporting to hugin: "..tostring(number).."/"..tostring(total)) + dt.print(string.format(_("exporting to hugin: %d / $d", number, total))) end local function create_panorama(storage, image_table, extra_data) --finalize @@ -146,7 +144,7 @@ local function create_panorama(storage, image_table, extra_data) --finalize end if first_file == nil then - dt.print("no file selected") + dt.print(_("no file selected")) return end diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index c63ff166..817dc7a6 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -73,8 +73,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- works with LUA API version 5.0.0 du.check_min_api_version("7.0.0", "image_stack") -dt.gettext.bindtextdomain("image_stack", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -141,19 +139,19 @@ local chkbtn_will_align = dt.new_widget("check_button"){ local chkbtn_radial_distortion = dt.new_widget("check_button"){ label = _('optimize radial distortion for all images'), value = dt.preferences.read("align_image_stack", "def_radial_distortion", "bool"), - tooltip = _('optimize radial distortion for all images, \nexcept for first'), + tooltip = _('optimize radial distortion for all images, \nexcept the first'), } local chkbtn_optimize_field = dt.new_widget("check_button"){ label = _('optimize field of view for all images'), value = dt.preferences.read("align_image_stack", "def_optimize_field", "bool"), - tooltip =_('optimize field of view for all images, except for first. \nUseful for aligning focus stacks (DFF) with slightly \ndifferent magnification.'), + tooltip =_('optimize field of view for all images, except the first. \nUseful for aligning focus stacks (DFF) with slightly \ndifferent magnification.'), } local chkbtn_optimize_image_center = dt.new_widget("check_button"){ label = _('optimize image center shift for all images'), value = dt.preferences.read("align_image_stack", "def_optimize_image_center", "bool"), - tooltip =_('optimize image center shift for all images, \nexcept for first.'), + tooltip =_('optimize image center shift for all images, \nexcept the first.'), } local chkbtn_auto_crop = dt.new_widget("check_button"){ @@ -506,7 +504,7 @@ local function image_stack(storage, image_table, extra_data) return end - job = dt.gui.create_job("image stack", true, stop_job) + job = dt.gui.create_job(_("image stack"), true, stop_job) job.percent = job.percent + percent_step -- align images if requested diff --git a/contrib/image_time.lua b/contrib/image_time.lua index ff5f8c28..4daf9c6b 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -116,8 +116,6 @@ img_time.event_registered = false du.check_min_api_version("7.0.0", "image_time") -dt.gettext.bindtextdomain("image_time", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -195,7 +193,7 @@ end local function synchronize_time(images) local sign = 1 - if img_time.sdir.value == "subtract" then + if img_time.sdir.value == _("subtract") then sign = -1 end synchronize_times(images, tonumber(img_time.diff_entry.text) * sign) @@ -511,7 +509,7 @@ img_time.stack = dt.new_widget("stack"){ dt.new_widget("box"){ orientation = "vertical", dt.new_widget("label"){label = _("set time")}, - dt.new_widget("section_label"){label = _("date: ")}, + dt.new_widget("section_label"){label = _("date:")}, img_time.sdy, img_time.smo, img_time.syr, diff --git a/contrib/jpg_group_leader.lua b/contrib/jpg_group_leader.lua index 6201b398..ef7cf910 100644 --- a/contrib/jpg_group_leader.lua +++ b/contrib/jpg_group_leader.lua @@ -64,8 +64,6 @@ du.check_min_api_version("7.0.0", MODULE) local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("jpg_group_leader", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -188,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_jpg_group_leader(images) end, - _("make jpg group leader for collection") + string.format(_("make jpg group leader for %s", "collection")) ) dt.register_event(MODULE .. "_select", "shortcut", @@ -196,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_jpg_group_leader(images) end, - _("make jpg group leader for selection") + string.format(_("make jpg group leader for %s", "selection")) ) return script_data \ No newline at end of file diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index ec1aa04a..561e8ba8 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -45,8 +45,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" du.check_min_api_version("7.0.0", "kml_export") -dt.gettext.bindtextdomain("kml_export", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -95,12 +93,12 @@ local function create_kml_file(storage, image_table, extra_data) end if not df.check_if_bin_exists(magickPath) then - dt.print_error(_("magick not found")) + dt.print_error("magick not found") return end if dt.configuration.running_os == "linux" then if not df.check_if_bin_exists("xdg-user-dir") then - dt.print_error(_("xdg-user-dir not found")) + dt.print_error("xdg-user-dir not found") return end end @@ -110,7 +108,7 @@ local function create_kml_file(storage, image_table, extra_data) if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true and dt.configuration.running_os == "linux") then if not df.check_if_bin_exists("zip") then - dt.print_error(_("zip not found")) + dt.print_error("zip not found") return end exportDirectory = dt.configuration.tmp_dir From ecff9205375bf7218945d4483efa99fbf7650f27 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 17 Sep 2024 15:29:16 -0400 Subject: [PATCH 077/169] contrib/[p-z]*.lua - cleaned up translatable strings --- contrib/RL_out_sharp.lua | 2 -- contrib/passport_guide.lua | 2 -- contrib/passport_guide_germany.lua | 2 -- contrib/pdf_slideshow.lua | 2 -- contrib/photils.lua | 5 ++--- contrib/quicktag.lua | 6 ++---- contrib/rate_group.lua | 2 -- contrib/rename-tags.lua | 2 -- contrib/rename_images.lua | 2 -- contrib/select_non_existing.lua | 2 -- contrib/select_untagged.lua | 2 -- contrib/slideshowMusic.lua | 2 -- contrib/transfer_hierarchy.lua | 4 +--- contrib/video_ffmpeg.lua | 10 ++++------ 14 files changed, 9 insertions(+), 36 deletions(-) diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index 9e0a1fb0..9083277f 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -68,8 +68,6 @@ du.check_min_api_version("7.0.0", MODULE_NAME) -- translation local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("RL_out_sharp", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/passport_guide.lua b/contrib/passport_guide.lua index 246e7378..91e07052 100644 --- a/contrib/passport_guide.lua +++ b/contrib/passport_guide.lua @@ -41,8 +41,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("2.0.0", "passport_guide") -dt.gettext.bindtextdomain("passport_guide", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/passport_guide_germany.lua b/contrib/passport_guide_germany.lua index 735c092a..9b34d0b1 100644 --- a/contrib/passport_guide_germany.lua +++ b/contrib/passport_guide_germany.lua @@ -45,8 +45,6 @@ local gettext = dt.gettext du.check_min_api_version("2.0.0", "passport_guide_germany") -- Tell gettext where to find the .mo file translating messages for a particular domain -dt.gettext.bindtextdomain("passport_guide_germany",dt.configuration.config_dir.."/lua/locale/") - local function _(msgid) return gettext.dgettext("passport_guide_germany", msgid) end diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index 911493f8..36b0b1b8 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -44,8 +44,6 @@ local df = require "lib/dtutils.file" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("pdf_slideshow", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/photils.lua b/contrib/photils.lua index b06dc6ef..35209401 100644 --- a/contrib/photils.lua +++ b/contrib/photils.lua @@ -45,7 +45,6 @@ local MODULE_NAME = "photils" du.check_min_api_version("7.0.0", MODULE_NAME) local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("photils", dt.configuration.config_dir .."/lua/locale/") local function _(msgid) return gettext(msgid) @@ -339,7 +338,7 @@ function PHOTILS.on_tags_clicked() end if #PHOTILS.tags == 0 then - local msg = string.format(_("no tags where found"), MODULE_NAME) + local msg = string.format(_("no tags were found"), MODULE_NAME) GUI.warning_label.label = msg GUI.stack.active = GUI.error_view return @@ -394,7 +393,7 @@ end local function install_module() if not PHOTILS.module_installed then dt.register_lib(MODULE_NAME, - "photils autotagger", + _("photils autotagger"), true, true, PHOTILS.plugin_display_views, diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index 7a17fa1b..7dd604da 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -51,8 +51,6 @@ du.check_min_api_version("7.0.0", "quicktag") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("quicktag", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -199,7 +197,7 @@ local function install_module() if not qt.module_installed then dt.register_lib( "quicktag", -- Module name - "quicktag", -- name + _("quick tag"), -- name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}}, @@ -306,7 +304,7 @@ end for i=1,qnr do dt.register_event("quicktag " .. tostring(i), "shortcut", function(event, shortcut) tagattach(tostring(quicktag_table[i])) end, - string.format(_("quicktag %i"),i)) + string.format(_("quick tag %i"),i)) end script_data.destroy = destroy diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index c75b504d..8c3a15d3 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -46,8 +46,6 @@ du.check_min_api_version("7.0.0", "rate_group") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("rate_group", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index 3b84eba6..69509a53 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -38,8 +38,6 @@ du.deprecated("contrib/rename-tags.lua","darktable release 4.0") local gettext = darktable.gettext.gettext -darktable.gettext.bindtextdomain("rename-tags", darktable.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/rename_images.lua b/contrib/rename_images.lua index c0132adb..d50c8e18 100644 --- a/contrib/rename_images.lua +++ b/contrib/rename_images.lua @@ -49,8 +49,6 @@ du.check_min_api_version("7.0.0", "rename_images") local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("rename_images", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/select_non_existing.lua b/contrib/select_non_existing.lua index f691589a..d7bfa641 100644 --- a/contrib/select_non_existing.lua +++ b/contrib/select_non_existing.lua @@ -31,8 +31,6 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" local gettext = dt.gettext.gettext -dt.gettext.bindtextdomain("select_non_existing", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/select_untagged.lua b/contrib/select_untagged.lua index 2aef97e8..c86cf831 100644 --- a/contrib/select_untagged.lua +++ b/contrib/select_untagged.lua @@ -24,8 +24,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "select_untagged") -dt.gettext.bindtextdomain("select_untagged", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index c5b9d8ce..d83dd248 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -31,8 +31,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "slideshowMusic") -dt.gettext.bindtextdomain("slideshowMusic", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end diff --git a/contrib/transfer_hierarchy.lua b/contrib/transfer_hierarchy.lua index b73b38a9..465b4f7e 100755 --- a/contrib/transfer_hierarchy.lua +++ b/contrib/transfer_hierarchy.lua @@ -80,8 +80,6 @@ local dtutils_system = require("lib/dtutils.system") local LIB_ID = "transfer_hierarchy" dtutils.check_min_api_version("7.0.0", LIB_ID) -darktable.gettext.bindtextdomain("transfer_hierarchy", darktable.configuration.config_dir .."/lua/locale/") - local function _(msgid) return darktable.gettext.gettext(msgid) end @@ -145,7 +143,7 @@ end local function install_module() if not th.module_installed then darktable.register_lib(LIB_ID, - "transfer hierarchy", true, true, { + _("transfer hierarchy"), true, true, { [darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 } }, th.transfer_widget, nil, nil) th.module_installed = true diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 4203188e..68d0af30 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -41,8 +41,6 @@ local gettext = dt.gettext.gettext du.check_min_api_version("7.0.0", "video_ffmpeg") -dt.gettext.bindtextdomain("video_ffmpeg", dt.configuration.config_dir .."/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -248,8 +246,8 @@ local function string_pref_write(name, widget_attribute) end local framerates_selector = dt.new_widget("combobox"){ - label = _("framerate"), - tooltip = _("select framerate of output video"), + label = _("frame rate"), + tooltip = _("select frame rate of output video"), value = combobox_pref_read("framerate", framerates), changed_callback = combobox_pref_write("framerate"), table.unpack(framerates) @@ -418,7 +416,7 @@ local function export(extra_data) local ffmpeg_path = df.check_if_bin_exists("ffmpeg") if not ffmpeg_path then dt.print_error("ffmpeg not found") - dt.print("ERROR - ffmpeg not found") + dt.print(_("ERROR - ffmpeg not found")) return end local dir = extra_data["tmp_dir"] @@ -450,7 +448,7 @@ local function finalize_export(storage, images_table, extra_data) dt.print_error(filename, file.filename) df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"]) end - dt.print("Start video building...") + dt.print(_("Start video building...")) local result, path = export(extra_data) if result ~= 0 then dt.print(_("ERROR: cannot build image, see console for more info")) From 36a6a54364fc96de015613795d471ccaa29e4d45 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 17 Sep 2024 18:29:05 -0400 Subject: [PATCH 078/169] official/apply_camera_string - clean up translatable strings --- official/apply_camera_style.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index 963586af..a72c8217 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -463,16 +463,18 @@ script_data.destroy = destroy -- E V E N T S -- - - - - - - - - - - - - - - - - - - - - - - - +local shortcut_string = _("apply darktable camera styles to %s") + dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(true) - end, _("apply darktable camera styles to collection") + end, string.format(shortcut_string, _("collection")) ) dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(false) - end, _("apply darktable camera styles to selection") + end, string.format(shortcut_string, _("selection")) ) dt.register_event(MODULE, "post-import-image", From f6cb8091e7d736a516192661dc577db81b2bc75b Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 20 Sep 2024 15:07:37 +0200 Subject: [PATCH 079/169] Fix types in examples/gui_action script. --- examples/gui_action.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/gui_action.lua b/examples/gui_action.lua index 3bff94b6..66c5a811 100644 --- a/examples/gui_action.lua +++ b/examples/gui_action.lua @@ -115,7 +115,7 @@ dt.register_lib( clicked_callback = function(_) local sp = NaN if wg.check.value then sp = wg.speed.text end - wg.return_value.text = dt.gui.action(wg.action.text, wg.instance.value, wg.element.text, wg.effect.text, sp) + wg.return_value.text = dt.gui.action(wg.action.text, tonumber(wg.instance.value), wg.element.text, wg.effect.text, tonumber(sp)) end }, dt.new_widget("box") From d754e37cf43c569d9cd833ed8919168a945800be Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 23 Sep 2024 10:42:31 +0200 Subject: [PATCH 080/169] Properly sanitize filenames in dtutils.file file_copy and file_move. --- lib/dtutils/file.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index e33fa5aa..a4599297 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -522,9 +522,9 @@ function dtutils_file.file_copy(fromFile, toFile) local result = nil -- if cp exists, use it if dt.configuration.running_os == "windows" then - result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') + result = os.execute('copy ' .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile)) elseif dtutils_file.check_if_bin_exists("cp") then - result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") + result = os.execute("cp " .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile)) end -- if cp was not present, or if cp failed, then a pure lua solution @@ -575,7 +575,7 @@ function dtutils_file.file_move(fromFile, toFile) if not success then -- an error occurred, so let's try using the operating system function if dtutils_file.check_if_bin_exists("mv") then - success = os.execute("mv '" .. fromFile .. "' '" .. toFile .. "'") + success = os.execute("mv " .. dtutils_file.sanitize_filename(fromFile) .. ' ' .. dtutils_file.sanitize_filename(toFile)) end -- if the mv didn't exist or succeed, then... if not success then From 64840089e97547a4dd1d9490efc696b9bcd92b52 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 12 Oct 2024 20:28:01 -0400 Subject: [PATCH 081/169] clean up a few issues with parentheses and missed strings. --- contrib/geoJSON_export.lua | 2 +- contrib/hif_group_leader.lua | 4 ++-- contrib/hugin.lua | 2 +- contrib/jpg_group_leader.lua | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index aff04b22..93ecd9ff 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -293,7 +293,7 @@ local function create_geoJSON_file(storage, image_table, extra_data) file:write(geoJSON_file) file:close() - dt.print(string.format(_("%s file created in %s", "geoJSON", exportDirectory))) + dt.print(string.format(_("%s file created in %s"), "geoJSON", exportDirectory)) -- Open the file with the standard programm if ( dt.preferences.read("geoJSON_export","OpengeoJSONFile","bool") == true ) then diff --git a/contrib/hif_group_leader.lua b/contrib/hif_group_leader.lua index 2252f967..9543becb 100644 --- a/contrib/hif_group_leader.lua +++ b/contrib/hif_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", "collection")) + string.format(_("make hif group leader for %s", _("collection"))) ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", "selection")) + string.format(_("make hif group leader for %s", _("selection"))) ) return script_data \ No newline at end of file diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 12849168..49868424 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -83,7 +83,7 @@ end local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) - dt.print(string.format(_("exporting to hugin: %d / $d", number, total))) + dt.print(string.format(_("exporting to hugin: %d / $d"), number, total)) end local function create_panorama(storage, image_table, extra_data) --finalize diff --git a/contrib/jpg_group_leader.lua b/contrib/jpg_group_leader.lua index ef7cf910..7ca1359b 100644 --- a/contrib/jpg_group_leader.lua +++ b/contrib/jpg_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", "collection")) + string.format(_("make jpg group leader for %s", _("collection"))) ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", "selection")) + string.format(_("make jpg group leader for %s", _("selection"))) ) return script_data \ No newline at end of file From 0e535b850c6c7423451f3e1535d4c87b45a0aef7 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 12 Oct 2024 20:41:02 -0400 Subject: [PATCH 082/169] Incorporated TurboGit's suggestions --- contrib/photils.lua | 2 +- contrib/video_ffmpeg.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/photils.lua b/contrib/photils.lua index 35209401..a5fe59be 100644 --- a/contrib/photils.lua +++ b/contrib/photils.lua @@ -393,7 +393,7 @@ end local function install_module() if not PHOTILS.module_installed then dt.register_lib(MODULE_NAME, - _("photils autotagger"), + _("photils auto-tagger"), true, true, PHOTILS.plugin_display_views, diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 68d0af30..70af1efe 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -448,7 +448,7 @@ local function finalize_export(storage, images_table, extra_data) dt.print_error(filename, file.filename) df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"]) end - dt.print(_("Start video building...")) + dt.print(_("start video building...")) local result, path = export(extra_data) if result ~= 0 then dt.print(_("ERROR: cannot build image, see console for more info")) From da7674ca5175ac1ac3f2e8c2a3a5992c38b5359f Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 14 Oct 2024 12:54:13 -0400 Subject: [PATCH 083/169] lib/dtutils/string - updated tooltip to use same translatable strings as gui/gtkentry.c for consistency and eas of translation --- lib/dtutils/string.lua | 172 ++++++++++++++++++++++------------------- 1 file changed, 91 insertions(+), 81 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index b1718b39..8432dc35 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -589,9 +589,9 @@ local PLACEHOLDERS = {"ROLL.NAME", "RATING.ICONS", -- Not Implemented "LABELS", "LABELS.ICONS", -- Not Implemented - "MAKER", - "MODEL", - "LENS", + "EXIF.MAKER", + "EXIF.MODEL", + "EXIF.LENS", "TITLE", "DESCRIPTION", "CREATOR", @@ -834,84 +834,94 @@ dtutils_string.libdoc.functions["get_substitution_tooltip"] = { } function dtutils_string.get_substitution_tooltip() - return string.format("$(ROLL.NAME) - %s\n", _("film roll of the input image")) .. - string.format("$(FILE.FOLDER) - %s\n", _("folder containing the input image")) .. - string.format("$(FILE.NAME) - %s\n", _("basename of the input image")) .. - string.format("$(FILE.EXTENSION) - %s\n", _("extension of the input image")) .. - string.format("$(ID) - %s\n", _("the image id")) .. - string.format("$(VERSION) - %s\n", _("the duplicate version number")) .. - string.format("$(VERSION.IF.MULTI) - %s\n", _("same as $(VERSION) but null string if only one version exists")) .. - string.format("$(VERSION.NAME) - %s\n", _("version name from metadata")) .. - string.format("$(DARKTABLE.VERSION) - %s\n", _("the version of the running darktable instance")) .. - --string.format("$(DARKTABLE.NAME) - %s\n", _("")) .. -- Not Implemented - string.format("$(SEQUENCE[n,m]) - %s\n", _("a sequence number within an export job with n digits and starting with m\nparameters are optional, default is [4,1]")) .. - string.format("$(WIDTH.SENSOR) - %s\n", _("width of RAW data in pixels before RAW crop")) .. - string.format("$(HEIGHT.SENSOR) - %s\n", _("height of RAW data in pixels before RAW crop")) .. - string.format("$(WIDTH.RAW) - %s\n", _("width of RAW data in pixels after RAW crop")) .. - string.format("$(HEIGHT.RAW) - %s\n", _("height of RAW data in pixels after RAW crop")) .. - string.format("$(WIDTH.CROP) - %s\n", _("image width in pixels at the end of the pixelpipe, but before export resize")) .. - string.format("$(HEIGHT.CROP) - %s\n", _("image height in pixels at the end of the pixelpipe, but before export resize")) .. - string.format("$(WIDTH.EXPORT) - %s\n", _("image width in pixels at the end of the pixelpipe and after export resize")) .. - string.format("$(HEIGHT.EXPORT) - %s\n", _("image height in pixels at the end of the pixelpipe and after export resize")) .. - --string.format("$(WIDTH.MAX) - %s\n", _("")) .. -- Not Implemented - --string.format("$(HEIGHT.MAX) - %s\n", _("")) .. -- Not Implemented - string.format("$(YEAR) - %s\n", _("current year")) .. - string.format("$(YEAR.SHORT) - %s\n", _("current two digit year")) .. - string.format("$(MONTH) - %s\n", _("current numeric (1-12) month")) .. - string.format("$(MONTH.LONG) - %s\n", _("full current month name")) .. - string.format("$(MONTH.SHORT) - %s\n", _("abbreviated current month name")) .. - string.format("$(DAY) - %s\n", _("current day")) .. - string.format("$(HOUR) - %s\n", _("current hour")) .. - string.format("$(MINUTE) - %s\n", _("current minute")) .. - string.format("$(SECOND) - %s\n", _("current second")) .. - string.format("$(MSEC) - %s\n", _("current millisecond")) .. - string.format("$(EXIF.YEAR) - EXIF %s\n", _("year")) .. - string.format("$(EXIF.YEAR.SHORT) - EXIF %s\n", _("year, two-digit version")) .. - string.format("$(EXIF.MONTH) - EXIF %s\n", _("month, numeric")) .. - string.format("$(EXIF.MONTH.LONG) - EXIF %s\n", _("month, full name")) .. - string.format("$(EXIF.MONTH.SHORT) - EXIF %s\n", _("month, abbreviated name")) .. - string.format("$(EXIF.DAY) - EXIF %s\n", _("day")) .. - string.format("$(EXIF.HOUR) - EXIF %s\n", _("hour")) .. - string.format("$(EXIF.MINUTE) - EXIF %s\n", _("minute")) .. - string.format("$(EXIF.SECOND) - EXIF %s\n", _("second")) .. - string.format("$(EXIF.MSEC) - EXIF %s\n", _("millisecond")) .. - --string.format("$(EXIF.DATE.REGIONAL) - %s\n", _("")) .. -- Not Implemented - --string.format("$(EXIF.TIME.REGIONAL) - %s\n", _("")) .. -- Not Implemented - string.format("$(EXIF.ISO) - EXIF ISO %s\n", _("value")) .. - string.format("$(EXIF.EXPOSURE) - EXIF %s\n", _("exposure")) .. - string.format("$(EXIF.EXPOSURE.BIAS) - EXIF %s\n", _("exposure bias")) .. - string.format("$(EXIF.APERTURE) - EXIF %s\n", _("aperture")) .. - string.format("$(EXIF.CROP.FACTOR) - EXIF %s\n", _("crop factor")) .. - string.format("$(EXIF.FOCAL.LENGTH) - EXIF %s\n", _("focal length")) .. - string.format("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35mm %s\n", _("equivalent focal length")) .. -- Not Implemented - string.format("$(EXIF.FOCUS.DISTANCE) - EXIF %s\n", _("focus distance")) .. - --string.format("$(IMAGE.EXIF) - %s\n", _("")) .. -- Not Implemented - string.format("$(LONGITUDE) - %s\n", _("longitude")) .. - string.format("$(LATITUDE) - %s\n", _("latitude")) .. - string.format("$(ELEVATION) - %s\n", _("elevation")) .. - --string.format("$(GPS.LOCATION) - %s\n", _("")) .. -- Not Implemented - string.format("$(STARS) - %s\n", _("star rating")) .. - --string.format("$(RATING.ICONS) - %s\n", _("")) .. -- Not Implemented - string.format("$(LABELS) - %s\n", _("colorlabels")) .. - --string.format("$(LABELS.ICONS) - %s\n", _("")) .. -- Not Implemented - string.format("$(MAKER) - %s\n", _("camera maker")) .. - string.format("$(MODEL) - %s\n", _("camera model")) .. - string.format("$(LENS) - %s\n", _("lens")) .. - string.format("$(TITLE) - %s\n", _("title from metadata")) .. - string.format("$(DESCRIPTION) - %s\n", _("description from metadata")) .. - string.format("$(CREATOR) - %s\n", _("creator from metadata")) .. - string.format("$(PUBLISHER) - %s\n", _("publisher from metadata")) .. - string.format("$(RIGHTS) - %s\n", _("rights from metadata")) .. - --string.format("$(TAGS) - %s\n", _("")) .. -- Not Implemented - string.format("$(CATEGORY[n,category]) - %s\n", _("tag name of level n [0,9] of selected category (or tag)")) .. - --string.format("$(SIDECAR.TXT) - %s\n", _("")) .. -- Not Implemented - string.format("$(FOLDER.PICTURES) - %s\n", _("pictures folder")) .. - string.format("$(FOLDER.HOME) - %s\n", _("home folder")) .. - string.format("$(FOLDER.DESKTOP) - %s\n", _("desktop folder")) .. - --string.format("$(OPENCL.ACTIVATED) - %s\n", _("")) .. -- Not Implemented - string.format("$(USERNAME) - %s\n", _("user name defined by OS")) - --string.format("$(NL) - %s\n", _("")) .. -- Not Implemented - --string.format("$(JOBCODE) - %s", _("")) -- Not Implemented + return _("$(ROLL.NAME) - roll of the input image") .. "\n" .. + _("$(FILE.FOLDER) - folder containing the input image") .. "\n" .. + _("$(FILE.NAME) - basename of the input image") .. "\n" .. + _("$(FILE.EXTENSION) - extension of the input image") .. "\n" .. + _("$(ID) - image ID") .. "\n" .. + _("$(VERSION) - duplicate version") .. "\n" .. + _("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists") .. "\n" .. + _("$(VERSION.NAME) - version name from metadata") .. "\n" .. + _("$(DARKTABLE.VERSION) - current darktable version") .. "\n" .. + -- _("$(DARKTABLE.NAME) - darktable name") .. "\n" .. -- not implemented + _("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number") .. "\n" .. + _("$(WIDTH.SENSOR) - image sensor width") .. "\n" .. + _("$(HEIGHT.SENSOR) - image sensor height") .. "\n" .. + _("$(WIDTH.RAW) - RAW image width") .. "\n" .. + _("$(HEIGHT.RAW) - RAW image height") .. "\n" .. + _("$(WIDTH.CROP) - image width after crop") .. "\n" .. + _("$(HEIGHT.CROP) - image height after crop") .. "\n" .. + _("$(WIDTH.EXPORT) - exported image width") .. "\n" .. + _("$(HEIGHT.EXPORT) - exported image height") .. "\n" .. + -- _("$(WIDTH.MAX) - maximum image export width") .. "\n" .. -- not implemented + -- _("$(HEIGHT.MAX) - maximum image export height") .. "\n" .. -- not implemented + _("$(YEAR) - year") .. "\n" .. + _("$(YEAR.SHORT) - year without century") .. "\n" .. + _("$(MONTH) - month") .. "\n" .. + _("$(MONTH.LONG) - full month name according to the current locale") .. "\n" .. + _("$(MONTH.SHORT) - abbreviated month name according to the current locale") .. "\n" .. + _("$(DAY) - day") .. "\n" .. + _("$(HOUR) - hour") .. "\n" .. + -- _("$(HOUR.AMPM) - hour, 12-hour clock") .. "\n" .. -- not implemented + _("$(MINUTE) - minute") .. "\n" .. + _("$(SECOND) - second") .. "\n" .. + _("$(MSEC) - millisecond") .. "\n" .. + _("$(EXIF.YEAR) - EXIF year") .. "\n" .. + _("$(EXIF.YEAR.SHORT) - EXIF year without century") .. "\n" .. + _("$(EXIF.MONTH) - EXIF month") .. "\n" .. + _("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale") .. "\n" .. + _("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale") .. "\n" .. + _("$(EXIF.DAY) - EXIF day") .. "\n" .. + _("$(EXIF.HOUR) - EXIF hour") .. "\n" .. + -- _("$(EXIF.HOUR.AMPM) - EXIF hour, 12-hour clock") .. "\n" .. -- not implemented + _("$(EXIF.MINUTE) - EXIF minute") .. "\n" .. + _("$(EXIF.SECOND) - EXIF second") .. "\n" .. + _("$(EXIF.MSEC) - EXIF millisecond") .. "\n" .. + -- _("$(EXIF.DATE.REGIONAL) - localized EXIF date") .. "\n" .. -- not implemented + -- _("$(EXIF.TIME.REGIONAL) - localized EXIF time") .. "\n" .. -- not implemented + _("$(EXIF.ISO) - ISO value") .. "\n" .. + _("$(EXIF.EXPOSURE) - EXIF exposure") .. "\n" .. + _("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias") .. "\n" .. + -- _("$(EXIF.EXPOSURE.PROGRAM) - EXIF exposure program") .. "\n" .. -- not implemented + _("$(EXIF.APERTURE) - EXIF aperture") .. "\n" .. + _("$(EXIF.CROP_FACTOR) - EXIF crop factor") .. "\n" .. + _("$(EXIF.FOCAL.LENGTH) - EXIF focal length") .. "\n" .. + _("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35 mm equivalent focal length") .. "\n" .. + _("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance") .. "\n" .. + _("$(EXIF.MAKER) - camera maker") .. + _("$(EXIF.MODEL) - camera model") .. + -- _("$(EXIF.WHITEBALANCE) - EXIF selected white balance") .. -- not implemented + -- _("$(EXIF.METERING) - EXIF exposure metering mode") .. -- not implemented + _("$(EXIF.LENS) - lens") .. + -- _("$(EXIF.FLASH.ICON) - icon indicating whether flash was used") .. -- not implemented + -- _("$(EXIF.FLASH) - was flash used (yes/no/--)") .. -- not implemented + -- _("$(GPS.LONGITUDE) - longitude") .. "\n" ..-- not implemented + -- _("$(GPS.LATITUDE) - latitude") .. "\n" ..-- not implemented + -- _("$(GPS.ELEVATION) - elevation") .. "\n" ..-- not implemented + -- _("$(GPS.LOCATION.ICON) - icon indicating whether GPS location is known") .. "\n" ..-- not implemented + _("$(LONGITUDE) - longitude") .. "\n" .. + _("$(LATITUDE) - latitude") .. "\n" .. + _("$(ELEVATION) - elevation") .. "\n" .. + _("$(STARS) - star rating as number (-1 for rejected)") .. "\n" .. + -- _("$(RATING.ICONS) - star/reject rating in icon form") .. "\n" ..-- not implemented + _("$(LABELS) - color labels as text") .. "\n" .. + -- _("$(LABELS.ICONS) - color labels as icons") .. "\n" ..-- not implemented + _("$(TITLE) - title from metadata") .. "\n" .. + _("$(DESCRIPTION) - description from metadata") .. "\n" .. + _("$(CREATOR) - creator from metadata") .. "\n" .. + _("$(PUBLISHER) - publisher from metadata") .. "\n" .. + _("$(RIGHTS) - rights from metadata") .. "\n" .. + --_("$(TAGS) - tags as set in metadata settings") .. "\n" .. + _("$(CATEGORY[n,category]) - subtag of level n in hierarchical tags") .. "\n" .. + _("$(SIDECAR_TXT) - contents of .txt sidecar file, if present") .. "\n" .. + _("$(FOLDER.PICTURES) - pictures folder") .. "\n" .. + _("$(FOLDER.HOME) - home folder") .. "\n" .. + _("$(FOLDER.DESKTOP) - desktop folder") .. "\n" .. + -- _("$(OPENCL.ACTIVATED) - whether OpenCL is activated") .. "\n" .. + _("$(USERNAME) - login name") .. "\n" .. + -- _("$(NL) - newline") .. "\n" .. + -- _("$(JOBCODE) - job code for import") .. "\n" .. + "" end -- handle different versions of names From a835ab89bcaf25fc1e1078472a4cb6e3906195a6 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 16 Oct 2024 00:42:08 -0400 Subject: [PATCH 084/169] lib/dtutils/string - added new variable. Removed invalid variables. Fixed build_substitute list call. Fixed invalid category return. --- lib/dtutils/string.lua | 209 +++++++++++++++++++++++------------------ 1 file changed, 117 insertions(+), 92 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 8432dc35..e85d5866 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -557,6 +557,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "MONTH.SHORT", "DAY", "HOUR", + "HOUR.AMPM", -- Not Implemented "MINUTE", "SECOND", "MSEC", @@ -567,6 +568,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "EXIF.MONTH.SHORT", "EXIF.DAY", "EXIF.HOUR", + "EXIF.HOUR.AMPM", -- Not Implemented "EXIF.MINUTE", "EXIF.SECOND", "EXIF.MSEC", @@ -575,12 +577,23 @@ local PLACEHOLDERS = {"ROLL.NAME", "EXIF.ISO", "EXIF.EXPOSURE", "EXIF.EXPOSURE.BIAS", + "EXIF.EXPOSURE.PROGRAM", -- Not Implemented "EXIF.APERTURE", "EXIF.CROP.FACTOR", "EXIF.FOCAL.LENGTH", "EXIF.FOCAL.LENGTH.EQUIV", -- Not Implemented "EXIF.FOCUS.DISTANCE", - "IMAGE.EXIF", -- Not Implemented + "EXIF.MAKER", + "EXIF.MODEL", + "EXIF.WHTIEBALANCE", -- Not Implemented + "EXIF.METERING", -- Not Implemented + "EXIF.LENS", + "EXIF.FLASH.ICON", -- Not Implemented + "EXIF.FLASH", -- Not Implemented + "GPS.LONGITUDE", -- Not Implemented + "GPS.LATITUDE", -- Not Implemented + "GPS.ELEVATION", -- Not Implemented + "GPS.LOCATION.ICON", -- Not Implemented "LONGITUDE", "LATITUDE", "ELEVATION", @@ -589,9 +602,6 @@ local PLACEHOLDERS = {"ROLL.NAME", "RATING.ICONS", -- Not Implemented "LABELS", "LABELS.ICONS", -- Not Implemented - "EXIF.MAKER", - "EXIF.MODEL", - "EXIF.LENS", "TITLE", "DESCRIPTION", "CREATOR", @@ -692,7 +702,7 @@ end -- build the argument substitution list from each image -function dtutils_string.build_substitution_list(image, sequence, variable_string, username, pic_folder, home, desktop) +function dtutils_string.build_substitute_list(image, sequence, variable_string, username, pic_folder, home, desktop) local old_log_level = log.log_level() log.log_level(dtutils_string.log_level) @@ -703,6 +713,8 @@ function dtutils_string.build_substitution_list(image, sequence, variable_string is_api_9_1 = false end + local is_api_9_4 = dt.configuration.api_version_string >= "9.4.0" and true or false + local datetime = os.date("*t") local long_month = os.date("%B") local short_month = os.date("%b") @@ -753,6 +765,7 @@ function dtutils_string.build_substitution_list(image, sequence, variable_string short_month, -- MONTH.SHORT string.format("%02d", datetime.day), -- DAY string.format("%02d", datetime.hour), -- HOUR + "", -- HOUR.AMPM string.format("%02d", datetime.min), -- MINUTE string.format("%02d", datetime.sec), -- SECOND 0, -- MSEC @@ -763,6 +776,7 @@ function dtutils_string.build_substitution_list(image, sequence, variable_string os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT eday, -- EXIF.DAY ehour, -- EXIF.HOUR + "", -- EXIF.HOUR.AMPM emin, -- EXIF.MINUTE esec, -- EXIF.SECOND emsec, -- EXIF.MSEC @@ -771,12 +785,23 @@ function dtutils_string.build_substitution_list(image, sequence, variable_string string.format("%d", image.exif_iso), -- EXIF.ISO string.format("%.0f", 1./image.exif_exposure), -- EXIF.EXPOSURE image.exif_exposure_bias, -- EXIF.EXPOSURE.BIAS + "", -- EXIF.EXPOSURE.PROGRAM string.format("%.01f", image.exif_aperture), -- EXIF.APERTURE string.format("%.01f", image.exif_crop),-- EXIF.CROP_FACTOR string.format("%.0f", image.exif_focal_length), -- EXIF.FOCAL.LENGTH string.format("%.0f", image.exif_focal_length * image.exif_crop), -- EXIF.FOCAL.LENGTH.EQUIV image.exif_focus_distance, -- EXIF.FOCUS.DISTANCE - "", -- IMAGE.EXIF + image.exif_maker, -- EXIF.MAKER + image.exif_model, -- EXIF.MODEL + is_api_9_4 and image.exif_whitebalance or "", -- EXIF.WHITEBALANCE + is_api_9_4 and image.exif_metering_mode or "", -- EXIF.METERING + image.exif_lens, -- LENS + "", -- EXIF.FLASH.ICON + is_api_9_4 and image.exif_flash or "", -- EXIF.FLASH + "", -- GPS.LONGITUDE + "", -- GPS.LATITUDE + "", -- GPS.ELEVATION + "", -- GPS.LOCATION.ICON image.longitude or "", -- LONGITUDE image.latitude or "", -- LATITUDE image.elevation or "", -- ELEVATION @@ -785,9 +810,6 @@ function dtutils_string.build_substitution_list(image, sequence, variable_string "", -- RATING.ICONS - wont be implemented labels, -- LABELS "", -- LABELS.ICONS - wont be implemented - image.exif_maker, -- MAKER - image.exif_model, -- MODEL - image.exif_lens, -- LENS image.title, -- TITLE image.description, -- DESCRIPTION image.creator, -- CREATOR @@ -834,94 +856,96 @@ dtutils_string.libdoc.functions["get_substitution_tooltip"] = { } function dtutils_string.get_substitution_tooltip() - return _("$(ROLL.NAME) - roll of the input image") .. "\n" .. - _("$(FILE.FOLDER) - folder containing the input image") .. "\n" .. - _("$(FILE.NAME) - basename of the input image") .. "\n" .. - _("$(FILE.EXTENSION) - extension of the input image") .. "\n" .. - _("$(ID) - image ID") .. "\n" .. - _("$(VERSION) - duplicate version") .. "\n" .. - _("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists") .. "\n" .. - _("$(VERSION.NAME) - version name from metadata") .. "\n" .. - _("$(DARKTABLE.VERSION) - current darktable version") .. "\n" .. - -- _("$(DARKTABLE.NAME) - darktable name") .. "\n" .. -- not implemented - _("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number") .. "\n" .. - _("$(WIDTH.SENSOR) - image sensor width") .. "\n" .. - _("$(HEIGHT.SENSOR) - image sensor height") .. "\n" .. - _("$(WIDTH.RAW) - RAW image width") .. "\n" .. - _("$(HEIGHT.RAW) - RAW image height") .. "\n" .. - _("$(WIDTH.CROP) - image width after crop") .. "\n" .. - _("$(HEIGHT.CROP) - image height after crop") .. "\n" .. - _("$(WIDTH.EXPORT) - exported image width") .. "\n" .. - _("$(HEIGHT.EXPORT) - exported image height") .. "\n" .. - -- _("$(WIDTH.MAX) - maximum image export width") .. "\n" .. -- not implemented - -- _("$(HEIGHT.MAX) - maximum image export height") .. "\n" .. -- not implemented - _("$(YEAR) - year") .. "\n" .. - _("$(YEAR.SHORT) - year without century") .. "\n" .. - _("$(MONTH) - month") .. "\n" .. - _("$(MONTH.LONG) - full month name according to the current locale") .. "\n" .. - _("$(MONTH.SHORT) - abbreviated month name according to the current locale") .. "\n" .. - _("$(DAY) - day") .. "\n" .. - _("$(HOUR) - hour") .. "\n" .. - -- _("$(HOUR.AMPM) - hour, 12-hour clock") .. "\n" .. -- not implemented - _("$(MINUTE) - minute") .. "\n" .. - _("$(SECOND) - second") .. "\n" .. - _("$(MSEC) - millisecond") .. "\n" .. - _("$(EXIF.YEAR) - EXIF year") .. "\n" .. - _("$(EXIF.YEAR.SHORT) - EXIF year without century") .. "\n" .. - _("$(EXIF.MONTH) - EXIF month") .. "\n" .. - _("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale") .. "\n" .. - _("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale") .. "\n" .. - _("$(EXIF.DAY) - EXIF day") .. "\n" .. - _("$(EXIF.HOUR) - EXIF hour") .. "\n" .. + + return table.concat({ + _("$(ROLL.NAME) - roll of the input image"), + _("$(FILE.FOLDER) - folder containing the input image"), + _("$(FILE.NAME) - basename of the input image"), + _("$(FILE.EXTENSION) - extension of the input image"), + _("$(ID) - image ID"), + _("$(VERSION) - duplicate version"), + _("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists"), + _("$(VERSION.NAME) - version name from metadata"), + _("$(DARKTABLE.VERSION) - current darktable version"), + -- _("$(DARKTABLE.NAME) - darktable name"), -- not implemented + _("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number"), + _("$(WIDTH.SENSOR) - image sensor width"), + _("$(HEIGHT.SENSOR) - image sensor height"), + _("$(WIDTH.RAW) - RAW image width"), + _("$(HEIGHT.RAW) - RAW image height"), + _("$(WIDTH.CROP) - image width after crop"), + _("$(HEIGHT.CROP) - image height after crop"), + _("$(WIDTH.EXPORT) - exported image width"), + _("$(HEIGHT.EXPORT) - exported image height"), + -- _("$(WIDTH.MAX) - maximum image export width"), -- not implemented + -- _("$(HEIGHT.MAX) - maximum image export height"), -- not implemented + _("$(YEAR) - year"), + _("$(YEAR.SHORT) - year without century"), + _("$(MONTH) - month"), + _("$(MONTH.LONG) - full month name according to the current locale"), + _("$(MONTH.SHORT) - abbreviated month name according to the current locale"), + _("$(DAY) - day"), + _("$(HOUR) - hour"), + -- _("$(HOUR.AMPM) - hour, 12-hour clock"), -- not implemented + _("$(MINUTE) - minute"), + _("$(SECOND) - second"), + _("$(MSEC) - millisecond"), + _("$(EXIF.YEAR) - EXIF year"), + _("$(EXIF.YEAR.SHORT) - EXIF year without century"), + _("$(EXIF.MONTH) - EXIF month"), + _("$(EXIF.MONTH.LONG) - full EXIF month name according to the current locale"), + _("$(EXIF.MONTH.SHORT) - abbreviated EXIF month name according to the current locale"), + _("$(EXIF.DAY) - EXIF day"), + _("$(EXIF.HOUR) - EXIF hour"), -- _("$(EXIF.HOUR.AMPM) - EXIF hour, 12-hour clock") .. "\n" .. -- not implemented - _("$(EXIF.MINUTE) - EXIF minute") .. "\n" .. - _("$(EXIF.SECOND) - EXIF second") .. "\n" .. - _("$(EXIF.MSEC) - EXIF millisecond") .. "\n" .. - -- _("$(EXIF.DATE.REGIONAL) - localized EXIF date") .. "\n" .. -- not implemented - -- _("$(EXIF.TIME.REGIONAL) - localized EXIF time") .. "\n" .. -- not implemented - _("$(EXIF.ISO) - ISO value") .. "\n" .. - _("$(EXIF.EXPOSURE) - EXIF exposure") .. "\n" .. - _("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias") .. "\n" .. - -- _("$(EXIF.EXPOSURE.PROGRAM) - EXIF exposure program") .. "\n" .. -- not implemented - _("$(EXIF.APERTURE) - EXIF aperture") .. "\n" .. - _("$(EXIF.CROP_FACTOR) - EXIF crop factor") .. "\n" .. - _("$(EXIF.FOCAL.LENGTH) - EXIF focal length") .. "\n" .. - _("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35 mm equivalent focal length") .. "\n" .. - _("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance") .. "\n" .. + _("$(EXIF.MINUTE) - EXIF minute"), + _("$(EXIF.SECOND) - EXIF second"), + _("$(EXIF.MSEC) - EXIF millisecond"), + -- _("$(EXIF.DATE.REGIONAL) - localized EXIF date"), -- not implemented + -- _("$(EXIF.TIME.REGIONAL) - localized EXIF time"), -- not implemented + _("$(EXIF.ISO) - ISO value"), + _("$(EXIF.EXPOSURE) - EXIF exposure"), + _("$(EXIF.EXPOSURE.BIAS) - EXIF exposure bias"), + -- _("$(EXIF.EXPOSURE.PROGRAM) - EXIF exposure program"), -- not implemented + _("$(EXIF.APERTURE) - EXIF aperture"), + _("$(EXIF.CROP_FACTOR) - EXIF crop factor"), + _("$(EXIF.FOCAL.LENGTH) - EXIF focal length"), + _("$(EXIF.FOCAL.LENGTH.EQUIV) - EXIF 35 mm equivalent focal length"), + _("$(EXIF.FOCUS.DISTANCE) - EXIF focal distance"), _("$(EXIF.MAKER) - camera maker") .. _("$(EXIF.MODEL) - camera model") .. - -- _("$(EXIF.WHITEBALANCE) - EXIF selected white balance") .. -- not implemented - -- _("$(EXIF.METERING) - EXIF exposure metering mode") .. -- not implemented + _("$(EXIF.WHITEBALANCE) - EXIF selected white balance") .. -- not implemented + _("$(EXIF.METERING) - EXIF exposure metering mode") .. -- not implemented _("$(EXIF.LENS) - lens") .. -- _("$(EXIF.FLASH.ICON) - icon indicating whether flash was used") .. -- not implemented - -- _("$(EXIF.FLASH) - was flash used (yes/no/--)") .. -- not implemented - -- _("$(GPS.LONGITUDE) - longitude") .. "\n" ..-- not implemented - -- _("$(GPS.LATITUDE) - latitude") .. "\n" ..-- not implemented - -- _("$(GPS.ELEVATION) - elevation") .. "\n" ..-- not implemented - -- _("$(GPS.LOCATION.ICON) - icon indicating whether GPS location is known") .. "\n" ..-- not implemented - _("$(LONGITUDE) - longitude") .. "\n" .. - _("$(LATITUDE) - latitude") .. "\n" .. - _("$(ELEVATION) - elevation") .. "\n" .. - _("$(STARS) - star rating as number (-1 for rejected)") .. "\n" .. - -- _("$(RATING.ICONS) - star/reject rating in icon form") .. "\n" ..-- not implemented - _("$(LABELS) - color labels as text") .. "\n" .. - -- _("$(LABELS.ICONS) - color labels as icons") .. "\n" ..-- not implemented - _("$(TITLE) - title from metadata") .. "\n" .. - _("$(DESCRIPTION) - description from metadata") .. "\n" .. - _("$(CREATOR) - creator from metadata") .. "\n" .. - _("$(PUBLISHER) - publisher from metadata") .. "\n" .. - _("$(RIGHTS) - rights from metadata") .. "\n" .. - --_("$(TAGS) - tags as set in metadata settings") .. "\n" .. - _("$(CATEGORY[n,category]) - subtag of level n in hierarchical tags") .. "\n" .. - _("$(SIDECAR_TXT) - contents of .txt sidecar file, if present") .. "\n" .. - _("$(FOLDER.PICTURES) - pictures folder") .. "\n" .. - _("$(FOLDER.HOME) - home folder") .. "\n" .. - _("$(FOLDER.DESKTOP) - desktop folder") .. "\n" .. - -- _("$(OPENCL.ACTIVATED) - whether OpenCL is activated") .. "\n" .. - _("$(USERNAME) - login name") .. "\n" .. - -- _("$(NL) - newline") .. "\n" .. - -- _("$(JOBCODE) - job code for import") .. "\n" .. - "" + _("$(EXIF.FLASH) - was flash used (yes/no/--)") .. -- not implemented + -- _("$(GPS.LONGITUDE) - longitude"),-- not implemented + -- _("$(GPS.LATITUDE) - latitude"),-- not implemented + -- _("$(GPS.ELEVATION) - elevation"),-- not implemented + -- _("$(GPS.LOCATION.ICON) - icon indicating whether GPS location is known"),-- not implemented + _("$(LONGITUDE) - longitude"), + _("$(LATITUDE) - latitude"), + _("$(ELEVATION) - elevation"), + _("$(STARS) - star rating as number (-1 for rejected)"), + -- _("$(RATING.ICONS) - star/reject rating in icon form"),-- not implemented + _("$(LABELS) - color labels as text"), + -- _("$(LABELS.ICONS) - color labels as icons"),-- not implemented + _("$(TITLE) - title from metadata"), + _("$(DESCRIPTION) - description from metadata"), + _("$(CREATOR) - creator from metadata"), + _("$(PUBLISHER) - publisher from metadata"), + _("$(RIGHTS) - rights from metadata"), + --_("$(TAGS) - tags as set in metadata settings"), + _("$(CATEGORY[n,category]) - subtag of level n in hierarchical tags"), + _("$(SIDECAR_TXT) - contents of .txt sidecar file, if present"), + _("$(FOLDER.PICTURES) - pictures folder"), + _("$(FOLDER.HOME) - home folder"), + _("$(FOLDER.DESKTOP) - desktop folder"), + -- _("$(OPENCL.ACTIVATED) - whether OpenCL is activated"), + _("$(USERNAME) - login name"), + -- _("$(NL) - newline"), + -- _("$(JOBCODE) - job code for import"), + ""}, "\n") end -- handle different versions of names @@ -957,6 +981,7 @@ local function treat(var_string) if string.match(var_string, "CATEGORY%d") or string.match(var_string, "CATEGORY%[") then log.msg(log.info, "substituting for " .. var_string) ret_val = substitutes[var_string] + if not ret_val then ret_val = "" log.msg(log.info, "ret_val is " .. ret_val) elseif string.match(var_string, "SEQUENCE%[") then From 99eb595f4c62330382ed2c2c04137d421b518da6 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 16 Oct 2024 21:00:27 -0400 Subject: [PATCH 085/169] lib/dtutils/string added end to if to ensure that ret_val has a value --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index e85d5866..2310a496 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -981,7 +981,7 @@ local function treat(var_string) if string.match(var_string, "CATEGORY%d") or string.match(var_string, "CATEGORY%[") then log.msg(log.info, "substituting for " .. var_string) ret_val = substitutes[var_string] - if not ret_val then ret_val = "" + if not ret_val then ret_val = "" end log.msg(log.info, "ret_val is " .. ret_val) elseif string.match(var_string, "SEQUENCE%[") then From 44da339202747490a20fe6bb60c8c772c29eb94c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 17 Oct 2024 22:17:38 -0400 Subject: [PATCH 086/169] alias gettext to darktable.gettext.gettext then change the call to gettext to only pass the message to be translated --- contrib/AutoGrouper.lua | 4 ++-- contrib/auto_snapshot.lua | 2 +- contrib/dbmaint.lua | 2 +- contrib/gpx_export.lua | 4 ++-- contrib/passport_guide_germany.lua | 4 ++-- contrib/transfer_hierarchy.lua | 3 ++- lib/dtutils/file.lua | 7 ++----- lib/dtutils/string.lua | 3 ++- tools/script_manager.lua | 4 ++-- 9 files changed, 16 insertions(+), 17 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 72654ae2..f8d8061b 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -43,10 +43,10 @@ du.check_min_api_version("7.0.0", "AutoGrouper") local MOD = 'autogrouper' -local gettext = dt.gettext +local gettext = dt.gettext.gettext local function _(msgid) - return gettext.gettext(msgid) + return gettext(msgid) end -- return data structure for script_manager diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua index 3b883412..e3ede5b1 100644 --- a/contrib/auto_snapshot.lua +++ b/contrib/auto_snapshot.lua @@ -60,7 +60,7 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c local gettext = dt.gettext.gettext local function _(msgid) - return gettext(MODULE, msgid) + return gettext(msgid) end diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua index 8d24f1d2..044a9ac5 100644 --- a/contrib/dbmaint.lua +++ b/contrib/dbmaint.lua @@ -64,7 +64,7 @@ du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that c local gettext = dt.gettext.gettext local function _(msgid) - return gettext(MODULE, msgid) + return gettext(msgid) end diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index 84fc7bf9..1525aff8 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -25,12 +25,12 @@ For each source folder, a separate is generated in the gpx file. local dt = require "darktable" local df = require "lib/dtutils.file" local dl = require "lib/dtutils" -local gettext = dt.gettext +local gettext = dt.gettext.gettext dl.check_min_api_version("7.0.0", "gpx_export") local function _(msgid) - return gettext.dgettext("gpx_export", msgid) + return gettext(msgid) end -- return data structure for script_manager diff --git a/contrib/passport_guide_germany.lua b/contrib/passport_guide_germany.lua index 9b34d0b1..745f86df 100644 --- a/contrib/passport_guide_germany.lua +++ b/contrib/passport_guide_germany.lua @@ -40,13 +40,13 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" -local gettext = dt.gettext +local gettext = dt.gettext.gettext du.check_min_api_version("2.0.0", "passport_guide_germany") -- Tell gettext where to find the .mo file translating messages for a particular domain local function _(msgid) - return gettext.dgettext("passport_guide_germany", msgid) + return gettext(msgid) end local script_data = {} diff --git a/contrib/transfer_hierarchy.lua b/contrib/transfer_hierarchy.lua index 465b4f7e..16c23593 100755 --- a/contrib/transfer_hierarchy.lua +++ b/contrib/transfer_hierarchy.lua @@ -76,12 +76,13 @@ local darktable = require("darktable") local dtutils = require("lib/dtutils") local dtutils_file = require("lib/dtutils.file") local dtutils_system = require("lib/dtutils.system") +local gettext = darktable.gettext.gettext local LIB_ID = "transfer_hierarchy" dtutils.check_min_api_version("7.0.0", LIB_ID) local function _(msgid) - return darktable.gettext.gettext(msgid) + return gettext(msgid) end -- return data structure for script_manager diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index e33fa5aa..13197b97 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -24,15 +24,12 @@ dtutils_file.libdoc = { functions = {} } -local gettext = dt.gettext +local gettext = dt.gettext.gettext du.check_min_api_version("5.0.0", "dtutils.file") --- Tell gettext where to find the .mo file translating messages for a particular domain -gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/") - local function _(msgid) - return gettext.dgettext("dtutils.file", msgid) + return gettext(msgid) end --[[ diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 2310a496..5cf49cd3 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -3,11 +3,12 @@ local dtutils_string = {} local dt = require "darktable" local du = require "lib/dtutils" local log = require "lib/dtutils.log" +local gettext = dt.gettext.gettext local DEFAULT_LOG_LEVEL = log.error local function _(msg) - return dt.gettext.gettext(msg) + return gettext(msg) end dtutils_string.log_level = DEFAULT_LOG_LEVEL diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 7991672e..a925d847 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -56,10 +56,10 @@ local dtsys = require "lib/dtutils.system" local log = require "lib/dtutils.log" local debug = require "darktable.debug" -local gettext = dt.gettext +local gettext = dt.gettext.gettext local function _(msgid) - return gettext.dgettext("script_manager", msgid) + return gettext(msgid) end -- api check From 300bb9d44377244ba9ef962a8f60c4ea1b76edf8 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 19 Oct 2024 22:04:56 -0400 Subject: [PATCH 087/169] lib/dtutils/string - fixed _should_be_sanitized with new patterns to detect non safe filename characters --- lib/dtutils/string.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 5cf49cd3..ff8f73ef 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -306,8 +306,16 @@ end local function _should_be_sanitized(str) local old_log_level = log.log_level() local result = false + local SAFE_POSIX_FILENAME_CHARS = "[^%w/._%-]+" + local SAFE_WIN_FILENAME_CHARS = "[^%w\\._%-:]+" + + local pattern = SAFE_POSIX_STRING_CHARS + if dt.configuration.running_os == "windows" then + pattern = SAFE_WIN_STRING_CHARS + end + log.log_level(dtutils_string.log_level) - if string.match(str, "[^%g]") then + if string.match(str, pattern) then result = true end log.log_level(old_log_level) From 8e4767c42a476c9423dbbfed53f00c3b58e4c2a9 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 21 Oct 2024 14:32:53 -0400 Subject: [PATCH 088/169] tools/script_manager - fixed translation of page %d of %d --- tools/script_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index a925d847..bbefb1e7 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -1006,7 +1006,7 @@ local function paginate(direction) last = first + sm.page_status.num_buttons - 1 end - sm.widgets.page_status.label = _(string.format("page %d of %d", cur_page, max_pages)) + sm.widgets.page_status.label = string.format(_("page %d of %d"), cur_page, max_pages) populate_buttons(folder, first, last) From 13f210bec9566b805ffa7136be0b087b3cba6d99 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 21 Oct 2024 22:52:11 -0400 Subject: [PATCH 089/169] official/apply_camera_style - made name tranlatable and made it into name string instead of the filename tools/script_manager - change get_script_metadata to retreive a script metadata block and store it as a metadata block making the indiviual fields accessible. change the script buttons to use the translated name if available and just the purpose for the tooltip. --- official/apply_camera_style.lua | 2 +- tools/script_manager.lua | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index a72c8217..8c8aeae4 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -89,7 +89,7 @@ script_data.restart = nil -- how to restart the (lib) script after it' script_data.show = nil -- only required for libs since the destroy_method only hides them script_data.metadata = { - name = "apply_camera_style", -- name of script + name = _("apply camera style"), -- name of script purpose = _("apply darktable camera style to matching images"), -- purpose of script author = "Bill Ferguson ", -- your name and optionally e-mail address help = "https://docs.darktable.org/lua/development/lua.scripts.manual/scripts/official/apply_camera_style/" -- URL to help/documentation diff --git a/tools/script_manager.lua b/tools/script_manager.lua index bbefb1e7..59c23c43 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -405,8 +405,8 @@ local function get_script_metadata(script) log.msg(log.debug, "processing metatdata for " .. script) - local description = nil - local metadata = nil + local metadata_block = nil + local metadata = {} f = io.open(LUA_DIR .. PS .. script .. ".lua") if f then @@ -414,20 +414,19 @@ local function get_script_metadata(script) local content = f:read("*all") f:close() -- grab the script_data.metadata table - description = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") + metadata_block = string.match(content, "script_data%.metadata = %{\r?\n(.-)\r?\n%}") else log.msg(log.error, "cant read from " .. script) end - if description then - metadata = "" - -- format it into a string block for display - local lines = du.split(description, "\n") + if metadata_block then + -- break up the lines into key value pairs + local lines = du.split(metadata_block, "\n") log.msg(log.debug, "got " .. #lines .. " lines") - local first = 1 for i = 1, #lines do log.msg(log.debug, "splitting line " .. lines[i]) local parts = du.split(lines[i], " = ") + parts[1] = string_trim(parts[1]) log.msg(log.debug, "got value " .. parts[1] .. " and data " .. parts[2]) if string.match(parts[2], "%_%(") then parts[2] = _(string_dequote(string_dei18n(parts[2]))) @@ -437,14 +436,15 @@ local function get_script_metadata(script) if string.match(parts[2], ",$") then parts[2] = string_chop(parts[2]) end - metadata = metadata .. string.format("%s%-10s\t%s", first and "" or "\n", parts[1], parts[2]) - first = nil + log.msg(log.debug, "parts 1 is " .. parts[1] .. " and parts 2 is " .. parts[2]) + metadata[parts[1]] = parts[2] + log.msg(log.debug, "metadata " .. parts[1] .. " is " .. metadata[parts[1]]) end - log.msg(log.debug, "script data is \n" .. metadata) + log.msg(log.debug, "script data found for " .. metadata["name"]) end restore_log_level(old_log_level) - return metadata + return metadata_block and metadata or nil end local function get_script_doc(script) @@ -910,11 +910,11 @@ local function populate_buttons(folder, first, last) end button.image = POWER_ICON - label.label = script.name + label.label = script.metadata and script.metadata.name or script.name label.name = "pb_label" button.ellipsize = "end" button.sensitive = true - label.tooltip = script.metadata and script.metadata or script.doc + label.tooltip = script.metadata and script.metadata.purpose or script.doc button.clicked_callback = function (this) local cb_script = script From d12363467933c99faec332253627b113a0c37dc8 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 22 Oct 2024 00:13:30 -0400 Subject: [PATCH 090/169] Changed script names in metadata from filenames to names by removing underscores, spelling out abbreviations, etc. --- contrib/AutoGrouper.lua | 2 +- contrib/CollectHelper.lua | 2 +- contrib/HDRMerge.lua | 2 +- contrib/LabelsToTags.lua | 2 +- contrib/OpenInExplorer.lua | 2 +- contrib/RL_out_sharp.lua | 2 +- contrib/auto_snapshot.lua | 2 +- contrib/autostyle.lua | 2 +- contrib/change_group_leader.lua | 2 +- contrib/clear_GPS.lua | 2 +- contrib/color_profile_manager.lua | 2 +- contrib/copy_attach_detach_tags.lua | 2 +- contrib/cr2hdr.lua | 2 +- contrib/cycle_group_leader.lua | 2 +- contrib/dbmaint.lua | 2 +- contrib/enfuseAdvanced.lua | 2 +- contrib/exportLUT.lua | 2 +- contrib/ext_editor.lua | 2 +- contrib/face_recognition.lua | 2 +- contrib/fujifilm_dynamic_range.lua | 2 +- contrib/fujifilm_ratings.lua | 2 +- contrib/geoJSON_export.lua | 2 +- contrib/geoToolbox.lua | 2 +- contrib/gimp.lua | 2 +- contrib/gpx_export.lua | 2 +- contrib/harmonic_armature_guide.lua | 2 +- contrib/hif_group_leader.lua | 2 +- contrib/hugin.lua | 2 +- contrib/image_stack.lua | 2 +- contrib/image_time.lua | 2 +- contrib/jpg_group_leader.lua | 2 +- contrib/kml_export.lua | 2 +- contrib/passport_guide.lua | 2 +- contrib/passport_guide_germany.lua | 2 +- contrib/pdf_slideshow.lua | 2 +- contrib/photils.lua | 2 +- contrib/quicktag.lua | 2 +- contrib/rate_group.lua | 2 +- contrib/rename-tags.lua | 2 +- contrib/rename_images.lua | 2 +- contrib/select_non_existing.lua | 2 +- contrib/select_untagged.lua | 2 +- contrib/slideshowMusic.lua | 2 +- contrib/transfer_hierarchy.lua | 2 +- contrib/video_ffmpeg.lua | 2 +- examples/api_version.lua | 2 +- examples/darkroom_demo.lua | 2 +- examples/gettextExample.lua | 2 +- examples/gui_action.lua | 2 +- examples/hello_world.lua | 2 +- examples/lighttable_demo.lua | 2 +- examples/moduleExample.lua | 2 +- examples/multi_os.lua | 2 +- examples/panels_demo.lua | 2 +- examples/preferenceExamples.lua | 2 +- examples/printExamples.lua | 2 +- examples/running_os.lua | 2 +- examples/x-touch.lua | 2 +- official/check_for_updates.lua | 2 +- official/copy_paste_metadata.lua | 2 +- official/delete_long_tags.lua | 2 +- official/delete_unused_tags.lua | 2 +- official/enfuse.lua | 2 +- official/generate_image_txt.lua | 2 +- official/image_path_in_ui.lua | 2 +- official/import_filter_manager.lua | 2 +- official/import_filters.lua | 2 +- official/save_selection.lua | 2 +- official/selection_to_pdf.lua | 2 +- tools/executable_manager.lua | 2 +- tools/get_lib_manpages.lua | 2 +- tools/get_libdoc.lua | 2 +- 72 files changed, 72 insertions(+), 72 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index f8d8061b..67609374 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -54,7 +54,7 @@ end local script_data = {} script_data.metadata = { - name = "AutoGrouper", + name = _("auto group"), purpose = _("automatically group images by time interval"), author = "Kevin Ertel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/AutoGrouper/" diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index f06b14e4..978aa309 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -62,7 +62,7 @@ end local script_data = {} script_data.metadata = { - name = "CollectHelper", + name = _("collection helper"), purpose = _("add collection helper buttons"), author = "Kevin Ertel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/CollectHelper/" diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 76067d53..9d7a951d 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -63,7 +63,7 @@ end local script_data = {} script_data.metadata = { - name = "HDRmerge", + name = _("HDR merge"), purpose = _("merge bracketed images into an HDR DNG image"), author = "Kevin Ertel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/HDRmerge" diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 0c00c123..32031a3b 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -63,7 +63,7 @@ end local script_data = {} script_data.metadata = { - name = "LabelsToTags", + name = _("labels to tags"), purpose = _("allows the mass-application of tags using color labels and ratings as a guide"), author = "August Schwerdfeger (august@schwerdfeger.name)", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/LabelsToTags" diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 4ba59690..14f788d7 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -65,7 +65,7 @@ end local script_data = {} script_data.metadata = { - name = "OpenInExplorer", + name = _("open in explorer"), purpose = _("open a selected file in the system file manager"), author = "Kevin Ertel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/OpenInExplorer" diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index 9083277f..b65581c9 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -77,7 +77,7 @@ local function _(msgid) local script_data = {} script_data.metadata = { - name = "RL_out_sharp", + name = _("RL output sharpening"), purpose = _("Richardson-Lucy output sharpening using GMic"), author = "Marco Carrarini ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/RL_out_sharp" diff --git a/contrib/auto_snapshot.lua b/contrib/auto_snapshot.lua index e3ede5b1..48f99f5b 100644 --- a/contrib/auto_snapshot.lua +++ b/contrib/auto_snapshot.lua @@ -76,7 +76,7 @@ script_data.restart = nil -- how to restart the (lib) script after it' script_data.show = nil -- only required for libs since the destroy_method only hides them script_data.metadata = { - name = "auto_snapshot", -- name of script + name = _("auto snapshot"), -- name of script purpose = _("automatically take a snapshot when an image is loaded in darkroom"), -- purpose of script author = "Bill Ferguson ", -- your name and optionally e-mail address help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_snapshot/" -- URL to help/documentation diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 46b14cac..093c29a3 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -54,7 +54,7 @@ end local script_data = {} script_data.metadata = { - name = "autostyle", + name = _("auto style"), purpose = _("automatically apply a style based on image EXIF tag"), author = "Marc Cousin ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle/" diff --git a/contrib/change_group_leader.lua b/contrib/change_group_leader.lua index e7f424b7..0e6f66e6 100644 --- a/contrib/change_group_leader.lua +++ b/contrib/change_group_leader.lua @@ -51,7 +51,7 @@ end local script_data = {} script_data.metadata = { - name = "change_group_leader", + name = _("change group leader"), purpose = _("automatically change the leader of raw+jpg paired image groups"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/change_group_leader" diff --git a/contrib/clear_GPS.lua b/contrib/clear_GPS.lua index 8c591661..2663ab4b 100644 --- a/contrib/clear_GPS.lua +++ b/contrib/clear_GPS.lua @@ -50,7 +50,7 @@ end local script_data = {} script_data.metadata = { - name = "clear_GPS", + name = _("clear GPS info"), purpose = _("remove GPS data from selected image(s)"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps/" diff --git a/contrib/color_profile_manager.lua b/contrib/color_profile_manager.lua index 6114992e..683ae990 100644 --- a/contrib/color_profile_manager.lua +++ b/contrib/color_profile_manager.lua @@ -363,7 +363,7 @@ end local script_data = {} script_data.metadata = { - name = "color_profile_manager", + name = _("color profile manager"), purpose = _("manage external darktable color profiles"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/color_profile_manager" diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index 2b66bc76..3f1f6d34 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -53,7 +53,7 @@ end local script_data = {} script_data.metadata = { - name = "copy_attach_detach_tags", + name = _("copy attach detach tags"), purpose = _("shortcuts to copy, paste, replace, or remove tags from images"), author = "Christian Kanzian", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags" diff --git a/contrib/cr2hdr.lua b/contrib/cr2hdr.lua index 209287cc..36f325f2 100644 --- a/contrib/cr2hdr.lua +++ b/contrib/cr2hdr.lua @@ -48,7 +48,7 @@ end local script_data = {} script_data.metadata = { - name = "cr2hdr", + name = _("cr2hdr"), purpose = _("process Magic Lantern dual ISO images"), author = "Till Theato ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr" diff --git a/contrib/cycle_group_leader.lua b/contrib/cycle_group_leader.lua index 665f0c09..e168e93b 100644 --- a/contrib/cycle_group_leader.lua +++ b/contrib/cycle_group_leader.lua @@ -71,7 +71,7 @@ end local script_data = {} script_data.metadata = { - name = "cycle_group_leader", + name = _("cycle group leader"), purpose = _("change image group leader"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cycle_group_leader" diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua index 044a9ac5..16ca9b91 100644 --- a/contrib/dbmaint.lua +++ b/contrib/dbmaint.lua @@ -80,7 +80,7 @@ script_data.restart = nil -- how to restart the (lib) script after it' script_data.show = nil -- only required for libs since the destroy_method only hides them script_data.metadata = { - name = "dbmaint", + name = _("db maintenance"), purpose = _("perform database maintenance"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/dbmaint/" diff --git a/contrib/enfuseAdvanced.lua b/contrib/enfuseAdvanced.lua index b35cb490..5027c564 100644 --- a/contrib/enfuseAdvanced.lua +++ b/contrib/enfuseAdvanced.lua @@ -79,7 +79,7 @@ end local script_data = {} script_data.metadata = { - name = "enfuseAdvanced", + name = _("enfuse advanced"), purpose = _("focus stack or exposure blend images"), author = "Kevin Ertel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseAdvanced" diff --git a/contrib/exportLUT.lua b/contrib/exportLUT.lua index 80854567..9ec29544 100644 --- a/contrib/exportLUT.lua +++ b/contrib/exportLUT.lua @@ -44,7 +44,7 @@ end local script_data = {} script_data.metadata = { - name = "exportLUT", + name = _("export LUT"), purpose = _("export a style as a LUT"), author = "Noah Clarke", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportLUT" diff --git a/contrib/ext_editor.lua b/contrib/ext_editor.lua index 0cee9454..be371e92 100644 --- a/contrib/ext_editor.lua +++ b/contrib/ext_editor.lua @@ -87,7 +87,7 @@ end local script_data = {} script_data.metadata = { - name = "ext_editor", + name = _("external editors"), purpose = _("edit images with external editors"), author = "Marco Carrarini, marco.carrarini@gmail.com", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor" diff --git a/contrib/face_recognition.lua b/contrib/face_recognition.lua index 74827f12..0c60a5c1 100644 --- a/contrib/face_recognition.lua +++ b/contrib/face_recognition.lua @@ -63,7 +63,7 @@ end local script_data = {} script_data.metadata = { - name = "face_recognition", + name = _("face recognition"), purpose = _("use facial recognition to tag images"), author = "Sebastian Witt", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition" diff --git a/contrib/fujifilm_dynamic_range.lua b/contrib/fujifilm_dynamic_range.lua index 7bb6ab53..37f79511 100644 --- a/contrib/fujifilm_dynamic_range.lua +++ b/contrib/fujifilm_dynamic_range.lua @@ -74,7 +74,7 @@ end local script_data = {} script_data.metadata = { - name = "fujifilm_dynamic_range", + name = _("fujifilm dynamic range"), purpose = _("compensate for Fujifilm raw files made using \"dynamic range\""), author = "Dan Torop ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range" diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index c9a034d8..b29996d9 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -40,7 +40,7 @@ end local script_data = {} script_data.metadata = { - name = "fujifilm_ratings", + name = _("fujifilm ratings"), purpose = _("import Fujifilm in-camera ratings"), author = "Ben Mendis ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings" diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 93ecd9ff..93e77289 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -49,7 +49,7 @@ end local script_data = {} script_data.metadata = { - name = "geoJSON_export", + name = _("geoJSON export"), purpose = _("export a geoJSON file from geo data"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoJSON_export" diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index f2ab6957..0fa07301 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -42,7 +42,7 @@ end local script_data = {} script_data.metadata = { - name = "geoToolbox", + name = _("geo toolbox"), purpose = _("geodata tools"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geoToolbox" diff --git a/contrib/gimp.lua b/contrib/gimp.lua index 8e285517..92c6d0ce 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -83,7 +83,7 @@ end local script_data = {} script_data.metadata = { - name = "gimp", + name = _("edit with GIMP"), purpose = _("export and edit with GIMP"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp" diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index 1525aff8..b310f3a9 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -38,7 +38,7 @@ end local script_data = {} script_data.metadata = { - name = "gpx_export", + name = _("gpx export"), purpose = _("export gpx information to a file"), author = "Jannis_V", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export" diff --git a/contrib/harmonic_armature_guide.lua b/contrib/harmonic_armature_guide.lua index a85195cc..ffe03a29 100644 --- a/contrib/harmonic_armature_guide.lua +++ b/contrib/harmonic_armature_guide.lua @@ -43,7 +43,7 @@ end local script_data = {} script_data.metadata = { - name = "harmonic_armature_guide", + name = _("harmonic armature guide"), purpose = _("harmonic artmature guide"), author = "Hubert Kowalski", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/harmonic_armature_guide" diff --git a/contrib/hif_group_leader.lua b/contrib/hif_group_leader.lua index 9543becb..f39d3ff0 100644 --- a/contrib/hif_group_leader.lua +++ b/contrib/hif_group_leader.lua @@ -75,7 +75,7 @@ end local script_data = {} script_data.metadata = { - name = "hif_group_leader", + name = _("HIF group leader"), purpose = _("make hif image group leader"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hif_group_leader" diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 49868424..85fe5f85 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -65,7 +65,7 @@ end local script_data = {} script_data.metadata = { - name = "hugin", + name = _("hugin"), purpose = _("stitch images into a panorama"), author = "Wolfgang Goetz", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin" diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index 817dc7a6..661b28d3 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -82,7 +82,7 @@ end local script_data = {} script_data.metadata = { - name = "image_stack", + name = _("image stack"), purpose = _("process a stack of images"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack" diff --git a/contrib/image_time.lua b/contrib/image_time.lua index 4daf9c6b..28570c1a 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -125,7 +125,7 @@ end local script_data = {} script_data.metadata = { - name = "image_time", + name = _("image time"), purpose = _("synchronize image time for images shot with different cameras or adjust or set image time"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time" diff --git a/contrib/jpg_group_leader.lua b/contrib/jpg_group_leader.lua index 7ca1359b..8391a670 100644 --- a/contrib/jpg_group_leader.lua +++ b/contrib/jpg_group_leader.lua @@ -75,7 +75,7 @@ end local script_data = {} script_data.metadata = { - name = "jpg_group_leader", + name = _("JPG group leader"), purpose = _("make jpg image group leader"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/jpg_group_leader" diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 561e8ba8..561724fa 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -54,7 +54,7 @@ end local script_data = {} script_data.metadata = { - name = "kml_export", + name = _("kml export"), purpose = _("export KML/KMZ data to a file"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export" diff --git a/contrib/passport_guide.lua b/contrib/passport_guide.lua index 91e07052..34e9fc79 100644 --- a/contrib/passport_guide.lua +++ b/contrib/passport_guide.lua @@ -48,7 +48,7 @@ end local script_data = {} script_data.metadata = { - name = "passport_guide", + name = _("passport guide"), purpose = _("guides for cropping passport photos"), author = "Kåre Hampf", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide" diff --git a/contrib/passport_guide_germany.lua b/contrib/passport_guide_germany.lua index 745f86df..a1b9f618 100644 --- a/contrib/passport_guide_germany.lua +++ b/contrib/passport_guide_germany.lua @@ -52,7 +52,7 @@ end local script_data = {} script_data.metadata = { - name = "passport_guide_germany", + name = _("passport guide Germany"), purpose = _("guides for cropping passport and ID card (\"Personalausweis\") photos based on the \"Passbild-Schablone\" from the German Federal Ministry of the Interior and Community"), author = "menschmachine", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide_germany" diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index 36b0b1b8..b93ae978 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -60,7 +60,7 @@ du.check_min_api_version("7.0.0", "pdf_slideshow") local script_data = {} script_data.metadata = { - name = "pdf_slideshow", + name = _("PDF slideshow"), purpose = _("generates a PDF slideshow (via Latex) containing all selected images one per slide"), author = "Pascal Obry", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow" diff --git a/contrib/photils.lua b/contrib/photils.lua index a5fe59be..7bb39c6f 100644 --- a/contrib/photils.lua +++ b/contrib/photils.lua @@ -55,7 +55,7 @@ end local script_data = {} script_data.metadata = { - name = "photils", + name = _("photils"), purpose = _("suggest tags based on image classification"), author = "Tobias Scheck", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils" diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index 7dd604da..c218f09c 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -60,7 +60,7 @@ end local script_data = {} script_data.metadata = { - name = "quicktag", + name = _("quick tag"), purpose = _("use buttons to quickly apply tags assigned to them"), author = "Christian Kanzian", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag" diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 8c3a15d3..0e9ddb29 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -55,7 +55,7 @@ end local script_data = {} script_data.metadata = { - name = "rate_group", + name = _("rate group"), purpose = _("rate all images in a group"), author = "Dom H (dom@hxy.io)", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group" diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index 69509a53..ae77056a 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -47,7 +47,7 @@ end local script_data = {} script_data.metadata = { - name = "rename-tags", + name = _("rename tags"), purpose = _("rename an existing tag"), author = "Sebastian Witt (se.witt@gmx.net)", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags" diff --git a/contrib/rename_images.lua b/contrib/rename_images.lua index d50c8e18..a0d37e99 100644 --- a/contrib/rename_images.lua +++ b/contrib/rename_images.lua @@ -65,7 +65,7 @@ rename.event_registered = false local script_data = {} script_data.metadata = { - name = "rename_images", + name = _("rename images"), purpose = _("rename an image file or files"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images" diff --git a/contrib/select_non_existing.lua b/contrib/select_non_existing.lua index d7bfa641..b47c7cf6 100644 --- a/contrib/select_non_existing.lua +++ b/contrib/select_non_existing.lua @@ -74,7 +74,7 @@ dt.gui.libs.select.register_selection( local script_data = {} script_data.metadata = { - name = "select_non_existing", + name = _("select non existing"), purpose = _("enable selection of non-existing images"), author = "Dirk Dittmar", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing" diff --git a/contrib/select_untagged.lua b/contrib/select_untagged.lua index c86cf831..0ca4b5d2 100644 --- a/contrib/select_untagged.lua +++ b/contrib/select_untagged.lua @@ -33,7 +33,7 @@ end local script_data = {} script_data.metadata = { - name = "select_untagged", + name = _("select untagged"), purpose = _("enable selection of untagged images"), author = "Jannis_V", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged" diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index d83dd248..bed3da6f 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -40,7 +40,7 @@ end local script_data = {} script_data.metadata = { - name = "slideshowMusic", + name = _("slideshow music"), purpose = _("play music during a slideshow"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic" diff --git a/contrib/transfer_hierarchy.lua b/contrib/transfer_hierarchy.lua index 16c23593..dd9c2708 100755 --- a/contrib/transfer_hierarchy.lua +++ b/contrib/transfer_hierarchy.lua @@ -90,7 +90,7 @@ end local script_data = {} script_data.metadata = { - name = "transfer_hierarchy", + name = _("transfer hierarchy"), purpose = _("allows the moving or copying of images from one directory tree to another, while preserving the existing hierarchy"), author = "August Schwerdfeger (august@schwerdfeger.name)", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy" diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 70af1efe..f0713fa6 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -50,7 +50,7 @@ end local script_data = {} script_data.metadata = { - name = "video_ffmpeg", + name = _("video ffmpeg"), purpose = _("timelapse video plugin based on ffmpeg"), author = "Dominik Markiewicz", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contib/video_ffmpeg" diff --git a/examples/api_version.lua b/examples/api_version.lua index aaf341c7..bf8a1c17 100644 --- a/examples/api_version.lua +++ b/examples/api_version.lua @@ -47,7 +47,7 @@ dt.print("API " .. _("version") .. ": " .. result) local script_data = {} script_data.metadata = { - name = "api_version", + name = _("APIversion"), purpose = _("display api_version example"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version" diff --git a/examples/darkroom_demo.lua b/examples/darkroom_demo.lua index 72bf1324..b76b552e 100644 --- a/examples/darkroom_demo.lua +++ b/examples/darkroom_demo.lua @@ -117,7 +117,7 @@ dt.gui.current_view(current_view) local script_data = {} script_data.metadata = { - name = "darkroom_demo", + name = _("darkroom demo"), purpose = _("example demonstrating how to control image display in darkroom mode"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo" diff --git a/examples/gettextExample.lua b/examples/gettextExample.lua index acffdca2..42e1a479 100644 --- a/examples/gettextExample.lua +++ b/examples/gettextExample.lua @@ -86,7 +86,7 @@ dt.print_error(_("hello world!")) local script_data = {} script_data.metadata = { - name = "gettextExample", + name = _("gettext example"), purpose = _("example of how translations works"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextExample" diff --git a/examples/gui_action.lua b/examples/gui_action.lua index b956e5a9..9fee3619 100644 --- a/examples/gui_action.lua +++ b/examples/gui_action.lua @@ -15,7 +15,7 @@ end local script_data = {} script_data.metadata = { - name = "gui_action", + name = _("gui action"), purpose = _("example of how to use darktable.gui.action() calls"), author = "Diederik ter Rahe", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gui_action" diff --git a/examples/hello_world.lua b/examples/hello_world.lua index d29c99c7..6e04efc4 100644 --- a/examples/hello_world.lua +++ b/examples/hello_world.lua @@ -53,7 +53,7 @@ dt.print(_("hello, world")) local script_data = {} script_data.metadata = { - name = "hello_world", + name = _("hello world"), purpose = _("example of how to print a message to the screen"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world" diff --git a/examples/lighttable_demo.lua b/examples/lighttable_demo.lua index d897ff8a..e28454cd 100644 --- a/examples/lighttable_demo.lua +++ b/examples/lighttable_demo.lua @@ -218,7 +218,7 @@ current_sort_order = dt.gui.libs.filter.sort_order(current_sort_order) local script_data = {} script_data.metadata = { - name = "lighttable_demo", + name = _("lighttable demo"), purpose = _("example demonstrating how to control lighttable display modes"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo" diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index 6996d2a9..bc432677 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -47,7 +47,7 @@ end local script_data = {} script_data.metadata = { - name = "moduleExample", + name = ("module example"), purpose = _("example of how to create a lighttable module"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleExample" diff --git a/examples/multi_os.lua b/examples/multi_os.lua index 83bfee98..21dc0cb1 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -260,7 +260,7 @@ dt.register_event( local script_data = {} script_data.metadata = { - name = "multi_os", + name = _("multi OS"), purpose = _("example module thet runs on different operating systems"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os" diff --git a/examples/panels_demo.lua b/examples/panels_demo.lua index 6fa82607..ac9e3de4 100644 --- a/examples/panels_demo.lua +++ b/examples/panels_demo.lua @@ -146,7 +146,7 @@ end local script_data = {} script_data.metadata = { - name = "panels_demo", + name = _("panels demo"), purpose = _("example demonstrating how to contol panel visibility"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo" diff --git a/examples/preferenceExamples.lua b/examples/preferenceExamples.lua index c53ce163..84425744 100644 --- a/examples/preferenceExamples.lua +++ b/examples/preferenceExamples.lua @@ -35,7 +35,7 @@ end local script_data = {} script_data.metadata = { - name = "preferenceExamples", + name = _("preference examples"), purpose = _("example to show the different preference types that are possible"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceExamples" diff --git a/examples/printExamples.lua b/examples/printExamples.lua index 4bedaffa..721138cd 100644 --- a/examples/printExamples.lua +++ b/examples/printExamples.lua @@ -58,7 +58,7 @@ dt.print_log("print log") local script_data = {} script_data.metadata = { - name = "printExamples", + name = _("print examples"), purpose = _("example showing the different types of printing messages"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printExamples" diff --git a/examples/running_os.lua b/examples/running_os.lua index 3c45cee8..69741288 100644 --- a/examples/running_os.lua +++ b/examples/running_os.lua @@ -52,7 +52,7 @@ dt.print(string.format(_("you are running: %s"), dt.configuration.running_os)) local script_data = {} script_data.metadata = { - name = "running_os", + name = _("running OS"), purpose = _("example of how to determine the operating system being used"), author = "Tobias Jakobs", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os" diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 969fd105..bbe9bbd6 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -66,7 +66,7 @@ end local script_data = {} script_data.metadata = { - name = "x-touch", + name = _("x-touch"), purpose = _("example of how to control an x-touch midi device"), author = "Diederik ter Rahe", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/x-touch" diff --git a/official/check_for_updates.lua b/official/check_for_updates.lua index 45ec19b0..aeb45edb 100644 --- a/official/check_for_updates.lua +++ b/official/check_for_updates.lua @@ -45,7 +45,7 @@ end local script_data = {} script_data.metadata = { - name = "check_for_updates", + name = _("check for updates"), purpose = _("check for newer darktable releases"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates" diff --git a/official/copy_paste_metadata.lua b/official/copy_paste_metadata.lua index 37eb94f4..00040c9b 100644 --- a/official/copy_paste_metadata.lua +++ b/official/copy_paste_metadata.lua @@ -40,7 +40,7 @@ end local script_data = {} script_data.metadata = { - name = "copy_paste_metadata", + name = _("copy paste metadata"), purpose = _("adds keyboard shortcuts and buttons to copy/paste metadata between images"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata" diff --git a/official/delete_long_tags.lua b/official/delete_long_tags.lua index 89b192f1..c770402b 100644 --- a/official/delete_long_tags.lua +++ b/official/delete_long_tags.lua @@ -44,7 +44,7 @@ end local script_data = {} script_data.metadata = { - name = "delete_long_tags", + name = _("delete long tags"), purpose = _("delete all tags longer than a set length"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags" diff --git a/official/delete_unused_tags.lua b/official/delete_unused_tags.lua index 62ebc95e..3815aea0 100644 --- a/official/delete_unused_tags.lua +++ b/official/delete_unused_tags.lua @@ -48,7 +48,7 @@ end local script_data = {} script_data.metadata = { - name = "delete_unused_tags", + name = _("delete unused tags"), purpose = _("delete unused tags"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags" diff --git a/official/enfuse.lua b/official/enfuse.lua index 317ab73d..28d6da86 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -52,7 +52,7 @@ end local script_data = {} script_data.metadata = { - name = "enfuse", + name = _("enfuse"), purpose = _("exposure blend images"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/enfuse" diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index 42f64987..c786aabf 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -51,7 +51,7 @@ du.check_min_api_version("7.0.0", "generate_image_txt") local script_data = {} script_data.metadata = { - name = "generate_image_txt", + name = _("generate image text"), purpose = _("overlay metadata on the selected image(s)"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt" diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index b4440db9..74671cd6 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -44,7 +44,7 @@ end local script_data = {} script_data.metadata = { - name = "image_path_in_ui", + name = _("image path in UI"), purpose = _("print the image path in the UI"), author = "Jérémy Rosen", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui" diff --git a/official/import_filter_manager.lua b/official/import_filter_manager.lua index 11191504..adf83620 100644 --- a/official/import_filter_manager.lua +++ b/official/import_filter_manager.lua @@ -41,7 +41,7 @@ end local script_data = {} script_data.metadata = { - name = "import_filter_manager", + name = _("import filter manager"), purpose = _("manage import filters"), author = "Tobias Ellinghaus", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager" diff --git a/official/import_filters.lua b/official/import_filters.lua index 44633062..87d8b0ef 100644 --- a/official/import_filters.lua +++ b/official/import_filters.lua @@ -39,7 +39,7 @@ end local script_data = {} script_data.metadata = { - name = "import_filters", + name = _("import filters"), purpose = _("import filtering"), author = "Tobias Ellinghaus & Christian Mandel", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filters" diff --git a/official/save_selection.lua b/official/save_selection.lua index 77e82eb4..64d33b4e 100644 --- a/official/save_selection.lua +++ b/official/save_selection.lua @@ -49,7 +49,7 @@ end local script_data = {} script_data.metadata = { - name = "save_selection", + name = _("save selection"), purpose = _("shortcuts providing multiple selection buffers"), author = "Jérémy Rosen", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection" diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index e4f22f5e..64b9c8be 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -49,7 +49,7 @@ end local script_data = {} script_data.metadata = { - name = "selection_to_pdf", + name = _("selection to PDF"), purpose = _("generate a pdf file of selected images"), author = "Jérémy Rosen & Pascal Obry", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf" diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 97421e4e..4d03be28 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -46,7 +46,7 @@ end local script_data = {} script_data.metadata = { - name = "executable_manager", + name = _("executable manager"), purpose = _("manage the list of external executables used by the lua scripts"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager" diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index 65c68046..0e641104 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -88,7 +88,7 @@ end local script_data = {} script_data.metadata = { - name = "get_lib_manpages", + name = _("get library man pages"), purpose = _("output the internal library documentation as man pages"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_lib_manpages" diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 78bbf760..2c4458d0 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -62,7 +62,7 @@ end local script_data = {} script_data.metadata = { - name = "get_libdoc", + name = _("get library docs"), purpose = _("retrieve and print the documentation to the console"), author = "Bill Ferguson ", help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_libdoc" From 8e129d3825c23ddf17cc8c57802d37891474470d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 22 Oct 2024 16:38:03 -0400 Subject: [PATCH 091/169] tools/script_manager - added translated folder names. cleaned up metadata handling (trimmed whitespace and comments) fixed bug with folder names partially matching other folder names --- tools/script_manager.lua | 44 +++++++++++++++++++++++++++++++++++----- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 59c23c43..573f1825 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -132,6 +132,7 @@ sm.event_registered = false sm.widgets = {} sm.folders = {} +sm.translated_folders = {} -- set log level for functions @@ -364,8 +365,9 @@ end local function string_trim(str) local old_log_level = set_log_level(sm.log_level) - local result = string.gsub(str, "^%s+", "") - result = string.gsub(result, "%s+$", "") + local result = string.gsub(str, "^%s+", "") -- trim leading spaces + result = string.gsub(result, "%s+$", "") -- trim trailing spaces + result = string.gsub(result, ",?%s+%-%-.+$", "") -- trim trailing comma and comments restore_log_level(old_log_level) return result @@ -387,11 +389,42 @@ end -- script handling ------------------ +local function is_folder_known(folder_table, name) + local match = false + + for _, folder_name in ipairs(folder_table) do + if name == folder_name then + match = true + end + end + + return match +end + +local function find_translated_name(folder) + local translated_name = nil + + if folder == "contrib" then + translated_name = _("contributed") + elseif folder == "examples" then + translated_name = _("examples") + elseif folder == "official" then + translated_name = _("official") + elseif folder == "tools" then + translated_name = _("tools") + else + translated_name = _(folder) -- in case we get lucky and the string got translated elsewhere + end + + return translated_name +end + local function add_script_folder(folder) local old_log_level = set_log_level(sm.log_level) - if #sm.folders == 0 or not string.match(du.join(sm.folders, " "), ds.sanitize_lua(folder)) then + if #sm.folders == 0 or not is_folder_known(sm.folders, folder) then table.insert(sm.folders, folder) + table.insert(sm.translated_folders, find_translated_name(folder)) sm.scripts[folder] = {} log.msg(log.debug, "created folder " .. folder) end @@ -427,6 +460,7 @@ local function get_script_metadata(script) log.msg(log.debug, "splitting line " .. lines[i]) local parts = du.split(lines[i], " = ") parts[1] = string_trim(parts[1]) + parts[2] = string_trim(parts[2]) log.msg(log.debug, "got value " .. parts[1] .. " and data " .. parts[2]) if string.match(parts[2], "%_%(") then parts[2] = _(string_dequote(string_dei18n(parts[2]))) @@ -1378,10 +1412,10 @@ sm.widgets.folder_selector = dt.new_widget("combobox"){ changed_callback = function(self) if sm.run then pref_write("folder_selector", "integer", self.selected) - change_folder(self.value) + change_folder(sm.folders[self.selected]) end end, - table.unpack(sm.folders), + table.unpack(sm.translated_folders), } -- a script "button" consists of: From a0af01884df3d83d5ee1b65394e584934634505c Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 23 Oct 2024 10:57:29 -0400 Subject: [PATCH 092/169] official/enfuse - fixed name and widget values to be translatable --- official/enfuse.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/official/enfuse.lua b/official/enfuse.lua index 28d6da86..7bda2cf4 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -72,7 +72,7 @@ local function install_module() if not enf.module_installed then dt.register_lib( "enfuse", -- plugin name - "enfuse", -- name + _("enfuse"), -- name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers @@ -135,7 +135,7 @@ if enfuse_installed then if version < "4.2" then exposure_mu = dt.new_widget("slider") { - label = "exposure mu", + label = _("exposure mu"), tooltip = _("center also known as mean of gaussian weighting function (0 <= mean <= 1); default: 0.5"), hard_min = 0, hard_max = 1, @@ -144,7 +144,7 @@ if enfuse_installed then else exposure_mu = dt.new_widget("slider") { - label = "exposure optimum", + label = _("exposure optimum"), tooltip = _("optimum exposure value, usually the maximum of the weighting function (0 <= optimum <=1); default 0.5"), hard_min = 0, hard_max = 1, @@ -154,7 +154,7 @@ if enfuse_installed then local depth = dt.new_widget("combobox") { - label = "depth", + label = _("depth"), tooltip = _("the number of bits per channel of the output image"), value = dt.preferences.read("enfuse", "depth", "integer"), changed_callback = function(w) dt.preferences.write("enfuse", "depth", "integer", w.selected) end, @@ -163,7 +163,7 @@ if enfuse_installed then local blend_colorspace = dt.new_widget("combobox") { - label = "blend colorspace", + label = _("blend colorspace"), tooltip = _("force blending in selected colorspace"), changed_callback = function(w) dt.preferences.write("enfuse", "blend_colorspace", "string", w.selected) end, "", "identity", "ciecam" From 21ec2883fe51e7bf8723ee6919b6e6bb04f300ea Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 23 Oct 2024 11:42:41 -0400 Subject: [PATCH 093/169] Added translation to module name --- examples/moduleExample.lua | 2 +- official/image_path_in_ui.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index bc432677..89c56bc2 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -75,7 +75,7 @@ local function install_module() -- https://www.darktable.org/lua-api/index.html#darktable_register_lib dt.register_lib( "exampleModule", -- Module name - "exampleModule", -- name + _("example module"), -- name true, -- expandable false, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index 74671cd6..2f23184a 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -63,7 +63,7 @@ local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle local function install_module() if not ipiu.module_installed then - dt.register_lib("image_path_no_ui","selected images path",true,false,{ + dt.register_lib("image_path_no_ui",_("selected images path"),true,false,{ [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",300} }, main_label ) From 5e4ba9da5eb2f157ee7242f96870d687cebd03a5 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 28 Oct 2024 15:49:32 -0400 Subject: [PATCH 094/169] contrib/rename_images - fixed call to sting library for building the string substitution list --- contrib/rename_images.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/rename_images.lua b/contrib/rename_images.lua index a0d37e99..0093c540 100644 --- a/contrib/rename_images.lua +++ b/contrib/rename_images.lua @@ -141,7 +141,7 @@ local function do_rename(images) for i, image in ipairs(images) do if job.valid then job.percent = i / #images - ds.build_substitution_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP) + ds.build_substitute_list(image, i, pattern, USER, PICTURES, HOME, DESKTOP) local new_name = ds.substitute_list(pattern) if new_name == -1 then dt.print(_("unable to do variable substitution, exiting...")) From 1b5cb1ae6ef64173872f556d86ba24f8303769d2 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 28 Oct 2024 20:18:22 -0400 Subject: [PATCH 095/169] lib/dtutils/string - corrected constrant names in assignment --- lib/dtutils/string.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index ff8f73ef..c142b1a1 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -309,9 +309,9 @@ local function _should_be_sanitized(str) local SAFE_POSIX_FILENAME_CHARS = "[^%w/._%-]+" local SAFE_WIN_FILENAME_CHARS = "[^%w\\._%-:]+" - local pattern = SAFE_POSIX_STRING_CHARS + local pattern = SAFE_POSIX_FILENAME_CHARS if dt.configuration.running_os == "windows" then - pattern = SAFE_WIN_STRING_CHARS + pattern = SAFE_WIN_FILENAME_CHARS end log.log_level(dtutils_string.log_level) From e7dbd6e369cb0e402f9f00894edfbbf1c38adcec Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 28 Oct 2024 20:41:02 -0400 Subject: [PATCH 096/169] lib/dtutils/string - fixed constant names to correctly define the patterns as the unsafe filename characters. --- lib/dtutils/string.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index c142b1a1..c8a1ce98 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -306,12 +306,12 @@ end local function _should_be_sanitized(str) local old_log_level = log.log_level() local result = false - local SAFE_POSIX_FILENAME_CHARS = "[^%w/._%-]+" - local SAFE_WIN_FILENAME_CHARS = "[^%w\\._%-:]+" + local UNSAFE_POSIX_FILENAME_CHARS = "[^%w/._%-]+" + local UNSAFE_WIN_FILENAME_CHARS = "[^%w\\._%-:]+" - local pattern = SAFE_POSIX_FILENAME_CHARS + local pattern = UNSAFE_POSIX_FILENAME_CHARS if dt.configuration.running_os == "windows" then - pattern = SAFE_WIN_FILENAME_CHARS + pattern = UNSAFE_WIN_FILENAME_CHARS end log.log_level(dtutils_string.log_level) From c5711880d3528c860e4721c302fd8b6206c0704d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 29 Oct 2024 12:23:25 -0400 Subject: [PATCH 097/169] contrib/autostyle - added check to make sure autostyle has been configured so that it doesn't print out a confusing error message. Fixed preference to use translated script name, (script_data.metadata.name). --- contrib/autostyle.lua | 110 +++++++++++++++++++++++------------------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 093c29a3..a5feda3f 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -53,6 +53,8 @@ end local script_data = {} +local have_not_printed_config_message = true + script_data.metadata = { name = _("auto style"), purpose = _("automatically apply a style based on image EXIF tag"), @@ -101,56 +103,62 @@ end local function autostyle_apply_one_image (image) local pref = darktable.preferences.read("autostyle", "exif_tag", "string") - -- We need the tag, the value and the style_name provided from the configuration string - local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*(%g+)%s*=>%s*(%g+)") - -- check they all exist (correct syntax) - if (not tag) then - darktable.print(string.format(_("EXIF tag not found in %s"), pref)) - return 0 - end - if (not value) then - darktable.print(string.format(_("value to match not found in %s"), pref)) - return 0 - end - if (not style_name) then - darktable.print(string.format(_("style name not found in %s"), pref)) - return 0 - end - if not filelib.check_if_bin_exists("exiftool") then - darktable.print(_("can't find exiftool")) - return 0 - end - - - -- First find the style (we have its name) - local styles = darktable.styles - local style - for _, s in ipairs(styles) do - if s.name == style_name then - style = s - end - end - if (not style) then - darktable.print(string.format(_("style not found for autostyle: %s"), style_name)) - return 0 - end - - -- Apply the style to image, if it is tagged - local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag) - --darktable.print_error("dr_attr:" .. auto_dr_attr) - -- If the lookup fails, stop here - if (not ok) then - darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr)) - return 0 - end - if auto_dr_attr == value then - darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref) - darktable.styles.apply(style,image) - return 1 - else - darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr) - return 0 + if pref and string.len(pref) >= 6 then + -- We need the tag, the value and the style_name provided from the configuration string + local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*(%g+)%s*=>%s*(%g+)") + + -- check they all exist (correct syntax) + if (not tag) then + darktable.print(string.format(_("EXIF tag not found in %s"), pref)) + return 0 + end + if (not value) then + darktable.print(string.format(_("value to match not found in %s"), pref)) + return 0 + end + if (not style_name) then + darktable.print(string.format(_("style name not found in %s"), pref)) + return 0 + end + if not filelib.check_if_bin_exists("exiftool") then + darktable.print(_("can't find exiftool")) + return 0 + end + + + -- First find the style (we have its name) + local styles = darktable.styles + local style + for _, s in ipairs(styles) do + if s.name == style_name then + style = s + end + end + if (not style) then + darktable.print(string.format(_("style not found for autostyle: %s"), style_name)) + return 0 + end + + -- Apply the style to image, if it is tagged + local ok, auto_dr_attr = pcall(exiftool_attribute, image.path .. '/' .. image.filename,tag) + --darktable.print_error("dr_attr:" .. auto_dr_attr) + -- If the lookup fails, stop here + if (not ok) then + darktable.print(string.format(_("couldn't get attribute %s from exiftool's output"), auto_dr_attr)) + return 0 + end + if auto_dr_attr == value then + darktable.print_log("Image " .. image.filename .. ": autostyle automatically applied " .. pref) + darktable.styles.apply(style,image) + return 1 + else + darktable.print_log("Image " .. image.filename .. ": autostyle not applied, exif tag " .. pref .. " not matched: " .. auto_dr_attr) + return 0 + end + elseif have_not_printed_config_message then + have_not_printed_config_message = false + darktable.print(string.format(_("%s is not configured, please configure the preference in Lua options"), script_data.metadata.name)) end end @@ -180,7 +188,9 @@ end darktable.register_event("autostyle", "shortcut", autostyle_apply, _("apply your chosen style from exiftool tags")) -darktable.preferences.register("autostyle", "exif_tag", "string", "Autostyle: EXIF_tag=value=>style", _("apply a style automatically if an EXIF tag matches value, find the tag with exiftool"), "") +darktable.preferences.register("autostyle", "exif_tag", "string", + string.format("%s: EXIF_tag=value=>style", script_data.metadata.name), + _("apply a style automatically if an EXIF tag matches value, find the tag with exiftool"), "") darktable.register_event("autostyle", "post-import-image", autostyle_apply_one_image_event) From b1e6bb8daa90787f1ec31b9750a38a5eeba7c3c0 Mon Sep 17 00:00:00 2001 From: Electro707 Date: Sat, 2 Nov 2024 21:29:53 -0400 Subject: [PATCH 098/169] Added sanitization around path for mkdir and rmdir --- lib/dtutils/file.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 9a8eee90..fc2ee9ef 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -812,7 +812,7 @@ dtutils_file.libdoc.functions["mkdir"] = { function dtutils_file.mkdir(path) if not dtutils_file.check_if_file_exists(path) then local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p" - return dsys.external_command(mkdir_cmd.." "..path) + return dsys.external_command(mkdir_cmd.." "..dtutils_file.sanitize_filename(path)) else return 0 end @@ -837,7 +837,7 @@ dtutils_file.libdoc.functions["rmdir"] = { function dtutils_file.rmdir(path) local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" - return dsys.external_command(rm_cmd.." "..path) + return dsys.external_command(rm_cmd.." "..dtutils_file.sanitize_filename(path)) end dtutils_file.libdoc.functions["create_tmp_file"] = { From 8581af29d2baae17108d6ef5305eebd6f3bbedbc Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 13 Nov 2024 19:05:16 -0500 Subject: [PATCH 099/169] lib/dtutils - added a gen_uuid() function to generate a UUID string for use as a unique identifier. --- lib/dtutils.lua | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index c8331cbd..6721eac9 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -374,7 +374,7 @@ dtutils.libdoc.functions["deprecated"] = { du.deprecated(script_name, removal_string) script_name - name of the script being deprecated - removal_strubg - a string explaining when the script will be removed]], + removal_string - a string explaining when the script will be removed]], Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]], Return_Value = [[]], Limitations = [[]], @@ -391,5 +391,40 @@ function dtutils.deprecated(script_name, removal_string) dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string) end +dtutils.libdoc.functions["gen_uuid"] = { + Name = [[gen_uuid]], + Synopsis = [[generate a UUID string]], + Usage = [[local du = require "lib/dtutils" + + uuid = du.gen_uuid(case) + case - "upper" or "lower" to specify the case of the UUID string]], + Description = [[gen_uuid prints an error message saying the script is gen_uuid and when it will be removed]], + Return_Value = [[uuid - string - a hexidecimal string representing the UUID in the requested case]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[https://gist.github.com/jrus/3197011]], + License = [[]], + Copyright = [[]], +} + +function dtutils.gen_uuid(case) + local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + + -- seed with os.time in seconds and add an extra degree of random for multiple calls in the same second + math.randomseed(os.time(), math.random(0, 65536)) + + local uuid = string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb) + return string.format('%x', v) + end + ) + + if case and case == "upper" then + uuid = string.upper(uuid) + end + + return uuid +end return dtutils From 96b7ba24bc8c84c6ac4cda3da9b14182418148db Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 13 Nov 2024 23:09:14 -0500 Subject: [PATCH 100/169] official/apply_camera_style - make the translatable strings contrib/hif_group_leader translation safe contrib/jpg_group_leader --- contrib/hif_group_leader.lua | 4 ++-- contrib/jpg_group_leader.lua | 4 ++-- official/apply_camera_style.lua | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/contrib/hif_group_leader.lua b/contrib/hif_group_leader.lua index f39d3ff0..412eea07 100644 --- a/contrib/hif_group_leader.lua +++ b/contrib/hif_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", _("collection"))) + _("make hif group leader for collection") ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", _("selection"))) + _("make hif group leader for selection") ) return script_data \ No newline at end of file diff --git a/contrib/jpg_group_leader.lua b/contrib/jpg_group_leader.lua index 8391a670..733071ed 100644 --- a/contrib/jpg_group_leader.lua +++ b/contrib/jpg_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", _("collection"))) + _("make jpg group leader for collection") ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", _("selection"))) + _("make jpg group leader for selection") ) return script_data \ No newline at end of file diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index 8c8aeae4..c75974bb 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -463,18 +463,16 @@ script_data.destroy = destroy -- E V E N T S -- - - - - - - - - - - - - - - - - - - - - - - - -local shortcut_string = _("apply darktable camera styles to %s") - dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(true) - end, string.format(shortcut_string, _("collection")) + end, _("apply darktable camera styles to collection") ) dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(false) - end, string.format(shortcut_string, _("selection")) + end, _("apply darktable camera styles to selection") ) dt.register_event(MODULE, "post-import-image", From 7e3c6471ff0ed65a3b2a8d516eda0879747d1325 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 1 Dec 2024 12:27:16 -0500 Subject: [PATCH 101/169] official/apply_camera_sytle - ensure style is only applied to raw images. Fixed comment. --- official/apply_camera_style.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index c75974bb..aac49d16 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -64,7 +64,7 @@ local CS = dt.configuration.running_os == "windows" and "&" or ";" -- A P I C H E C K -- - - - - - - - - - - - - - - - - - - - - - - - -du.check_min_api_version("9.4.0", MODULE) -- styles use filmic V7 which appeared in darktable 4.4 +du.check_min_api_version("9.4.0", MODULE) -- camera styles added to darktable 5.0 -- - - - - - - - - - - - - - - - - - - - - - - - - - @@ -477,7 +477,9 @@ dt.register_event(MODULE, "shortcut", dt.register_event(MODULE, "post-import-image", function(event, image) - table.insert(acs.imported_images, image) + if image.is_raw then + table.insert(acs.imported_images, image) + end end ) From e4e194b0ec250991348d472f7531e724cb429250 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 09:01:48 +0100 Subject: [PATCH 102/169] Fixed $(FILE.NAME) variable substitution According to comments and https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/ it should be a basename. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index c8a1ce98..a710201b 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -748,7 +748,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, local replacements = {image.film.path, -- ROLL.NAME image.path, -- FILE.FOLDER - image.filename, -- FILE.NAME + dtutils_string.get_basename(image.filename),-- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION image.id, -- ID image.duplicate_index, -- VERSION From 3fa913d68ad75ea3ce49d706c670a0bd557a4872 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 09:56:45 +0100 Subject: [PATCH 103/169] Fixed $ROLL.NAME substitution Accd to https://github.com/darktable-org/darktable/blob/57d3ad4bd30372b1f7ae7368f6887d3292269b2a/src/common/variables.c#L660 it should be a basename of the file path. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index a710201b..2be4ef6b 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -746,7 +746,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, local version_multi = #image:get_group_members() > 1 and image.version or "" - local replacements = {image.film.path, -- ROLL.NAME + local replacements = {dtutils_string.get_basename(image.film.path),-- ROLL.NAME image.path, -- FILE.FOLDER dtutils_string.get_basename(image.filename),-- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION From da275c2a662466ded7170c3aa6c4fb98bcc81673 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 10:11:23 +0100 Subject: [PATCH 104/169] Fixed $(VERSION.IF_MULTI) Variable substitution actually errored out when duplicates were present. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 2be4ef6b..7648f6f6 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -744,7 +744,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$") end - local version_multi = #image:get_group_members() > 1 and image.version or "" + local version_multi = #image:get_group_members() > 1 and image.duplicate_index or "" local replacements = {dtutils_string.get_basename(image.film.path),-- ROLL.NAME image.path, -- FILE.FOLDER From 9bfda93b510d16fb2b0d989626312f4449a4e367 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 13 Nov 2024 23:09:14 -0500 Subject: [PATCH 105/169] official/apply_camera_style - make the translatable strings contrib/hif_group_leader translation safe contrib/jpg_group_leader --- contrib/hif_group_leader.lua | 4 ++-- contrib/jpg_group_leader.lua | 4 ++-- official/apply_camera_style.lua | 6 ++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/contrib/hif_group_leader.lua b/contrib/hif_group_leader.lua index f39d3ff0..412eea07 100644 --- a/contrib/hif_group_leader.lua +++ b/contrib/hif_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", _("collection"))) + _("make hif group leader for collection") ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_hif_group_leader(images) end, - string.format(_("make hif group leader for %s", _("selection"))) + _("make hif group leader for selection") ) return script_data \ No newline at end of file diff --git a/contrib/jpg_group_leader.lua b/contrib/jpg_group_leader.lua index 8391a670..733071ed 100644 --- a/contrib/jpg_group_leader.lua +++ b/contrib/jpg_group_leader.lua @@ -186,7 +186,7 @@ dt.register_event(MODULE .. "_collect", "shortcut", local images = dt.collection make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", _("collection"))) + _("make jpg group leader for collection") ) dt.register_event(MODULE .. "_select", "shortcut", @@ -194,7 +194,7 @@ dt.register_event(MODULE .. "_select", "shortcut", local images = dt.gui.selection() make_existing_jpg_group_leader(images) end, - string.format(_("make jpg group leader for %s", _("selection"))) + _("make jpg group leader for selection") ) return script_data \ No newline at end of file diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index 8c8aeae4..c75974bb 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -463,18 +463,16 @@ script_data.destroy = destroy -- E V E N T S -- - - - - - - - - - - - - - - - - - - - - - - - -local shortcut_string = _("apply darktable camera styles to %s") - dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(true) - end, string.format(shortcut_string, _("collection")) + end, _("apply darktable camera styles to collection") ) dt.register_event(MODULE, "shortcut", function(event, shortcut) apply_camera_style(false) - end, string.format(shortcut_string, _("selection")) + end, _("apply darktable camera styles to selection") ) dt.register_event(MODULE, "post-import-image", From b5c93e55d16e52e3d3bdafe603dac85cdb3ac895 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 13 Nov 2024 19:05:16 -0500 Subject: [PATCH 106/169] lib/dtutils - added a gen_uuid() function to generate a UUID string for use as a unique identifier. --- lib/dtutils.lua | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index c8331cbd..6721eac9 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -374,7 +374,7 @@ dtutils.libdoc.functions["deprecated"] = { du.deprecated(script_name, removal_string) script_name - name of the script being deprecated - removal_strubg - a string explaining when the script will be removed]], + removal_string - a string explaining when the script will be removed]], Description = [[deprecated prints an error message saying the script is deprecated and when it will be removed]], Return_Value = [[]], Limitations = [[]], @@ -391,5 +391,40 @@ function dtutils.deprecated(script_name, removal_string) dt.print_error("WARNING: " .. script_name .. " is deprecated and will be removed in " .. removal_string) end +dtutils.libdoc.functions["gen_uuid"] = { + Name = [[gen_uuid]], + Synopsis = [[generate a UUID string]], + Usage = [[local du = require "lib/dtutils" + + uuid = du.gen_uuid(case) + case - "upper" or "lower" to specify the case of the UUID string]], + Description = [[gen_uuid prints an error message saying the script is gen_uuid and when it will be removed]], + Return_Value = [[uuid - string - a hexidecimal string representing the UUID in the requested case]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[https://gist.github.com/jrus/3197011]], + License = [[]], + Copyright = [[]], +} + +function dtutils.gen_uuid(case) + local template = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx' + + -- seed with os.time in seconds and add an extra degree of random for multiple calls in the same second + math.randomseed(os.time(), math.random(0, 65536)) + + local uuid = string.gsub(template, '[xy]', function (c) + local v = (c == 'x') and math.random(0, 0xf) or math.random(8, 0xb) + return string.format('%x', v) + end + ) + + if case and case == "upper" then + uuid = string.upper(uuid) + end + + return uuid +end return dtutils From ed9f2bb0d055754c12058098af4d53336453f7cc Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 1 Dec 2024 12:27:16 -0500 Subject: [PATCH 107/169] official/apply_camera_sytle - ensure style is only applied to raw images. Fixed comment. --- official/apply_camera_style.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index c75974bb..aac49d16 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -64,7 +64,7 @@ local CS = dt.configuration.running_os == "windows" and "&" or ";" -- A P I C H E C K -- - - - - - - - - - - - - - - - - - - - - - - - -du.check_min_api_version("9.4.0", MODULE) -- styles use filmic V7 which appeared in darktable 4.4 +du.check_min_api_version("9.4.0", MODULE) -- camera styles added to darktable 5.0 -- - - - - - - - - - - - - - - - - - - - - - - - - - @@ -477,7 +477,9 @@ dt.register_event(MODULE, "shortcut", dt.register_event(MODULE, "post-import-image", function(event, image) - table.insert(acs.imported_images, image) + if image.is_raw then + table.insert(acs.imported_images, image) + end end ) From 9bceb79a150c41f4aaa3e3546722ed03fc4ca57d Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 09:01:48 +0100 Subject: [PATCH 108/169] Fixed $(FILE.NAME) variable substitution According to comments and https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/ it should be a basename. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index c8a1ce98..a710201b 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -748,7 +748,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, local replacements = {image.film.path, -- ROLL.NAME image.path, -- FILE.FOLDER - image.filename, -- FILE.NAME + dtutils_string.get_basename(image.filename),-- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION image.id, -- ID image.duplicate_index, -- VERSION From d27df0c0ed31a26bd9b68eca5831e49751fce12a Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 09:56:45 +0100 Subject: [PATCH 109/169] Fixed $ROLL.NAME substitution Accd to https://github.com/darktable-org/darktable/blob/57d3ad4bd30372b1f7ae7368f6887d3292269b2a/src/common/variables.c#L660 it should be a basename of the file path. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index a710201b..2be4ef6b 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -746,7 +746,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, local version_multi = #image:get_group_members() > 1 and image.version or "" - local replacements = {image.film.path, -- ROLL.NAME + local replacements = {dtutils_string.get_basename(image.film.path),-- ROLL.NAME image.path, -- FILE.FOLDER dtutils_string.get_basename(image.filename),-- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION From 87ca8bde27219034a6e250d19cea14f0be6f1ca6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 10:11:23 +0100 Subject: [PATCH 110/169] Fixed $(VERSION.IF_MULTI) Variable substitution actually errored out when duplicates were present. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 2be4ef6b..7648f6f6 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -744,7 +744,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$") end - local version_multi = #image:get_group_members() > 1 and image.version or "" + local version_multi = #image:get_group_members() > 1 and image.duplicate_index or "" local replacements = {dtutils_string.get_basename(image.film.path),-- ROLL.NAME image.path, -- FILE.FOLDER From 3b520d62918dc21129bd0672ea0b90b1edbfd9aa Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 23 Aug 2024 17:53:03 +0200 Subject: [PATCH 111/169] 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 00000000..f3b4d193 --- /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 From b963eaa38ccabf69dc47c9b6de4fb973c115cd7f Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 26 Aug 2024 16:28:12 +0200 Subject: [PATCH 112/169] Added encoding variant as an explicit UI setting (though only API-4 is supported now). --- contrib/ultrahdr.lua | 102 +++++++++++++++++++++++++++++++------------ 1 file changed, 74 insertions(+), 28 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index f3b4d193..381b013b 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -79,7 +79,23 @@ local function image_path(image) return image.path .. "/" .. image.filename end -local function merge_ultrahdr(base, gainmap, ultrahdr_app, exiftool, metadata, output) +local ENCODING_VARIANT_API_4 = 1 + +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 +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 " .. @@ -101,8 +117,6 @@ local function assert_settings_correct() 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) @@ -120,40 +134,42 @@ local function assert_settings_correct() 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 + return ultrahdr_app, exiftool, 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() + local ultrahdr_app, exiftool, 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 + 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 - 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) + 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) - 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 @@ -161,9 +177,14 @@ local function destroy() dt.destroy_storage(namespace) end + + local function is_supported(storage, format) - if format.extension == "jpg" then - return true + 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 end @@ -254,10 +275,35 @@ local function output_directory_widget() 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", - metadata_file_widget(), output_directory_widget(), + encoding_variant_widget(), df.executable_path_widget({"ultrahdr_app", "exiftool"}) } From 1986f4e14843453274f571b4f3a534beaf04b8d5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 12:49:04 +0200 Subject: [PATCH 113/169] Major overhaul of the plugin. Added SDR + HDR (JPEG-XL) support. --- contrib/ultrahdr.lua | 627 +++++++++++++++++++++++++++++-------------- 1 file changed, 426 insertions(+), 201 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 381b013b..d2540a3a 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -17,8 +17,7 @@ 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. @@ -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 - -This plugin will add a new storage option. +* set binary tool paths -]] -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 -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, output = assert_settings_correct() - if not ultrahdr_app or not exiftool then - return - end - 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 + 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 - 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) end - log.log_level(saved_log_level) -end -local function destroy() - dt.destroy_storage(namespace) + return settings 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" +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 - 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. + -- 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 has_gainmap_tag = false + local is_extra = false tags = dt.tags.get_tags(v) for ignore, tag in pairs(tags) do - if tag.name == "gainmap" then - has_gainmap_tag = true + if tag.name == extra_image_content_type then + is_extra = true end end - local key = image_path(v) - if extra_data[key] == nil then - extra_data[key] = {} + if extra_image_extension and df.get_filetype(v.filename) == extra_image_extension then + is_extra = true end - if extra_data[key]["base"] or has_gainmap_tag then - extra_data[key]["gainmap"] = v + -- 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 - extra_data[key]["base"] = v + stacks[key]["sdr"] = 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 + -- 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 - log.log_level(saved_log_level) - return nil + return stacks, count 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 function stop_job(job) + job.valid = false +end - local box = dt.new_widget("box") { - orientation = "vertical", - table.unpack(box_widgets) - } - return box +local function execute_cmd(cmd) + log.msg(log.debug, cmd) + return dtsys.external_command(cmd) 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 = "" +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 - 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) + + 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 box = dt.new_widget("box") { - orientation = "vertical", - table.unpack(box_widgets) - } - return box + 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 encoding_variant_widget() - local metadata_widget = metadata_file_widget() +local function main() + local saved_log_level = log.log_level() + log.log_level(log.info) + + save_preferences() + 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, - } + log.msg(log.info, string.format("using encoding variant %d", encoding_variant)) - combobox.changed_callback(combobox) - return dt.new_widget("box") { - orientation = "vertical", - combobox, - metadata_widget - } + 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 -local ultrahdr_widget = dt.new_widget("box") { +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", - output_directory_widget(), - encoding_variant_widget(), - df.executable_path_widget({"ultrahdr_app", "exiftool"}) + GUI.optionwidgets.metadata_path_label, + GUI.optionwidgets.metadata_path_widget } --- Register +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 -dt.register_storage(namespace, _("UltraHDR JPEG"), nil, create_hdr, is_supported, initialize, ultrahdr_widget) + 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.gui.libs[namespace].visible = false +end + +local function restart() + dt.gui.libs[namespace].visible = true +end + +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) + flags.event_registered = true -- keep track of whether we have an event handler installed + end +end script_data.destroy = destroy +script_data.restart = restart +script_data.destroy_method = "hide" +script_data.show = restart return script_data From 865d29fb4084af6e1eef15d5b5f8fe33a27c3c15 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 13:13:47 +0200 Subject: [PATCH 114/169] Changed the UltraHDR JPEG quality to 95. --- contrib/ultrahdr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index d2540a3a..0d0e34b9 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -296,7 +296,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total " -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 " .. + df.sanitize_filename(extra) .. " -a 0 -b 3 -c 1 -C 1 -t 2 -M 1 -s 1 -q 95 -Q 95 -D 1 " .. " -w " .. tostring(images["sdr"].final_width) .. " -h " .. tostring(images["sdr"].final_height) .. " -z " .. df.sanitize_filename(uhdr)) update_job_progress() From 28f87566e1522432856911bee9862136c095ed7c Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 15:11:52 +0200 Subject: [PATCH 115/169] Added better messaging. --- contrib/ultrahdr.lua | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 0d0e34b9..2efeb2c0 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -237,9 +237,6 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total 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 @@ -324,6 +321,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end dt.tags.attach(tagnr, img) end + + local msg = string.format(_("Generated %s."), df.get_filename(output_file)) + log.msg(log.info, msg) + dt.print(msg) update_job_progress() end @@ -369,6 +370,9 @@ local function main() job.valid = false end + local msg = string.format(_("Generated %d UltraHDR image(s)."), count) + log.msg(log.info, msg) + dt.print(msg) log.log_level(saved_log_level) end From 496f8e704dc22a9d9f01c1aa6090582152a72a65 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 17:09:54 +0200 Subject: [PATCH 116/169] Fixed doc. --- contrib/ultrahdr.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 2efeb2c0..628600ad 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -20,14 +20,10 @@ ]] --[[ ULTRAHDR -Add a new storage option to generate UltraHDR JPG images. +Generate UltraHDR JPG images from various combinations of source files (SDR, HDR, gainmap). 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 From 1a3a7f17ff53a0e33b3958134e31fb093965c2bb Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 17:54:15 +0200 Subject: [PATCH 117/169] Added SDR + auto gainmap generation encoding variant. --- contrib/ultrahdr.lua | 82 +++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 628600ad..ee92c994 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -92,6 +92,7 @@ script_data.metadata = { local PS = dt.configuration.running_os == "windows" and "\\" or "/" local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 local ENCODING_VARIANT_SDR_AND_HDR = 2 +local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 local function save_preferences() dt.preferences.write(namespace, "encoding_variant", "integer", GUI.optionwidgets.encoding_variant_combo.selected) @@ -128,7 +129,7 @@ local function assert_settings_correct(encoding_variant) 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, + copy_exif = GUI.optionwidgets.copy_exif.value, metadata = GUI.optionwidgets.metadata_path_widget.value, tmpdir = dt.configuration.tmp_dir } @@ -164,6 +165,8 @@ local function get_stacks(images, encoding_variant) elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then extra_image_extension = "jxl" extra_image_content_type = "hdr" + elseif encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then + extra_image_content_type = nil end local tags = nil @@ -173,7 +176,7 @@ local function get_stacks(images, encoding_variant) 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 + if extra_image_content_type and tag.name == extra_image_content_type then is_extra = true end end @@ -185,23 +188,27 @@ local function get_stacks(images, encoding_variant) if stacks[key] == nil then stacks[key] = {} end - if stacks[key]["sdr"] or is_extra then + if extra_image_content_type and (stacks[key]["sdr"] or is_extra) then stacks[key][extra_image_content_type] = v - else + elseif not is_extra then 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 + if extra_image_content_type then + 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 + end + end + if stacks[k] then count = count + 1 end end @@ -231,7 +238,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total job.percent = (total_substeps * step + substep) / (total_steps * total_substeps) end - if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then + if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then total_substeps = 6 -- Export both SDR and gainmap to JPEGs local exporter = dt.new_format("jpeg") @@ -239,20 +246,27 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total 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) - + local gainmap + if encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then -- SDR is also a gainmap + gainmap = sdr + else + gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg") + exporter:write_image(images["gainmap"], gainmap) + end + dd.dprint(images["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") + if sdr ~= gainmap then + execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. " -overwrite_original") + end 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 " .. + execute_cmd(settings.bin.ultrahdr_app .. " -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() @@ -265,7 +279,9 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total -- Cleanup os.remove(sdr) os.remove(sdr .. ".noexif") - os.remove(gainmap) + if sdr ~= gainmap then + os.remove(gainmap) + end update_job_progress() elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then total_substeps = 5 @@ -289,9 +305,9 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total " -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 95 -Q 95 -D 1 " .. - " -w " .. tostring(images["sdr"].final_width) .. " -h " .. tostring(images["sdr"].final_height) .. - " -z " .. df.sanitize_filename(uhdr)) + df.sanitize_filename(extra) .. " -a 0 -b 3 -c 1 -C 1 -t 2 -M 1 -s 1 -q 95 -Q 95 -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 " .. @@ -320,7 +336,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total local msg = string.format(_("Generated %s."), df.get_filename(output_file)) log.msg(log.info, msg) - dt.print(msg) + dt.print(msg) update_job_progress() end @@ -340,14 +356,7 @@ local function main() 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) + local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant) dt.print(string.format(_("Detected %d image stack(s)"), stack_count)) if stack_count == 0 then log.log_level(saved_log_level) @@ -433,13 +442,15 @@ GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") { %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) +%s: SDR image only. Gainmaps will be just copies of SDR images. 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")), +By default, the first image in a pair is treated as SDR, and the next one store extra gainmap/HDR data. +You can force the image into a specific slot by attaching "hdr" / "gainmap" tags. +]]), _("SDR + monochrome gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), selected = 0, changed_callback = function(self) if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP then @@ -448,8 +459,9 @@ UltraHDR image will be created for each pair of images that: 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, + _("SDR + monochrome gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP + _("SDR + JPEG-XL HDR"), -- ENCODING_VARIANT_SDR_AND_HDR + _("SDR (auto gainmap)") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP } GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { From 794b12305c4173b0dfa796975c6a1cd5339335ae Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 19 Sep 2024 18:07:25 +0200 Subject: [PATCH 118/169] Fixed tooltip language. --- contrib/ultrahdr.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index ee92c994..3b3f5574 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -440,15 +440,15 @@ 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) -%s: SDR image only. Gainmaps will be just copies of SDR images. +- %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) +- %s: SDR images only. Gainmaps will be copies of SDR images (the simplest option). 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 -By default, the first image in a pair is treated as SDR, and the next one store extra gainmap/HDR data. +By default, the first image in a pair is treated as SDR, and the second one stores extra gainmap/HDR data. You can force the image into a specific slot by attaching "hdr" / "gainmap" tags. ]]), _("SDR + monochrome gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), selected = 0, From fe6d262d9b2e078394c5e11972532aa133799bd5 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 20 Sep 2024 09:29:49 +0200 Subject: [PATCH 119/169] Display all error messages when checking configuration. Changed config defaults to require less user input (use_original_directory=TRUE, import_to_darktable=TRUE). Account for nil values when checking for file existence. --- contrib/ultrahdr.lua | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 3b3f5574..4e55cfc3 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -100,7 +100,9 @@ local function save_preferences() 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) + if GUI.optionwidgets.output_directory_widget.value then + dt.preferences.write(namespace, "output_directory", "string", GUI.optionwidgets.output_directory_widget.value) + end 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 @@ -108,11 +110,11 @@ end 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") + GUI.optionwidgets.metadata_path_widget.value = dt.preferences.read(namespace, "metadata_path", "string") or "" + GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool") or true + GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string") or "" + GUI.optionwidgets.import_to_darktable.value = dt.preferences.read(namespace, "import_to_darktable", "bool") or true + GUI.optionwidgets.copy_exif.value = dt.preferences.read(namespace, "copy_exif", "bool") or false end local function get_encoding_variant() @@ -120,6 +122,7 @@ local function get_encoding_variant() end local function assert_settings_correct(encoding_variant) + local errors = {} local settings = { bin = { ultrahdr_app = df.check_if_bin_exists("ultrahdr_app"), @@ -134,27 +137,26 @@ local function assert_settings_correct(encoding_variant) tmpdir = dt.configuration.tmp_dir } - 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 + if not settings.use_original_dir and (not settings.output or not df.check_if_file_exists(settings.output)) then + table.insert(errors, string.format(_("output directory (%s) not found"), settings.output)) end 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 + table.insert(errors, string.format(_("%s binary not found"), k)) end end 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 + if not settings.metadata or not df.check_if_file_exists(settings.metadata) then + table.insert(errors, _("metadata.cfg file not found (select one from libultrahdr/examples directory)")) end end - return settings + if #errors > 0 then + return nil, errors + end + return settings, nil end local function get_stacks(images, encoding_variant) @@ -253,7 +255,6 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg") exporter:write_image(images["gainmap"], gainmap) end - dd.dprint(images["gainmap"]) log.msg(log.debug, string.format(_("Exported files: %s, %s"), sdr, gainmap)) update_job_progress() -- Strip EXIFs @@ -349,9 +350,9 @@ local function main() local encoding_variant = get_encoding_variant() log.msg(log.info, string.format("using encoding variant %d", encoding_variant)) - local settings = assert_settings_correct(encoding_variant) + local settings, errors = assert_settings_correct(encoding_variant) if not settings then - dt.print(_("Export settings are incorrect, exiting...")) + dt.print(string.format(_("Export settings are incorrect, exiting:\n\n%s"), table.concat(errors, "\n"))) log.log_level(saved_log_level) return end @@ -453,6 +454,7 @@ You can force the image into a specific slot by attaching "hdr" / "gainmap" tags ]]), _("SDR + monochrome gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), selected = 0, changed_callback = function(self) + GUI.run.sensitive = self.selected and self.selected > 0 if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP then GUI.optionwidgets.metadata_path_box.visible = true else From 2e95c4837bc06fe27b04f63dd4d31ee20ca4caef Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 20 Sep 2024 11:16:28 +0200 Subject: [PATCH 120/169] Added option to generate metadata file. Fixed pref load / saving code defaults. Metadata file is required in ENCODING_VARIANT_SDR_AUTO_GAINMAP mode. --- contrib/ultrahdr.lua | 78 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 62 insertions(+), 16 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 4e55cfc3..5f0ca6fb 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -67,9 +67,11 @@ local GUI = { output_directory_widget = {}, copy_exif = {}, import_to_darktable = {}, + metadata_file_generate = {}, metadata_path_label = {}, metadata_path_widget = {}, metadata_path_box = {}, + metadata_path_box2 = {}, edit_executables_button = {}, executable_path_widget = {} }, @@ -94,6 +96,26 @@ local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 local ENCODING_VARIANT_SDR_AND_HDR = 2 local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 +local function generate_metadata_file() + local default_metadata_file = [[--maxContentBoost 6.0 +--minContentBoost 1.0 +--gamma 1.0 +--offsetSdr 0.0 +--offsetHdr 0.0 +--hdrCapacityMin 1.0 +--hdrCapacityMax 6.0]] + + local filename = dt.configuration.config_dir .. PS .. "ultrahdr_metadata.cfg" + local f, err = io.open(filename, "w+") + if not f then + dt.print(err) + return nil + end + f:write(default_metadata_file) + f:close() + return filename +end + 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 @@ -108,13 +130,22 @@ local function save_preferences() end 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") or "" - GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool") or true - GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string") or "" - GUI.optionwidgets.import_to_darktable.value = dt.preferences.read(namespace, "import_to_darktable", "bool") or true - GUI.optionwidgets.copy_exif.value = dt.preferences.read(namespace, "copy_exif", "bool") or false + -- Since the option #1 is the default, and empty numeric prefs are 0, we can use math.max + GUI.optionwidgets.encoding_variant_combo.selected = math.max( + dt.preferences.read(namespace, "encoding_variant", "integer"), ENCODING_VARIANT_SDR_AND_GAINMAP) + GUI.optionwidgets.metadata_path_widget.value = dt.preferences.read(namespace, "metadata_path", "string") + if not GUI.optionwidgets.metadata_path_widget.value then -- file widgets reset "" value to nil + GUI.optionwidgets.metadata_path_widget.value = generate_metadata_file() + -- Avoid regenerating the file if the user only enables the plugin, but never clicks "Generate" + dt.preferences.write(namespace, "metadata_path", "string", GUI.optionwidgets.metadata_path_widget.value) + end + GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string") + GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool") + if not GUI.optionwidgets.output_directory_widget.value then + GUI.optionwidgets.use_original_directory.value = true + end + 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() @@ -147,9 +178,9 @@ local function assert_settings_correct(encoding_variant) end end - if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then + if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then if not settings.metadata or not df.check_if_file_exists(settings.metadata) then - table.insert(errors, _("metadata.cfg file not found (select one from libultrahdr/examples directory)")) + table.insert(errors, _("metadata.cfg file not found (use 'Generate' button)")) end end @@ -423,25 +454,40 @@ GUI.optionwidgets.output_settings_box = dt.new_widget("box") { } GUI.optionwidgets.metadata_path_label = dt.new_widget("label") { - label = _("ultrahdr_app metadata.cfg file") + label = _("Gainmap metadata file") } GUI.optionwidgets.metadata_path_widget = dt.new_widget("file_chooser_button") { - title = "select libultrahdr metadata path", + title = _("Select libultrahdr metadata.cfg file"), is_directory = false } +GUI.optionwidgets.metadata_file_generate = dt.new_widget("button") { + label = _("Generate"), + tooltip = _("Generate new metadata file with default values"), + clicked_callback = function() + local metadata_file = generate_metadata_file() + GUI.optionwidgets.metadata_path_widget.value = metadata_file + end +} + +GUI.optionwidgets.metadata_path_box2 = dt.new_widget("box") { + orientation = "horizontal", + GUI.optionwidgets.metadata_path_widget, + GUI.optionwidgets.metadata_file_generate +} + GUI.optionwidgets.metadata_path_box = dt.new_widget("box") { orientation = "vertical", GUI.optionwidgets.metadata_path_label, - GUI.optionwidgets.metadata_path_widget + GUI.optionwidgets.metadata_path_box2 } 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 gain map image - %s: SDR image paired with a JPEG-XL HDR image (10-bit, 'PQ P3 RGB' profile recommended) - %s: SDR images only. Gainmaps will be copies of SDR images (the simplest option). @@ -451,17 +497,17 @@ UltraHDR image will be created for each pair of images that: By default, the first image in a pair is treated as SDR, and the second one stores extra gainmap/HDR data. You can force the image into a specific slot by attaching "hdr" / "gainmap" tags. -]]), _("SDR + monochrome gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), +]]), _("SDR + gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 - if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP then + if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP or self.selected == ENCODING_VARIANT_SDR_AUTO_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 + gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP _("SDR + JPEG-XL HDR"), -- ENCODING_VARIANT_SDR_AND_HDR _("SDR (auto gainmap)") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP } From 7e8340c526a264a98efcd2917a9e5e1663fcc04a Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Fri, 20 Sep 2024 11:38:24 +0200 Subject: [PATCH 121/169] Don't overwrite existing stack slots (that way the first matching image in a stack will be used, vs the last one). --- contrib/ultrahdr.lua | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 5f0ca6fb..cba1cc90 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -216,15 +216,21 @@ local function get_stacks(images, encoding_variant) 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 + -- 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 extra_image_content_type and (stacks[key]["sdr"] or is_extra) then - stacks[key][extra_image_content_type] = v + if extra_image_content_type and (is_extra or stacks[key]["sdr"]) then + -- Don't overwrite existing entries + if not stacks[key][extra_image_content_type] then + stacks[key][extra_image_content_type] = v + end elseif not is_extra then - stacks[key]["sdr"] = v + -- Don't overwrite existing entries + if not stacks[key]["sdr"] then + stacks[key]["sdr"] = v + end end end -- remove invalid stacks From 99c3548950533de135bbcd2cb5e67585341b0030 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 21 Sep 2024 07:54:45 +0200 Subject: [PATCH 122/169] Copy only EXIF tags to UltraHDR. --- contrib/ultrahdr.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index cba1cc90..326b0988 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -310,7 +310,9 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total 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 " .. + -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). + -- This might hapen e.g. when the source files are Adobe gainmap HDRs. + execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. df.sanitize_filename(uhdr) .. " -overwrite_original -preserve") end update_job_progress() @@ -348,7 +350,9 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total df.sanitize_filename(uhdr)) update_job_progress() if settings.copy_exif then - execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -all>all " .. + -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). + -- This might hapen e.g. when the source files are Adobe gainmap HDRs. + execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. df.sanitize_filename(uhdr) .. " -overwrite_original -preserve") end -- Cleanup From 39f63cc8c59441fe2557be469bbf9090c642bcd6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 21 Sep 2024 09:44:56 +0200 Subject: [PATCH 123/169] Fallback to reading image's width/height, as sometimes final_width/height is 0. --- contrib/ultrahdr.lua | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 326b0988..2ccf2ebc 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -190,7 +190,14 @@ local function assert_settings_correct(encoding_variant) return settings, nil end -local function get_stacks(images, encoding_variant) +local function get_dimensions(image) + if image.final_width > 0 then + return image.final_width, image.final_height + end + return image.width, image.height +end + +local function get_stacks(images, encoding_variant, selection_type) local stacks = {} local extra_image_content_type, extra_image_extension if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then @@ -239,12 +246,15 @@ local function get_stacks(images, encoding_variant) if extra_image_content_type then 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 + local sdr_w, sdr_h = get_dimensions(v["sdr"]) + local extra_w, extra_h = get_dimensions(v[extra_image_content_type]) + if (sdr_w ~= extra_w) or (sdr_h ~= extra_h) then + stacks[k] = nil + end end end if stacks[k] then @@ -344,9 +354,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total df.sanitize_filename(images["hdr"].path .. PS .. images["hdr"].filename) .. " -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(extra)) update_job_progress() + local sdr_w, sdr_h = get_dimensions(images["sdr"]) 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 95 -Q 95 -D 1 " .. " -w " .. - tostring(images["sdr"].final_width) .. " -h " .. tostring(images["sdr"].final_height) .. " -z " .. + tostring(sdr_w) .. " -h " .. tostring(sdr_h) .. " -z " .. df.sanitize_filename(uhdr)) update_job_progress() if settings.copy_exif then From 08726427aab2afc80d34743d98b06de48eb8371b Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 21 Sep 2024 09:47:08 +0200 Subject: [PATCH 124/169] Don't re-export JPEG files if their images are not altered. --- contrib/ultrahdr.lua | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 2ccf2ebc..c6cf9dc1 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -287,20 +287,28 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total job.percent = (total_substeps * step + substep) / (total_steps * total_substeps) end + function copy_or_export_jpg(src, dest) + if df.get_filetype(src.filename) == "jpg" and not src.is_altered then + df.file_copy(src.path .. PS .. src.filename, dest) + else + local exporter = dt.new_format("jpeg") + exporter.quality = 95 + exporter:write_image(src, dest) + end + end + if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then total_substeps = 6 - -- Export both SDR and gainmap to JPEGs - local exporter = dt.new_format("jpeg") - exporter.quality = 95 + -- Export/copy both SDR and gainmap to JPEGs local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".jpg") - exporter:write_image(images["sdr"], sdr) + copy_or_export_jpg(images["sdr"], sdr) local gainmap if encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then -- SDR is also a gainmap gainmap = sdr else gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg") - exporter:write_image(images["gainmap"], gainmap) + copy_or_export_jpg(images["gainmap"], gainmap) end log.msg(log.debug, string.format(_("Exported files: %s, %s"), sdr, gainmap)) update_job_progress() From 44e442e7eb508f90c2a31c45cd5657fb7b487fa0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 21 Sep 2024 09:49:06 +0200 Subject: [PATCH 125/169] Support grouping images into stacks by different criteria (or forcing the selection to be interpreted as a single UltraHDR stack). --- contrib/ultrahdr.lua | 68 ++++++++++++++++++++++++++++++-------------- 1 file changed, 46 insertions(+), 22 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index c6cf9dc1..c4faddef 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -60,6 +60,7 @@ local GUI = { optionwidgets = { settings_label = {}, encoding_variant_combo = {}, + selection_type_combo = {}, encoding_settings_box = {}, output_settings_label = {}, output_settings_box = {}, @@ -96,6 +97,9 @@ local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 local ENCODING_VARIANT_SDR_AND_HDR = 2 local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 +local SELECTION_TYPE_ONE_STACK = 1 +local SELECTION_TYPE_GROUP_BY_FNAME = 2 + local function generate_metadata_file() local default_metadata_file = [[--maxContentBoost 6.0 --minContentBoost 1.0 @@ -118,6 +122,7 @@ end local function save_preferences() dt.preferences.write(namespace, "encoding_variant", "integer", GUI.optionwidgets.encoding_variant_combo.selected) + dt.preferences.write(namespace, "selection_type", "integer", GUI.optionwidgets.selection_type_combo.selected) if GUI.optionwidgets.metadata_path_widget.value then dt.preferences.write(namespace, "metadata_path", "string", GUI.optionwidgets.metadata_path_widget.value) end @@ -133,6 +138,9 @@ local function load_preferences() -- Since the option #1 is the default, and empty numeric prefs are 0, we can use math.max GUI.optionwidgets.encoding_variant_combo.selected = math.max( dt.preferences.read(namespace, "encoding_variant", "integer"), ENCODING_VARIANT_SDR_AND_GAINMAP) + GUI.optionwidgets.selection_type_combo.selected = math.max( + dt.preferences.read(namespace, "selection_type", "integer"), SELECTION_TYPE_ONE_STACK) + GUI.optionwidgets.metadata_path_widget.value = dt.preferences.read(namespace, "metadata_path", "string") if not GUI.optionwidgets.metadata_path_widget.value then -- file widgets reset "" value to nil GUI.optionwidgets.metadata_path_widget.value = generate_metadata_file() @@ -148,10 +156,6 @@ local function load_preferences() GUI.optionwidgets.copy_exif.value = dt.preferences.read(namespace, "copy_exif", "bool") end -local function get_encoding_variant() - return GUI.optionwidgets.encoding_variant_combo.selected -end - local function assert_settings_correct(encoding_variant) local errors = {} local settings = { @@ -210,8 +214,8 @@ local function get_stacks(images, encoding_variant, selection_type) 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 + -- Group images into (sdr [,extra]) stacks + -- Assume that the first encountered image from each stack 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) @@ -224,7 +228,12 @@ local function get_stacks(images, encoding_variant, selection_type) 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) + local key + if selection_type == SELECTION_TYPE_GROUP_BY_FNAME then + key = df.chop_filetype(v.path .. PS .. v.filename) + elseif selection_type == SELECTION_TYPE_ONE_STACK then + key = "the_one_and_only" + end if stacks[key] == nil then stacks[key] = {} end @@ -407,8 +416,9 @@ local function main() save_preferences() - local encoding_variant = get_encoding_variant() - log.msg(log.info, string.format("using encoding variant %d", encoding_variant)) + local selection_type = GUI.optionwidgets.selection_type_combo.selected + local encoding_variant = GUI.optionwidgets.encoding_variant_combo.selected + log.msg(log.info, string.format("using selection type %d, encoding variant %d", selection_type, encoding_variant)) local settings, errors = assert_settings_correct(encoding_variant) if not settings then @@ -417,7 +427,7 @@ local function main() return end - local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant) + local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant, selection_type) dt.print(string.format(_("Detected %d image stack(s)"), stack_count)) if stack_count == 0 then log.log_level(saved_log_level) @@ -513,20 +523,17 @@ GUI.optionwidgets.metadata_path_box = dt.new_widget("box") { } GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") { - label = _("Source images"), - tooltip = string.format(_([[Select types of images in the selection. + label = _("Each stack contains"), + tooltip = string.format(_([[Select the types of images in each stack. +This will determine the method used to generate UltraHDR. -- %s: SDR image paired with a gain map image -- %s: SDR image paired with a JPEG-XL HDR image (10-bit, 'PQ P3 RGB' profile recommended) +- %s: SDR image paired with a gain map image. +- %s: SDR image paired with a JPEG-XL HDR image (10-bit, 'PQ P3 RGB' profile recommended). - %s: SDR images only. Gainmaps will be copies of SDR images (the simplest option). -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 - -By default, the first image in a pair is treated as SDR, and the second one stores extra gainmap/HDR data. -You can force the image into a specific slot by attaching "hdr" / "gainmap" tags. -]]), _("SDR + gainmap"), _("SDR + JPEG-XL HDR"), _("SDR (auto gainmap)")), +By default, the first image in a stack is treated as SDR, and the second one is a gainmap/HDR. +You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to them. +]]), _("SDR + gainmap"), _("SDR + JPEG-XL HDR"), _("SDR only")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 @@ -538,11 +545,28 @@ You can force the image into a specific slot by attaching "hdr" / "gainmap" tags end, _("SDR + gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP _("SDR + JPEG-XL HDR"), -- ENCODING_VARIANT_SDR_AND_HDR - _("SDR (auto gainmap)") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP + _("SDR only") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP +} + +GUI.optionwidgets.selection_type_combo = dt.new_widget("combobox") { + label = _("Selection contains"), + tooltip = string.format(_([[Select types of images selected in Darktable. +This determines how the plugin groups images into separate stacks (each stack will produce a single UltraHDR image). + +- %s: All selected image(s) belong to one stack. There will be 1 output UltraHDR image. +- %s: Group images into stacks, using the source image path + filename (ignoring extension). + Use this method if the source images for a given stack are Darktable duplicates. + +As an added precaution, each image in a stack needs to have the same dimensions. +]]), _("one stack"), _("multiple stacks (use filename)")), + selected = 0, + _("one stack"), -- SELECTION_TYPE_ONE_STACK + _("multiple stacks (use filename)") -- SELECTION_TYPE_GROUP_BY_FNAME } GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { orientation = "vertical", + GUI.optionwidgets.selection_type_combo, GUI.optionwidgets.encoding_variant_combo, GUI.optionwidgets.metadata_path_box } From 118bbad5cbdb6970d58417f1b6c9cdd60a55c1af Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sun, 22 Sep 2024 17:02:24 +0200 Subject: [PATCH 126/169] Exposed sliders for crucial settings in a metadata file, removed file picker for metadata.cfg. Temporary metadata file will be created during UltraHDR generation, using the values from the UI. --- contrib/ultrahdr.lua | 171 +++++++++++++++++++++++++++++-------------- 1 file changed, 117 insertions(+), 54 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index c4faddef..4ca4b2a5 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -68,11 +68,12 @@ local GUI = { output_directory_widget = {}, copy_exif = {}, import_to_darktable = {}, - metadata_file_generate = {}, - metadata_path_label = {}, - metadata_path_widget = {}, - metadata_path_box = {}, - metadata_path_box2 = {}, + min_content_boost = {}, + max_content_boost = {}, + hdr_capacity_min = {}, + hdr_capacity_max = {}, + metadata_label = {}, + metadata_box = {}, edit_executables_button = {}, executable_path_widget = {} }, @@ -100,22 +101,24 @@ local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 local SELECTION_TYPE_ONE_STACK = 1 local SELECTION_TYPE_GROUP_BY_FNAME = 2 -local function generate_metadata_file() - local default_metadata_file = [[--maxContentBoost 6.0 ---minContentBoost 1.0 +local function generate_metadata_file(settings) + local metadata_file_fmt = [[--maxContentBoost %f +--minContentBoost %f --gamma 1.0 --offsetSdr 0.0 --offsetHdr 0.0 ---hdrCapacityMin 1.0 ---hdrCapacityMax 6.0]] +--hdrCapacityMin %f +--hdrCapacityMax %f]] - local filename = dt.configuration.config_dir .. PS .. "ultrahdr_metadata.cfg" + local filename = df.create_unique_filename(settings.tmpdir .. PS .. "metadata.cfg") local f, err = io.open(filename, "w+") if not f then dt.print(err) return nil end - f:write(default_metadata_file) + local content = string.format(metadata_file_fmt, settings.metadata.max_content_boost, + settings.metadata.min_content_boost, settings.metadata.hdr_capacity_min, settings.metadata.hdr_capacity_max) + f:write(content) f:close() return filename end @@ -123,15 +126,25 @@ end local function save_preferences() dt.preferences.write(namespace, "encoding_variant", "integer", GUI.optionwidgets.encoding_variant_combo.selected) dt.preferences.write(namespace, "selection_type", "integer", GUI.optionwidgets.selection_type_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) if GUI.optionwidgets.output_directory_widget.value then dt.preferences.write(namespace, "output_directory", "string", GUI.optionwidgets.output_directory_widget.value) end 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) + if GUI.optionwidgets.min_content_boost.value then + dt.preferences.write(namespace, "min_content_boost", "float", GUI.optionwidgets.min_content_boost.value) + dt.preferences.write(namespace, "max_content_boost", "float", GUI.optionwidgets.max_content_boost.value) + dt.preferences.write(namespace, "hdr_capacity_min", "float", GUI.optionwidgets.hdr_capacity_min.value) + dt.preferences.write(namespace, "hdr_capacity_max", "float", GUI.optionwidgets.hdr_capacity_max.value) + end +end + +local function default_to(value, default) + if value == 0 then + return default + end + return value end local function load_preferences() @@ -141,12 +154,6 @@ local function load_preferences() GUI.optionwidgets.selection_type_combo.selected = math.max( dt.preferences.read(namespace, "selection_type", "integer"), SELECTION_TYPE_ONE_STACK) - GUI.optionwidgets.metadata_path_widget.value = dt.preferences.read(namespace, "metadata_path", "string") - if not GUI.optionwidgets.metadata_path_widget.value then -- file widgets reset "" value to nil - GUI.optionwidgets.metadata_path_widget.value = generate_metadata_file() - -- Avoid regenerating the file if the user only enables the plugin, but never clicks "Generate" - dt.preferences.write(namespace, "metadata_path", "string", GUI.optionwidgets.metadata_path_widget.value) - end GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string") GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool") if not GUI.optionwidgets.output_directory_widget.value then @@ -154,6 +161,14 @@ local function load_preferences() end 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") + GUI.optionwidgets.min_content_boost.value = default_to(dt.preferences.read(namespace, "min_content_boost", "float"), + 1.0) + GUI.optionwidgets.max_content_boost.value = default_to(dt.preferences.read(namespace, "max_content_boost", "float"), + 6.0) + GUI.optionwidgets.hdr_capacity_min.value = default_to(dt.preferences.read(namespace, "hdr_capacity_min", "float"), + 1.0) + GUI.optionwidgets.hdr_capacity_max.value = default_to(dt.preferences.read(namespace, "hdr_capacity_max", "float"), + 6.0) end local function assert_settings_correct(encoding_variant) @@ -168,7 +183,12 @@ local function assert_settings_correct(encoding_variant) 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, + metadata = { + min_content_boost = GUI.optionwidgets.min_content_boost.value, + max_content_boost = GUI.optionwidgets.max_content_boost.value, + hdr_capacity_min = GUI.optionwidgets.hdr_capacity_min.value, + hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value + }, tmpdir = dt.configuration.tmp_dir } @@ -183,8 +203,11 @@ local function assert_settings_correct(encoding_variant) end if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then - if not settings.metadata or not df.check_if_file_exists(settings.metadata) then - table.insert(errors, _("metadata.cfg file not found (use 'Generate' button)")) + if settings.metadata.min_content_boost >= settings.metadata.max_content_boost then + table.insert(errors, _("min_content_boost should not be greater than max_content_boost")) + end + if settings.metadata.hdr_capacity_min >= settings.metadata.hdr_capacity_max then + table.insert(errors, _("hdr_capacity_min should not be greater than hdr_capacity_max")) end end @@ -233,7 +256,7 @@ local function get_stacks(images, encoding_variant, selection_type) key = df.chop_filetype(v.path .. PS .. v.filename) elseif selection_type == SELECTION_TYPE_ONE_STACK then key = "the_one_and_only" - end + end if stacks[key] == nil then stacks[key] = {} end @@ -299,11 +322,11 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total function copy_or_export_jpg(src, dest) if df.get_filetype(src.filename) == "jpg" and not src.is_altered then df.file_copy(src.path .. PS .. src.filename, dest) - else + else local exporter = dt.new_format("jpeg") - exporter.quality = 95 + exporter.quality = 95 exporter:write_image(src, dest) - end + end end if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then @@ -328,11 +351,13 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. " -overwrite_original") end update_job_progress() + -- Generate metadata.cfg file + local metadata_file = generate_metadata_file(settings) -- Merge files uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg" execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -i " .. df.sanitize_filename(sdr .. ".noexif") .. " -g " .. - df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(settings.metadata) .. " -z " .. + df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(metadata_file) .. " -z " .. df.sanitize_filename(uhdr)) update_job_progress() -- Copy SDR's EXIF to UltraHDR file @@ -346,6 +371,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total -- Cleanup os.remove(sdr) os.remove(sdr .. ".noexif") + os.remove(metadata_file) if sdr ~= gainmap then os.remove(gainmap) end @@ -374,8 +400,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total local sdr_w, sdr_h = get_dimensions(images["sdr"]) 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 95 -Q 95 -D 1 " .. " -w " .. - tostring(sdr_w) .. " -h " .. tostring(sdr_h) .. " -z " .. - df.sanitize_filename(uhdr)) + tostring(sdr_w) .. " -h " .. tostring(sdr_h) .. " -z " .. df.sanitize_filename(uhdr)) update_job_progress() if settings.copy_exif then -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). @@ -492,34 +517,72 @@ GUI.optionwidgets.output_settings_box = dt.new_widget("box") { GUI.optionwidgets.copy_exif } -GUI.optionwidgets.metadata_path_label = dt.new_widget("label") { - label = _("Gainmap metadata file") +GUI.optionwidgets.metadata_label = dt.new_widget("label") { + label = _("Gainmap metadata") } -GUI.optionwidgets.metadata_path_widget = dt.new_widget("file_chooser_button") { - title = _("Select libultrahdr metadata.cfg file"), - is_directory = false +GUI.optionwidgets.min_content_boost = dt.new_widget("slider") { + label = _('Min content boost'), + tooltip = _( + 'How much darker an image can get, when shown on an HDR display, relative to the SDR rendition (linear, SDR = 1.0). Also called "GainMapMin". '), + hard_min = 0.9, + hard_max = 10, + soft_min = 0.9, + soft_max = 2, + step = 1, + digits = 1, + reset_callback = function(self) + self.value = 1.0 + end } - -GUI.optionwidgets.metadata_file_generate = dt.new_widget("button") { - label = _("Generate"), - tooltip = _("Generate new metadata file with default values"), - clicked_callback = function() - local metadata_file = generate_metadata_file() - GUI.optionwidgets.metadata_path_widget.value = metadata_file +GUI.optionwidgets.max_content_boost = dt.new_widget("slider") { + label = _('Max content boost'), + tooltip = _( + 'How much brighter an image can get, when shown on an HDR display, relative to the SDR rendition (linear, SDR = 1.0). Also called "GainMapMax". \n\nMust not be lower than Min content boost'), + hard_min = 1, + hard_max = 10, + soft_min = 2, + soft_max = 10, + step = 1, + digits = 1, + reset_callback = function(self) + self.value = 6.0 end } - -GUI.optionwidgets.metadata_path_box2 = dt.new_widget("box") { - orientation = "horizontal", - GUI.optionwidgets.metadata_path_widget, - GUI.optionwidgets.metadata_file_generate +GUI.optionwidgets.hdr_capacity_min = dt.new_widget("slider") { + label = _('Min HDR capacity'), + tooltip = _('Minimum display boost value for which the gain map is applied at all (linear, SDR = 1.0).'), + hard_min = 0.9, + hard_max = 10, + soft_min = 1, + soft_max = 2, + step = 1, + digits = 1, + reset_callback = function(self) + self.value = 1.0 + end +} +GUI.optionwidgets.hdr_capacity_max = dt.new_widget("slider") { + label = _('Max HDR capacity'), + tooltip = _('Maximum display boost value for which the gain map is applied completely (linear, SDR = 1.0).'), + hard_min = 1, + hard_max = 10, + soft_min = 2, + soft_max = 10, + digits = 1, + step = 1, + reset_callback = function(self) + self.value = 6.0 + end } -GUI.optionwidgets.metadata_path_box = dt.new_widget("box") { +GUI.optionwidgets.metadata_box = dt.new_widget("box") { orientation = "vertical", - GUI.optionwidgets.metadata_path_label, - GUI.optionwidgets.metadata_path_box2 + GUI.optionwidgets.metadata_label, + GUI.optionwidgets.min_content_boost, + GUI.optionwidgets.max_content_boost, + GUI.optionwidgets.hdr_capacity_min, + GUI.optionwidgets.hdr_capacity_max } GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") { @@ -538,9 +601,9 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP or self.selected == ENCODING_VARIANT_SDR_AUTO_GAINMAP then - GUI.optionwidgets.metadata_path_box.visible = true + GUI.optionwidgets.metadata_box.visible = true else - GUI.optionwidgets.metadata_path_box.visible = false + GUI.optionwidgets.metadata_box.visible = false end end, _("SDR + gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP @@ -568,7 +631,7 @@ GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { orientation = "vertical", GUI.optionwidgets.selection_type_combo, GUI.optionwidgets.encoding_variant_combo, - GUI.optionwidgets.metadata_path_box + GUI.optionwidgets.metadata_box } GUI.optionwidgets.executable_path_widget = df.executable_path_widget({"ultrahdr_app", "exiftool", "ffmpeg"}) From f6a1d1c20b7e162030fa75a00add12661e072e67 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Tue, 24 Sep 2024 14:57:35 +0200 Subject: [PATCH 127/169] Made the UltraHDR generation more robust. - Appropriate color profiles are used when generating UltraHDR files. DT_COLORSPACE_DISPLAY_P3 is used for SDR, and DT_COLORSPACE_PQ_P3 is used for HDR, regardless of what was selected in the export module UI. - Added correction for odd image dimensions in SDR + HDR mode. - Added quality setting for JPEG compression - SDR + HDR option can source from any DT image (JPEG-XL is generated on the fly). - generation can abort on errors and display error messages. --- contrib/ultrahdr.lua | 190 ++++++++++++++++++++++++++++++++----------- 1 file changed, 142 insertions(+), 48 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 4ca4b2a5..f013ff37 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -75,7 +75,8 @@ local GUI = { metadata_label = {}, metadata_box = {}, edit_executables_button = {}, - executable_path_widget = {} + executable_path_widget = {}, + quality_widget = {} }, options = {}, run = {} @@ -101,6 +102,10 @@ local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 local SELECTION_TYPE_ONE_STACK = 1 local SELECTION_TYPE_GROUP_BY_FNAME = 2 +-- Values are defined in darktable/src/common/colorspaces.h +local DT_COLORSPACE_PQ_P3 = 24 +local DT_COLORSPACE_DISPLAY_P3 = 26 + local function generate_metadata_file(settings) local metadata_file_fmt = [[--maxContentBoost %f --minContentBoost %f @@ -138,6 +143,7 @@ local function save_preferences() dt.preferences.write(namespace, "hdr_capacity_min", "float", GUI.optionwidgets.hdr_capacity_min.value) dt.preferences.write(namespace, "hdr_capacity_max", "float", GUI.optionwidgets.hdr_capacity_max.value) end + dt.preferences.write(namespace, "quality", "integer", GUI.optionwidgets.quality_widget.value) end local function default_to(value, default) @@ -169,6 +175,30 @@ local function load_preferences() 1.0) GUI.optionwidgets.hdr_capacity_max.value = default_to(dt.preferences.read(namespace, "hdr_capacity_max", "float"), 6.0) + GUI.optionwidgets.quality_widget.value = default_to(dt.preferences.read(namespace, "quality", "integer"), 95) +end + +-- Changes the combobox selection blindly until a paired config value is set. +-- Workaround for https://github.com/darktable-org/lua-scripts/issues/522 +local function set_combobox(path, instance, config_name, new_config_value) + + local pref = dt.preferences.read("darktable", config_name, "integer") + if pref == new_config_value then + return new_config_value + end + + dt.gui.action(path, 0, "selection", "first", 1.0) + local limit, i = 30, 0 -- in case there is no matching config value in the first n entries of a combobox. + while i < limit do + i = i + 1 + dt.gui.action(path, 0, "selection", "next", 1.0) + dt.control.sleep(10) + if dt.preferences.read("darktable", config_name, "integer") == new_config_value then + log.msg(log.debug, string.format(_("Changed %s from %d to %d"), config_name, pref, new_config_value)) + return pref + end + end + log.msg(log.error, string.format(_("Could not change %s from %d to %d"), config_name, pref, new_config_value)) end local function assert_settings_correct(encoding_variant) @@ -189,6 +219,7 @@ local function assert_settings_correct(encoding_variant) hdr_capacity_min = GUI.optionwidgets.hdr_capacity_min.value, hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value }, + quality = GUI.optionwidgets.quality_widget.value, tmpdir = dt.configuration.tmp_dir } @@ -226,11 +257,10 @@ end local function get_stacks(images, encoding_variant, selection_type) local stacks = {} - local extra_image_content_type, extra_image_extension + local extra_image_content_type 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" elseif encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then extra_image_content_type = nil @@ -278,9 +308,6 @@ local function get_stacks(images, encoding_variant, selection_type) if extra_image_content_type then if not v["sdr"] or not v[extra_image_content_type] 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 local sdr_w, sdr_h = get_dimensions(v["sdr"]) local extra_w, extra_h = get_dimensions(v[extra_image_content_type]) @@ -309,6 +336,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total local total_substeps local substep = 0 local uhdr + local errors = {} function update_job_progress() substep = substep + 1 @@ -319,28 +347,56 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total job.percent = (total_substeps * step + substep) / (total_steps * total_substeps) end - function copy_or_export_jpg(src, dest) - if df.get_filetype(src.filename) == "jpg" and not src.is_altered then - df.file_copy(src.path .. PS .. src.filename, dest) + function copy_or_export(src_image, dest, format, colorspace, props) + if df.get_filetype(src_image.filename) == df.get_filetype(dest) and not src_image.is_altered then + return df.file_copy(src_image.path .. PS .. src_image.filename, dest) else - local exporter = dt.new_format("jpeg") - exporter.quality = 95 - exporter:write_image(src, dest) + local prev = set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", colorspace) + if not prev then + return false + end + local exporter = dt.new_format(format) + for k, v in pairs(props) do + exporter[k] = v + end + local ok = not exporter:write_image(src_image, dest) + if prev then + set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", prev) + end + return ok end + return true end if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then total_substeps = 6 + local ok -- Export/copy both SDR and gainmap to JPEGs local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".jpg") - copy_or_export_jpg(images["sdr"], sdr) + ok = copy_or_export(images["sdr"], sdr, "jpeg", DT_COLORSPACE_DISPLAY_P3, { + quality = settings.quality + }) + if not ok then + os.remove(sdr) + table.insert(errors, string.format(_("Error exporting %s to %s"), images["sdr"].filename, "jpeg")) + return false, errors + end + local gainmap if encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then -- SDR is also a gainmap gainmap = sdr else gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg") - copy_or_export_jpg(images["gainmap"], gainmap) + ok = copy_or_export(images["gainmap"], gainmap, "jpeg", DT_COLORSPACE_DISPLAY_P3, { + quality = settings.quality + }) + if not ok then + os.remove(sdr) + os.remove(sdr) + table.insert(errors, string.format(_("Error exporting %s to %s"), images["gainmap"].filename, "jpeg")) + return false, errors + end end log.msg(log.debug, string.format(_("Exported files: %s, %s"), sdr, gainmap)) update_job_progress() @@ -377,30 +433,54 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end update_job_progress() elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then - total_substeps = 5 + local ok + total_substeps = 6 -- 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 + -- Step 1: Export HDR to JPEG-XL with DT_COLORSPACE_PQ_P3 + local hdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["hdr"].filename) .. + ".jxl") + ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { + bpp = 10, + quality = 100 -- lossless + }) + if not ok then + os.remove(hdr) + table.insert(errors, string.format(_("Error exporting %s to %s"), images["hdr"].filename, "jxl")) + return false, errors + end + update_job_progress() + -- Step 2: Export SDR to PNG local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".png") - exporter:write_image(images["sdr"], sdr) + ok = copy_or_export(images["sdr"], sdr, "png", DT_COLORSPACE_DISPLAY_P3, { + bpp = 8 + }) + if not ok then + os.remove(hdr) + os.remove(sdr) + table.insert(errors, string.format(_("Error exporting %s to %s"), images["sdr"].filename, "png")) + return false, errors + end 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 " .. + -- Step 3: Generate libultrahdr RAW images + local sdr_w, sdr_h = get_dimensions(images["sdr"]) + local resize_cmd = "" + if sdr_h % 2 + sdr_w % 2 > 0 then -- needs resizing to even dimensions. + resize_cmd = string.format(" -vf 'crop=%d:%d:0:0' ", sdr_w - sdr_w % 2, sdr_h - sdr_h % 2) + end + + execute_cmd(settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(sdr) .. resize_cmd .. " -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)) + execute_cmd(settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(hdr) .. resize_cmd .. " -pix_fmt p010le -f rawvideo " .. + df.sanitize_filename(hdr .. ".raw")) update_job_progress() - local sdr_w, sdr_h = get_dimensions(images["sdr"]) 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 95 -Q 95 -D 1 " .. " -w " .. - tostring(sdr_w) .. " -h " .. tostring(sdr_h) .. " -z " .. df.sanitize_filename(uhdr)) + df.sanitize_filename(hdr .. ".raw") .. + string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 1 -s 1 -q %d -Q %d -D 1 ", settings.quality, + settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. " -z " .. + df.sanitize_filename(uhdr)) update_job_progress() if settings.copy_exif then -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). @@ -409,9 +489,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total df.sanitize_filename(uhdr) .. " -overwrite_original -preserve") end -- Cleanup + os.remove(hdr) os.remove(sdr) + os.remove(hdr .. ".raw") os.remove(sdr .. ".raw") - os.remove(extra) update_job_progress() end @@ -433,12 +514,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total log.msg(log.info, msg) dt.print(msg) update_job_progress() + return true, nil end local function main() - local saved_log_level = log.log_level() - log.log_level(log.info) - save_preferences() local selection_type = GUI.optionwidgets.selection_type_combo.selected @@ -447,21 +526,25 @@ local function main() local settings, errors = assert_settings_correct(encoding_variant) if not settings then - dt.print(string.format(_("Export settings are incorrect, exiting:\n\n%s"), table.concat(errors, "\n"))) - log.log_level(saved_log_level) + dt.print(string.format(_("Export settings are incorrect, exiting:\n\n- %s"), table.concat(errors, "\n- "))) return end local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant, selection_type) 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 + local msg for i, v in pairs(stacks) do - generate_ultrahdr(encoding_variant, v, settings, count, stack_count) + local ok, errors = generate_ultrahdr(encoding_variant, v, settings, count, stack_count) + if not ok then + dt.print(string.format(_("Errors generating images:\n\n- %s"), table.concat(errors, "\n- "))) + job.valid = false + return + end count = count + 1 -- sleep for a short moment to give stop_job callback function a chance to run dt.control.sleep(10) @@ -471,10 +554,9 @@ local function main() job.valid = false end - local msg = string.format(_("Generated %d UltraHDR image(s)."), count) + msg = string.format(_("Generated %d UltraHDR image(s)."), count) log.msg(log.info, msg) dt.print(msg) - log.log_level(saved_log_level) end GUI.optionwidgets.settings_label = dt.new_widget("section_label") { @@ -591,12 +673,12 @@ GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") { This will determine the method used to generate UltraHDR. - %s: SDR image paired with a gain map image. -- %s: SDR image paired with a JPEG-XL HDR image (10-bit, 'PQ P3 RGB' profile recommended). -- %s: SDR images only. Gainmaps will be copies of SDR images (the simplest option). +- %s: SDR image paired with an HDR image. +- %s: Each stack consists of a single SDR image. Gainmaps will be copies of SDR images. By default, the first image in a stack is treated as SDR, and the second one is a gainmap/HDR. -You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to them. -]]), _("SDR + gainmap"), _("SDR + JPEG-XL HDR"), _("SDR only")), +You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to it. +]]), _("SDR + gainmap"), _("SDR + HDR"), _("SDR only")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 @@ -607,7 +689,7 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap end end, _("SDR + gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP - _("SDR + JPEG-XL HDR"), -- ENCODING_VARIANT_SDR_AND_HDR + _("SDR + HDR"), -- ENCODING_VARIANT_SDR_AND_HDR _("SDR only") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP } @@ -627,10 +709,25 @@ As an added precaution, each image in a stack needs to have the same dimensions. _("multiple stacks (use filename)") -- SELECTION_TYPE_GROUP_BY_FNAME } +GUI.optionwidgets.quality_widget = dt.new_widget("slider") { + label = _('Quality'), + tooltip = _('Quality of the output UltraHDR JPEG file'), + hard_min = 0, + hard_max = 100, + soft_min = 0, + soft_max = 100, + step = 1, + digits = 0, + reset_callback = function(self) + self.value = 95 + end +} + GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { orientation = "vertical", GUI.optionwidgets.selection_type_combo, GUI.optionwidgets.encoding_variant_combo, + GUI.optionwidgets.quality_widget, GUI.optionwidgets.metadata_box } @@ -656,10 +753,7 @@ GUI.options = dt.new_widget("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) -]]), + tooltip = _("Generate UltraHDR image(s) from selection"), clicked_callback = main } From 3791b47914bce99129fa3eed69f06d20772b5deb Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Tue, 24 Sep 2024 15:03:52 +0200 Subject: [PATCH 128/169] Use lowercase labels to adjust to DT style. --- contrib/ultrahdr.lua | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index f013ff37..4d37a6c3 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -1,6 +1,6 @@ --[[ - UltraHDR storage for darktable + UltraHDR image generation for darktable copyright (c) 2024 Krzysztof Kotowicz @@ -20,7 +20,7 @@ ]] --[[ ULTRAHDR -Generate UltraHDR JPG images from various combinations of source files (SDR, HDR, gainmap). +Generate UltraHDR JPEG images from various combinations of source files (SDR, HDR, gainmap). https://developer.android.com/media/platform/hdr-image-format @@ -34,6 +34,7 @@ ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT USAGE * require this file from your main luarc config file * set binary tool paths +* Use UltraHDR module to generate UltraHDR images from selection ]] local dt = require "darktable" local du = require "lib/dtutils" @@ -564,16 +565,16 @@ GUI.optionwidgets.settings_label = dt.new_widget("section_label") { } GUI.optionwidgets.output_settings_label = dt.new_widget("section_label") { - label = _("Output") + label = _("output") } GUI.optionwidgets.output_directory_widget = dt.new_widget("file_chooser_button") { - title = _("Select directory to write UltraHDR image files to"), + 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"), + 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 @@ -581,12 +582,12 @@ GUI.optionwidgets.use_original_directory = dt.new_widget("check_button") { } GUI.optionwidgets.import_to_darktable = dt.new_widget("check_button") { - label = _("Import UltraHDRs to Darktable"), + label = _("import UltraHDRs to library"), 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)"), + label = _("copy EXIF data"), tooltip = _("Copy EXIF data into UltraHDR file(s) from their SDR sources.") } @@ -600,11 +601,11 @@ GUI.optionwidgets.output_settings_box = dt.new_widget("box") { } GUI.optionwidgets.metadata_label = dt.new_widget("label") { - label = _("Gainmap metadata") + label = _("gainmap metadata") } GUI.optionwidgets.min_content_boost = dt.new_widget("slider") { - label = _('Min content boost'), + label = _('min content boost'), tooltip = _( 'How much darker an image can get, when shown on an HDR display, relative to the SDR rendition (linear, SDR = 1.0). Also called "GainMapMin". '), hard_min = 0.9, @@ -618,7 +619,7 @@ GUI.optionwidgets.min_content_boost = dt.new_widget("slider") { end } GUI.optionwidgets.max_content_boost = dt.new_widget("slider") { - label = _('Max content boost'), + label = _('max content boost'), tooltip = _( 'How much brighter an image can get, when shown on an HDR display, relative to the SDR rendition (linear, SDR = 1.0). Also called "GainMapMax". \n\nMust not be lower than Min content boost'), hard_min = 1, @@ -632,7 +633,7 @@ GUI.optionwidgets.max_content_boost = dt.new_widget("slider") { end } GUI.optionwidgets.hdr_capacity_min = dt.new_widget("slider") { - label = _('Min HDR capacity'), + label = _('min HDR capacity'), tooltip = _('Minimum display boost value for which the gain map is applied at all (linear, SDR = 1.0).'), hard_min = 0.9, hard_max = 10, @@ -645,7 +646,7 @@ GUI.optionwidgets.hdr_capacity_min = dt.new_widget("slider") { end } GUI.optionwidgets.hdr_capacity_max = dt.new_widget("slider") { - label = _('Max HDR capacity'), + label = _('max HDR capacity'), tooltip = _('Maximum display boost value for which the gain map is applied completely (linear, SDR = 1.0).'), hard_min = 1, hard_max = 10, @@ -668,7 +669,7 @@ GUI.optionwidgets.metadata_box = dt.new_widget("box") { } GUI.optionwidgets.encoding_variant_combo = dt.new_widget("combobox") { - label = _("Each stack contains"), + label = _("each stack contains"), tooltip = string.format(_([[Select the types of images in each stack. This will determine the method used to generate UltraHDR. @@ -694,7 +695,7 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap } GUI.optionwidgets.selection_type_combo = dt.new_widget("combobox") { - label = _("Selection contains"), + label = _("selection contains"), tooltip = string.format(_([[Select types of images selected in Darktable. This determines how the plugin groups images into separate stacks (each stack will produce a single UltraHDR image). @@ -710,7 +711,7 @@ As an added precaution, each image in a stack needs to have the same dimensions. } GUI.optionwidgets.quality_widget = dt.new_widget("slider") { - label = _('Quality'), + label = _('quality'), tooltip = _('Quality of the output UltraHDR JPEG file'), hard_min = 0, hard_max = 100, @@ -735,7 +736,7 @@ GUI.optionwidgets.executable_path_widget = df.executable_path_widget({"ultrahdr_ GUI.optionwidgets.executable_path_widget.visible = false GUI.optionwidgets.edit_executables_button = dt.new_widget("button") { - label = _("Show / hide executables"), + 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 @@ -752,7 +753,7 @@ GUI.options = dt.new_widget("box") { } GUI.run = dt.new_widget("button") { - label = _("Generate UltraHDR"), + label = _("generate UltraHDR"), tooltip = _("Generate UltraHDR image(s) from selection"), clicked_callback = main } From 84a1c1ddfe4ca43e919f95f11f40e58169f7727b Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Wed, 25 Sep 2024 19:47:18 +0200 Subject: [PATCH 129/169] Use single channel gainmaps in SDR+HDR. --- contrib/ultrahdr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 4d37a6c3..a3b30e0a 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -479,7 +479,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total update_job_progress() execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr .. ".raw") .. " -p " .. df.sanitize_filename(hdr .. ".raw") .. - string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 1 -s 1 -q %d -Q %d -D 1 ", settings.quality, + string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. " -z " .. df.sanitize_filename(uhdr)) update_job_progress() From 749db97dd53fbf21b98042b759de7c61df006c7d Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Wed, 25 Sep 2024 19:47:41 +0200 Subject: [PATCH 130/169] Speed up export in HDR+SDR. --- contrib/ultrahdr.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index a3b30e0a..508ef8dd 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -442,7 +442,8 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total ".jxl") ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { bpp = 10, - quality = 100 -- lossless + quality = 100, -- lossless + effort = 1, -- we don't care about the size, the faile is temporary. }) if not ok then os.remove(hdr) From 2a4452cd20fcb7586556d2e8ad61264674ab1074 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Wed, 25 Sep 2024 21:30:09 +0200 Subject: [PATCH 131/169] Workaround https://github.com/darktable-org/darktable/pull/17529 for future DT versions. --- contrib/ultrahdr.lua | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 508ef8dd..f1f859cf 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -46,8 +46,8 @@ 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") +-- works with darktable API version from 4.8.0 on +du.check_min_api_version("9.3.0", "ultrahdr") dt.gettext.bindtextdomain(namespace, dt.configuration.config_dir .. "/lua/locale/") @@ -360,7 +360,12 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total for k, v in pairs(props) do exporter[k] = v end - local ok = not exporter:write_image(src_image, dest) + local ok = exporter:write_image(src_image, dest) + if dt.configuration.api_version_string == "9.3.0" then + -- Workaround for https://github.com/darktable-org/darktable/issues/17528 + ok = not ok + end + if prev then set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", prev) end From ea7ac8e474bd8a066f2ee034f1401f9cd360606a Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Wed, 25 Sep 2024 21:38:35 +0200 Subject: [PATCH 132/169] Abort and cleanup early when any of the binaries fail. --- contrib/ultrahdr.lua | 142 ++++++++++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 57 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index f1f859cf..83568c8f 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -328,16 +328,23 @@ 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 local errors = {} + local remove_files = {} + local ok + local cmd + + local function execute_cmd(cmd, errormsg) + log.msg(log.debug, cmd) + local code = dtsys.external_command(cmd) + if errormsg and code > 0 then + table.insert(errors, errormsg) + end + return code == 0 + end function update_job_progress() substep = substep + 1 @@ -374,19 +381,25 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total return true end + function cleanup() + for _, v in pairs(remove_files) do + os.remove(v) + end + return false + end + if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then - total_substeps = 6 - local ok + total_substeps = 5 -- Export/copy both SDR and gainmap to JPEGs local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".jpg") + table.insert(remove_files, sdr) ok = copy_or_export(images["sdr"], sdr, "jpeg", DT_COLORSPACE_DISPLAY_P3, { quality = settings.quality }) if not ok then - os.remove(sdr) table.insert(errors, string.format(_("Error exporting %s to %s"), images["sdr"].filename, "jpeg")) - return false, errors + return cleanup(), errors end local gainmap @@ -394,52 +407,56 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total gainmap = sdr else gainmap = df.create_unique_filename(settings.tmpdir .. PS .. images["gainmap"].filename .. "_gainmap.jpg") + table.insert(remove_files, gainmap) ok = copy_or_export(images["gainmap"], gainmap, "jpeg", DT_COLORSPACE_DISPLAY_P3, { quality = settings.quality }) if not ok then - os.remove(sdr) - os.remove(sdr) table.insert(errors, string.format(_("Error exporting %s to %s"), images["gainmap"].filename, "jpeg")) - return false, errors + return cleanup(), errors end end 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")) + table.insert(remove_files, sdr .. ".noexif") + cmd = settings.bin.exiftool .. " -all= " .. df.sanitize_filename(sdr) .. " -o " .. + df.sanitize_filename(sdr .. ".noexif") + if not execute_cmd(cmd, string.format(_("Error stripping EXIF from %s"), sdr)) then + return cleanup(), errors + end if sdr ~= gainmap then - execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. " -overwrite_original") + if not execute_cmd(settings.bin.exiftool .. " -all= " .. df.sanitize_filename(gainmap) .. + " -overwrite_original", string.format(_("Error stripping EXIF from %s"), gainmap)) then + return cleanup(), errors + end end update_job_progress() -- Generate metadata.cfg file local metadata_file = generate_metadata_file(settings) + table.insert(remove_files, metadata_file) -- Merge files uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg" - - execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -i " .. df.sanitize_filename(sdr .. ".noexif") .. " -g " .. - df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(metadata_file) .. " -z " .. - df.sanitize_filename(uhdr)) + table.insert(remove_files, uhdr) + cmd = settings.bin.ultrahdr_app .. " -m 0 -i " .. df.sanitize_filename(sdr .. ".noexif") .. " -g " .. + df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(metadata_file) .. " -z " .. + df.sanitize_filename(uhdr) + if not execute_cmd(cmd, string.format(_("Error merging UltraHDR to %s"), uhdr)) then + return cleanup(), errors + end update_job_progress() -- Copy SDR's EXIF to UltraHDR file if settings.copy_exif then -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). -- This might hapen e.g. when the source files are Adobe gainmap HDRs. - execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. - df.sanitize_filename(uhdr) .. " -overwrite_original -preserve") - end - update_job_progress() - -- Cleanup - os.remove(sdr) - os.remove(sdr .. ".noexif") - os.remove(metadata_file) - if sdr ~= gainmap then - os.remove(gainmap) + cmd = settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. + df.sanitize_filename(uhdr) .. " -overwrite_original -preserve" + if not execute_cmd(cmd, string.format(_("Error adding EXIF to %s"), uhdr)) then + return cleanup(), errors + end end update_job_progress() elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then - local ok total_substeps = 6 -- https://discuss.pixls.us/t/manual-creation-of-ultrahdr-images/45004/20 -- Step 1: Export HDR to JPEG-XL with DT_COLORSPACE_PQ_P3 @@ -448,28 +465,26 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { bpp = 10, quality = 100, -- lossless - effort = 1, -- we don't care about the size, the faile is temporary. + effort = 1 -- we don't care about the size, the faile is temporary. }) if not ok then - os.remove(hdr) table.insert(errors, string.format(_("Error exporting %s to %s"), images["hdr"].filename, "jxl")) - return false, errors + return cleanup(), errors end update_job_progress() -- Step 2: Export SDR to PNG local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".png") + table.insert(remove_files, sdr) ok = copy_or_export(images["sdr"], sdr, "png", DT_COLORSPACE_DISPLAY_P3, { bpp = 8 }) if not ok then - os.remove(hdr) - os.remove(sdr) table.insert(errors, string.format(_("Error exporting %s to %s"), images["sdr"].filename, "png")) - return false, errors + return cleanup(), errors end uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg" - + table.insert(remove_files, uhdr) update_job_progress() -- Step 3: Generate libultrahdr RAW images local sdr_w, sdr_h = get_dimensions(images["sdr"]) @@ -477,35 +492,48 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total if sdr_h % 2 + sdr_w % 2 > 0 then -- needs resizing to even dimensions. resize_cmd = string.format(" -vf 'crop=%d:%d:0:0' ", sdr_w - sdr_w % 2, sdr_h - sdr_h % 2) end - - execute_cmd(settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(sdr) .. resize_cmd .. " -pix_fmt rgba -f rawvideo " .. - df.sanitize_filename(sdr .. ".raw")) - execute_cmd(settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(hdr) .. resize_cmd .. " -pix_fmt p010le -f rawvideo " .. - df.sanitize_filename(hdr .. ".raw")) + table.insert(remove_files, sdr .. ".raw") + table.insert(remove_files, hdr .. ".raw") + cmd = + settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(sdr) .. resize_cmd .. " -pix_fmt rgba -f rawvideo " .. + df.sanitize_filename(sdr .. ".raw") + if not execute_cmd(cmd, string.format(_("Error generating %s"), sdr .. ".raw")) then + return cleanup(), errors + end + cmd = settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(hdr) .. resize_cmd .. + " -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(hdr .. ".raw") + if not execute_cmd(cmd, string.format(_("Error generating %s"), hdr .. ".raw")) then + return cleanup(), errors + end update_job_progress() - execute_cmd(settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr .. ".raw") .. " -p " .. - df.sanitize_filename(hdr .. ".raw") .. - string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, - settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. " -z " .. - df.sanitize_filename(uhdr)) + cmd = settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr .. ".raw") .. " -p " .. + df.sanitize_filename(hdr .. ".raw") .. + string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, + settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. + " -z " .. df.sanitize_filename(uhdr) + if not execute_cmd(cmd, string.format(_("Error merging %s"), uhdr)) then + return cleanup(), errors + end update_job_progress() if settings.copy_exif then -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). -- This might hapen e.g. when the source files are Adobe gainmap HDRs. - execute_cmd(settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. - df.sanitize_filename(uhdr) .. " -overwrite_original -preserve") + cmd = settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(sdr) .. " -exif " .. + df.sanitize_filename(uhdr) .. " -overwrite_original -preserve" + if not execute_cmd(cmd, string.format(_("Error adding EXIF to %s"), uhdr)) then + return cleanup(), errors + end end - -- Cleanup - os.remove(hdr) - os.remove(sdr) - os.remove(hdr .. ".raw") - os.remove(sdr .. ".raw") 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) + ok = df.file_move(uhdr, output_file) + if not ok then + table.insert(errors, string.format(_("Error generating UltraHDR for %s"), images["sdr"].filename)) + return cleanup(), errors + end if settings.import_to_darktable then local img = dt.database.import(output_file) -- Add "ultrahdr" tag to the imported image @@ -516,11 +544,11 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end dt.tags.attach(tagnr, img) end - + cleanup() + update_job_progress() local msg = string.format(_("Generated %s."), df.get_filename(output_file)) log.msg(log.info, msg) dt.print(msg) - update_job_progress() return true, nil end From 268aa7df42dadb1770f078b2a7fffcb677b636e4 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 26 Sep 2024 09:05:31 +0200 Subject: [PATCH 133/169] Removed darktable capitalization. --- contrib/ultrahdr.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 83568c8f..d26a72f9 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -617,7 +617,7 @@ GUI.optionwidgets.use_original_directory = dt.new_widget("check_button") { GUI.optionwidgets.import_to_darktable = dt.new_widget("check_button") { label = _("import UltraHDRs to library"), - tooltip = _("Import UltraHDR images to Darktable library after generating, with an 'ultrahdr' tag attached.") + tooltip = _("Import UltraHDR images to darktable library after generating, with an 'ultrahdr' tag attached.") } GUI.optionwidgets.copy_exif = dt.new_widget("check_button") { @@ -730,12 +730,12 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap GUI.optionwidgets.selection_type_combo = dt.new_widget("combobox") { label = _("selection contains"), - tooltip = string.format(_([[Select types of images selected in Darktable. + tooltip = string.format(_([[Select types of images selected in darktable. This determines how the plugin groups images into separate stacks (each stack will produce a single UltraHDR image). - %s: All selected image(s) belong to one stack. There will be 1 output UltraHDR image. - %s: Group images into stacks, using the source image path + filename (ignoring extension). - Use this method if the source images for a given stack are Darktable duplicates. + Use this method if the source images for a given stack are darktable duplicates. As an added precaution, each image in a stack needs to have the same dimensions. ]]), _("one stack"), _("multiple stacks (use filename)")), From 13ae486ab3abbebbb8c5f379748a9cf249573ca9 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 26 Sep 2024 10:02:47 +0200 Subject: [PATCH 134/169] Verify raw file size in pixels. Added option to keep temporary files around for analysis. --- contrib/ultrahdr.lua | 45 +++++++++++++++++++++++++++++++++----------- 1 file changed, 34 insertions(+), 11 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index d26a72f9..74baab9e 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -221,7 +221,8 @@ local function assert_settings_correct(encoding_variant) hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value }, quality = GUI.optionwidgets.quality_widget.value, - tmpdir = dt.configuration.tmp_dir + tmpdir = dt.configuration.tmp_dir, + skip_cleanup = false -- keep temporary files around, for debugging. } if not settings.use_original_dir and (not settings.output or not df.check_if_file_exists(settings.output)) then @@ -328,6 +329,16 @@ local function stop_job(job) job.valid = false end +local function file_size(path) + local f, err = io.open(path, "r") + if not f then + return 0 + end + local size = f:seek("end") + f:close() + return size +end + local function generate_ultrahdr(encoding_variant, images, settings, step, total_steps) local total_substeps local substep = 0 @@ -382,6 +393,9 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end function cleanup() + if settings.skip_cleanup then + return false + end for _, v in pairs(remove_files) do os.remove(v) end @@ -462,6 +476,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total -- Step 1: Export HDR to JPEG-XL with DT_COLORSPACE_PQ_P3 local hdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["hdr"].filename) .. ".jxl") + table.insert(remove_files, hdr) ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { bpp = 10, quality = 100, -- lossless @@ -487,27 +502,34 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total table.insert(remove_files, uhdr) update_job_progress() -- Step 3: Generate libultrahdr RAW images + local sdr_raw, hdr_raw = sdr .. ".raw", hdr .. ".raw" + table.insert(remove_files, sdr_raw) + table.insert(remove_files, hdr_raw) local sdr_w, sdr_h = get_dimensions(images["sdr"]) local resize_cmd = "" if sdr_h % 2 + sdr_w % 2 > 0 then -- needs resizing to even dimensions. resize_cmd = string.format(" -vf 'crop=%d:%d:0:0' ", sdr_w - sdr_w % 2, sdr_h - sdr_h % 2) end - table.insert(remove_files, sdr .. ".raw") - table.insert(remove_files, hdr .. ".raw") + local size_in_px = (sdr_w - sdr_w % 2) * (sdr_h - sdr_h % 2) cmd = settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(sdr) .. resize_cmd .. " -pix_fmt rgba -f rawvideo " .. - df.sanitize_filename(sdr .. ".raw") - if not execute_cmd(cmd, string.format(_("Error generating %s"), sdr .. ".raw")) then + df.sanitize_filename(sdr_raw) + if not execute_cmd(cmd, string.format(_("Error generating %s"), sdr_raw)) then return cleanup(), errors end cmd = settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(hdr) .. resize_cmd .. - " -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(hdr .. ".raw") - if not execute_cmd(cmd, string.format(_("Error generating %s"), hdr .. ".raw")) then + " -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(hdr_raw) + if not execute_cmd(cmd, string.format(_("Error generating %s"), hdr_raw)) then + return cleanup(), errors + end + -- sanity check for file sizes (sometimes dt exports different size images if the files were never opened in darktable view) + if file_size(sdr_raw) ~= size_in_px * 4 or file_size(hdr_raw) ~= size_in_px & 3 then + table.insert(errors, string.format(_("Wrong raw image dimensions: %s, expected %dx%d. Try opening the image in darktable mode first."), images["sdr"].filename, sdr_w, sdr_h)) return cleanup(), errors end update_job_progress() - cmd = settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr .. ".raw") .. " -p " .. - df.sanitize_filename(hdr .. ".raw") .. + cmd = settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr_raw) .. " -p " .. + df.sanitize_filename(hdr_raw) .. string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. " -z " .. df.sanitize_filename(uhdr) @@ -566,17 +588,18 @@ local function main() end local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant, selection_type) - dt.print(string.format(_("Detected %d image stack(s)"), stack_count)) if stack_count == 0 then + dt.print(string.format(_("No image stacks detected.\n\nMake sure that the image pairs have the same widths and heights."), stack_count)) return end + dt.print(string.format(_("Detected %d image stack(s)"), stack_count)) job = dt.gui.create_job(_("Generating UltraHDR images"), true, stop_job) local count = 0 local msg for i, v in pairs(stacks) do local ok, errors = generate_ultrahdr(encoding_variant, v, settings, count, stack_count) if not ok then - dt.print(string.format(_("Errors generating images:\n\n- %s"), table.concat(errors, "\n- "))) + dt.print(string.format(_("Generating UltraHDR images failed:\n\n- %s"), table.concat(errors, "\n- "))) job.valid = false return end From a3404b64deb80df6cf482d2aea8bebeb78686a54 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 26 Sep 2024 10:44:14 +0200 Subject: [PATCH 135/169] Renamed gainmap to "gain map" in UI. --- contrib/ultrahdr.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 74baab9e..abe9da95 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -20,7 +20,7 @@ ]] --[[ ULTRAHDR -Generate UltraHDR JPEG images from various combinations of source files (SDR, HDR, gainmap). +Generate UltraHDR JPEG images from various combinations of source files (SDR, HDR, gain map). https://developer.android.com/media/platform/hdr-image-format @@ -658,7 +658,7 @@ GUI.optionwidgets.output_settings_box = dt.new_widget("box") { } GUI.optionwidgets.metadata_label = dt.new_widget("label") { - label = _("gainmap metadata") + label = _("gain map metadata") } GUI.optionwidgets.min_content_boost = dt.new_widget("slider") { @@ -732,11 +732,11 @@ This will determine the method used to generate UltraHDR. - %s: SDR image paired with a gain map image. - %s: SDR image paired with an HDR image. -- %s: Each stack consists of a single SDR image. Gainmaps will be copies of SDR images. +- %s: Each stack consists of a single SDR image. Gain maps will be copies of SDR images. -By default, the first image in a stack is treated as SDR, and the second one is a gainmap/HDR. +By default, the first image in a stack is treated as SDR, and the second one is a gain map/HDR. You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to it. -]]), _("SDR + gainmap"), _("SDR + HDR"), _("SDR only")), +]]), _("SDR + gain map"), _("SDR + HDR"), _("SDR only")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 @@ -746,7 +746,7 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap GUI.optionwidgets.metadata_box.visible = false end end, - _("SDR + gainmap"), -- ENCODING_VARIANT_SDR_AND_GAINMAP + _("SDR + gain map"), -- ENCODING_VARIANT_SDR_AND_GAINMAP _("SDR + HDR"), -- ENCODING_VARIANT_SDR_AND_HDR _("SDR only") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP } From e4da226661da5ac2c4a35867d351da8cbeef36f7 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 26 Sep 2024 10:45:03 +0200 Subject: [PATCH 136/169] Added gainmap downsampling. --- contrib/ultrahdr.lua | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index abe9da95..261a8862 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -77,7 +77,8 @@ local GUI = { metadata_box = {}, edit_executables_button = {}, executable_path_widget = {}, - quality_widget = {} + quality_widget = {}, + gainmap_downsampling_widget = {}, }, options = {}, run = {} @@ -145,6 +146,7 @@ local function save_preferences() dt.preferences.write(namespace, "hdr_capacity_max", "float", GUI.optionwidgets.hdr_capacity_max.value) end dt.preferences.write(namespace, "quality", "integer", GUI.optionwidgets.quality_widget.value) + dt.preferences.write(namespace, "gainmap_downsampling", "integer", GUI.optionwidgets.gainmap_downsampling_widget.value) end local function default_to(value, default) @@ -177,6 +179,7 @@ local function load_preferences() GUI.optionwidgets.hdr_capacity_max.value = default_to(dt.preferences.read(namespace, "hdr_capacity_max", "float"), 6.0) GUI.optionwidgets.quality_widget.value = default_to(dt.preferences.read(namespace, "quality", "integer"), 95) + GUI.optionwidgets.gainmap_downsampling_widget.value = default_to(dt.preferences.read(namespace, "gainmap_downsampling", "integer"), 0) end -- Changes the combobox selection blindly until a paired config value is set. @@ -221,6 +224,7 @@ local function assert_settings_correct(encoding_variant) hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value }, quality = GUI.optionwidgets.quality_widget.value, + downsample = 2^GUI.optionwidgets.gainmap_downsampling_widget.value, tmpdir = dt.configuration.tmp_dir, skip_cleanup = false -- keep temporary files around, for debugging. } @@ -531,7 +535,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total cmd = settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr_raw) .. " -p " .. df.sanitize_filename(hdr_raw) .. string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, - settings.quality) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. + settings.quality) .. string.format(" -s %d ", settings.downsample) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. " -z " .. df.sanitize_filename(uhdr) if not execute_cmd(cmd, string.format(_("Error merging %s"), uhdr)) then return cleanup(), errors @@ -742,8 +746,10 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap GUI.run.sensitive = self.selected and self.selected > 0 if self.selected == ENCODING_VARIANT_SDR_AND_GAINMAP or self.selected == ENCODING_VARIANT_SDR_AUTO_GAINMAP then GUI.optionwidgets.metadata_box.visible = true + GUI.optionwidgets.gainmap_downsampling_widget.visible = false else GUI.optionwidgets.metadata_box.visible = false + GUI.optionwidgets.gainmap_downsampling_widget.visible = true end end, _("SDR + gain map"), -- ENCODING_VARIANT_SDR_AND_GAINMAP @@ -781,11 +787,26 @@ GUI.optionwidgets.quality_widget = dt.new_widget("slider") { end } +GUI.optionwidgets.gainmap_downsampling_widget = dt.new_widget("slider") { + label = _('gain map downsampling steps'), + tooltip = _('Exponent (2^x) of the gain map downsampling factor.\nDownsampling reduces the file size, at the expense of quality.\n\n0 = don\'t downsample the gain map, 7 = maximum downsampling (128x)'), + hard_min = 0, + hard_max = 7, + soft_min = 0, + soft_max = 7, + step = 1, + digits = 0, + reset_callback = function(self) + self.value = 0 + end +} + GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { orientation = "vertical", GUI.optionwidgets.selection_type_combo, GUI.optionwidgets.encoding_variant_combo, GUI.optionwidgets.quality_widget, + GUI.optionwidgets.gainmap_downsampling_widget, GUI.optionwidgets.metadata_box } From 72ce0b0f937d7d823a597e1a5c888ec61d4dba6c Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 24 Oct 2024 08:13:49 +0200 Subject: [PATCH 137/169] Small fixes --- contrib/ultrahdr.lua | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 261a8862..e17c2a6c 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -484,7 +484,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { bpp = 10, quality = 100, -- lossless - effort = 1 -- we don't care about the size, the faile is temporary. + effort = 1 -- we don't care about the size, the file is temporary. }) if not ok then table.insert(errors, string.format(_("Error exporting %s to %s"), images["hdr"].filename, "jxl")) @@ -527,8 +527,8 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total return cleanup(), errors end -- sanity check for file sizes (sometimes dt exports different size images if the files were never opened in darktable view) - if file_size(sdr_raw) ~= size_in_px * 4 or file_size(hdr_raw) ~= size_in_px & 3 then - table.insert(errors, string.format(_("Wrong raw image dimensions: %s, expected %dx%d. Try opening the image in darktable mode first."), images["sdr"].filename, sdr_w, sdr_h)) + if file_size(sdr_raw) ~= size_in_px * 4 or file_size(hdr_raw) ~= size_in_px * 3 then + table.insert(errors, string.format(_("Wrong raw image resolution: %s, expected %dx%d. Try opening the image in darktable mode first."), images["sdr"].filename, sdr_w, sdr_h)) return cleanup(), errors end update_job_progress() @@ -766,7 +766,7 @@ This determines how the plugin groups images into separate stacks (each stack wi - %s: Group images into stacks, using the source image path + filename (ignoring extension). Use this method if the source images for a given stack are darktable duplicates. -As an added precaution, each image in a stack needs to have the same dimensions. +As an added precaution, each image in a stack needs to have the same resolution. ]]), _("one stack"), _("multiple stacks (use filename)")), selected = 0, _("one stack"), -- SELECTION_TYPE_ONE_STACK @@ -789,7 +789,7 @@ GUI.optionwidgets.quality_widget = dt.new_widget("slider") { GUI.optionwidgets.gainmap_downsampling_widget = dt.new_widget("slider") { label = _('gain map downsampling steps'), - tooltip = _('Exponent (2^x) of the gain map downsampling factor.\nDownsampling reduces the file size, at the expense of quality.\n\n0 = don\'t downsample the gain map, 7 = maximum downsampling (128x)'), + tooltip = _('Exponent (2^x) of the gain map downsampling factor.\nDownsampling reduces the gain map resolution.\n\n0 = don\'t downsample the gain map, 7 = maximum downsampling (128x)'), hard_min = 0, hard_max = 7, soft_min = 0, From 95e2e4924ef2942ad0421403c2f1e682b03a0897 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 25 Nov 2024 17:14:30 +0100 Subject: [PATCH 138/169] Added HDR only encoding variant. - Increase timeouts to improve stability of choosing export profiles. - Added peak nits setting. --- contrib/ultrahdr.lua | 192 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 157 insertions(+), 35 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index e17c2a6c..35d02031 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -79,6 +79,7 @@ local GUI = { executable_path_widget = {}, quality_widget = {}, gainmap_downsampling_widget = {}, + target_display_peak_nits_widget = {} }, options = {}, run = {} @@ -100,6 +101,7 @@ local PS = dt.configuration.running_os == "windows" and "\\" or "/" local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 local ENCODING_VARIANT_SDR_AND_HDR = 2 local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 +local ENCODING_VARIANT_HDR_ONLY = 4 local SELECTION_TYPE_ONE_STACK = 1 local SELECTION_TYPE_GROUP_BY_FNAME = 2 @@ -146,7 +148,11 @@ local function save_preferences() dt.preferences.write(namespace, "hdr_capacity_max", "float", GUI.optionwidgets.hdr_capacity_max.value) end dt.preferences.write(namespace, "quality", "integer", GUI.optionwidgets.quality_widget.value) - dt.preferences.write(namespace, "gainmap_downsampling", "integer", GUI.optionwidgets.gainmap_downsampling_widget.value) + dt.preferences.write(namespace, "gainmap_downsampling", "integer", + GUI.optionwidgets.gainmap_downsampling_widget.value) + dt.preferences.write(namespace, "target_display_peak_nits", "integer", + (GUI.optionwidgets.target_display_peak_nits_widget.value+0.5)//1) + end local function default_to(value, default) @@ -179,7 +185,10 @@ local function load_preferences() GUI.optionwidgets.hdr_capacity_max.value = default_to(dt.preferences.read(namespace, "hdr_capacity_max", "float"), 6.0) GUI.optionwidgets.quality_widget.value = default_to(dt.preferences.read(namespace, "quality", "integer"), 95) - GUI.optionwidgets.gainmap_downsampling_widget.value = default_to(dt.preferences.read(namespace, "gainmap_downsampling", "integer"), 0) + GUI.optionwidgets.target_display_peak_nits_widget.value = default_to( + dt.preferences.read(namespace, "target_display_peak_nits", "integer"), 10000) + GUI.optionwidgets.gainmap_downsampling_widget.value = default_to( + dt.preferences.read(namespace, "gainmap_downsampling", "integer"), 0) end -- Changes the combobox selection blindly until a paired config value is set. @@ -192,11 +201,12 @@ local function set_combobox(path, instance, config_name, new_config_value) end dt.gui.action(path, 0, "selection", "first", 1.0) + dt.control.sleep(50) local limit, i = 30, 0 -- in case there is no matching config value in the first n entries of a combobox. while i < limit do i = i + 1 dt.gui.action(path, 0, "selection", "next", 1.0) - dt.control.sleep(10) + dt.control.sleep(50) if dt.preferences.read("darktable", config_name, "integer") == new_config_value then log.msg(log.debug, string.format(_("Changed %s from %d to %d"), config_name, pref, new_config_value)) return pref @@ -224,7 +234,8 @@ local function assert_settings_correct(encoding_variant) hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value }, quality = GUI.optionwidgets.quality_widget.value, - downsample = 2^GUI.optionwidgets.gainmap_downsampling_widget.value, + target_display_peak_nits = (GUI.optionwidgets.target_display_peak_nits_widget.value+0.5)//1, + downsample = 2 ^ GUI.optionwidgets.gainmap_downsampling_widget.value, tmpdir = dt.configuration.tmp_dir, skip_cleanup = false -- keep temporary files around, for debugging. } @@ -263,23 +274,27 @@ end local function get_stacks(images, encoding_variant, selection_type) local stacks = {} - local extra_image_content_type + local primary = "sdr" + local extra if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP then - extra_image_content_type = "gainmap" + extra = "gainmap" elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then - extra_image_content_type = "hdr" + extra = "hdr" elseif encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then - extra_image_content_type = nil + extra = nil + elseif encoding_variant == ENCODING_VARIANT_HDR_ONLY then + extra = nil + primary = "hdr" end local tags = nil - -- Group images into (sdr [,extra]) stacks - -- Assume that the first encountered image from each stack is an sdr one, unless it has a tag matching the expected extra_image_type, or has the expected extension + -- Group images into (primary [,extra]) stacks + -- Assume that the first encountered image from each stack is a primary 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 extra_image_content_type and tag.name == extra_image_content_type then + if extra and tag.name == extra then is_extra = true end end @@ -296,27 +311,27 @@ local function get_stacks(images, encoding_variant, selection_type) if stacks[key] == nil then stacks[key] = {} end - if extra_image_content_type and (is_extra or stacks[key]["sdr"]) then + if extra and (is_extra or stacks[key][primary]) then -- Don't overwrite existing entries - if not stacks[key][extra_image_content_type] then - stacks[key][extra_image_content_type] = v + if not stacks[key][extra] then + stacks[key][extra] = v end elseif not is_extra then -- Don't overwrite existing entries - if not stacks[key]["sdr"] then - stacks[key]["sdr"] = v + if not stacks[key][primary] then + stacks[key][primary] = v end end end -- remove invalid stacks local count = 0 for k, v in pairs(stacks) do - if extra_image_content_type then - if not v["sdr"] or not v[extra_image_content_type] then + if extra then + if not v[primary] or not v[extra] then stacks[k] = nil else - local sdr_w, sdr_h = get_dimensions(v["sdr"]) - local extra_w, extra_h = get_dimensions(v[extra_image_content_type]) + local sdr_w, sdr_h = get_dimensions(v[primary]) + local extra_w, extra_h = get_dimensions(v[extra]) if (sdr_w ~= extra_w) or (sdr_h ~= extra_h) then stacks[k] = nil end @@ -346,6 +361,7 @@ end local function generate_ultrahdr(encoding_variant, images, settings, step, total_steps) local total_substeps local substep = 0 + local best_source_image local uhdr local errors = {} local remove_files = {} @@ -408,6 +424,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total if encoding_variant == ENCODING_VARIANT_SDR_AND_GAINMAP or encoding_variant == ENCODING_VARIANT_SDR_AUTO_GAINMAP then total_substeps = 5 + best_source_image = images["sdr"] -- Export/copy both SDR and gainmap to JPEGs local sdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["sdr"].filename) .. ".jpg") @@ -456,9 +473,13 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total -- Merge files uhdr = df.chop_filetype(sdr) .. "_ultrahdr.jpg" table.insert(remove_files, uhdr) - cmd = settings.bin.ultrahdr_app .. " -m 0 -i " .. df.sanitize_filename(sdr .. ".noexif") .. " -g " .. - df.sanitize_filename(gainmap) .. " -f " .. df.sanitize_filename(metadata_file) .. " -z " .. - df.sanitize_filename(uhdr) + cmd = settings.bin.ultrahdr_app .. + string.format(" -m 0 -i %s -g %s -L %d -f %s -z %s", df.sanitize_filename(sdr .. ".noexif"), -- -i + df.sanitize_filename(gainmap), -- -g + settings.target_display_peak_nits, -- -L + df.sanitize_filename(metadata_file), -- -f + df.sanitize_filename(uhdr) -- -z + ) if not execute_cmd(cmd, string.format(_("Error merging UltraHDR to %s"), uhdr)) then return cleanup(), errors end @@ -476,6 +497,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total update_job_progress() elseif encoding_variant == ENCODING_VARIANT_SDR_AND_HDR then total_substeps = 6 + best_source_image = images["sdr"] -- https://discuss.pixls.us/t/manual-creation-of-ultrahdr-images/45004/20 -- Step 1: Export HDR to JPEG-XL with DT_COLORSPACE_PQ_P3 local hdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["hdr"].filename) .. @@ -528,15 +550,26 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end -- sanity check for file sizes (sometimes dt exports different size images if the files were never opened in darktable view) if file_size(sdr_raw) ~= size_in_px * 4 or file_size(hdr_raw) ~= size_in_px * 3 then - table.insert(errors, string.format(_("Wrong raw image resolution: %s, expected %dx%d. Try opening the image in darktable mode first."), images["sdr"].filename, sdr_w, sdr_h)) + table.insert(errors, + string.format( + _("Wrong raw image resolution: %s, expected %dx%d. Try opening the image in darktable mode first."), + images["sdr"].filename, sdr_w, sdr_h)) return cleanup(), errors end update_job_progress() - cmd = settings.bin.ultrahdr_app .. " -m 0 -y " .. df.sanitize_filename(sdr_raw) .. " -p " .. - df.sanitize_filename(hdr_raw) .. - string.format(" -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -s 1 -q %d -Q %d -D 1 ", settings.quality, - settings.quality) .. string.format(" -s %d ", settings.downsample) .. " -w " .. tostring(sdr_w - sdr_w % 2) .. " -h " .. tostring(sdr_h - sdr_h % 2) .. - " -z " .. df.sanitize_filename(uhdr) + cmd = settings.bin.ultrahdr_app .. + string.format( + " -m 0 -y %s -p %s -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -q %d -Q %d -L %d -D 1 -s %d -w %d -h %d -z %s", + df.sanitize_filename(sdr_raw), -- -y + df.sanitize_filename(hdr_raw), -- -p + settings.quality, -- -q + settings.quality, -- -Q + settings.target_display_peak_nits, -- -L + settings.downsample, -- -s + sdr_w - sdr_w % 2, -- w + sdr_h - sdr_h % 2, -- h + df.sanitize_filename(uhdr) -- z + ) if not execute_cmd(cmd, string.format(_("Error merging %s"), uhdr)) then return cleanup(), errors end @@ -551,13 +584,82 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end end update_job_progress() + elseif encoding_variant == ENCODING_VARIANT_HDR_ONLY then + total_substeps = 5 + best_source_image = images["hdr"] + -- TODO: Check if exporting to JXL would be ok too. + -- Step 1: Export HDR to JPEG-XL with DT_COLORSPACE_PQ_P3 + local hdr = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["hdr"].filename) .. + ".jxl") + table.insert(remove_files, hdr) + ok = copy_or_export(images["hdr"], hdr, "jpegxl", DT_COLORSPACE_PQ_P3, { + bpp = 10, + quality = 100, -- lossless + effort = 1 -- we don't care about the size, the file is temporary. + }) + if not ok then + table.insert(errors, string.format(_("Error exporting %s to %s"), images["hdr"].filename, "jxl")) + return cleanup(), errors + end + update_job_progress() + -- Step 1: Generate raw HDR image + local hdr_raw = df.create_unique_filename(settings.tmpdir .. PS .. df.chop_filetype(images["hdr"].filename) .. + ".raw") + table.insert(remove_files, hdr_raw) + local hdr_w, hdr_h = get_dimensions(images["hdr"]) + local resize_cmd = "" + if hdr_h % 2 + hdr_w % 2 > 0 then -- needs resizing to even dimensions. + resize_cmd = string.format(" -vf 'crop=%d:%d:0:0' ", hdr_w - hdr_w % 2, hdr_h - hdr_h % 2) + end + local size_in_px = (hdr_w - hdr_w % 2) * (hdr_h - hdr_h % 2) + cmd = settings.bin.ffmpeg .. " -i " .. df.sanitize_filename(hdr) .. resize_cmd .. + " -pix_fmt p010le -f rawvideo " .. df.sanitize_filename(hdr_raw) + if not execute_cmd(cmd, string.format(_("Error generating %s"), hdr_raw)) then + return cleanup(), errors + end + if file_size(hdr_raw) ~= size_in_px * 3 then + table.insert(errors, + string.format( + _("Wrong raw image resolution: %s, expected %dx%d. Try opening the image in darktable mode first."), + images["hdr"].filename, hdr_w, hdr_h)) + return cleanup(), errors + end + update_job_progress() + uhdr = df.chop_filetype(hdr_raw) .. "_ultrahdr.jpg" + table.insert(remove_files, uhdr) + cmd = settings.bin.ultrahdr_app .. + string.format( + " -m 0 -p %s -a 0 -b 3 -c 1 -C 1 -t 2 -M 0 -q %d -Q %d -D 1 -L %d -s %d -w %d -h %d -z %s", + df.sanitize_filename(hdr_raw), -- -p + settings.quality, -- -q + settings.quality, -- -Q + settings.target_display_peak_nits, -- -L + settings.downsample, -- s + hdr_w - hdr_w % 2, -- -w + hdr_h - hdr_h % 2, -- -h + df.sanitize_filename(uhdr) -- -z + ) + if not execute_cmd(cmd, string.format(_("Error merging %s"), uhdr)) then + return cleanup(), errors + end + update_job_progress() + if settings.copy_exif then + -- Restricting tags to EXIF only, to make sure we won't mess up XMP tags (-all>all). + -- This might hapen e.g. when the source files are Adobe gainmap HDRs. + cmd = settings.bin.exiftool .. " -tagsfromfile " .. df.sanitize_filename(hdr) .. " -exif " .. + df.sanitize_filename(uhdr) .. " -overwrite_original -preserve" + if not execute_cmd(cmd, string.format(_("Error adding EXIF to %s"), uhdr)) then + return cleanup(), errors + end + end + update_job_progress() end - local output_dir = settings.use_original_dir and images["sdr"].path or settings.output + local output_dir = settings.use_original_dir and best_source_image.path or settings.output local output_file = df.create_unique_filename(output_dir .. PS .. df.get_filename(uhdr)) ok = df.file_move(uhdr, output_file) if not ok then - table.insert(errors, string.format(_("Error generating UltraHDR for %s"), images["sdr"].filename)) + table.insert(errors, string.format(_("Error generating UltraHDR for %s"), best_source_image.filename)) return cleanup(), errors end if settings.import_to_darktable then @@ -593,7 +695,9 @@ local function main() local stacks, stack_count = get_stacks(dt.gui.selection(), encoding_variant, selection_type) if stack_count == 0 then - dt.print(string.format(_("No image stacks detected.\n\nMake sure that the image pairs have the same widths and heights."), stack_count)) + dt.print(string.format(_( + "No image stacks detected.\n\nMake sure that the image pairs have the same widths and heights."), + stack_count)) return end dt.print(string.format(_("Detected %d image stack(s)"), stack_count)) @@ -737,10 +841,11 @@ This will determine the method used to generate UltraHDR. - %s: SDR image paired with a gain map image. - %s: SDR image paired with an HDR image. - %s: Each stack consists of a single SDR image. Gain maps will be copies of SDR images. +- %s: Each stack consists of a single HDR image. HDR will be tone mapped to SDR. By default, the first image in a stack is treated as SDR, and the second one is a gain map/HDR. You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to it. -]]), _("SDR + gain map"), _("SDR + HDR"), _("SDR only")), +]]), _("SDR + gain map"), _("SDR + HDR"), _("SDR only"), _("HDR only")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 @@ -754,7 +859,8 @@ You can force the image into a specific stack slot by attaching "hdr" / "gainmap end, _("SDR + gain map"), -- ENCODING_VARIANT_SDR_AND_GAINMAP _("SDR + HDR"), -- ENCODING_VARIANT_SDR_AND_HDR - _("SDR only") -- ENCODING_VARIANT_SDR_AUTO_GAINMAP + _("SDR only"), -- ENCODING_VARIANT_SDR_AUTO_GAINMAP + _("HDR only") -- ENCODING_VARIANT_HDR_ONLY } GUI.optionwidgets.selection_type_combo = dt.new_widget("combobox") { @@ -787,9 +893,24 @@ GUI.optionwidgets.quality_widget = dt.new_widget("slider") { end } +GUI.optionwidgets.target_display_peak_nits_widget = dt.new_widget("slider") { + label = _('target display peak brightness (nits)'), + tooltip = _('Peak brightness of target display in nits (defaults to 10000)'), + hard_min = 203, + hard_max = 10000, + soft_min = 1000, + soft_max = 10000, + step = 10, + digits = 0, + reset_callback = function(self) + self.value = 10000 + end +} + GUI.optionwidgets.gainmap_downsampling_widget = dt.new_widget("slider") { label = _('gain map downsampling steps'), - tooltip = _('Exponent (2^x) of the gain map downsampling factor.\nDownsampling reduces the gain map resolution.\n\n0 = don\'t downsample the gain map, 7 = maximum downsampling (128x)'), + tooltip = _( + 'Exponent (2^x) of the gain map downsampling factor.\nDownsampling reduces the gain map resolution.\n\n0 = don\'t downsample the gain map, 7 = maximum downsampling (128x)'), hard_min = 0, hard_max = 7, soft_min = 0, @@ -807,6 +928,7 @@ GUI.optionwidgets.encoding_settings_box = dt.new_widget("box") { GUI.optionwidgets.encoding_variant_combo, GUI.optionwidgets.quality_widget, GUI.optionwidgets.gainmap_downsampling_widget, + GUI.optionwidgets.target_display_peak_nits_widget, GUI.optionwidgets.metadata_box } From a09140c03c475bd9d0deb51645574ddf5172550f Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Mon, 2 Dec 2024 23:40:15 +0100 Subject: [PATCH 139/169] Variable substitution in output directory. Force exporting files, but left the code to reconfigure it. Added more robust way to set export profile. --- contrib/ultrahdr.lua | 100 +++++++++++++++++++++++++++---------------- 1 file changed, 62 insertions(+), 38 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 35d02031..111c521b 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -39,6 +39,7 @@ USAGE ]] local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local ds = require "lib/dtutils.string" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" local dd = require "lib/dtutils.debug" @@ -65,8 +66,9 @@ local GUI = { encoding_settings_box = {}, output_settings_label = {}, output_settings_box = {}, - use_original_directory = {}, - output_directory_widget = {}, + output_filepath_label = {}, + output_filepath_widget = {}, + overwrite_on_conflict = {}, copy_exif = {}, import_to_darktable = {}, min_content_boost = {}, @@ -110,6 +112,12 @@ local SELECTION_TYPE_GROUP_BY_FNAME = 2 local DT_COLORSPACE_PQ_P3 = 24 local DT_COLORSPACE_DISPLAY_P3 = 26 +-- 1-based position of a colorspace in export profile combobox. +local COLORSPACE_TO_GUI_ACTION = { + [DT_COLORSPACE_PQ_P3] = 9, + [DT_COLORSPACE_DISPLAY_P3] = 11 +} + local function generate_metadata_file(settings) local metadata_file_fmt = [[--maxContentBoost %f --minContentBoost %f @@ -135,10 +143,8 @@ end local function save_preferences() dt.preferences.write(namespace, "encoding_variant", "integer", GUI.optionwidgets.encoding_variant_combo.selected) dt.preferences.write(namespace, "selection_type", "integer", GUI.optionwidgets.selection_type_combo.selected) - dt.preferences.write(namespace, "use_original_directory", "bool", GUI.optionwidgets.use_original_directory.value) - if GUI.optionwidgets.output_directory_widget.value then - dt.preferences.write(namespace, "output_directory", "string", GUI.optionwidgets.output_directory_widget.value) - end + dt.preferences.write(namespace, "output_filepath_pattern", "string", GUI.optionwidgets.output_filepath_widget.text) + dt.preferences.write(namespace, "overwrite_on_conflict", "bool", GUI.optionwidgets.overwrite_on_conflict.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) if GUI.optionwidgets.min_content_boost.value then @@ -151,7 +157,7 @@ local function save_preferences() dt.preferences.write(namespace, "gainmap_downsampling", "integer", GUI.optionwidgets.gainmap_downsampling_widget.value) dt.preferences.write(namespace, "target_display_peak_nits", "integer", - (GUI.optionwidgets.target_display_peak_nits_widget.value+0.5)//1) + (GUI.optionwidgets.target_display_peak_nits_widget.value + 0.5) // 1) end @@ -169,11 +175,8 @@ local function load_preferences() GUI.optionwidgets.selection_type_combo.selected = math.max( dt.preferences.read(namespace, "selection_type", "integer"), SELECTION_TYPE_ONE_STACK) - GUI.optionwidgets.output_directory_widget.value = dt.preferences.read(namespace, "output_directory", "string") - GUI.optionwidgets.use_original_directory.value = dt.preferences.read(namespace, "use_original_directory", "bool") - if not GUI.optionwidgets.output_directory_widget.value then - GUI.optionwidgets.use_original_directory.value = true - end + GUI.optionwidgets.output_filepath_widget.text = dt.preferences.read(namespace, "output_filepath_pattern", "string") + GUI.optionwidgets.overwrite_on_conflict.value = dt.preferences.read(namespace, "overwrite_on_conflict", "bool") 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") GUI.optionwidgets.min_content_boost.value = default_to(dt.preferences.read(namespace, "min_content_boost", "float"), @@ -191,10 +194,25 @@ local function load_preferences() dt.preferences.read(namespace, "gainmap_downsampling", "integer"), 0) end +local function set_profile(colorspace) + local set_directly = true + + if set_directly then + -- New method, with hardcoded export profile values. + local old = dt.gui.action("lib/export/profile", 0, "selection", "", "") * -1 + local new = COLORSPACE_TO_GUI_ACTION[colorspace] or colorspace + log.msg(log.debug, string.format("%d %d %d %d", colorspace, new, old, new - old)) + dt.gui.action("lib/export/profile", 0, "selection", "next", new - old) + return old + else + -- Old method, timing-dependent + return set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", colorspace) + end +end + -- Changes the combobox selection blindly until a paired config value is set. -- Workaround for https://github.com/darktable-org/lua-scripts/issues/522 local function set_combobox(path, instance, config_name, new_config_value) - local pref = dt.preferences.read("darktable", config_name, "integer") if pref == new_config_value then return new_config_value @@ -223,8 +241,8 @@ local function assert_settings_correct(encoding_variant) 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, + overwrite_on_conflict = GUI.optionwidgets.overwrite_on_conflict.value, + output_filepath_pattern = GUI.optionwidgets.output_filepath_widget.text, import_to_darktable = GUI.optionwidgets.import_to_darktable.value, copy_exif = GUI.optionwidgets.copy_exif.value, metadata = { @@ -234,16 +252,13 @@ local function assert_settings_correct(encoding_variant) hdr_capacity_max = GUI.optionwidgets.hdr_capacity_max.value }, quality = GUI.optionwidgets.quality_widget.value, - target_display_peak_nits = (GUI.optionwidgets.target_display_peak_nits_widget.value+0.5)//1, + target_display_peak_nits = (GUI.optionwidgets.target_display_peak_nits_widget.value + 0.5) // 1, downsample = 2 ^ GUI.optionwidgets.gainmap_downsampling_widget.value, tmpdir = dt.configuration.tmp_dir, - skip_cleanup = false -- keep temporary files around, for debugging. + skip_cleanup = false, -- keep temporary files around, for debugging. + force_export = true -- if false, will copy source files instead of exporting if the file extension matches the format expectation. } - if not settings.use_original_dir and (not settings.output or not df.check_if_file_exists(settings.output)) then - table.insert(errors, string.format(_("output directory (%s) not found"), settings.output)) - end - for k, v in pairs(settings.bin) do if not v then table.insert(errors, string.format(_("%s binary not found"), k)) @@ -387,10 +402,11 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end function copy_or_export(src_image, dest, format, colorspace, props) - if df.get_filetype(src_image.filename) == df.get_filetype(dest) and not src_image.is_altered then + if not settings.force_export and df.get_filetype(src_image.filename) == df.get_filetype(dest) and + not src_image.is_altered then return df.file_copy(src_image.path .. PS .. src_image.filename, dest) else - local prev = set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", colorspace) + local prev = set_profile(colorspace) if not prev then return false end @@ -405,7 +421,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end if prev then - set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", prev) + set_profile(prev) end return ok end @@ -655,8 +671,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total update_job_progress() end - local output_dir = settings.use_original_dir and best_source_image.path or settings.output - local output_file = df.create_unique_filename(output_dir .. PS .. df.get_filename(uhdr)) + local output_file = ds.substitute(best_source_image, step + 1, settings.output_filepath_pattern) .. ".jpg" + if not settings.overwrite_on_conflict then + output_file = df.create_unique_filename(output_file) + end ok = df.file_move(uhdr, output_file) if not ok then table.insert(errors, string.format(_("Error generating UltraHDR for %s"), best_source_image.filename)) @@ -733,17 +751,20 @@ 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.output_filepath_label = dt.new_widget("label") { + label = _("file path pattern"), + tooltip = ds.get_substitution_tooltip() } -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.output_filepath_widget = dt.new_widget("entry") { + tooltip = ds.get_substitution_tooltip(), + placeholder = _("e.g. $(FILE_FOLDER)/$(FILE_NAME)_ultrahdr") +} + +GUI.optionwidgets.overwrite_on_conflict = dt.new_widget("check_button") { + label = _("overwrite if exists"), + tooltip = _( + "If the output file already exists, overwrite it. If unchecked, a unique filename will be created instead.") } GUI.optionwidgets.import_to_darktable = dt.new_widget("check_button") { @@ -759,8 +780,9 @@ GUI.optionwidgets.copy_exif = dt.new_widget("check_button") { 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.output_filepath_label, + GUI.optionwidgets.output_filepath_widget, + GUI.optionwidgets.overwrite_on_conflict, GUI.optionwidgets.import_to_darktable, GUI.optionwidgets.copy_exif } @@ -845,7 +867,9 @@ This will determine the method used to generate UltraHDR. By default, the first image in a stack is treated as SDR, and the second one is a gain map/HDR. You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to it. -]]), _("SDR + gain map"), _("SDR + HDR"), _("SDR only"), _("HDR only")), + +For HDR source images, apply a log2(203 nits/10000 nits) = -5.62 EV exposure correction +before generating UltraHDR.]]), _("SDR + gain map"), _("SDR + HDR"), _("SDR only"), _("HDR only")), selected = 0, changed_callback = function(self) GUI.run.sensitive = self.selected and self.selected > 0 From 77c405882d9cca8f68163c67eca6b8ebad067922 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Tue, 3 Dec 2024 19:49:54 +0100 Subject: [PATCH 140/169] Added logging code and a workaround for https://github.com/darktable-org/darktable/issues/17528 in 9.4.0. --- .DS_Store | Bin 0 -> 6148 bytes contrib/ultrahdr.lua | 40 ++++++++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..c6f7696114afdb45c3563536fccffc402bfedec9 GIT binary patch literal 6148 zcmeHKJ8DBQ5S&d6F}QJ=Qdh_ggmF&b3k2H)48{-g>0gy|< z4@cjUNdYM!1*Cu!kOCK0pbG4Kap7}yoD`4(*ROzo9~#}U7mkVX>EIA80CC1}80XPT z5Ss^xy>Luqgl0)4Ce^CNu%t8IDz6ugiAjf5^I>(fRfl46JI`;C4(o{;rGOMTSKv06 zORxWr^k4e_bCOn4KnnaT1#Gt7tXF(e)z-=5yw*1Q6Wwz@>290{g+r8MVw7Vpyd2+0 cQsy 0 then return nil, errors end @@ -288,6 +309,7 @@ local function get_dimensions(image) end local function get_stacks(images, encoding_variant, selection_type) + local old_log_level = set_log_level(LOG_LEVEL) local stacks = {} local primary = "sdr" local extra @@ -356,6 +378,7 @@ local function get_stacks(images, encoding_variant, selection_type) count = count + 1 end end + restore_log_level(old_log_level) return stacks, count end @@ -374,6 +397,7 @@ local function file_size(path) end local function generate_ultrahdr(encoding_variant, images, settings, step, total_steps) + local old_log_level = set_log_level(LOG_LEVEL) local total_substeps local substep = 0 local best_source_image @@ -402,6 +426,8 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end function copy_or_export(src_image, dest, format, colorspace, props) + -- Workaround for https://github.com/darktable-org/darktable/issues/17528 + local needs_workaround = dt.configuration.api_version_string == "9.3.0" or dt.configuration.api_version_string == "9.4.0" if not settings.force_export and df.get_filetype(src_image.filename) == df.get_filetype(dest) and not src_image.is_altered then return df.file_copy(src_image.path .. PS .. src_image.filename, dest) @@ -415,11 +441,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total exporter[k] = v end local ok = exporter:write_image(src_image, dest) - if dt.configuration.api_version_string == "9.3.0" then - -- Workaround for https://github.com/darktable-org/darktable/issues/17528 + if needs_workaround then ok = not ok end - + log.msg(log.info, string.format("Exporting %s to %s (format: %s): %s", src_image.filename, dest, format, ok)) if prev then set_profile(prev) end @@ -695,10 +720,12 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total local msg = string.format(_("Generated %s."), df.get_filename(output_file)) log.msg(log.info, msg) dt.print(msg) + restore_log_level(old_log_level) return true, nil end local function main() + local old_log_level = set_log_level(LOG_LEVEL) save_preferences() local selection_type = GUI.optionwidgets.selection_type_combo.selected @@ -741,6 +768,7 @@ local function main() msg = string.format(_("Generated %d UltraHDR image(s)."), count) log.msg(log.info, msg) dt.print(msg) + restore_log_level(old_log_level) end GUI.optionwidgets.settings_label = dt.new_widget("section_label") { From 3bdbfd12bf7f76e7de148ef3c2024cfe8de34cc0 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 5 Dec 2024 00:45:34 +0100 Subject: [PATCH 141/169] Apply write_image workaround only in 9.3.0 --- contrib/ultrahdr.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 29b30d9c..fc696ded 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -427,7 +427,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total function copy_or_export(src_image, dest, format, colorspace, props) -- Workaround for https://github.com/darktable-org/darktable/issues/17528 - local needs_workaround = dt.configuration.api_version_string == "9.3.0" or dt.configuration.api_version_string == "9.4.0" + local needs_workaround = dt.configuration.api_version_string == "9.3.0" if not settings.force_export and df.get_filetype(src_image.filename) == df.get_filetype(dest) and not src_image.is_altered then return df.file_copy(src_image.path .. PS .. src_image.filename, dest) From 1ba488d0735a778ec2f0f5c11ee79fc403001dfe Mon Sep 17 00:00:00 2001 From: Martin Straeten <39386816+MStraeten@users.noreply.github.com> Date: Sun, 15 Dec 2024 19:08:24 +0100 Subject: [PATCH 142/169] Enhanced x-touch.lua for colorequalizer just adapted the colorzone stuff for color equalizer. requires explicitly set the focus via a further switch --- examples/x-touch.lua | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/examples/x-touch.lua b/examples/x-touch.lua index bbe9bbd6..314135e4 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -48,6 +48,7 @@ midi:A-1=iop/colorzones;focus midi:A#-1=iop/toneequal;focus midi:B-1=iop/colorbalancergb;focus midi:C0=iop/channelmixerrgb;focus +midi:C#0=iop/colorequal;focus ]] local dt = require "darktable" @@ -119,6 +120,18 @@ for k = 1,8 do "magenta" } element = e[k] + -- try if colorequalizer module is focused; if so select element of graph + elseif dt.gui.action("iop/colorequal", "focus") ~= 0 then + local e = { "red", + "orange", + "yellow", + "green", + "cyan", + "blue", + "lavender", + "magenta" } + which = "iop/colorequal/graph" + element = e[k] -- if the sigmoid rgb primaries is focused, -- check sliders From 3c67c0c69304bbe229056ce01408750fb0818720 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 21 Dec 2024 10:20:02 +0100 Subject: [PATCH 143/169] Create output directory if it doesn't exist. --- contrib/ultrahdr.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index fc696ded..ef7a61fd 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -700,6 +700,8 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total if not settings.overwrite_on_conflict then output_file = df.create_unique_filename(output_file) end + local output_path = ds.get_path(output_file) + df.mkdir(output_path) ok = df.file_move(uhdr, output_file) if not ok then table.insert(errors, string.format(_("Error generating UltraHDR for %s"), best_source_image.filename)) From 6d16a8778b61aea1196d78ab901a1aff04eb2ca1 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sun, 22 Dec 2024 08:39:38 +0100 Subject: [PATCH 144/169] Removed .DS_Store --- .DS_Store | Bin 6148 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index c6f7696114afdb45c3563536fccffc402bfedec9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKJ8DBQ5S&d6F}QJ=Qdh_ggmF&b3k2H)48{-g>0gy|< z4@cjUNdYM!1*Cu!kOCK0pbG4Kap7}yoD`4(*ROzo9~#}U7mkVX>EIA80CC1}80XPT z5Ss^xy>Luqgl0)4Ce^CNu%t8IDz6ugiAjf5^I>(fRfl46JI`;C4(o{;rGOMTSKv06 zORxWr^k4e_bCOn4KnnaT1#Gt7tXF(e)z-=5yw*1Q6Wwz@>290{g+r8MVw7Vpyd2+0 cQsy Date: Wed, 1 Jan 2025 17:24:30 -0500 Subject: [PATCH 145/169] README.md - updated links to current documentation --- README.md | 132 +++++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/README.md b/README.md index d57c8e68..9a5521bb 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,17 @@ These scripts are written primarily by the darktable developers and maintained b Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[check_for_updates](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable -[copy_paste_metadata](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/copy_paste_metadata)|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images -[delete_long_tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length -[delete_unused_tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images -[enfuse](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR) -[generate_image_txt](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/generate_image_txt)|No|L|Generate txt sidecar files to be overlaid on zoomed images -[image_path_in_ui](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path -[import_filter_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters -[import_filters](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager -[save_selection](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers -[selection_to_pdf](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images +[check_for_updates](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable +[copy_paste_metadata](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata)|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images +[delete_long_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length +[delete_unused_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images +[enfuse](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR) +[generate_image_txt](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt)|No|L|Generate txt sidecar files to be overlaid on zoomed images +[image_path_in_ui](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path +[import_filter_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters +[import_filters](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager +[save_selection](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers +[selection_to_pdf](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images ### Contributed Scripts @@ -34,50 +34,50 @@ These scripts are contributed by users. They are meant to have an "owner", i.e. Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[AutoGrouper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time -[autostyle](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import +[AutoGrouper](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time +[autostyle](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import change_group_leader|Yes|LMW|Change which image leads group -[clear_GPS](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images -[CollectHelper](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection +[clear_GPS](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images +[CollectHelper](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection color_profile_manager|Yes|LMW|Manage darktable input and output color profiles -[copy_attach_detach_tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images -[cr2hdr](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO +[copy_attach_detach_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images +[cr2hdr](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images -[enfuseAdvanced](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images -[exportLUT](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it -[ext_editor](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors -[face_recognition](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition -[fujifilm_dynamic_range](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting -[fujifilm_ratings](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings -[geoJSON_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ... -[geoToolbox](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions -[gimp](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result -[gpx_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data +[enfuseAdvanced](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images +[exportLUT](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it +[ext_editor](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors +[face_recognition](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition +[fujifilm_dynamic_range](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting +[fujifilm_ratings](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings +[geoJSON_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ... +[geoToolbox](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions +[gimp](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result +[gpx_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data harmonic_armature|Yes|LMW|provide a harmonic armature guide hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image -[HDRMerge](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result -[hugin](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result -[image_stack](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects -[image_time](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time +[HDRMerge](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result +[hugin](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result +[image_stack](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects +[image_time](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image -[kml_export](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth -[LabelsToTags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings -[OpenInExplorer](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager -[passport_guide](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool +[kml_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth +[LabelsToTags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings +[OpenInExplorer](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager +[passport_guide](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool -[pdf_slideshow](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow -[photils](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images -[quicktag](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags -[rate_group](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images -[rename_images](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images -[rename-tags](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name -[RL_out_sharp](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm) -[select_non_existing](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images -[select_untagged](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images -[slideshowMusic](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow -[transfer_hierarchy](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy -[video_ffmpeg](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable +[pdf_slideshow](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow +[photils](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images +[quicktag](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags +[rate_group](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images +[rename_images](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images +[rename-tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name +[RL_out_sharp](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm) +[select_non_existing](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images +[select_untagged](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images +[slideshowMusic](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow +[transfer_hierarchy](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy +[video_ffmpeg](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable ### Example Scripts @@ -85,18 +85,18 @@ These scripts provide examples of how to use specific portions of the API. They Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[api_version](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version -[darkroom_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom -[gettextExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation +[api_version](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version +[darkroom_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom +[gettextExample](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls -[hello_world](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts -[lighttable_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering -[moduleExample](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module -[multi_os](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable -[panels_demo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels -[preferenceExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script -[printExamples](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script -[running_os](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system +[hello_world](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts +[lighttable_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering +[moduleExample](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module +[multi_os](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable +[panels_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels +[preferenceExamples](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script +[printExamples](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script +[running_os](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI ### Tools @@ -105,11 +105,11 @@ Tool scripts perform functions relating to the repository, such as generating do Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[executable_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts -[gen_i18n_mo](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po) -[get_lib_manpages](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/get_lib_manpages)|No|LM|Retrieve the library documentation and output it in man page and PDF format -[get_libdoc](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text -[script_manager](https://darktable-org.github.io/luadocs/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts +[executable_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts +[gen_i18n_mo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po) +[get_lib_manpages](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/get_lib_manpages)|No|LM|Retrieve the library documentation and output it in man page and PDF format +[get_libdoc](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text +[script_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts ### Related third-party projects @@ -237,18 +237,18 @@ To update the script repository, open a terminal or command prompt and do the fo ## Documentation -The [Lua Scripts Manual](https://darktable-org.github.io/luadocs/lua.scripts.manual/) provides documentation +The [Lua Scripts Manual](https://docs.darktable-org/lua/stable/lua.scripts.manual/) provides documentation for the scripts transcribed from the header comments. Each script also contains comments and usage instructions in the header comments. -The [Lua Scripts Library API Manual](https://darktable-org.github.io/luadocs/lua.scripts.api.manual/) provides +The [Lua Scripts Library API Manual](https://docs.darktable-org/lua/stable/lua.scripts.api.manual/) provides documentation of the libraries and functions. Lua-script libraries documentation may also be generated using the tools in the tools/ directory. More information about the scripting with Lua can be found in the darktable user manual: [Scripting with Lua](https://darktable-org.github.io/dtdocs/lua/) -The [Lua API Manual](https://darktable-org.github.io/luadocs/lua.api.manual/) provides docuemntation of the +The [Lua API Manual](https://docs.darktable-org/lua/stable/lua.api.manual/) provides docuemntation of the darktable Lua API. ## Troubleshooting From 7b2942ca293409f10d3e83f901749f2aff50648a Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 1 Jan 2025 17:28:03 -0500 Subject: [PATCH 146/169] README.md fixed darktable-org to darktable.org --- README.md | 142 +++++++++++++++++++++++++++--------------------------- 1 file changed, 71 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index 9a5521bb..7a7cf35b 100644 --- a/README.md +++ b/README.md @@ -15,17 +15,17 @@ These scripts are written primarily by the darktable developers and maintained b Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[check_for_updates](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable -[copy_paste_metadata](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata)|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images -[delete_long_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length -[delete_unused_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images -[enfuse](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR) -[generate_image_txt](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt)|No|L|Generate txt sidecar files to be overlaid on zoomed images -[image_path_in_ui](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path -[import_filter_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters -[import_filters](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager -[save_selection](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers -[selection_to_pdf](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images +[check_for_updates](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/check_for_updates)|Yes|LMW|Check for updates to darktable +[copy_paste_metadata](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/copy_paste_metadata)|Yes|LMW|Copy and paste metadata, tags, ratings, and color labels between images +[delete_long_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_long_tags)|Yes|LMW|Delete all tags longer than a specified length +[delete_unused_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/delete_unused_tags)|Yes|LMW|Delete tags that have no associated images +[enfuse](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/enfuse)|No|L|Exposure blend several images (HDR) +[generate_image_txt](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/generate_image_txt)|No|L|Generate txt sidecar files to be overlaid on zoomed images +[image_path_in_ui](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/image_path_in_ui)|Yes|LMW|Plugin to display selected image path +[import_filter_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filter_manager)|Yes|LMW|Manager for import filters +[import_filters](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/import_filters)|No|LMW|Two import filters for use with import_filter_manager +[save_selection](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/save_selection)|Yes|LMW|Provide save and restore from multiple selection buffers +[selection_to_pdf](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection_to_pdf)|No|L|Generate a PDF file from the selected images ### Contributed Scripts @@ -34,50 +34,50 @@ These scripts are contributed by users. They are meant to have an "owner", i.e. Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[AutoGrouper](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time -[autostyle](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import +[AutoGrouper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time +[autostyle](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import change_group_leader|Yes|LMW|Change which image leads group -[clear_GPS](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images -[CollectHelper](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection +[clear_GPS](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images +[CollectHelper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection color_profile_manager|Yes|LMW|Manage darktable input and output color profiles -[copy_attach_detach_tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images -[cr2hdr](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO +[copy_attach_detach_tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/copy_attach_detach_tags)|Yes|LMW|Copy and paste tags from/to images +[cr2hdr](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/cr2hdr)|Yes|L|Process image created with Magic Lantern Dual ISO cycle_group_leader|Yes|LMW|cycle through images of a group making each the group leader in turn dbmaint|Yes|LMW|find and remove database entries for missing film rolls and images -[enfuseAdvanced](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images -[exportLUT](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it -[ext_editor](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors -[face_recognition](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition -[fujifilm_dynamic_range](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting -[fujifilm_ratings](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings -[geoJSON_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ... -[geoToolbox](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions -[gimp](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result -[gpx_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data +[enfuseAdvanced](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/enfuseadvanced)|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images +[exportLUT](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/exportlut)|Yes|LMW|Create a LUT from a style and export it +[ext_editor](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/ext_editor)|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors +[face_recognition](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/face_recognition)|No|LM|Identify and tag images using facial recognition +[fujifilm_dynamic_range](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_dynamic_range)|No|LMW|Correct fujifilm exposure based on exposure bias camera setting +[fujifilm_ratings](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/fujifilm_ratings)|No|LM|Support importing Fujifilm ratings +[geoJSON_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geojson_export)|No|L|Create a geo JSON script with thumbnails for use in ... +[geoToolbox](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/geotoolbox)|No|LMW|A toolbox of geo functions +[gimp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gimp)|No|LMW|Open an image in GIMP for editing and return the result +[gpx_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/gpx_export)|No|LMW|Export a GPX track file from selected images GPS data harmonic_armature|Yes|LMW|provide a harmonic armature guide hif_group_leader|Yes|LMW|change the group leader in a raw+heif image pair to the heif image -[HDRMerge](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result -[hugin](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result -[image_stack](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects -[image_time](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time +[HDRMerge](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hdrmerge)|No|LMW|Combine the selected images into an HDR DNG and return the result +[hugin](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/hugin)|No|LMW|Combine selected images into a panorama and return the result +[image_stack](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_stack)|No|LMW|Combine a stack of images to remove noise or transient objects +[image_time](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/image_time)|Yes|LMW|Adjust the EXIF image time jpg_group_leader|Yes|LMW|change the group leader for a raw+jpg pair to the jpg image -[kml_export](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth -[LabelsToTags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings -[OpenInExplorer](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager -[passport_guide](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool +[kml_export](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/kml_export)|No|L|Export photos with a KML file for usage in Google Earth +[LabelsToTags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/labelstotags)|Yes|LMW|Apply tags based on color labels and ratings +[OpenInExplorer](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/openinexplorer)|No|LMW|Open the selected images in the system file manager +[passport_guide](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/passport_guide)|Yes|LMW|Add passport cropping guide to darkroom crop tool passport_guide_germany|Yes|LMW|Add passport cropping guide for German passports to darkroom crop tool -[pdf_slideshow](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow -[photils](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images -[quicktag](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags -[rate_group](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images -[rename_images](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images -[rename-tags](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name -[RL_out_sharp](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm) -[select_non_existing](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images -[select_untagged](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images -[slideshowMusic](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow -[transfer_hierarchy](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy -[video_ffmpeg](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable +[pdf_slideshow](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/pdf_slideshow)|No|LM|Export images to a PDF slideshow +[photils](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/photils)|No|LM|Automatic tag suggestions for your images +[quicktag](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/quicktag)|Yes|LMW|Create shortcuts for quickly applying tags +[rate_group](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rate_group)|Yes|LMW|Apply or remove a star rating from grouped images +[rename_images](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename_images)|Yes|LMW|Rename single or multiple images +[rename-tags](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rename-tags)|Yes|LMW|Change a tag name +[RL_out_sharp](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/rl_out_sharp)|No|LW|Output sharpening using GMic (Richardson-Lucy algorithm) +[select_non_existing](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_non_existing)|Yes|LMW|Enable selection of non-existing images in the the currently worked on images +[select_untagged](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/select_untagged)|Yes|LMW|Enable selection of untagged images +[slideshowMusic](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/slideshowMusic)|No|L|Play music during a slideshow +[transfer_hierarchy](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/transfer_hierarchy)|Yes|LMW|Image move/copy preserving directory hierarchy +[video_ffmpeg](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/video_ffmpeg)|No|LMW|Export video from darktable ### Example Scripts @@ -85,18 +85,18 @@ These scripts provide examples of how to use specific portions of the API. They Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[api_version](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version -[darkroom_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom -[gettextExample](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation +[api_version](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/api_version)|Yes|LMW|Print the current API version +[darkroom_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/darkroom_demo)|Yes|LMW|Demonstrate changing images in darkoom +[gettextExample](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/gettextexample)|Yes|LM|How to use translation gui_actions|Yes|LMW|demonstrate controlling the GUI using darktable.gui.action calls -[hello_world](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts -[lighttable_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering -[moduleExample](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module -[multi_os](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable -[panels_demo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels -[preferenceExamples](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script -[printExamples](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script -[running_os](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system +[hello_world](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/hello_world)|Yes|LMW|Prints hello world when darktable starts +[lighttable_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/lighttable_demo)|Yes|LMW|Demonstrate controlling lighttable mode, zoom, sorting and filtering +[moduleExample](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/moduleexample)|Yes|LMW|How to create a lighttable module +[multi_os](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/multi_os)|No|LMW|How to create a cross platform script that calls an external executable +[panels_demo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/panels_demo)|Yes|LMW|Demonstrate hiding and showing darktable panels +[preferenceExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/preferenceexamples)|Yes|LMW|How to use preferences in a script +[printExamples](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/printexamples)|Yes|LMW|How to use various print functions from a script +[running_os](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/examples/running_os)|Yes|LMW|Print out the running operating system x-touch|Yes|LMW|demonstrate how to use an x-touch mini MIDI controller to control the darktable GUI ### Tools @@ -105,11 +105,11 @@ Tool scripts perform functions relating to the repository, such as generating do Name|Standalone|OS |Purpose ----|:--------:|:---:|------- -[executable_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts -[gen_i18n_mo](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po) -[get_lib_manpages](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/get_lib_manpages)|No|LM|Retrieve the library documentation and output it in man page and PDF format -[get_libdoc](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text -[script_manager](https://docs.darktable-org/lua/stable/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts +[executable_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/executable_manager)|Yes|LMW|Manage the external executables used by the lua scripts +[gen_i18n_mo](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/gen_i18n_mo)|No|LMW|Generate compiled translation files (.mo) from source files (.po) +[get_lib_manpages](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_lib_manpages)|No|LM|Retrieve the library documentation and output it in man page and PDF format +[get_libdoc](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/get_libdoc)|No|LMW|Retrieve the library documentation and output it as text +[script_manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/script_manager)|No|LMW|Manage (install, update, enable, disable) the lua scripts ### Related third-party projects @@ -142,7 +142,7 @@ The snap version of darktable comes with lua included starting with version 2.4. Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/snap/darktable/current - git clone https://github.com/darktable-org/lua-scripts.git lua + git clone https://github.com/darktable.org/lua-scripts.git lua ### flatpak packages @@ -152,7 +152,7 @@ Flatpak packages now use the internal lua interpreter. Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/.var/app/org.darktable.Darktable/config/darktable - git clone https://github.com/darktable-org/lua-scripts.git lua + git clone https://github.com/darktable.org/lua-scripts.git lua ### appimage packages @@ -163,14 +163,14 @@ These packages run in their own environment and don't have access to a lua inter Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/.config/darktable/ - git clone https://github.com/darktable-org/lua-scripts.git lua + git clone https://github.com/darktable.org/lua-scripts.git lua ### Windows Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run: cd %LOCALAPPDATA%\darktable - git clone https://github.com/darktable-org/lua-scripts.git lua + git clone https://github.com/darktable.org/lua-scripts.git lua If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least once, because the directory is created at the first start of darktable. @@ -237,18 +237,18 @@ To update the script repository, open a terminal or command prompt and do the fo ## Documentation -The [Lua Scripts Manual](https://docs.darktable-org/lua/stable/lua.scripts.manual/) provides documentation +The [Lua Scripts Manual](https://docs.darktable.org/lua/stable/lua.scripts.manual/) provides documentation for the scripts transcribed from the header comments. Each script also contains comments and usage instructions in the header comments. -The [Lua Scripts Library API Manual](https://docs.darktable-org/lua/stable/lua.scripts.api.manual/) provides +The [Lua Scripts Library API Manual](https://docs.darktable.org/lua/stable/lua.scripts.api.manual/) provides documentation of the libraries and functions. Lua-script libraries documentation may also be generated using the tools in the tools/ directory. More information about the scripting with Lua can be found in the darktable user manual: -[Scripting with Lua](https://darktable-org.github.io/dtdocs/lua/) +[Scripting with Lua](https://darktable.org.github.io/dtdocs/lua/) -The [Lua API Manual](https://docs.darktable-org/lua/stable/lua.api.manual/) provides docuemntation of the +The [Lua API Manual](https://docs.darktable.org/lua/stable/lua.api.manual/) provides docuemntation of the darktable Lua API. ## Troubleshooting From 3def8b43d9f66534d0491a96709872d9acec0fa8 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 1 Jan 2025 17:33:20 -0500 Subject: [PATCH 147/169] README.md - fixed git clone links --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7a7cf35b..8caa5c2e 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ The snap version of darktable comes with lua included starting with version 2.4. Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/snap/darktable/current - git clone https://github.com/darktable.org/lua-scripts.git lua + git clone https://github.com/darktable-org/lua-scripts.git lua ### flatpak packages @@ -152,7 +152,7 @@ Flatpak packages now use the internal lua interpreter. Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/.var/app/org.darktable.Darktable/config/darktable - git clone https://github.com/darktable.org/lua-scripts.git lua + git clone https://github.com/darktable-org/lua-scripts.git lua ### appimage packages @@ -163,14 +163,14 @@ These packages run in their own environment and don't have access to a lua inter Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/.config/darktable/ - git clone https://github.com/darktable.org/lua-scripts.git lua + git clone https://github.com/darktable-org/lua-scripts.git lua ### Windows Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run: cd %LOCALAPPDATA%\darktable - git clone https://github.com/darktable.org/lua-scripts.git lua + git clone https://github.com/darktable-org/lua-scripts.git lua If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least once, because the directory is created at the first start of darktable. From 0ab4b0cd6dfac3a49c9f4efc16f8ea411990125e Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Thu, 2 Jan 2025 15:21:59 +0100 Subject: [PATCH 148/169] Added 50ms sleep to the colorspace change. The old profile still seems to be used occasionally without the sleep. --- contrib/ultrahdr.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index ef7a61fd..94df2173 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -120,6 +120,7 @@ local COLORSPACE_TO_GUI_ACTION = { [DT_COLORSPACE_DISPLAY_P3] = 11 } +local UI_SLEEP_MS = 50 -- How many ms to sleep after UI action. local function set_log_level(level) local old_log_level = log.log_level() @@ -221,9 +222,10 @@ local function set_profile(colorspace) local new = COLORSPACE_TO_GUI_ACTION[colorspace] or colorspace log.msg(log.debug, string.format("Changing export profile from %d to %d", old, new)) dt.gui.action("lib/export/profile", 0, "selection", "next", new - old) + dt.control.sleep(UI_SLEEP_MS) return old else - -- Old method, timing-dependent + -- Old method return set_combobox("lib/export/profile", 0, "plugins/lighttable/export/icctype", colorspace) end end @@ -238,12 +240,12 @@ local function set_combobox(path, instance, config_name, new_config_value) end dt.gui.action(path, 0, "selection", "first", 1.0) - dt.control.sleep(50) + dt.control.sleep(UI_SLEEP_MS) local limit, i = 30, 0 -- in case there is no matching config value in the first n entries of a combobox. while i < limit do i = i + 1 dt.gui.action(path, 0, "selection", "next", 1.0) - dt.control.sleep(50) + dt.control.sleep(UI_SLEEP_MS) if dt.preferences.read("darktable", config_name, "integer") == new_config_value then log.msg(log.debug, string.format(_("Changed %s from %d to %d"), config_name, pref, new_config_value)) return pref From 9f3fb161613334f11c7542916997a28fe1818ab2 Mon Sep 17 00:00:00 2001 From: Krzysztof Kotowicz Date: Sat, 4 Jan 2025 00:49:56 +0100 Subject: [PATCH 149/169] Addressed review comments. --- contrib/ultrahdr.lua | 52 +++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/contrib/ultrahdr.lua b/contrib/ultrahdr.lua index 94df2173..e5562324 100644 --- a/contrib/ultrahdr.lua +++ b/contrib/ultrahdr.lua @@ -45,15 +45,13 @@ local dtsys = require "lib/dtutils.system" local dd = require "lib/dtutils.debug" local gettext = dt.gettext.gettext -local namespace = "module_ultrahdr" +local namespace = "ultrahdr" -local LOG_LEVEL = log.info +local LOG_LEVEL = log.info -- works with darktable API version from 4.8.0 on du.check_min_api_version("9.3.0", "ultrahdr") -dt.gettext.bindtextdomain(namespace, dt.configuration.config_dir .. "/lua/locale/") - local function _(msgid) return gettext(msgid) end @@ -96,31 +94,32 @@ flags.module_installed = false -- keep track of whether the module is module_ins local script_data = {} script_data.metadata = { - name = "ultrahdr", + name = _("UltraHDR"), purpose = _("generate UltraHDR images"), author = "Krzysztof Kotowicz" } -local PS = dt.configuration.running_os == "windows" and "\\" or "/" -local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 -local ENCODING_VARIANT_SDR_AND_HDR = 2 -local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 -local ENCODING_VARIANT_HDR_ONLY = 4 +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +local ENCODING_VARIANT_SDR_AND_GAINMAP = 1 +local ENCODING_VARIANT_SDR_AND_HDR = 2 +local ENCODING_VARIANT_SDR_AUTO_GAINMAP = 3 +local ENCODING_VARIANT_HDR_ONLY = 4 -local SELECTION_TYPE_ONE_STACK = 1 -local SELECTION_TYPE_GROUP_BY_FNAME = 2 +local SELECTION_TYPE_ONE_STACK = 1 +local SELECTION_TYPE_GROUP_BY_FNAME = 2 -- Values are defined in darktable/src/common/colorspaces.h -local DT_COLORSPACE_PQ_P3 = 24 -local DT_COLORSPACE_DISPLAY_P3 = 26 +local DT_COLORSPACE_PQ_P3 = 24 +local DT_COLORSPACE_DISPLAY_P3 = 26 -- 1-based position of a colorspace in export profile combobox. -local COLORSPACE_TO_GUI_ACTION = { +local COLORSPACE_TO_GUI_ACTION = { [DT_COLORSPACE_PQ_P3] = 9, [DT_COLORSPACE_DISPLAY_P3] = 11 } -local UI_SLEEP_MS = 50 -- How many ms to sleep after UI action. +local UI_SLEEP_MS = 50 -- How many ms to sleep after UI action. local function set_log_level(level) local old_log_level = log.log_level() @@ -179,7 +178,7 @@ local function save_preferences() end local function default_to(value, default) - if value == 0 then + if value == 0 or value == "" then return default end return value @@ -193,7 +192,8 @@ local function load_preferences() GUI.optionwidgets.selection_type_combo.selected = math.max( dt.preferences.read(namespace, "selection_type", "integer"), SELECTION_TYPE_ONE_STACK) - GUI.optionwidgets.output_filepath_widget.text = dt.preferences.read(namespace, "output_filepath_pattern", "string") + GUI.optionwidgets.output_filepath_widget.text = default_to(dt.preferences.read(namespace, "output_filepath_pattern", "string"), + "$(FILE_FOLDER)/$(FILE_NAME)_ultrahdr") GUI.optionwidgets.overwrite_on_conflict.value = dt.preferences.read(namespace, "overwrite_on_conflict", "bool") 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") @@ -247,11 +247,11 @@ local function set_combobox(path, instance, config_name, new_config_value) dt.gui.action(path, 0, "selection", "next", 1.0) dt.control.sleep(UI_SLEEP_MS) if dt.preferences.read("darktable", config_name, "integer") == new_config_value then - log.msg(log.debug, string.format(_("Changed %s from %d to %d"), config_name, pref, new_config_value)) + log.msg(log.debug, string.format("Changed %s from %d to %d", config_name, pref, new_config_value)) return pref end end - log.msg(log.error, string.format(_("Could not change %s from %d to %d"), config_name, pref, new_config_value)) + log.msg(log.error, string.format("Could not change %s from %d to %d", config_name, pref, new_config_value)) restore_log_level(old_log_level) end @@ -494,7 +494,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total return cleanup(), errors end end - log.msg(log.debug, string.format(_("Exported files: %s, %s"), sdr, gainmap)) + log.msg(log.debug, string.format("Exported files: %s, %s", sdr, gainmap)) update_job_progress() -- Strip EXIFs table.insert(remove_files, sdr .. ".noexif") @@ -721,9 +721,8 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total end cleanup() update_job_progress() - local msg = string.format(_("Generated %s."), df.get_filename(output_file)) - log.msg(log.info, msg) - dt.print(msg) + log.msg(log.info, string.format("Generated %s.", df.get_filename(output_file))) + dt.print(string.format(_("Generated %s."), df.get_filename(output_file))) restore_log_level(old_log_level) return true, nil end @@ -769,9 +768,8 @@ local function main() job.valid = false end - msg = string.format(_("Generated %d UltraHDR image(s)."), count) - log.msg(log.info, msg) - dt.print(msg) + log.msg(log.info, string.format("Generated %d UltraHDR image(s).", count)) + dt.print(string.format(_("Generated %d UltraHDR image(s)."), count)) restore_log_level(old_log_level) end From 1e33ea419266a93858664d0d85daa0aa91ee719d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nahuel=20Jos=C3=A9?= Date: Mon, 6 Jan 2025 18:47:57 -0300 Subject: [PATCH 150/169] fix: add change encoding to bat file for dtutils windows_command --- lib/dtutils/system.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 0df0dee9..f7db561b 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -87,7 +87,13 @@ function dtutils_system.windows_command(command) if file then dt.print_log("opened file") command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell - file:write(command) + file:write("@echo off\n") + file:write('for /f "tokens=2 delims=:." %%x in (\'chcp\') do set cp=%%x\n') + file:write("chcp 65001>nul\n") -- change the encoding of the terminal to handle non-english characters in path + file:write("\n") + file:write(command .. "\n") + file:write("\n") + file:write("chcp %%cp%%>nul\n") file:close() result = dt.control.execute(fname) From 914a17fb4ecf04c88b79fdb723398651fb792e6e Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 6 Jan 2025 22:06:35 -0500 Subject: [PATCH 151/169] contrib/AutoGrouper - create group leader for first image in table. Add grouped images to the group leader and not the previous image. --- contrib/AutoGrouper.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 67609374..889c9af1 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -132,10 +132,11 @@ local function main(on_collection) for i, image in ipairs(images) do if i == 1 then prev_image = image + image:make_group_leader() elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group local curr_image = image if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then - images[i]:group_with(images[i-1]) + images[i]:group_with(images[i-1].group_leader) end prev_image = curr_image end From 4e140f10d156bda264e80f3c4aba53a8399fffb5 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 17 Jan 2025 14:18:19 -0500 Subject: [PATCH 152/169] lib/dtutils/system - fixed Lua escaping in windows_command() --- lib/dtutils/system.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index f7db561b..10ea29f3 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -86,14 +86,13 @@ function dtutils_system.windows_command(command) local file = io.open(fname, "w") if file then dt.print_log("opened file") - command = string.gsub(command, "%%", "%%%%") -- escape % from windows shell file:write("@echo off\n") file:write('for /f "tokens=2 delims=:." %%x in (\'chcp\') do set cp=%%x\n') file:write("chcp 65001>nul\n") -- change the encoding of the terminal to handle non-english characters in path file:write("\n") file:write(command .. "\n") file:write("\n") - file:write("chcp %%cp%%>nul\n") + file:write("chcp %cp%>nul\n") file:close() result = dt.control.execute(fname) From 96457bc5985a61d94db99533cd0f1abac8e4c3e2 Mon Sep 17 00:00:00 2001 From: Martin Straeten <39386816+MStraeten@users.noreply.github.com> Date: Sat, 18 Jan 2025 23:44:48 +0100 Subject: [PATCH 153/169] Update x-touch.lua switch colorequal tab added recommended additional shortcuts to switch between hue, saturation and brightness tabs: midi:D0=iop/colorequal/page;previous midi:D#0=iop/colorequal/page;next --- examples/x-touch.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/examples/x-touch.lua b/examples/x-touch.lua index 314135e4..7da3d54c 100644 --- a/examples/x-touch.lua +++ b/examples/x-touch.lua @@ -49,6 +49,8 @@ midi:A#-1=iop/toneequal;focus midi:B-1=iop/colorbalancergb;focus midi:C0=iop/channelmixerrgb;focus midi:C#0=iop/colorequal;focus +midi:D0=iop/colorequal/page;previous +midi:D#0=iop/colorequal/page;next ]] local dt = require "darktable" From b70fdc0ecf64192be36eb8508d9fcef3b34150c1 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 7 Feb 2025 14:29:45 -0500 Subject: [PATCH 154/169] official/apply_camera_style - Fixed decoding errors caused by #18332. --- official/apply_camera_style.lua | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index aac49d16..b162ffef 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -52,7 +52,9 @@ local log = require "lib/dtutils.log" local MODULE = "apply_camera_style" local DEFAULT_LOG_LEVEL = log.info local TMP_DIR = dt.configuration.tmp_dir -local STYLE_PREFIX = "_l10n_darktable camera styles|" +local STYLE_PREFIX = "_l10n_darktable|_l10n_camera styles|" +local MAKER = 3 +local STYLE = 4 -- path separator local PS = dt.configuration.running_os == "windows" and "\\" or "/" @@ -222,25 +224,25 @@ local function get_camera_styles() log.msg(log.debug, "got " .. style.name) local parts = du.split(style.name, "|") - parts[2] = string.lower(parts[2]) - log.msg(log.debug, "maker is " .. parts[2]) + parts[MAKER] = string.lower(parts[MAKER]) + log.msg(log.debug, "maker is " .. parts[MAKER]) - if not acs.styles[parts[2]] then - acs.styles[parts[2]] = {} - acs.styles[parts[2]]["styles"] = {} - acs.styles[parts[2]]["patterns"] = {} + if not acs.styles[parts[MAKER]] then + acs.styles[parts[MAKER]] = {} + acs.styles[parts[MAKER]]["styles"] = {} + acs.styles[parts[MAKER]]["patterns"] = {} end - if parts[3] then - if not string.match(parts[3], "]") then - table.insert(acs.styles[parts[2]].styles, style) + if parts[STYLE] then + if not string.match(parts[STYLE], "]") then + table.insert(acs.styles[parts[MAKER]].styles, style) local processed_pattern = process_pattern(parts[#parts]) - table.insert(acs.styles[parts[2]].patterns, processed_pattern) + table.insert(acs.styles[parts[MAKER]].patterns, processed_pattern) log.msg(log.debug, "pattern for " .. style.name .. " is " .. processed_pattern) else - local processed_patterns = process_set(parts[3]) + local processed_patterns = process_set(parts[STYLE]) for _, pat in ipairs(processed_patterns) do - table.insert(acs.styles[parts[2]].styles, style) - table.insert(acs.styles[parts[2]].patterns, pat) + table.insert(acs.styles[parts[MAKER]].styles, style) + table.insert(acs.styles[parts[MAKER]].patterns, pat) log.msg(log.debug, "pattern for " .. style.name .. " is " .. pat) end end From 252cbc96f2af34d165e13153a8913e1a4bfeca0a Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 15 Feb 2025 13:55:08 -0500 Subject: [PATCH 155/169] apply_camera_style - made spaces conditional in the pattern match so that pattern names with spaces will match models that don't have spaces --- official/apply_camera_style.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index aac49d16..b748ef95 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -162,6 +162,7 @@ local function process_pattern(pattern) else pattern = string.gsub(pattern, "?", ".") end + pattern = string.gsub(pattern, " ", " ?") -- escape dashes pattern = string.gsub(pattern, "%-", "%%-") -- until we end up with a set, I'll defer set processing, i.e. [...] From 363524fbafd021161bf0a65a828e7e2900e6a26b Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 15 Feb 2025 20:19:29 -0500 Subject: [PATCH 156/169] tools/script_manager - added a start queue for scripts that start at start up to allow for including and using libraries in other than the default path. Scripts are now started after all directories are scanned instead of being started as they were found. --- tools/script_manager.lua | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 573f1825..d1ab17e4 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -178,6 +178,7 @@ sm.log_level = DEFAULT_LOG_LEVEL ]] sm.scripts = {} +sm.start_queue = {} sm.page_status = {} sm.page_status.num_buttons = DEFAULT_BUTTONS_PER_PAGE sm.page_status.buttons_created = 0 @@ -595,6 +596,17 @@ local function deactivate(script) restore_log_level(old_log_level) end +local function start_scripts() + for _, script in ipairs(sm.start_queue) do + activate(script) + end + sm.start_queue = {} +end + +local function queue_script_to_start(script) + table.insert(sm.start_queue, script) +end + local function add_script_name(name, path, folder) local old_log_level = set_log_level(sm.log_level) @@ -614,7 +626,7 @@ local function add_script_name(name, path, folder) table.insert(sm.scripts[folder], script) if pref_read(script.script_name, "bool") then - activate(script) + queue_script_to_start(script) else pref_write(script.script_name, "bool", false) end @@ -821,6 +833,7 @@ local function scan_repositories() end end + start_scripts() update_script_update_choices() restore_log_level(old_log_level) From 067fe29f7655d150a4f3255a3879d93e54256f32 Mon Sep 17 00:00:00 2001 From: Georg Lutz Date: Sun, 23 Feb 2025 12:23:37 +0100 Subject: [PATCH 157/169] Fix version comparison Currently this is a textual string comparison which will break with 10.0.0 API version. --- lib/dtutils.lua | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index 6721eac9..87e99cc8 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -61,7 +61,7 @@ dtutils.libdoc.functions["check_min_api_version"] = { function dtutils.check_min_api_version(min_api, script_name) local current_api = dt.configuration.api_version_string - if min_api > current_api then + if dtutils.compare_versions(min_api, current_api) > 0 then dt.print_error("This application is written for lua api version " .. min_api .. " or later.") dt.print_error("The current lua api version is " .. current_api) dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.") @@ -96,7 +96,7 @@ dtutils.libdoc.functions["check_max_api_version"] = { function dtutils.check_max_api_version(max_api, script_name) local current_api = dt.configuration.api_version_string - if current_api > max_api then + if dtutils.compare_versions(current_api, max_api) > 0 then dt.print_error("This application is written for lua api version " .. max_api .. " or earlier.") dt.print_error("The current lua api version is " .. current_api) dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. max_api .. " or earlier required.") @@ -427,4 +427,41 @@ function dtutils.gen_uuid(case) return uuid end +dtutils.libdoc.functions["compare_versions"] = { + Name = [[compare_versions]], + Synopsis = [[compare two version strings]], + Usage = [[local du = require "lib/dtutils" + + local result = du.compare_versions(version1, version2) + version1 - string - the first version string to compare (example: "5.0.0") + version2 - string - the second version string to compare (example: "5.1.0")]], + Description = [[compare_versions compares two version strings and returns 1 if version1 is greater, + -1 if version2 is greater, and 0 if they are equal.]], + Return_Value = [[result - 1 if version1 is greater, -1 if version2 is greater, 0 if they are equal.]], + Limitations = [[]], + Example = [[compare_versions("5.0.0", "5.1.0") returns -1]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils.compare_versions(version1, version2) + local v1 = {} + for num in version1:gmatch("%d+") do table.insert(v1, tonumber(num)) end + local v2 = {} + for num in version2:gmatch("%d+") do table.insert(v2, tonumber(num)) end + + for i = 1, math.max(#v1, #v2) do + local num1 = v1[i] or 0 + local num2 = v2[i] or 0 + if num1 > num2 then + return 1 + elseif num1 < num2 then + return -1 + end + end + return 0 +end + return dtutils From 2624338100c0b42298b7809c718f8c6233296c40 Mon Sep 17 00:00:00 2001 From: Georg Lutz Date: Sun, 23 Feb 2025 18:29:34 +0100 Subject: [PATCH 158/169] Rename compare_versions to compare_api_versions --- lib/dtutils.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index 87e99cc8..e6595865 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -61,7 +61,7 @@ dtutils.libdoc.functions["check_min_api_version"] = { function dtutils.check_min_api_version(min_api, script_name) local current_api = dt.configuration.api_version_string - if dtutils.compare_versions(min_api, current_api) > 0 then + if dtutils.compare_api_versions(min_api, current_api) > 0 then dt.print_error("This application is written for lua api version " .. min_api .. " or later.") dt.print_error("The current lua api version is " .. current_api) dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.") @@ -96,7 +96,7 @@ dtutils.libdoc.functions["check_max_api_version"] = { function dtutils.check_max_api_version(max_api, script_name) local current_api = dt.configuration.api_version_string - if dtutils.compare_versions(current_api, max_api) > 0 then + if dtutils.compare_api_versions(current_api, max_api) > 0 then dt.print_error("This application is written for lua api version " .. max_api .. " or earlier.") dt.print_error("The current lua api version is " .. current_api) dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. max_api .. " or earlier required.") @@ -427,26 +427,26 @@ function dtutils.gen_uuid(case) return uuid end -dtutils.libdoc.functions["compare_versions"] = { - Name = [[compare_versions]], - Synopsis = [[compare two version strings]], +dtutils.libdoc.functions["compare_api_versions"] = { + Name = [[compare_api_versions]], + Synopsis = [[compare two API version strings]], Usage = [[local du = require "lib/dtutils" - local result = du.compare_versions(version1, version2) + local result = du.compare_api_versions(version1, version2) version1 - string - the first version string to compare (example: "5.0.0") version2 - string - the second version string to compare (example: "5.1.0")]], - Description = [[compare_versions compares two version strings and returns 1 if version1 is greater, + Description = [[compare_api_versions compares two version strings and returns 1 if version1 is greater, -1 if version2 is greater, and 0 if they are equal.]], Return_Value = [[result - 1 if version1 is greater, -1 if version2 is greater, 0 if they are equal.]], Limitations = [[]], - Example = [[compare_versions("5.0.0", "5.1.0") returns -1]], + Example = [[compare_api_versions("5.0.0", "5.1.0") returns -1]], See_Also = [[]], Reference = [[]], License = [[]], Copyright = [[]], } -function dtutils.compare_versions(version1, version2) +function dtutils.compare_api_versions(version1, version2) local v1 = {} for num in version1:gmatch("%d+") do table.insert(v1, tonumber(num)) end local v2 = {} From cddb86ded0257231a7bbf208340493a0a9a45d3e Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Fri, 28 Feb 2025 15:11:15 -0500 Subject: [PATCH 159/169] contrib/autostyle - fixed pattern match for tag result to handle spaces in the pattern --- contrib/autostyle.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index a5feda3f..32add29b 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -106,7 +106,7 @@ local function autostyle_apply_one_image (image) if pref and string.len(pref) >= 6 then -- We need the tag, the value and the style_name provided from the configuration string - local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*(%g+)%s*=>%s*(%g+)") + local tag, value, style_name = string.match(pref, "(%g+)%s*=%s*([%g ]-)%s*=>%s*(%g+)") -- check they all exist (correct syntax) if (not tag) then From 9bf7efe71f57a0a141e836ea33e5b81cfd37c74f Mon Sep 17 00:00:00 2001 From: Georg Lutz Date: Sat, 1 Mar 2025 08:46:00 +0100 Subject: [PATCH 160/169] Fix dbmaint.lua "log.log_msg" triggers a lua function error, because that function does not exist. Additionally add add set_log_level / restore_log_level to improve compatibility with other files. --- contrib/dbmaint.lua | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/contrib/dbmaint.lua b/contrib/dbmaint.lua index 16ca9b91..c08309a2 100644 --- a/contrib/dbmaint.lua +++ b/contrib/dbmaint.lua @@ -115,6 +115,20 @@ local namespace = dbmaint -- F U N C T I O N S -- - - - - - - - - - - - - - - - - - - - - - - - +------------------- +-- helper functions +------------------- + +local function set_log_level(level) + local old_log_level = log.log_level() + log.log_level(level) + return old_log_level +end + +local function restore_log_level(level) + log.log_level(level) +end + local function scan_film_rolls() local missing_films = {} @@ -128,19 +142,21 @@ local function scan_film_rolls() end local function scan_images(film) + local old_log_level = set_log_level(DEFAULT_LOG_LEVEL) local missing_images = {} if film then for i = 1, #film do local image = film[i] - log.log_msg(log.debug, "checking " .. image.filename) + log.msg(log.debug, "checking " .. image.filename) if not df.check_if_file_exists(image.path .. PS .. image.filename) then - log.log_msg(log.info, image.filename .. " not found") + log.msg(log.info, image.filename .. " not found") table.insert(missing_images, image) end end end + restore_log_level(old_log_level) return missing_images end @@ -207,6 +223,8 @@ dbmaint.scan_button = dt.new_widget("button"){ clicked_callback = function(this) local found = nil local found_text = "" + local old_log_level = set_log_level(DEFAULT_LOG_LEVEL) + log.msg(log.debug, "Button clicked") if dbmaint.chooser.selected == 1 then -- film rolls found = scan_film_rolls() if #found > 0 then @@ -216,7 +234,7 @@ dbmaint.scan_button = dt.new_widget("button"){ end end else - log.log_msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files") + log.msg(log.debug, "checking path " .. dt.collection[1].path .. " for missing files") found = scan_images(dt.collection[1].film) if #found > 0 then for _, image in ipairs(found) do @@ -225,10 +243,14 @@ dbmaint.scan_button = dt.new_widget("button"){ end end if #found > 0 then + log.msg(log.debug, "found " .. #found .. " missing items") dbmaint.list_widget.text = found_text dbmaint.found = found dbmaint.remove_button.sensitive = true + else + log.msg(log.debug, "no missing items found") end + restore_log_level(old_log_level) end, reset_callback = function(this) dbmaint.found = nil From 59b5039579b08189d1aa8bc1f9b638767b3a2761 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sat, 8 Mar 2025 15:46:55 -0500 Subject: [PATCH 161/169] tools/script_manager - Moved start_scripts() later in the script to ensure the UI is populated before starting the scripts. Added a check to see if the script is started and if so the power button is updated to show the script is running. --- tools/script_manager.lua | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/script_manager.lua b/tools/script_manager.lua index d1ab17e4..becb3d05 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -599,6 +599,13 @@ end local function start_scripts() for _, script in ipairs(sm.start_queue) do activate(script) + for i = 1, sm.page_status.num_buttons do + local name = script.metadata and script.metadata.name or script.name + if sm.widgets.labels[i].label == name then + sm.widgets.buttons[i].name = "pb_on" + break + end + end end sm.start_queue = {} end @@ -833,7 +840,6 @@ local function scan_repositories() end end - start_scripts() update_script_update_choices() restore_log_level(old_log_level) @@ -1224,6 +1230,7 @@ local function install_module() log.msg(log.debug, "set run to true, loading preferences") load_preferences() scan_repositories() + start_scripts() restore_log_level(old_log_level) end From ea2596ae137f4f0d01b233d97d5cf4faab1cda41 Mon Sep 17 00:00:00 2001 From: Yuri van der Burg Date: Tue, 18 Mar 2025 20:38:21 +0100 Subject: [PATCH 162/169] Fixed typo in hugin script - should resolve issue # 570 --- contrib/hugin.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 85fe5f85..46fbd90c 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -83,7 +83,7 @@ end local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) - dt.print(string.format(_("exporting to hugin: %d / $d"), number, total)) + dt.print(string.format(_("exporting to hugin: %d / %d"), number, total)) end local function create_panorama(storage, image_table, extra_data) --finalize From f20721373ae715fd9bd7e226523f445b3b47d2ff Mon Sep 17 00:00:00 2001 From: fjb2020 <92430217+fjb2020@users.noreply.github.com> Date: Wed, 19 Mar 2025 15:57:10 +0000 Subject: [PATCH 163/169] Fix issue #571, use of for _,image in ipairs(sel_images) do conflicts with function _(msgid) --- contrib/geoToolbox.lua | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 0fa07301..01a14eea 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -167,7 +167,7 @@ local function get_first_coordinate() first_elevation = '' first_image_date = 0 - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if not image then first_have_data = false else @@ -206,7 +206,7 @@ local function get_second_coordinate() second_elevation = '' second_image_date = 0 - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if not image then second_have_data = false else @@ -242,7 +242,7 @@ local calc_in_between_slider = dt.new_widget("slider") --ToDo: this needs more love local function calc_in_between() local sel_images = dt.gui.action_images - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if image then image_date = make_time_stamp(image.exif_datetime_taken) if (first_have_data and second_have_data) then @@ -289,7 +289,7 @@ local function copy_gps() copy_gps_longitude = '' copy_gps_elevation = '' - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if not image then copy_gps_have_data = false else @@ -316,7 +316,7 @@ end local function paste_gps(image) local sel_images = dt.gui.action_images - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if (label_copy_gps_lat.value) then image.latitude = copy_gps_latitude end @@ -343,7 +343,7 @@ local function open_location_in_gnome_maps() local i = 0; -- Use the first image with geo information - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -386,7 +386,7 @@ local function reverse_geocode() local i = 0; -- Use the first image with geo information - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -461,7 +461,7 @@ local function calc_distance() local sel_images = dt.gui.selection() - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -545,7 +545,7 @@ local function altitude_profile() local elevationAdd = 0; local sel_images = dt.gui.action_images - for _,image in ipairs(sel_images) do + for jj,image in ipairs(sel_images) do if ((not isnan(image.longitude) and not isnan(image.latitude) and not isnan(image.elevation) and image.elevation) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then From 87d18549a6cc1a91d743a270c4074e7f61463bb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Milo=C5=A1=20Komar=C4=8Devi=C4=87?= <4973094+kmilos@users.noreply.github.com> Date: Tue, 1 Apr 2025 16:11:56 +0200 Subject: [PATCH 164/169] Fix de_DE translation charset --- locale/de_DE/LC_MESSAGES/clear_GPS.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locale/de_DE/LC_MESSAGES/clear_GPS.po b/locale/de_DE/LC_MESSAGES/clear_GPS.po index ad8960b0..420ec484 100644 --- a/locale/de_DE/LC_MESSAGES/clear_GPS.po +++ b/locale/de_DE/LC_MESSAGES/clear_GPS.po @@ -14,7 +14,7 @@ msgstr "" "Language-Team: LANGUAGE \n" "Language: \n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" +"Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "X-Generator: Poedit 1.5.4\n" "Language: de_DE\n" From c6eee15d759754799ace381c60e67f29a15a1f5b Mon Sep 17 00:00:00 2001 From: Tasos Bakogiannis Date: Wed, 9 Apr 2025 23:48:48 +0200 Subject: [PATCH 165/169] Align sequence tooltip with code. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 7648f6f6..e92aff45 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -877,7 +877,7 @@ function dtutils_string.get_substitution_tooltip() _("$(VERSION.NAME) - version name from metadata"), _("$(DARKTABLE.VERSION) - current darktable version"), -- _("$(DARKTABLE.NAME) - darktable name"), -- not implemented - _("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number"), + _("$(SEQUENCE[m,n]) - sequence number, m: start number, n: number of digits"), _("$(WIDTH.SENSOR) - image sensor width"), _("$(HEIGHT.SENSOR) - image sensor height"), _("$(WIDTH.RAW) - RAW image width"), From 5309ff1726b23105298c491635f4429168937402 Mon Sep 17 00:00:00 2001 From: Tasos Bakogiannis Date: Thu, 10 Apr 2025 19:25:01 +0200 Subject: [PATCH 166/169] Revert "Align sequence tooltip with code." This reverts commit c6eee15d759754799ace381c60e67f29a15a1f5b. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index e92aff45..7648f6f6 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -877,7 +877,7 @@ function dtutils_string.get_substitution_tooltip() _("$(VERSION.NAME) - version name from metadata"), _("$(DARKTABLE.VERSION) - current darktable version"), -- _("$(DARKTABLE.NAME) - darktable name"), -- not implemented - _("$(SEQUENCE[m,n]) - sequence number, m: start number, n: number of digits"), + _("$(SEQUENCE[n,m]) - sequence number, n: number of digits, m: start number"), _("$(WIDTH.SENSOR) - image sensor width"), _("$(HEIGHT.SENSOR) - image sensor height"), _("$(WIDTH.RAW) - RAW image width"), From 0cb9a8872e276487568411e47714b866396fd9d7 Mon Sep 17 00:00:00 2001 From: Tasos Bakogiannis Date: Thu, 10 Apr 2025 19:26:45 +0200 Subject: [PATCH 167/169] Update sequence variable order. --- lib/dtutils/string.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 7648f6f6..48221f19 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -994,7 +994,7 @@ local function treat(var_string) log.msg(log.info, "ret_val is " .. ret_val) elseif string.match(var_string, "SEQUENCE%[") then - local start, width = string.match(var_string, "(%d+),(%d)") + local width, start = string.match(var_string, "(%d+),(%d)") local seq_val = tonumber(substitutes[var]) local pat = "%0" .. width .. "d" substitutes[var_string] = string.format(pat, start + (seq_val - 1)) From 980fe3962c64592c3b5a8d3385b8c04065fa2262 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 1 May 2025 20:37:24 -0400 Subject: [PATCH 168/169] image_time - added default values of 0 for fields not supplied in set time so that an exiftime can be created.\ --- contrib/image_time.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/image_time.lua b/contrib/image_time.lua index 28570c1a..c0971306 100644 --- a/contrib/image_time.lua +++ b/contrib/image_time.lua @@ -154,12 +154,12 @@ local function systime2exiftime(systime) end local function vars2exiftime(year, month, day, hour, min, sec) - local y = tonumber(year) and string.format("%4d", year) or " " - local mo = tonumber(month) and string.format("%02d", month) or " " - local d = tonumber(day) and string.format("%02d", day) or " " - local h = tonumber(hour) and string.format("%02d", hour) or " " - local m = tonumber(min) and string.format("%02d", min) or " " - local s = tonumber(sec) and string.format("%02d", sec) or " " + local y = tonumber(year) and string.format("%4d", year) or "0000" + local mo = tonumber(month) and string.format("%02d", month) or "00" + local d = tonumber(day) and string.format("%02d", day) or "00" + local h = tonumber(hour) and string.format("%02d", hour) or "00" + local m = tonumber(min) and string.format("%02d", min) or "00" + local s = tonumber(sec) and string.format("%02d", sec) or "00" return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s) end From b9cc0dccce648887ae2b54ee5ad0f00fcd1c9f91 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 1 Jun 2025 16:36:52 -0400 Subject: [PATCH 169/169] official/apply_camera_style - made spaces in camera name conditional matches as some are squashed and others (Olympus) are not. --- official/apply_camera_style.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/official/apply_camera_style.lua b/official/apply_camera_style.lua index b162ffef..c82a91b3 100644 --- a/official/apply_camera_style.lua +++ b/official/apply_camera_style.lua @@ -166,6 +166,8 @@ local function process_pattern(pattern) end -- escape dashes pattern = string.gsub(pattern, "%-", "%%-") + -- make spaces optional + pattern = string.gsub(pattern, " ", " ?") -- until we end up with a set, I'll defer set processing, i.e. [...] -- anchor the pattern to ensure we don't short match pattern = "^" .. pattern .. "$"