Merge branch 'master' into BzKevin-enfuseAdvanced-refactor

This commit is contained in:
supertobi
2019-05-07 15:54:12 +02:00
committed by GitHub
51 changed files with 1841 additions and 357 deletions

158
contrib/AutoGrouper.lua Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
]]
--[[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
}
)

221
contrib/CollectHelper.lua Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
]]
--[[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
)

424
contrib/HDRMerge.lua Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
]]
--[[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
}
)

View File

@@ -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

113
contrib/OpenInExplorer.lua Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
]]
--[[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

View File

@@ -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)

View File

@@ -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

View File

@@ -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/")

View File

@@ -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 = {}

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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/")

View File

@@ -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 &amp; 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,"&", "&amp;")
str = string.gsub(str,"\"", "&quot;")
str = string.gsub(str,"'", "&apos;")
str = string.gsub(str,"<", "&lt;")
str = string.gsub(str,">", "&gt;")
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.." <name>"..image_title.."</name>\n"
kml_file = kml_file.." <description>"..image_description.."</description>\n"
@@ -371,7 +231,7 @@ local function create_kml_file(storage, image_table, extra_data)
kml_file = kml_file.." <LineString>\n"
kml_file = kml_file.." <coordinates>\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.."</Document>\n"
kml_file = kml_file.."</kml>"
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;

View File

@@ -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/")

View File

@@ -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",

View File

@@ -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/")

View File

@@ -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

View File

@@ -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" }

View File

@@ -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/")

View File

@@ -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/")

460
contrib/video_ffmpeg.lua Normal file
View File

@@ -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 <http://www.gnu.org/licenses/>.
]]
--[[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
)

View File

@@ -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

View File

@@ -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!")

View File

@@ -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")

View File

@@ -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}

View File

@@ -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

View File

@@ -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

View File

@@ -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).

View File

@@ -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)

View File

@@ -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]],

View File

@@ -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

View File

@@ -101,4 +101,33 @@ function dtutils_system.windows_command(command)
return result
end
return dtutils_system
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

View File

@@ -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,

View File

@@ -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

View File

@@ -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",

View File

@@ -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 = {}

View File

@@ -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

View File

@@ -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",

View File

@@ -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"}

View File

@@ -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

View File

@@ -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",

View File

@@ -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

View File

@@ -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 "/"

View File

@@ -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"}

View File

@@ -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"}

1
tools/plugin_manager.lua Symbolic link
View File

@@ -0,0 +1 @@
/home/bill/src/plugins/lua-scripts/tools/plugin_manager.lua

View File

@@ -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