Skip to content

Instantly share code, notes, and snippets.

@vredesbyyrd
Created May 24, 2023 20:15
Show Gist options
  • Save vredesbyyrd/b826e876763eb39337a1dc4b7c13ab10 to your computer and use it in GitHub Desktop.
Save vredesbyyrd/b826e876763eb39337a1dc4b7c13ab10 to your computer and use it in GitHub Desktop.
--[[
This file is part of darktable,
copyright (c)2021 Bill Ferguson <wpferguson@gmail.com>
darktable 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.
darktable 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 darktable. If not, see <http://www.gnu.org/licenses/>.
]]
--[[
metadata_manager.lua - a tool for adding additional metadata to the image information
metadata_manager allows a user to extend the image information display by adding
additional Exif tags to query and display. The Exif tags are those used by exiv2.
To see what tags are available, run "exiv2 -PE <image file>". A list of the Exif tags
used by the image are printed along with their values.
USAGE
* start metadata_manager
* specify a display name for the information in the metadata display name field
* enter the Exif tag for the information in the exiv2 metadata tag field
* click the create metadata information button
* repeat as many times as necesssary.
* select the names of the metadata you wish to activate
* click the activate button to add them to the image information display
and start monitoring which image the mouse is over. The image information
display will update every time a new image is hovered over
* To remove metadata fields from the image information, select the names to
deactivate and click the deactivate button.
* To remove an item from the list, select it by itself and click the remove from
list button.
ADDITIONAL SOFTWARE REQUIRED
exiv2
]]
local dt = require "darktable"
local du = require "lib/dtutils"
local df = require "lib/dtutils.file"
local ds = require "lib/dtutils.string"
local log = require "lib/dtutils.log"
local function dummy(str)
return str
end
--ds.sanitize_lua = dummy
local metadata_manager = {}
local mm = metadata_manager
mm.event_registered = false
mm.module_installed = false
mm.cache = ""
mm.widgets = {}
mm.metadata_widgets = {}
mm.metadata_pairs = {}
mm.metadata = {}
mm.callbacks = {}
mm.query_string = " -PEkt"
mm.exiv2 = df.check_if_bin_exists("exiv2")
mm.cache_active = false
mm.last_image = nil
mm.run = false
local log_level = log.log_level()
log.log_level(log.debug)
local function save_preferences()
local pref = ""
for _, md in ipairs(mm.metadata_pairs) do
if #pref > 5 then
pref = string.format("%s,", pref)
end
pref = string.format("%s{%s,%s,%s,%s,%s}", pref, md.name, md.tag, tostring(md.selected), tostring(md.registered), md.query)
end
log.msg(log.info, "saving pref string " .. pref)
dt.preferences.write("metadata_manager", "mdpairs", "string", pref)
end
local function update_query_string()
local query_string = " -PEkt"
for _, md in ipairs(mm.metadata_pairs) do
if md.registered then
query_string = query_string .. md.query
end
end
mm.query_string = query_string
end
local function update_cache(image)
if image ~= mm.last_image then
log.msg(log.debug, "generating cache for new image ")
--log.msg(log.debug, "new image id is " .. tostring(image))
if mm.exiv2 then
if mm.query_string and image then
--local cmd = mm.exiv2 .. mm.query_string .. " " .. string.format('%q', image.path) .. "/" .. image.filename
local cmd = string.format('%s%s %q', mm.exiv2, mm.query_string, image.path .. "/" .. image.filename)
log.msg(log.debug, "cmd is " .. cmd)
local p = io.popen(cmd)
if p then
mm.cache = p:read("*all")
p:close()
mm.last_image = image
else
log.msg(log.error, "exiv2 encountered a problem and could not read the file")
end
else
log.msg(log.warn, "no keys to query, not updating...")
end
else
log.msg(log.error, "exiv2 executable not found")
end
end
end
local function create_callback(key)
return function(image)
local result = "-"
update_cache(image)
local match_string = ds.sanitize_lua(key) .. "%s+(.-)\n"
log.msg(log.debug, "mm.cache is " .. mm.cache .. " and match_string is " .. match_string)
if mm.cache then
local a = string.match(mm.cache, match_string)
if a then
result = a
end
end
log.msg(log.debug, "returning result " .. result)
return result
end
end
local function add_metadata_pair(name, tag)
table.insert(mm.metadata_pairs, {name = name, tag = tag, callback = create_callback(tag), selected = true, registered = false, query = " -g " .. tag})
end
local function remove_metadata_pair(selected)
-- unload the box from the bottom so the widgets don't change
-- position while you are deleting
for i = #mm.widgets.metadata_box, 1, -1 do
log.msg(log.debug, "clearing widget " .. i)
mm.widgets.metadata_box[i] = nil
end
for _, name in ipairs(selected) do
for i, md in ipairs(mm.metadata_pairs) do
if string.match(md.name, ds.sanitize_lua(name)) then
if md.registered then
log.msg(log.info, "removing " .. md.name .. " from image information")
dt.gui.libs.metadata_view.destroy_info(md.name)
end
table.remove(mm.metadata_pairs, i)
break
end
end
for i, widget in ipairs(mm.metadata_widgets) do
if string.match(widget.label, ds.sanitize_lua(name)) then
table.remove(mm.metadata_widgets, i) -- we should destroy the widget but there's not a function to do that... yet
break
end
end
end
log.msg(log.debug, "reloading metadata choices")
for i, widget in ipairs(mm.metadata_widgets) do
table.insert(mm.widgets.metadata_box, widget)
end
update_query_string()
save_preferences()
end
local function clear_image_info()
log.msg(log.info, "clearing all lua widgets from image information")
for _, md in ipairs(mm.metadata_pairs) do
if md.registered then
dt.gui.libs.metadata_view.destroy_info(md.name)
md.registered = false
end
end
log.msg(log.debug, "reset query string")
mm.query_string = " -PEkt"
end
local function load_image_info()
log.msg(log.info, "adding lua widgets to image information")
local loaded = false
for _, md in ipairs(mm.metadata_pairs) do
if md.registered then
log.msg(log.debug, "adding " .. md.name)
dt.gui.libs.metadata_view.register_info(md.name, md.callback)
loaded = true
end
end
update_query_string()
end
local function update_image_info(add)
local loaded = false
for _, md in ipairs(mm.metadata_pairs) do
if md.selected then
if add then
if not md.registered then
log.msg(log.debug, "adding " .. md.name .. " to image information display")
dt.gui.libs.metadata_view.register_info(md.name, md.callback)
md.registered = true
loaded = true
end
else -- remove
log.msg(log.debug, "removing " .. md.name .. " from image information display")
dt.gui.libs.metadata_view.destroy_info(md.name)
md.registered = false
end
end
end
update_query_string()
save_preferences()
end
local function update_button_sensitivity()
local count = 0
local selected = 0
for _, widget in ipairs(mm.metadata_widgets) do
count = count + 1
if widget.value then
selected = selected + 1
end
end
log.msg(log.debug, selected .. " buttons are selected of " .. count)
if selected > 0 then
mm.widgets.select_none.sensitive = true
mm.widgets.invert_selection.sensitive = true
mm.widgets.activate.sensitive = true
mm.widgets.deactivate.sensitive = true
mm.widgets.remove.sensitive = true
else
mm.widgets.select_none.sensitive = false
mm.widgets.invert_selection.sensitive = false
mm.widgets.activate.sensitive = false
mm.widgets.deactivate.sensitive = false
mm.widgets.remove.sensitive = false
end
if selected == count then
mm.widgets.select_all.sensitive = false
else
mm.widgets.select_all.sensitive = true
end
end
local function create_new_metadata_selector(name, tag, selected)
local sel = true
if selected ~= nil then
sel = selected
end
local widget = dt.new_widget("check_button"){
label = name,
tooltip = tag,
value = true,
clicked_callback = function(this)
if mm.run then
for _, md in ipairs(mm.metadata_pairs) do
if string.match(this.label, ds.sanitize_lua(md.name)) then
md.selected = this.value
end
end
save_preferences()
update_button_sensitivity()
end
end
}
widget.value = sel
return widget
end
local function load_preferences()
local pref = dt.preferences.read("metadata_manager", "mdpairs", "string")
log.msg(log.debug, "got pref string " .. pref)
if #pref > 4 then
local e = -1
local s = true
local str = ""
while(s) do
s, e, str = string.find(pref, "({.-})", e + 1)
if s then
str = str.gsub(str, "[{}]", "")
local el = du.split(str, ",")
if string.match(el[3], "true") then
el[3] = true
else
el[3] = false
end
if string.match(el[4], "true") then
el[4] = true
else
el[4] = false
end
table.insert(mm.metadata_pairs, {name = el[1], tag = el[2], selected = el[3], registered = el[4], callback = create_callback(el[2]), query = el[5]})
table.insert(mm.metadata_widgets, create_new_metadata_selector(el[1], el[2], el[3]))
table.insert(mm.widgets.metadata_box, mm.metadata_widgets[#mm.metadata_widgets])
end
end
load_image_info()
end
end
local function install_module()
if not mm.module_installed then
dt.register_lib(
"metadata_manager", -- Module name
"metadata manager", -- Visible name
true, -- expandable
false, -- resetable
{[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers
mm.widgets.main_box,
nil,-- view_enter
nil -- view_leave
)
mm.module_installed = true
end
load_preferences()
mm.run = true
end
local function destroy()
save_preferences()
clear_image_info()
dt.gui.libs.metadata_manager.visible = false
end
local function restart()
load_preferences()
dt.gui.libs.metadata_manager.visible = false
load_image_info()
end
mm.widgets.metadata_name = dt.new_widget("entry"){
tooltip = "enter name to display for metadata",
placeholder = "enter display name for metadata",
text = "",
}
mm.widgets.metadata_tag = dt.new_widget("entry"){
tooltip = "enter exiv2 tag for the metadata",
placeholder = "enter exiv2 tag for the metadata",
text = ""
}
mm.widgets.create_metadata = dt.new_widget("button"){
label = "create metadata information",
tooltip = "add a metadata name and tag for selection",
clicked_callback = function(this)
add_metadata_pair(mm.widgets.metadata_name.text, mm.widgets.metadata_tag.text)
mm.run = false
table.insert(mm.metadata_widgets, create_new_metadata_selector(mm.widgets.metadata_name.text, mm.widgets.metadata_tag.text))
mm.run = true
table.insert(mm.widgets.metadata_box, mm.metadata_widgets[#mm.metadata_widgets])
update_button_sensitivity()
-- push widget into widget box
mm.widgets.metadata_name.text = ""
mm.widgets.metadata_tag.text = ""
end
}
mm.widgets.select_all = dt.new_widget("button"){
label = "select all",
tooltip = "select all metadata entries",
clicked_callback = function(this)
for _, widget in ipairs(mm.metadata_widgets) do
widget.value = true
end
save_preferences()
end
}
mm.widgets.select_none = dt.new_widget("button"){
label = "select none",
tooltip = "clear metadata selections",
clicked_callback = function(this)
for _, widget in ipairs(mm.metadata_widgets) do
widget.value = false
end
save_preferences()
end
}
mm.widgets.invert_selection = dt.new_widget("button"){
label = "invert_selection",
tooltip = "invert selected metatdata",
clicked_callback = function(this)
for _, widget in ipairs(mm.metadata_widgets) do
if widget.value then
widget.value = false
else
widget.value = true
end
end
save_preferences()
end
}
mm.widgets.activate = dt.new_widget("button"){
label = "activate",
tooltip = "add selected metadata to image information",
clicked_callback = function(this)
update_image_info(true)
end
}
mm.widgets.deactivate = dt.new_widget("button"){
label = "deactivate",
tooltip = "remove selected metadata from image information",
clicked_callback = function(this)
update_image_info(false)
end
}
mm.widgets.remove = dt.new_widget("button"){
label = "remove from list",
tooltip = "remove metadata selector from list",
clicked_callback = function(this)
local selected = {}
for _, md in ipairs(mm.metadata_pairs) do
if md.registered then
update_image_info(false)
end
if md.selected then
table.insert(selected, md.name)
end
end
remove_metadata_pair(selected)
end
}
mm.widgets.metadata_box = dt.new_widget("box"){
orientation = "vertical",
}
mm.widgets.button_box = dt.new_widget("box"){
orientation = "vertical",
dt.new_widget("section_label"){label = "add metadata selectors"},
dt.new_widget("label"){label = "metadata display name"},
mm.widgets.metadata_name,
dt.new_widget("label"){label = "exiv2 metadata tag"},
mm.widgets.metadata_tag,
mm.widgets.create_metadata,
dt.new_widget("section_label"){label = "select metadata"},
dt.new_widget("box"){
orientation = "horizontal",
mm.widgets.select_all,
mm.widgets.select_none,
},
mm.widgets.invert_selection,
dt.new_widget("section_label"){label = "selected metadata"},
dt.new_widget("box"){
orientation = "horizontal",
mm.widgets.activate,
mm.widgets.deactivate
},
mm.widgets.remove
}
mm.widgets.main_box = dt.new_widget("box"){
orientation = "vertical",
mm.widgets.metadata_box,
dt.new_widget("separator"){},
mm.widgets.button_box
}
if dt.gui.current_view().id == "lighttable" then
install_module()
else
if not mm.event_registered then
dt.register_event(
"view-changed",
function(event, old_view, new_view)
if new_view.name == "lighttable" and old_view.name == "darkroom" then
install_module()
end
end
)
mm.event_registered = true
end
end
local script_data = {}
script_data.destroy = destroy
script_data.destroy_method = "hide"
script_data.restart = restart
log.log_level(log_level)
return script_data
@wpferguson
Copy link

After 494 add a line

script_data.show = restart

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment