1349 lines
52 KiB
Lua
1349 lines
52 KiB
Lua
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 gettext(msg)
|
|
end
|
|
|
|
dtutils_string.log_level = DEFAULT_LOG_LEVEL
|
|
|
|
dtutils_string.libdoc = {
|
|
Name = [[dtutils.string]],
|
|
Synopsis = [[a library of string utilities for use in darktable lua scripts]],
|
|
Usage = [[local ds = require "lib/dtutils.string"]],
|
|
Description = [[This library contains string manipulation routines to aid in building
|
|
darktable lua scripts.]],
|
|
Return_Value = [[du - library - the darktable lua string library]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[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 <http://www.gnu.org/licenses/>.]],
|
|
Copyright = [[Copyright (c) 2016 Bill Ferguson <wpferguson@gmail.com>]],
|
|
functions = {}
|
|
}
|
|
|
|
dtutils_string.libdoc.functions["strip_accents"] = {
|
|
Name = [[strip_accents]],
|
|
Synopsis = [[strip accents from characters]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.strip_accents(str)
|
|
str - string - the string with characters that need accents removed]],
|
|
Description = [[strip_accents removes accents from accented characters returning the
|
|
unaccented character.]],
|
|
Return_Value = [[result - string - the string containing unaccented characters]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[Copied from https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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"
|
|
tableAccents["â"] = "a"
|
|
tableAccents["ã"] = "a"
|
|
tableAccents["ä"] = "a"
|
|
tableAccents["ç"] = "c"
|
|
tableAccents["è"] = "e"
|
|
tableAccents["é"] = "e"
|
|
tableAccents["ê"] = "e"
|
|
tableAccents["ë"] = "e"
|
|
tableAccents["ì"] = "i"
|
|
tableAccents["í"] = "i"
|
|
tableAccents["î"] = "i"
|
|
tableAccents["ï"] = "i"
|
|
tableAccents["ñ"] = "n"
|
|
tableAccents["ò"] = "o"
|
|
tableAccents["ó"] = "o"
|
|
tableAccents["ô"] = "o"
|
|
tableAccents["õ"] = "o"
|
|
tableAccents["ö"] = "o"
|
|
tableAccents["ù"] = "u"
|
|
tableAccents["ú"] = "u"
|
|
tableAccents["û"] = "u"
|
|
tableAccents["ü"] = "u"
|
|
tableAccents["ý"] = "y"
|
|
tableAccents["ÿ"] = "y"
|
|
tableAccents["À"] = "A"
|
|
tableAccents["Á"] = "A"
|
|
tableAccents["Â"] = "A"
|
|
tableAccents["Ã"] = "A"
|
|
tableAccents["Ä"] = "A"
|
|
tableAccents["Ç"] = "C"
|
|
tableAccents["È"] = "E"
|
|
tableAccents["É"] = "E"
|
|
tableAccents["Ê"] = "E"
|
|
tableAccents["Ë"] = "E"
|
|
tableAccents["Ì"] = "I"
|
|
tableAccents["Í"] = "I"
|
|
tableAccents["Î"] = "I"
|
|
tableAccents["Ï"] = "I"
|
|
tableAccents["Ñ"] = "N"
|
|
tableAccents["Ò"] = "O"
|
|
tableAccents["Ó"] = "O"
|
|
tableAccents["Ô"] = "O"
|
|
tableAccents["Õ"] = "O"
|
|
tableAccents["Ö"] = "O"
|
|
tableAccents["Ù"] = "U"
|
|
tableAccents["Ú"] = "U"
|
|
tableAccents["Û"] = "U"
|
|
tableAccents["Ü"] = "U"
|
|
tableAccents["Ý"] = "Y"
|
|
|
|
local normalizedString = ""
|
|
|
|
for strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do
|
|
if tableAccents[strChar] ~= nil then
|
|
normalizedString = normalizedString..tableAccents[strChar]
|
|
else
|
|
normalizedString = normalizedString..strChar
|
|
end
|
|
end
|
|
|
|
log.log_level(old_log_level)
|
|
return normalizedString
|
|
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["escape_xml_characters"] = {
|
|
Name = [[escape_xml_characters]],
|
|
Synopsis = [[escape characters for xml documents]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.escape_xml_characters(str)
|
|
str - string - the string that needs escaped]],
|
|
Description = [[escape_xml_characters provides the escape sequences for
|
|
"&", '"', "'", "<", and ">" with the corresponding "&",
|
|
""", "'", "<", and ">".]],
|
|
Return_Value = [[result - string - the string containing escapes for the xml characters]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
-- 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,"\"", """)
|
|
str = string.gsub(str,"'", "'")
|
|
str = string.gsub(str,"<", "<")
|
|
str = string.gsub(str,">", ">")
|
|
|
|
log.log_level(old_log_level)
|
|
return str
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["urlencode"] = {
|
|
Name = [[urlencode]],
|
|
Synopsis = [[encode a string in a websage manner]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.urlencode(str)
|
|
str - string - the string that needs to be made websafe]],
|
|
Description = [[urlencode converts a string into a websafe version suitable for
|
|
use in a web browser.]],
|
|
Return_Value = [[result - string - a websafe string]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
|
|
|
|
dtutils_string.libdoc.functions["is_not_sanitized"] = {
|
|
Name = [[is_not_sanitized]],
|
|
Synopsis = [[Check if a string has been sanitized]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
local result = ds.is_not_sanitized(str)
|
|
str - string - the string that needs to be made safe]],
|
|
Description = [[is_not_sanitized checks a string to see if it
|
|
has been made safe use passing as an argument in a system command.]],
|
|
Return_Value = [[result - boolean - true if the string is not sanitized otherwise false]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
local function _is_not_sanitized_posix(str)
|
|
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
|
|
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
|
|
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)
|
|
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
|
|
log.log_level(old_log_level)
|
|
return _is_not_sanitized_windows(str)
|
|
else
|
|
log.log_level(old_log_level)
|
|
return _is_not_sanitized_posix(str)
|
|
end
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["sanitize"] = {
|
|
Name = [[sanitize]],
|
|
Synopsis = [[surround a string in quotes making it safe to pass as an argument]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.sanitize(str)
|
|
str - string - the string that needs to be made safe]],
|
|
Description = [[sanitize converts a string into a version suitable for
|
|
use passing as an argument in a system command.]],
|
|
Return_Value = [[result - string - a websafe string]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
log.log_level(old_log_level)
|
|
return "'" .. string.gsub(str, "'", "'\\''") .. "'"
|
|
else
|
|
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
|
|
log.log_level(old_log_level)
|
|
return "\"" .. string.gsub(str, "\"", "\"^\"\"") .. "\""
|
|
else
|
|
log.log_level(old_log_level)
|
|
return str
|
|
end
|
|
end
|
|
|
|
local function _should_be_sanitized(str)
|
|
local old_log_level = log.log_level()
|
|
local result = false
|
|
local UNSAFE_POSIX_FILENAME_CHARS <const> = "[^%w/._%-]+"
|
|
local UNSAFE_WIN_FILENAME_CHARS <const> = "[^%w\\._%-:]+"
|
|
|
|
local pattern = UNSAFE_POSIX_FILENAME_CHARS
|
|
if dt.configuration.running_os == "windows" then
|
|
pattern = UNSAFE_WIN_FILENAME_CHARS
|
|
end
|
|
|
|
log.log_level(dtutils_string.log_level)
|
|
if string.match(str, pattern) 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 _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
|
|
sanitized_str = str
|
|
end
|
|
log.log_level(old_log_level)
|
|
return sanitized_str
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["sanitize_lua"] = {
|
|
Name = [[sanitize_lua]],
|
|
Synopsis = [[escape lua 'magic' characters from a pattern string]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.sanitize_lua(str)
|
|
str - string - the string that needs to be made safe]],
|
|
Description = [[sanitize_lua escapes lua 'magic' characters so that
|
|
a string may be used in lua string/patten matching.]],
|
|
Return_Value = [[result - string - a lua pattern safe string]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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, "%[", "%%[")
|
|
str = string.gsub(str, "%]", "%%]")
|
|
str = string.gsub(str, "+", "%%+")
|
|
log.log_level(old_log_level)
|
|
return str
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["split_filepath"] = {
|
|
Name = [[split_filepath]],
|
|
Synopsis = [[split a filepath into parts]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.split_filepath(filepath)
|
|
filepath - string - path and filename]],
|
|
Description = [[split_filepath splits a filepath into the path, filename, basename and filetype and puts
|
|
that in a table]],
|
|
Return_Value = [[result - table - a table containing the path, filename, basename, and filetype]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
function dtutils_string.split_filepath(str)
|
|
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 = {}
|
|
-- Thank you Tobias Jakobs for the awesome regular expression, which I tweaked a little
|
|
result["path"], result["filename"], result["basename"], result["filetype"] = string.match(str, "(.-)(([^\\/]-)%.?([^%.\\/]*))$")
|
|
if result["basename"] == "" and result["filetype"]:len() > 1 then
|
|
result["basename"] = result["filetype"]
|
|
result["filetype"] = ""
|
|
end
|
|
log.log_level(old_log_level)
|
|
return result
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["get_path"] = {
|
|
Name = [[get_path]],
|
|
Synopsis = [[get the path from a file path]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.get_path(filepath)
|
|
filepath - string - path and filename]],
|
|
Description = [[get_path strips the filename and filetype from a path and returns the path]],
|
|
Return_Value = [[result - string - the path]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
|
|
dtutils_string.libdoc.functions["get_filename"] = {
|
|
Name = [[get_filename]],
|
|
Synopsis = [[get the filename and extension from a file path]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.get_filename(filepath)
|
|
filepath - string - path and filename]],
|
|
Description = [[get_filename strips the path from a filepath and returns the filename]],
|
|
Return_Value = [[result - string - the file name and type]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
|
|
dtutils_string.libdoc.functions["get_basename"] = {
|
|
Name = [[get_basename]],
|
|
Synopsis = [[get the filename without the path or extension]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.get_basename(filepath)
|
|
filepath - string - path and filename]],
|
|
Description = [[get_basename returns the name of the file without the path or filetype
|
|
]],
|
|
Return_Value = [[result - string - the basename of the file]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
|
|
dtutils_string.libdoc.functions["get_filetype"] = {
|
|
Name = [[get_filetype]],
|
|
Synopsis = [[get the filetype from a filename]],
|
|
Usage = [[local ds = require "lib/dtutils.string"
|
|
|
|
local result = ds.get_filetype(filepath)
|
|
filepath - string - path and filename]],
|
|
Description = [[get_filetype returns the filetype from the supplied filepath]],
|
|
Return_Value = [[result - string - the filetype]],
|
|
Limitations = [[]],
|
|
Example = [[]],
|
|
See_Also = [[]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
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
|
|
|
|
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_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
|
|
[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_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,
|
|
then an empty string is used for the value.]],
|
|
Example = [[]],
|
|
See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]],
|
|
Reference = [[]],
|
|
License = [[]],
|
|
Copyright = [[]],
|
|
}
|
|
|
|
local substitutes = {}
|
|
local category_substitutes = {}
|
|
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
-- C O N S T A N T S
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
local PLACEHOLDERS = {"ROLL.NAME",
|
|
"FILE.FOLDER",
|
|
"FILE.NAME",
|
|
"FILE.EXTENSION",
|
|
"ID",
|
|
"IMAGE.ID",
|
|
"IMAGE.ID.NEXT",
|
|
"VERSION",
|
|
"VERSION.IF.MULTI",
|
|
"VERSION.NAME",
|
|
"DARKTABLE.VERSION",
|
|
"Xmp.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",
|
|
"YEAR.SHORT",
|
|
"MONTH",
|
|
"MONTH.LONG",
|
|
"MONTH.SHORT",
|
|
"DAY",
|
|
"HOUR",
|
|
"HOUR.AMPM", -- Not Implemented
|
|
"MINUTE",
|
|
"SECOND",
|
|
"MSEC",
|
|
"EXIF.YEAR",
|
|
"EXIF.YEAR.SHORT",
|
|
"EXIF.MONTH",
|
|
"EXIF.MONTH.LONG",
|
|
"EXIF.MONTH.SHORT",
|
|
"EXIF.DAY",
|
|
"EXIF.HOUR",
|
|
"EXIF.HOUR.AMPM", -- Not Implemented
|
|
"EXIF.MINUTE",
|
|
"EXIF.SECOND",
|
|
"EXIF.MSEC",
|
|
"EXIF.DATE.REGIONAL", -- Not Implemented
|
|
"EXIF.TIME.REGIONAL", -- Not Implemented
|
|
"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",
|
|
"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",
|
|
"GPS.LOCATION", -- Not Implemented
|
|
"STARS",
|
|
"RATING.ICONS", -- Not Implemented
|
|
"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",
|
|
"FOLDER.HOME",
|
|
"FOLDER.DESKTOP",
|
|
"OPENCL.ACTIVATED", -- Not Implemented
|
|
"USERNAME",
|
|
"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 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
|
|
|
|
-- 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)
|
|
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, "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)
|
|
|
|
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
|
|
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
|
|
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
|
|
|
|
-- build the argument substitution list from each image
|
|
|
|
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)
|
|
|
|
-- 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
|
|
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")
|
|
local user_name = username or USER
|
|
local pictures_folder = pic_folder or PICTURES
|
|
local home_folder = home or HOME
|
|
local desktop_folder = desktop or DESKTOP
|
|
|
|
local labels = get_colorlabels(image)
|
|
|
|
local emon_long, emon_short
|
|
local eyear, emon, eday, ehour, emin, esec, emsec
|
|
local use_millisecs = false
|
|
if dt.preferences.read("darktable", "lighttable/ui/milliseconds", "bool") and is_api_9_1 then
|
|
use_millisecs = true
|
|
end
|
|
|
|
if image.exif_datetime_taken and image.exif_datetime_taken ~= "" then
|
|
emon_long = os.date("%B", exiftime2systime(image.exif_datetime_taken))
|
|
emon_short = os.date("%b", exiftime2systime(image.exif_datetime_taken))
|
|
if use_millisecs 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
|
|
else
|
|
log.msg(log.warn, "no capture date and time found in EXIF data")
|
|
emon_long, emon_short, eyear, emon, eday, ehour, emin, esec, emsec = "", "", "", "", "", "", "", "", ""
|
|
end
|
|
|
|
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
|
|
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
|
|
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 -- 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
|
|
long_month, -- MONTH.LONG
|
|
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
|
|
eyear, -- EXIF.YEAR
|
|
(eyear ~= nil) and string.sub(eyear, 3) or "", -- EXIF.YEAR.SHORT
|
|
emon, -- EXIF.MONTH
|
|
emon_long, -- EXIF.MONTH.LONG
|
|
emon_short, -- EXIF.MONTH.SHORT
|
|
eday, -- EXIF.DAY
|
|
ehour, -- EXIF.HOUR
|
|
"", -- EXIF.HOUR.AMPM
|
|
emin, -- EXIF.MINUTE
|
|
esec, -- EXIF.SECOND
|
|
emsec, -- EXIF.MSEC
|
|
"", -- EXIF.DATE.REGIONAL - wont be implemented
|
|
"", -- EXIF.TIME.REGIONAL - wont be implemented
|
|
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_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
|
|
"", -- GPS.LOCATION - wont be implemented
|
|
image.rating, -- STARS
|
|
"", -- RATING.ICONS - wont be implemented
|
|
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
|
|
home_folder, -- FOLDER.HOME
|
|
desktop_folder, -- FOLDER.DESKTOP
|
|
"", -- OPENCL.ACTIVATED - wont be implemented
|
|
(user_name ~= nil) and user_name or "",-- USERNAME
|
|
"", -- NL - wont be implemented
|
|
"" -- 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)
|
|
build_rollname_substitution_list(image, 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 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"),
|
|
_("$(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"),
|
|
_("$(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"),
|
|
_("$(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.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"),-- 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"),
|
|
_("$(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"),
|
|
_("$(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
|
|
|
|
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)
|
|
|
|
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") 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 = "" 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])
|
|
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
|
|
|
|
local valid_var = false
|
|
|
|
if ret_val 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
|
|
|
|
-- 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
|
|
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 = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]],
|
|
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)
|
|
|
|
substitutes = {}
|
|
category_substitutes = {}
|
|
|
|
log.log_level(old_log_level)
|
|
end
|
|
|
|
dtutils_string.libdoc.functions["substitute"] = {
|
|
Name = [[substitute]],
|
|
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
|
|
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
|
|
[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_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 = [[]],
|
|
Example = [[]],
|
|
See_Also = [[https://docs.darktable.org/usermanual/4.6/en/special-topics/variables/]],
|
|
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_substitute_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
|
|
|
|
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
|