diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua new file mode 100644 index 0000000..0d92915 --- /dev/null +++ b/contrib/AutoGrouper.lua @@ -0,0 +1,158 @@ +--[[AutoGrouper plugin for darktable + + copyright (c) 2019 Kevin Ertel + + 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 . +]] + +--[[About this Plugin +This plugin adds the module "Auto Group" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +None + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Set a gap amount in second which will be used to determine when images should no +longer be added to a group. If an image is more then the specified amount of time +from the last image in the group it will not be added. Images without timestamps +in exif data will be ignored. + +There are two buttons. One allows the grouping to be performed only on the currently +selected images, the other button performs grouping on the entire active collection +]] + +local dt = require "darktable" +local MOD = 'autogrouper' +local gettext = dt.gettext +-- Tell gettext where to find the .mo file translating messages for a particular domain +gettext.bindtextdomain("AutoGrouper",dt.configuration.config_dir.."/lua/locale/") +local function _(msgid) + return gettext.dgettext("AutoGrouper", msgid) +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 + else + return false + end +end + +local function CompTime(first, second) --compares the timestamps and returns true if first was taken before second + first_time = first.exif_datetime_taken + if string.match(first_time, '[0-9]') == nil then first_time = '9999:99:99 99:99:99' end + first_time = tonumber(string.gsub(first_time, '[^0-9]*','')) + second_time = second.exif_datetime_taken + if string.match(second_time, '[0-9]') == nil then second_time = '9999:99:99 99:99:99' end + second_time = tonumber(string.gsub(second_time, '[^0-9]*','')) + return first_time < second_time +end + +local function SeperateTime(str) --seperates the timestamp into individual components for used with OS.time operations + local cleaned = string.gsub(str, '[^%d]',':') + cleaned = string.gsub(cleaned, '::*',':') --YYYY:MM:DD:hh:mm:ss + local year = string.sub(cleaned,1,4) + local month = string.sub(cleaned,6,7) + local day = string.sub(cleaned,9,10) + local hour = string.sub(cleaned,12,13) + local min = string.sub(cleaned,15,16) + local sec = string.sub(cleaned,18,19) + return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} +end + +local function GetTimeDiff(curr_image, prev_image) --returns the time difference (in sec.) from current image and the previous image + local curr_time = SeperateTime(curr_image.exif_datetime_taken) + local prev_time = SeperateTime(prev_image.exif_datetime_taken) + return os.time(curr_time)-os.time(prev_time) +end + +local function main(on_collection) + local images = {} + if on_collection then + local col_images = dt.collection + for i,image in ipairs(col_images) do --copy images to a standard table, table.sort barfs on type dt_lua_singleton_image_collection + table.insert(images,i,image) + end + else + images = dt.gui.selection() + end + dt.preferences.write(MOD, 'active_gap', 'integer', GUI.gap.value) + if #images < 2 then + dt.print('please select at least 2 images') + return + end + table.sort(images, function(first, second) return CompTime(first,second) end) --sort images by timestamp + + for i, image in ipairs(images) do + if i == 1 then + prev_image = image + elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then --make sure current image has a timestamp, if so check if it is within the user specified gap value and add to group + local curr_image = image + if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then + images[i]:group_with(images[i-1]) + end + prev_image = curr_image + end + 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'){ + label = _('group gap [sec.]'), + tooltip = _('minimum gap, in seconds, between groups'), + soft_min = 1, + soft_max = 60, + hard_min = 1, + hard_max = 86400, + step = 1, + digits = 0, + value = temp, + reset_callback = function(self) + self.value = 3 + end +} +GUI.selected = dt.new_widget("button"){ + label = _('auto group: selected'), + tooltip =_('auto group selected images'), + clicked_callback = function() main(false) end +} +GUI.collection = dt.new_widget("button"){ + label = _('auto group: collection'), + 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 + } +) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua new file mode 100644 index 0000000..5996592 --- /dev/null +++ b/contrib/CollectHelper.lua @@ -0,0 +1,221 @@ +--[[Collect Helper plugin for darktable + + copyright (c) 2019 Kevin Ertel + + 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 . +]] + +--[[About this plugin +This plugin adds the button(s) to the "Selected Images" module: +1) Return to Previous Collection +2) Collect on image's Folder +3) Collect on image's Color Label(s) +4) Collect on All (AND) + +It also adds 3 preferences to the lua options dialog box which allow the user to activate/deactivate the 3 "Collect on" buttons. + +Button Behavior: +1) Return to Previous Collection - Will reset the collect parameters to the previously active settings +2) Collect on image's Folder - Will change the collect parameters to be "Folder" with a value of the selected image's folder location +3) Collect on image's Color Label(s) - Will change the collect parameter to be "Color" with a value of the selected images color labels, will apply multiple parameters with AND logic if multiple exist +4) Collect on All (AND) - Will collect on all parameters activated by the preferences dialog, as such this button is redundant if you only have one of the two other options enabled + +----REQUIRED SOFTWARE---- +NA + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Select the photo you wish to change you collection based on. +In the "Selected Images" module click on "Collect on this Image" + +----KNOWN ISSUES---- +]] + +local dt = require "darktable" +local gettext = dt.gettext +local previous = nil +local all_active = false + +-- Tell gettext where to find the .mo file translating messages for a particular domain +gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/") + +local function _(msgid) + return gettext.dgettext("CollectHelper", msgid) +end + +-- FUNCTION -- +local function CheckSingleImage(selection) + if #selection ~= 1 then + dt.print(_("Please select a single image")) + return true + end + return false +end +local function CheckHasColorLabel(selection) + local ret = false + for _,image in pairs(selection) do + if image.red then ret = true end + if image.blue then ret = true end + if image.green then ret = true end + if image.yellow then ret = true end + if image.purple then ret = true end + end + return ret +end +local function PreviousCollection() + if previous ~= nil then + previous = dt.gui.libs.collect.filter(previous) + end +end +local function CollectOnFolder(all_rules, all_active) + local images = dt.gui.selection() + if CheckSingleImage(images) then + return + end + local rules = {} + local rule = dt.gui.libs.collect.new_rule() + rule.mode = "DT_LIB_COLLECT_MODE_AND" + rule.data = images[1].path + rule.item = "DT_COLLECTION_PROP_FOLDERS" + table.insert(rules, rule) + if all_active then + for _,active_rule in pairs(rules) do + table.insert(all_rules, active_rule) + end + return all_rules + else + previous = dt.gui.libs.collect.filter(rules) + end +end +local function CollectOnColors(all_rules, all_active) + local images = dt.gui.selection() + if CheckSingleImage(images) then + return + end + if not CheckHasColorLabel(images) then + dt.print(_('select an image with an active color label')) + return + end + for _,image in pairs(images) do + local rules = {} + if image.red then + local red_rule = dt.gui.libs.collect.new_rule() + red_rule.mode = "DT_LIB_COLLECT_MODE_AND" + red_rule.data = "red" + red_rule.item = "DT_COLLECTION_PROP_COLORLABEL" + table.insert(rules, red_rule) + end + if image.blue then + local blue_rule = dt.gui.libs.collect.new_rule() + blue_rule.mode = "DT_LIB_COLLECT_MODE_AND" + blue_rule.data = "blue" + blue_rule.item = "DT_COLLECTION_PROP_COLORLABEL" + table.insert(rules, blue_rule) + end + if image.green then + local green_rule = dt.gui.libs.collect.new_rule() + green_rule.mode = "DT_LIB_COLLECT_MODE_AND" + green_rule.data = "green" + green_rule.item = "DT_COLLECTION_PROP_COLORLABEL" + table.insert(rules, green_rule) + end + if image.yellow then + local yellow_rule = dt.gui.libs.collect.new_rule() + yellow_rule.mode = "DT_LIB_COLLECT_MODE_AND" + yellow_rule.data = "yellow" + yellow_rule.item = "DT_COLLECTION_PROP_COLORLABEL" + table.insert(rules, yellow_rule) + end + if image.purple then + local purple_rule = dt.gui.libs.collect.new_rule() + purple_rule.mode = "DT_LIB_COLLECT_MODE_AND" + purple_rule.data = "purple" + purple_rule.item = "DT_COLLECTION_PROP_COLORLABEL" + table.insert(rules, purple_rule) + end + if all_active then + for _,active_rule in pairs(rules) do + table.insert(all_rules, active_rule) + end + return all_rules + else + previous = dt.gui.libs.collect.filter(rules) + end + end +end +local function CollectOnAll_AND() + local images = dt.gui.selection() + if CheckSingleImage(images) then + return + end + local rules = {} + if dt.preferences.read('module_CollectHelper','folder','bool') then + rules = CollectOnFolder(rules, true) + end + if dt.preferences.read('module_CollectHelper','colors','bool') then + rules = CollectOnColors(rules, true) + end + previous = dt.gui.libs.collect.filter(rules) +end + +-- GUI -- +dt.gui.libs.image.register_action( + _("collect: previous"), + function() PreviousCollection() end, + _("Sets the Collect parameters to be the previously active parameters") +) +if dt.preferences.read('module_CollectHelper','folder','bool') then + dt.gui.libs.image.register_action( + _("collect: folder"), + function() CollectOnFolder(_ , false) end, + _("Sets the Collect parameters to be the selected images's folder") + ) +end +if dt.preferences.read('module_CollectHelper','colors','bool') then + dt.gui.libs.image.register_action( + _("collect: color label(s)"), + function() CollectOnColors(_ , false) end, + _("Sets the Collect parameters to be the selected images's color label(s)") + ) +end +if dt.preferences.read('module_CollectHelper','all_and','bool') then + dt.gui.libs.image.register_action( + _("collect: all (AND)"), + function() CollectOnAll_AND() end, + _("Sets the Collect parameters based on all activated CollectHelper options") + ) +end + +-- PREFERENCES -- +dt.preferences.register("module_CollectHelper", "all_and", -- name + "bool", -- type + _('CollectHelper: All'), -- label + _('Will create a collect parameter set that utelizes all enabled CollectHelper types (AND)'), -- tooltip + true -- default +) +dt.preferences.register("module_CollectHelper", "colors", -- name + "bool", -- type + _('CollectHelper: Color Label(s)'), -- label + _('Enable the button that allows you to swap to a collection based on selected image\'s COLOR LABEL(S)'), -- tooltip + true -- default +) +dt.preferences.register("module_CollectHelper", "folder", -- name + "bool", -- type + _('CollectHelper: Folder'), -- label + _('Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location'), -- tooltip + true -- default +) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua new file mode 100644 index 0000000..ad7dd3a --- /dev/null +++ b/contrib/HDRMerge.lua @@ -0,0 +1,424 @@ +--[[HDRMerge plugin for darktable + + copyright (c) 2018 Kevin Ertel + + 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 . +]] + +--[[About this Plugin +This plugin adds the module 'HDRMerge' to darktable's lighttable view + +----REQUIRED SOFTWARE---- +HDRMerge ver. 4.5 or greater + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your 'lua/contrib' folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in +On the initial startup go to darktable settings > lua options and set your executable paths and other preferences, then restart darktable + +Select bracketed images and press the Run HDRMerge button. The resulting DNG will be auto-imported into darktable. +Additional tags or style can be applied on auto import as well, if you desire. + +Base Options: +Select your desired BPS (bits per sample and Embedded Preview Size. + +Batch Options: +Select if you want to run in batch mode or not +Select the gap, in seconds, between images for auto grouping in batch mode + +See HDRMerge manual for further detail: http://jcelaya.github.io/hdrmerge/documentation/2014/07/11/user-manual.html + +Auto-import Options: +Select a style, whether you want tags to be copied from the original, and any additional tags you desire added when the new image is auto-imported +]] + +local dt = require 'darktable' +local df = require 'lib/dtutils.file' +local dsys = require 'lib/dtutils.system' +local mod = 'module_HDRMerge' +local os_path_seperator = '/' +if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end + +-- Tell gettext where to find the .mo file translating messages for a particular domain +local gettext = dt.gettext +gettext.bindtextdomain('HDRMerge',dt.configuration.config_dir..'/lua/locale/') +local function _(msgid) + return gettext.dgettext('HDRMerge', msgid) +end + +local temp +local HDRM = { --HDRMerge Program Table + name = 'HDRMerge', + bin = '', + first_run = true, + install_error = false, + arg_string = '', + images_string = '', + args = { + bps ={text = '-b ', style = 'integer'}, + size ={text = '-p ', style = 'string'}, + batch ={text = '-B ', style = 'bool'}, + gap ={text = '-g ', style = 'integer'} + } +} +local GUI = { --GUI Elements Table + HDR = { + bps ={}, + size ={}, + batch ={}, + gap ={} + }, + Target = { + style ={}, + copy_tags ={}, + add_tags ={} + }, + run = {}, + stack = {}, + options = {}, + exes = { + HDRMerge = {}, + update = {}, + } +} + +--Detect User Styles-- +local styles = dt.styles +local styles_count = 1 -- 'none' = 1 +for _,i in pairs(dt.styles) do + if type(i) == 'userdata' then styles_count = styles_count + 1 end +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 + else + return false + end +end + +local function GetFileName(full_path) --Parses a full path (path/filename_identifier.extension) into individual parts +--[[Input: Folder1/Folder2/Folder3/Img_0001.CR2 + + Returns: + path: Folder1/Folder2/Folder3/ + filename: Img_0001 + identifier: 0001 + extension: .CR2 + + EX: + path_1, file_1, id_1, ext_1 = GetFileName(full_path_1) + ]] + local path = string.match(full_path, '.*[\\/]') + local filename = string.gsub(string.match(full_path, '[%w-_]*%.') , '%.' , '' ) + local identifier = string.match(filename, '%d*$') + local extension = string.match(full_path, '%.%w*') + return path, filename, identifier, extension +end + +local function CleanSpaces(text) --removes spaces from the front and back of passed in text + text = string.gsub(text,'^%s*','') + text = string.gsub(text,'%s*$','') + return text +end + +local function BuildExecuteCmd(prog_table) --creates a program command using elements of the passed in program table + local result = CleanSpaces(prog_table.bin)..' '..CleanSpaces(prog_table.arg_string)..' '..CleanSpaces(prog_table.images_string) + return result +end + +local function PreCall(prog_tbl) --looks to see if this is the first call, if so checks to see if program is installed properly + for _,prog in pairs(prog_tbl) do + if prog.first_run then + prog.bin = df.check_if_bin_exists(prog.name) + if not prog.bin then + prog.install_error = true + dt.preferences.write(mod, 'bin_exists', 'bool', false) + else + prog.bin = CleanSpaces(prog.bin) + end + prog.first_run = false + end + end + if not dt.preferences.read(mod, 'bin_exists', 'bool') then + GUI.stack.active = 2 + dt.print(_('please update you binary locatoin')) + end +end + +local function ExeUpdate(prog_tbl) + dt.preferences.write(mod, 'bin_exists', 'bool', true) + for _,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 + prog.install_error = true + dt.preferences.write(mod, 'bin_exists', 'bool', false) + else + prog.bin = CleanSpaces(prog.bin) + end + prog.first_run = false + end + if dt.preferences.read(mod, 'bin_exists', 'bool') then + GUI.stack.active = 1 + dt.print(_('update successful')) + else + dt.print(_('update unsuccessful, please try again')) + end +end + +local function UpdateActivePreference() --sliders & entry boxes do not have a click/changed callback, so their values must be saved to the active preference + temp = GUI.HDR.gap.value + dt.preferences.write(mod, 'active_gap', 'integer', temp) + temp = GUI.Target.add_tags.text + dt.preferences.write(mod, 'active_add_tags', 'string', temp) +end + +local function main() + PreCall({HDRM}) --check if furst run then check if install OK + if HDRM.install_error then + dt.print_error(_('HDRMerge install issue')) + dt.print(_('HDRMerge install issue, please ensure the binary path is proper')) + return + end + images = dt.gui.selection() --get selected images + if #images < 2 then --ensure enough images selected + dt.print(_('not enough images selected, select at least 2 images to merge')) + return + end + + UpdateActivePreference() --save current gui elements to active preference so those values will be pre-loaded at next startup + + --create image string and output path + HDRM.images_string = '' + local out_path = '' + local smallest_id = math.huge + local smallest_name = '' + local largest_id = 0 + local source_raw = {} + for _,image in pairs(images) do --loop to concat the images string, also track the image indexes for use in creating the final image name (eg; IMG_1034-1037.dng) + local curr_image = image.path..os_path_seperator..image.filename + HDRM.images_string = HDRM.images_string..df.sanitize_filename(curr_image)..' ' + out_path = image.path + _, source_name, source_id = GetFileName(image.filename) + source_id = tonumber(source_id) + if source_id < smallest_id then + smallest_id = source_id + smallest_name = source_name + source_raw = image + end + if source_id > largest_id then largest_id = source_id end + end + out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng' + out_path = df.create_unique_filename(out_path) + + --create argument string + HDRM.arg_string = HDRM.args.bps.text..GUI.HDR.bps.value..' '..HDRM.args.size.text..GUI.HDR.size.value..' ' + if GUI.HDR.batch.value then + HDRM.arg_string = HDRM.arg_string..HDRM.args.batch.text..HDRM.args.gap.text..math.floor(GUI.HDR.gap.value)..' -a' + else + HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path) + end + + -- create run command and execute + local run_cmd = BuildExecuteCmd(HDRM) + resp = dsys.external_command(run_cmd) + + if resp == 0 and not GUI.HDR.batch.value then + local imported = dt.database.import(out_path) -- import the new file + if GUI.Target.style.selected > 1 then -- apply selected style + local set_style = styles[GUI.Target.style.selected - 1] + dt.styles.apply(set_style , imported) + end + if GUI.Target.copy_tags.value then -- copy tags from the original file (ignore 'darktable' generated tags) + local all_tags = dt.tags.get_tags(source_raw) + for _,tag in pairs(all_tags) do + if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end + end + end + local set_tag = GUI.Target.add_tags.text + if set_tag ~= nil then -- add additional user-specified tags + for tag in string.gmatch(set_tag, '[^,]+') do + tag = CleanSpaces(tag) + tag = dt.tags.create(tag) + dt.tags.attach(tag, imported) + end + end + dt.print(_('HDRMerge completed successfully')) + else + dt.print_error(_('HDRMerge failed')) + dt.print(_('HDRMerge failed')) + end + +end + +-- GUI Elements -- +local lbl_hdr = dt.new_widget('section_label'){ + label = _('HDRMerge options') +} +temp = dt.preferences.read(mod, 'active_bps_ind', 'integer') +if not InRange(temp, 1, 3) then temp = 3 end +GUI.HDR.bps = dt.new_widget('combobox'){ + label = _('bits per sample'), + tooltip =_('number of bits per sample in the output image'), + selected = temp, + '16','24','32', + changed_callback = function(self) + dt.preferences.write(mod, 'active_bps', 'integer', self.value) + dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected) + end, + reset_callback = function(self) + self.selected = 3 + dt.preferences.write(mod, 'active_bps', 'integer', self.value) + dt.preferences.write(mod, 'active_bps_ind', 'integer', self.selected) + end +} +temp = dt.preferences.read(mod, 'active_size_ind', 'integer') +if not InRange(temp, 1, 3) then temp = 2 end +GUI.HDR.size = dt.new_widget('combobox'){ + label = _('embedded preview size'), + tooltip =_('size of the embedded preview in output image'), + selected = temp, + _('none'),_('half'),_('full'), + changed_callback = function(self) + dt.preferences.write(mod, 'active_size', 'string', self.value) + dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected) + end, + reset_callback = function(self) + self.selected = 2 + dt.preferences.write(mod, 'active_size', 'string', self.value) + dt.preferences.write(mod, 'active_size_ind', 'integer', self.selected) + end +} +GUI.HDR.batch = dt.new_widget('check_button'){ + label = _('batch mode'), + value = dt.preferences.read(mod, 'active_batch', 'bool'), + tooltip = _('enable batch mode operation \nNOTE: resultant files will NOT be auto-imported'), + clicked_callback = function(self) + dt.preferences.write(mod, 'active_batch', 'bool', self.value) + GUI.HDR.gap.sensitive = self.value + end, + reset_callback = function(self) self.value = false end +} +temp = dt.preferences.read(mod, 'active_gap', 'integer') +if not InRange(temp, 1, 3600) then temp = 3 end +GUI.HDR.gap = dt.new_widget('slider'){ + label = _('batch gap [sec.]'), + tooltip = _('gap, in seconds, between batch mode groups'), + soft_min = 1, + soft_max = 30, + hard_min = 1, + hard_max = 3600, + step = 1, + digits = 0, + value = temp, + sensitive = GUI.HDR.batch.value, + reset_callback = function(self) + self.value = 3 + end +} +local lbl_import = dt.new_widget('section_label'){ + label = _('import options') +} +GUI.Target.style = dt.new_widget('combobox'){ + label = _('apply style on import'), + tooltip = _('Apply selected style on auto-import to newly created image'), + selected = 1, + _('none'), + changed_callback = function(self) + dt.preferences.write(mod, 'active_style', 'string', self.value) + dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected) + end, + reset_callback = function(self) + self.selected = 1 + dt.preferences.write(mod, 'active_style', 'string', self.value) + dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected) + end +} +for k=1, (styles_count-1) do + GUI.Target.style[k+1] = styles[k].name +end +temp = dt.preferences.read(mod, 'active_style_ind', 'integer') +if not InRange(temp, 1, styles_count) then temp = 1 end +GUI.Target.style.selected = temp +GUI.Target.copy_tags = dt.new_widget('check_button'){ + label = _('copy tags'), + value = dt.preferences.read(mod, 'active_copy_tags', 'bool'), + tooltip = _('copy tags from first source image'), + clicked_callback = function(self) dt.preferences.write(mod, 'active_copy_tags', 'bool', self.value) end, + reset_callback = function(self) self.value = true end +} +temp = dt.preferences.read(mod, 'active_add_tags', 'string') +if temp == '' then temp = nil end +GUI.Target.add_tags = dt.new_widget('entry'){ + tooltip = _('Additional tags to be added on import. Seperate with commas, all spaces will be removed'), + text = temp, + placeholder = _('Enter tags, seperated by commas'), + editable = true +} +GUI.run = dt.new_widget('button'){ + label = _('merge'), + tooltip =_('run HDRMerge with the above specified settings'), + clicked_callback = function() main() end +} +GUI.exes.HDRMerge = dt.new_widget('file_chooser_button'){ + title = _('Select HDRmerge executable'), + value = df.get_executable_path_preference(HDRM.name), + is_directory = false +} +GUI.exes.update = dt.new_widget('button'){ + label = _('update'), + tooltip =_('update the binary path with current value'), + clicked_callback = function() ExeUpdate({HDRM}) end +} +GUI.options = dt.new_widget('box'){ + orientation = 'vertical', + lbl_hdr, + GUI.HDR.bps, + GUI.HDR.size, + GUI.HDR.batch, + GUI.HDR.gap, + lbl_import, + GUI.Target.style, + GUI.Target.copy_tags, + GUI.Target.add_tags, + GUI.run +} +local exes_box = dt.new_widget('box'){ + orientation = 'vertical', + GUI.exes.HDRMerge, + GUI.exes.update +} +GUI.stack = dt.new_widget('stack'){ + GUI.options, + exes_box +} +if dt.preferences.read(mod, 'bin_exists', 'bool') then + GUI.stack.active = 1 +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 + } +) diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 16f9996..044e920 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -48,7 +48,9 @@ ]] local darktable = require("darktable") -darktable.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("3.0.0", "LabelsToTags") -- Lua 5.3 no longer has "unpack" but "table.unpack" unpack = unpack or table.unpack diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua new file mode 100644 index 0000000..11f004f --- /dev/null +++ b/contrib/OpenInExplorer.lua @@ -0,0 +1,113 @@ +--[[ +OpenInExplorer plugin for darktable + + copyright (c) 2018 Kevin Ertel + + 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 . +]] + +--[[About this plugin +This plugin adds the module "OpenInExplorer" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +Microsoft Windows or Linux with installed Nautilus + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Select the photo(s) you wish to find in explorer and press "Go to Folder". +A file explorer window will be opened for each selected file at the file's location; the file will be highlighted. + +----KNOWN ISSUES---- +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" + +--Check API version +du.check_min_api_version("5.0.0", "OpenInExplorer") + +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +--Detect OS and modify accordingly-- +local proper_install = false +if dt.configuration.running_os ~= "macos" then + proper_install = true +else + dt.print_error('OpenInExplorer plug-in only supports Windows and Linux at this time') + return +end + + +-- FUNCTIONS -- + +local function open_in_explorer() --Open in Explorer + local images = dt.gui.selection() + local curr_image = "" + 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 + local run_cmd = "explorer.exe /select, "..curr_image + dt.print_log("OpenInExplorer run_cmd = "..run_cmd) + resp = dsys.external_command(run_cmd) + end + else + dt.print('please select fewer images (max 15)') + end +end + +local function open_in_nautilus() --Open in Nautilus + local images = dt.gui.selection() + local curr_image = "" + 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 + local run_cmd = [[busctl --user call org.freedesktop.FileManager1 /org/freedesktop/FileManager1 org.freedesktop.FileManager1 ShowItems ass 1 ]] .. df.sanitize_filename("file://"..curr_image) .. [[ ""]] + dt.print_log("OpenInExplorer run_cmd = "..run_cmd) + resp = dsys.external_command(run_cmd) + end + else + dt.print('please select fewer images (max 15)') + end +end + +local function open_in_filemanager() --Open + --Inits-- + if not proper_install then + return + end + + if (dt.configuration.running_os == "windows") then + open_in_explorer() + elseif (dt.configuration.running_os == "linux") then + open_in_nautilus() + end +end + +-- GUI -- +if proper_install then + dt.gui.libs.image.register_action( + "show in file explorer", + function() open_in_filemanager() end, + "Opens File Explorer at the selected image's location" + ) +end diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 77c04cb..0b3891f 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -37,13 +37,14 @@ GPLv2 ]] local darktable = require "darktable" +local du = require "lib/dtutils" local filelib = require "lib/dtutils.file" -- Forward declare the functions local autostyle_apply_one_image,autostyle_apply_one_image_event,autostyle_apply,exiftool_attribute,capture -- Tested it with darktable 1.6.1 and darktable git from 2014-01-25 -darktable.configuration.check_version(...,{2,0,2},{2,1,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.2", "autostyle") -- Receive the event triggered function autostyle_apply_one_image_event(event,image) diff --git a/contrib/clear_GPS.lua b/contrib/clear_GPS.lua index 2a84a61..3172495 100644 --- a/contrib/clear_GPS.lua +++ b/contrib/clear_GPS.lua @@ -37,12 +37,13 @@ ]] local dt = require "darktable" +local du = require "lib/dtutils" local gettext = dt.gettext -- not a number local NaN = 0/0 -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "clear_GPS") -- Tell gettext where to find the .mo file translating messages for a particular domain diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index d7dab8a..2fb8330 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -37,10 +37,12 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local debug = require "darktable.debug" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) + +du.check_min_api_version("3.0.0", "copy_attach_detach_tags") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/cr2hdr.lua b/contrib/cr2hdr.lua index a308ffa..7f7a5bc 100644 --- a/contrib/cr2hdr.lua +++ b/contrib/cr2hdr.lua @@ -33,9 +33,10 @@ USAGE ]] local darktable = require "darktable" +local du = require "lib/dtutils" -- Tested with darktable 2.0.1 -darktable.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.0", "cr2hdr") local queue = {} local processed_files = {} diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index 38b3c21..6cf92c9 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -24,10 +24,11 @@ Dependencies: --]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" local gettext = dt.gettext -dt.configuration.check_version(...,{4,0,0},{5,0,0}) +du.check_min_api_version("4.0.0", "fujifilm_ratings") gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 6fc76fa..4834bb0 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -33,11 +33,12 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "geoJSON_export") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 8a28001..19e3c7c 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -26,11 +26,12 @@ require "geoToolbox" ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "geoToolbox") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gimp.lua b/contrib/gimp.lua index e7df43b..d7528d1 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -64,12 +64,13 @@ ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" local dtsys = require "lib/dtutils.system" local gettext = dt.gettext local gimp_widget = nil -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "gimp") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index 0518931..b6fed82 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -27,7 +27,7 @@ local df = require "lib/dtutils.file" local dl = require "lib/dtutils" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +dl.check_min_api_version("3.0.0", "gpx-export") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 90e5938..83ec3be 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -36,6 +36,7 @@ This plugin will add a new storage option and calls hugin after export. ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" @@ -54,7 +55,7 @@ local executable_table = {"hugin", "hugin_executor", "pto_gen"} local PQ = dt.configuration.running_os == "windows" and '"' or "'" -- works with darktable API version from 5.0.0 on -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "hugin") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index fd03fb6..de3e0c0 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -71,7 +71,7 @@ local job = nil local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- works with LUA API version 5.0.0 -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "image_stack") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 070a02f..279ad76 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -20,28 +20,30 @@ darktable KML export script ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT -* mkdir -* zip (only if you create KMZ files) -* convert (ImageMagick) -* xdg-open -* xdg-user-dir +* zip (at the moment Linux only and only if you create KMZ files) +* magick (ImageMagick) +* xdg-user-dir (Linux) WARNING This script is only tested with Linux USAGE -* require script "official/yield" from your main Lua file in the first line * require this script from your main Lua file * when choosing file format, pick JPEG or PNG as Google Earth doesn't support other formats ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" +local ds = require "lib/dtutils.string" +local dsys = require "lib/dtutils.system" + local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + +du.check_min_api_version("5.0.0", kml_export) -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/locale/") @@ -50,168 +52,10 @@ local function _(msgid) return gettext.dgettext("kml_export", msgid) end --- Sort a table -local function spairs(_table, order) -- Code copied from http://stackoverflow.com/questions/15706270/sort-a-table-in-lua - -- collect the keys - local keys = {} - for _key in pairs(_table) do keys[#keys + 1] = _key end - - -- if order function given, sort by it by passing the table and keys a, b, - -- otherwise just sort the keys - if order then - table.sort(keys, function(a,b) return order(_table, a, b) end) - else - table.sort(keys) - end - - -- return the iterator function - local i = 0 - return function() - i = i + 1 - if keys[i] then - return keys[i], _table[keys[i]] - end - end -end - local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) dt.print(string.format(_("Export Image %i/%i"), number, total)) end --- Strip accents from a string --- Copied from https://forums.coronalabs.com/topic/43048-remove-special-characters-from-string/ -function string.stripAccents( str ) - local tableAccents = {} - -- A - tableAccents["à"] = "a" - tableAccents["À"] = "A" - tableAccents["á"] = "a" - tableAccents["Á"] = "A" - tableAccents["â"] = "a" - tableAccents["Â"] = "A" - tableAccents["ã"] = "a" - tableAccents["Ã"] = "A" - tableAccents["ä"] = "a" - tableAccents["Ä"] = "A" - -- B - -- C - tableAccents["ç"] = "c" - tableAccents["Ç"] = "C" - tableAccents["č"] = "c" - tableAccents["Č"] = "C" - -- D - tableAccents["ď"] = "d" - tableAccents["Ď"] = "d" - -- E - tableAccents["è"] = "e" - tableAccents["È"] = "E" - tableAccents["é"] = "e" - tableAccents["É"] = "E" - tableAccents["ê"] = "e" - tableAccents["Ê"] = "E" - tableAccents["ë"] = "e" - tableAccents["Ë"] = "E" - tableAccents["ě"] = "e" - tableAccents["Ě"] = "E" - -- F - -- G - -- H - -- I - tableAccents["ì"] = "i" - tableAccents["Ì"] = "I" - tableAccents["í"] = "i" - tableAccents["Í"] = "I" - tableAccents["î"] = "i" - tableAccents["Î"] = "I" - tableAccents["ï"] = "i" - tableAccents["Ï"] = "I" - -- J - -- K - -- L - tableAccents["ĺ"] = "l" - tableAccents["Ĺ"] = "L" - tableAccents["ľ"] = "l" - tableAccents["Ľ"] = "L" - -- M - -- N - tableAccents["ñ"] = "n" - tableAccents["Ñ"] = "N" - tableAccents["ň"] = "n" - tableAccents["Ň"] = "N" - -- O - tableAccents["ò"] = "o" - tableAccents["Ò"] = "O" - tableAccents["ó"] = "o" - tableAccents["Ó"] = "O" - tableAccents["ô"] = "o" - tableAccents["Ô"] = "O" - tableAccents["õ"] = "o" - tableAccents["Õ"] = "O" - tableAccents["ö"] = "o" - tableAccents["Ö"] = "O" - -- P - -- Q - -- R - tableAccents["ŕ"] = "r" - tableAccents["Ŕ"] = "R" - tableAccents["ř"] = "r" - tableAccents["Ř"] = "R" - -- S - tableAccents["š"] = "s" - tableAccents["Š"] = "S" - -- T - tableAccents["ť"] = "t" - tableAccents["Ť"] = "T" - -- U - tableAccents["ù"] = "u" - tableAccents["Ù"] = "U" - tableAccents["ú"] = "u" - tableAccents["Ú"] = "U" - tableAccents["û"] = "u" - tableAccents["Û"] = "U" - tableAccents["ü"] = "u" - tableAccents["Ü"] = "U" - tableAccents["ů"] = "u" - tableAccents["Ů"] = "U" - -- V - -- W - -- X - -- Y - tableAccents["ý"] = "y" - tableAccents["Ý"] = "Y" - tableAccents["ÿ"] = "y" - tableAccents["Ÿ"] = "Y" - -- Z - tableAccents["ž"] = "z" - tableAccents["Ž"] = "Z" - - local normalizedString = "" - - for strChar in string.gmatch(str, "([%z\1-\127\194-\244][\128-\191]*)") do - if tableAccents[strChar] ~= nil then - normalizedString = normalizedString..tableAccents[strChar] - else - normalizedString = normalizedString..strChar - end - end - - return normalizedString -end - --- Escape XML characters --- Keep & first, otherwise it will double escape other characters --- https://stackoverflow.com/questions/1091945/what-characters-do-i-need-to-escape-in-xml-documents -function string.escapeXmlCharacters( str ) - - str = string.gsub(str,"&", "&") - str = string.gsub(str,"\"", """) - str = string.gsub(str,"'", "'") - str = string.gsub(str,"<", "<") - str = string.gsub(str,">", ">") - - return str -end - -- Add duplicate index to filename -- image.filename does not have index, exported_image has index function addDuplicateIndex( index, filename ) @@ -227,41 +71,40 @@ function addDuplicateIndex( index, filename ) end local function create_kml_file(storage, image_table, extra_data) - if not df.check_if_bin_exists("mkdir") then - dt.print_error(_("mkdir not found")) - return - end - if not df.check_if_bin_exists("convert") then - dt.print_error(_("convert not found")) - return - end - if not df.check_if_bin_exists("xdg-open") then - dt.print_error(_("xdg-open not found")) - return - end - if not df.check_if_bin_exists("xdg-user-dir") then - dt.print_error(_("xdg-user-dir not found")) - return + + local magickPath + if dt.configuration.running_os == "linux" then + magickPath = 'convert' + else + magickPath = dt.preferences.read("kml_export","magickPath","string") end - dt.print_error("Will try to export KML file now") + if not df.check_if_bin_exists(magickPath) then + dt.print_error(_("magick not found")) + return + end + if dt.configuration.running_os == "linux" then + if not df.check_if_bin_exists("xdg-user-dir") then + dt.print_error(_("xdg-user-dir not found")) + return + end + end + dt.print_log("Will try to export KML file now") local imageFoldername - if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then + if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true + and dt.configuration.running_os == "linux") then if not df.check_if_bin_exists("zip") then dt.print_error(_("zip not found")) return end - exportDirectory = dt.configuration.tmp_dir imageFoldername = "" else exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string") - -- Creates dir if not exsists - imageFoldername = "files/" - local mkdirCommand = "mkdir -p "..exportDirectory.."/"..imageFoldername - dt.control.execute(mkdirCommand) + imageFoldername = "files"..PS + df.mkdir(df.sanitize_filename(exportDirectory..PS..imageFoldername)) end -- Create the thumbnails @@ -272,15 +115,20 @@ local function create_kml_file(storage, image_table, extra_data) local path, filename, filetype = string.match(exported_image, "(.-)([^\\/]-%.?([^%.\\/]*))$") filename = string.upper(string.gsub(filename,"%.%w*", "")) - -- convert -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg + -- magick -size 92x92 filename.jpg -resize 92x92 +profile "*" thumbnail.jpg -- In this example, '-size 120x120' gives a hint to the JPEG decoder that the image is going to be downscaled to -- 120x120, allowing it to run faster by avoiding returning full-resolution images to GraphicsMagick for the -- subsequent resizing operation. The '-resize 120x120' specifies the desired dimensions of the output image. It -- will be scaled so its largest dimension is 120 pixels. The '+profile "*"' removes any ICM, EXIF, IPTC, or other -- profiles that might be present in the input and aren't needed in the thumbnail. - local convertToThumbCommand = "convert -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory.."/"..imageFoldername.."thumb_"..filename..".jpg" - dt.control.execute(convertToThumbCommand) + local convertToThumbCommand = ds.sanitize(magickPath) .. " -size 96x96 " .. exported_image .. " -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" " .. exportDirectory .. PS .. imageFoldername .. "thumb_" .. filename .. ".jpg" + + if (exported_image ~= exportDirectory..PS..imageFoldername..filename.."."..filetype) then + df.file_copy(exported_image, exportDirectory..PS..imageFoldername..filename.."."..filetype) + end + dsys.external_command(convertToThumbCommand) + else -- Remove exported image if it has no GPS data os.remove(exported_image) @@ -291,7 +139,19 @@ local function create_kml_file(storage, image_table, extra_data) -- Strip accents from the filename, because GoogleEarth can't open them -- https://github.com/darktable-org/lua-scripts/issues/54 - filmName = string.stripAccents(filmName) + filmName = ds.strip_accents(filmName) + + -- Remove chars we don't like to have in filenames + filmName = string.gsub(filmName, [[\]], "") + filmName = string.gsub(filmName, [[/]], "") + filmName = string.gsub(filmName, [[:]], "") + filmName = string.gsub(filmName, [["]], "") + filmName = string.gsub(filmName, "<", "") + filmName = string.gsub(filmName, ">", "") + filmName = string.gsub(filmName, "|", "") + filmName = string.gsub(filmName, "*", "") + filmName = string.gsub(filmName, "?", "") + filmName = string.gsub(filmName,'[.]', "") -- At least Windwows has problems with the "." and the start command end exportKMLFilename = filmName..".kml" @@ -321,12 +181,12 @@ local function create_kml_file(storage, image_table, extra_data) local image_title, image_description if (image.title and image.title ~= "") then - image_title = string.escapeXmlCharacters(image.title) + image_title = ds.escape_xml_characters(image.title) else image_title = filename..extension end -- Characters should not be escaped in CDATA, but we are using HTML fragment, so we must escape them - image_description = string.escapeXmlCharacters(image.description) + image_description = ds.escape_xml_characters(image.description) kml_file = kml_file.." "..image_title.."\n" kml_file = kml_file.." "..image_description.."\n" @@ -371,7 +231,7 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.." \n" kml_file = kml_file.." \n" - for image,exported_image in spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do + for image,exported_image in du.spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -393,19 +253,21 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.."\n" kml_file = kml_file.."" - local file = io.open(exportDirectory.."/"..exportKMLFilename, "w") + local file = io.open(exportDirectory..PS..exportKMLFilename, "w") + file:write(kml_file) file:close() dt.print("KML file created in "..exportDirectory) -- Compress the files to create a KMZ file - if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then + if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true + and dt.configuration.running_os == "linux") then exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string") local createKMZCommand = "zip --test --move --junk-paths " - createKMZCommand = createKMZCommand .."\""..exportDirectory.."/"..exportKMZFilename.."\" " -- KMZ filename - createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir.."/"..exportKMLFilename.."\" " -- KML file + createKMZCommand = createKMZCommand .."\""..exportDirectory..PS..exportKMZFilename.."\" " -- KMZ filename + createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir..PS..exportKMLFilename.."\" " -- KML file for image,exported_image in pairs(image_table) do if ((image.longitude and image.latitude) and @@ -415,7 +277,7 @@ local function create_kml_file(storage, image_table, extra_data) -- Handle duplicates filename = addDuplicateIndex( image.duplicate_index, filename ) - createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir.."/"..imageFoldername.."thumb_"..filename..".jpg\" " -- thumbnails + createKMZCommand = createKMZCommand .."\""..dt.configuration.tmp_dir..PS..imageFoldername.."thumb_"..filename..".jpg\" " -- thumbnails createKMZCommand = createKMZCommand .."\""..exported_image.."\" " -- images end end @@ -425,40 +287,65 @@ local function create_kml_file(storage, image_table, extra_data) -- Open the file with the standard programm if ( dt.preferences.read("kml_export","OpenKmlFile","bool") == true ) then - local kmlFileOpenCommand + local path - if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then - kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMZFilename.."\"" + if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true + and dt.configuration.running_os == "linux") then + path = exportDirectory..PS..exportKMZFilename else - kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMLFilename.."\"" + path = exportDirectory..PS..exportKMLFilename end - dt.control.execute(kmlFileOpenCommand) + + dsys.launch_default_app(df.sanitize_filename(path)) end end -- Preferences -dt.preferences.register("kml_export", - "OpenKmlFile", - "bool", - _("KML export: Open KML/KMZ file after export"), - _("Opens the KML file after the export with the standard program for KML files"), - false ) - -local handle = io.popen("xdg-user-dir DESKTOP") -local result = handle:read() -if (result == nil) then - result = "" +if dt.configuration.running_os == "windows" then + dt.preferences.register("kml_export", + "OpenKmlFile", + "bool", + _("KML export: Open KML file after export"), + _("Opens the KML file after the export with the standard program for KML files"), + false ) +else + dt.preferences.register("kml_export", + "OpenKmlFile", + "bool", + _("KML export: Open KML/KMZ file after export"), + _("Opens the KML file after the export with the standard program for KML files"), + false ) end -handle:close() + +local defaultDir = '' +if dt.configuration.running_os == "windows" then + defaultDir = os.getenv("USERPROFILE") +elseif dt.configuration.running_os == "macos" then + defaultDir = os.getenv("home") +else + local handle = io.popen("xdg-user-dir DESKTOP") + defaultDir = handle:read() + handle:close() +end + dt.preferences.register("kml_export", "ExportDirectory", "directory", _("KML export: Export directory"), _("A directory that will be used to export the KML/KMZ files"), - result ) + defaultDir ) +if dt.configuration.running_os ~= "linux" then + dt.preferences.register("kml_export", + "magickPath", -- name + "file", -- type + _("KML export: ImageMagick binary Location"), -- label + _("Install location of magick[.exe]. Requires restart to take effect."), -- tooltip + "magick") -- default +end + dt.preferences.register("kml_export", "CreatePath", "bool", @@ -466,15 +353,21 @@ dt.preferences.register("kml_export", _("connect all images with a path"), false ) -dt.preferences.register("kml_export", - "CreateKMZ", - "bool", - _("KML export: Create KMZ file"), - _("Compress all imeges to one KMZ file"), - true ) +if dt.configuration.running_os == "linux" then + dt.preferences.register("kml_export", + "CreateKMZ", + "bool", + _("KML export: Create KMZ file"), + _("Compress all imeges to one KMZ file"), + true ) +end -- Register -dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file) +if dt.configuration.running_os == "windows" then + dt.register_storage("kml_export", _("KML Export"), nil, create_kml_file) +else + dt.register_storage("kml_export", _("KML/KMZ Export"), nil, create_kml_file) +end -- vim: shiftwidth=2 expandtab tabstop=2 cindent syntax=lua -- kate: hl Lua; diff --git a/contrib/passport_guide.lua b/contrib/passport_guide.lua index 48ae64f..72611eb 100644 --- a/contrib/passport_guide.lua +++ b/contrib/passport_guide.lua @@ -36,9 +36,10 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local gettext = dt.gettext -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.0", "passport_guide") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index d5a71d4..f01b014 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -39,6 +39,7 @@ 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" @@ -56,7 +57,7 @@ if not df.check_if_bin_exists("pdflatex") then return end -dt.configuration.check_version(...,{4,0,0},{5,0,0}) +du.check_min_api_version("4.0.0", "pdf_slideshow") dt.preferences.register ("pdf_slideshow","open with","string", diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index c5ade26..31a4b79 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -44,12 +44,13 @@ USAGE local dt = require "darktable" +local du = require "lib/dtutils" local debug = require "darktable.debug" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "quicktag") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 3a557a8..d820d94 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -39,9 +39,10 @@ ]] local dt = require "darktable" +local du = require "lib/dtutils" -- added version check -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "rate_group") local function apply_rating(rating) local images = dt.gui.action_images diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index 217357b..a0c70be 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -29,8 +29,12 @@ Changes ]] local darktable = require "darktable" +local du = require "lib/dtutils" local debug = require "darktable.debug" +-- check API version +du.check_min_api_version("3.0.0", "rename-tags") + -- GUI entries local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" } local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" } diff --git a/contrib/select_untagged.lua b/contrib/select_untagged.lua index 9e504c0..2db6dae 100644 --- a/contrib/select_untagged.lua +++ b/contrib/select_untagged.lua @@ -19,9 +19,10 @@ Enable selection of untagged images (darktable|* tags are ignored) ]] local dt = require "darktable" +local du = require "lib/dtutils" local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "select_untagged") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index 2da53dc..90d6bc3 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -25,11 +25,12 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -dt.configuration.check_version(...,{2,0,2},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.2", "slideshowMusic") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua new file mode 100644 index 0000000..6755e5c --- /dev/null +++ b/contrib/video_ffmpeg.lua @@ -0,0 +1,460 @@ +--[[Timelapse video plugin based on ffmpeg for darktable 2.4.X and 2.6.X + + copyright (c) 2018, 2019 Dominik Markiewicz + + 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 . +]] + +--[[About this plugin +This plugin will add the new export module "video ffmpeg". + +----REQUIRED SOFTWARE---- +ffmpeg + +----USAGE---- +1. Go to Lighttable +2. Select images you want to use as a video ffmpeg frames +3. In image export module select "video ffmpeg" +4. Configure you video settings +5. Export + +----WARNING---- +This script has been tested under Linux only +]] + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" +local gettext = dt.gettext + +du.check_min_api_version("5.0.0") + +local MODULE_NAME = "video_ffmpeg" + +local PS = dt.configuration.running_os == "windows" and "\\" or "/" +gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS) + +local function _(msgid) + return gettext.dgettext(MODULE_NAME, msgid) +end + + +---- DECLARATIONS + +local resolutions = { + ["QVGA"] = { + ["label"] = "QVGA 320x240 (4:3)", + ["w"] = 320, + ["h"] = 240 + }, + ["HVGA"] = { + ["label"] = "HVGA 480x320 (3:2)", + ["w"] = 480, + ["h"] = 320 + }, + ["VGA"] = { + ["label"] = "VGA 640x480 (4:3)", + ["w"] = 640, + ["h"] = 480 + }, + ["HDTV 720p"] = { + ["label"] = "HDTV 720p 1280x720 (16:9)", + ["w"] = 1280, + ["h"] = 720 + }, + ["HDTV 1080p"] = { + ["label"] = "HDTV 1080p 1920x1080 (16:9)", + ["w"] = 1920, + ["h"] = 1080 + }, + ["Cinema TV"] = { + ["label"] = "Cinema TV 2560x1080 (21:9)", + ["w"] = 2560, + ["h"] = 1080 + }, + ["2K"] = { + ["label"] = "2K 2048x1152 (16:9)", + ["w"] = 2048, + ["h"] = 1152 + }, + ["4K"] = { + ["label"] = "4K 4096x2304 (16:9)", + ["w"] = 4096, + ["h"] = 2304 + } +} + +local framerates = {"15", "16", "23.98", "24", "25", "29,97", "30", "48", "50", "59.94", "60"} + +local formats = { + ["AVI"] = { + ["extension"] = "avi", + ["codecs"] = {"mpeg4", "h263p", "h264", "mpeg2video", "hevc", "vp9"} + }, + ["Matroska"] = { + ["extension"] = "mkv", + ["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video", "hevc", "vp9"} + }, + ["WebM"] = { + ["extension"] = "webm", + ["codecs"] = {"vp9", "h263p", "h264", "mpeg4", "mpeg2video", "hevc"} + }, + ["MP4"] = { + ["extension"] = "mp4", + ["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video"} + }, + ["QuickTime"] = { + ["extension"] = "mov", + ["codecs"] = {"h264", "h263p", "mpeg4", "mpeg2video"} + } +} + +local res_list = {} +for i, v in pairs(resolutions) do + table.insert(res_list, v["label"]) +end + +table.sort(res_list) + +-- inital fulfill list of all available formats +local format_list = {} +for i,v in pairs(formats) do + table.insert(format_list, i) +end +table.sort(format_list) + +-- initial fulfill list of all available codecs for the first (default if nothig saved yet) format +-- if format was changed and stored in preferences, this list will be replaced by one matching selected format +local codec_list = formats[format_list[1]]["codecs"] +table.sort(codec_list) + +local function extract_resolution(description) + for _, v in pairs(resolutions) do + if v["label"] == description then + return v["w"].."x"..v["h"] + end + end + return "100x100" +end + +---- GENERIC UTILS + +local function replace_combobox_elements(combobox, new_items, to_select) + if to_select == nil then + to_select = combobox.value + end + + to_select_idx = 1 + for i , name in ipairs(new_items) do + if name == to_select then + to_select_idx = i + break + end + end + + local old_elements_count = #combobox + for i, name in ipairs(new_items) do + combobox[i] = name + end + if old_elements_count > #new_items then + for j = old_elements_count, #new_items + 1, -1 do + combobox[j] = nil + end + end + + combobox.value = to_select_idx +end + +--[[ +This function allow to format string by substitute tokens like `{label}` by value provided in `symbols` table + ex: +```lua + format_string("I like {bananas} and {potatos}. {bananas}!", {["bananas"]: "darktable", ["potatos"]: "lua"}) + -> "I like darktable and lua. darktable!" +``` + +I you want to preserve given currly braces, you need to escape it by backslash like so: +```lua + format_string("it preserve \\{label\\} but substitute {label}", {["label"]: "this"}) + -> "It preserve {label} but substitute " +``` +--]] +local function format_string(label, symbols) + local es1, es2 = "\u{ffe0}", "\u{ffe1}" -- for simplicity, just some strange utf characters + local result = label:gsub("\\{", es1):gsub("\\}", es2) + for s,v in pairs(symbols) do + result = result:gsub("{"..s.."}", v) + end + return result:gsub(es1, "{"):gsub(es2, "}") +end + +----- COMPONENTS + +local function combobox_pref_read(name, all_values) + local value = dt.preferences.read(MODULE_NAME, name, "string") + for i,v in pairs(all_values) do + if v == value then return i end + end + return 1 +end + +local function combobox_pref_write(name) + local writer = function(widget) + dt.preferences.write(MODULE_NAME, name, "string", widget.value) + end + return writer +end + +local function string_pref_read(name, default) + local value = dt.preferences.read(MODULE_NAME, name, "string") + if value ~= nil and value ~= "" then return value end + return default +end + +local function string_pref_write(name, widget_attribute) + widget_attribute = widget_attribute or "value" + local writer = function(widget) + dt.preferences.write(MODULE_NAME, name, "string", widget[widget_attribute]) + end + return writer +end + +local framerates_selector = dt.new_widget("combobox"){ + label = _("framerate"), + tooltip = _("select framerate of output video"), + value = combobox_pref_read("framerate", framerates), + changed_callback = combobox_pref_write("framerate"), + table.unpack(framerates) +} + +local res_selector = dt.new_widget("combobox"){ + label = _("resolution"), + tooltip = _("select resolution of output video"), + value = combobox_pref_read("resolution", res_list), + changed_callback = combobox_pref_write("resolution"), + table.unpack(res_list) +} + +local codec_selector = dt.new_widget("combobox"){ + label = _("codec"), + tooltip = _("select codec"), + value = combobox_pref_read("codec", codec_list), + changed_callback = combobox_pref_write("codec"), + table.unpack(codec_list) +} + +local format_selector = dt.new_widget("combobox"){ + label = _("format container"), + tooltip = _("select format of output video"), + value = combobox_pref_read("format", format_list), + changed_callback = function(widget) + combobox_pref_write("format")(widget) + codec_list = formats[widget.value]["codecs"] + table.sort(codec_list) + replace_combobox_elements(codec_selector, codec_list) + end, + table.unpack(format_list) +} + +local destination_label = dt.new_widget("section_label"){ + label = _("output file destination"), + tooltip = _("settings of output file destination and name") +} + +local defaultVideoDir = '' +if dt.configuration.running_os == "windows" then + defaultVideoDir = os.getenv("USERPROFILE")..PS .."videos" +elseif dt.configuration.running_os == "macos" then + defaultVideoDir = os.getenv("home")..PS.."Videos" +else + local handle = io.popen("xdg-user-dir VIDEOS") + defaultVideoDir = handle:read() + handle:close() +end + +local output_directory_chooser = dt.new_widget("file_chooser_button"){ + title = _("Select export path"), + is_directory = true, + tooltip =_("select the target directory for the timelapse. \nthe filename is created automatically."), + value = string_pref_read("export_path", defaultVideoDir), + changed_callback = string_pref_write("export_path") +} + +local auto_output_directory_btn = dt.new_widget("check_button") { + label = "", + tooltip = _("if selected, output video will be placed in the same directory as first of selected images"), + value = not dt.preferences.read(MODULE_NAME, "not_auto_output_directory", "bool"), -- reverse, for true as default + clicked_callback = function (widget) + dt.preferences.write(MODULE_NAME, "not_auto_output_directory", "bool", not widget.value) + output_directory_chooser.sensitive = not output_directory_chooser.sensitive + end +} + +local destination_box = dt.new_widget("box") { + orientation = "horizontal", + auto_output_directory_btn, + output_directory_chooser +} + +local override_output_cb = dt.new_widget("check_button"){ + label = _("override output file on conflict"), + tooltip = _("if checked, in case of file name conflict, the file will be overwritten"), + value = dt.preferences.read(MODULE_NAME, "override_output", "bool"), + clicked_callback = function (widget) + dt.preferences.write(MODULE_NAME, "override_output", "bool", widget.value) + end +} + +local filename_entry = dt.new_widget("entry"){ + tooltip = _("enter output file name without extension.\n\n".. + "You can use some placeholders:\n".. + "- {time} - time in format HH-mm-ss\n".. + "- {date} - date in foramt YYYY-mm-dd\n".. + "- {first_file} - name of first input file\n".. + "- {last_file} - name of last last_file" + ), + text = string_pref_read("filename_entry","timelapse_{date}_{time}"), + -- changed_callback = string_pref_write("filename_entry", "text") -- Not imlemented yet +} + +local output_box = dt.new_widget("box"){ + orientation="vertical", + destination_label, + override_output_cb, + destination_box, + filename_entry, +} + +local open_after_export_cb = dt.new_widget("check_button"){ + label = _(" open after export"), + tooltip = _("open video file after successful export"), + value = dt.preferences.read(MODULE_NAME, "open_after_export", "bool"), + clicked_callback = function (widget) + dt.preferences.write(MODULE_NAME, "open_after_export", "bool", widget.value) + end +} + +local widgets_list = {} +if not df.check_if_bin_exists("ffmpeg") then + table.insert(widgets_list, df.executable_path_widget({"ffmpeg"})) +end +table.insert(widgets_list, res_selector) +table.insert(widgets_list, framerates_selector) +table.insert(widgets_list, format_selector) +table.insert(widgets_list, codec_selector) +table.insert(widgets_list, output_box) +table.insert(widgets_list, open_after_export_cb) + + +local module_widget = dt.new_widget("box") { + orientation = "vertical", + table.unpack(widgets_list) +} + +---- EXPORT & REGISTRATION + +local function show_status(enf_storage, image, format, filename, number, total, high_quality, extra_data) + dt.print(_("export ")..tostring(number).." / "..tostring(total)) +end + +local function init_export(storage, img_format, images, high_quality, extra_data) + -- store filename preference here cause there is no changed_callback on entry yet + string_pref_write("filename_entry", "text")(filename_entry) + + extra_data["images"] = images -- needed, to preserve images order + extra_data["tmp_dir"] = dt.configuration.tmp_dir..PS..MODULE_NAME.."_"..os.time() + extra_data["fps"] = framerates_selector.value + extra_data["res"] = extract_resolution(res_selector.value) + extra_data["codec"] = codec_selector.value + extra_data["img_ext"] = "."..img_format.extension + local override_output = override_output_cb.value + local output_directory = auto_output_directory_btn.value and images[1].path or output_directory_chooser.value + local filename_mappings = { + date = os.date("%Y-%m-%d"), + time = os.date("%H-%M-%S"), + first_file = images[1].filename, + last_file = images[#images].filename + } + local output_extension = "."..formats[format_selector.value]["extension"] + local filename = format_string(filename_entry.text, filename_mappings) + local path = output_directory..PS..filename..output_extension + if not override_output then + path = df.create_unique_filename(path) + end + + extra_data["output_file"] = path + extra_data["open_after_export"] = open_after_export_cb.value +end + +local function export(extra_data) + local ffmpeg_path = df.check_if_bin_exists("ffmpeg") + if not ffmpeg_path then + dt.print_error("ffmpeg not found") + dt.print("ERROR - ffmpeg not found") + return + end + local dir = extra_data["tmp_dir"] + local fps = extra_data["fps"] + local res = extra_data["res"] + local codec = extra_data["codec"] + local img_ext = extra_data["img_ext"] + local output_file = extra_data["output_file"] + + local dir_create_result = df.mkdir(df.sanitize_filename(df.get_path(output_file))) + if dir_create_result ~= 0 then return dir_create_result end + + local cmd = ffmpeg_path.." -y -r "..fps.." -i "..dir..PS.."%d"..img_ext.." -s:v "..res.." -c:v "..codec.." -crf 18 -preset veryslow "..df.sanitize_filename(output_file) + return dsys.external_command(cmd), output_file +end + +local function finalize_export(storage, images_table, extra_data) + local tmp_dir = extra_data["tmp_dir"] + + dt.print(_("prepare merge process")) + + local result = df.mkdir(df.sanitize_filename(tmp_dir)) + if result ~= 0 then dt.print(_("ERROR: cannot create temp directory")) end + + local images = extra_data["images"] + -- rename all images to consecutive numbers + for i, file in pairs(images) do + local filename = images_table[file] + dt.print_error(filename, file.filename) + df.file_move(filename, tmp_dir .. PS .. i .. extra_data["img_ext"]) + end + dt.print("Start video building...") + local result, path = export(extra_data) + if result ~= 0 then + dt.print(_("ERROR: cannot build image, see console for more info")) + else + dt.print(_("SUCCESS")) + if extra_data["open_after_export"] then + dsys.launch_default_app(df.sanitize_filename(path)) + end + end + + df.rmdir(df.sanitize_filename(tmp_dir)) +end + +dt.register_storage( + "module_video_ffmpeg", + _(MODULE_NAME), + show_status, + finalize_export, + nil, + init_export, + module_widget +) + diff --git a/contrib/video_mencoder.lua b/contrib/video_mencoder.lua index 3f39587..71c8a4d 100644 --- a/contrib/video_mencoder.lua +++ b/contrib/video_mencoder.lua @@ -31,11 +31,12 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -dt.configuration.check_version(...,{2,0,1},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.1", "video_mencoder") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("video_mencoder",dt.configuration.config_dir.."/lua/locale/") @@ -49,6 +50,10 @@ local function show_status(storage, image, format, filename, number, total, high end local function create_video_mencoder(storage, image_table, extra_data) + dt.print("video_mencoder is deprecated. Please use video_ffmpeg instead. Please see the log for more details.") + dt.print_log("The mencoder executable is no longer distributed as part of most distributions,") + dt.print_log("therefore video_mencoder is deprecated. video_ffmpeg should be used in it's place. video_mencoder") + dt.print_log("will be removed when darktable 2.8 is released.") if not df.check_if_bin_exists("mencoder") then dt.print_error(_("mencoder not found")) return diff --git a/examples/gettextExample.lua b/examples/gettextExample.lua index 856a825..c6eda49 100644 --- a/examples/gettextExample.lua +++ b/examples/gettextExample.lua @@ -53,7 +53,10 @@ LUA ERROR Hallo Welt! ]] local dt = require "darktable" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0})) +local du = require "lib/dtutils" + +--check API version +du.check_min_api_version("3.0.0", "gettextExample") -- Not translated Text dt.print_error("Hello World!") diff --git a/examples/hello_world.lua b/examples/hello_world.lua index 6a19880..11a287e 100644 --- a/examples/hello_world.lua +++ b/examples/hello_world.lua @@ -27,7 +27,9 @@ USAGE ]] local dt = require "darktable" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("2.0.0", "hello_world") dt.print("hello, world") diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index eda8748..ecc2ac3 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -31,8 +31,9 @@ https://www.darktable.org/lua-api/index.html.php#darktable_new_widget ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "moduleExample") -- add a new lib local check_button = dt.new_widget("check_button"){label = "MyCheck_button", value = true} diff --git a/examples/multi_os.lua b/examples/multi_os.lua index 0371768..96b60c2 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -80,12 +80,13 @@ local function _(msgid) end --[[ - Specify the version(s) of the lua API that this script is compatible with. This script uses new - features released in the latest version of darktable and is only compatible with the latest API version. - Multiple API versions may be specified, seperated by commas. + Check that the current api version is greater than or equal to the specified minimum. If it's not + then du.check_min_api_version will print an error to the log and return false. If the minimum api is + not met, then just refuse to load and return. Optionally, you could print an error message to the + screen stating that you couldn't load because the minimum api version wasn't met. ]] -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "multi_os") --[[ copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image diff --git a/examples/preferenceExamples.lua b/examples/preferenceExamples.lua index 7ac1cfd..9710b95 100644 --- a/examples/preferenceExamples.lua +++ b/examples/preferenceExamples.lua @@ -22,7 +22,9 @@ USAGE * require this script from your main lua file ]] local dt = require "darktable" -dt.configuration.check_version(...,{2,0,1},{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("2.0.1", "preferenceExamples") dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesString", -- name diff --git a/examples/printExamples.lua b/examples/printExamples.lua index 5ef3695..074a5db 100644 --- a/examples/printExamples.lua +++ b/examples/printExamples.lua @@ -20,7 +20,9 @@ USAGE * require this file from your main lua config file: ]] local dt = require "darktable" -dt.configuration.check_version(...,{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("5.0.0", "printExamples") -- Will print a string to the darktable control log (the long -- overlayed window that appears over the main panel). diff --git a/examples/running_os.lua b/examples/running_os.lua index a615fa9..632c033 100644 --- a/examples/running_os.lua +++ b/examples/running_os.lua @@ -26,7 +26,9 @@ prints the operating system ]] local dt = require "darktable" -dt.configuration.check_version(...,{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("5.0.0", "running_os") dt.print("You are running: "..dt.configuration.running_os) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index abe51d6..e1d403e 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -32,7 +32,42 @@ local dt = require "darktable" local log = require "lib/dtutils.log" -dt.configuration.check_version(...,{5,0,0}) +dtutils.libdoc.functions["check_min_api_version"] = { + Name = [[check_min_api_version]], + Synopsis = [[check the minimum required api version against the current api version]], + Usage = [[local du = require "lib/dtutils" + + local result = du.check_min_api_version(min_api, script_name) + min_api - string - the api version that the application was written for (example: "5.0.0") + script_name - string - the name of the script]], + Description = [[check_min_api_version compares the minimum api required for the appllication to + run against the current api version. The minimum api version is typically the api version that + was current when the application was created. If the minimum api version is not met, then an + error message is printed saying the script_name failed to load, then an error is thrown causing the + program to stop executing. + + This function is intended to replace darktable.configuration.check_version(). The application code + won't have to be updated each time the api changes because this only checks the minimum version required.]], + Return_Value = [[result - true if the minimum api version is available, false if not.]], + Limitations = [[When using the default handler on a script being executed from the luarc file, the error thrown + will stop the luarc file from executing any remaining statements. This limitation does not apply to script_manger.]], + Example = [[check_min_api_version("5.0.0") does nothing if the api is greater or equal to 5.0.0 otherwise an + error message is printed and an error is thrown stopping execution of the script.]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils.check_min_api_version(min_api, script_name) + local current_api = dt.configuration.api_version_string + if min_api > current_api then + dt.print_error("This application is written for lua api version " .. min_api .. " or later.") + dt.print_error("The current lua api version is " .. current_api) + dt.print("ERROR: " .. script_name .. " failed to load. Lua API version " .. min_api .. " or later required.") + error("Minimum API " .. min_api .. " not met for " .. script_name .. ".", 0) + end +end dtutils.libdoc.functions["split"] = { Name = [[split]], diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 120b70f..f4306ff 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -1,6 +1,8 @@ local dtutils_file = {} local dt = require "darktable" +local du = require "lib/dtutils" local ds = require "lib/dtutils.string" +local dsys = require "lib/dtutils.system" local log = require "lib/dtutils.log" @@ -24,7 +26,7 @@ dtutils_file.libdoc = { local gettext = dt.gettext -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "dtutils.file") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/") @@ -289,9 +291,12 @@ dtutils_file.libdoc.functions["file_copy"] = { function dtutils_file.file_copy(fromFile, toFile) local result = nil -- if cp exists, use it - if dtutils_file.check_if_bin_exists("cp") then + if dt.configuration.running_os == "windows" then + result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') + elseif dtutils_file.check_if_bin_exists("cp") then result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") end + -- if cp was not present, or if cp failed, then a pure lua solution if not result then local fileIn, err = io.open(fromFile, 'rb') @@ -553,5 +558,55 @@ function dtutils_file.sanitize_filename(filename) return ds.sanitize(filename) end +dtutils_file.libdoc.functions["mkdir"] = { + Name = [[mkdir]], + Synopsis = [[create the directory(ies) if they do not already exists]], + Usage = [[local df = require "lib/dtutils.file" + + df.mkdir(path) + path - string - a directory path]], + Description = [[mkdir creates directories if not already exists. It + create whole parents subtree if needed + ]], + Return_Value = [[path - string - a directory path]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} +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) + else + return 0 + end +end + +dtutils_file.libdoc.functions["rmdir"] = { + Name = [[rmdir]], + Synopsis = [[recursively remove a directory]], + Usage = [[local df = require "lib/dtutils.file" + + df.rmdir(path) + path - string - a directory path]], + Description = [[rm allow to recursively remove directories]], + Return_Value = [[path - string - a directory path]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils_file.rmdir(path) + local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" + return dsys.external_command(rm_cmd.." "..path) +end + + return dtutils_file diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 0425a10..f202d6d 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -101,4 +101,33 @@ function dtutils_system.windows_command(command) return result end -return dtutils_system \ No newline at end of file + +dtutils_system.libdoc.functions["launch_default_app"] = { + Name = [[launch_default_app]], + Synopsis = [[open file in default application]], + Usage = [[local dsys = require "lib/dtutils.file" + + result = dsys.launch_default_app(path) + path - string - a file path]], + Description = [[launch_default_app allows opening a file in the application that is assigned as default + for that filetype in the users's system]], + Return_Value = [[result - the return value signalling success or failure.]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} +function dtutils_system.launch_default_app(path) + local open_cmd = "xdg-open " + if (dt.configuration.running_os == "windows") then + open_cmd = "" -- On Windows we don't need any command. (start e.g. has problems with spaces in the filename, even if we put quoter around them.) https://stackoverflow.com/questions/13691827/opening-file-with-spaces-in-windows-via-command-prompt + elseif (dt.configuration.running_os == "macos") then + open_cmd = "open " + end + return dtutils_system.external_command(open_cmd .. path) +end + + +return dtutils_system diff --git a/official/check_for_updates.lua b/official/check_for_updates.lua index 221a660..712daab 100644 --- a/official/check_for_updates.lua +++ b/official/check_for_updates.lua @@ -28,10 +28,11 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" local https = require "ssl.https" local cjson = require "cjson" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.0", "check_for_updates") -- compare two version strings of the form "major.minor.patch" -- returns -1, 0, 1 if the first version is smaller, equal, greater than the second version, diff --git a/official/copy_paste_metadata.lua b/official/copy_paste_metadata.lua index 4d1c70b..ca7ccae 100644 --- a/official/copy_paste_metadata.lua +++ b/official/copy_paste_metadata.lua @@ -26,8 +26,9 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "copy_paste_metadata") -- set this to "false" if you don't want to overwrite metadata fields -- (title, description, creator, publisher and rights) that are already set diff --git a/official/delete_long_tags.lua b/official/delete_long_tags.lua index 5a2da73..9ab1a28 100644 --- a/official/delete_long_tags.lua +++ b/official/delete_long_tags.lua @@ -29,7 +29,9 @@ all tags longer than the given length will be automatically deleted at every res ]] local dt = require "darktable" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("2.0.0", "delete_long_tags") dt.preferences.register("delete_long_tags", "length", "integer", "maximum length of tags to keep", diff --git a/official/delete_unused_tags.lua b/official/delete_unused_tags.lua index 337f5f6..4c11423 100644 --- a/official/delete_unused_tags.lua +++ b/official/delete_unused_tags.lua @@ -27,8 +27,9 @@ all tags that are not used will be automatically deleted at every restart ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0", "delete_unused_tags") -- deleting while iterating the tags list seems to break the iterator! local unused_tags = {} diff --git a/official/enfuse.lua b/official/enfuse.lua index 04dda4d..e3b43f9 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -33,14 +33,18 @@ TODO ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" -require "official/yield" +local dtsys = require "lib/dtutils.system" + +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + local gettext = dt.gettext -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +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.."/lua/locale/") +gettext.bindtextdomain("enfuse",dt.configuration.config_dir..PS .. "lua" .. PS .. "locale" .. PS) local function _(msgid) return gettext.dgettext("enfuse", msgid) @@ -50,15 +54,21 @@ end -- is enfuse installed? local enfuse_installed = df.check_if_bin_exists("enfuse") +-- instance of DT tiff exporter +local tiff_exporter = dt.new_format("tiff") +tiff_exporter.bpp = 16 +tiff_exporter.max_height = 0 +tiff_exporter.max_width = 0 + -- check the version so that we can use the correct arguments local version = nil -local p = io.popen(enfuse_installed .. " --version | grep enfuse | grep -e \"[0123456789\\.]\"") +local p = io.popen(enfuse_installed .. " --version") local f = p:read("all") -version = string.match(f, "[%d\\.]+" ) -dt.print_log("enfuse version is " .. version) p:close() +version = string.match(f, "enfuse (%d.%d)") +dt.print_log("enfuse version is " .. version) -- initialize exposure_mu value and depth setting in config to sane defaults (would be 0 otherwise) @@ -104,6 +114,115 @@ local depth = dt.new_widget("combobox") "8", "16", "32" } +local enfuse_button = dt.new_widget("button") +{ + label = enfuse_installed and "run enfuse" or "enfuse not installed", + clicked_callback = function () + -- remember exposure_mu + -- TODO: find a way to save it whenever the value changes + local mu = exposure_mu.value + if version < "4.2" then + dt.preferences.write("enfuse", "exposure-mu", "float", mu) + else + dt.preferences.write("enfuse", "exposure-optimum", "float", mu) + end + + -- create a temp response file + local response_file = os.tmpname() + if dt.configuration.running_os == "windows" then + response_file = dt.configuration.tmp_dir .. response_file -- windows os.tmpname() defaults to root directory + end + local f = io.open(response_file, "w") + if not f then + dt.print(string.format(_("Error writing to `%s`"), response_file)) + os.remove(response_file) + return + end + + -- add all filenames to the response file + local cnt = 0 + local n_skipped = 0 + local target_dir + for i_, i in ipairs(dt.gui.action_images) do + + -- only use ldr files as enfuse can't open raws + if i.is_ldr then + cnt = cnt + 1 + f:write(i.path..PS..i.filename.."\n") + target_dir = i.path + + -- alternatively raws will be exported as tiff + elseif i.is_raw then + local tmp_exported = os.tmpname()..".tif" + if dt.configuration.running_os == "windows" then + tmp_exported = dt.configuration.tmp_dir .. tmp_exported -- windows os.tmpname() defaults to root directory + end + dt.print(string.format(_("Converting raw file '%s' to tiff..."), i.filename)) + tiff_exporter:write_image(i, tmp_exported, false) + dt.print_log(string.format("Raw file '%s' converted to '%s'", i.filename, tmp_exported)) + + cnt = cnt + 1 + f:write(tmp_exported.."\n") + target_dir = i.path + + -- other images will be skipped + else + dt.print(string.format(_("Skipping %s..."), i.filename)) + n_skipped = n_skipped + 1 + end + end + f:close() + -- bail out if there is nothing to do + if cnt == 0 then + dt.print(_("No suitable images selected, nothing to do for enfuse")) + os.remove(response_file) + return + end + + if n_skipped > 0 then + dt.print(string.format(_("%d image(s) skipped"), n_skipped)) + end + + -- call enfuse on the response file + -- TODO: find something nicer + local ugly_decimal_point_hack = string.gsub(string.format("%.04f", mu), ",", ".") + -- TODO: make filename unique + local output_image = target_dir.. PS .. "enfuse.tif" + local exposure_option = " --exposure-optimum " + if version < "4.2" then + exposure_option = " --exposure-mu " + end + local command = enfuse_installed.." --depth "..depth.value..exposure_option..ugly_decimal_point_hack + .." -o \""..output_image.."\" \"@"..response_file.."\"" + if dtsys.external_command( command) > 0 then + dt.print(_("Enfuse failed, see terminal output for details")) + os.remove(response_file) + return + end + + -- remove the response file + os.remove(response_file) + + -- import resulting tiff + local image = dt.database.import(output_image) + + -- tell the user that everything worked + dt.print(_("enfuse was successful, resulting image has been imported")) + -- normally printing to stdout is bad, but we allow enfuse to show its output, so adding one extra line is ok + print(string.format(_("enfuse: done, resulting image '%s' has been imported with id %d"), output_image, image.id)) + end +} + +local lib_widgets = {} + +if not enfuse_installed then + table.insert(lib_widgets, df.executable_path_widget({"ffmpeg"})) +end +table.insert(lib_widgets, exposure_mu) +table.insert(lib_widgets, depth) +table.insert(lib_widgets, enfuse_button) + + -- ... and tell dt about it all dt.register_lib( "enfuse", -- plugin name @@ -115,87 +234,7 @@ dt.register_lib( { orientation = "vertical", sensitive = enfuse_installed, - exposure_mu, - depth, - dt.new_widget("button") - { - label = enfuse_installed and "run enfuse" or "enfuse not installed", - clicked_callback = function (_) - -- remember exposure_mu - -- TODO: find a way to save it whenever the value changes - local mu = exposure_mu.value - if version < "4.2" then - dt.preferences.write("enfuse", "exposure-mu", "float", mu) - else - dt.preferences.write("enfuse", "exposure-optimum", "float", mu) - end - - -- create a temp response file - local response_file = os.tmpname() - local f = io.open(response_file, "w") - if not f then - dt.print("error writing to `"..response_file.."'") - os.remove(response_file) - return - end - - -- add all filenames to the response file - local cnt = 0 - local n_skipped = 0 - local target_dir - for _, i in ipairs(dt.gui.action_images) do - -- only use ldr files as enfuse can't open raws. alternatively we could export raws that we encounter - if i.is_ldr then - cnt = cnt + 1 - f:write(i.path.."/"..i.filename.."\n") - target_dir = i.path - else - dt.print("skipping "..i.filename) - n_skipped = n_skipped + 1 - end - end - f:close() - -- bail out if there is nothing to do - if cnt == 0 then - dt.print("no suitable images selected, nothing to do for enfuse") - os.remove(response_file) - return - end - if n_skipped == 1 then - dt.print(n_skipped.." image skipped") - elseif n_skipped > 1 then - dt.print(n_skipped.." images skipped") - end - - -- call enfuse on the response file - -- TODO: find something nicer - local ugly_decimal_point_hack = string.gsub(string.format("%.04f", mu), ",", ".") - -- TODO: make filename unique - local output_image = target_dir.."/enfuse.tiff" - local exposure_option = " --exposure-optimum " - if version < "4.2" then - exposure_option = " --exposure-mu " - end - local command = "enfuse --depth "..depth.value..exposure_option..ugly_decimal_point_hack - .." -o \""..output_image.."\" \"@"..response_file.."\"" - if dt.control.execute( command) > 0 then - dt.print("enfuse failed, see terminal output for details") - os.remove(response_file) - return - end - - -- remove the response file - os.remove(response_file) - - -- import resulting tiff - local image = dt.database.import(output_image) - - -- tell the user that everything worked - dt.print("enfuse was successful, resulting image was imported") - -- normally printing to stdout is bad, but we allow enfuse to show its output, so adding one extra line is ok - print("enfuse: done, resulting image '"..output_image.."' was imported with id "..image.id) - end - } + table.unpack(lib_widgets) }, nil,-- view_enter nil -- view_leave diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index 8dcd5d1..a214a2a 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -35,10 +35,11 @@ USAGE -- * make filenames with double quotes (") work local dt = require "darktable" +local du = require "lib/dtutils" require "darktable.debug" require "official/yield" -dt.configuration.check_version(...,{2,1,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.1.0", "generate_image_txt") dt.preferences.register("generate_image_txt", "enabled", diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index b21a218..18e57ca 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -29,7 +29,9 @@ This plugin will add a widget at the bottom of the left column in lighttable mod ]] local dt = require "darktable" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("2.0.0", "image_path_in_ui") local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle", halign = "start"} diff --git a/official/save_selection.lua b/official/save_selection.lua index fd9f4f8..1d71ea9 100644 --- a/official/save_selection.lua +++ b/official/save_selection.lua @@ -34,7 +34,9 @@ increase it if you need more temporary selection buffers ]] local dt = require "darktable" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +du.check_min_api_version("2.0.0", "save_selection") local buffer_count = 5 diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index f44a8dc..f6fbfe8 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -34,9 +34,10 @@ Plugin allows you to choose how many thumbnails you need per row ]] local dt = require "darktable" +local du = require "lib/dtutils" require "official/yield" -dt.configuration.check_version(...,{2,0,0},{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("2.0.0", "selection_to_pdf") dt.preferences.register ("selection_to_pdf","Open with","string", diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 4c8f68d..03e35f6 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -32,6 +32,8 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +du.check_min_api_version("5.0.0", "executable_manager") + local PS = dt.configuration.running_os == "windows" and "\\" or "/" local gettext = dt.gettext @@ -110,7 +112,7 @@ local exec_table = {} for _,pref in ipairs(matches) do local parts = du.split(pref, "=") - local tmp = du.split(parts[1], PS) + local tmp = du.split(parts[1], "/") -- preferences are stored with forward slashes table.insert(exec_table, tmp[#tmp]) end diff --git a/tools/gen_i18n_mo.lua b/tools/gen_i18n_mo.lua index d3b5b5b..d032484 100644 --- a/tools/gen_i18n_mo.lua +++ b/tools/gen_i18n_mo.lua @@ -30,6 +30,8 @@ local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" +du.check_min_api_version("5.0.0", "gen_I18n_mo") + -- figure out the path separator local PS = dt.configuration.running_os == "windows" and "\\" or "/" diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index cea3bbc..9ea2f69 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -10,7 +10,7 @@ local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local libname = nil -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "get_lib_manpages") local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 42b19e4..ed0813a 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -5,8 +5,9 @@ ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +du.check_min_api_version("3.0.0", "get_libdoc") local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/plugin_manager.lua b/tools/plugin_manager.lua new file mode 120000 index 0000000..bc0e1bf --- /dev/null +++ b/tools/plugin_manager.lua @@ -0,0 +1 @@ +/home/bill/src/plugins/lua-scripts/tools/plugin_manager.lua \ No newline at end of file diff --git a/tools/script_manager.lua b/tools/script_manager.lua index d8484ea..e16b0cc 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -455,7 +455,7 @@ end -- api check -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0") -- set up tables to contain all the widgets and choices