diff --git a/README.md b/README.md index 6b0e20f..8ca96db 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,7 @@ CollectHelper|Yes|LMW|Add buttons to selected images module to manipulate the co copy_attach_detach_tags|Yes|LMW|Copy and paste tags from/to images cr2hdr|Yes|L|Process image created with Magic Lantern Dual ISO enfuseAdvanced|No|LMW|Merge multiple images into Dynamic Range Increase (DRI) or Depth From Focus (DFF) images +exportLUT|Yes|LMW|Create a LUT from a style and export it ext_editor|No|LW|Export pictures to collection and edit them with up to nine user-defined external editors face_recognition|No|LM|Identify and tag images using facial recognition fujifilm_ratings|No|LM|Support importing Fujifilm ratings @@ -46,14 +47,16 @@ geoJSON_export|No|L|Create a geo JSON script with thumbnails for use in ... geoToolbox|No|LMW|A toolbox of geo functions gimp|No|LMW|Open an image in GIMP for editing and return the result gpx_export|No|LMW|Export a GPX track file from selected images GPS data -HDRMerge|No|LMW|Combind the selected images into an HDR DNG and return the result +HDRMerge|No|LMW|Combine the selected images into an HDR DNG and return the result hugin|No|LMW|Combine selected images into a panorama and return the result image_stack|No|LMW|Combine a stack of images to remove noise or transient objects +image_time|Yes|LMW|Adjust the EXIF image time kml_export|No|L|Export photos with a KML file for usage in Google Earth LabelsToTags|Yes|LMW|Apply tags based on color labels and ratings OpenInExplorer|No|LMW|Open the selected images in the system file manager passport_guide|Yes|LMW|Add passport cropping guide to darkroom crop tool pdf_slideshow|No|LM|Export images to a PDF slideshow +[photils](https://github.com/scheckmedia/photils-dt)|No|LM|Automatic tag suggestions for your images quicktag|Yes|LMW|Create shortcuts for quickly applying tags rate_group|Yes|LMW|Apply or remove a star rating from grouped images rename-tags|Yes|LMW|Change a tag name @@ -116,14 +119,24 @@ are met as well as providing an easy update path. Single scripts listed as stand ### snap packages -The snap version of darktable comes with lua included starting with version 2.4.3snap2. It is currently in the edge channel, but should reach the stable channel soon. +The snap version of darktable comes with lua included starting with version 2.4.3snap2. Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: cd ~/snap/darktable/current git clone https://github.com/darktable-org/lua-scripts.git lua -### flatpak and appimage packages +### flatpak packages + +Flatpak packages now use the internal lua interpreter. + + +Ensure git is installed on your system. If it isn't, use the package manager to install it. Then open a terminal and: + + cd ~/.var/app/org.darktable.Darktable/config/darktable + git clone https://github.com/darktable-org/lua-scripts.git lua + +### appimage packages These packages run in their own environment and don't have access to a lua interpreter, therefore the scripts can't run. The packagers could enable the internal interpreter, or allow the package to link the interpreter from the operating system, or bundle a copy of lua with the package. If you use one of these packages and wish to use the lua scripts, please contact the package maintainer and suggest the above fixes. @@ -138,8 +151,6 @@ Ensure git is installed on your system. If it isn't, use the package manager to Ensure git is installed on your system. Git can be obtained from https://gitforwindows.org/, as well as other places. If you use the gitforwindows.org distribution, install the Git Bash Shell also as it will aid in debugging the scripts if necessary. Then open a command prompt and run: -Open a command prompt. - cd %LOCALAPPDATA%\darktable git clone https://github.com/darktable-org/lua-scripts.git lua @@ -149,6 +160,24 @@ 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 "/"` 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: + +### Linux or MacOS + + echo 'require "tools/script_manager"' > ~/.config/darktable/luarc + +### Windows + + echo "require 'tools/script_manager'" > %LOCALAPPDATA%\darktable\luarc + +### Snap + + echo 'require "tools/script_manager"' > ~/snap/darktable/current/luarc + +### Flatpak + + echo require "tools/script_manager"' > ~/.var/app/org.darktable.Darktable/config/darktable/luarc + You can also create or add lines to the luarc file from the command line: `echo 'require "contrib/gimp"' > ~/.config/darktable/luarc` to create the file with a gimp entry\ @@ -172,6 +201,12 @@ To update the script repository, open a terminal or command prompt and do the fo cd ~/snap/darktable/current/lua git pull + +### Flatpak + + cd ~/.var/app/org.darktable.Darktable/config/darktable/lua + git pull + ### Linux and MacOS cd ~/.config/darktable/lua/ @@ -202,13 +237,17 @@ Running darktable with Lua debugging enabled provides more information about wha Open a terminal and start darktable with the command `snap run darktable -d lua`. This provides debugging information to give you insight into what is happening. -### Linux and MacOS +### Linux Open a terminal and start darktable with the command `darktable -d lua`. This provides debugging information to give you insight into what is happening. +### MacOS + +Open a terminal and start darktable with the command `/Applications/darktable.app/Contents/MacOS/darktable -d lua`. This provides debugging information to give you insight into what is happening. + ### Windows -Open the Git Bash Shell. Start darktable with the command `/c/Program\ Files/darktable/bin/darktable -d lua`. This provides debugging information to give you insight into what is happening. +Open a command prompt. Start darktable with the command "C:\Program Files\darktable\bin\darktable" -d lua > log.txt. This provides debugging information to give you insight into what is happening. ## Contributing diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 0d92915..4198952 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -45,6 +45,17 @@ local function _(msgid) return gettext.dgettext("AutoGrouper", msgid) end +local Ag = {} +Ag.module_installed = false +Ag.event_registered = false + +local GUI = { + gap = {}, + selected = {}, + collection = {} +} + + local function InRange(test, low, high) --tests if test value is within range of low and high (inclusive) if test >= low and test <= high then return true @@ -111,12 +122,26 @@ local function main(on_collection) end end +local function install_module() + if not Ag.module_installed then + dt.register_lib( + 'AutoGroup_Lib', -- Module name + _('auto group'), -- name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers + dt.new_widget("box"){ + orientation = "vertical", + GUI.gap, + GUI.selected, + GUI.collection + } + ) + Ag.module_installed = true + end +end + -- GUI -- -GUI = { - gap = {}, - selected = {}, - collection = {} -} temp = dt.preferences.read(MOD, 'active_gap', 'integer') if not InRange(temp, 1, 86400) then temp = 3 end GUI.gap = dt.new_widget('slider'){ @@ -143,16 +168,20 @@ GUI.collection = dt.new_widget("button"){ tooltip =_('auto group the entire collection'), clicked_callback = function() main(true) end } -dt.register_lib( - 'AutoGroup_Lib', -- Module name - _('auto group'), -- name - true, -- expandable - true, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers - dt.new_widget("box"){ - orientation = "vertical", - GUI.gap, - GUI.selected, - GUI.collection - } -) + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not Ag.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 + ) + Ag.event_registered = true + end +end + diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index ad7dd3a..e9e5d03 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -94,6 +94,10 @@ local GUI = { --GUI Elements Table } } +HDRM.module_installed = false +HDRM.event_registered = false + + --Detect User Styles-- local styles = dt.styles local styles_count = 1 -- 'none' = 1 @@ -264,6 +268,23 @@ local function main() end +local function install_module() + if not HDRM.module_installed then + dt.register_lib( -- register HDRMerge module + 'HDRMerge_Lib', -- Module name + _('HDRMerge'), -- name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers + dt.new_widget('box'){ + orientation = 'vertical', + GUI.stack + } + ) + HDRM.module_installed = true + end +end + -- GUI Elements -- local lbl_hdr = dt.new_widget('section_label'){ label = _('HDRMerge options') @@ -411,14 +432,18 @@ else GUI.stack.active = 2 end -dt.register_lib( -- register HDRMerge module - 'HDRMerge_Lib', -- Module name - _('HDRMerge'), -- name - true, -- expandable - true, -- resetable - {[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers - dt.new_widget('box'){ - orientation = 'vertical', - GUI.stack - } -) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not HDRM.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 + ) + HDRM.event_registered = true + end +end diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 044e920..8c5e5a5 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -55,6 +55,10 @@ du.check_min_api_version("3.0.0", "LabelsToTags") -- Lua 5.3 no longer has "unpack" but "table.unpack" unpack = unpack or table.unpack +local ltt = {} +ltt.module_installed = false +ltt.event_registered = false + local LIB_ID = "LabelsToTags" -- Helper functions: BEGIN @@ -184,7 +188,7 @@ local function doTagging(selfC) job.valid = false end -local my_widget = darktable.new_widget("box") { +ltt.my_widget = darktable.new_widget("box") { orientation = "vertical", mappingComboBox, darktable.new_widget("button") { @@ -217,6 +221,15 @@ darktable.register_tag_mapping = function(name, mapping) mappingComboBox.reset_callback(mappingComboBox) end +local function install_module() + if not ltt.module_installed then + darktable.register_lib(LIB_ID,"labels to tags",true,true,{ + [darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20}, + },ltt.my_widget,nil,nil) + ltt.module_installed = true + end +end + --[[ darktable.register_tag_mapping("Example", { ["+----*"] = { "Red", "Only red" }, @@ -229,6 +242,19 @@ darktable.register_tag_mapping("Example", ["*****R"] = { "Rejected" } }) ]] -darktable.register_lib(LIB_ID,"labels to tags",true,true,{ - [darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",20}, - },my_widget,nil,nil) +if darktable.gui.current_view().id == "lighttable" then + install_module() +else + if not ltt.event_registered then + darktable.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 + ) + ltt.event_registered = true + end +end + diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index d456911..f1430cf 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -32,12 +32,18 @@ Install: (see here for more detail: https://github.com/darktable-org/lua-scripts Select the photo(s) you wish to find in your operating system's file manager and press "show in file explorer" in the "selected images" section. -- Nautilus (Linux), Explorer (Windows), and Finder (macOS before Catalina) will open one window for each selected image at the file's location. The file name will be highlighted. +- Nautilus (Linux), Explorer (Windows), and Finder (macOS prior to Mojave) will open one window for each selected image at the file's location. The file name will be highlighted. -- On macOS Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?). +- On macOS Mojave and Catalina the Finder will open one window for each different directory. In these windows only the last one of the corresponding files will be highlighted (bug or feature?). - Dolphin (Linux) will open one window with tabs for the different directories. All the selected images' file names are highlighted in their respective directories. +As an alternative option you can choose to show the image file names as symbolic links in an arbitrary directory. Go to preferences|Lua options. This option is not available for Windows users as on Windows solely admins are allowed to create links. + +- Pros: You do not clutter up your display with multiple windows. So there is no need to limit the number of selections. + +- Cons: If you want to work with the files you are one step behind the original data. + ----KNOWN ISSUES---- ]] @@ -50,6 +56,7 @@ local gettext = dt.gettext --Check API version du.check_min_api_version("5.0.0", "OpenInExplorer") +-- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("OpenInExplorer",dt.configuration.config_dir.."/lua/locale/") local function _(msgid) @@ -59,53 +66,150 @@ end local act_os = dt.configuration.running_os local PS = act_os == "windows" and "\\" or "/" ---Detect OS and quit if it is not supported-- -local proper_install = true +--Detect OS and quit if it is not supported. if act_os ~= "macos" and act_os ~= "windows" and act_os ~= "linux" then - proper_install = false - dt.print_error(_('OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time')) + dt.print(_("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time")) + dt.print_error("OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time") return end ---Format strings for the commands to open the corresponding OS' file manager -local fmng_cmd = {} -fmng_cmd.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s""]] -fmng_cmd.macos = 'open -Rn %s' -fmng_cmd.windows = 'explorer.exe /select, %s' +local use_links, links_dir = false, "" +if act_os ~= "windows" then + use_links = dt.preferences.read("OpenInExplorer", "use_links", "bool") + links_dir = dt.preferences.read("OpenInExplorer", "linked_image_files_dir", "string") +end ---The working function that opens the file manager windows with the selected image file names highlighted. -local function open_in_fmanager(op_sys, fmcmd) - local images = dt.gui.selection() - local curr_image, run_cmd, file_uris = '', '', '' - if #images == 0 then - dt.print(_('Please select an image')) - elseif #images <= 15 then - for _,image in pairs(images) do - curr_image = image.path..PS..image.filename - if op_sys == 'linux' then - file_uris = file_uris .. df.sanitize_filename("file://" .. curr_image) .. " " - dt.print_log("file_uris is " .. file_uris) - else - run_cmd = string.format(fmcmd, df.sanitize_filename(curr_image)) - dt.print_log("OpenInExplorer run_cmd = "..run_cmd) - dsys.external_command(run_cmd) - end - end - if op_sys == 'linux' then - run_cmd = string.format(fmcmd, #images, file_uris) - dt.print_log("OpenInExplorer run_cmd = "..run_cmd) - dsys.external_command(run_cmd) - end - else - dt.print(_('Please select fewer images (max 15)')) +--Check if the directory exists that was chosen for the file links. Return boolean. +local function check_if_links_dir_exists() + local dir_exists = true + if not links_dir then + --Just for paranoic reasons. I tried, but I couldn't devise a setting for a nil value. + dt.print(_("No links directory selected.\nPlease check the dt preferences (lua options)")) + dt.print_error("OpenInExplorer: No links directory selected") + dir_exists = false + elseif not df.check_if_file_exists(links_dir) then + dt.print(string.format(_("Links directory '%s' not found.\nPlease check the dt preferences (lua options)"), links_dir)) + dt.print_error(string.format("OpenInExplorer: Links directory '%s' not found", links_dir)) + dir_exists = false + end + return dir_exists +end + +--Format strings for the commands to open the corresponding OS's file manager. +local open_dir = {} +open_dir.windows = "explorer.exe /n, %s" +open_dir.macos = "open %s" +open_dir.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowFolders ass 1 %s ""]] + +local open_files = {} +open_files.windows = "explorer.exe /select, %s" +open_files.macos = "open -Rn %s" +open_files.linux = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass %d %s ""]] + +--Call the file mangager for each selected image on Linux. +--There is one call to busctl containing a list of all the image file names. +local function call_list_of_files(selected_images) + local current_image, file_uris, run_cmd = "", "", "" + for _, image in pairs(selected_images) do + current_image = image.path..PS..image.filename + file_uris = file_uris .. df.sanitize_filename("file://" .. current_image) .. " " + dt.print_log("file_uris is " .. file_uris) + end + run_cmd = string.format(open_files.linux, #selected_images, file_uris) + dt.print_log("OpenInExplorer run_cmd = "..run_cmd) + dsys.external_command(run_cmd) +end + +--Call the file manager for each selected image on Windows and macOS. +local function call_file_by_file(selected_images) + local current_image, run_cmd = "", "" + for _, image in pairs(selected_images) do + current_image = image.path..PS..image.filename + run_cmd = string.format(open_files[act_os], df.sanitize_filename(current_image)) + dt.print_log("OpenInExplorer run_cmd = "..run_cmd) + dsys.external_command(run_cmd) end end +--Create a link for each selected image, and finally call the file manager. +local function set_links(selected_images) + local current_image, link_target, run_cmd, k = "", "", "", nil + for k, image in pairs(selected_images) do + current_image = image.path..PS..image.filename + link_target = df.create_unique_filename(links_dir .. PS .. image.filename) + run_cmd = string.format("ln -s %s %s", df.sanitize_filename(current_image), df.sanitize_filename(link_target)) + --[[ + In case Windows will allow normal users to create soft links: + if act_os == "windows" then + run_cmd = string.format("mklink %s %s", df.sanitize_filename(link_target), df.sanitize_filename(current_image)) + end + ]] + if dsys.external_command(run_cmd) ~= 0 then + dt.print(_("Failed to create links. Missing rights?")) + dt.print_error("OpenInExplorer: Failed to create links") + return + end + end + --The URI format is necessary only for the Linux busctl command. + --But it is accepted by the Windows Explorer and macOS's Finder all the same. + run_cmd = string.format(open_dir[act_os], df.sanitize_filename("file://"..links_dir)) + dt.print_log("OpenInExplorer run_cmd = "..run_cmd) + dsys.external_command(run_cmd) +end + +--The working function that starts the particular task. +local function open_in_fmanager() + local images = dt.gui.selection() + if #images == 0 then + dt.print(_("Please select an image")) + else + if use_links and not check_if_links_dir_exists() then + return + end + if #images > 15 and not use_links then + dt.print(_("Please select fewer images (max. 15)")) + elseif use_links then + set_links(images) + else + if act_os == "linux" then + call_list_of_files(images) + else + call_file_by_file(images) + end + end + end +end + + -- GUI -- -if proper_install then - dt.gui.libs.image.register_action( - _("show in file explorer"), - function() open_in_fmanager(act_os, fmng_cmd[act_os]) end, - _("Opens the file manager at the selected image's location") +dt.gui.libs.image.register_action( + _("show in file explorer"), + function() open_in_fmanager() end, + _("Open the file manager at the selected image's location") +) +if act_os ~= "windows" then + dt.preferences.register("OpenInExplorer", "linked_image_files_dir", -- name + "directory", -- type + _("OpenInExplorer: linked files directory"), -- label + _("Directory to store the links to the file names. Requires restart to take effect"), -- tooltip + "Links to image files", -- default + dt.new_widget("file_chooser_button"){ + title = _("Select directory"), + is_directory = true, + } + ) + dt.preferences.register("OpenInExplorer", "use_links", -- name + "bool", -- type + _("OpenInExplorer: use links"), -- label + _("Use links instead of multiple windows. Requires restart to take effect"), -- tooltip + false, -- default + "" ) end + +dt.register_event( + "shortcut", + function(event, shortcut) open_in_fmanager() end, + "OpenInExplorer" +) + diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index fcc553e..75446d1 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -1,10 +1,29 @@ --[[ + Richardson-Lucy output sharpening for darktable using GMic + 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 . +]] + +--[[ DESCRIPTION RL_out_sharp.lua - Richardson-Lucy output sharpening using GMic This script provides a new target storage "RL output sharpen". Images exported will be sharpened using GMic (RL deblur algorithm) + + REQUIRED SOFTWARE + GMic command line interface (CLI) https://gmic.eu/download.shtml USAGE * require this script from main lua file @@ -33,7 +52,6 @@ CHANGES * 20200308 - initial version - ]] local dt = require "darktable" @@ -156,7 +174,7 @@ sigma_slider = dt.new_widget("slider"){ soft_max = 2.0, hard_min = 0.0, hard_max = 3.0, - step = 0.1, + step = 0.05, digits = 2, value = 1.0 } diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index 2fb8330..43b40e2 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -51,6 +51,11 @@ local function _(msgid) return gettext.dgettext("copy_attach_detach_tags", msgid) end +local cadt = {} +cadt.module_installed = false +cadt.event_registered = false +cadt.widget_table = {} + local image_tags = {} @@ -156,6 +161,26 @@ local function replace_tags() dt.print(_('Tags replaced')) end +local function install_module() + if not cadt.module_installed then + dt.register_lib("tagging_addon","Tagging addon",true,true,{ + [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500} + }, + dt.new_widget("box") { + -- orientation = "vertical", + reset_callback = function() + taglist_label.label = "" + image_tags = {} + end, + table.unpack(cadt.widget_table), + }, + nil, + nil + ) + cadt.module_installed = true + end +end + -- create modul Tagging addons taglist_label.reset_callback = mcopy_tags @@ -190,31 +215,31 @@ local box2 = dt.new_widget("box"){ local sep = dt.new_widget("separator"){} -- pack elements into widget table for a nicer layout -local widget_table = {} -widget_table[1] = box1 -widget_table[#widget_table+1] = box2 +cadt.widget_table[1] = box1 +cadt.widget_table[#cadt.widget_table+1] = box2 -widget_table[#widget_table+1] = sep -widget_table[#widget_table+1] = taglabel -widget_table[#widget_table+1] = taglist_label +cadt.widget_table[#cadt.widget_table+1] = sep +cadt.widget_table[#cadt.widget_table+1] = taglabel +cadt.widget_table[#cadt.widget_table+1] = taglist_label -- create modul -dt.register_lib("tagging_addon","Tagging addon",true,true,{ - [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER",500} - }, - dt.new_widget("box") { - -- orientation = "vertical", - reset_callback = function() - taglist_label.label = "" - image_tags = {} - end, - table.unpack(widget_table), - }, - nil, - nil - ) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not cadt.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 + ) + cadt.event_registered = true + end +end -- shortcut for copy diff --git a/contrib/enfuseAdvanced.lua b/contrib/enfuseAdvanced.lua index 932a6cd..a9f6ea0 100644 --- a/contrib/enfuseAdvanced.lua +++ b/contrib/enfuseAdvanced.lua @@ -180,6 +180,11 @@ for _,i in pairs(dt.styles) do end -- FUNCTION -- + +local function sanitize_decimals(cmd) -- make sure decimal separator is a '.' + return string.gsub(cmd, '(%d),(%d)', "%1.%2") +end + local function InRange(test, low, high) -- tests if test value is within range of low and high (inclusive) if test >= low and test <= high then return true @@ -240,7 +245,7 @@ end local function ExeUpdate(prog_tbl) --updates executable paths and verifies them dt.preferences.write(mod, 'bin_exists', 'bool', true) - for _,prog in pairs(prog_tbl) do + for x,prog in pairs(prog_tbl) do dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value) prog.bin = df.check_if_bin_exists(prog.name) if not prog.bin then @@ -311,7 +316,7 @@ local function UpdateENFargs(image_table, prefix) --updates the Enfuse arguments local largest_id = 0 local first_raw = {} for raw, temp_image in pairs(image_table) do - _, source_name, source_id = GetFileName(raw.filename) + local _, source_name, source_id = GetFileName(raw.filename) source_id = tonumber(source_id) if source_id < smallest_id then smallest_id = source_id @@ -435,6 +440,9 @@ local function main(storage, image_table, extra_data) local job = dt.gui.create_job('aligning images') image_table, images_to_remove, AIS.images_string = UpdateAISargs(image_table, images_to_remove) local run_cmd = BuildExecuteCmd(AIS) + dt.print_log("AIS run command is " .. run_cmd) + run_cmd = sanitize_decimals(run_cmd) + dt.print_log("AIS decimaal sanitized command is " .. run_cmd) local resp = dsys.external_command(run_cmd) job.valid = false if resp ~= 0 then @@ -455,11 +463,14 @@ local function main(storage, image_table, extra_data) local image_num = 0 local job = dt.gui.create_job('blending '..#variants..' image(s)', true) --create a GUI job bar to display enfuse progress UpdateActivePreference() --load current GUI values into active preference (only applies to elements without a clicked/changed callback) - for _,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute. + for x,prefix in pairs(variants) do --for each image to be created load in the preference values, build arguments string, output image, and run command then execute. job.percent = image_num /(#variants) image_num = image_num+1 ENF.images_string, final_image, source_raw = UpdateENFargs(image_table, prefix) local run_cmd = BuildExecuteCmd(ENF) + dt.print_log("ENF run command is " .. run_cmd) + run_cmd = sanitize_decimals(run_cmd) + dt.print_log("ENF decimaal sanitized command is " .. run_cmd) local resp = dsys.external_command(run_cmd) if resp ~= 0 then remove_temp_files(images_to_remove) @@ -470,11 +481,16 @@ local function main(storage, image_table, extra_data) --copy exif data from original file run_cmd = EXF.bin..' -TagsFromFile '..df.sanitize_filename(source_raw.path..os_path_seperator..source_raw.filename)..' -exif:all --subifd:all -overwrite_original '..df.sanitize_filename(final_image) + -- replace comma decimal separator with period + dt.print_log("EXF run command is " .. run_cmd) + run_cmd = sanitize_decimals(run_cmd) + dt.print_log("EXF decimaal sanitized command is " .. run_cmd) resp = dsys.external_command(run_cmd) if GUI.Target.auto_import.value then --import image into dt if specified local imported = dt.database.import(final_image) + dt.print_log("image imported") if GUI.Target.apply_style.selected > 1 then --apply specified style to imported image local set_style = styles[GUI.Target.apply_style.selected - 1] dt.styles.apply(set_style , imported) @@ -939,7 +955,7 @@ GUI.Presets.current_preset = dt.new_widget('combobox'){ end } GUI.Presets.load = dt.new_widget('button'){ - label = _('laod fusion preset'), + label = _('load fusion preset'), tooltip = _('load current fusion parameters from selected preset'), clicked_callback = function() LoadFromPreference(GUI.Presets.current_preset.value) end } diff --git a/contrib/exportLUT.lua b/contrib/exportLUT.lua new file mode 100644 index 0000000..9c7a184 --- /dev/null +++ b/contrib/exportLUT.lua @@ -0,0 +1,174 @@ +--[[ + This file is part of darktable, + copyright (c) 2020 Noah Clarke + + 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 . +]] +--[[ +Add the following line to .config/darktable/luarc to enable this lightable module: + require "contrib/exportLut" + +Given a haldCLUT identity file this script generates haldCLUTS from all the user's +styles and exports them to a location of their choosing. + +Warning: during export if a naming collision occurs the older file is automatically +overwritten silently. +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require("lib/dtutils.file") +local ds = require("lib/dtutils.system") + +local gettext = dt.gettext + +gettext.bindtextdomain("exportLUT",dt.configuration.config_dir.."/lua/locale/") + +local function _(msgid) + return gettext.dgettext("exportLUT", msgid) +end + +du.check_min_api_version("5.0.0", "exportLUT") + +local eL = {} +eL.module_installed = false +eL.event_registered = false +eL.widgets = {} + +-- Thanks Kevin Ertel for this bit +local os_path_seperator = '/' +if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end +local mkdir_command = 'mkdir -p ' +if dt.configuration.running_os == 'windows' then mkdir_command = 'mkdir ' end + +local file_chooser_button = dt.new_widget("file_chooser_button"){ + title = _("Identity_file_chooser"), + value = "", + is_directory = false +} + +local export_chooser_button = dt.new_widget("file_chooser_button"){ + title = _("Export_location_chooser"), + value = "", + is_directory = true +} + +local identity_label = dt.new_widget("label"){ + label = _("choose the identity haldclut file") +} + +local output_label = dt.new_widget("label"){ + label = _("choose the output location") +} + +local warning_label = dt.new_widget("label"){ + label = _("WARNING: files may be silently overwritten") +} + +local function end_job(job) + job.valid = false +end + +local function output_path(style_name, job) + local output_location = export_chooser_button.value .. os_path_seperator .. style_name .. ".png" + output_location = string.gsub(output_location, "|", os_path_seperator) + local output_dir = string.reverse(output_location) + output_dir = string.gsub(output_dir, ".-" .. os_path_seperator, os_path_seperator, 1) + output_dir = string.reverse(output_dir) + if(output_dir ~= "") then + df.mkdir(df.sanitize_filename(output_dir)) + end + return output_location +end + +local function export_luts() + local identity = dt.database.import(file_chooser_button.value) + if(type(identity) ~= "userdata") then + dt.print(_("Invalid identity lut file")) + else + local job = dt.gui.create_job(_('Exporting styles as haldCLUTs'), true, end_job) + + local size = 1 + + for style_num, style in ipairs(dt.styles) do + size = size + 1 + end + + local count = 0 + for style_num, style in ipairs(dt.styles) do + + identity:reset() + dt.styles.apply(style, identity) + local io_lut = dt.new_format("png") + io_lut.bpp = 8 + + io_lut:write_image(identity, output_path(style.name, job)) + count = count + 1 + job.percent = count / size + dt.print(_("Exported: ") .. output_path(style.name, job)) + end + dt.print(_("Done exporting haldCLUTs")) + job.valid = false + identity:reset() + end +end + +local function install_module() + if not eL.module_installed then + dt.register_lib( + _("export haldclut"), + _("export haldclut"), + true, + false, + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, + dt.new_widget("box") + { + orientation = "vertical", + table.unpack(eL.widgets), + }, + nil, + nil + ) + eL.module_installed = true + end +end + +local export_button = dt.new_widget("button"){ + label = _("export"), + clicked_callback = export_luts +} + +table.insert(eL.widgets, identity_label) +table.insert(eL.widgets, file_chooser_button) +table.insert(eL.widgets, output_label) +table.insert(eL.widgets, export_chooser_button) +table.insert(eL.widgets, warning_label) +table.insert(eL.widgets, export_button) + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not eL.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 + ) + eL.event_registered = true + end +end + diff --git a/contrib/ext_editor.lua b/contrib/ext_editor.lua index 4575542..ea957b5 100644 --- a/contrib/ext_editor.lua +++ b/contrib/ext_editor.lua @@ -1,64 +1,69 @@ --[[ + ext_editor.lua - edit images with external editors - DESCRIPTION + 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 . +]] + +--[[ ext_editor.lua - edit images with external editors - This script provides helpers to edit image files with programs external to darktable. - It adds: - - a new target storage "collection". Image exported will be reimported to collection for - further edit with external programs - - a new lighttable module "external editors", to select a program from a list of up to - - 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS) - - a set of lua preferences in order to configure name and path of up to 9 external editors - - a set of lua shortcuts in order to quick launch the external editors + This script provides helpers to edit image files with programs external to darktable. It adds: + - a new target storage "collection". Image exported will be reimported to collection for further edit with external programs + - a new lighttable module "external editors", to select a program from a list of up to + - 9 external editors and run it on a selected image (adjust this limit by changing MAX_EDITORS) + - a set of lua preferences in order to configure name and path of up to 9 external editors + - a set of lua shortcuts in order to quick launch the external editors USAGE * require this script from main lua file - -- setup -- - * in "preferences/lua options" configure name and path/command of external programs - * note that if a program name is left empty, that and all following entries will be ignored - * in "preferences/shortcuts/lua" configure shortcuts for external programs (optional) - * whenever programs preferences are changed, in lighttable/external editors, press "update list" + -- setup -- + * in "preferences/lua options" configure name and path/command of external programs + * note that if a program name is left empty, that and all following entries will be ignored + * in "preferences/shortcuts/lua" configure shortcuts for external programs (optional) + * whenever programs preferences are changed, in lighttable/external editors, press "update list" - -- use -- - * in the export dialog choose "collection" and select the format and bit depth for the - exported image - * press "export" - * the exported image will be imported into collection and grouped with the original image + -- use -- + * in the export dialog choose "collection" and select the format and bit depth for the + exported image + * press "export" + * the exported image will be imported into collection and grouped with the original image + + * select an image for editing with en external program, and: + * in lighttable/external editors, select program and press "edit" + * edit the image with the external editor, overwite the file, quit the external program + * the selected image will be updated + or + * in lighttable/external editors, select program and press "edit a copy" + * edit the image with the external editor, overwite the file, quit the external program + * a copy of the selected image will be created and updated + or + * in lighttable select target storage "collection" + * enter in darkroom + * to create an export or a copy press CRTL+E + * use the shortcut to edit the current image with the corresponding external editor + * overwite the file, quit the external program + * the darkroom view will be updated - * select an image for editing with en external program, and: - * in lighttable/external editors, select program and press "edit" - * edit the image with the external editor, overwite the file, quit the external program - * the selected image will be updated - or - * in lighttable/external editors, select program and press "edit a copy" - * edit the image with the external editor, overwite the file, quit the external program - * a copy of the selected image will be created and updated - or - * in lighttable select target storage "collection" - * enter in darkroom - * to create an export or a copy press CRTL+E - * use the shortcut to edit the current image with the corresponding external editor - * overwite the file, quit the external program - * the darkroom view will be updated - - * warning: mouseover on lighttable/filmstrip will prevail on current image - * this is the default DT behavior, not a bug of this script + * warning: mouseover on lighttable/filmstrip will prevail on current image + * this is the default DT behavior, not a bug of this script - CAVEATS - * MAC compatibility not tested + CAVEATS + * MAC compatibility not tested - TODO - * send multiple images to the same program, maybe - - BUGS, COMMENTS, SUGGESTIONS + BUGS, COMMENTS, SUGGESTIONS * send to Marco Carrarini, marco.carrarini@gmail.com - - CHANGES - * 20191224 - initial version - * 20191227 - added button "update list", better error handling, fixed bug with groups/tags in "edit" - ]] @@ -73,12 +78,18 @@ local MODULE_NAME = "ext_editor" -- check API version -du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.0 +du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.x -- OS compatibility local PS = dt.configuration.running_os == "windows" and "\\" or "/" +-- namespace +local ee = {} +ee.module_installed = false +ee.event_registered = false +ee.widgets = {} + -- translation local gettext = dt.gettext @@ -98,6 +109,7 @@ local n_entries local allowed_file_types = {"JPG", "jpg", "JPEG", "jpeg", "TIF", "tif", "TIFF", "tiff", "EXR", "exr", "PNG", "png"} +-- last used editor initialization if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then dt.preferences.write(MODULE_NAME, "lastchoice", "integer", 0) dt.preferences.write(MODULE_NAME, "initialized", "bool", true) @@ -163,11 +175,7 @@ local function OpenWith(images, choice, copy) local bin = program_paths[choice] local friendly_name = program_names[choice] - -- check if external program executable exists, return if not - if not df.check_if_bin_exists(bin) then - dt.print(friendly_name.._(" not found")) - return - end + if dt.configuration.running_os == "macos" then bin = "open -W -a "..bin end -- image to be edited local image @@ -175,7 +183,7 @@ local function OpenWith(images, choice, copy) local name = image.path..PS..image.filename -- check if image format is allowed - local file_ext = df.get_filetype (image.filename) + local file_ext = df.get_filetype(image.filename) local allowed = false for i,v in pairs(allowed_file_types) do if v == file_ext then @@ -207,11 +215,7 @@ local function OpenWith(images, choice, copy) if copy then -- create unique filename - while df.check_if_file_exists(df.sanitize_filename(new_name)) do - new_name = df.filename_increment(new_name) - -- limit to 50 more exports of the original export - if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end - end + new_name = df.create_unique_filename(new_name) -- physical copy, check result, return if error local copy_success = df.file_copy(name, new_name) @@ -306,7 +310,7 @@ local function program_shortcut(event, shortcut) -- export images and reimport in collection ----------------------------------- local function export2collection(storage, image_table, extra_data) - local new_name, new_image, result + local temp_name, new_name, new_image, move_success for image, temp_name in pairs(image_table) do @@ -314,11 +318,7 @@ local function export2collection(storage, image_table, extra_data) -- create unique filename new_name = image.path..PS..df.get_filename(temp_name) - while df.check_if_file_exists(df.sanitize_filename(new_name)) do - new_name = df.filename_increment(new_name) - -- limit to 50 more exports of the original export - if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end - end + new_name = 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) @@ -335,12 +335,26 @@ local function export2collection(storage, image_table, extra_data) dt.print (_("finished exporting")) end - --- register new storage ------------------------------------------------------- --- note that placing this declaration later makes the export selected module --- not to remember the choice "collection" when restarting DT, don't know why -dt.register_storage("exp2coll", _("collection"), null, export2collection) - +-- install the module in the UI +local function install_module() + if not ee.module_installed then + -- register new module "external editors" in lighttable ------------------------ + dt.register_lib( + MODULE_NAME, + _("external editors"), + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, + dt.new_widget("box") { + orientation = "vertical", + table.unpack(ee.widgets), + }, + nil, -- view_enter + nil -- view_leave + ) + ee.module_installed = true + end +end -- combobox, with variable number of entries ---------------------------------- local combobox = dt.new_widget("combobox") { @@ -393,33 +407,42 @@ local box1 = dt.new_widget("box") { button_update_list } +table.insert(ee.widgets, combobox) +table.insert(ee.widgets, box1) -- register new module "external editors" in lighttable ------------------------ -dt.register_lib( - MODULE_NAME, - _("external editors"), - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, - dt.new_widget("box") { - orientation = "vertical", - combobox, - box1 - }, - nil, -- view_enter - nil -- view_leave - ) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not ee.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 + ) + ee.event_registered = true + end +end + -- initialize list of programs and widgets ------------------------------------ UpdateProgramList(combobox, button_edit, button_edit_copy, false) +-- register new storage ------------------------------------------------------- +dt.register_storage("exp2coll", _("collection"), nil, export2collection) + + -- register the new preferences ----------------------------------------------- for i = MAX_EDITORS, 1, -1 do dt.preferences.register(MODULE_NAME, "program_path_"..i, "file", _("executable for external editor ")..i, _("select executable for external editor") , _("(None)")) + dt.preferences.register(MODULE_NAME, "program_name_"..i, "string", _("name of external editor ")..i, _("friendly name of external editor"), "") diff --git a/contrib/face_recognition.lua b/contrib/face_recognition.lua index 26caff6..6d9b0c0 100644 --- a/contrib/face_recognition.lua +++ b/contrib/face_recognition.lua @@ -55,6 +55,8 @@ local OUTPUT = dt.configuration.tmp_dir .. PS .. "facerecognition.txt" -- namespace local fc = {} +fc.module_installed = false +fc.event_registered = false -- ensure we meet the minimum api du.check_min_api_version("5.0.0", "face_recognition") @@ -337,6 +339,22 @@ local function face_recognition () end end +local function install_module() + if not fc.module_installed then + dt.register_lib( + "face_recognition", -- Module name + _("face recognition"), -- Visible name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers + fc.widget, + nil,-- view_enter + nil -- view_leave + ) + fc.module_installed = true + end +end + -- build the interface fc.unknown_tag = dt.new_widget("entry"){ @@ -466,16 +484,21 @@ fc.widget = dt.new_widget("box"){ table.unpack(widgets), } -dt.register_lib( - "face_recognition", -- Module name - _("face recognition"), -- Visible name - true, -- expandable - true, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 300}}, -- containers - fc.widget, - nil,-- view_enter - nil -- view_leave -) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not fc.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 + ) + fc.event_registered = true + end +end fc.tolerance.value = dt.preferences.read(MODULE, "tolerance", "float") diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index 6cf92c9..29ec406 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -41,7 +41,7 @@ local function detect_rating(event, image) dt.print_error(_("exiftool not found")) return end - local RAF_filename = tostring(image) + local RAF_filename = df.sanitize_filename(tostring(image)) local JPEG_filename = string.gsub(RAF_filename, "%.RAF$", ".JPG") local command = "exiftool -Rating " .. JPEG_filename dt.print_error(command) diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 4834bb0..4815e07 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -35,7 +35,6 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" local gettext = dt.gettext du.check_min_api_version("3.0.0", "geoJSON_export") diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 19e3c7c..86dd533 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -28,7 +28,6 @@ require "geoToolbox" local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" local gettext = dt.gettext du.check_min_api_version("3.0.0", "geoToolbox") @@ -40,6 +39,12 @@ local function _(msgid) return gettext.dgettext("geoToolbox", msgid) end + +local gT = {} +gT.module_installed = false +gT.event_registered = false + + -- local labelDistance = dt.new_widget("label") labelDistance.label = _("Distance:") @@ -572,6 +577,22 @@ local function altitude_profile() end +local function install_module() + if not gT.module_installed then + dt.register_lib( + "geoToolbox", -- Module name + "geo toolbox", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers + gT.widget, + nil,-- view_enter + nil -- view_leave + ) + gT.module_installed = true + end +end + local separator = dt.new_widget("separator"){} local separator2 = dt.new_widget("separator"){} @@ -579,13 +600,7 @@ local separator3 = dt.new_widget("separator"){} local separator4 = dt.new_widget("separator"){} local separator5 = dt.new_widget("separator"){} -dt.register_lib( - "geoToolbox", -- Module name - "geo toolbox", -- name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers - dt.new_widget("box") +gT.widget = dt.new_widget("box") { orientation = "vertical", dt.new_widget("button") @@ -664,10 +679,24 @@ dt.register_lib( clicked_callback = altitude_profile }, labelDistance - }, - nil,-- view_enter - nil -- view_leave -) + } + + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not gT.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 + ) + gT.event_registered = true + end +end -- Preferences dt.preferences.register("geoToolbox", diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index b6fed82..cb76174 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -36,6 +36,10 @@ local function _(msgid) return gettext.dgettext("gpx_export", msgid) end +local gpx = {} +gpx.module_installed = false +gpx.event_registered = false + local path_entry = dt.new_widget("entry") { text = dt.preferences.read("gpx_exporter", "gpxExportPath", "string"), @@ -127,31 +131,55 @@ local function create_gpx_file() end end -dt.register_lib( - "gpx_exporter", - "gpx export", - true, -- expandable - true, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers +local function install_module() + if not gpx.module_installed then + dt.register_lib( + "gpx_exporter", + "gpx export", + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers + gpx.widget, + nil,-- view_enter + nil -- view_leave + ) + gpx.module_installed = true + end +end + +gpx.widget = dt.new_widget("box") +{ + orientation = "vertical", + dt.new_widget("button") + { + label = _("export"), + tooltip = _("export gpx file"), + clicked_callback = create_gpx_file + }, dt.new_widget("box") { - orientation = "vertical", - dt.new_widget("button") + orientation = "horizontal", + dt.new_widget("label") { - label = _("export"), - tooltip = _("export gpx file"), - clicked_callback = create_gpx_file - }, - dt.new_widget("box") - { - orientation = "horizontal", - dt.new_widget("label") - { - label = _("file:"), - }, - path_entry + label = _("file:"), }, + path_entry }, - nil,-- view_enter - nil -- view_leave -) +} + + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not gpx.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 + ) + gpx.event_registered = true + end +end diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 83ec3be..1de8f04 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -40,7 +40,6 @@ local du = require "lib/dtutils" local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" -require "official/yield" local gettext = dt.gettext local namespace = 'module_hugin' @@ -227,4 +226,4 @@ dt.register_storage(namespace, _("hugin panorama"), show_status, create_panorama -- --- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua \ No newline at end of file +-- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua diff --git a/contrib/image_time.lua b/contrib/image_time.lua new file mode 100644 index 0000000..b285c61 --- /dev/null +++ b/contrib/image_time.lua @@ -0,0 +1,564 @@ +--[[ + + image_time.lua - synchronize image time for images shot with different cameras + + Copyright (C) 2019, 2020 Bill Ferguson . + + 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 . +]] +--[[ + image_time - non-destructively modify the image time + + DESCRIPTION + + image_time non destructively adjusts image times by modifying the + database image exif_datetime_taken field. There are 4 modes: adjust time, + set time, synchronize time, and reset time. + + ADJUST TIME + + adjust time mode lets you chose an offset in terms of years, months, + days, hours, minutes, and seconds. The adjustment can be added or + subtracted. + + WARNING: When adding and subtracting months the result will usually + be what is expected unless the time being adjusted is at the end of + the month. This is because a month is a variable amount of time that + can be 28, 29, 30 or 31 days depending on the month. Example: It's + March 31st and I subtract a month which not sets the time to February + 31st. When that gets set to a valid time, then the date changes to + March 3rd. + + SET TIME + + set time mode allows you to pick a date and time and set the image + time accordingly. Fields may be left out. This is useful when + importing scanned images that don't have an embedded date. + + SYNCHRONIZE TIME + + I recently purchased a 7DmkII to replace my aging 7D. My 7D was still + serviceable, so I bought a remote control and figured I'd try shooting + events from 2 different perspectives. I didn't think to synchonize the + time between the 2 cameras, so when I loaded the images and sorted by + time it was a disaster. I hacked a script together with hard coded values + to adjust the exif_datetime_taken value in the database for the 7D images + so that everything sorted properly. I've tried shooting with 2 cameras + several times since that first attempt. I've gotten better at getting the + camera times close, but still haven't managed to get them to sync. So I + decided to think the problem through and write a proper script to take + care of the problem. + + RESET TIME + + Select the images and click reset. + + USAGE + + ADJUST TIME + + Change the year, month, day, hour, minute, second dropdowns to the amount + of change desired. Select add or subtract. Select the images. Click + adjust. + + SET TIME + + Set the time fields to the desired time. Select the images to change. Click + set. + + SYNCHRONIZE TIME + + Select 2 images, one from each camera, of the same moment in time. Click + the Calculate button to calculate the time difference. The difference is + displayed in the difference entry. You can manually adjust it by changing + the value if necessary. + + Select the images that need their time adjusted. Determine which way to adjust + adjust the time (add or subtract) and select the appropriate choice. + + If the image times get messed up and you just want to start over, select reset time + from the mode and reset the image times. + + RESET TIME + + Select the images and click reset. + + ADDITIONAL SOFTWARE REQUIRED + * exiv2 + + BUGS, COMMENTS, SUGGESTIONS + * Send to 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 gettext = dt.gettext + +local img_time = {} +img_time.module_installed = false +img_time.event_registered = false + +du.check_min_api_version("3.0.0", "image_time") + + +-- Tell gettext where to find the .mo file translating messages for a particular domain +gettext.bindtextdomain("image_time",dt.configuration.config_dir.."/lua/locale/") + +local function _(msgid) + return gettext.dgettext("image_time", msgid) +end + +local PS = dt.configuration.runnin_os == "windows" and "\\" or "/" +local ERROR = -1 + +-- function to convert from exif time to system time +local function exiftime2systime(exiftime) + local yr,mo,dy,h,m,s = string.match(exiftime, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d+)") + return(os.time{year=yr, month=mo, day=dy, hour=h, min=m, sec=s}) +end + +-- function to convert from systime to exif time +local function systime2exiftime(systime) + local t = os.date("*t", systime) + return(string.format("%4d:%02d:%02d %02d:%02d:%02d", t.year, t.month, t.day, t.hour, t.min, t.sec)) +end + +local function vars2exiftime(year, month, day, hour, min, sec) + local y = tonumber(year) and string.format("%4d", year) or " " + local mo = tonumber(month) and string.format("%02d", month) or " " + local d = tonumber(day) and string.format("%02d", day) or " " + local h = tonumber(hour) and string.format("%02d", hour) or " " + local m = tonumber(min) and string.format("%02d", min) or " " + local s = tonumber(sec) and string.format("%02d", sec) or " " + return(y .. ":" .. mo .. ":" .. d .. " " .. h .. ":" .. m .. ":" .. s) +end + +local function exiftime2vars(exiftime) + return string.match(exiftime, "(%d+):(%d+):(%d+) (%d+):(%d+):(%d+)") +end + +local function calc_time_difference(image1, image2) + return math.abs(exiftime2systime(image1.exif_datetime_taken) - exiftime2systime(image2.exif_datetime_taken)) +end + +local function adjust_image_time(image, difference) + image.exif_datetime_taken = systime2exiftime(exiftime2systime(image.exif_datetime_taken) + difference) + return +end + +local function calculate_difference(images) + if #images == 2 then + img_time.diff_entry.text = calc_time_difference(images[1], images[2]) + img_time.btn.sensitive = true + else + dt.print(_("Error: 2 images must be selected")) + end +end + +local function synchronize_times(images, difference) + for _, image in ipairs(images) do + adjust_image_time(image, difference) + end +end + +local function synchronize_time(images) + local sign = 1 + if img_time.sdir.value == "subtract" then + sign = -1 + end + synchronize_times(images, tonumber(img_time.diff_entry.text) * sign) +end + +local function add_time(images) + synchronize_times(images, tonumber(img_time.diff_entry.text)) +end + +local function year_month2months(year, month) + year_months = tonumber(year) and year * 12 or 0 + local months = tonumber(month) and tonumber(month) or 0 + + return year_months + months +end + +local function months2year_month(months) + dt.print_log("months is " .. months) + local year = math.floor(months / 12) + local month = months - (year * 12) + + return year, month +end + +local function get_image_taken_time(image) + -- get original image time + local datetime = nil + + local exiv2 = df.check_if_bin_exists("exiv2") + if exiv2 then + p = io.popen(exiv2 .. " -K Exif.Image.DateTime " .. image.path .. PS .. image.filename) + if p then + for line in p:lines() do + if string.match(line, "Exif.Image.DateTime") then + datetime = string.match(line, "(%d-:%d-:%d- %d-:%d-:%d+)") + end + end + p:close() + end + else + dt.print(_("unable to detect exiv2")) + datetime = ERROR + end + return datetime +end + +local function _get_windows_image_file_creation_time(image) + local datetime = nil + local p = io.popen("dir " .. image.path .. PS .. image.filename) + if p then + for line in p:lines() do + if string.match(line, ds.sanitize_lua(image.filename)) then + local mo, day, yr, hr, min, apm = string.match(line, "(%d+)/(%d+)/(%d+) (%d-):(%d+) (%S+)") + if apm == "PM" then + hr = hr + 12 + end + datetime = vars2exiftime(yr, mo, day, hr, min, 0) + end + end + p:close() + else + dt.print(_("unable to get information for ") .. image.filename) + datetime = ERROR + end + return datetime +end + +local function _get_nix_image_file_creation_time(image) + local datetime = nil + local p = io.popen("ls -lL --time-style=full-iso " .. image.path .. PS .. image.filename) + if p then + for line in p:lines() do + if string.match(line, ds.sanitize_lua(image.filename)) then + datetime = vars2exiftime(string.match(line, "(%d+)%-(%d-)%-(%d-) (%d-):(%d-):(%d+).")) + end + end + p:close() + else + dt.print(_("unable to get information for ") .. image.filename) + datetime = ERROR + end + return datetime +end + +local function get_image_file_creation_time(image) + -- no exif time in the image file so get the creation time + local datetime = nil + if dt.configuration.running_os == "windows" then + datetime = _get_windows_image_file_creation_time(image) + else + datetime = _get_nix_image_file_creation_time(image) + end + return datetime +end + +local function get_original_image_time(image) + local image_time = image.exif_datetime_taken + local reset_time = nil + + reset_time = get_image_taken_time(image) + + if reset_time then + if reset_time == ERROR then + return image_time + else + return reset_time + end + else + reset_time = get_image_file_creation_time(image) + + if reset_time then + if reset_time == ERROR then + return image_time + else + return reset_time + end + end + end +end + +local function reset_time(images) + if #images > 0 then + for _, image in ipairs(images) do + image.exif_datetime_taken = get_original_image_time(image) + end + else + dt.print_error(_("reset time: no images selected")) + dt.print(_("please select the images that need their time reset")) + end +end + +local function adjust_time(images) + local SEC_MIN = 60 + local SEC_HR = SEC_MIN * 60 + local SEC_DY = SEC_HR * 24 + + local offset = nil + local sign = 1 + + if #images < 1 then + dt.print(_("please select some images and try again")) + return + end + + if img_time.adir.value == _("subtract") then + sign = -1 + end + + for _, image in ipairs(images) do + local y, mo, d, h, m, s = exiftime2vars(image.exif_datetime_taken) + local image_months = year_month2months(y, mo) + local months_diff = year_month2months(img_time.ayr.value, img_time.amo.value) + y, mo = months2year_month(image_months + (months_diff * sign)) + local exif_new = vars2exiftime(y, mo, d, h, m, s) + offset = img_time.ady.value * SEC_DY + offset = offset + img_time.ahr.value * SEC_HR + offset = offset + img_time.amn.value * SEC_MIN + offset = offset + img_time.asc.value + offset = offset * sign + image.exif_datetime_taken = systime2exiftime(exiftime2systime(exif_new) + offset) + end +end + +local function set_time(images) + if #images < 1 then + dt.print(_("please select some images and try again")) + return + end + + local y = img_time.syr.value + local mo = img_time.smo.value + local d = img_time.sdy.value + local h = img_time.shr.value + local m = img_time.smn.value + local s = img_time.ssc.value + + for _, image in ipairs(images) do + image.exif_datetime_taken = vars2exiftime(y, mo, d, h, m, s) + end +end + +local function seq(first, last) + local result = {} + + local num = first + + while num <= last do + table.insert(result, num) + num = num + 1 + end + + return table.unpack(result) +end + +local function reset_widgets() + dt.print_log("took the reset function") + img_time.ayr.selected = 1 + img_time.amo.selected = 1 + img_time.ady.selected = 1 + img_time.ahr.selected = 1 + img_time.ayr.selected = 1 + img_time.amn.selected = 1 + img_time.asc.selected = 1 + img_time.adir.selected = 1 + img_time.syr.selected = #img_time.syr + img_time.smo.selected = 1 + img_time.sdy.selected = 1 + img_time.shr.selected = 1 + img_time.smn.selected = 1 + img_time.ssc.selected = 1 + img_time.adir.selected = 1 +end + +local function install_module() + if not img_time.module_installed then + dt.register_lib( + "image_time", -- Module name + _("image time"), -- Visible name + true, -- expandable + true, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers + img_time.widget, + nil,-- view_enter + nil -- view_leave + ) + img_time.module_installed = true + end +end + +-- widgets + +img_time.widgets = { + -- name, type, tooltip, placeholder, + {"ayr", "combobox", _("years"), _("years to adjust by, 0 - ?"), {seq(0,20)}, 1}, + {"amo", "combobox", _("months"), ("months to adjust by, 0-12"), {seq(0,12)}, 1}, + {"ady", "combobox", _("days"), ("days to adjust by, 0-31"), {seq(0,31)}, 1}, + {"ahr", "combobox", _("hours"), _("hours to adjust by, 0-23"), {seq(0,23)}, 1}, + {"amn", "combobox", _("minutes"), _("minutes to adjust by, 0-59"), {seq(0,59)}, 1}, + {"asc", "combobox", _("seconds"), _("seconds to adjust by, 0-59"), {seq(0,59)}, 1}, + {"adir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1}, + {"syr", "combobox", _("year"), _("year to set, 1900 - now"), {" ", seq(1900,os.date("*t", os.time()).year)}, 1}, + {"smo", "combobox", _("month"), _("month to set, 1-12"), {" ", seq(1,12)}, 1}, + {"sdy", "combobox", _("day"), _("day to set, 1-31"), {" ", seq(1,31)}, 1}, + {"shr", "combobox", _("hour"), _("hour to set, 0-23"), {" ", seq(0,23)}, 1}, + {"smn", "combobox", _("minute"), _("minutes to set, 0-59"), {" ", seq(0, 59)}, 1}, + {"ssc", "combobox", _("seconds"), _("seconds to set, 0-59"), {" ", seq(0,59)}, 1}, + {"sdir", "combobox", _("add/subtract"), _("add or subtract time"), {_("add"), _("subtract")}, 1}, +} + +for _, widget in ipairs(img_time.widgets) do + img_time[widget[1]] = dt.new_widget(widget[2]){ + label = widget[3], + tooltip = widget[4], + selected = widget[6], + table.unpack(widget[5]) + } +end + +img_time.syr.selected = #img_time.syr + +img_time.diff_entry = dt.new_widget("entry"){ + tooltip = _("Time difference between images in seconds"), + placeholder = _("Select 2 images and use the calculate button"), + text = "", +} + +img_time.calc_btn = dt.new_widget("button"){ + label = _("Calculate"), + tooltip = _("calculate time difference between 2 images"), + clicked_callback = function() + calculate_difference(dt.gui.action_images) + end +} + +img_time.btn = dt.new_widget("button"){ + label = _("synchronize image times"), + tooltip = _("apply the time difference from selected images"), + sensitive = false, + clicked_callback = function() + synchronize_time(dt.gui.action_images) + end +} + +img_time.stack = dt.new_widget("stack"){ + dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("label"){label = _("adjust time")}, + dt.new_widget("section_label"){label = _("days, months, years")}, + img_time.ady, + img_time.amo, + img_time.ayr, + dt.new_widget("section_label"){label = _("hours, minutes, seconds")}, + img_time.ahr, + img_time.amn, + img_time.asc, + dt.new_widget("section_label"){label = _("adjustment direction")}, + img_time.adir, + dt.new_widget("button"){ + label = _("adjust"), + clicked_callback = function() + adjust_time(dt.gui.action_images) + end + } + }, + dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("label"){label = _("set time")}, + dt.new_widget("section_label"){label = _("date: ")}, + img_time.sdy, + img_time.smo, + img_time.syr, + dt.new_widget("section_label"){label = _("time:")}, + img_time.shr, + img_time.smn, + img_time.ssc, + dt.new_widget("button"){ + label = _("set"), + clicked_callback = function() + set_time(dt.gui.action_images) + end + } + }, + dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("label"){label = _("synchronize image time")}, + dt.new_widget("section_label"){label = _("calculate difference between images")}, + img_time.diff_entry, + img_time.calc_btn, + dt.new_widget("section_label"){label = _("apply difference")}, + img_time.sdir, + img_time.btn, + }, + dt.new_widget("box"){ + orientation = "vertical", + dt.new_widget("label"){label = _("reset to original time")}, + dt.new_widget("separator"){}, + dt.new_widget("button"){ + label = _("reset"), + clicked_callback = function() + reset_time(dt.gui.action_images) + end + } + }, +} + +img_time.mode = dt.new_widget("combobox"){ + label = _("mode"), + tooltip = _("select mode"), + selected = 1, + changed_callback = function(this) + img_time.stack.active = this.selected + end, + _("adjust time"), + _("set time"), + _("synchronize time"), + _("reset time") +} + +img_time.widget = dt.new_widget("box"){ + orientation = "vertical", + reset_callback = function(this) + reset_widgets() + end, + img_time.mode, + img_time.stack, +} + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not img_time.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 + ) + img_time.event_registered = true + end +end + diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index f01b014..10e84db 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -41,7 +41,6 @@ format (all fields can be the empty string): local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" local gettext = dt.gettext diff --git a/contrib/photils.lua b/contrib/photils.lua new file mode 100644 index 0000000..361ba86 --- /dev/null +++ b/contrib/photils.lua @@ -0,0 +1,453 @@ +--[[ photils Auto Tagging plugin + copyright (c) 2020 Tobias Scheck + + 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 . +--]] + +--[[ + A darktable plugin that tries to predict keywords based on the selected image. + This plugin uses photils-cli to handle this task. Photils-cli is an application + that passes the image through a neural network, classifies it, and extracts the + suggested tags. Everything happens offline without the need that your data are + sent over the internet. + + ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT + * photils-cli - https://github.com/scheckmedia/photils-cli at the moment only + available for Linux and MacOS + + USAGE + * require this script from your main lua file + To do this add this line to the file .config/darktable/luarc: + require "contrib/photils" + * Select an image + * Press "get tags" + * Select the tags you want from a list of suggestions + * Press "Attach .. Tags" to add the selected tags to your image +--]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" + +local MODULE_NAME = "photils" +du.check_min_api_version("5.0.0", MODULE_NAME) + +local PS = dt.configuration.running_os == "windows" and "\\" or "/" +local gettext = dt.gettext +gettext.bindtextdomain(MODULE_NAME, + dt.configuration.config_dir .. PS .. "lua" .. PS .. "locale" .. PS) + +local exporter = dt.new_format("jpeg") +exporter.quality = 80 +exporter.max_height = 224 +exporter.max_width = 224 + +-- helper functions + +local function _(msgid) + return gettext.dgettext(MODULE_NAME, msgid) +end + +local function num_keys(tbl) + local num = 0 + for _ in pairs(tbl) do num = num + 1 end + return num +end + +local function has_key(tbl, value) + for k, _ in pairs(tbl) do + if k == value then + return true + end + end + + return false +end + +local photils_installed = df.check_if_bin_exists("photils-cli") + +--[[ + local state object + + maybe per_page is a preference variable but I think 10 + is a good value for the ui +]] +local PHOTILS = { + tags = {}, + confidences = {}, + page = 1, + per_page = 10, + selected_tags = {}, + in_pagination = false, + tagged_image = "", + module_installed = false, + event_registered = false, + plugin_display_views = { + [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}, + [dt.gui.views.darkroom] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 100} + }, +} + +local GUI = { + container = dt.new_widget("box") { + orientation = "vertical", + sensitive = true, + dt.new_widget("button") { + label = _("get tags"), + sensitive = photils_installed, + clicked_callback = function() PHOTILS.on_tags_clicked() end + }, + reset_callback = function() PHOTILS.on_reset(true) end + }, + stack = dt.new_widget("stack"), + prev_button = dt.new_widget("button") { + label = "<", + sensitive = false, + clicked_callback = function() + PHOTILS.page = PHOTILS.page - 1 + PHOTILS.paginate() + end + }, + next_button = dt.new_widget("button") { + label = ">", + sensitive = false, + clicked_callback = function() + PHOTILS.page = PHOTILS.page + 1 + PHOTILS.paginate() + end + }, + tag_box = dt.new_widget("box") {orientation = "vertical"}, + tag_view = dt.new_widget("box") {orientation = "vertical"}, + page_label = dt.new_widget("label") {label = ""}, + error_view = dt.new_widget("box") {orientation = "vertical"}, + warning_label = dt.new_widget("label") { + label = "" + }, + restart_required_label = dt.new_widget("label") { + label = _("requires a restart to be applied") + }, + attach_button = dt.new_widget("button") { + label = "", + sensitive = false, + clicked_callback = function() PHOTILS.attach_tags() end + }, + confidence_slider = dt.new_widget("slider") { + step = 1, + digits = 0, + value = 90, + hard_max = 100, + hard_min = 0, + soft_max = 100, + soft_min = 0, + label = _("min confidence value") + }, + warning = dt.new_widget("label") +} + +function PHOTILS.image_changed() + local current_image = tostring(dt.gui.selection()[1]) + if current_image ~= PHOTILS.tagged_image then + if PHOTILS.tagged_image ~= "" then + PHOTILS.tagged_image_has_changed() + end + + PHOTILS.tagged_image = tostring(current_image) + end +end + +function PHOTILS.tagged_image_has_changed() + GUI.warning.label = _("The suggested tags were not generated\n for the currently selected image!") +end + +function PHOTILS.paginate() + PHOTILS.in_pagination = true + local num_pages = math.ceil(#PHOTILS.tags / PHOTILS.per_page) + GUI.page_label.label = string.format(_(" page %s of %s "), PHOTILS.page, + num_pages) + + if PHOTILS.page <= 1 then + PHOTILS.page = 1 + GUI.prev_button.sensitive = false + else + GUI.prev_button.sensitive = true + end + + if PHOTILS.page > num_pages - 1 then + PHOTILS.page = num_pages + GUI.next_button.sensitive = false + else + GUI.next_button.sensitive = true + end + + --[[ + calculates the start positon in the tag array based on the current page + and takes N tags from that array to show these in darktable + e.g. page 1 goes from 1 to 10, page 2 from 11 to 20 a.s.o. + the paginaton approach is related to a problem with the dynamic addition + of mutliple widgets https://github.com/darktable-org/darktable/issues/4934#event-3318100463 + ]]-- + local offset = ((PHOTILS.page - 1) * PHOTILS.per_page) + 1 + local tag_index = 1 + for i = offset, offset + PHOTILS.per_page - 1, 1 do + local tag = PHOTILS.tags[i] + local conf = PHOTILS.confidences[i] + + GUI.tag_box[tag_index].value = has_key(PHOTILS.selected_tags, tag) + + if tag then + if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then + tag = tag .. string.format(" (%.3f)", conf) + end + + GUI.tag_box[tag_index].label = tag + GUI.tag_box[tag_index].sensitive = true + else + GUI.tag_box[tag_index].label = "" + GUI.tag_box[tag_index].sensitive = false + end + tag_index = tag_index + 1 + end + + PHOTILS.in_pagination = false +end + +function PHOTILS.attach_tags() + local image = dt.gui.selection()[1] + for tag, _ in pairs(PHOTILS.selected_tags) do + local dt_tag = dt.tags.create(tag) + dt.tags.attach(dt_tag, image) + end + + dt.print(_("Tags successfully attached to image")) +end + +function PHOTILS.get_tags(image, with_export) + local tmp_file = df.create_tmp_file() + local in_arg = df.sanitize_filename(tostring(image)) + local out_arg = df.sanitize_filename(tmp_file) + local executable = photils_installed + + if dt.configuration.running_os == "macos" then + executable = executable .. "/Contents/MacOS/photils-cli" + end + + if with_export then + dt.print_log("use export to for prediction") + local export_file = df.create_tmp_file() + exporter:write_image(image, export_file) + in_arg = df.sanitize_filename(tostring(export_file)) + end + + local command = executable .. " -c " .. " -i " .. in_arg .. " -o " .. out_arg + + local ret = dtsys.external_command(command) + if ret > 0 then + dt.print_error(string.format("command %s returned error code %d", command, ret)) + os.remove(tmp_file) + + -- try to export the image and run tagging + if not with_export then + return PHOTILS.get_tags(image, true) + end + + return false + end + + for i = #PHOTILS.tags, 1, -1 do + PHOTILS.tags[i] = nil + PHOTILS.confidences[i] = nil + end + + for tag in io.lines(tmp_file) do + local splitted = du.split(tag, ":") + if 100 * tonumber(splitted[2]) >= GUI.confidence_slider.value then + PHOTILS.tags[#PHOTILS.tags + 1] = splitted[1] + PHOTILS.confidences[#PHOTILS.confidences+1] = splitted[2] + end + end + + dt.print(string.format(_("%s found %d tags for your image"), MODULE_NAME, + #PHOTILS.tags)) + os.remove(tmp_file) + + return true +end + +function PHOTILS.on_tags_clicked() + PHOTILS.page = 1 + GUI.warning.label = "" + + PHOTILS.on_reset(false) + + local images = dt.gui.selection() + + if #images == 0 then + dt.print(_("No image selected.")) + dt.control.sleep(2000) + else + if #images > 1 then + dt.print(_("This plugin can only handle a single image.")) + dt.gui.selection({images[1]}) + dt.control.sleep(2000) + end + + if not PHOTILS.get_tags(images[1], true) then + local msg = string.format(_("%s failed, see terminal output for details"), MODULE_NAME) + GUI.warning_label.label = msg + GUI.stack.active = GUI.error_view + dt.print(msg) + return + end + + if #PHOTILS.tags == 0 then + local msg = string.format(_("no tags where found"), MODULE_NAME) + GUI.warning_label.label = msg + GUI.stack.active = GUI.error_view + return + end + + GUI.stack.active = GUI.tag_view + PHOTILS.paginate() + end +end + +function PHOTILS.tag_selected(tag_button) + if PHOTILS.in_pagination then return end + + if tag_button.value then + local tag = tag_button.label + if dt.preferences.read(MODULE_NAME, "show_confidence", "bool") then + local idx = string.find(tag, "%(") - 2 + tag = string.sub(tag, 0, idx) + end + + PHOTILS.selected_tags[tag] = tag + else + PHOTILS.selected_tags[tag_button.label] = nil + end + + local num_selected = num_keys(PHOTILS.selected_tags) + if num_selected == 0 then + GUI.attach_button.label = "" + GUI.attach_button.sensitive = false + else + GUI.attach_button.label = string.format(_("attach %d tags"), + num_selected) + GUI.attach_button.sensitive = true + end +end + +function PHOTILS.on_reset(with_view) + if with_view then GUI.stack.active = 1 end + + for k, _ in pairs(PHOTILS.selected_tags) do + PHOTILS.selected_tags[k] = nil + end + + for _, v in ipairs(GUI.tag_box) do + v.value = false + end + + GUI.attach_button.label = "" + GUI.attach_button.sensitive = false +end + +local function install_module() + if not PHOTILS.module_installed then + dt.register_lib(MODULE_NAME, + "photils autotagger", + true, + true, + PHOTILS.plugin_display_views, + GUI.container, + nil, + nil + ) + PHOTILS.module_installed = true + end +end + +-- add a fix number of buttons +for _ = 1, PHOTILS.per_page, 1 do + local btn_tag = dt.new_widget("check_button") { + label = "", + sensitive = false, + clicked_callback = PHOTILS.tag_selected + } + + table.insert(GUI.tag_box, btn_tag) +end + +if not photils_installed then + GUI.warning_label.label = _("photils-cli not found") + dt.print_log(_("photils-cli not found")) +else + GUI.warning_label.label = _("Select an image, click \"get tags\" and get \nsuggestions for tags.") +end + +GUI.pagination = dt.new_widget("box") { + orientation = "horizontal", + GUI.prev_button, + GUI.page_label, + GUI.next_button +} + + +table.insert(GUI.error_view, GUI.warning_label) +if not photils_installed then + table.insert(GUI.error_view, df.executable_path_widget({"photils-cli"})) + table.insert(GUI.error_view, GUI.restart_required_label) +end +table.insert(GUI.stack, GUI.error_view) +table.insert(GUI.stack, GUI.tag_view) + +table.insert(GUI.tag_view, GUI.pagination) +table.insert(GUI.tag_view, GUI.tag_box) +table.insert(GUI.tag_view, GUI.attach_button) +table.insert(GUI.tag_view, GUI.warning) + +table.insert(GUI.container, GUI.confidence_slider) +table.insert(GUI.container, GUI.stack) + +GUI.stack.active = 1 + + + +-- uses photils: prefix because script settings are all together and not seperated by script +dt.preferences.register(MODULE_NAME, + "show_confidence", + "bool", + _("photils: show confidence value"), + _("if enabled, the confidence value for each tag is displayed"), + true) + +dt.register_event("mouse-over-image-changed",PHOTILS.image_changed) + +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not PHOTILS.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 + ) + PHOTILS.event_registered = true + end +end + diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index 31a4b79..09f0be9 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -47,6 +47,12 @@ local dt = require "darktable" local du = require "lib/dtutils" local debug = require "darktable.debug" +local qt = {} +qt.module_installed = false +qt.event_registered = false +qt.widget_table = {} + + local gettext = dt.gettext @@ -177,6 +183,27 @@ local function update_quicktag_list() end end +local function install_module() + if not qt.module_installed then + dt.register_lib( + "quicktag", -- Module name + "quicktag", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}}, + + dt.new_widget("box"){ + orientation = "vertical", + table.unpack(qt.widget_table), + + }, + nil,-- view_enter + nil -- view_leave + ) + qt.module_installed = true + end +end + update_quicktag_list() local new_quicktag = dt.new_widget("entry"){ @@ -213,33 +240,32 @@ local new_qt_widget = dt.new_widget ("box") { -- back UI elements in a table -- thanks to wpferguson for the hint -local widget_table = {} for i=1,qnr do - widget_table[#widget_table + 1] = button[i] + qt.widget_table[#qt.widget_table + 1] = button[i] end -widget_table[#widget_table + 1] = dt.new_widget("separator"){} -widget_table[#widget_table + 1] = old_quicktag -widget_table[#widget_table + 1] = new_qt_widget +qt.widget_table[#qt.widget_table + 1] = dt.new_widget("separator"){} +qt.widget_table[#qt.widget_table + 1] = old_quicktag +qt.widget_table[#qt.widget_table + 1] = new_qt_widget --create module -dt.register_lib( - "quicktag", -- Module name - "quicktag", -- name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 490}}, - - dt.new_widget("box"){ - orientation = "vertical", - table.unpack(widget_table), - - }, - nil,-- view_enter - nil -- view_leave -) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not qt.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 + ) + qt.event_registered = true + end +end -- create shortcuts for i=1,qnr do diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index a0c70be..5ccba2b 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -35,6 +35,10 @@ local debug = require "darktable.debug" -- check API version du.check_min_api_version("3.0.0", "rename-tags") +local rt = {} +rt.module_installed = false +rt.event_registered = false + -- GUI entries local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" } local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" } @@ -99,6 +103,13 @@ local function rename_tags() rename_reset() end +local function install_module() + if not rt.module_installed then + darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rt.rename_widget, nil, nil) + rt.module_installed = true + end +end + -- GUI local old_widget = darktable.new_widget ("box") { orientation = "horizontal", @@ -112,7 +123,7 @@ local new_widget = darktable.new_widget ("box") { new_tag } -local rename_widget = darktable.new_widget ("box") { +rt.rename_widget = darktable.new_widget ("box") { orientation = "vertical", reset_callback = rename_reset, old_widget, @@ -120,6 +131,20 @@ local rename_widget = darktable.new_widget ("box") { darktable.new_widget("button") { label = "Go", clicked_callback = rename_tags } } +if darktable.gui.current_view().id == "lighttable" then + install_module() +else + if not rt.event_registered then + darktable.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 + ) + rt.event_registered = true + end +end -darktable.register_lib ("rename_tags", "rename tag", true, true, {[darktable.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 20},}, rename_widget, nil, nil) diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index 90d6bc3..921c6ff 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -27,7 +27,6 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" local gettext = dt.gettext du.check_min_api_version("2.0.2", "slideshowMusic") diff --git a/contrib/transfer_hierarchy.lua b/contrib/transfer_hierarchy.lua index c2ba9cb..727595c 100755 --- a/contrib/transfer_hierarchy.lua +++ b/contrib/transfer_hierarchy.lua @@ -99,6 +99,10 @@ end -- Helper functions: BEGIN +local th = {} +th.module_installed = false +th.event_registered = false + local function pathExists(path) local success, err, errno = os.rename(path, path) if not success then @@ -122,6 +126,16 @@ local function createDirectory(path) end end +local function install_module() + if not th.module_installed then + darktable.register_lib(LIB_ID, + "transfer hierarchy", true, true, { + [darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 } + }, th.transfer_widget, nil, nil) + th.module_installed = true + end +end + -- Helper functions: END @@ -292,7 +306,7 @@ end -local transfer_widget = darktable.new_widget("box") { +th.transfer_widget = darktable.new_widget("box") { orientation = "vertical", darktable.new_widget("button") { label = _("calculate"), @@ -347,12 +361,18 @@ darktable.preferences.register( -- Preferences: END - - - - - -darktable.register_lib(LIB_ID, - "transfer hierarchy", true, true, { - [darktable.gui.views.lighttable] = { "DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 700 } - }, transfer_widget, nil, nil) +if darktable.gui.current_view().id == "lighttable" then + install_module() +else + if not th.event_registered then + darktable.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 + ) + th.event_registered = true + end +end diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 6755e5c..1d59935 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -96,7 +96,7 @@ local resolutions = { } } -local framerates = {"15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60"} +local framerates = {"1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60", "120", "240", "300"} local formats = { ["AVI"] = { diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index 2c46a08..76a992e 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -46,6 +46,44 @@ local function _(msgid) return gettext.dgettext("moduleExample", msgid) end +-- declare a local namespace and a couple of variables we'll need to install the module +local mE = {} +mE.widgets = {} +mE.event_registered = false -- keep track of whether we've added an event callback or not +mE.module_installed = false -- keep track of whether the module is module_installed + +--[[ We have to create the module in one of two ways depending on which view darktable starts + in. In orker to not repeat code, we wrap the darktable.register_lib in a local function. + ]] + +local function install_module() + if not mE.module_installed then + -- https://www.darktable.org/lua-api/index.html#darktable_register_lib + dt.register_lib( + "exampleModule", -- Module name + "exampleModule", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers + -- https://www.darktable.org/lua-api/types_lua_box.html + dt.new_widget("box") -- widget + { + orientation = "vertical", + dt.new_widget("button") + { + label = _("MyButton"), + clicked_callback = function (_) + dt.print(_("Button clicked")) + end + }, + table.unpack(mE.widgets), + }, + nil,-- view_enter + nil -- view_leave + ) + mE.module_installed = true + end +end -- https://www.darktable.org/lua-api/types_lua_check_button.html local check_button = dt.new_widget("check_button"){label = _("MyCheck_button"), value = true} @@ -90,35 +128,35 @@ local slider = dt.new_widget("slider") value = 52 -- The current value of the slider } --- https://www.darktable.org/lua-api/index.html#darktable_register_lib -dt.register_lib( - "exampleModule", -- Module name - "exampleModule", -- name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers - -- https://www.darktable.org/lua-api/types_lua_box.html - dt.new_widget("box") -- widget - { - orientation = "vertical", - dt.new_widget("button") - { - label = _("MyButton"), - clicked_callback = function (_) - dt.print(_("Button clicked")) +-- pack the widgets in a table for loading in the module + +table.insert(mE.widgets, check_button) +table.insert(mE.widgets, combobox) +table.insert(mE.widgets, entry) +table.insert(mE.widgets, file_chooser_button) +table.insert(mE.widgets, label) +table.insert(mE.widgets, separator) +table.insert(mE.widgets, slider) + +-- ... and tell dt about it all + + +if dt.gui.current_view().id == "lighttable" then -- make sure we are in lighttable view + install_module() -- register the lib +else + if not mE.event_registered then -- if we are not in lighttable view then register an event to signal when we might be + -- https://www.darktable.org/lua-api/index.html#darktable_register_event + dt.register_event( + "view-changed", -- we want to be informed when the view changes + function(event, old_view, new_view) + if new_view.name == "lighttable" and old_view.name == "darkroom" then -- if the view changes from darkroom to lighttable + install_module() -- register the lib + end end - }, - check_button, - combobox, - entry, - file_chooser_button, - label, - separator, - slider - }, - nil,-- view_enter - nil -- view_leave -) + ) + mE.event_registered = true -- keep track of whether we have an event handler installed + end +end -- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua -- kate: hl Lua; diff --git a/examples/multi_os.lua b/examples/multi_os.lua index 96b60c2..04acf56 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -204,24 +204,26 @@ end to see if the executable is there. ]] -local executable = "ufraw-batch" -local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){ - title = _("Select ufraw-batch[.exe] executable"), - value = df.get_executable_path_preference(executable), - is_directory = false, - changed_callback = function(self) - if df.check_if_bin_exists(self.value) then - df.set_executable_path_preference(executable, self.value) +if dt.configuration.running_os ~= "linux" then + local executable = "ufraw-batch" + local ufraw_batch_path_widget = dt.new_widget("file_chooser_button"){ + title = _("Select ufraw-batch[.exe] executable"), + value = df.get_executable_path_preference(executable), + is_directory = false, + changed_callback = function(self) + if df.check_if_bin_exists(self.value) then + df.set_executable_path_preference(executable, self.value) + end end - end -} -dt.preferences.register("executable_paths", "ufraw-batch", -- name - "file", -- type - _('multi_os: ufraw-batch location'), -- label - _('Installed location of ufraw-batch. Requires restart to take effect.'), -- tooltip - "ufraw-batch", -- default - ufraw_batch_path_widget -) + } + dt.preferences.register("executable_paths", "ufraw-batch", -- name + "file", -- type + _('multi_os: ufraw-batch location'), -- label + _('Installed location of ufraw-batch. Requires restart to take effect.'), -- tooltip + "ufraw-batch", -- default + ufraw_batch_path_widget + ) +end --[[ Add a button to the selected images module in lighttable diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 2bb0a7a..28bb9c4 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -329,6 +329,7 @@ local function _old_check_if_bin_exists(bin) -- only run on windows if preferen local path = nil if string.match(bin, "\\") then + path = bin else path = dtutils_file.get_executable_path_preference(bin) @@ -540,7 +541,7 @@ function dtutils_file.check_if_file_exists(filepath) local p = io.popen("if exist " .. dtutils_file.sanitize_filename(filepath) .. " (echo 'yes') else (echo 'no')") local ans = p:read("*all") p:close() - if string.match(ans, "yes") then + if string.match(ans, "yes") then result = true end -- result = os.execute('if exist "'..filepath..'" (cmd /c exit 0) else (cmd /c exit 1)') @@ -690,8 +691,8 @@ dtutils_file.libdoc.functions["filename_increment"] = { local result = df.filename_increment(filepath) filepath - string - filename to increment]], - Description = [[filename_increment solves the problem of filename confllict by adding an - increment to the filename. If the supplied filename has no increment then + Description = [[filename_increment solves the problem of filename confllict by adding an + increment to the filename. If the supplied filename has no increment then "01" is added to the basename. If the filename already has an increment, then 1 is added to it and the filename returned.]], Return_Value = [[result - string - the incremented filename]], @@ -743,7 +744,7 @@ dtutils_file.libdoc.functions["create_unique_filename"] = { filepath - string - the path and filename requested]], Description = [[create_unique_filename takes a requested filepath and checks to see if it exists. If if doesn't then it's returned intact. If it already exists, then a two - digit increment is added to the filename and it is tested again. The increment keeps + digit increment is added to the filename and it is tested again. The increment keeps increasing until either a unique filename is found or there have been 100 attempts.]], Return_Value = [[result - string - the incremented filename]], Limitations = [[create_unique_filename will only attempt 100 increments.]], @@ -822,7 +823,7 @@ dtutils_file.libdoc.functions["executable_path_widget"] = { local widget = df.executable_path_widget(executables) executables - table - a table of strings that are executable names]], Description = [[executable_path_widget takes a table of executable names - and builds a set of file selector widgets to get the path to the executable. + and builds a set of file selector widgets to get the path to the executable. The resulting widgets are wrapped in a box widget and returned.]], Return_Value = [[widget - widget - a widget containing a file selector widget for each executable.]], @@ -837,10 +838,10 @@ dtutils_file.libdoc.functions["executable_path_widget"] = { function dtutils_file.executable_path_widget(executables) local box_widgets = {} table.insert(box_widgets, dt.new_widget("section_label"){label = "select executable(s)"}) - for _, executable in pairs(executables) do + for _, executable in pairs(executables) do table.insert(box_widgets, dt.new_widget("label"){label = "select " .. executable .. " executable"}) local path = dtutils_file.get_executable_path_preference(executable) - if not path then + if not path then path = "" end table.insert(box_widgets, dt.new_widget("file_chooser_button"){ @@ -870,7 +871,7 @@ dtutils_file.libdoc.functions["sanitize_filename"] = { local sanitized_filename = df.sanitize_filename(filename) filename - string - a filepath and filename]], Description = [[sanitize_file places quotes around the filename in an - operating system specific manner. The result is safe to pass as + operating system specific manner. The result is safe to pass as an argument to the operating system.]], Return_Value = [[sanitized_filename - string - quoted filename]], Limitations = [[]], @@ -892,7 +893,7 @@ dtutils_file.libdoc.functions["mkdir"] = { df.mkdir(path) path - string - a directory path]], - Description = [[mkdir creates directories if not already exists. It + Description = [[mkdir creates directories if not already exists. It create whole parents subtree if needed ]], Return_Value = [[path - string - a directory path]], @@ -904,7 +905,7 @@ dtutils_file.libdoc.functions["mkdir"] = { Copyright = [[]], } -function dtutils_file.mkdir(path) +function dtutils_file.mkdir(path) if not dtutils_file.check_if_file_exists(path) then local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p" return dsys.external_command(mkdir_cmd.." "..path) @@ -983,5 +984,6 @@ if dt.configuration.running_os == "windows" then false) end + return dtutils_file diff --git a/lib/dtutils/string.lua b/lib/dtutils/string.lua index f58f38c..e3cc817 100644 --- a/lib/dtutils/string.lua +++ b/lib/dtutils/string.lua @@ -269,5 +269,31 @@ function dtutils_string.sanitize(str) end end +dtutils_string.libdoc.functions["sanitize_lua"] = { + Name = [[sanitize_lua]], + Synopsis = [[escape lua 'magic' characters from a pattern string]], + Usage = [[local ds = require "lib/dtutils.string" + + local result = ds.sanitize_lua(str) + str - string - the string that needs to be made safe]], + Description = [[sanitize_lua escapes lua 'magic' characters so that + a string may be used in lua string/patten matching.]], + Return_Value = [[result - string - a lua pattern safe string]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_string.sanitize_lua(str) + str = string.gsub(str, "%-", "%%-") + str = string.gsub(str, "%(", "%%(") + str = string.gsub(str, "%)", "%%)") + return str +end + + return dtutils_string diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index f202d6d..f41f821 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -1,7 +1,6 @@ local dtutils_system = {} local dt = require "darktable" -require "official/yield" -- necessary for dt.control.execute() dtutils_system.libdoc = { Name = [[dtutils.system]], diff --git a/locale/de_DE/LC_MESSAGES/OpenInExplorer.po b/locale/de_DE/LC_MESSAGES/OpenInExplorer.po index 07d451a..f0dc8ef 100644 --- a/locale/de_DE/LC_MESSAGES/OpenInExplorer.po +++ b/locale/de_DE/LC_MESSAGES/OpenInExplorer.po @@ -1,39 +1,78 @@ -# OpenInExplorer Darktable Plug-In. -# Copyright (C) 2020 Volker Lenhardt +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # Volker Lenhardt , 2020. # #, fuzzy msgid "" msgstr "" -"Project-Id-Version: OpenInExplorer\n" +"Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2020-04-19 14:43+0200\n" +"POT-Creation-Date: 2020-06-11 19:07+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" -"Last-Translator: Volker Lenhardt \n" +"Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" "Language: de_DE\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -#: OpenInExplorer.lua:64 +#: OpenInExplorer.lua:71 msgid "" -"OpenInExplorer plug-in only supports Linux, macOS, and Windows at this time" -msgstr "OpenInExplorer-Plug-in kann nur auf macOS, Linux oder Windows laufen" +"OpenInExplorer plug-in only supports Linux and macOS, and Windows at this time" +msgstr "OpenInExplorer-Plug-in unterstützt zur Zeit nur Linux, macOS oder Windows" -#: OpenInExplorer.lua:79 +#: OpenInExplorer.lua:87 +msgid "" +"No links directory selected.\n" +"Please check the dt preferences (lua options)" +msgstr "Kein Verknüpfungs-Verzeichnis ausgewählt.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)" + +#: OpenInExplorer.lua:91 +#, lua-format +msgid "" +"Links directory '%s' not found.\n" +"Please check the dt preferences (lua options)" +msgstr "Das Verknüpfungs-Verzeichnis '%s' konnte nicht gefunden werden.\nBitte die dt-Voreinstellungen überprüfen (Lua-Optionen)" + +#: OpenInExplorer.lua:148 +msgid "Failed to create links. Missing rights?" +msgstr "Die Verknüpfungen konnten nicht erstellt werden. Fehlende Rechte?" + +#: OpenInExplorer.lua:164 msgid "Please select an image" msgstr "Es wurde kein Bild ausgewählt" -#: OpenInExplorer.lua:98 -msgid "Please select fewer images (max 15)" +#: OpenInExplorer.lua:170 +msgid "Please select fewer images (max. 15)" msgstr "Bitte nicht mehr als 15 Bilder auswählen" -#: OpenInExplorer.lua:105 +#: OpenInExplorer.lua:186 msgid "show in file explorer" msgstr "im Dateimanager anzeigen" -#: OpenInExplorer.lua:107 -msgid "Opens the file manager at the selected image's location" +#: OpenInExplorer.lua:188 +msgid "Open the file manager at the selected image's location" msgstr "Öffnet den Dateimanager an der Position des ausgewählten Bildes" + +#: OpenInExplorer.lua:193 +msgid "OpenInExplorer: linked files directory" +msgstr "OpenInExplorer: Verknüpfungs-Verzeichnis" + +#: OpenInExplorer.lua:194 +msgid "" +"Directory to store the links to the file names. Requires restart to take " +"effect" +msgstr "Verzeichnis für die Verknüpfungen zu den Dateinamen. Erfordert dt-Neustart" + +#: OpenInExplorer.lua:197 +msgid "Select directory" +msgstr "Verzeichnis auswählen" + +#: OpenInExplorer.lua:203 +msgid "OpenInExplorer: use links" +msgstr "OpenInExplorer: Verknüpfungen nutzen" + +#: OpenInExplorer.lua:204 +msgid "Use links instead of multiple windows. Requires restart to take effect" +msgstr "Verknüpfungen statt einzelner Fenster nutzen. Erfordert dt-Neustart" diff --git a/locale/de_DE/LC_MESSAGES/photils.po b/locale/de_DE/LC_MESSAGES/photils.po new file mode 100644 index 0000000..b81dbc7 --- /dev/null +++ b/locale/de_DE/LC_MESSAGES/photils.po @@ -0,0 +1,73 @@ +# SOME DESCRIPTIVE TITLE. +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER +# This file is distributed under the same license as the PACKAGE package. +# FIRST AUTHOR , YEAR. +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: Lua photils\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2020-05-12 13:12+0200\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: de\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +msgid "get tags" +msgstr "hole stichwörter" + +msgid "requires a restart to be applied" +msgstr "erfordert einen Neustart um angewendet zu werden" + +msgid "min confidence value" +msgstr "min vertrauenswert" + +msgid "" +"The suggested tags were not generated\n" +" for the currently selected image!" +msgstr "" +"Die vorgeschlagenen Stichwörter wurden für das\n" +"aktuell ausgewählte Bild nicht generiert!" + +#, lua-format +msgid " page %s of %s " +msgstr " seite %s von %s " + +msgid "Tags successfully attached to image" +msgstr "Stichwörter erfolgreich an Bild angefügt" + +#, lua-format +msgid "Error writing to `%s`" +msgstr "Fehler beim Schreiben in `%s`" + +#, lua-format +msgid "%s failed, see terminal output for details" +msgstr "%s fehlgeschlagen, siehe Terminal-Ausgabe für Details" + +#, lua-format +msgid "%s found %d tags for your image" +msgstr "%s gefunden %d-Stichwörter für Ihr Bild" + +msgid "No image selected." +msgstr "Kein Bild ausgewählt." + +msgid "This plugin can only handle a single image." +msgstr "Dieses Plugin kann nur ein einziges Bild verarbeiten." + +#, lua-format +msgid "attach %d tags" +msgstr "%d stichwörter anhängen" + +msgid "photils-cli not found" +msgstr "photils-cli nicht gefunden" + +msgid "" +"Select an image, click \"Get Tags\" and get \n" +"suggestions for tags." +msgstr "" +"Wählen Sie ein Bild aus, drücken Sie \"Hole Stichwörter\"\n" +" und erhalten Sie Vorschläge für Stichwörter." diff --git a/official/enfuse.lua b/official/enfuse.lua index 3b5bb7c..a2ac3ec 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -46,10 +46,35 @@ du.check_min_api_version("3.0.0", "enfuse") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("enfuse",dt.configuration.config_dir..PS .. "lua" .. PS .. "locale" .. PS) +local enf = {} +enf.event_registered = false +enf.module_installed = false +enf.lib_widgets = {} + local function _(msgid) return gettext.dgettext("enfuse", msgid) end +local function install_module() + if not enf.module_installed then + dt.register_lib( + "enfuse", -- plugin name + "enfuse", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers + dt.new_widget("box") -- widget + { + orientation = "vertical", + sensitive = enfuse_installed, + table.unpack(enf.lib_widgets) + }, + nil,-- view_enter + nil -- view_leave + ) + enf.module_installed = true + end +end -- add a new lib -- is enfuse installed? local enfuse_installed = df.check_if_bin_exists("enfuse") @@ -229,30 +254,30 @@ if enfuse_installed then local lib_widgets = {} if not enfuse_installed then - table.insert(lib_widgets, df.executable_path_widget({"ffmpeg"})) + table.insert(enf.lib_widgets, df.executable_path_widget({"ffmpeg"})) end - table.insert(lib_widgets, exposure_mu) - table.insert(lib_widgets, depth) - table.insert(lib_widgets, blend_colorspace) - table.insert(lib_widgets, enfuse_button) + table.insert(enf.lib_widgets, exposure_mu) + table.insert(enf.lib_widgets, depth) + table.insert(enf.lib_widgets, blend_colorspace) + table.insert(enf.lib_widgets, enfuse_button) -- ... and tell dt about it all - dt.register_lib( - "enfuse", -- plugin name - "enfuse", -- name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers - dt.new_widget("box") -- widget - { - orientation = "vertical", - sensitive = enfuse_installed, - table.unpack(lib_widgets) - }, - nil,-- view_enter - nil -- view_leave - ) + if dt.gui.current_view().id == "lighttable" then + install_module() + else + if not enf.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 + ) + enf.event_registered = true + end + end else dt.print_error("enfuse executable not found") error("enfuse executable not found") diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index a214a2a..0d34330 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -37,7 +37,6 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" require "darktable.debug" -require "official/yield" du.check_min_api_version("2.1.0", "generate_image_txt") diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index 18e57ca..7459c52 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -33,8 +33,22 @@ local du = require "lib/dtutils" du.check_min_api_version("2.0.0", "image_path_in_ui") +local ipiu = {} +ipiu.module_installed = false +ipiu.event_registered = false + local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle", halign = "start"} +local function install_module() + if not ipiu.module_installed then + dt.register_lib("image_path_no_ui","Selected Images path",true,false,{ + [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",300} + }, main_label + ) + ipiu.module_installed = true + end +end + local function reset_widget() local selection = dt.gui.selection() local result = "" @@ -54,10 +68,21 @@ end main_label.reset_callback = reset_widget -dt.register_lib("image_path_no_ui","Selected Images path",true,false,{ - [dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER",300} - }, main_label - ); +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not ipiu.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 + ) + ipiu.event_registered = true + end +end dt.register_event("mouse-over-image-changed",reset_widget); diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index f6fbfe8..9d548da 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -35,7 +35,6 @@ Plugin allows you to choose how many thumbnails you need per row ]] local dt = require "darktable" local du = require "lib/dtutils" -require "official/yield" du.check_min_api_version("2.0.0", "selection_to_pdf") diff --git a/official/yield.lua b/official/yield.lua deleted file mode 100644 index 3e5cb9b..0000000 --- a/official/yield.lua +++ /dev/null @@ -1,42 +0,0 @@ ---[[ - This file is part of darktable, - Copyright 2016 by Tobias Jakobs. - - 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 . -]] ---[[ -darktable yield compatibility script - -USAGE -* require this script from your main Lua file in the first line - -]] -local dt = require "darktable" -local yield_orig = coroutine.yield - -if (dt.configuration.api_version_major < 4) then - dt.control = {} - dt.control.execute = function(command) - yield_orig("RUN_COMMAND", command) - end - dt.control.read = function(command) - yield_orig("FILE_READABLE", command) - end - dt.control.sleep = function(command) - yield_orig("WAIT_MS", command) - end -end - --- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua --- kate: hl Lua; diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 03e35f6..db65dfa 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -40,6 +40,10 @@ local gettext = dt.gettext gettext.bindtextdomain("executable_manager",dt.configuration.config_dir.."/lua/locale/") +local exec_man = {} -- our own namespace +exec_man.module_installed = false +exec_man.event_registered = false + -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -- F U N C T I O N S -- - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -86,12 +90,30 @@ local function update_combobox_choices(combobox, choice_table, selected) combobox.value = selected end +local function install_module() + if not exec_man.module_installed then + dt.register_lib( + "executable_manager", -- Module name + "executable manager", -- Visible name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 100}}, -- containers + dt.new_widget("box") -- widget + { + orientation = "vertical", + exec_man.selector, + exec_man.stack, + }, + nil,-- view_enter + nil -- view_leave + ) + exec_man.module_installed = true + end +end -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -- M A I N P R O G R A M -- - - - - - - - - - - - - - - - - - - - - - - - - - - - -local exec_man = {} -- our own namespace - local DARKTABLERC = dt.configuration.config_dir .. PS .. "darktablerc" @@ -117,8 +139,15 @@ for _,pref in ipairs(matches) do end local executable_path_widgets = {} +local executable_path_values = {} +local placeholder_text = dt.configuration.running_os == windows and _("select an executable") or _("search path for executable") for i,exec in ipairs(exec_table) do + executable_path_values[exec] = dt.new_widget("entry"){ + text = df.get_executable_path_preference(exec), + placeholder = placeholder_text, + editable = false + } executable_path_widgets[exec] = dt.new_widget("file_chooser_button"){ title = _("select ") .. exec .. _(" executable"), value = df.get_executable_path_preference(exec), @@ -126,6 +155,7 @@ for i,exec in ipairs(exec_table) do changed_callback = function(self) if df.check_if_bin_exists(self.value) then df.set_executable_path_preference(exec, self.value) + executable_path_values[exec].text = df.get_executable_path_preference(exec) end end } @@ -155,13 +185,18 @@ exec_man.selector = dt.new_widget("combobox"){ for i,exec in ipairs(exec_table) do exec_man.stack[i] = dt.new_widget("box"){ + dt.new_widget("section_label"){label = _("current")}, + executable_path_values[exec], + dt.new_widget("section_label"){label = _("select")}, executable_path_widgets[exec], + dt.new_widget("section_label"){label = _("reset")}, dt.new_widget("button"){ label = "clear", tooltip = _("Clear path for ") .. exec, clicked_callback = function() df.set_executable_path_preference(exec, "") executable_path_widgets[exec].value = "" + executable_path_values[exec].text = "" end } @@ -174,19 +209,18 @@ update_combobox_choices(exec_man.selector, exec_table, 1) -- register the lib -dt.register_lib( - "executable_manager", -- Module name - "executable manager", -- Visible name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_BOTTOM", 100}}, -- containers - dt.new_widget("box") -- widget - { - orientation = "vertical", - exec_man.selector, - exec_man.stack, - }, - nil,-- view_enter - nil -- view_leave -) - +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not exec_man.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 + ) + exec_man.event_registered = true + end +end diff --git a/tools/script_manager.lua b/tools/script_manager.lua index 9759b88..684d488 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -39,6 +39,7 @@ 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 gettext = dt.gettext @@ -64,6 +65,7 @@ local CS = dt.configuration.running_os == "windows" and "&" or ";" local LUA_DIR = dt.configuration.config_dir .. PS .. "lua" local LUA_SCRIPT_REPO = "https://github.com/darktable-org/lua-scripts.git" +local CURR_API_VERSION = dt.configuration.api_version_string dt.print_log("LUA_DIR is " .. LUA_DIR) @@ -215,7 +217,7 @@ local function scan_scripts() -- scan the scripts local output = io.popen(find_cmd) for line in output:lines() do - local l = string.gsub(line, LUA_DIR .. PS, "") -- strip the lua dir off + local l = string.gsub(line, ds.sanitize_lua(LUA_DIR) .. PS, "") -- strip the lua dir off local script_file = l:sub(1,-5) if not string.match(script_file, "script_manager") then -- let's not include ourself if not string.match(script_file, "plugins") then -- skip plugins @@ -224,7 +226,7 @@ local function scan_scripts() if not string.match(script_file, "yield") then -- special case, because everything needs this add_script_data(script_file) else - prequire(script_file) -- load yield.lua + -- prequire(script_file) -- load yield.lua end end end @@ -283,7 +285,7 @@ local function deactivate(script, scriptname) end local function create_enable_disable_button(btext, sname, req) - return dt.new_widget("button") + local button = dt.new_widget("button") { label = btext .. sname, tooltip = get_script_doc(req), @@ -309,6 +311,10 @@ local function create_enable_disable_button(btext, sname, req) end end } + if CURR_API_VERSION >= "6.0.1" then + button.ellipsize = "middle" + end + return button end local function load_script_stack() @@ -449,6 +455,26 @@ local function link_downloads_directory() os.execute("ln -s " .. "$HOME/Downloads " .. LUA_DIR .. "/downloads") end +local function install_module() + if not sm.module_installed then + dt.register_lib( + "script_manager", -- Module name + "script manager", -- Visible name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers + dt.new_widget("box") -- widget + { + orientation = "vertical", + sm.main_box, + }, + nil,-- view_enter + nil -- view_leave + ) + sm.module_installed = true + end +end + -- - - - - - - - - - - - - - - - - - - - - - - - -- M A I N P R O G R A M -- - - - - - - - - - - - - - - - - - - - - - - - @@ -465,6 +491,8 @@ sm.script_names = {} sm.script_paths = {} sm.main_menu_choices = {} sm.main_stack_items = {} +sm.event_registered = false +sm.module_installed = false -- see if we've run this before @@ -710,6 +738,12 @@ sm.main_stack = dt.new_widget("stack"){ table.unpack(sm.main_stack_items), } +if CURR_API_VERSION >= "6.0.1" then + sm.main_stack.h_size_fixed = false + sm.main_stack.v_size_fixed = false +end + + -- make a combobox for the selector sm.main_menu = dt.new_widget("combobox"){ @@ -741,21 +775,21 @@ sm.main_box = dt.new_widget("box"){ -- D A R K T A B L E I N T E G R A T I O N -- - - - - - - - - - - - - - - - - - - - - - - - --- register the module -dt.register_lib( - "script_manager", -- Module name - "script manager", -- Visible name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_LEFT_CENTER", 0}}, -- containers - dt.new_widget("box") -- widget - { - orientation = "vertical", - sm.main_box, - }, - nil,-- view_enter - nil -- view_leave -) +if dt.gui.current_view().id == "lighttable" then + install_module() +else + if not sm.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 + ) + sm.event_registered = true + end +end -- set up the scripts block if we have them otherwise we'll wait until we download them