From e9f18a3d36fef3032c569758dccb1c9ab3a4ad41 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 2 Dec 2025 18:34:18 -0500 Subject: [PATCH 1/6] lib/dtutils/string - add IMAGE.ID and IMAGE.ID.NEXT variables --- lib/dtutils/string.lua | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index d4ab849..b6195f4 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -542,7 +542,9 @@ local PLACEHOLDERS = {"ROLL.NAME", "FILE.FOLDER", "FILE.NAME", "FILE.EXTENSION", - "ID", + "ID", + "IMAGE.ID", + "IMAGE.ID.NEXT", "VERSION", "VERSION.IF.MULTI", "VERSION.NAME", @@ -768,6 +770,8 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, dtutils_string.get_basename(image.filename),-- FILE.NAME dtutils_string.get_filetype(image.filename),-- FILE.EXTENSION image.id, -- ID + image.id, -- IMAGE.ID + dt.database[#dt.database].id + 1, -- IMAGE.ID.NEXT image.duplicate_index, -- VERSION version_multi, -- VERSION.IF_MULTI image.version_name and image.version_name or "", -- VERSION.NAME @@ -889,6 +893,8 @@ function dtutils_string.get_substitution_tooltip() _("$(FILE.NAME) - basename of the input image"), _("$(FILE.EXTENSION) - extension of the input image"), _("$(ID) - image ID"), + _("$(IMAGE.ID) - image ID"), + _("$(IMAGE.ID.NEXT) - next image ID to be assigned on import") _("$(VERSION) - duplicate version"), _("$(VERSION.IF_MULTI) - same as $(VERSION) but null string if only one version exists"), _("$(VERSION.NAME) - version name from metadata"), From 8f6cc850fb4eb2ab806baaaf29f7c9f5cdc481a2 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 2 Dec 2025 18:57:05 -0500 Subject: [PATCH 2/6] lib/dtutils/string - Added Xmp.darktable.version and Xmp.dc.[title, description,publisher,rights,creator] --- lib/dtutils/string.lua | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index b6195f4..a26bae5 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -549,6 +549,7 @@ local PLACEHOLDERS = {"ROLL.NAME", "VERSION.IF.MULTI", "VERSION.NAME", "DARKTABLE.VERSION", + "Xmp.darktable.version", "DARKTABLE.NAME", -- Not Implemented "SEQUENCE", "WIDTH.SENSOR", @@ -614,10 +615,15 @@ local PLACEHOLDERS = {"ROLL.NAME", "LABELS", "LABELS.ICONS", -- Not Implemented "TITLE", + "Xmp.dc.title", "DESCRIPTION", + "Xmp.dc.description", "CREATOR", + "Xmp.dc.creator", "PUBLISHER", + "Xmp.dc.publisher", "RIGHTS", + "Xmp.dc.rights", "TAGS", -- Not Implemented "SIDECAR.TXT", -- Not Implemented "FOLDER.PICTURES", @@ -776,6 +782,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, version_multi, -- VERSION.IF_MULTI image.version_name and image.version_name or "", -- VERSION.NAME dt.configuration.version, -- DARKTABLE.VERSION + dt.configuration.version, -- Xmp.darktable.version "", -- DARKTABLE.NAME string.format("%04d", sequence), -- SEQUENCE image.width, -- WIDTH.SENSOR @@ -841,10 +848,15 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, labels, -- LABELS "", -- LABELS.ICONS - wont be implemented image.title and image.title or "", -- TITLE + image.title and image.title or "", -- Xmp.dc.title image.description and image.description or "", -- DESCRIPTION + image.description and image.description or "", -- Xmp.dc.description image.creator and image.creator or "", -- CREATOR + image.creator and image.creator or "", -- Xmp.dc.creator image.publisher and image.publisher or "", -- PUBLISHER + image.publisher and image.publisher or "", -- Xmp.dc.publisher image.rights and image.rights or "", -- RIGHTS + image.rights and image.rights or "", -- Xmp.dc.rights "", -- TAGS - wont be implemented "", -- SIDECAR.TXT - wont be implemented pictures_folder, -- FOLDER.PICTURES @@ -894,11 +906,12 @@ function dtutils_string.get_substitution_tooltip() _("$(FILE.EXTENSION) - extension of the input image"), _("$(ID) - image ID"), _("$(IMAGE.ID) - image ID"), - _("$(IMAGE.ID.NEXT) - next image ID to be assigned on import") + _("$(IMAGE.ID.NEXT) - next image ID to be assigned on import"), _("$(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"), + _("$(Xmp.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"), @@ -963,10 +976,15 @@ function dtutils_string.get_substitution_tooltip() _("$(LABELS) - color labels as text"), -- _("$(LABELS.ICONS) - color labels as icons"),-- not implemented _("$(TITLE) - title from metadata"), + _("$(Xmp.dc.title) - title from metadata"), _("$(DESCRIPTION) - description from metadata"), + _("$(Xmp.dc.description) - description from metadata"), _("$(CREATOR) - creator from metadata"), + _("$(Xmp.dc.creator) - creator from metadata"), _("$(PUBLISHER) - publisher from metadata"), + _("$(Xmp.dc.publisher) - publisher from metadata"), _("$(RIGHTS) - rights from metadata"), + _("$(Xmp.dc.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"), From d54a9f8b7ef449c31282d2139aefa52b2d22200e Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 2 Dec 2025 19:54:10 -0500 Subject: [PATCH 3/6] lib/dtutils/string - implemented variable substitution for ROLL.NAME[n] --- lib/dtutils/string.lua | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index a26bae5..6e0e7b2 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -660,6 +660,37 @@ local function get_colorlabels(image) return labels end +-- handle the ROLL.NAME[n] case + +local function build_rollname_substitution_list(image, variable_string) + local old_log_level = log.log_level() + log.log_level(dtutils_string.log_level) + + for match in string.gmatch(variable_string, "%$%(.-%)?%)") do -- grab each complete variable + log.msg(log.info, "match is " .. match) + + local var = string.match(match, "%$%((.-%)?)%)") -- strip of the leading $( and trailing ) + log.msg(log.info, "var is " .. var) + + if string.match(var, "ROLL.NAME") then + local element = "1" + + if string.match(var, "ROLL.NAME%[") then + element = string.match(var, "%[(%d)%]") + end + + local path = du.split(image.film.path, dt.configuration.running_os ~= "windows" and "/" or "\\") + if element > #path then + substitutes["ROLL.NAME"] = "" + log.msg(log.warn, "ROLL.NAME element requested was more than number of elements in film roll") + else + substitutes["ROLL.NAME"] = path[(#path + 1) - tonumber(element)] + end + return + end + end +end + -- find the $CATEGORYn and $CATEGORY[n,m] requests and add them to the substitute list local function build_category_substitution_list(image, variable_string) @@ -878,6 +909,7 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string, -- do category substitutions separately build_category_substitution_list(image, variable_string) + build_rollname_substitution_list(image, variable_string) log.log_level(old_log_level) end From 3856ae3449f02c933dbd14dc5dcdddcfe1a8f30d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 2 Dec 2025 20:40:03 -0500 Subject: [PATCH 4/6] lib/dtutils/string - added format_exposure function to take an exif_exposure and return it as an exposure string (exif_exposure = 500, string 1/500) --- lib/dtutils/string.lua | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 6e0e7b2..1ea3e33 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -1299,6 +1299,49 @@ function dtutils_string.substitute(image, sequence, variable_string, username, p return str end +dtutils_string.libdoc.functions["format_exposure"] = { + Name = [[format_exposure]], + Synopsis = [[format exif_exposure from dt_lua_image_t to an exposure time]], + Usage = [[local ds = require "lib/dtutils.string" + ds.format_exposure(image.exif_exposure) + image.exif_exposure - dt_lua_image_t exif exposure field + Description = [[format_exposure takes the EXIF exposure field and formats it into an exposure string]], + Return_Value = [[result - string - the EXIF exposure formatted as an exposure string]], + Limitations = [[]], + Example = [[]], + See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} +local function _nearbyintf(v) + return math.floor(v + .5) +end + +function dtutils_string.format_exposure(exif_exposure) + result = nil + + if exif_exposure > 1.0 then + if _nearbyintf(exif_exposure) == exif_exposure then + result = string.format("%.0f″", exif_exposure) + else + result = string.format("%.1f″", exif_exposure) + end + -- catch everything below 0.3 seconds + elseif exif_exposure < 0.2 then + result = string.format("1/%.0f", 1.0 / exif_exposure) + -- catch 1/2, 1/3, ... + elseif _nearbyintf(1.0 / exif_exposure) == (1.0 / exif_exposure) then + result = string.format("1/%.0f", 1.0 / exif_exposure) + -- catch 1/1.3, 1/1.6, ... + elseif (10 * _nearbyintf(10.0 / exif_exposure)) == _nearbyintf(100.0 / exif_exposure) then + result = string.format("1/%.1f", 1.0 / exif_exposure) + else + result = string.format("%.1f″", exif_exposure) + end + + return result +end return dtutils_string From 3e430ee2be3e5e63c01eb06a020b492dfa2d0f2b Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 3 Dec 2025 18:26:13 -0500 Subject: [PATCH 5/6] lib/dtutils/string - converted element from string to number for use in test --- lib/dtutils/string.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 1ea3e33..122f6d2 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -680,7 +680,8 @@ local function build_rollname_substitution_list(image, variable_string) end local path = du.split(image.film.path, dt.configuration.running_os ~= "windows" and "/" or "\\") - if element > #path then + + if tonumber(element) > #path then substitutes["ROLL.NAME"] = "" log.msg(log.warn, "ROLL.NAME element requested was more than number of elements in film roll") else From d75f00daa01fb1fa3beb7884b1e6936c0a6d49fd Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 3 Dec 2025 20:54:47 -0500 Subject: [PATCH 6/6] lib/dtutils/string - fixed ROLL.NAME to support the original and the ROLL.NAME[n] variant in the same variable string --- lib/dtutils/string.lua | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index 122f6d2..9a1085f 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -672,22 +672,21 @@ local function build_rollname_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, "ROLL.NAME") then - local element = "1" + local element = nil - if string.match(var, "ROLL.NAME%[") then - element = string.match(var, "%[(%d)%]") - end + if string.match(var, "ROLL.NAME%[") then + element = string.match(var, "%[(%d)%]") local path = du.split(image.film.path, dt.configuration.running_os ~= "windows" and "/" or "\\") + + element = tonumber(element) + 1 -- add one because c arrays are 0 based and lua are 1 based - if tonumber(element) > #path then - substitutes["ROLL.NAME"] = "" + if element > #path then log.msg(log.warn, "ROLL.NAME element requested was more than number of elements in film roll") + substitutes[var] = "" else - substitutes["ROLL.NAME"] = path[(#path + 1) - tonumber(element)] + substitutes[var] = path[(#path + 1) - element] end - return end end end @@ -1067,6 +1066,11 @@ local function treat(var_string) if not ret_val then ret_val = "" end log.msg(log.info, "ret_val is " .. ret_val) + elseif string.match(var_string, "ROLL.NAME%[") then + ret_val = substitutes[var_string] + if not ret_val then ret_val = "" end + log.msg(log.info, "ret_val is " .. ret_val) + elseif string.match(var_string, "SEQUENCE%[") then local width, start = string.match(var_string, "(%d+),(%d+)") local seq_val = tonumber(substitutes[var])