official/selection2collection - create a temporary collection from

a selection.  Temporary collections are removed when darktable exits.
Temporary collections can be kept by setting a preference in the Lua options.
This commit is contained in:
Bill Ferguson
2026-03-24 20:29:35 -04:00
parent d197851478
commit cb209d1d56

View File

@@ -0,0 +1,354 @@
--[[
selection2collection.lua - create a temporary collection from a selection
Copyright (C) 2026 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/>.
]]
--[[
selection2collection - create a temporary collection from a selection
selection2collection creates a temporary collection, using functional
tagging, from a selection. The collection lasts until darktable exits
and then the collections are removed, but not the images.
To create the temporary collection, a function tag (darktable|functional|s2c)
tag with a date and a time is applied to the selected images. The collection
module filters on the tag to display the connection. More than one temporary
collection can be created and collections can be changed by selecting the
appropriate tag.
The tags are autmatically destroyed when darktable exits, thus removing the
collections. There is a preference in the Lua options to keep the collections
across darktable runs.
ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT
None
USAGE
* start from script_manager
* make a selection
* click the create temporary collection button the the actions on selected images
module
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("9.6.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 = _("selection2collection"), -- visible name of script
purpose = _("create a temporary collection from a selection"), -- purpose of script
author = "Bill Ferguson <wpferguson@gmail.com>", -- your name and optionally e-mail address
help = "https://docs.darktable.org/lua/stable/lua.scripts.manual/scripts/official/selection2collection/" -- URL to help/documentation
}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- C O N S T A N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
local MODULE <const> = "selection2collection"
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 ";"
-- script specific
local TAG_HEADER <const> = "darktable|functional|s2c|"
-- - - - - - - - - - - - - - - - - - - - - - - -
-- L O G L E V E L
-- - - - - - - - - - - - - - - - - - - - - - - -
log.log_level(DEFAULT_LOG_LEVEL)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- N A M E S P A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
local selection2collection = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- G L O B A L V A R I A B L E S
-- - - - - - - - - - - - - - - - - - - - - - - -
selection2collection.log_level = DEFAULT_LOG_LEVEL
selection2collection.keep_collections = false
selection2collection.collection_rules = {}
-- - - - - - - - - - - - - - - - - - - - - - - -
-- P R E F E R E N C E S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.preferences.register(MODULE, "keep_collections", "bool",
script_data.metadata.name .. _(" keep collections created from selection"),
_("keep collections created from selections"), false)
-- - - - - - - - - - - - - - - - - - - - - - - -
-- A L I A S E S
-- - - - - - - - - - - - - - - - - - - - - - - -
local namespace = selection2collection
local s2c = selection2collection
-- - - - - - - - - - - - - - - - - - - - - - - -
-- F U N C T I O N S
-- - - - - - - - - - - - - - - - - - - - - - - -
-------------------
-- helper functions
-------------------
local function set_log_level(level)
if not level then
level = namespace.log_level
end
local old_log_level = log.log_level()
log.log_level(level)
return old_log_level
end
local function restore_log_level(level)
if not level then
level = namespace.log_level
end
log.log_level(level)
end
local function reset_log_level()
log.log_level(DEFAULT_LOG_LEVEL)
namespace.log_level = DEFAULT_LOG_LEVEL
end
local function pref_read(name, pref_type)
local old_log_level = set_log_level(namespace.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(namespace.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
-------------------
-- program functions
-------------------
local function clear_collections()
s2c.keep_collections = pref_read("keep_collections", "bool")
if not s2c.keep_collections then
for _, tag in ipairs(dt.tags) do
if string.match(tag.name, TAG_HEADER) then
tag:delete()
end
end
end
end
local function create_collection_tag()
local t = os.date("*t")
local ymd = string.format("%4d:%02d:%02d", t.year, t.month, t.day)
local hms = string.format("%02d:%02d:%02d", t.hour, t.min, t.sec)
local tag_string = TAG_HEADER .. ymd .. "|" .. hms
dt.print_log("tag_string is " .. tag_string)
local tag = dt.tags.create(tag_string)
return tag
end
local function display_collection(tag_name)
local collection_rules = dt.gui.libs.collect.filter()
local rule = dt.gui.libs.collect.new_rule()
rule.mode = "DT_LIB_COLLECT_MODE_AND"
rule.data = tostring(tag_name)
rule.item = "DT_COLLECTION_PROP_TAG"
table.insert(collection_rules, rule)
-- dt.gui.libs.collect.filter returns the previous rule set when you install another
s2c.collection_rules = dt.gui.libs.collect.filter(collection_rules)
end
local function create_collection()
local images = dt.gui.selection()
if #images > 1 then
local tag = create_collection_tag()
if tag then
for _, image in ipairs(images) do
image:attach_tag(tag)
end
end
display_collection(tag.name)
else
log.msg(log.screen, _("not enough images to create a collection"))
end
end
local function communications_handler(sender, receiver, message)
if receiver == MODULE then
if message == "create" then
create_collection()
end
end
end
local function set_button_sensitive(images)
if #images > 1 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
-- - - - - - - - - - - - - - - - - - - - - - - -
s2c.keep_collections = pref_read("keep_collections", "bool")
-- - - - - - - - - - - - - - - - - - - - - - - -
-- U S E R I N T E R F A C E
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.gui.libs.image.register_action(
MODULE, _("create temporary collection"),
function(event, images)
create_collection()
end,
_("convert selection to collection")
)
set_button_sensitive(dt.gui.selection())
-- - - - - - - - - - - - - - - - - - - - - - - -
-- 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, "inter-script-communication")
dt.destroy_event(MODULE, "exit")
dt.destroy_event(MODULE, "selection_changed")
dt.gui.libs.destroy_action(MODULE, "create_collection")
end
script_data.destroy = destroy
-- - - - - - - - - - - - - - - - - - - - - - - -
-- E V E N T S
-- - - - - - - - - - - - - - - - - - - - - - - -
dt.register_event(MODULE, "inter-script-communication",
function(event, sender, receiver, message)
communications_handler(sender, receiver, message)
end
)
if not dt.query_event(MODULE, "shortcut") then -- skip if shortcut already registered
dt.register_event(MODULE, "shortcut",
function(event, shortcut)
create_collection()
end, _("convert selection to collection")
)
end
dt.register_event(MODULE, "exit",
function(event)
if not s2c.keep_collections then
dt.gui.libs.collect.filter(s2c.collection_rules)
clear_collections()
end
end
)
dt.register_event(MODULE, "selection-changed",
function(event)
set_button_sensitive(dt.gui.selection())
end
)
return script_data