14 Commits

Author SHA1 Message Date
Bill Ferguson
77733b334e Modifed script_manager to handle embedding the lua-scripts in the darktable release. Lua API 9.7.0 starts script_manager from the lua init module. script_manager takes care of running the user luarc file if it exists 2025-12-20 19:42:19 -05:00
wpferguson
7f09e55525 Merge pull request #616 from wpferguson/fix_multi_os
examples/multi_os - replaced ufraw-batch with exiftool
2025-12-07 14:18:29 -05:00
wpferguson
f83ac1366a Merge pull request #617 from wpferguson/ext_editor_sanitize
ext_editor sanitize file name
2025-12-07 14:18:11 -05:00
Bill Ferguson
6cda27172e contrib/ext_editor - sanitize filename before importing to collection 2025-12-06 19:11:12 -05:00
Bill Ferguson
df78522a79 examples/multi_os - replaced ufraw-batch with exiftool to extract
the embedded jpeg or preview image.
2025-12-06 18:40:37 -05:00
wpferguson
6e64d6db6f Merge pull request #613 from wpferguson/string_lib_fixes
String lib fixes
2025-12-04 15:13:56 -05:00
Bill Ferguson
d75f00daa0 lib/dtutils/string - fixed ROLL.NAME to support the original and
the ROLL.NAME[n] variant in the same variable
                     string
2025-12-03 20:54:47 -05:00
Bill Ferguson
3e430ee2be lib/dtutils/string - converted element from string to number for use in test 2025-12-03 18:26:13 -05:00
Bill Ferguson
3856ae3449 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)
2025-12-02 20:40:03 -05:00
Bill Ferguson
d54a9f8b7e lib/dtutils/string - implemented variable substitution for ROLL.NAME[n] 2025-12-02 19:54:10 -05:00
Bill Ferguson
8f6cc850fb lib/dtutils/string - Added Xmp.darktable.version and Xmp.dc.[title,
description,publisher,rights,creator]
2025-12-02 18:57:05 -05:00
Bill Ferguson
e9f18a3d36 lib/dtutils/string - add IMAGE.ID and IMAGE.ID.NEXT variables 2025-12-02 18:34:18 -05:00
wpferguson
f3a89a89bd Merge pull request #612 from wpferguson/fix_apply_camera_style
official/apply_camera_style - fixed pattern matching bugs
2025-12-02 16:30:56 -05:00
Bill Ferguson
d075fb7128 official/apply_camera_style - fixed pattern matching bug that allowed
camera styles to be applied twice

                              fixed pattern matching bug affecting
                              Canon EOS R, RP, and R5

                              Added more debugging statements to
                              pattern matching functions
2025-12-02 16:23:28 -05:00
4 changed files with 204 additions and 17 deletions

View File

@@ -328,7 +328,7 @@ local function export2collection(storage, image_table, extra_data)
-- create unique filename
new_name = image.path..PS..df.get_filename(temp_name)
new_name = df.create_unique_filename(new_name)
new_name = df.sanitize_filename(df.create_unique_filename(new_name))
-- move image to collection folder, check result, return if error
move_success = df.file_move(temp_name, new_name)

View File

@@ -29,8 +29,7 @@
by hovering over the image and pressing the key combination.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
* ufraw-batch - https://ufraw.sourceforge.net
MacOS - install with homebrew
* exiftool - https://exiftool.org
USAGE
* require this script from your main lua file
@@ -44,6 +43,7 @@
* Send to Bill Ferguson, wpferguson@gmail.com
CHANGES
20251205 - replaced ufraw-batch with exiftool
]]
--[[
@@ -139,6 +139,36 @@ local function copy_image_attributes(from, to, ...)
end
end
local function check_file_size(img_name)
local result = 0
if dt.configuration.running_os == "windows" then
local path = df.get_path(img_name)
local fname = df.get_filename(img_name)
local p = io.open("forfiles /P " .. path .. " /M " .. fname .. " /C \"cmd /c echo $fsize\"")
if p then
for line in p:lines() do
if line:length() > 5 then
result = tonumber(line)
end
end
p:close()
end
else
local p = io.popen("ls -s " .. img_name)
if p then
local line = p:read("*a")
result = tonumber(du.split(line, " ")[1])
p:close()
end
end
return result
end
--[[
The main function called from the button or the shortcut. It takes one or more raw files, passed
in a table and extracts the embedded jpeg images
@@ -152,11 +182,12 @@ local function extract_embedded_jpeg(images)
in the user's path. When it finds the executable, it returns the command to run it.
]]
local ufraw_executable = df.check_if_bin_exists("ufraw-batch")
if ufraw_executable then
local exiftool_executable = df.check_if_bin_exists("exiftool")
if exiftool_executable then
for _, image in ipairs(images) do
if image.is_raw then
local img_file = du.join({image.path, image.filename}, "/")
local img_file = tostring(image)
local jpg_img_file = string.gsub(img_file, "%....$", ".jpg")
--[[
dtsys.external_command() is operating system aware and formats the command as necessary.
@@ -164,8 +195,12 @@ local function extract_embedded_jpeg(images)
operating system aware and uses the corresponding quotes.
]]
if dtsys.external_command(ufraw_executable .. " --silent --embedded-image " .. df.sanitize_filename(img_file)) then
local jpg_img_file = df.chop_filetype(img_file) .. ".embedded.jpg"
if dtsys.external_command(exiftool_executable .. " -b -JpgFromRaw " .. df.sanitize_filename(img_file) .. " > " .. df.sanitize_filename(jpg_img_file)) then
dt.print_log("created jpg from raw")
if check_file_size(df.sanitize_filename(jpg_img_file)) == 0 then
dt.print_log("using preview")
dtsys.external_command(exiftool_executable .. " -b -PreviewImage " .. df.sanitize_filename(img_file) .. " > " .. df.sanitize_filename(jpg_img_file))
end
dt.print_log("jpg_img_file set to ", jpg_img_file) -- print a debugging message
local myimage = dt.database.import(jpg_img_file)
myimage:group_with(image.group_leader)
@@ -188,8 +223,8 @@ local function extract_embedded_jpeg(images)
end
end
else
dt.print_error("ufraw-batch not found. Exiting...") -- print debugging error message
dt.print("ufraw-batch not found, exiting...") -- print the error to the screen
dt.print_error("exiftool not found. Exiting...") -- print debugging error message
dt.print("exiftool not found, exiting...") -- print the error to the screen
end
end

View File

@@ -542,11 +542,14 @@ local PLACEHOLDERS = {"ROLL.NAME",
"FILE.FOLDER",
"FILE.NAME",
"FILE.EXTENSION",
"ID",
"ID",
"IMAGE.ID",
"IMAGE.ID.NEXT",
"VERSION",
"VERSION.IF.MULTI",
"VERSION.NAME",
"DARKTABLE.VERSION",
"Xmp.darktable.version",
"DARKTABLE.NAME", -- Not Implemented
"SEQUENCE",
"WIDTH.SENSOR",
@@ -612,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",
@@ -652,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)
local element = nil
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 element > #path then
log.msg(log.warn, "ROLL.NAME element requested was more than number of elements in film roll")
substitutes[var] = ""
else
substitutes[var] = path[(#path + 1) - element]
end
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)
@@ -768,10 +807,13 @@ 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
dt.configuration.version, -- DARKTABLE.VERSION
dt.configuration.version, -- Xmp.darktable.version
"", -- DARKTABLE.NAME
string.format("%04d", sequence), -- SEQUENCE
image.width, -- WIDTH.SENSOR
@@ -837,10 +879,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
@@ -862,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
@@ -889,10 +937,13 @@ 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"),
_("$(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"),
@@ -957,10 +1008,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"),
@@ -1010,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])
@@ -1243,6 +1304,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

View File

@@ -54,7 +54,7 @@ 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"
local dtdebug = require "darktable.debug"
local gettext = dt.gettext.gettext
@@ -62,6 +62,21 @@ local function _(msgid)
return gettext(msgid)
end
-- figure out where we are running from
local info = debug.getinfo(1, "S")
local scripts_path = info.source:sub(2)
dt.print_log("scripts path is " .. scripts_path)
-- assume user based
local system_based = false
if string.match(scripts_path, dt.configuration.data_dir) then
dt.print_log("script_manager using system based scripts")
system_based = true
end
-- api check
-- du.check_min_api_version("9.3.0", "script_manager")
@@ -71,7 +86,7 @@ end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- script_manager required API version
local SM_API_VER_REQD <const> = "9.3.0"
local SM_API_VER_REQD <const> = "9.7.0"
-- path separator
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
@@ -87,15 +102,29 @@ local DEFAULT_BUTTONS_PER_PAGE <const> = 10
local DEFAULT_LOG_LEVEL <const> = log.warn
local LUA_DIR <const> = dt.configuration.config_dir .. PS .. "lua"
local USER_LUA_DIR <const> = dt.configuration.config_dir .. PS .. "lua"
local SYSTEM_LUA_DIR <const> = dt.configuration.data_dir .. PS .. "lua-scripts"
local LUA_SCRIPT_REPO <const> = "https://github.com/darktable-org/lua-scripts.git"
local LUA_API_VER <const> = "API-" .. dt.configuration.api_version_string
-- local POWER_ICON = dt.configuration.config_dir .. "/lua/data/data/icons/power.png"
local POWER_ICON <const> = dt.configuration.config_dir .. "/lua/data/icons/path20.png"
local BLANK_ICON <const> = dt.configuration.config_dir .. "/lua/data/icons/blank20.png"
local USER_POWER_ICON <const> = dt.configuration.config_dir .. "/lua/data/icons/path20.png"
local USER_BLANK_ICON <const> = dt.configuration.config_dir .. "/lua/data/icons/blank20.png"
local SYSTEM_POWER_ICON <const> = dt.configuration.data_dir .. "/lua-scripts/data/icons/path20.png"
local SYSTEM_BLANK_ICON <const> = dt.configuration.data_dir .. "/lua-scripts/data/icons/blank20.png"
local LUA_DIR = USER_LUA_DIR
local POWER_ICON = USER_POWER_ICON
local BLANK_ICON = USER_BLANK_ICON
if system_based then
LUA_DIR = SYSTEM_LUA_DIR
POWER_ICON = SYSTEM_POWER_ICON
BLANK_ICON = SYSTEM_BLANK_ICON
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
@@ -1243,7 +1272,7 @@ end
script_manager_running_script = "script_manager"
if check_for_updates or SM_API_VER_REQD > dt.configuration.api_version_string then
if (check_for_updates or SM_API_VER_REQD > dt.configuration.api_version_string) and not system_based 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)
@@ -1325,6 +1354,25 @@ end
scan_scripts(LUA_DIR)
log.msg(log.debug, "finished processing scripts")
if system_based then
-- exec user luarc file if it exists
local luarc = loadfile(USER_LUA_DIR .. "rc")
if luarc then
luarc()
end
if df.test_file(USER_LUA_DIR, "d") then
LUA_DIR = USER_LUA_DIR
scan_scripts(LUA_DIR)
LUA_DIR = SYSTEM_LUA_DIR
end
dt.configuration["lua_dir"] = LUA_DIR
dt.print_log("lua dir is " .. dt.configuration.lua_dir)
-- scan_script(USER_LUA_DIR)
end
-- - - - - - - - - - - - - - - - - - - - - - - -
-- U S E R I N T E R F A C E