Compare commits
44 Commits
API-9.4.0
...
17422_bund
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
77733b334e | ||
|
|
7f09e55525 | ||
|
|
f83ac1366a | ||
|
|
6cda27172e | ||
|
|
df78522a79 | ||
|
|
6e64d6db6f | ||
|
|
d75f00daa0 | ||
|
|
3e430ee2be | ||
|
|
3856ae3449 | ||
|
|
d54a9f8b7e | ||
|
|
8f6cc850fb | ||
|
|
e9f18a3d36 | ||
|
|
f3a89a89bd | ||
|
|
d075fb7128 | ||
|
|
e831f377bf | ||
|
|
d696e6e3f2 | ||
|
|
db505f1e0a | ||
|
|
57d15a3fa6 | ||
|
|
4d54b5eaba | ||
|
|
dbf0c5d853 | ||
|
|
193a615772 | ||
|
|
447980ddc4 | ||
|
|
8af5476f8d | ||
|
|
c95547caa7 | ||
|
|
b4d3b2901f | ||
|
|
cb6b3161d8 | ||
|
|
2d82eea237 | ||
|
|
3beee93af4 | ||
|
|
68291414f3 | ||
|
|
5662519314 | ||
|
|
28ab065293 | ||
|
|
aed3275943 | ||
|
|
d7aeb43340 | ||
|
|
ea79d10b11 | ||
|
|
e4001f984f | ||
|
|
453d744b69 | ||
|
|
b9cc0dccce | ||
|
|
84fea20ee5 | ||
|
|
e054bfc59d | ||
|
|
8cd10dce68 | ||
|
|
f8d49ccd01 | ||
|
|
25afb41924 | ||
|
|
3001a0dc1a | ||
|
|
b70fdc0ecf |
@@ -36,6 +36,7 @@ Name|Standalone|OS |Purpose
|
||||
----|:--------:|:---:|-------
|
||||
[AutoGrouper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autogrouper)|Yes|LMW|Group images together by time
|
||||
[autostyle](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/autostyle)|Yes|LMW|Automatically apply styles on import
|
||||
[auto_tag_modified](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_tag_modified)|Yes|LMW|Automatically tag images which have been manually modified in the darkroom
|
||||
change_group_leader|Yes|LMW|Change which image leads group
|
||||
[clear_GPS](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/clear_gps)|Yes|LMW|Reset GPS information for selected images
|
||||
[CollectHelper](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/collecthelper)|Yes|LMW|Add buttons to selected images module to manipulate the collection
|
||||
@@ -178,7 +179,7 @@ If you don't have %LOCALAPPDATA%\darktable you have to start dartable at least o
|
||||
|
||||
When darktable starts it looks for a file name `~/.config/darktable/luarc` (`%LOCALAPPDATA%\darktable\luarc` for windows) and reads it to see which scripts to include. The file is a plain text file with entries of the form `require "<directory>/<name>"` where directory is the directory containing the scripts, from the above list, and name is the name from the above list. To include GIMP the line would be `require "contrib/gimp"`.
|
||||
|
||||
The recommended way to enable and disable specific scripts is using the script manager module. To use script manager do the following:
|
||||
The recommended way to enable and disable specific scripts is using the [script manager](https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/tools/script_manager/) module. To use script manager do the following:
|
||||
|
||||
### Linux or MacOS
|
||||
|
||||
|
||||
@@ -106,7 +106,7 @@ local function preserve_metadata(original, sharpened)
|
||||
if exiftool then
|
||||
dtsys.external_command("exiftool -overwrite_original_in_place -tagsFromFile " .. original .. " " .. sharpened)
|
||||
else
|
||||
dt.print_log(MODULE .. " exiftool not found, metadata not preserved")
|
||||
dt.print_log(MODULE_NAME .. " exiftool not found, metadata not preserved")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
129
contrib/auto_tag_modified.lua
Normal file
129
contrib/auto_tag_modified.lua
Normal file
@@ -0,0 +1,129 @@
|
||||
--[[
|
||||
|
||||
auto_tag_modified.lua - automatically tag an image when it has been manually modified in darkroom
|
||||
|
||||
Copyright (C) 2025 Michael Reiger <michael@rauschpfeife.net>, modeled after auto_snapshot.lua by Bill Ferguson <wpferguson@gamil.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/>.
|
||||
]]
|
||||
--[[
|
||||
auto_tag_modified -
|
||||
|
||||
automatically tag an image when it has been manually modified in the darkroom, that is if its history has changed at the time it is closed from the darkroom.
|
||||
(This will not catch applying styles from the light table though.)
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
* start the script from script_manager
|
||||
* open an image in darkroom, and close it after making an operation that changes the history
|
||||
|
||||
BUGS, COMMENTS, SUGGESTIONS
|
||||
Michael Reiger <michael@rauschpfeife.net>
|
||||
|
||||
CHANGES
|
||||
]]
|
||||
|
||||
local dt = require "darktable"
|
||||
local du = require "lib/dtutils"
|
||||
local log = require "lib/dtutils.log"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "auto_tag_modified"
|
||||
local DEFAULT_TAG_NAME <const> = "darktable manually modified"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.error
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A P I C H E C K
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- I 1 8 N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
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 = _("auto tag modified"), -- name of script
|
||||
purpose = _("automatically tag an image when it has been manually modified in darkroom"), -- purpose of script
|
||||
author = "Michael Reiger <michael@rauschpfeife.net>", -- your name and optionally e-mail address
|
||||
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/contrib/auto_tag_modified/" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local auto_tag_modified = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.preferences.register(MODULE, "auto_modified_tag_name", "string", "auto_tag_modified - " .. _("tag name to use for auto tagging modified images"),
|
||||
_("tag name to use for auto tagging modified images"), DEFAULT_TAG_NAME)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- D A R K T A B L E I N T E G R A T I O N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local function destroy()
|
||||
dt.destroy_event(MODULE, "darkroom-image-history-changed")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "darkroom-image-history-changed",
|
||||
function(event, image)
|
||||
local tag_name = dt.preferences.read(MODULE, "auto_modified_tag_name", "string")
|
||||
local tag = dt.tags.create(tag_name)
|
||||
dt.tags.attach(tag, image)
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -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)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
--[[
|
||||
This file is part of darktable,
|
||||
copyright (c) 2016 Tobias Jakobs
|
||||
copyright (c) 2025 Balázs Dura-Kovács
|
||||
|
||||
darktable is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
@@ -63,21 +64,33 @@ gT.event_registered = false
|
||||
local labelDistance = dt.new_widget("label")
|
||||
labelDistance.label = _("distance:")
|
||||
|
||||
local label_copy_gps_lat = dt.new_widget("check_button")
|
||||
local checkbox_copy_gps_lat = dt.new_widget("check_button")
|
||||
{
|
||||
label = _("latitude: "),
|
||||
value = true
|
||||
}
|
||||
local label_copy_gps_lon = dt.new_widget("check_button")
|
||||
local entry_gps_lat = dt.new_widget("entry")
|
||||
{
|
||||
tooltip = _("latitude (editable)"),
|
||||
placeholder = _("latitude")
|
||||
}
|
||||
local checkbox_copy_gps_lon = dt.new_widget("check_button")
|
||||
{
|
||||
label = _("longitude: "),
|
||||
value = true
|
||||
}
|
||||
local label_copy_gps_ele = dt.new_widget("check_button")
|
||||
local entry_gps_lon = dt.new_widget("entry")
|
||||
{
|
||||
tooltip = _("longitude (editable)"),
|
||||
placeholder = _("longitude")
|
||||
}
|
||||
local checkbox_copy_gps_ele = dt.new_widget("check_button")
|
||||
{
|
||||
label = _("elevation: "),
|
||||
value = true
|
||||
}
|
||||
local entry_gps_ele = dt.new_widget("entry")
|
||||
{
|
||||
tooltip = _("elevation (editable)"),
|
||||
placeholder = _("elevation")
|
||||
}
|
||||
-- </GUI>
|
||||
|
||||
local function select_with_gps()
|
||||
@@ -294,20 +307,20 @@ local function copy_gps()
|
||||
copy_gps_have_data = false
|
||||
else
|
||||
copy_gps_have_data = true
|
||||
if (image.latitude and label_copy_gps_lat.value) then
|
||||
if (image.latitude and checkbox_copy_gps_lat.value) then
|
||||
copy_gps_latitude = image.latitude
|
||||
end
|
||||
if (image.longitude and label_copy_gps_lon.value) then
|
||||
if (image.longitude and checkbox_copy_gps_lon.value) then
|
||||
copy_gps_longitude = image.longitude
|
||||
end
|
||||
if (image.elevation and label_copy_gps_ele.value) then
|
||||
if (image.elevation and checkbox_copy_gps_ele.value) then
|
||||
copy_gps_elevation = image.elevation
|
||||
end
|
||||
end
|
||||
|
||||
label_copy_gps_lat.label = _("latitude: ") .. copy_gps_latitude
|
||||
label_copy_gps_lon.label = _("longitude: ") .. copy_gps_longitude
|
||||
label_copy_gps_ele.label = _("elevation: ") .. copy_gps_elevation
|
||||
entry_gps_lat.text = copy_gps_latitude
|
||||
entry_gps_lon.text = copy_gps_longitude
|
||||
entry_gps_ele.text = copy_gps_elevation
|
||||
|
||||
return
|
||||
end
|
||||
@@ -317,14 +330,14 @@ local function paste_gps(image)
|
||||
local sel_images = dt.gui.action_images
|
||||
|
||||
for jj,image in ipairs(sel_images) do
|
||||
if (label_copy_gps_lat.value) then
|
||||
image.latitude = copy_gps_latitude
|
||||
if (checkbox_copy_gps_lat.value) then
|
||||
image.latitude = entry_gps_lat.text
|
||||
end
|
||||
if (label_copy_gps_lon.value) then
|
||||
image.longitude = copy_gps_longitude
|
||||
if (checkbox_copy_gps_lon.value) then
|
||||
image.longitude = entry_gps_lon.text
|
||||
end
|
||||
if (label_copy_gps_ele.value) then
|
||||
image.elevation = copy_gps_elevation
|
||||
if (checkbox_copy_gps_ele.value) then
|
||||
image.elevation = entry_gps_ele.text
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -663,13 +676,25 @@ gT.widget = dt.new_widget("box")
|
||||
tooltip = _("copy GPS data"),
|
||||
clicked_callback = copy_gps
|
||||
},
|
||||
label_copy_gps_lat,
|
||||
label_copy_gps_lon,
|
||||
label_copy_gps_ele,
|
||||
dt.new_widget("box"){
|
||||
orientation = "horizontal",
|
||||
checkbox_copy_gps_lat,
|
||||
entry_gps_lat
|
||||
},
|
||||
dt.new_widget("box"){
|
||||
orientation = "horizontal",
|
||||
checkbox_copy_gps_lon,
|
||||
entry_gps_lon
|
||||
},
|
||||
dt.new_widget("box"){
|
||||
orientation = "horizontal",
|
||||
checkbox_copy_gps_ele,
|
||||
entry_gps_ele
|
||||
},
|
||||
dt.new_widget("button")
|
||||
{
|
||||
label = _("paste GPS data"),
|
||||
tooltip = _("paste GPS data"),
|
||||
label = _("apply GPS data to image"),
|
||||
tooltip = _("apply GPS data to image"),
|
||||
clicked_callback = paste_gps
|
||||
},
|
||||
separator2,--------------------------------------------------------
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -734,16 +773,33 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string,
|
||||
|
||||
local labels = get_colorlabels(image)
|
||||
|
||||
local eyear, emon, eday, ehour, emin, esec, emsec
|
||||
local datetime_taken = ""
|
||||
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
|
||||
datetime_taken = image.exif_datetime_taken
|
||||
else
|
||||
if use_millisecs then
|
||||
datetime_taken = "0000:00:00 00:00:00.0"
|
||||
else
|
||||
datetime_taken = "0000:00:00 00:00:00"
|
||||
end
|
||||
end
|
||||
|
||||
local eyear, emon, eday, ehour, emin, esec, emsec
|
||||
if use_millisecs then
|
||||
eyear, emon, eday, ehour, emin, esec, emsec =
|
||||
string.match(image.exif_datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)%.(%d+)$")
|
||||
string.match(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+)$")
|
||||
string.match(datetime_taken, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)$")
|
||||
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
|
||||
@@ -751,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, -- VERSION.NAME
|
||||
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
|
||||
@@ -781,8 +840,8 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string,
|
||||
eyear, -- EXIF.YEAR
|
||||
string.sub(eyear, 3), -- EXIF.YEAR.SHORT
|
||||
emon, -- EXIF.MONTH
|
||||
os.date("%B", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.LONG
|
||||
os.date("%b", exiftime2systime(image.exif_datetime_taken)), -- EXIF.MONTH.SHORT
|
||||
os.date("%B", exiftime2systime(datetime_taken)), -- EXIF.MONTH.LONG
|
||||
os.date("%b", exiftime2systime(datetime_taken)), -- EXIF.MONTH.SHORT
|
||||
eday, -- EXIF.DAY
|
||||
ehour, -- EXIF.HOUR
|
||||
"", -- EXIF.HOUR.AMPM
|
||||
@@ -819,11 +878,16 @@ function dtutils_string.build_substitute_list(image, sequence, variable_string,
|
||||
"", -- RATING.ICONS - wont be implemented
|
||||
labels, -- LABELS
|
||||
"", -- LABELS.ICONS - wont be implemented
|
||||
image.title, -- TITLE
|
||||
image.description, -- DESCRIPTION
|
||||
image.creator, -- CREATOR
|
||||
image.publisher, -- PUBLISHER
|
||||
image.rights, -- RIGHTS
|
||||
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
|
||||
@@ -845,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
|
||||
@@ -872,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"),
|
||||
@@ -940,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"),
|
||||
@@ -993,8 +1066,13 @@ 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 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))
|
||||
@@ -1226,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
|
||||
|
||||
@@ -34,12 +34,17 @@
|
||||
Bill Ferguson <wpferguson@gmail.com>
|
||||
|
||||
CHANGES
|
||||
|
||||
20251202 - fixed pattern matching bugs affecting Canon EOS R? series
|
||||
styles and styles being applied twice if images are reimported
|
||||
|
||||
Added more debugging statements to pattern matching functions
|
||||
]]
|
||||
|
||||
local dt = require "darktable"
|
||||
local du = require "lib/dtutils"
|
||||
-- local df = require "lib/dtutils.file"
|
||||
-- local ds = require "lib/dtutils.string"
|
||||
local ds = require "lib/dtutils.string"
|
||||
-- local dtsys = require "lib/dtutils.system"
|
||||
local log = require "lib/dtutils.log"
|
||||
-- local debug = require "darktable.debug"
|
||||
@@ -50,9 +55,11 @@ local log = require "lib/dtutils.log"
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "apply_camera_style"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.info
|
||||
local DEFAULT_LOG_LEVEL <const> = log.warn
|
||||
local TMP_DIR <const> = dt.configuration.tmp_dir
|
||||
local STYLE_PREFIX <const> = "_l10n_darktable camera styles|"
|
||||
local STYLE_PREFIX <const> = "_l10n_darktable|_l10n_camera styles|"
|
||||
local MAKER = 3
|
||||
local STYLE = 4
|
||||
|
||||
-- path separator
|
||||
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
@@ -152,22 +159,32 @@ local function process_pattern(pattern)
|
||||
|
||||
local log_level = set_log_level(acs.log_level)
|
||||
|
||||
log.msg(log.debug, "input pattern is " .. pattern)
|
||||
|
||||
pattern = string.lower(pattern)
|
||||
log.msg(log.debug, "lower case pattern is " .. pattern)
|
||||
-- strip off series
|
||||
pattern = string.gsub(pattern, " series$", "?")
|
||||
log.msg(log.debug, "series stripped off pattern is " .. pattern)
|
||||
-- match a character
|
||||
if string.match(pattern, "?$") then
|
||||
if string.match(pattern, "r%?%?$") then
|
||||
-- handle EOS R case
|
||||
pattern = string.gsub(pattern, "?", ".?")
|
||||
pattern = string.gsub(pattern, "%?%?", ".?")
|
||||
log.msg(log.debug, "matched EOS R pattern is " .. pattern)
|
||||
else
|
||||
pattern = string.gsub(pattern, "?", ".")
|
||||
log.msg(log.debug, "didn't match EOS R pattern is " .. pattern)
|
||||
end
|
||||
pattern = string.gsub(pattern, " ", " ?")
|
||||
-- escape dashes
|
||||
pattern = string.gsub(pattern, "%-", "%%-")
|
||||
log.msg(log.debug, "escape dashes conditional pattern is " .. pattern)
|
||||
-- make spaces optional
|
||||
pattern = string.gsub(pattern, " ", " ?")
|
||||
-- until we end up with a set, I'll defer set processing, i.e. [...]
|
||||
--log.msg(log.debug, "make spaces conditional again? pattern is " .. pattern)
|
||||
-- anchor the pattern to ensure we don't short match
|
||||
pattern = "^" .. pattern .. "$"
|
||||
log.msg(log.debug, "anchored pattern is " .. pattern)
|
||||
|
||||
restore_log_level(log_level)
|
||||
|
||||
@@ -223,25 +240,25 @@ local function get_camera_styles()
|
||||
log.msg(log.debug, "got " .. style.name)
|
||||
|
||||
local parts = du.split(style.name, "|")
|
||||
parts[2] = string.lower(parts[2])
|
||||
log.msg(log.debug, "maker is " .. parts[2])
|
||||
parts[MAKER] = string.lower(parts[MAKER])
|
||||
log.msg(log.debug, "maker is " .. parts[MAKER])
|
||||
|
||||
if not acs.styles[parts[2]] then
|
||||
acs.styles[parts[2]] = {}
|
||||
acs.styles[parts[2]]["styles"] = {}
|
||||
acs.styles[parts[2]]["patterns"] = {}
|
||||
if not acs.styles[parts[MAKER]] then
|
||||
acs.styles[parts[MAKER]] = {}
|
||||
acs.styles[parts[MAKER]]["styles"] = {}
|
||||
acs.styles[parts[MAKER]]["patterns"] = {}
|
||||
end
|
||||
if parts[3] then
|
||||
if not string.match(parts[3], "]") then
|
||||
table.insert(acs.styles[parts[2]].styles, style)
|
||||
if parts[STYLE] then
|
||||
if not string.match(parts[STYLE], "]") then
|
||||
table.insert(acs.styles[parts[MAKER]].styles, style)
|
||||
local processed_pattern = process_pattern(parts[#parts])
|
||||
table.insert(acs.styles[parts[2]].patterns, processed_pattern)
|
||||
table.insert(acs.styles[parts[MAKER]].patterns, processed_pattern)
|
||||
log.msg(log.debug, "pattern for " .. style.name .. " is " .. processed_pattern)
|
||||
else
|
||||
local processed_patterns = process_set(parts[3])
|
||||
local processed_patterns = process_set(parts[STYLE])
|
||||
for _, pat in ipairs(processed_patterns) do
|
||||
table.insert(acs.styles[parts[2]].styles, style)
|
||||
table.insert(acs.styles[parts[2]].patterns, pat)
|
||||
table.insert(acs.styles[parts[MAKER]].styles, style)
|
||||
table.insert(acs.styles[parts[MAKER]].patterns, pat)
|
||||
log.msg(log.debug, "pattern for " .. style.name .. " is " .. pat)
|
||||
end
|
||||
end
|
||||
@@ -322,9 +339,11 @@ local function has_style_tag(image, tag_name)
|
||||
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)
|
||||
log.msg(log.debug, "got attached tag " .. tag.name)
|
||||
local cleaned_tag_name = string.gsub(tag_name, "_l10n_", "")
|
||||
log.msg(log.debug, "checking against " .. cleaned_tag_name)
|
||||
if ds.sanitize_lua(tag.name) == cleaned_tag_name then
|
||||
log.msg(log.debug, "matched tag " .. cleaned_tag_name)
|
||||
result = true
|
||||
end
|
||||
end
|
||||
@@ -354,7 +373,7 @@ local function mangle_model(model)
|
||||
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)
|
||||
log.msg(log.debug, "mangle model returning " .. model)
|
||||
end
|
||||
|
||||
restore_log_level(log_level)
|
||||
@@ -385,14 +404,15 @@ local function apply_style_to_images(images)
|
||||
if acs.styles[maker] then
|
||||
local no_match = true
|
||||
for i, pattern in ipairs(acs.styles[maker].patterns) do
|
||||
log.msg(log.debug, "pattern is " .. pattern)
|
||||
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
|
||||
no_match = false
|
||||
log.log_level(loglevel)
|
||||
break
|
||||
end
|
||||
|
||||
267
official/regenerate_thumbnails.lua
Normal file
267
official/regenerate_thumbnails.lua
Normal file
@@ -0,0 +1,267 @@
|
||||
--[[
|
||||
|
||||
regenerate_thumbnails.lua - regenerate mipmap cache for selected images
|
||||
|
||||
Copyright (C) 2025 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/>.
|
||||
]]
|
||||
--[[
|
||||
regenerate_thumbnails - regenerate mipmap cache for selected images
|
||||
|
||||
regenerate_thumbnails drops the cached thumbnail for each selected image
|
||||
and generates a new thumbnail.
|
||||
|
||||
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
|
||||
None
|
||||
|
||||
USAGE
|
||||
* enable the script in script_manager
|
||||
* assign a shortcut, if desired, to apply the script by hovering
|
||||
over a skull and using the shortcut to regenerate the thumbnail
|
||||
|
||||
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"
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A P I C H E C K
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
du.check_min_api_version("7.0.0", MODULE) -- choose the minimum version that contains the features you need
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- I 1 8 N
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local gettext = dt.gettext.gettext
|
||||
|
||||
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 = _("regenerate_thumbnails"), -- visible name of script
|
||||
purpose = _("regenerate mipmap cache for selected 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/regenerate_thumbnails" -- URL to help/documentation
|
||||
}
|
||||
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- C O N S T A N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local MODULE <const> = "regenerate_thumbnails"
|
||||
local DEFAULT_LOG_LEVEL <const> = log.info
|
||||
local TMP_DIR <const> = dt.configuration.tmp_dir
|
||||
|
||||
-- path separator
|
||||
local PS <const> = dt.configuration.running_os == "windows" and "\\" or "/"
|
||||
|
||||
-- command separator
|
||||
local CS <const> = dt.configuration.running_os == "windows" and "&" or ";"
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- L O G L E V E L
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
log.log_level(DEFAULT_LOG_LEVEL)
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- N A M E S P A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local regenerate_thumbnails = {}
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- G L O B A L V A R I A B L E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- P R E F E R E N C E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- A L I A S E S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
local namespace = regenerate_thumbnails
|
||||
local rt = regenerate_thumbnails
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- F U N C T I O N S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-------------------
|
||||
-- helper functions
|
||||
-------------------
|
||||
|
||||
local function set_log_level(level)
|
||||
local old_log_level = log.log_level()
|
||||
log.log_level(level)
|
||||
return old_log_level
|
||||
end
|
||||
|
||||
local function restore_log_level(level)
|
||||
log.log_level(level)
|
||||
end
|
||||
|
||||
local function pref_read(name, pref_type)
|
||||
local old_log_level = set_log_level(sm.log_level)
|
||||
|
||||
log.msg(log.debug, "name is " .. name .. " and type is " .. pref_type)
|
||||
|
||||
local val = dt.preferences.read(MODULE, name, pref_type)
|
||||
|
||||
log.msg(log.debug, "read value " .. tostring(val))
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
return val
|
||||
end
|
||||
|
||||
local function pref_write(name, pref_type, value)
|
||||
local old_log_level = set_log_level(sm.log_level)
|
||||
|
||||
log.msg(log.debug, "writing value " .. tostring(value) .. " for name " .. name)
|
||||
|
||||
dt.preferences.write(MODULE, name, pref_type, value)
|
||||
|
||||
restore_log_level(old_log_level)
|
||||
end
|
||||
|
||||
local function stop_job()
|
||||
rt.job.valid = false
|
||||
end
|
||||
|
||||
local function generate_thumbnails(images)
|
||||
local has_job = false
|
||||
|
||||
if #images > 50 then
|
||||
rt.job = dt.gui.create_job("regenerating thumbnails", true, stop_job)
|
||||
has_job = true
|
||||
end
|
||||
|
||||
for count, image in ipairs(images) do
|
||||
image:drop_cache()
|
||||
image:generate_cache(true, 1, 3)
|
||||
if has_job then
|
||||
if not rt.job.valid then
|
||||
return
|
||||
end
|
||||
if count % 10 == 0 then
|
||||
rt.job.percent = count / #images
|
||||
end
|
||||
end
|
||||
end
|
||||
if has_job then
|
||||
rt.job.valid = false
|
||||
end
|
||||
end
|
||||
|
||||
local function update_button_sensitivity()
|
||||
if #dt.gui.action_images > 0 then
|
||||
dt.gui.libs.image.set_sensitive(MODULE, true)
|
||||
else
|
||||
dt.gui.libs.image.set_sensitive(MODULE, false)
|
||||
end
|
||||
end
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- M A I N P R O G R A M
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- U S E R I N T E R F A C E
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.gui.libs.image.register_action(
|
||||
MODULE,
|
||||
_("generate thumbnails"),
|
||||
function(event, images)
|
||||
generate_thumbnails(images)
|
||||
end,
|
||||
_("generate thumbnails")
|
||||
)
|
||||
|
||||
update_button_sensitivity()
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- 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, "selection-changed")
|
||||
dt.destroy_event(MODULE, "mouse-over-image-changed")
|
||||
dt.gui.libs.image.destroy_action(MODULE, "regenerate thumbnails")
|
||||
end
|
||||
|
||||
script_data.destroy = destroy
|
||||
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
-- E V E N T S
|
||||
-- - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
dt.register_event(MODULE, "shortcut",
|
||||
function(event, shortcut)
|
||||
local images = dt.gui.action_images
|
||||
if images then
|
||||
generate_thumbnails(images)
|
||||
end
|
||||
end, "regenerate thumbnails"
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "selection-changed",
|
||||
function(event)
|
||||
update_button_sensitivity()
|
||||
end
|
||||
)
|
||||
|
||||
dt.register_event(MODULE, "mouse-over-image-changed",
|
||||
function(event, image)
|
||||
if #dt.gui.selection() < 1 then
|
||||
if image then
|
||||
dt.gui.libs.image.set_sensitive(MODULE, true)
|
||||
else
|
||||
dt.gui.libs.image.set_sensitive(MODULE, false)
|
||||
end
|
||||
end
|
||||
end
|
||||
)
|
||||
|
||||
return script_data
|
||||
@@ -44,6 +44,10 @@ local function _(msg)
|
||||
return gettext(msg)
|
||||
end
|
||||
|
||||
local NIX_OPEN <const> = "xdg-open "
|
||||
local MAC_OPEN <const> = "open "
|
||||
local WIN_OPEN <const> = "start "
|
||||
|
||||
-- return data structure for script_manager
|
||||
|
||||
local script_data = {}
|
||||
@@ -63,8 +67,8 @@ script_data.show = nil -- only required for libs since the destroy_method only h
|
||||
dt.preferences.register
|
||||
("selection_to_pdf","Open with","string",
|
||||
_("a pdf viewer"),
|
||||
_("can be an absolute pathname or the tool may be in the PATH"),
|
||||
"xdg-open")
|
||||
_("can be an absolute pathname or the tool may be in the PATH\nif not specified the system default will be used to open the pdf"),
|
||||
"")
|
||||
|
||||
local title_widget = dt.new_widget("entry") {
|
||||
placeholder = _("title")
|
||||
@@ -115,8 +119,9 @@ local function thumbnail(latexfile,i,image,file)
|
||||
-- fact is that latex will get confused if the filename has multiple dots.
|
||||
-- so \includegraphics{file.01.jpg} wont work. We need to output the filename
|
||||
-- and extention separated, e.g: \includegraphics{{file.01}.jpg}
|
||||
local filenoext=string.gsub(file, "(.*)(%..*)", "%1")
|
||||
local ext=string.gsub(file, "(.*)(%..*)", "%2")
|
||||
|
||||
local filenoext = string.gsub(file, "(.*)(%..*)", "%1")
|
||||
local ext = string.gsub(file, "(.*)(%..*)", "%2")
|
||||
my_write(latexfile,"\\begin{minipage}[b]{"..width.."\\textwidth}\n")
|
||||
my_write(latexfile,"\\includegraphics[width=\\textwidth]{{"..filenoext.."}"..ext.."}\\newline\n")
|
||||
my_write(latexfile,"\\centering{"..i..": \\verb|"..title.."|}\n")
|
||||
@@ -138,7 +143,7 @@ dt.register_storage("export_pdf", _("export thumbnails to pdf"),
|
||||
end
|
||||
local thumbs_per_line = no_of_thumbs_widget.value
|
||||
thumbs_per_line = tonumber(thumbs_per_line)
|
||||
width = (1/thumbs_per_line) - 0.01
|
||||
width = (1 / thumbs_per_line) - 0.01
|
||||
|
||||
local preamble = [[
|
||||
\documentclass[a4paper,10pt]{article}
|
||||
@@ -148,31 +153,31 @@ dt.register_storage("export_pdf", _("export thumbnails to pdf"),
|
||||
\usepackage{geometry}
|
||||
\geometry{a4paper,left=5mm,right=5mm, top=5mm, bottom=5mm}
|
||||
\begin{document}
|
||||
{\Large\bfseries ]]..my_title..[[} \\
|
||||
{\Large\bfseries ]] .. my_title .. [[} \\
|
||||
\bigskip\bigskip
|
||||
]]
|
||||
|
||||
local errmsg
|
||||
local latexfile
|
||||
latexfile,errmsg=io.open(filename,"w")
|
||||
latexfile,errmsg = io.open(filename, "w")
|
||||
if not latexfile then
|
||||
error(errmsg)
|
||||
end
|
||||
my_write(latexfile,preamble)
|
||||
my_write(latexfile, preamble)
|
||||
local i = 1
|
||||
for img,file in pairs(image_table) do
|
||||
thumbnail(latexfile,i,img,file)
|
||||
for img, file in pairs(image_table) do
|
||||
thumbnail(latexfile, i, img, file)
|
||||
if i % thumbs_per_line == 0 then
|
||||
my_write(latexfile,"\n\\bigskip\n")
|
||||
my_write(latexfile, "\n\\bigskip\n")
|
||||
end
|
||||
i = i+1
|
||||
i = i + 1
|
||||
end
|
||||
my_write(latexfile,ending)
|
||||
latexfile:close()
|
||||
|
||||
-- convert to PDF
|
||||
local dir=string.gsub(filename, "(.*/)(.*)", "%1")
|
||||
local locfile=string.gsub(filename, "(.*/)(.*)", "%2")
|
||||
local dir = string.gsub(filename, "(.*/)(.*)", "%1")
|
||||
local locfile = string.gsub(filename, "(.*/)(.*)", "%2")
|
||||
local command = "pdflatex -halt-on-error -output-directory "..dir.." "..locfile
|
||||
local result = dt.control.execute(command)
|
||||
if result ~= 0 then
|
||||
@@ -181,9 +186,18 @@ dt.register_storage("export_pdf", _("export thumbnails to pdf"),
|
||||
end
|
||||
|
||||
-- open the PDF
|
||||
local pdffile=string.gsub(filename, ".tex", ".pdf")
|
||||
local pdffile = string.gsub(filename, ".tex", ".pdf")
|
||||
command = dt.preferences.read("selection_to_pdf","Open with","string")
|
||||
command = command.." "..pdffile
|
||||
if command == "" then
|
||||
if dt.configuration.running_os == "windows" then
|
||||
command = WIN_OPEN
|
||||
elseif dt.configuration.running_os == "macos" then
|
||||
command = MAC_OPEN
|
||||
else
|
||||
command = NIX_OPEN
|
||||
end
|
||||
end
|
||||
command = command .. " " .. pdffile
|
||||
local result = dt.control.execute(command)
|
||||
if result ~= 0 then
|
||||
dt.print(_("problem running pdf viewer")) -- this one is probably usefull to the user
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user