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