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.
494 lines
14 KiB
Lua
494 lines
14 KiB
Lua
--[[
|
|
|
|
apply_camera_style.lua - apply camera style to matching images
|
|
|
|
Copyright (C) 2024 Bill Ferguson <wpferguson@gmail.com>.
|
|
|
|
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/>.
|
|
]]
|
|
--[[
|
|
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
|
|
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 <wpferguson@gmail.com>
|
|
|
|
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 <const> = "apply_camera_style"
|
|
local DEFAULT_LOG_LEVEL <const> = log.info
|
|
local TMP_DIR <const> = dt.configuration.tmp_dir
|
|
local STYLE_PREFIX <const> = "_l10n_darktable camera styles|"
|
|
|
|
-- path separator
|
|
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
|
|
|
-- command separator
|
|
local CS <const> = 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 darktable camera style to matching images"), -- purpose of script
|
|
author = "Bill Ferguson <wpferguson@gmail.com>", -- 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
|
|
-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
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"))
|
|
)
|
|
|
|
dt.register_event(MODULE, "shortcut",
|
|
function(event, shortcut)
|
|
apply_camera_style(false)
|
|
end, string.format(shortcut_string, _("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
|