Merge branch 'master' into BzKevin-enfuseAdvanced-refactor
This commit is contained in:
158
contrib/AutoGrouper.lua
Normal file
158
contrib/AutoGrouper.lua
Normal 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
221
contrib/CollectHelper.lua
Normal 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
424
contrib/HDRMerge.lua
Normal 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
|
||||
}
|
||||
)
|
||||
@@ -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
113
contrib/OpenInExplorer.lua
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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/")
|
||||
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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.." <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;
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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/")
|
||||
|
||||
@@ -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
460
contrib/video_ffmpeg.lua
Normal 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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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!")
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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]],
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 = {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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 "/"
|
||||
|
||||
@@ -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"}
|
||||
|
||||
@@ -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
1
tools/plugin_manager.lua
Symbolic link
@@ -0,0 +1 @@
|
||||
/home/bill/src/plugins/lua-scripts/tools/plugin_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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user