From e05e05d866d9f41b98e7aab88c3cd9acd6111cf6 Mon Sep 17 00:00:00 2001 From: luca Date: Sun, 6 Jan 2019 21:42:49 +0100 Subject: [PATCH 01/61] enfuse: convert raws to tiff automatically it uses darktable-cli (if installed) to convert to a temporary file the raw files that enfuse is not able to process. --- official/enfuse.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/official/enfuse.lua b/official/enfuse.lua index 04dda4d..413554a 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -49,6 +49,7 @@ end -- add a new lib -- is enfuse installed? local enfuse_installed = df.check_if_bin_exists("enfuse") +local darktable_cli_installed = df.check_if_bin_exists("darktable-cli") -- check the version so that we can use the correct arguments @@ -149,6 +150,14 @@ dt.register_lib( cnt = cnt + 1 f:write(i.path.."/"..i.filename.."\n") target_dir = i.path + elseif i.is_raw and darktable_cli_installed then + local tmp_exported = os.tmpname()..".tif" + os.execute("darktable-cli \""..i.path.."/"..i.filename.."\" \""..tmp_exported.."\"") + dt.print("Raw file "..i.filename.." automatically converted to tiff") + + cnt = cnt + 1 + f:write(tmp_exported.."\n") + target_dir = i.path else dt.print("skipping "..i.filename) n_skipped = n_skipped + 1 From f464a52ea47ae98ea5b476ceb525983fb0cf8da4 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 8 Jan 2019 20:46:32 +0100 Subject: [PATCH 02/61] dt.print made properly with string.format All print call have been replaced with string.format (when needed) and call to dt.gettext, in a future opening to translation --- official/enfuse.lua | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/official/enfuse.lua b/official/enfuse.lua index 413554a..f011859 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -121,7 +121,7 @@ dt.register_lib( dt.new_widget("button") { label = enfuse_installed and "run enfuse" or "enfuse not installed", - clicked_callback = function (_) + clicked_callback = function () -- remember exposure_mu -- TODO: find a way to save it whenever the value changes local mu = exposure_mu.value @@ -135,7 +135,7 @@ dt.register_lib( local response_file = os.tmpname() local f = io.open(response_file, "w") if not f then - dt.print("error writing to `"..response_file.."'") + dt.print(string.format(_("Error writing to `%s`"), response_file)) os.remove(response_file) return end @@ -144,7 +144,7 @@ dt.register_lib( local cnt = 0 local n_skipped = 0 local target_dir - for _, i in ipairs(dt.gui.action_images) do + for i_, 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 @@ -152,28 +152,27 @@ dt.register_lib( target_dir = i.path elseif i.is_raw and darktable_cli_installed then local tmp_exported = os.tmpname()..".tif" + dt.print(string.format(_("Converting raw file '%s' to tiff..."), i.filename)) os.execute("darktable-cli \""..i.path.."/"..i.filename.."\" \""..tmp_exported.."\"") - dt.print("Raw file "..i.filename.." automatically converted to tiff") cnt = cnt + 1 f:write(tmp_exported.."\n") target_dir = i.path else - dt.print("skipping "..i.filename) + 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") + 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") + + if n_skipped > 0 then + dt.print(string.format(_("%d image(s) skipped"), n_skipped)) end -- call enfuse on the response file @@ -188,7 +187,7 @@ dt.register_lib( 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") + dt.print(_("Enfuse failed, see terminal output for details")) os.remove(response_file) return end @@ -200,9 +199,9 @@ dt.register_lib( local image = dt.database.import(output_image) -- tell the user that everything worked - dt.print("enfuse was successful, resulting image was imported") + 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("enfuse: done, resulting image '"..output_image.."' was imported with id "..image.id) + print(string.format(_("enfuse: done, resulting image '%s' has been imported with id %d"), output_image, image.id)) end } }, From 32f6310947852cef04eadcf3f0b55685be6a00c6 Mon Sep 17 00:00:00 2001 From: luca Date: Tue, 8 Jan 2019 20:56:35 +0100 Subject: [PATCH 03/61] Used DT internal conversion (in place of darktable-cli os call) Now a TIFF exporter is instanced at the beginning and used for all selected raws No system call to darktable-cli anymore --- official/enfuse.lua | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/official/enfuse.lua b/official/enfuse.lua index f011859..306cdcc 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -49,7 +49,12 @@ end -- add a new lib -- is enfuse installed? local enfuse_installed = df.check_if_bin_exists("enfuse") -local darktable_cli_installed = df.check_if_bin_exists("darktable-cli") + +-- 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 @@ -145,19 +150,25 @@ dt.register_lib( 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. alternatively we could export raws that we encounter + + -- only use ldr files as enfuse can't open raws if i.is_ldr then cnt = cnt + 1 f:write(i.path.."/"..i.filename.."\n") target_dir = i.path - elseif i.is_raw and darktable_cli_installed then - local tmp_exported = os.tmpname()..".tif" + + -- alternatively raws will be exported as tiff + elseif i.is_raw then + local tmp_exported = os.tmpname()..".tiff" dt.print(string.format(_("Converting raw file '%s' to tiff..."), i.filename)) - os.execute("darktable-cli \""..i.path.."/"..i.filename.."\" \""..tmp_exported.."\"") + tiff_exporter.write_image(tiff_exporter, 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 @@ -184,7 +195,7 @@ dt.register_lib( if version < "4.2" then exposure_option = " --exposure-mu " end - local command = "enfuse --depth "..depth.value..exposure_option..ugly_decimal_point_hack + local command = enfuse_installed.." --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")) From 3a129d3c4fcb0fca114fc6329d32f75f27302323 Mon Sep 17 00:00:00 2001 From: theres Date: Mon, 21 Jan 2019 01:13:44 +0100 Subject: [PATCH 04/61] Timelapse plugin based on ffmpeg --- contrib/timelapse.lua | 463 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 contrib/timelapse.lua diff --git a/contrib/timelapse.lua b/contrib/timelapse.lua new file mode 100644 index 0000000..2976cc7 --- /dev/null +++ b/contrib/timelapse.lua @@ -0,0 +1,463 @@ +--[[Timelapse video plugin for darktable 2.4.X and 2.6.X + + copyright (c) 2018, 2019 Dominik Markiewicz + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this plugin +This plugin will add the new export module "timelapse". + +----REQUIRED SOFTWARE---- +ffmpeg + +----USAGE---- +1. Go to Lighttable +2. Select images you want to use as a timelapse frames +3. In image export module select 'timelapse' +4. Configure you video settings +5. Export + +----WARNING---- +This script has been tested under Linux only +]] + +local dt = require "darktable" +local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" +local gettext = dt.gettext + +dt.configuration.check_version(...,{5,0,0}) + +local MODULE_NAME = 'timelapse' +gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..'/lua/') + +local function _(msgid) + return gettext.dgettext(MODULE_NAME, msgid) +end + +local OS_PATH_SEPARATOR = dt.configuration.running_os == "windows" and "\\" or "/" + +---- 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'} + } +} + +res_list = {} +for i, v in pairs(resolutions) do + table.insert(res_list, v['label']) +end + +table.sort(res_list) + +format_list = {} +for i,v in pairs(formats) do + table.insert(format_list, i) +end + +table.sort(format_list) + +codec_list = formats['AVI']['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 + +---- ENSURE ffmpeg is INSTALLED + +dt.print_log(MODULE_NAME .. " - Executable Path Preference: "..df.get_executable_path_preference("ffmpeg")) +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") +end + +local ffmpeg_path_widget = df.executable_path_widget({'ffmpeg'}) +dt.preferences.register("executable_paths", "ffmpeg", + "file", + MODULE_NAME .. ': ffmpeg location', + 'Install location of ffmpeg', + "ffmpeg", + ffmpeg_path_widget +) + +---- 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 + +local function format(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 + +local function open_file(filename) + local open_cmd = 'xdg-open' + if (dt.configuration.running_os == 'windows') then + open_cmd = 'start' + elseif (dt.configuration.running_os == 'macos') then + open_cmd = 'open' + end + return dsys.external_command(open_cmd..' '..df.sanitize_filename(filename)) +end + +local function mkdir(path) + local mkdir_cmd = dt.configuration.running_os == 'windows' and 'mkdir' or 'mkdir -p' + return dsys.external_command(mkdir_cmd..' '..df.sanitize_filename(path)) +end + +local function rm(path) + local rm_cmd = dt.configuration.running_os == 'windows' and 'rmdir /S /Q' or 'rm -r' + return dsys.external_command(rm_cmd..' '..df.sanitize_filename(path)) +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 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", os.getenv('HOME')), + 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 module_widget = dt.new_widget('box') { + orientation = 'vertical', + res_selector, + framerates_selector, + format_selector, + codec_selector, + output_box, + open_after_export_cb +} + +---- EXPORT & REGISTRATION + +local function support_format(storage, format) + return true +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 .. '/'..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(filename_entry.text, filename_mappings) + local path = output_directory..OS_PATH_SEPARATOR..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 = mkdir(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..OS_PATH_SEPARATOR..'%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 = mkdir(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 .. OS_PATH_SEPARATOR .. 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 + open_file(path) + end + end + + rm(tmp_dir) +end + +dt.register_storage( + 'module_timelapse', + _(MODULE_NAME), + nil, + finalize_export, + support_format, + init_export, + module_widget +) + From 3675f4d7cdfba921653c982fe4924a8310730120 Mon Sep 17 00:00:00 2001 From: theres Date: Sun, 27 Jan 2019 02:34:01 +0100 Subject: [PATCH 05/61] video ffmpeg - post review fixes - rename OS_PATH_SEPARATOR -> PS and use in all needed places - change function name from `open_file` to `launch_default_app` - move `mkdir`, `rm` and `launch_default_app` helper functions to libs - add executable_path_widget to export module if needed, not to the preferences - add show_status - remove support_format - change function name `format` to `format_string`, add comments - replace apostrophes by quote marks --- contrib/timelapse.lua | 463 --------------------------------------- contrib/video_ffmpeg.lua | 448 +++++++++++++++++++++++++++++++++++++ lib/dtutils/file.lua | 46 ++++ lib/dtutils/system.lua | 30 +++ 4 files changed, 524 insertions(+), 463 deletions(-) delete mode 100644 contrib/timelapse.lua create mode 100644 contrib/video_ffmpeg.lua diff --git a/contrib/timelapse.lua b/contrib/timelapse.lua deleted file mode 100644 index 2976cc7..0000000 --- a/contrib/timelapse.lua +++ /dev/null @@ -1,463 +0,0 @@ ---[[Timelapse video plugin for darktable 2.4.X and 2.6.X - - copyright (c) 2018, 2019 Dominik Markiewicz - - darktable is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - darktable is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with darktable. If not, see . -]] - ---[[About this plugin -This plugin will add the new export module "timelapse". - -----REQUIRED SOFTWARE---- -ffmpeg - -----USAGE---- -1. Go to Lighttable -2. Select images you want to use as a timelapse frames -3. In image export module select 'timelapse' -4. Configure you video settings -5. Export - -----WARNING---- -This script has been tested under Linux only -]] - -local dt = require "darktable" -local df = require "lib/dtutils.file" -local dsys = require "lib/dtutils.system" -local gettext = dt.gettext - -dt.configuration.check_version(...,{5,0,0}) - -local MODULE_NAME = 'timelapse' -gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..'/lua/') - -local function _(msgid) - return gettext.dgettext(MODULE_NAME, msgid) -end - -local OS_PATH_SEPARATOR = dt.configuration.running_os == "windows" and "\\" or "/" - ----- 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'} - } -} - -res_list = {} -for i, v in pairs(resolutions) do - table.insert(res_list, v['label']) -end - -table.sort(res_list) - -format_list = {} -for i,v in pairs(formats) do - table.insert(format_list, i) -end - -table.sort(format_list) - -codec_list = formats['AVI']['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 - ----- ENSURE ffmpeg is INSTALLED - -dt.print_log(MODULE_NAME .. " - Executable Path Preference: "..df.get_executable_path_preference("ffmpeg")) -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") -end - -local ffmpeg_path_widget = df.executable_path_widget({'ffmpeg'}) -dt.preferences.register("executable_paths", "ffmpeg", - "file", - MODULE_NAME .. ': ffmpeg location', - 'Install location of ffmpeg', - "ffmpeg", - ffmpeg_path_widget -) - ----- 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 - -local function format(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 - -local function open_file(filename) - local open_cmd = 'xdg-open' - if (dt.configuration.running_os == 'windows') then - open_cmd = 'start' - elseif (dt.configuration.running_os == 'macos') then - open_cmd = 'open' - end - return dsys.external_command(open_cmd..' '..df.sanitize_filename(filename)) -end - -local function mkdir(path) - local mkdir_cmd = dt.configuration.running_os == 'windows' and 'mkdir' or 'mkdir -p' - return dsys.external_command(mkdir_cmd..' '..df.sanitize_filename(path)) -end - -local function rm(path) - local rm_cmd = dt.configuration.running_os == 'windows' and 'rmdir /S /Q' or 'rm -r' - return dsys.external_command(rm_cmd..' '..df.sanitize_filename(path)) -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 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", os.getenv('HOME')), - 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 module_widget = dt.new_widget('box') { - orientation = 'vertical', - res_selector, - framerates_selector, - format_selector, - codec_selector, - output_box, - open_after_export_cb -} - ----- EXPORT & REGISTRATION - -local function support_format(storage, format) - return true -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 .. '/'..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(filename_entry.text, filename_mappings) - local path = output_directory..OS_PATH_SEPARATOR..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 = mkdir(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..OS_PATH_SEPARATOR..'%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 = mkdir(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 .. OS_PATH_SEPARATOR .. 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 - open_file(path) - end - end - - rm(tmp_dir) -end - -dt.register_storage( - 'module_timelapse', - _(MODULE_NAME), - nil, - finalize_export, - support_format, - init_export, - module_widget -) - diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua new file mode 100644 index 0000000..9b8c188 --- /dev/null +++ b/contrib/video_ffmpeg.lua @@ -0,0 +1,448 @@ +--[[Timelapse video plugin based on ffmpeg for darktable 2.4.X and 2.6.X + + copyright (c) 2018, 2019 Dominik Markiewicz + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this plugin +This plugin will add the new export module "video ffmpeg". + +----REQUIRED SOFTWARE---- +ffmpeg + +----USAGE---- +1. Go to Lighttable +2. Select images you want to use as a video ffmpeg frames +3. In image export module select "video ffmpeg" +4. Configure you video settings +5. Export + +----WARNING---- +This script has been tested under Linux only +]] + +local dt = require "darktable" +local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" +local gettext = dt.gettext + +dt.configuration.check_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 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", os.getenv("HOME")), + 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(timelapse_widgets, 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 .. "/"..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.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(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(path) + end + end + + df.rm(tmp_dir) +end + +dt.register_storage( + "module_video_ffmpeg", + _(MODULE_NAME), + show_status, + finalize_export, + nil, + init_export, + module_widget +) + diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 120b70f..4015e3a 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -1,6 +1,7 @@ local dtutils_file = {} local dt = require "darktable" local ds = require "lib/dtutils.string" +local dsys = require "lib/dtutils.system" local log = require "lib/dtutils.log" @@ -553,5 +554,50 @@ 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) + local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p" + return dsys.external_command(mkdir_cmd.." "..dtutils_file.sanitize_filename(path)) +end + +dtutils_file.libdoc.functions["rm"] = { + Name = [[rm]], + Synopsis = [[remove file or directory]], + Usage = [[local df = require "lib/dtutils.file" + + df.rm(path) + path - string - a file or directory path]], + Description = [[rm allow to recursively remove files or directories]], + Return_Value = [[path - string - a file or directory path]], + Limitations = [[]], + Example = [[]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} +function dtutils_file.rm(path) + local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" + return dsys.external_command(rm_cmd.." "..dtutils_file.sanitize_filename(path)) +end + + return dtutils_file diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 0425a10..c40fc0c 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -101,4 +101,34 @@ function dtutils_system.windows_command(command) return result end + +dtutils_system.libdoc.functions["launch_default_app"] = { + Name = [[launch_default_app]], + Synopsis = [[try to open file in default application]], + Usage = [[local dsys = require "lib/dtutils.file" + + dsys.launch_default_app(path) + path - string - a file path]], + Description = [[launch_default_app allow to open file in application that is assigned as default + one in users's system for given filetype + ]], + Return_Value = [[path - string - a file path]], + 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 = "start" + elseif (dt.configuration.running_os == "macos") then + open_cmd = "open" + end + return dtutils_system.external_command(open_cmd..' "'..path..'"') +end + + return dtutils_system \ No newline at end of file From 56159e91ce1c28420be549499604fa92077c8550 Mon Sep 17 00:00:00 2001 From: theres Date: Sun, 3 Feb 2019 12:57:34 +0100 Subject: [PATCH 06/61] video_ffmpg: fix executable path widget for ffmpeg; try to get default location for files - logic for craeting vidget for specifing ffmpeg location was invalid - now default export location should be set to default system Video/Movie directory --- contrib/video_ffmpeg.lua | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 9b8c188..c5d390f 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -272,11 +272,22 @@ local destination_label = dt.new_widget("section_label"){ 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 + 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", os.getenv("HOME")), + value = string_pref_read("export_path", defaultVideoDir), changed_callback = string_pref_write("export_path") } @@ -336,7 +347,7 @@ local open_after_export_cb = dt.new_widget("check_button"){ local widgets_list = {} if not df.check_if_bin_exists("ffmpeg") then - table.insert(timelapse_widgets, df.executable_path_widget("ffmpeg")) + table.insert(widgets_list, df.executable_path_widget({"ffmpeg"})) end table.insert(widgets_list, res_selector) table.insert(widgets_list, framerates_selector) From 2a2fbd18cddaf3a70c20ae105101ea00927a1dd4 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Wed, 13 Feb 2019 12:21:57 -0500 Subject: [PATCH 07/61] Add HDRMerge --- contrib/HDRMerge.lua | 368 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 368 insertions(+) create mode 100644 contrib/HDRMerge.lua diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua new file mode 100644 index 0000000..9829be1 --- /dev/null +++ b/contrib/HDRMerge.lua @@ -0,0 +1,368 @@ +--[[HDRMerge plugin for darktable + + copyright (c) 2018 Kevin Ertel + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this Plugin +This plugin adds the module "HDRMerge" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +HDRMerge ver. 4.5 or greater + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in +On the initial startup go to darktable settings > lua options and set your executable paths and other preferences, then restart darktable + +Select bracketed images and press the Run HDRMerge button. The resulting DNG will be auto-imported into darktable if you have that option enabled. +Additional tags or style can be applied on auto import as well, if you desire. + +Base Options: +Select your desired BPS and 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 + +----KNOWN ISSUES---- +]] + +local dt = require "darktable" +local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" +require "official/yield" + +--Check if HDRMerge is installed-- +local not_installed = 0 +dt.print_log("HDRMerge - Executable Path Preference: "..df.get_executable_path_preference("HDRMerge")) +local HDRMerge_path = df.check_if_bin_exists("HDRMerge") +if not HDRMerge_path then + dt.print_error("HDRMerge not found") + dt.print("ERROR - HDRMerge not found") + not_installed = 1 +end + +--Detect OS and modify accordingly-- +local os_path_seperator = "/" +if dt.configuration.running_os == "windows" then os_path_seperator = "\\" end + +-- READ PREFERENCES -- +local pref_bps = dt.preferences.read("module_HDRMerge", "bits_per_sample", "enum") +local pref_bps_enum = 0 +if pref_bps == "32" then + pref_bps_enum = 3 +elseif pref_bps == "24" then + pref_bps_enum = 2 +else + pref_bps_enum = 1 +end +local pref_size = dt.preferences.read("module_HDRMerge", "preview_size", "enum") +local pref_size_enum = 0 +if pref_size == "none" then + pref_size_enum = 3 +elseif pref_size == "full" then + pref_size_enum = 2 +else + pref_size_enum = 1 +end +--pref_cpytags = dt.preferences.read("module_HDRMerge", "copy_tags", "bool") +--pref_addtags = dt.preferences.read("module_HDRMerge", "add_tags", "string") +local pref_style = dt.preferences.read("module_HDRMerge", "style", "string") + +--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 + +-- FUNCTION -- +local function build_execute_command(cmd, args, file_list) + local result = cmd.." "..args.." "..file_list + return result +end +local function HDRMerge() + if not_installed == 1 then + dt.print("Required software not found") + dt.print_log("Required software not found - HDRMerge did not run") + return + end + dt.print_log("Running HDRMerge") + dt.print("Running HDRMerge") + gui_job = dt.gui.create_job("HDRMerge", 1) + + --Inits-- + local images = dt.gui.selection() + local images_to_merge = "" + local image_path = "" + local curr_image = "" + local first_image = "" + local last_image = "" + local output_file = "" + local num_images = 0 + + --Read Settings-- + local set_bps = HDRMerge_cmbx_bps.value + local set_size = HDRMerge_cmbx_size.value + local set_batch = HDRMerge_chkbtn_batch.value + local set_gap = HDRMerge_sldr_gap.value + local set_cpytags = HDRMerge_chkbtn_cpytags.value + local set_tag = HDRMerge_entry_tag.text + + --Create Images String-- + for _,image in pairs(images) do + curr_image = image.path..os_path_seperator..image.filename + images_to_merge = images_to_merge..df.sanitize_filename(curr_image).." " + last_image = string.gsub(string.match(image.filename,'^.*%.'), "%." , "") + image_path = image.path + if _ == 1 then first_image = last_image end + num_images = num_images + 1 + end + output_file = image_path..os_path_seperator..first_image.."-"..string.match(last_image, '%d*$')..".dng" + output_file = df.create_unique_filename(output_file) + --output_file = df.sanitize_filename(output_file) + --Check if at least 2 images selected-- + if num_images < 2 then + dt.print_error("Less than 2 images selected") + dt.print("ERROR - please select at least 2 images") + gui_job.valid = false + return + end + + --Create Command Args-- + cmd_args = "-b "..set_bps.." -p "..set_size + if (set_batch) then + cmd_args = cmd_args.." -B -g "..set_gap.." -a" + else --add ability to launch with gui here by omiting -o arg + cmd_args = cmd_args.." -o "..df.sanitize_filename(output_file) + end + + gui_job.percent = .1 + + --Execute with Run Command-- + run_cmd = build_execute_command(HDRMerge_path, cmd_args, images_to_merge) + dt.print_log("run_cmd = "..run_cmd) + resp = dsys.external_command(run_cmd) + + gui_job.percent = .9 + + if resp == 0 and not(set_batch) then + --Import Image-- + imported = dt.database.import(output_file) + + --Apply Selected Style (IF)-- + if HDRMerge_cmbx_style.selected > 1 then + set_style = styles[HDRMerge_cmbx_style.selected - 1] + dt.styles.apply(set_style , imported) + end + + --Copy Tags (IF)-- + if (set_cpytags) then + all_tags = dt.tags.get_tags(images[1]) + for _,tag in pairs(all_tags) do + if string.match(tag.name, 'darktable|') == nil then + dt.tags.attach(tag, imported) + end + end + end + + --Apply Entered Tags (IF)-- + if set_tag ~= nil then + for tag in string.gmatch(set_tag, '[^,]+') do + tag = string.gsub(tag, " ", "") + tag = dt.tags.create(tag) + dt.tags.attach(tag, imported) + end + end + + dt.print_log("Merge Successful") + dt.print("Merge Successful") + elseif resp == 0 then + dt.print_log("Merge Successful") + dt.print("Merge Successful") + else + dt.print_error("Merge Not Successful") + dt.print("ERROR - Merge Not Successful") + end + gui_job.percent = 1 + gui_job.valid = false +end + +-- GUI -- +HDRMerge_lbl_base= dt.new_widget("section_label"){ + label = "Base Options", + } +HDRMerge_cmbx_bps = dt.new_widget("combobox"){ + label = 'Bits Per Sample', + tooltip = 'Output file\'s bit depth \ndefault: '..pref_bps, + value = pref_bps_enum, + "16", "24", "32", + } +HDRMerge_cmbx_size = dt.new_widget("combobox"){ + label = 'Preview Size', + tooltip = 'Output file\'s built-in preview size \ndefault: '..pref_size, + value = pref_size_enum, + "half", "full", "none", + } +HDRMerge_lbl_batch= dt.new_widget("section_label"){ + label = "Batch Options", + } +HDRMerge_chkbtn_batch = dt.new_widget("check_button"){ + label = ' Batch Mode', + value = false, --pref_batch, + tooltip ='Operate in batch mode. When operating in batch mode output files will NOT be auto-imported \ndefault: (false).', + } +HDRMerge_sldr_gap = dt.new_widget("slider"){ + label = 'Batch Gap [sec.]', + tooltip = 'Gap, in seconds, between batch mode groups \ndefault: (3).', + soft_min = 1, + soft_max = 60, + hard_min = 1, + hard_max = 60, + step = 1, + digits = 0, + value = 3, --pref_gap, + } +HDRMerge_lbl_out= dt.new_widget("section_label"){ + label = "Auto-import Options", + } +HDRMerge_cmbx_style = dt.new_widget("combobox"){ + label = "Apply Style on Import", + tooltip = "Apply selected style on auto-import to newly created HDRMerge DNG\nDoes not apply when in Batch mode.", + value = 1, + "none", + } +pref_style_enum = 1 +for k=1, (styles_count-1) do + HDRMerge_cmbx_style[k+1] = styles[k].name + if styles[k].name == pref_style then pref_style_enum = k+1 end +end +HDRMerge_cmbx_style.value = pref_style_enum +HDRMerge_chkbtn_cpytags = dt.new_widget("check_button"){ + label = ' Copy Tags', + value = dt.preferences.read("module_HDRMerge", "copy_tags", "bool"), + tooltip ='Copy tags from first image. When operating in batch mode this will NOT be performed. \ndefault: (true).', + } +HDRMerge_lbl_tags= dt.new_widget("label"){ + label = 'Additional Tags', + } +HDRMerge_entry_tag = dt.new_widget("entry"){ + tooltip = "Additional tags to be added on import. Seperate with commas, all spaces will be removed", + text = dt.preferences.read("module_HDRMerge", "add_tags", "string"), + placeholder = "Enter tags, seperated by commas", + editable = true +} +HDRMerge_btn_run = dt.new_widget("button"){ + label = "Run HDRMerge", + tooltip = "Runs HDRMerge on selected images, with selected settings", + clicked_callback = function() HDRMerge() end + } +HDRMerge_lbl_note= dt.new_widget("label"){ + label = 'Defaults can be adjusted under:\n"Settings > lua options"', + } +dt.register_lib( + "HDRMerge_Lib", -- Module name + "HDRMerge", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers + dt.new_widget("box"){ + orientation = "vertical", + HDRMerge_lbl_base, + HDRMerge_cmbx_bps, + HDRMerge_cmbx_size, + HDRMerge_lbl_batch, + HDRMerge_chkbtn_batch, + HDRMerge_sldr_gap, + HDRMerge_lbl_out, + HDRMerge_cmbx_style, + HDRMerge_chkbtn_cpytags, + HDRMerge_lbl_tags, + HDRMerge_entry_tag, + HDRMerge_btn_run, + HDRMerge_lbl_note + } +) + +-- PREFERENCES -- +HDRMerge_entry_widget_tags = dt.new_widget("entry"){ + tooltip = "Seperate with commas, all spaces will be removed", + text = nil, + placeholder = "Enter default tags", + editable = true +} +dt.preferences.register("module_HDRMerge", "add_tags", -- name + "string", -- type + 'HDRMerge: Defualt additional tags', -- label + 'Changes DEFAULT entry in the additional tags option. Requires restart to take effect.', -- tooltip + "", -- default + HDRMerge_entry_widget_tags +) +HDRMerge_entry_widget_style = dt.new_widget("entry"){ + tooltip = "Enter the style name exactly as it is", + text = nil, + placeholder = "Enter Style name", + editable = true +} +dt.preferences.register("module_HDRMerge", "style", -- name + "string", -- type + 'HDRMerge: Defualt Style', -- label + 'Changes DEFAULT entry in the Style option. Requires restart to take effect.', -- tooltip + "", -- default + HDRMerge_entry_widget_style +) +dt.preferences.register("module_HDRMerge", "copy_tags", -- name + "bool", -- type + 'HDRMerge: Copy tags from first image by default', -- label + 'Changes DEFAULT selection for Copy Tags, Requires restart to take effect.', -- tooltip + true -- default +) +dt.preferences.register("module_HDRMerge", "preview_size", -- name + "enum", -- type + 'HDRMerge: Default DNG Preview Size', -- label + "Change the DEFAULT preview size. Requires restart to take effect.", -- tooltip + "half", -- default + "full", "none" --values +) +dt.preferences.register("module_HDRMerge", "bits_per_sample", -- name + "enum", -- type + 'HDRMerge: Default Bits Per Sample', -- label + 'Change the DEFAULT bit depth. Requires restart to take effect.', -- tooltip + "16", -- default + "24","32" --value +) + +if not HDRMerge_path then + HDRMerge_path = "" +end +HDRMerge_path_widget = dt.new_widget("file_chooser_button"){ + title = "Select HDRMerge[.exe] file", + value = HDRMerge_path, + is_directory = false, +} +dt.preferences.register("executable_paths", "HDRMerge", -- name + "file", -- type + 'HDRMerge: Binary Location', -- label + 'Install location of HDRMerge[.exe]. Requires restart to take effect.', -- tooltip + "HDRMerge", -- default + HDRMerge_path_widget +) From be386e1172b87352c1f255c5c16390287c96379e Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Wed, 13 Feb 2019 12:22:42 -0500 Subject: [PATCH 08/61] Add OpenInExplorer --- contrib/OpenInExplorer.lua | 80 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 contrib/OpenInExplorer.lua diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua new file mode 100644 index 0000000..1616baf --- /dev/null +++ b/contrib/OpenInExplorer.lua @@ -0,0 +1,80 @@ +--[[OpenInExplorer plugin for darktable + + copyright (c) 2018 Kevin Ertel + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this plugin +This plugin adds the module "OpenInExplorer" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +Microsoft Windows Operating System + +----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 df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" +require "official/yield" + +--Detect OS and modify accordingly-- +local proper_install = 0 +if dt.configuration.running_os == "windows" then + proper_install = 1 +end + +-- FUNCTION -- +local function OpenInExplorer() --Open in Explorer + --Inits-- + if proper_install ~= 1 then + return + end + local images = dt.gui.selection() + local curr_image = "" + + for _,image in pairs(images) do + curr_image = image.path..'\\'..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 +end + +-- GUI -- +OpenInExplorer_btn_run = dt.new_widget("button"){ + label = "Go to Folder", + tooltip = "Opens selected image(s) location(s) in file explorer", + clicked_callback = function() OpenInExplorer() end + } +dt.register_lib( --OpenInExplorer + "OpenInExplorer_Lib", -- Module name + "Open In Explorer", -- name + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers + dt.new_widget("box"){ + orientation = "vertical", + OpenInExplorer_btn_run + } +) \ No newline at end of file From 4d6dfea3a5e977264c914bdb3ccde983fc4073a9 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Wed, 13 Feb 2019 12:23:34 -0500 Subject: [PATCH 09/61] Add CollectHelper --- contrib/CollectHelper.lua | 199 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 199 insertions(+) create mode 100644 contrib/CollectHelper.lua diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua new file mode 100644 index 0000000..37e9a8e --- /dev/null +++ b/contrib/CollectHelper.lua @@ -0,0 +1,199 @@ +--[[Collect Helper plugin for darktable + + copyright (c) 2019 Kevin Ertel + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this plugin +This plugin adds the button(s) to the "Selected Images" module: +1) Return to Previous Collection +2) Collect on image's Folder +3) Collect on image's Color Label(s) +4) Collect on All (AND) + +It also adds 3 preferences to the lua options dialog box which allow the user to activate/deactivate the 3 "Collect on" buttons. + +Button Behavior: +1) Return to Previous Collection - Will reset the collect parameters to the previously active settings +2) Collect on image's Folder - Will change the collect parameters to be "Folder" with a value of the selected image's folder location +3) Collect on image's Color Label(s) - Will change the collect parameter to be "Color" with a value of the selected images color labels, will apply multiple parameters with AND logic if multiple exist +4) Collect on All (AND) - Will collect on all parameters activated by the preferences dialog, as such this button is redundant if you only have one of the two other options enabled + +----REQUIRED SOFTWARE---- +NA + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Select the photo you wish to change you collection based on. +In the "Selected Images" module click on "Collect on this Image" + +----KNOWN ISSUES---- +]] + +local dt = require "darktable" +previous = nil +all_active = false + +-- FUNCTION -- +local function CheckSingleImage(selection) + if #selection ~= 1 then + dt.print("Please select a single image") + return 1 + end + return 0 +end +local function PreviousCollection() + if previous ~= nil then + previous = dt.gui.libs.collect.filter(previous) + end +end +local function CollectOnFolder() + local images = dt.gui.selection() + if CheckSingleImage(images) == 1 then + return + end + rules = {} + all_rules = {} + 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 + else + previous = dt.gui.libs.collect.filter(rules) + end +end +local function CollectOnColors() + local images = dt.gui.selection() + if CheckSingleImage(images) == 1 then + return + end + for _,image in pairs(images) do + rules = {} + if image.red then + 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 + 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 + 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 + 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 + 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 + else + previous = dt.gui.libs.collect.filter(rules) + end + end +end +local function CollectOnAll_AND() + local images = dt.gui.selection() + if CheckSingleImage(images) == 1 then + return + end + all_rules = {} + all_active = true + if dt.preferences.read('module_CollectHelper','folder','bool') then + CollectOnFolder() + end + if dt.preferences.read('module_CollectHelper','colors','bool') then + CollectOnColors() + end + all_active = false + previous = dt.gui.libs.collect.filter(all_rules) +end + +-- GUI -- +dt.gui.libs.image.register_action( + "Return to Previous Collection", + 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 on image's Folder", + function() CollectOnFolder() 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 on image's Color Label(s)", + function() CollectOnColors() 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 on 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 +) \ No newline at end of file From ff0352cfe644c568e720f876b2f758049007edd5 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Wed, 13 Feb 2019 13:42:54 -0500 Subject: [PATCH 10/61] Updated version of Collect Helper Reworded the button text to better match with the rest of darktable's buttons --- contrib/CollectHelper.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index 37e9a8e..fabe4c9 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -152,27 +152,27 @@ end -- GUI -- dt.gui.libs.image.register_action( - "Return to Previous Collection", + "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 on image's Folder", + "collect: folder", function() CollectOnFolder() 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 on image's Color Label(s)", + "collect: color label(s)", function() CollectOnColors() 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 on All (AND)", + "collect: all (AND)", function() CollectOnAll_AND() end, "Sets the Collect parameters based on all activated CollectHelper options" ) From f6112136623408a356624c53fe2bf620fb54edfd Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Wed, 13 Feb 2019 13:46:22 -0500 Subject: [PATCH 11/61] Update OpenInExplorer instead of being it's own lib this is now added to the selected image's module and the button text has been updated to match dt's style --- contrib/OpenInExplorer.lua | 31 ++++++++++--------------------- 1 file changed, 10 insertions(+), 21 deletions(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 1616baf..8a9e52f 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -34,20 +34,18 @@ A file explorer window will be opened for each selected file at the file's locat ]] local dt = require "darktable" -local df = require "lib/dtutils.file" local dsys = require "lib/dtutils.system" -require "official/yield" --Detect OS and modify accordingly-- -local proper_install = 0 +local proper_install = false if dt.configuration.running_os == "windows" then - proper_install = 1 + proper_install = true end -- FUNCTION -- local function OpenInExplorer() --Open in Explorer --Inits-- - if proper_install ~= 1 then + if not proper_install then return end local images = dt.gui.selection() @@ -62,19 +60,10 @@ local function OpenInExplorer() --Open in Explorer end -- GUI -- -OpenInExplorer_btn_run = dt.new_widget("button"){ - label = "Go to Folder", - tooltip = "Opens selected image(s) location(s) in file explorer", - clicked_callback = function() OpenInExplorer() end - } -dt.register_lib( --OpenInExplorer - "OpenInExplorer_Lib", -- Module name - "Open In Explorer", -- name - true, -- expandable - false, -- resetable - {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers - dt.new_widget("box"){ - orientation = "vertical", - OpenInExplorer_btn_run - } -) \ No newline at end of file +if proper_install then + dt.gui.libs.image.register_action( + "show in file explorer", + function() OpenInExplorer() end, + "Opens File Explorer at the selecte image's location" + ) +end From 2cb3397d759c1850fd343c4d9898c84a848f87af Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 15 Feb 2019 13:22:49 -0500 Subject: [PATCH 12/61] Update HDRMerge Now only checks for binary proper install once the functionality of the program is actually utilized. This avoids to many external command calls at dt startup just to check if certain things are installed. --- contrib/HDRMerge.lua | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 9829be1..4300b6c 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -52,14 +52,9 @@ local dsys = require "lib/dtutils.system" require "official/yield" --Check if HDRMerge is installed-- -local not_installed = 0 -dt.print_log("HDRMerge - Executable Path Preference: "..df.get_executable_path_preference("HDRMerge")) -local HDRMerge_path = df.check_if_bin_exists("HDRMerge") -if not HDRMerge_path then - dt.print_error("HDRMerge not found") - dt.print("ERROR - HDRMerge not found") - not_installed = 1 -end +local first_run = true +local not_installed = false +local HDRMerge_path --Detect OS and modify accordingly-- local os_path_seperator = "/" @@ -101,7 +96,16 @@ local function build_execute_command(cmd, args, file_list) return result end local function HDRMerge() - if not_installed == 1 then + if first_run then + HDRMerge_path = df.check_if_bin_exists("HDRMerge") + if not HDRMerge_path then + dt.print_error("HDRMerge not found") + dt.print("ERROR - HDRMerge not found") + not_installed = true + end + first_run = false + end + if not_installed then dt.print("Required software not found") dt.print_log("Required software not found - HDRMerge did not run") return @@ -350,15 +354,6 @@ dt.preferences.register("module_HDRMerge", "bits_per_sample", -- name "16", -- default "24","32" --value ) - -if not HDRMerge_path then - HDRMerge_path = "" -end -HDRMerge_path_widget = dt.new_widget("file_chooser_button"){ - title = "Select HDRMerge[.exe] file", - value = HDRMerge_path, - is_directory = false, -} dt.preferences.register("executable_paths", "HDRMerge", -- name "file", -- type 'HDRMerge: Binary Location', -- label From d1676ac2e0281470bf9437d201eb30cf92122421 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 15 Feb 2019 13:40:03 -0500 Subject: [PATCH 13/61] Update OpenInExplorer Added: 1) Check if no image selected or more than 15 images selected 2) Popup error message if not a windows OS detected 3) Fixed a typo --- contrib/OpenInExplorer.lua | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 8a9e52f..9330ad8 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -40,6 +40,8 @@ local dsys = require "lib/dtutils.system" local proper_install = false if dt.configuration.running_os == "windows" then proper_install = true +else + dt.print_error('OpenInExplorer plug-in only supports Windows OS at this time') end -- FUNCTION -- @@ -50,12 +52,17 @@ local function OpenInExplorer() --Open in Explorer end local images = dt.gui.selection() local curr_image = "" - - for _,image in pairs(images) do - curr_image = image.path..'\\'..image.filename - local run_cmd = "explorer.exe /select, "..curr_image - dt.print_log("OpenInExplorer run_cmd = "..run_cmd) - resp = dsys.external_command(run_cmd) + if #images == 0 then + dt.print('please select an image') + elseif #images <= 15 then + for _,image in pairs(images) do + curr_image = image.path..'\\'..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 @@ -64,6 +71,6 @@ if proper_install then dt.gui.libs.image.register_action( "show in file explorer", function() OpenInExplorer() end, - "Opens File Explorer at the selecte image's location" + "Opens File Explorer at the selected image's location" ) end From b682284a2a8433ec856a52601b26d8b945df9778 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 15 Feb 2019 14:08:04 -0500 Subject: [PATCH 14/61] Update with 'local' variable designation Updated variable definitions with 'local' designation. Doing so slightly broke the script so I modified the function calls a tad to pass variables in and out as needed for "CollectOnAll" function to work. --- contrib/CollectHelper.lua | 53 +++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 27 deletions(-) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index fabe4c9..3684686 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -46,30 +46,29 @@ In the "Selected Images" module click on "Collect on this Image" ]] local dt = require "darktable" -previous = nil -all_active = false +local previous = nil +local all_active = false -- FUNCTION -- local function CheckSingleImage(selection) if #selection ~= 1 then dt.print("Please select a single image") - return 1 + return true end - return 0 + return false end local function PreviousCollection() if previous ~= nil then previous = dt.gui.libs.collect.filter(previous) end end -local function CollectOnFolder() +local function CollectOnFolder(all_rules, all_active) local images = dt.gui.selection() - if CheckSingleImage(images) == 1 then + if CheckSingleImage(images) then return end - rules = {} - all_rules = {} - rule = dt.gui.libs.collect.new_rule() + 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" @@ -78,47 +77,48 @@ local function CollectOnFolder() 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() +local function CollectOnColors(all_rules, all_active) local images = dt.gui.selection() - if CheckSingleImage(images) == 1 then + if CheckSingleImage(images) then return end for _,image in pairs(images) do - rules = {} + local rules = {} if image.red then - red_rule = dt.gui.libs.collect.new_rule() + 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 - blue_rule = dt.gui.libs.collect.new_rule() + 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 - green_rule = dt.gui.libs.collect.new_rule() + 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 - yellow_rule = dt.gui.libs.collect.new_rule() + 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 - purple_rule = dt.gui.libs.collect.new_rule() + 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" @@ -128,6 +128,7 @@ local function CollectOnColors() 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 @@ -135,19 +136,17 @@ local function CollectOnColors() end local function CollectOnAll_AND() local images = dt.gui.selection() - if CheckSingleImage(images) == 1 then + if CheckSingleImage(images) then return end - all_rules = {} - all_active = true + local rules = {} if dt.preferences.read('module_CollectHelper','folder','bool') then - CollectOnFolder() + rules = CollectOnFolder(rules, true) end if dt.preferences.read('module_CollectHelper','colors','bool') then - CollectOnColors() + rules = CollectOnColors(rules, true) end - all_active = false - previous = dt.gui.libs.collect.filter(all_rules) + previous = dt.gui.libs.collect.filter(rules) end -- GUI -- @@ -159,14 +158,14 @@ dt.gui.libs.image.register_action( if dt.preferences.read('module_CollectHelper','folder','bool') then dt.gui.libs.image.register_action( "collect: folder", - function() CollectOnFolder() end, + 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() end, + function() CollectOnColors(_ , false) end, "Sets the Collect parameters to be the selected images's color label(s)" ) end @@ -196,4 +195,4 @@ dt.preferences.register("module_CollectHelper", "folder", -- name 'CollectHelper: Folder', -- label 'Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location', -- tooltip true -- default -) \ No newline at end of file +) From 5fd3a56a3181b16dbcd381076b2effbabf9a98b3 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 15 Feb 2019 14:24:51 -0500 Subject: [PATCH 15/61] Remove extraneous widgets in preferences I now understand how preferences work better and have removed the extraneous widgets which were doing nothing --- contrib/HDRMerge.lua | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 4300b6c..9614f49 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -308,31 +308,17 @@ dt.register_lib( ) -- PREFERENCES -- -HDRMerge_entry_widget_tags = dt.new_widget("entry"){ - tooltip = "Seperate with commas, all spaces will be removed", - text = nil, - placeholder = "Enter default tags", - editable = true -} dt.preferences.register("module_HDRMerge", "add_tags", -- name "string", -- type 'HDRMerge: Defualt additional tags', -- label 'Changes DEFAULT entry in the additional tags option. Requires restart to take effect.', -- tooltip - "", -- default - HDRMerge_entry_widget_tags + "" -- default ) -HDRMerge_entry_widget_style = dt.new_widget("entry"){ - tooltip = "Enter the style name exactly as it is", - text = nil, - placeholder = "Enter Style name", - editable = true -} dt.preferences.register("module_HDRMerge", "style", -- name "string", -- type 'HDRMerge: Defualt Style', -- label 'Changes DEFAULT entry in the Style option. Requires restart to take effect.', -- tooltip - "", -- default - HDRMerge_entry_widget_style + "" -- default ) dt.preferences.register("module_HDRMerge", "copy_tags", -- name "bool", -- type @@ -358,6 +344,5 @@ dt.preferences.register("executable_paths", "HDRMerge", -- name "file", -- type 'HDRMerge: Binary Location', -- label 'Install location of HDRMerge[.exe]. Requires restart to take effect.', -- tooltip - "HDRMerge", -- default - HDRMerge_path_widget + "HDRMerge" -- default ) From 130a8edcef03309de653c50451fb3c1756dc800d Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 15 Feb 2019 20:23:07 -0500 Subject: [PATCH 16/61] Cancel script if not windows added return to cancel installation of the script if not windows, after displaying a message to user --- contrib/OpenInExplorer.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 9330ad8..d7ea877 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -42,6 +42,7 @@ if dt.configuration.running_os == "windows" then proper_install = true else dt.print_error('OpenInExplorer plug-in only supports Windows OS at this time') + return end -- FUNCTION -- From 451c7fb956c3ec38e582d76b5e3db76fda3b9c79 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Mon, 18 Feb 2019 19:46:11 -0500 Subject: [PATCH 17/61] Added check_min_api_function to dtutils.lua to check the minimum version of the lua API required for a script to run. This is intended to replace the API check done with darktable.configuration.check_version(), which requires changing each time the API version changes. check_min_api_version should work for all API version changes unless backward compatibility is broken. All the scripts using darktable.configuration.check_version() were changed to use check_min_api_version(). --- contrib/LabelsToTags.lua | 7 +++++- contrib/OpenInExplorer.lua | 7 ++++++ contrib/autostyle.lua | 6 ++++- contrib/clear_GPS.lua | 6 ++++- contrib/copy_attach_detach_tags.lua | 7 +++++- contrib/cr2hdr.lua | 6 ++++- contrib/enfuseAdvanced.lua | 6 ++++- contrib/fujifilm_ratings.lua | 6 ++++- contrib/geoJSON_export.lua | 6 ++++- contrib/geoToolbox.lua | 6 ++++- contrib/gimp.lua | 6 ++++- contrib/gpx_export.lua | 5 ++++- contrib/hugin.lua | 6 ++++- contrib/image_stack.lua | 5 ++++- contrib/kml_export.lua | 6 ++++- contrib/passport_guide.lua | 6 ++++- contrib/pdf_slideshow.lua | 6 ++++- contrib/quicktag.lua | 6 ++++- contrib/rate_group.lua | 6 ++++- contrib/rename-tags.lua | 7 ++++++ contrib/select_untagged.lua | 6 ++++- contrib/slideshowMusic.lua | 6 ++++- contrib/video_mencoder.lua | 6 ++++- examples/gettextExample.lua | 8 ++++++- examples/hello_world.lua | 7 +++++- examples/moduleExample.lua | 6 ++++- examples/multi_os.lua | 12 ++++++---- examples/preferenceExamples.lua | 7 +++++- examples/printExamples.lua | 7 +++++- examples/running_os.lua | 7 +++++- lib/dtutils.lua | 35 ++++++++++++++++++++++++++++- lib/dtutils/file.lua | 5 ++++- official/check_for_updates.lua | 6 ++++- official/copy_paste_metadata.lua | 6 ++++- official/delete_long_tags.lua | 7 +++++- official/delete_unused_tags.lua | 6 ++++- official/enfuse.lua | 6 ++++- official/generate_image_txt.lua | 6 ++++- official/image_path_in_ui.lua | 7 +++++- official/save_selection.lua | 7 +++++- official/selection_to_pdf.lua | 6 ++++- tools/get_lib_manpages.lua | 5 ++++- tools/get_libdoc.lua | 6 ++++- tools/script_manager.lua | 5 ++++- 44 files changed, 262 insertions(+), 45 deletions(-) diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 16f9996..15a44f3 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -48,7 +48,12 @@ ]] local darktable = require("darktable") -darktable.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +local du = require "lib/dtutils" + +if not du.check_min_api_version("3.0.0") then + darktable.print("ERROR:LabelsToTags failed to load. Lua API version 3.0.0 or later required.") + return +end -- Lua 5.3 no longer has "unpack" but "table.unpack" unpack = unpack or table.unpack diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index d7ea877..b223c71 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -34,8 +34,15 @@ A file explorer window will be opened for each selected file at the file's locat ]] local dt = require "darktable" +local du = require "lib/dtutils" local dsys = require "lib/dtutils.system" +--Check API version +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:OpenInExplorer failed to load. Lua API version 5.0.0 or greater required.") + return +end + --Detect OS and modify accordingly-- local proper_install = false if dt.configuration.running_os == "windows" then diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 77c04cb..34b95f0 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -37,13 +37,17 @@ 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}) +if not du.check_min_api_version("2.0.2") then + darktable.print("ERROR:autostyle failed to load. Lua API version 2.0.2 or later required.") + return +end -- Receive the event triggered function autostyle_apply_one_image_event(event,image) diff --git a/contrib/clear_GPS.lua b/contrib/clear_GPS.lua index 2a84a61..5d3fdf7 100644 --- a/contrib/clear_GPS.lua +++ b/contrib/clear_GPS.lua @@ -37,12 +37,16 @@ ]] 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:clear_GPS failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index d7dab8a..3f60d9e 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -37,10 +37,15 @@ 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}) + +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:copy_attach_detach_tags failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/cr2hdr.lua b/contrib/cr2hdr.lua index a308ffa..5705751 100644 --- a/contrib/cr2hdr.lua +++ b/contrib/cr2hdr.lua @@ -33,9 +33,13 @@ 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}) +if not du.check_min_api_version("2.0.0") then + darktable.print("ERROR:cr2hdr failed to load. Lua API version 2.0.0 or later required.") + return +end local queue = {} local processed_files = {} diff --git a/contrib/enfuseAdvanced.lua b/contrib/enfuseAdvanced.lua index 7c1edd3..377db49 100644 --- a/contrib/enfuseAdvanced.lua +++ b/contrib/enfuseAdvanced.lua @@ -60,13 +60,17 @@ Pops up multiple CMD windows on windows machienes ]] 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 local preferences_version = 1 --When releasing an update increment this number by one if changes have been made to the preferences structure that would require a re-initialization -- works with LUA API version 5.0.0 -dt.configuration.check_version(...,{5,0,0}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:enfuseAdvanced failed to load. Lua API version 5.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("enfuseAdvanced",dt.configuration.config_dir.."/lua/") diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index 38b3c21..294623e 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -24,10 +24,14 @@ 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}) +if not du.check_min_api_version("4.0.0") then + dt.print("ERROR:fujifilm_ratings failed to load. Lua API version 4.0.0 or later required.") + return +end gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 6fc76fa..99edc31 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -33,11 +33,15 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:geoJSON_export failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 8a28001..0092289 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -26,11 +26,15 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:geoToolbox failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gimp.lua b/contrib/gimp.lua index e7df43b..0210e01 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -64,12 +64,16 @@ ]] 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}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:gimp failed to load. Lua API version 5.0.0 or greater required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index 0518931..b137d58 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -27,7 +27,10 @@ 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}) +if not dl.check_min_api_version("3.0.0") then + dt.print("ERROR:gpx-export failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 90e5938..8b9887d 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -36,6 +36,7 @@ This plugin will add a new storage option and calls hugin after export. ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" @@ -54,7 +55,10 @@ 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}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:hugin failed to load. Lua API version 5.0.0 or greater required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index fd03fb6..ebf87dd 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -71,7 +71,10 @@ 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}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:image_stack failed to load. Lua API version 5.0.0 or greater required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 070a02f..2b932da 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -37,11 +37,15 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:kml_export failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("kml_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/passport_guide.lua b/contrib/passport_guide.lua index 48ae64f..442a28f 100644 --- a/contrib/passport_guide.lua +++ b/contrib/passport_guide.lua @@ -36,9 +36,13 @@ 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}) +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:passport_guide failed to load. Lua API version 2.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index d5a71d4..eae9141 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -39,6 +39,7 @@ format (all fields can be the empty string): ]] local dt = require "darktable" +local du = require "lib/dtutils" local df = require "lib/dtutils.file" require "official/yield" @@ -56,7 +57,10 @@ if not df.check_if_bin_exists("pdflatex") then return end -dt.configuration.check_version(...,{4,0,0},{5,0,0}) +if not du.check_min_api_version("4.0.0") then + dt.print("ERROR:pdf_slideshow failed to load. Lua API version 4.0.0 or greater required.") + return +end dt.preferences.register ("pdf_slideshow","open with","string", diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index c5ade26..cfc072c 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -44,12 +44,16 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:quicktag failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 3a557a8..8d5019f 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -39,9 +39,13 @@ ]] 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:rate_group failed to load. Lua API version 3.0.0 or later required.") + return +end local function apply_rating(rating) local images = dt.gui.action_images diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index 217357b..c839b6b 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -29,8 +29,15 @@ Changes ]] local darktable = require "darktable" +local du = require "lib/dtutils" local debug = require "darktable.debug" +-- check API version +if not du.check_min_api_version("3.0.0") then + darktable.print("ERROR:rename_tags failed to load. Lua API version 3.0.0 or later required.") + return +end + -- GUI entries local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" } local new_tag = darktable.new_widget("entry") { tooltip = "Enter new tag" } diff --git a/contrib/select_untagged.lua b/contrib/select_untagged.lua index 9e504c0..8ab4508 100644 --- a/contrib/select_untagged.lua +++ b/contrib/select_untagged.lua @@ -19,9 +19,13 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:select_untagged failed to load. Lua API version 3.0.0 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index 2da53dc..5563617 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -25,11 +25,15 @@ 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}) +if not du.check_min_api_version("2.0.2") then + dt.print("ERROR:slideshowMusic failed to load. Lua API version 2.0.2 or greater required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/video_mencoder.lua b/contrib/video_mencoder.lua index 3f39587..ec90ffc 100644 --- a/contrib/video_mencoder.lua +++ b/contrib/video_mencoder.lua @@ -31,11 +31,15 @@ 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}) +if not du.check_min_api_version("2.0.1") then + dt.print("ERROR:video_mencoder failed to load. Lua API version 2.0.1 or later required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("video_mencoder",dt.configuration.config_dir.."/lua/locale/") diff --git a/examples/gettextExample.lua b/examples/gettextExample.lua index 856a825..788911e 100644 --- a/examples/gettextExample.lua +++ b/examples/gettextExample.lua @@ -53,7 +53,13 @@ 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 +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:gettextExample failed to load. Lua API version 3.0.0 or greater required.") + return +end -- Not translated Text dt.print_error("Hello World!") diff --git a/examples/hello_world.lua b/examples/hello_world.lua index 6a19880..23d9027 100644 --- a/examples/hello_world.lua +++ b/examples/hello_world.lua @@ -27,7 +27,12 @@ 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" + +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:hello_world failed to load. Lua API version 2.0.0 or greater required.") + return +end dt.print("hello, world") diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index eda8748..4dec3fd 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -31,8 +31,12 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:moduleExample failed to load. Lua API version 3.0.0 or greater required.") + return +end -- add a new lib local check_button = dt.new_widget("check_button"){label = "MyCheck_button", value = true} diff --git a/examples/multi_os.lua b/examples/multi_os.lua index 0371768..ac429a8 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -80,12 +80,16 @@ 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}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:mulit_os failed to load: Lua API version 5.0.0 or greater required.") + return +end --[[ copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image diff --git a/examples/preferenceExamples.lua b/examples/preferenceExamples.lua index 7ac1cfd..5abb76a 100644 --- a/examples/preferenceExamples.lua +++ b/examples/preferenceExamples.lua @@ -22,7 +22,12 @@ 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" + +if not du.check_min_api_version("2.0.1") then + dt.print("ERROR:preferenceExamples failed to load. Lua API version 2.0.1 or greater required.") + return +end dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesString", -- name diff --git a/examples/printExamples.lua b/examples/printExamples.lua index 5ef3695..a37e9e1 100644 --- a/examples/printExamples.lua +++ b/examples/printExamples.lua @@ -20,7 +20,12 @@ 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" + +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:printExamples failed to load. Lua API version 5.0.0 or greater required.") + return +end -- Will print a string to the darktable control log (the long -- overlayed window that appears over the main panel). diff --git a/examples/running_os.lua b/examples/running_os.lua index a615fa9..0879a73 100644 --- a/examples/running_os.lua +++ b/examples/running_os.lua @@ -26,7 +26,12 @@ prints the operating system ]] local dt = require "darktable" -dt.configuration.check_version(...,{5,0,0}) +local du = require "lib/dtutils" + +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:running_os not loaded. Lua API version 5.0.0 or greater required.") + return +end dt.print("You are running: "..dt.configuration.running_os) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index abe51d6..fc077be 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -32,7 +32,40 @@ 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) + min_api - string - the api version that the application was written for (example: "5.0.0")]], + 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 to the log. + + 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 = [[]], + Example = [[check_min_api_version("5.0.0") returns true if the api is greater or equal to 5.0.0 otherwise an + error message is printed to the log and false is returned.]], + See_Also = [[]], + Reference = [[]], + License = [[]], + Copyright = [[]], +} + +function dtutils.check_min_api_version(min_api) + local current_api = dt.configuration.api_version_string + if min_api <= current_api then + return true + else + 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) + return false + end +end dtutils.libdoc.functions["split"] = { Name = [[split]], diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 120b70f..5c4a114 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -1,5 +1,6 @@ local dtutils_file = {} local dt = require "darktable" +local du = require "lib/dtutils" local ds = require "lib/dtutils.string" local log = require "lib/dtutils.log" @@ -24,7 +25,9 @@ dtutils_file.libdoc = { local gettext = dt.gettext -dt.configuration.check_version(...,{5,0,0}) +if not du.check_min_api_version("5.0.0") then + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("dtutils.file",dt.configuration.config_dir.."/lua/locale/") diff --git a/official/check_for_updates.lua b/official/check_for_updates.lua index 221a660..cfcf914 100644 --- a/official/check_for_updates.lua +++ b/official/check_for_updates.lua @@ -28,10 +28,14 @@ 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}) +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:check_for_updates not loaded. Lua API version 2.0.0 or greater required.") + return +end -- compare two version strings of the form "major.minor.patch" -- returns -1, 0, 1 if the first version is smaller, equal, greater than the second version, diff --git a/official/copy_paste_metadata.lua b/official/copy_paste_metadata.lua index 4d1c70b..e417f61 100644 --- a/official/copy_paste_metadata.lua +++ b/official/copy_paste_metadata.lua @@ -26,8 +26,12 @@ USAGE ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:copy_paste_metadata not loaded. Lua API version 3.0.0 or greater required.") + return +end -- set this to "false" if you don't want to overwrite metadata fields -- (title, description, creator, publisher and rights) that are already set diff --git a/official/delete_long_tags.lua b/official/delete_long_tags.lua index 5a2da73..cf4ebfd 100644 --- a/official/delete_long_tags.lua +++ b/official/delete_long_tags.lua @@ -29,7 +29,12 @@ 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" + +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:delete_long_tags not loaded. Lua API version 2.0.0 or greater required.") + return +end dt.preferences.register("delete_long_tags", "length", "integer", "maximum length of tags to keep", diff --git a/official/delete_unused_tags.lua b/official/delete_unused_tags.lua index 337f5f6..8ceaa7e 100644 --- a/official/delete_unused_tags.lua +++ b/official/delete_unused_tags.lua @@ -27,8 +27,12 @@ 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}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:delete_unused_tags not loaded. Lua API version 5.0.0 or greater required.") + return +end -- deleting while iterating the tags list seems to break the iterator! local unused_tags = {} diff --git a/official/enfuse.lua b/official/enfuse.lua index 04dda4d..8f960f5 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -33,11 +33,15 @@ TODO ]] 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR:enfuse not loaded. Lua API version 3.0.0 or greater required.") + return +end -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("enfuse",dt.configuration.config_dir.."/lua/locale/") diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index 8dcd5d1..2e0d13b 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -35,10 +35,14 @@ 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}) +if not du.check_min_api_version("2.1.0") then + dt.print("ERROR:generate_image_txt not loaded. Lua API version 2.1.0 or greater required.") + return +end dt.preferences.register("generate_image_txt", "enabled", diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index b21a218..074bb6a 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -29,7 +29,12 @@ 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" + +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:image_path_in_ui not loaded. Lua API version 2.0.0 or greater required.") + return +end local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle", halign = "start"} diff --git a/official/save_selection.lua b/official/save_selection.lua index fd9f4f8..32555a2 100644 --- a/official/save_selection.lua +++ b/official/save_selection.lua @@ -34,7 +34,12 @@ 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" + +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:save_selection not loaded. Lua API version 2.0.0 or greater required.") + return +end local buffer_count = 5 diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index f44a8dc..dcc797f 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -34,9 +34,13 @@ 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}) +if not du.check_min_api_version("2.0.0") then + dt.print("ERROR:selection_to_pdf not loaded. Lua API version 2.0.0 or greater required.") + return +end dt.preferences.register ("selection_to_pdf","Open with","string", diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index cea3bbc..6e21bba 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -10,7 +10,10 @@ 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}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR: get_lib_manpages failed to load. Lua API version 3.0.0 or greater required.") + return +end local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 42b19e4..47689cf 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -5,8 +5,12 @@ ]] local dt = require "darktable" +local du = require "lib/dtutils" -dt.configuration.check_version(...,{3,0,0},{4,0,0},{5,0,0}) +if not du.check_min_api_version("3.0.0") then + dt.print("ERROR: get_libdoc failed to load. Lua API version 3.0.0 or greater required.") + return +end local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/script_manager.lua b/tools/script_manager.lua index d8484ea..d0aabd2 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -455,7 +455,10 @@ end -- api check -dt.configuration.check_version(...,{5,0,0}) +if not du.check_min_api_version("5.0.0") then + dt.print("ERROR:script_manager failed to load. Lua API version 5.0.0 or greater required.") + return +end -- set up tables to contain all the widgets and choices From 5555ee278eb960288509a4eb828f74477e6cef92 Mon Sep 17 00:00:00 2001 From: Dominik Markiewicz Date: Tue, 19 Feb 2019 11:35:02 +0100 Subject: [PATCH 18/61] file lib: mkdir - handle existing directory case on Windows --- lib/dtutils/file.lua | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 4015e3a..1beb191 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -573,8 +573,12 @@ dtutils_file.libdoc.functions["mkdir"] = { Copyright = [[]], } function dtutils_file.mkdir(path) - local mkdir_cmd = dt.configuration.running_os == "windows" and "mkdir" or "mkdir -p" - return dsys.external_command(mkdir_cmd.." "..dtutils_file.sanitize_filename(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.." "..dtutils_file.sanitize_filename(path)) + else + return 0 + end end dtutils_file.libdoc.functions["rm"] = { From 2a3d5328816ceda6a5399dfd5dc1d9357f59b728 Mon Sep 17 00:00:00 2001 From: Dominik Markiewicz Date: Tue, 19 Feb 2019 11:36:42 +0100 Subject: [PATCH 19/61] video ffmpeg: fix default macos video location; replace missing path separator by const --- contrib/video_ffmpeg.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index c5d390f..ffcf1ca 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -276,7 +276,7 @@ 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" + defaultVideoDir = os.getenv("home")..PS.."Videos" else handle = io.popen("xdg-user-dir VIDEOS") defaultVideoDir = handle:read() @@ -373,7 +373,7 @@ local function init_export(storage, img_format, images, high_quality, extra_data 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 .. "/"..MODULE_NAME .. "_" .. os.time() + 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 From 8b714edb6755c73289c352ceff5cd9a2b6995d01 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 20 Feb 2019 00:22:05 -0500 Subject: [PATCH 20/61] Changed check_min_api_version() to print error messages and throw an error if the minimum api is not satisfied. Updated the scripts to reflect the changed check_min_api_version(). --- contrib/LabelsToTags.lua | 5 +---- contrib/OpenInExplorer.lua | 5 +---- contrib/autostyle.lua | 5 +---- contrib/clear_GPS.lua | 5 +---- contrib/copy_attach_detach_tags.lua | 5 +---- contrib/cr2hdr.lua | 5 +---- contrib/enfuseAdvanced.lua | 5 +---- contrib/fujifilm_ratings.lua | 5 +---- contrib/geoJSON_export.lua | 5 +---- contrib/geoToolbox.lua | 5 +---- contrib/gimp.lua | 5 +---- contrib/gpx_export.lua | 5 +---- contrib/hugin.lua | 5 +---- contrib/image_stack.lua | 5 +---- contrib/kml_export.lua | 5 +---- contrib/passport_guide.lua | 5 +---- contrib/pdf_slideshow.lua | 5 +---- contrib/quicktag.lua | 5 +---- contrib/rate_group.lua | 5 +---- contrib/rename-tags.lua | 5 +---- contrib/select_untagged.lua | 5 +---- contrib/slideshowMusic.lua | 5 +---- contrib/video_mencoder.lua | 5 +---- examples/gettextExample.lua | 5 +---- examples/hello_world.lua | 5 +---- examples/moduleExample.lua | 5 +---- examples/multi_os.lua | 5 +---- examples/preferenceExamples.lua | 5 +---- examples/printExamples.lua | 5 +---- examples/running_os.lua | 5 +---- lib/dtutils.lua | 30 +++++++++++++++-------------- lib/dtutils/file.lua | 4 +--- official/check_for_updates.lua | 5 +---- official/copy_paste_metadata.lua | 5 +---- official/delete_long_tags.lua | 5 +---- official/delete_unused_tags.lua | 5 +---- official/enfuse.lua | 5 +---- official/generate_image_txt.lua | 5 +---- official/image_path_in_ui.lua | 5 +---- official/save_selection.lua | 5 +---- official/selection_to_pdf.lua | 5 +---- tools/executable_manager.lua | 2 ++ tools/gen_i18n_mo.lua | 2 ++ tools/get_lib_manpages.lua | 5 +---- tools/get_libdoc.lua | 5 +---- tools/plugin_manager.lua | 1 + tools/script_manager.lua | 5 +---- 47 files changed, 64 insertions(+), 185 deletions(-) create mode 120000 tools/plugin_manager.lua diff --git a/contrib/LabelsToTags.lua b/contrib/LabelsToTags.lua index 15a44f3..044e920 100644 --- a/contrib/LabelsToTags.lua +++ b/contrib/LabelsToTags.lua @@ -50,10 +50,7 @@ local darktable = require("darktable") local du = require "lib/dtutils" -if not du.check_min_api_version("3.0.0") then - darktable.print("ERROR:LabelsToTags failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "LabelsToTags") -- Lua 5.3 no longer has "unpack" but "table.unpack" unpack = unpack or table.unpack diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index b223c71..380838b 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -38,10 +38,7 @@ local du = require "lib/dtutils" local dsys = require "lib/dtutils.system" --Check API version -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:OpenInExplorer failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "OpenInExplorer") --Detect OS and modify accordingly-- local proper_install = false diff --git a/contrib/autostyle.lua b/contrib/autostyle.lua index 34b95f0..0b3891f 100644 --- a/contrib/autostyle.lua +++ b/contrib/autostyle.lua @@ -44,10 +44,7 @@ local filelib = require "lib/dtutils.file" 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 -if not du.check_min_api_version("2.0.2") then - darktable.print("ERROR:autostyle failed to load. Lua API version 2.0.2 or later required.") - return -end +du.check_min_api_version("2.0.2", "autostyle") -- Receive the event triggered function autostyle_apply_one_image_event(event,image) diff --git a/contrib/clear_GPS.lua b/contrib/clear_GPS.lua index 5d3fdf7..3172495 100644 --- a/contrib/clear_GPS.lua +++ b/contrib/clear_GPS.lua @@ -43,10 +43,7 @@ local gettext = dt.gettext -- not a number local NaN = 0/0 -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:clear_GPS failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "clear_GPS") -- Tell gettext where to find the .mo file translating messages for a particular domain diff --git a/contrib/copy_attach_detach_tags.lua b/contrib/copy_attach_detach_tags.lua index 3f60d9e..2fb8330 100644 --- a/contrib/copy_attach_detach_tags.lua +++ b/contrib/copy_attach_detach_tags.lua @@ -42,10 +42,7 @@ local debug = require "darktable.debug" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:copy_attach_detach_tags failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "copy_attach_detach_tags") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("copy_attach_detach_tags",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/cr2hdr.lua b/contrib/cr2hdr.lua index 5705751..7f7a5bc 100644 --- a/contrib/cr2hdr.lua +++ b/contrib/cr2hdr.lua @@ -36,10 +36,7 @@ local darktable = require "darktable" local du = require "lib/dtutils" -- Tested with darktable 2.0.1 -if not du.check_min_api_version("2.0.0") then - darktable.print("ERROR:cr2hdr failed to load. Lua API version 2.0.0 or later required.") - return -end +du.check_min_api_version("2.0.0", "cr2hdr") local queue = {} local processed_files = {} diff --git a/contrib/enfuseAdvanced.lua b/contrib/enfuseAdvanced.lua index 377db49..f24a6d0 100644 --- a/contrib/enfuseAdvanced.lua +++ b/contrib/enfuseAdvanced.lua @@ -67,10 +67,7 @@ local gettext = dt.gettext local preferences_version = 1 --When releasing an update increment this number by one if changes have been made to the preferences structure that would require a re-initialization -- works with LUA API version 5.0.0 -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:enfuseAdvanced failed to load. Lua API version 5.0.0 or later required.") - return -end +du.check_min_api_version("5.0.0", "enfuseAdvanced") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("enfuseAdvanced",dt.configuration.config_dir.."/lua/") diff --git a/contrib/fujifilm_ratings.lua b/contrib/fujifilm_ratings.lua index 294623e..6cf92c9 100644 --- a/contrib/fujifilm_ratings.lua +++ b/contrib/fujifilm_ratings.lua @@ -28,10 +28,7 @@ local du = require "lib/dtutils" local df = require "lib/dtutils.file" local gettext = dt.gettext -if not du.check_min_api_version("4.0.0") then - dt.print("ERROR:fujifilm_ratings failed to load. Lua API version 4.0.0 or later required.") - return -end +du.check_min_api_version("4.0.0", "fujifilm_ratings") gettext.bindtextdomain("fujifilm_ratings", dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoJSON_export.lua b/contrib/geoJSON_export.lua index 99edc31..4834bb0 100644 --- a/contrib/geoJSON_export.lua +++ b/contrib/geoJSON_export.lua @@ -38,10 +38,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:geoJSON_export failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "geoJSON_export") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoJSON_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/geoToolbox.lua b/contrib/geoToolbox.lua index 0092289..19e3c7c 100644 --- a/contrib/geoToolbox.lua +++ b/contrib/geoToolbox.lua @@ -31,10 +31,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:geoToolbox failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "geoToolbox") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("geoToolbox",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gimp.lua b/contrib/gimp.lua index 0210e01..d7528d1 100644 --- a/contrib/gimp.lua +++ b/contrib/gimp.lua @@ -70,10 +70,7 @@ local dtsys = require "lib/dtutils.system" local gettext = dt.gettext local gimp_widget = nil -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:gimp failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "gimp") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/gpx_export.lua b/contrib/gpx_export.lua index b137d58..b6fed82 100644 --- a/contrib/gpx_export.lua +++ b/contrib/gpx_export.lua @@ -27,10 +27,7 @@ local df = require "lib/dtutils.file" local dl = require "lib/dtutils" local gettext = dt.gettext -if not dl.check_min_api_version("3.0.0") then - dt.print("ERROR:gpx-export failed to load. Lua API version 3.0.0 or later required.") - return -end +dl.check_min_api_version("3.0.0", "gpx-export") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("gpx_export",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/hugin.lua b/contrib/hugin.lua index 8b9887d..83ec3be 100644 --- a/contrib/hugin.lua +++ b/contrib/hugin.lua @@ -55,10 +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 -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:hugin failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "hugin") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("hugin",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/image_stack.lua b/contrib/image_stack.lua index ebf87dd..de3e0c0 100644 --- a/contrib/image_stack.lua +++ b/contrib/image_stack.lua @@ -71,10 +71,7 @@ local job = nil local PS = dt.configuration.running_os == "windows" and "\\" or "/" -- works with LUA API version 5.0.0 -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:image_stack failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "image_stack") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("image_stack",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 2b932da..0f83f6d 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -42,10 +42,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:kml_export failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.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/") diff --git a/contrib/passport_guide.lua b/contrib/passport_guide.lua index 442a28f..72611eb 100644 --- a/contrib/passport_guide.lua +++ b/contrib/passport_guide.lua @@ -39,10 +39,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local gettext = dt.gettext -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:passport_guide failed to load. Lua API version 2.0.0 or later required.") - return -end +du.check_min_api_version("2.0.0", "passport_guide") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("passport_guide",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/pdf_slideshow.lua b/contrib/pdf_slideshow.lua index eae9141..f01b014 100644 --- a/contrib/pdf_slideshow.lua +++ b/contrib/pdf_slideshow.lua @@ -57,10 +57,7 @@ if not df.check_if_bin_exists("pdflatex") then return end -if not du.check_min_api_version("4.0.0") then - dt.print("ERROR:pdf_slideshow failed to load. Lua API version 4.0.0 or greater required.") - return -end +du.check_min_api_version("4.0.0", "pdf_slideshow") dt.preferences.register ("pdf_slideshow","open with","string", diff --git a/contrib/quicktag.lua b/contrib/quicktag.lua index cfc072c..31a4b79 100644 --- a/contrib/quicktag.lua +++ b/contrib/quicktag.lua @@ -50,10 +50,7 @@ local debug = require "darktable.debug" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:quicktag failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "quicktag") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("quicktag",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/rate_group.lua b/contrib/rate_group.lua index 8d5019f..d820d94 100644 --- a/contrib/rate_group.lua +++ b/contrib/rate_group.lua @@ -42,10 +42,7 @@ local dt = require "darktable" local du = require "lib/dtutils" -- added version check -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:rate_group failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "rate_group") local function apply_rating(rating) local images = dt.gui.action_images diff --git a/contrib/rename-tags.lua b/contrib/rename-tags.lua index c839b6b..a0c70be 100644 --- a/contrib/rename-tags.lua +++ b/contrib/rename-tags.lua @@ -33,10 +33,7 @@ local du = require "lib/dtutils" local debug = require "darktable.debug" -- check API version -if not du.check_min_api_version("3.0.0") then - darktable.print("ERROR:rename_tags failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "rename-tags") -- GUI entries local old_tag = darktable.new_widget("entry") { tooltip = "Enter old tag" } diff --git a/contrib/select_untagged.lua b/contrib/select_untagged.lua index 8ab4508..2db6dae 100644 --- a/contrib/select_untagged.lua +++ b/contrib/select_untagged.lua @@ -22,10 +22,7 @@ local dt = require "darktable" local du = require "lib/dtutils" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:select_untagged failed to load. Lua API version 3.0.0 or later required.") - return -end +du.check_min_api_version("3.0.0", "select_untagged") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("select_untagged",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/slideshowMusic.lua b/contrib/slideshowMusic.lua index 5563617..90d6bc3 100644 --- a/contrib/slideshowMusic.lua +++ b/contrib/slideshowMusic.lua @@ -30,10 +30,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("2.0.2") then - dt.print("ERROR:slideshowMusic failed to load. Lua API version 2.0.2 or greater required.") - return -end +du.check_min_api_version("2.0.2", "slideshowMusic") -- Tell gettext where to find the .mo file translating messages for a particular domain gettext.bindtextdomain("slideshowMusic",dt.configuration.config_dir.."/lua/locale/") diff --git a/contrib/video_mencoder.lua b/contrib/video_mencoder.lua index ec90ffc..1ad71d8 100644 --- a/contrib/video_mencoder.lua +++ b/contrib/video_mencoder.lua @@ -36,10 +36,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("2.0.1") then - dt.print("ERROR:video_mencoder failed to load. Lua API version 2.0.1 or later required.") - return -end +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/") diff --git a/examples/gettextExample.lua b/examples/gettextExample.lua index 788911e..c6eda49 100644 --- a/examples/gettextExample.lua +++ b/examples/gettextExample.lua @@ -56,10 +56,7 @@ local dt = require "darktable" local du = require "lib/dtutils" --check API version -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:gettextExample failed to load. Lua API version 3.0.0 or greater required.") - return -end +du.check_min_api_version("3.0.0", "gettextExample") -- Not translated Text dt.print_error("Hello World!") diff --git a/examples/hello_world.lua b/examples/hello_world.lua index 23d9027..11a287e 100644 --- a/examples/hello_world.lua +++ b/examples/hello_world.lua @@ -29,10 +29,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:hello_world failed to load. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "hello_world") dt.print("hello, world") diff --git a/examples/moduleExample.lua b/examples/moduleExample.lua index 4dec3fd..ecc2ac3 100644 --- a/examples/moduleExample.lua +++ b/examples/moduleExample.lua @@ -33,10 +33,7 @@ https://www.darktable.org/lua-api/index.html.php#darktable_new_widget local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:moduleExample failed to load. Lua API version 3.0.0 or greater required.") - return -end +du.check_min_api_version("3.0.0", "moduleExample") -- add a new lib local check_button = dt.new_widget("check_button"){label = "MyCheck_button", value = true} diff --git a/examples/multi_os.lua b/examples/multi_os.lua index ac429a8..96b60c2 100644 --- a/examples/multi_os.lua +++ b/examples/multi_os.lua @@ -86,10 +86,7 @@ end screen stating that you couldn't load because the minimum api version wasn't met. ]] -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:mulit_os failed to load: Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "multi_os") --[[ copy_image_attributes is a local subroutine to copy image attributes in the database from the raw image diff --git a/examples/preferenceExamples.lua b/examples/preferenceExamples.lua index 5abb76a..9710b95 100644 --- a/examples/preferenceExamples.lua +++ b/examples/preferenceExamples.lua @@ -24,10 +24,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("2.0.1") then - dt.print("ERROR:preferenceExamples failed to load. Lua API version 2.0.1 or greater required.") - return -end +du.check_min_api_version("2.0.1", "preferenceExamples") dt.preferences.register("preferenceExamples", -- script: This is a string used to avoid name collision in preferences (i.e namespace). Set it to something unique, usually the name of the script handling the preference. "preferenceExamplesString", -- name diff --git a/examples/printExamples.lua b/examples/printExamples.lua index a37e9e1..074a5db 100644 --- a/examples/printExamples.lua +++ b/examples/printExamples.lua @@ -22,10 +22,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:printExamples failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "printExamples") -- Will print a string to the darktable control log (the long -- overlayed window that appears over the main panel). diff --git a/examples/running_os.lua b/examples/running_os.lua index 0879a73..632c033 100644 --- a/examples/running_os.lua +++ b/examples/running_os.lua @@ -28,10 +28,7 @@ prints the operating system local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:running_os not loaded. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "running_os") dt.print("You are running: "..dt.configuration.running_os) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index fc077be..f64998d 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -37,33 +37,35 @@ dtutils.libdoc.functions["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) - min_api - string - the api version that the application was written for (example: "5.0.0")]], + 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 to the log. + 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 + 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 = [[]], - Example = [[check_min_api_version("5.0.0") returns true if the api is greater or equal to 5.0.0 otherwise an - error message is printed to the log and false is returned.]], + 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) +function dtutils.check_min_api_version(min_api, script_name) local current_api = dt.configuration.api_version_string - if min_api <= current_api then - return true - else + 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) - return false + 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 diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 5c4a114..69bf932 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -25,9 +25,7 @@ dtutils_file.libdoc = { local gettext = dt.gettext -if not du.check_min_api_version("5.0.0") then - return -end +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/") diff --git a/official/check_for_updates.lua b/official/check_for_updates.lua index cfcf914..712daab 100644 --- a/official/check_for_updates.lua +++ b/official/check_for_updates.lua @@ -32,10 +32,7 @@ local du = require "lib/dtutils" local https = require "ssl.https" local cjson = require "cjson" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:check_for_updates not loaded. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "check_for_updates") -- compare two version strings of the form "major.minor.patch" -- returns -1, 0, 1 if the first version is smaller, equal, greater than the second version, diff --git a/official/copy_paste_metadata.lua b/official/copy_paste_metadata.lua index e417f61..ca7ccae 100644 --- a/official/copy_paste_metadata.lua +++ b/official/copy_paste_metadata.lua @@ -28,10 +28,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:copy_paste_metadata not loaded. Lua API version 3.0.0 or greater required.") - return -end +du.check_min_api_version("3.0.0", "copy_paste_metadata") -- set this to "false" if you don't want to overwrite metadata fields -- (title, description, creator, publisher and rights) that are already set diff --git a/official/delete_long_tags.lua b/official/delete_long_tags.lua index cf4ebfd..9ab1a28 100644 --- a/official/delete_long_tags.lua +++ b/official/delete_long_tags.lua @@ -31,10 +31,7 @@ all tags longer than the given length will be automatically deleted at every res local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:delete_long_tags not loaded. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "delete_long_tags") dt.preferences.register("delete_long_tags", "length", "integer", "maximum length of tags to keep", diff --git a/official/delete_unused_tags.lua b/official/delete_unused_tags.lua index 8ceaa7e..4c11423 100644 --- a/official/delete_unused_tags.lua +++ b/official/delete_unused_tags.lua @@ -29,10 +29,7 @@ all tags that are not used will be automatically deleted at every restart local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:delete_unused_tags not loaded. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0", "delete_unused_tags") -- deleting while iterating the tags list seems to break the iterator! local unused_tags = {} diff --git a/official/enfuse.lua b/official/enfuse.lua index 8f960f5..2bc4161 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -38,10 +38,7 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR:enfuse not loaded. Lua API version 3.0.0 or greater required.") - return -end +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/") diff --git a/official/generate_image_txt.lua b/official/generate_image_txt.lua index 2e0d13b..a214a2a 100644 --- a/official/generate_image_txt.lua +++ b/official/generate_image_txt.lua @@ -39,10 +39,7 @@ local du = require "lib/dtutils" require "darktable.debug" require "official/yield" -if not du.check_min_api_version("2.1.0") then - dt.print("ERROR:generate_image_txt not loaded. Lua API version 2.1.0 or greater required.") - return -end +du.check_min_api_version("2.1.0", "generate_image_txt") dt.preferences.register("generate_image_txt", "enabled", diff --git a/official/image_path_in_ui.lua b/official/image_path_in_ui.lua index 074bb6a..18e57ca 100644 --- a/official/image_path_in_ui.lua +++ b/official/image_path_in_ui.lua @@ -31,10 +31,7 @@ This plugin will add a widget at the bottom of the left column in lighttable mod local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:image_path_in_ui not loaded. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "image_path_in_ui") local main_label = dt.new_widget("label"){selectable = true, ellipsize = "middle", halign = "start"} diff --git a/official/save_selection.lua b/official/save_selection.lua index 32555a2..1d71ea9 100644 --- a/official/save_selection.lua +++ b/official/save_selection.lua @@ -36,10 +36,7 @@ increase it if you need more temporary selection buffers local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:save_selection not loaded. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "save_selection") local buffer_count = 5 diff --git a/official/selection_to_pdf.lua b/official/selection_to_pdf.lua index dcc797f..f6fbfe8 100644 --- a/official/selection_to_pdf.lua +++ b/official/selection_to_pdf.lua @@ -37,10 +37,7 @@ local dt = require "darktable" local du = require "lib/dtutils" require "official/yield" -if not du.check_min_api_version("2.0.0") then - dt.print("ERROR:selection_to_pdf not loaded. Lua API version 2.0.0 or greater required.") - return -end +du.check_min_api_version("2.0.0", "selection_to_pdf") dt.preferences.register ("selection_to_pdf","Open with","string", diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 4c8f68d..5f79074 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -32,6 +32,8 @@ local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +du.check_min_api_version("5.0.0", "executable_manager") + local PS = dt.configuration.running_os == "windows" and "\\" or "/" local gettext = dt.gettext diff --git a/tools/gen_i18n_mo.lua b/tools/gen_i18n_mo.lua index d3b5b5b..d032484 100644 --- a/tools/gen_i18n_mo.lua +++ b/tools/gen_i18n_mo.lua @@ -30,6 +30,8 @@ local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local dtsys = require "lib/dtutils.system" +du.check_min_api_version("5.0.0", "gen_I18n_mo") + -- figure out the path separator local PS = dt.configuration.running_os == "windows" and "\\" or "/" diff --git a/tools/get_lib_manpages.lua b/tools/get_lib_manpages.lua index 6e21bba..9ea2f69 100644 --- a/tools/get_lib_manpages.lua +++ b/tools/get_lib_manpages.lua @@ -10,10 +10,7 @@ local df = require "lib/dtutils.file" local log = require "lib/dtutils.log" local libname = nil -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR: get_lib_manpages failed to load. Lua API version 3.0.0 or greater required.") - return -end +du.check_min_api_version("3.0.0", "get_lib_manpages") local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/get_libdoc.lua b/tools/get_libdoc.lua index 47689cf..ed0813a 100644 --- a/tools/get_libdoc.lua +++ b/tools/get_libdoc.lua @@ -7,10 +7,7 @@ local dt = require "darktable" local du = require "lib/dtutils" -if not du.check_min_api_version("3.0.0") then - dt.print("ERROR: get_libdoc failed to load. Lua API version 3.0.0 or greater required.") - return -end +du.check_min_api_version("3.0.0", "get_libdoc") local keys = {"Name", "Synopsis", "Usage", "Description", "Return_Value", "Limitations", "Example", "See_Also", "Reference", "License", "Copyright"} diff --git a/tools/plugin_manager.lua b/tools/plugin_manager.lua new file mode 120000 index 0000000..bc0e1bf --- /dev/null +++ b/tools/plugin_manager.lua @@ -0,0 +1 @@ +/home/bill/src/plugins/lua-scripts/tools/plugin_manager.lua \ No newline at end of file diff --git a/tools/script_manager.lua b/tools/script_manager.lua index d0aabd2..e16b0cc 100644 --- a/tools/script_manager.lua +++ b/tools/script_manager.lua @@ -455,10 +455,7 @@ end -- api check -if not du.check_min_api_version("5.0.0") then - dt.print("ERROR:script_manager failed to load. Lua API version 5.0.0 or greater required.") - return -end +du.check_min_api_version("5.0.0") -- set up tables to contain all the widgets and choices From 245a7b59c4f15e1a5c83528fb7b0a5730617fd4d Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 20 Feb 2019 11:48:37 -0500 Subject: [PATCH 21/61] Incorporated supertobi's comment about spacing. --- lib/dtutils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/dtutils.lua b/lib/dtutils.lua index f64998d..e1d403e 100644 --- a/lib/dtutils.lua +++ b/lib/dtutils.lua @@ -64,7 +64,7 @@ function dtutils.check_min_api_version(min_api, script_name) 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.") + 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 From 76cc1d47f66c416ebaa876ab50f3a599d5bea231 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 21 Feb 2019 11:53:56 -0500 Subject: [PATCH 22/61] Added messages to video_mencoder.lua to notify users of it's deprecation and pending removal. --- contrib/video_mencoder.lua | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/contrib/video_mencoder.lua b/contrib/video_mencoder.lua index 1ad71d8..93b2792 100644 --- a/contrib/video_mencoder.lua +++ b/contrib/video_mencoder.lua @@ -50,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,\n + therefore video_mencoder is deprecated. video_ffmpeg should be used in it's place. video_mencoder\n + 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 From 8f6a236eb65bcdf62da7aaca647fbd0eac68e831 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 21 Feb 2019 23:49:52 -0500 Subject: [PATCH 23/61] Updated files --- contrib/video_ffmpeg.lua | 5 +++-- lib/dtutils/file.lua | 18 +++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index ffcf1ca..7c7f0bd 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -34,11 +34,12 @@ 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 -dt.configuration.check_version(...,{5,0,0}) +du.check_min_api_version("5.0.0") local MODULE_NAME = "video_ffmpeg" @@ -444,7 +445,7 @@ local function finalize_export(storage, images_table, extra_data) end end - df.rm(tmp_dir) + df.rmdir(tmp_dir) end dt.register_storage( diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index cff780b..6b5c5c8 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -582,15 +582,15 @@ function dtutils_file.mkdir(path) end end -dtutils_file.libdoc.functions["rm"] = { +dtutils_file.libdoc.functions["rmdir"] = { Name = [[rm]], - Synopsis = [[remove file or directory]], + Synopsis = [[recursively remove a directory]], Usage = [[local df = require "lib/dtutils.file" - df.rm(path) - path - string - a file or directory path]], - Description = [[rm allow to recursively remove files or directories]], - Return_Value = [[path - string - a file or directory path]], + df.rmdir(path) + path - string - a directory path]], + Description = [[rmdir recursively removes directories and any files contained within]], + Return_Value = [[path - string - a directory path]], Limitations = [[]], Example = [[]], See_Also = [[]], @@ -598,9 +598,9 @@ dtutils_file.libdoc.functions["rm"] = { License = [[]], Copyright = [[]], } -function dtutils_file.rm(path) - local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" - return dsys.external_command(rm_cmd.." "..dtutils_file.sanitize_filename(path)) +function dtutils_file.rmdir(path) + local rmdir_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" + return dsys.external_command(rmdir_cmd.." "..dtutils_file.sanitize_filename(path)) end From de087f743f5ef6c9964544e3bc33cc2b1b374a33 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Sun, 24 Feb 2019 15:19:37 -0500 Subject: [PATCH 24/61] Fixed compatibility issues. Added new version check to scripts. Renamed lib/dtutils/file/rm() to lib/dtutils/file/rmdir since removing a file in windows with rmdir /q/s results in an error. Fixed os.tmpname() issued in enfuse. Under windows os.tmpname() doesn't put the file in a temporary directory, so fixed it to use darktable.configuration.tmp_dir. Cleaned up documentation in lib/dtutils/system.lua. --- contrib/video_ffmpeg.lua | 2 +- lib/dtutils/file.lua | 11 +- lib/dtutils/system.lua | 11 +- official/enfuse.lua | 217 +++++++++++++++++++++------------------ 4 files changed, 130 insertions(+), 111 deletions(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 7c7f0bd..d561d55 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -279,7 +279,7 @@ if dt.configuration.running_os == "windows" then elseif dt.configuration.running_os == "macos" then defaultVideoDir = os.getenv("home")..PS.."Videos" else - handle = io.popen("xdg-user-dir VIDEOS") + local handle = io.popen("xdg-user-dir VIDEOS") defaultVideoDir = handle:read() handle:close() end diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 6b5c5c8..e9fc36f 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -582,14 +582,14 @@ function dtutils_file.mkdir(path) end end -dtutils_file.libdoc.functions["rmdir"] = { - Name = [[rm]], +dtutils_file.libdoc.functions["rm"] = { + Name = [[rmdir]], Synopsis = [[recursively remove a directory]], Usage = [[local df = require "lib/dtutils.file" df.rmdir(path) path - string - a directory path]], - Description = [[rmdir recursively removes directories and any files contained within]], + Description = [[rm allow to recursively remove directories]], Return_Value = [[path - string - a directory path]], Limitations = [[]], Example = [[]], @@ -598,9 +598,10 @@ dtutils_file.libdoc.functions["rmdir"] = { License = [[]], Copyright = [[]], } + function dtutils_file.rmdir(path) - local rmdir_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" - return dsys.external_command(rmdir_cmd.." "..dtutils_file.sanitize_filename(path)) + local rm_cmd = dt.configuration.running_os == "windows" and "rmdir /S /Q" or "rm -r" + return dsys.external_command(rm_cmd.." "..dtutils_file.sanitize_filename(path)) end diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index c40fc0c..418a59d 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -104,15 +104,14 @@ end dtutils_system.libdoc.functions["launch_default_app"] = { Name = [[launch_default_app]], - Synopsis = [[try to open file in default application]], + Synopsis = [[open file in default application]], Usage = [[local dsys = require "lib/dtutils.file" - dsys.launch_default_app(path) + result = dsys.launch_default_app(path) path - string - a file path]], - Description = [[launch_default_app allow to open file in application that is assigned as default - one in users's system for given filetype - ]], - Return_Value = [[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 = [[]], diff --git a/official/enfuse.lua b/official/enfuse.lua index fc6e2d6..e3b43f9 100644 --- a/official/enfuse.lua +++ b/official/enfuse.lua @@ -35,13 +35,16 @@ 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 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) @@ -61,11 +64,11 @@ tiff_exporter.max_width = 0 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) @@ -111,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 @@ -122,100 +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(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.."/"..i.filename.."\n") - target_dir = i.path - - -- alternatively raws will be exported as tiff - elseif i.is_raw then - local tmp_exported = os.tmpname()..".tiff" - dt.print(string.format(_("Converting raw file '%s' to tiff..."), i.filename)) - tiff_exporter.write_image(tiff_exporter, 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.."/enfuse.tiff" - 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 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 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 - } + table.unpack(lib_widgets) }, nil,-- view_enter nil -- view_leave From a5304fe74c6e90d207c98eb8ddc4140856d2f786 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 25 Feb 2019 09:48:50 +0100 Subject: [PATCH 25/61] First step to make kml_export.lua windows friendly Replaced "/" with PS like in other scripts --- contrib/kml_export.lua | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 0f83f6d..7ba7dbf 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -42,6 +42,8 @@ local df = require "lib/dtutils.file" require "official/yield" local gettext = dt.gettext +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + du.check_min_api_version("3.0.0", kml_export) -- Tell gettext where to find the .mo file translating messages for a particular domain @@ -260,8 +262,8 @@ local function create_kml_file(storage, image_table, extra_data) exportDirectory = dt.preferences.read("kml_export","ExportDirectory","string") -- Creates dir if not exsists - imageFoldername = "files/" - local mkdirCommand = "mkdir -p "..exportDirectory.."/"..imageFoldername + imageFoldername = "files"..PS + local mkdirCommand = "mkdir -p "..exportDirectory..PS..imageFoldername dt.control.execute(mkdirCommand) end @@ -280,7 +282,7 @@ local function create_kml_file(storage, image_table, extra_data) -- 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" + local convertToThumbCommand = "convert -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" dt.control.execute(convertToThumbCommand) else -- Remove exported image if it has no GPS data @@ -394,7 +396,7 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.."\n" kml_file = kml_file.."" - local file = io.open(exportDirectory.."/"..exportKMLFilename, "w") + local file = io.open(exportDirectory..PS..exportKMLFilename, "w") file:write(kml_file) file:close() @@ -405,8 +407,8 @@ local function create_kml_file(storage, image_table, extra_data) 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 @@ -416,7 +418,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 @@ -429,9 +431,9 @@ local function create_kml_file(storage, image_table, extra_data) local kmlFileOpenCommand if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then - kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMZFilename.."\"" + kmlFileOpenCommand = "xdg-open "..exportDirectory..PS.."\""..exportKMZFilename.."\"" else - kmlFileOpenCommand = "xdg-open "..exportDirectory.."/\""..exportKMLFilename.."\"" + kmlFileOpenCommand = "xdg-open "..exportDirectory..PS.."\""..exportKMLFilename.."\"" end dt.control.execute(kmlFileOpenCommand) end From a038333cce3187b684d09d442c95a62ecc7f91f6 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 25 Feb 2019 10:37:16 +0100 Subject: [PATCH 26/61] make kml_export multi OS replaced mkdir, xdg-open, xdg-user-dir --- contrib/kml_export.lua | 67 +++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 7ba7dbf..9da5f2b 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -20,11 +20,9 @@ darktable KML export script ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT -* mkdir -* zip (only if you create KMZ files) +* zip (at the moment Linux only and only if you create KMZ files) * convert (ImageMagick) -* xdg-open -* xdg-user-dir +* xdg-user-dir (Linux) WARNING This script is only tested with Linux @@ -39,6 +37,7 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local dsys = require "lib/dtutils.system" require "official/yield" local gettext = dt.gettext @@ -230,23 +229,16 @@ 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 + if dt.configuration.running_os == "windows" then + if not df.check_if_bin_exists("xdg-user-dir") then + dt.print_error(_("xdg-user-dir not found")) + return + end end - if not df.check_if_bin_exists("xdg-user-dir") then - dt.print_error(_("xdg-user-dir not found")) - return - end - dt.print_error("Will try to export KML file now") local imageFoldername @@ -263,8 +255,7 @@ local function create_kml_file(storage, image_table, extra_data) -- Creates dir if not exsists imageFoldername = "files"..PS - local mkdirCommand = "mkdir -p "..exportDirectory..PS..imageFoldername - dt.control.execute(mkdirCommand) + df.mkdir(exportDirectory..PS..imageFoldername) end -- Create the thumbnails @@ -428,14 +419,15 @@ 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..PS.."\""..exportKMZFilename.."\"" + path = exportDirectory..PS.."\""..exportKMZFilename.."\"" else - kmlFileOpenCommand = "xdg-open "..exportDirectory..PS.."\""..exportKMLFilename.."\"" + path = exportDirectory..PS.."\""..exportKMLFilename.."\"" end - dt.control.execute(kmlFileOpenCommand) + + dsys.launch_default_app(path) end end @@ -448,19 +440,24 @@ dt.preferences.register("kml_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 = "" +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 -handle:close() + 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 ) dt.preferences.register("kml_export", "CreatePath", @@ -469,12 +466,14 @@ 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) From 68a8eabbbbfc38c4cdcaa777713830715f63a506 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 25 Feb 2019 16:31:22 +0100 Subject: [PATCH 27/61] kml_export added preference for path to convert kml_export added preference for path to convert as windows programs are not by default in the PATH --- contrib/kml_export.lua | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 9da5f2b..595311c 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -229,11 +229,13 @@ function addDuplicateIndex( index, filename ) end local function create_kml_file(storage, image_table, extra_data) - if not df.check_if_bin_exists("convert") then + + local convertPath = dt.preferences.read("kml_export","convertPath","string") + if not df.check_if_bin_exists(convertPath) then dt.print_error(_("convert not found")) return end - if dt.configuration.running_os == "windows" then + 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 @@ -273,7 +275,7 @@ local function create_kml_file(storage, image_table, extra_data) -- 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..PS..imageFoldername.."thumb_"..filename..".jpg" + local convertToThumbCommand = convertPath .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" dt.control.execute(convertToThumbCommand) else -- Remove exported image if it has no GPS data @@ -459,6 +461,15 @@ dt.preferences.register("kml_export", _("A directory that will be used to export the KML/KMZ files"), defaultDir ) +if dt.configuration.running_os ~= "linux" then + dt.preferences.register("kml_export", + "convertPath", -- name + "file", -- type + _("KML export: convert binary Location"), -- label + _("Install location of convert[.exe]. Requires restart to take effect."), -- tooltip + "convert") -- default +end + dt.preferences.register("kml_export", "CreatePath", "bool", From 5c24077a30fc81274280f9d49f80b179edad0a1d Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 25 Feb 2019 17:06:34 +0100 Subject: [PATCH 28/61] kml_export the command "convert" is deprecated and replaced by "magick" --- contrib/kml_export.lua | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 595311c..6e5fb33 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -21,7 +21,7 @@ darktable KML export script ADDITIONAL SOFTWARE NEEDED FOR THIS SCRIPT * zip (at the moment Linux only and only if you create KMZ files) -* convert (ImageMagick) +* magick (ImageMagick) * xdg-user-dir (Linux) WARNING @@ -230,9 +230,9 @@ end local function create_kml_file(storage, image_table, extra_data) - local convertPath = dt.preferences.read("kml_export","convertPath","string") - if not df.check_if_bin_exists(convertPath) then - dt.print_error(_("convert not found")) + local magickPath = dt.preferences.read("kml_export","magickPath","string") + if not df.check_if_bin_exists(magickPath) then + dt.print_error(_("magick not found")) return end if dt.configuration.running_os == "linux" then @@ -244,7 +244,8 @@ local function create_kml_file(storage, image_table, extra_data) dt.print_error("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 @@ -268,14 +269,14 @@ 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 = convertPath .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" + local convertToThumbCommand = magickPath .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" dt.control.execute(convertToThumbCommand) else -- Remove exported image if it has no GPS data @@ -390,13 +391,15 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.."" 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 " @@ -463,11 +466,11 @@ dt.preferences.register("kml_export", if dt.configuration.running_os ~= "linux" then dt.preferences.register("kml_export", - "convertPath", -- name + "magickPath", -- name "file", -- type - _("KML export: convert binary Location"), -- label - _("Install location of convert[.exe]. Requires restart to take effect."), -- tooltip - "convert") -- default + _("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", From 944008516916dc8c29272a2f456caf7c02bb37d7 Mon Sep 17 00:00:00 2001 From: supertobi Date: Tue, 26 Feb 2019 14:02:16 +0100 Subject: [PATCH 29/61] kml_export: use more utility functions kml_export: use more utility functions as suggested by @wpferguson --- contrib/kml_export.lua | 196 ++++++----------------------------------- 1 file changed, 25 insertions(+), 171 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 6e5fb33..bb134c4 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -28,7 +28,6 @@ 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 @@ -37,13 +36,14 @@ USAGE local dt = require "darktable" local du = require "lib/dtutils" local df = require "lib/dtutils.file" +local ds = require "lib/dtutils.string" local dsys = require "lib/dtutils.system" -require "official/yield" + local gettext = dt.gettext local PS = dt.configuration.running_os == "windows" and "\\" or "/" -du.check_min_api_version("3.0.0", kml_export) +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/") @@ -52,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 ) @@ -241,7 +83,7 @@ local function create_kml_file(storage, image_table, extra_data) return end end - dt.print_error("Will try to export KML file now") + dt.print_log("Will try to export KML file now") local imageFoldername if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true @@ -276,8 +118,8 @@ local function create_kml_file(storage, image_table, extra_data) -- 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 = magickPath .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..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" + dsys.external_command(convertToThumbCommand) else -- Remove exported image if it has no GPS data os.remove(exported_image) @@ -288,7 +130,18 @@ 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, "?", "") end exportKMLFilename = filmName..".kml" @@ -318,12 +171,12 @@ local function create_kml_file(storage, image_table, extra_data) local image_title, image_description if (image.title and image.title ~= "") then - image_title = string.escapeXmlCharacters(image.title) + image_title = ds.escape_xml_characters(image.title) else image_title = filename..extension end -- Characters should not be escaped in CDATA, but we are using HTML fragment, so we must escape them - image_description = string.escapeXmlCharacters(image.description) + image_description = ds.escape_xml_characters(image.description) kml_file = kml_file.." "..image_title.."\n" kml_file = kml_file.." "..image_description.."\n" @@ -368,7 +221,7 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.." \n" kml_file = kml_file.." \n" - for image,exported_image in spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do + for image,exported_image in du.spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -426,10 +279,11 @@ local function create_kml_file(storage, image_table, extra_data) if ( dt.preferences.read("kml_export","OpenKmlFile","bool") == true ) then local path - if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true ) then - path = exportDirectory..PS.."\""..exportKMZFilename.."\"" + if ( dt.preferences.read("kml_export","CreateKMZ","bool") == true + and dt.configuration.running_os == "linux") then + path = exportDirectory..PS..exportKMZFilename else - path = exportDirectory..PS.."\""..exportKMLFilename.."\"" + path = exportDirectory..PS..exportKMLFilename end dsys.launch_default_app(path) From 5ff36fe2ec6d55f94f60d424fd149eb60d486f0a Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Tue, 26 Feb 2019 12:03:04 -0500 Subject: [PATCH 30/61] Added a space after the commands and removed quotes in the command invocation. --- lib/dtutils/system.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 418a59d..69a7f69 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -120,13 +120,13 @@ dtutils_system.libdoc.functions["launch_default_app"] = { Copyright = [[]], } function dtutils_system.launch_default_app(path) - local open_cmd = "xdg-open" + local open_cmd = "xdg-open " if (dt.configuration.running_os == "windows") then - open_cmd = "start" + open_cmd = "start " elseif (dt.configuration.running_os == "macos") then - open_cmd = "open" + open_cmd = "open " end - return dtutils_system.external_command(open_cmd..' "'..path..'"') + return dtutils_system.external_command(open_cmd .. path) end From 9e0490d37158118858251dedaa10e2e7aef6578e Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 26 Feb 2019 14:30:11 -0500 Subject: [PATCH 31/61] Add gettext, check for color label Add translate-ability. Also adds a check that the selected image has an active color label and displays a message if it does not --- contrib/CollectHelper.lua | 53 ++++++++++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 15 deletions(-) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index 3684686..bd58ce7 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -46,17 +46,36 @@ In the "Selected Images" module click on "Collect on this Image" ]] 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("gimp",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") + 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) @@ -87,6 +106,10 @@ local function CollectOnColors(all_rules, all_active) 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 @@ -151,48 +174,48 @@ end -- GUI -- dt.gui.libs.image.register_action( - "collect: previous", + _("collect: previous"), function() PreviousCollection() end, - "Sets the Collect parameters to be the previously active parameters" + _("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", + _("collect: folder"), function() CollectOnFolder(_ , false) end, - "Sets the Collect parameters to be the selected images's folder" + _("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)", + _("collect: color label(s)"), function() CollectOnColors(_ , false) end, - "Sets the Collect parameters to be the selected images's color label(s)" + _("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)", + _("collect: all (AND)"), function() CollectOnAll_AND() end, - "Sets the Collect parameters based on all activated CollectHelper options" + _("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 + _('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 + _('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 + _('CollectHelper: Folder'), -- label + _('Enable the button that allows you to swap to a collection based on selected image\'s FOLDER location'), -- tooltip true -- default ) From 9183beeed4d9a4960fb973a2c35abba5bff12b86 Mon Sep 17 00:00:00 2001 From: supertobi Date: Wed, 27 Feb 2019 09:57:36 +0100 Subject: [PATCH 32/61] video_ffmpeg add df.sanitize_filename() I'm not 100% sure, but I think we need the df.sanitize_filename() here. --- contrib/video_ffmpeg.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index d561d55..af797e5 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -441,7 +441,7 @@ local function finalize_export(storage, images_table, extra_data) else dt.print(_("SUCCESS")) if extra_data["open_after_export"] then - dsys.launch_default_app(path) + dsys.launch_default_app(df.sanitize_filename(path)) end end From 5a6ee174af3682fd102bafaf46758a77fe1d7c2e Mon Sep 17 00:00:00 2001 From: supertobi Date: Wed, 27 Feb 2019 10:05:59 +0100 Subject: [PATCH 33/61] kml_export sanitize_filename() dsys.launch_default_app(df.sanitize_filename(path)) --- contrib/kml_export.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index bb134c4..e0c365b 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -286,7 +286,7 @@ local function create_kml_file(storage, image_table, extra_data) path = exportDirectory..PS..exportKMLFilename end - dsys.launch_default_app(path) + dsys.launch_default_app(df.sanitize_filename(path)) end end From 11881b3b73229b4742687781a54db40cdbd17811 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Wed, 27 Feb 2019 13:29:29 -0500 Subject: [PATCH 34/61] removed sanitize_filename wrapper from path argument --- lib/dtutils/file.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index e9fc36f..69243e9 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -576,13 +576,13 @@ dtutils_file.libdoc.functions["mkdir"] = { 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.." "..dtutils_file.sanitize_filename(path)) + return dsys.external_command(mkdir_cmd.." "..path) else return 0 end end -dtutils_file.libdoc.functions["rm"] = { +dtutils_file.libdoc.functions["rmdir"] = { Name = [[rmdir]], Synopsis = [[recursively remove a directory]], Usage = [[local df = require "lib/dtutils.file" @@ -601,7 +601,7 @@ dtutils_file.libdoc.functions["rm"] = { 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.." "..dtutils_file.sanitize_filename(path)) + return dsys.external_command(rm_cmd.." "..path) end From 24862a26f23fe18e971283f60f8e0e23e7a1b16b Mon Sep 17 00:00:00 2001 From: supertobi Date: Thu, 28 Feb 2019 11:41:56 +0100 Subject: [PATCH 35/61] viedeo_ffmpeg df.sanitize_filename() viedeo_ffmpeg df.sanitize_filename() for mkdir and rmdir --- contrib/video_ffmpeg.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index af797e5..4fbce4a 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -412,7 +412,7 @@ local function export(extra_data) local img_ext = extra_data["img_ext"] local output_file = extra_data["output_file"] - local dir_create_result = df.mkdir(df.get_path(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) @@ -424,7 +424,7 @@ local function finalize_export(storage, images_table, extra_data) dt.print(_("prepare merge process")) - local result = df.mkdir(tmp_dir) + local result = df.mkdir(df.sanitize_filename()) if result ~= 0 then dt.print(_("ERROR: cannot create temp directory")) end local images = extra_data["images"] @@ -445,7 +445,7 @@ local function finalize_export(storage, images_table, extra_data) end end - df.rmdir(tmp_dir) + df.rmdir(df.sanitize_filename(tmp_dir)) end dt.register_storage( From 287d9e94cf791fe2e81ca7e8807e53ebaaca5de5 Mon Sep 17 00:00:00 2001 From: supertobi Date: Thu, 28 Feb 2019 13:22:10 +0100 Subject: [PATCH 36/61] video_ffmpeg I forgot the argument --- contrib/video_ffmpeg.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/video_ffmpeg.lua b/contrib/video_ffmpeg.lua index 4fbce4a..6755e5c 100644 --- a/contrib/video_ffmpeg.lua +++ b/contrib/video_ffmpeg.lua @@ -424,7 +424,7 @@ local function finalize_export(storage, images_table, extra_data) dt.print(_("prepare merge process")) - local result = df.mkdir(df.sanitize_filename()) + 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"] From bd3a74954c14bd7d4200c8a74350aec3b1538b50 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Thu, 28 Feb 2019 15:08:57 -0500 Subject: [PATCH 37/61] gettext incorrectly pointed to "gimp" --- contrib/CollectHelper.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/CollectHelper.lua b/contrib/CollectHelper.lua index bd58ce7..5996592 100644 --- a/contrib/CollectHelper.lua +++ b/contrib/CollectHelper.lua @@ -51,7 +51,7 @@ local previous = nil local all_active = false -- Tell gettext where to find the .mo file translating messages for a particular domain -gettext.bindtextdomain("gimp",dt.configuration.config_dir.."/lua/locale/") +gettext.bindtextdomain("CollectHelper",dt.configuration.config_dir.."/lua/locale/") local function _(msgid) return gettext.dgettext("CollectHelper", msgid) From 0ef9f5fc6c069b42c30fa68a72f546cac33eb347 Mon Sep 17 00:00:00 2001 From: supertobi Date: Fri, 1 Mar 2019 14:22:35 +0100 Subject: [PATCH 38/61] kml_export df.mkdir(df.sanitize_filename()) df.mkdir(df.sanitize_filename(xportDirectory..PS..imageFoldername)) --- contrib/kml_export.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index e0c365b..de2fecf 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -100,7 +100,7 @@ local function create_kml_file(storage, image_table, extra_data) -- Creates dir if not exsists imageFoldername = "files"..PS - df.mkdir(exportDirectory..PS..imageFoldername) + df.mkdir(df.sanitize_filename(xportDirectory..PS..imageFoldername)) end -- Create the thumbnails From fa25ec4a2690fa3048a4489c31e0df726742dc6f Mon Sep 17 00:00:00 2001 From: supertobi Date: Fri, 1 Mar 2019 23:27:26 +0100 Subject: [PATCH 39/61] OpenInExplorer Tap to Space --- contrib/OpenInExplorer.lua | 52 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 380838b..edab7b6 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -43,39 +43,39 @@ du.check_min_api_version("5.0.0", "OpenInExplorer") --Detect OS and modify accordingly-- local proper_install = false if dt.configuration.running_os == "windows" then - proper_install = true + proper_install = true else - dt.print_error('OpenInExplorer plug-in only supports Windows OS at this time') - return + dt.print_error('OpenInExplorer plug-in only supports Windows OS at this time') + return end -- FUNCTION -- local function OpenInExplorer() --Open in Explorer - --Inits-- - if not proper_install then - return - end - 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..'\\'..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 + --Inits-- + if not proper_install then + return + end + 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..'\\'..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 -- GUI -- if proper_install then - dt.gui.libs.image.register_action( - "show in file explorer", - function() OpenInExplorer() end, - "Opens File Explorer at the selected image's location" - ) + dt.gui.libs.image.register_action( + "show in file explorer", + function() OpenInExplorer() end, + "Opens File Explorer at the selected image's location" + ) end From 2e5e2e9f0de493cb525aee8f7ebe337331fdaef5 Mon Sep 17 00:00:00 2001 From: supertobi Date: Fri, 1 Mar 2019 23:30:58 +0100 Subject: [PATCH 40/61] OpenInExplorer added Nautilus support --- contrib/OpenInExplorer.lua | 56 ++++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index edab7b6..1dbe4d6 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -1,4 +1,5 @@ ---[[OpenInExplorer plugin for darktable +--[[ +OpenInExplorer plugin for darktable copyright (c) 2018 Kevin Ertel @@ -20,7 +21,7 @@ This plugin adds the module "OpenInExplorer" to darktable's lighttable view ----REQUIRED SOFTWARE---- -Microsoft Windows Operating System +Microsoft Windows or Linux with installed Nautilus ----USAGE---- Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) @@ -35,33 +36,34 @@ A file explorer window will be opened for each selected file at the file's locat 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 == "windows" then +if dt.configuration.running_os ~= "macos" then proper_install = true else - dt.print_error('OpenInExplorer plug-in only supports Windows OS at this time') + dt.print_error('OpenInExplorer plug-in only supports Windows and Linux at this time') return end --- FUNCTION -- -local function OpenInExplorer() --Open in Explorer - --Inits-- - if not proper_install then - 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..'\\'..image.filename + 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) @@ -71,11 +73,41 @@ local function OpenInExplorer() --Open in Explorer 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 = "nautilus --select " .. df.sanitize_filename(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() OpenInExplorer() end, + function() open_in_filemanager() end, "Opens File Explorer at the selected image's location" ) end From 49c4406bb43c3b8dc265f25f2dc3c27afb929252 Mon Sep 17 00:00:00 2001 From: supertobi Date: Sun, 3 Mar 2019 17:41:04 +0100 Subject: [PATCH 41/61] Update video_mencoder.lua fix linebrakes --- contrib/video_mencoder.lua | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contrib/video_mencoder.lua b/contrib/video_mencoder.lua index 93b2792..71c8a4d 100644 --- a/contrib/video_mencoder.lua +++ b/contrib/video_mencoder.lua @@ -51,9 +51,9 @@ 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,\n - therefore video_mencoder is deprecated. video_ffmpeg should be used in it's place. video_mencoder\n - will be removed when darktable 2.8 is released.") + 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 From 7bc4846c0a4143b47637405be71336371247558a Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Sun, 3 Mar 2019 15:48:08 -0500 Subject: [PATCH 42/61] Refactored After experiencing some issues after upgrading to dt2.6 I decided it would be worthwhile to refactor the code for more reliable performance. Specifically the script would crash dt when attempting to apply a style on import and sometimes just fail to run. I have refactored this in a manor that should make it more logical for future users to add/maintain as applicable. Refactored script has only been tested on Windows 10 w/ dt2.6. Hopefullt all cross-platform aspects have been maintained. --- contrib/HDRMerge.lua | 510 ++++++++++++++++++++++--------------------- 1 file changed, 267 insertions(+), 243 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 9614f49..c0cafc3 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -28,11 +28,11 @@ Install: (see here for more detail: https://github.com/darktable-org/lua-scripts 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 if you have that option enabled. +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 and Preview Size. +Select your desired BPS (bits per sample and Embedded Preview Size. Batch Options: Select if you want to run in batch mode or not @@ -42,46 +42,50 @@ See HDRMerge manual for further detail: http://jcelaya.github.io/hdrmerge/docume 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 - -----KNOWN ISSUES---- ]] local dt = require "darktable" local df = require "lib/dtutils.file" local dsys = require "lib/dtutils.system" -require "official/yield" +local mod = 'module_HDRMerge' +local os_path_seperator = '/' +if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end ---Check if HDRMerge is installed-- -local first_run = true -local not_installed = false -local HDRMerge_path - ---Detect OS and modify accordingly-- -local os_path_seperator = "/" -if dt.configuration.running_os == "windows" then os_path_seperator = "\\" end - --- READ PREFERENCES -- -local pref_bps = dt.preferences.read("module_HDRMerge", "bits_per_sample", "enum") -local pref_bps_enum = 0 -if pref_bps == "32" then - pref_bps_enum = 3 -elseif pref_bps == "24" then - pref_bps_enum = 2 -else - pref_bps_enum = 1 -end -local pref_size = dt.preferences.read("module_HDRMerge", "preview_size", "enum") -local pref_size_enum = 0 -if pref_size == "none" then - pref_size_enum = 3 -elseif pref_size == "full" then - pref_size_enum = 2 -else - pref_size_enum = 1 -end ---pref_cpytags = dt.preferences.read("module_HDRMerge", "copy_tags", "bool") ---pref_addtags = dt.preferences.read("module_HDRMerge", "add_tags", "string") -local pref_style = dt.preferences.read("module_HDRMerge", "style", "string") +dt.preferences.register("executable_paths", "HDRMerge", -- name + "file", -- type + 'HDRMerge: binary location', -- label + 'Install location of HDRMerge. Requires restart to take effect.', -- tooltip + "HDRMerge" -- default +) +local temp +local HDRM = { + 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 = { + HDR = { + bps ={}, + size ={}, + batch ={}, + gap ={} + }, + Target = { + style ={}, + copy_tags ={}, + add_tags ={} + }, + run = {} +} --Detect User Styles-- local styles = dt.styles @@ -90,259 +94,279 @@ for _,i in pairs(dt.styles) do if type(i) == "userdata" then styles_count = styles_count + 1 end end --- FUNCTION -- -local function build_execute_command(cmd, args, file_list) - local result = cmd.." "..args.." "..file_list +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 the passed in text string + front = string.match(text, '^%s') + back = string.match(text, '%s$') + if front then + text = string.sub(text,2) + end + if back then + text = string.sub(text,1,-2) + end + return text +end + +local function BuildExecuteCmd(prog_table) + local result = prog_table.bin..' '..prog_table.arg_string..' '..prog_table.images_string return result end -local function HDRMerge() - if first_run then - HDRMerge_path = df.check_if_bin_exists("HDRMerge") - if not HDRMerge_path then - dt.print_error("HDRMerge not found") - dt.print("ERROR - HDRMerge not found") - not_installed = true + +local function PreCall(prog_tbl) + 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 + else + prog.bin = CleanSpaces(prog.bin) + end + prog.first_run = false end - first_run = false end - if not_installed then - dt.print("Required software not found") - dt.print_log("Required software not found - HDRMerge did not run") +end + +local function UpdateActivePreference() + 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 HDRMerge_main() + PreCall({HDRM}) + if HDRM.install_error then + dt.print_error('HDRMerge install issue, please ensure the binary path is proper') return end - dt.print_log("Running HDRMerge") - dt.print("Running HDRMerge") - gui_job = dt.gui.create_job("HDRMerge", 1) - - --Inits-- - local images = dt.gui.selection() - local images_to_merge = "" - local image_path = "" - local curr_image = "" - local first_image = "" - local last_image = "" - local output_file = "" - local num_images = 0 - - --Read Settings-- - local set_bps = HDRMerge_cmbx_bps.value - local set_size = HDRMerge_cmbx_size.value - local set_batch = HDRMerge_chkbtn_batch.value - local set_gap = HDRMerge_sldr_gap.value - local set_cpytags = HDRMerge_chkbtn_cpytags.value - local set_tag = HDRMerge_entry_tag.text - - --Create Images String-- - for _,image in pairs(images) do - curr_image = image.path..os_path_seperator..image.filename - images_to_merge = images_to_merge..df.sanitize_filename(curr_image).." " - last_image = string.gsub(string.match(image.filename,'^.*%.'), "%." , "") - image_path = image.path - if _ == 1 then first_image = last_image end - num_images = num_images + 1 + images = dt.gui.selection() + local count = 0 + for _,image in pairs(images) do + count = count + 1 end - output_file = image_path..os_path_seperator..first_image.."-"..string.match(last_image, '%d*$')..".dng" - output_file = df.create_unique_filename(output_file) - --output_file = df.sanitize_filename(output_file) - --Check if at least 2 images selected-- - if num_images < 2 then - dt.print_error("Less than 2 images selected") - dt.print("ERROR - please select at least 2 images") - gui_job.valid = false + if count < 2 then + dt.print_error('not enough images selected, select at least 2 images to merge') return end - --Create Command Args-- - cmd_args = "-b "..set_bps.." -p "..set_size - if (set_batch) then - cmd_args = cmd_args.." -B -g "..set_gap.." -a" - else --add ability to launch with gui here by omiting -o arg - cmd_args = cmd_args.." -o "..df.sanitize_filename(output_file) - end + UpdateActivePreference() - gui_job.percent = .1 + --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 + 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 + HDRM.images_string = CleanSpaces(HDRM.images_string) + out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng' + out_path = df.create_unique_filename(out_path) - --Execute with Run Command-- - run_cmd = build_execute_command(HDRMerge_path, cmd_args, images_to_merge) - dt.print_log("run_cmd = "..run_cmd) + --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 + HDRM.arg_string = CleanSpaces(HDRM.arg_string) + + -- create run command and execute + local run_cmd = BuildExecuteCmd(HDRM) resp = dsys.external_command(run_cmd) - gui_job.percent = .9 - - if resp == 0 and not(set_batch) then - --Import Image-- - imported = dt.database.import(output_file) - - --Apply Selected Style (IF)-- - if HDRMerge_cmbx_style.selected > 1 then - set_style = styles[HDRMerge_cmbx_style.selected - 1] + if resp == 0 and not GUI.HDR.batch.value then + local imported = dt.database.import(out_path) + if GUI.Target.style.selected > 1 then + local set_style = styles[GUI.Target.style.selected - 1] dt.styles.apply(set_style , imported) end - - --Copy Tags (IF)-- - if (set_cpytags) then - all_tags = dt.tags.get_tags(images[1]) + if GUI.Target.copy_tags.value then + 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 + if string.match(tag.name, 'darktable|') == nil then dt.tags.attach(tag, imported) end end end - - --Apply Entered Tags (IF)-- + local set_tag = GUI.Target.add_tags.text if set_tag ~= nil then for tag in string.gmatch(set_tag, '[^,]+') do - tag = string.gsub(tag, " ", "") + tag = CleanSpaces(tag) tag = dt.tags.create(tag) dt.tags.attach(tag, imported) end end - - dt.print_log("Merge Successful") - dt.print("Merge Successful") - elseif resp == 0 then - dt.print_log("Merge Successful") - dt.print("Merge Successful") else - dt.print_error("Merge Not Successful") - dt.print("ERROR - Merge Not Successful") + dt.print_error('HDRMerge failed') end - gui_job.percent = 1 - gui_job.valid = false + end --- GUI -- -HDRMerge_lbl_base= dt.new_widget("section_label"){ - label = "Base Options", - } -HDRMerge_cmbx_bps = dt.new_widget("combobox"){ - label = 'Bits Per Sample', - tooltip = 'Output file\'s bit depth \ndefault: '..pref_bps, - value = pref_bps_enum, - "16", "24", "32", - } -HDRMerge_cmbx_size = dt.new_widget("combobox"){ - label = 'Preview Size', - tooltip = 'Output file\'s built-in preview size \ndefault: '..pref_size, - value = pref_size_enum, - "half", "full", "none", - } -HDRMerge_lbl_batch= dt.new_widget("section_label"){ - label = "Batch Options", - } -HDRMerge_chkbtn_batch = dt.new_widget("check_button"){ - label = ' Batch Mode', - value = false, --pref_batch, - tooltip ='Operate in batch mode. When operating in batch mode output files will NOT be auto-imported \ndefault: (false).', - } -HDRMerge_sldr_gap = dt.new_widget("slider"){ - label = 'Batch Gap [sec.]', - tooltip = 'Gap, in seconds, between batch mode groups \ndefault: (3).', +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 = 60, + soft_max = 30, hard_min = 1, - hard_max = 60, + hard_max = 3600, step = 1, digits = 0, - value = 3, --pref_gap, - } -HDRMerge_lbl_out= dt.new_widget("section_label"){ - label = "Auto-import Options", - } -HDRMerge_cmbx_style = dt.new_widget("combobox"){ - label = "Apply Style on Import", - tooltip = "Apply selected style on auto-import to newly created HDRMerge DNG\nDoes not apply when in Batch mode.", - value = 1, + 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", - } -pref_style_enum = 1 + 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 - HDRMerge_cmbx_style[k+1] = styles[k].name - if styles[k].name == pref_style then pref_style_enum = k+1 end + GUI.Target.style[k+1] = styles[k].name end -HDRMerge_cmbx_style.value = pref_style_enum -HDRMerge_chkbtn_cpytags = dt.new_widget("check_button"){ - label = ' Copy Tags', - value = dt.preferences.read("module_HDRMerge", "copy_tags", "bool"), - tooltip ='Copy tags from first image. When operating in batch mode this will NOT be performed. \ndefault: (true).', - } -HDRMerge_lbl_tags= dt.new_widget("label"){ - label = 'Additional Tags', - } -HDRMerge_entry_tag = dt.new_widget("entry"){ +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 = dt.preferences.read("module_HDRMerge", "add_tags", "string"), + text = temp, placeholder = "Enter tags, seperated by commas", editable = true } -HDRMerge_btn_run = dt.new_widget("button"){ - label = "Run HDRMerge", - tooltip = "Runs HDRMerge on selected images, with selected settings", - clicked_callback = function() HDRMerge() end - } -HDRMerge_lbl_note= dt.new_widget("label"){ - label = 'Defaults can be adjusted under:\n"Settings > lua options"', - } +GUI.run = dt.new_widget("button"){ + label = 'merge', + tooltip ='run HDRMerge with the above specified settings', + clicked_callback = function() HDRMerge_main() end +} dt.register_lib( "HDRMerge_Lib", -- Module name "HDRMerge", -- name true, -- expandable - false, -- resetable + true, -- resetable {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers dt.new_widget("box"){ orientation = "vertical", - HDRMerge_lbl_base, - HDRMerge_cmbx_bps, - HDRMerge_cmbx_size, - HDRMerge_lbl_batch, - HDRMerge_chkbtn_batch, - HDRMerge_sldr_gap, - HDRMerge_lbl_out, - HDRMerge_cmbx_style, - HDRMerge_chkbtn_cpytags, - HDRMerge_lbl_tags, - HDRMerge_entry_tag, - HDRMerge_btn_run, - HDRMerge_lbl_note - } + 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 + } ) --- PREFERENCES -- -dt.preferences.register("module_HDRMerge", "add_tags", -- name - "string", -- type - 'HDRMerge: Defualt additional tags', -- label - 'Changes DEFAULT entry in the additional tags option. Requires restart to take effect.', -- tooltip - "" -- default -) -dt.preferences.register("module_HDRMerge", "style", -- name - "string", -- type - 'HDRMerge: Defualt Style', -- label - 'Changes DEFAULT entry in the Style option. Requires restart to take effect.', -- tooltip - "" -- default -) -dt.preferences.register("module_HDRMerge", "copy_tags", -- name - "bool", -- type - 'HDRMerge: Copy tags from first image by default', -- label - 'Changes DEFAULT selection for Copy Tags, Requires restart to take effect.', -- tooltip - true -- default -) -dt.preferences.register("module_HDRMerge", "preview_size", -- name - "enum", -- type - 'HDRMerge: Default DNG Preview Size', -- label - "Change the DEFAULT preview size. Requires restart to take effect.", -- tooltip - "half", -- default - "full", "none" --values -) -dt.preferences.register("module_HDRMerge", "bits_per_sample", -- name - "enum", -- type - 'HDRMerge: Default Bits Per Sample', -- label - 'Change the DEFAULT bit depth. Requires restart to take effect.', -- tooltip - "16", -- default - "24","32" --value -) -dt.preferences.register("executable_paths", "HDRMerge", -- name - "file", -- type - 'HDRMerge: Binary Location', -- label - 'Install location of HDRMerge[.exe]. Requires restart to take effect.', -- tooltip - "HDRMerge" -- default -) From 18783e7ea59c6d233eba7f8ab21a15d04503c554 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 4 Mar 2019 15:12:04 +0100 Subject: [PATCH 43/61] kml_export fixed typo --- contrib/kml_export.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index de2fecf..c809c45 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -100,7 +100,7 @@ local function create_kml_file(storage, image_table, extra_data) -- Creates dir if not exsists imageFoldername = "files"..PS - df.mkdir(df.sanitize_filename(xportDirectory..PS..imageFoldername)) + df.mkdir(df.sanitize_filename(exportDirectory..PS..imageFoldername)) end -- Create the thumbnails From 85c242ff0e926541b4cf6628ec124e1e38bc4a29 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 4 Mar 2019 09:45:05 -0500 Subject: [PATCH 44/61] Comments added, minor updates I have added comments throughout, mainly to the functions I improved the performance of the CleanSpaces() function Improved communication to user via dt.print() --- contrib/HDRMerge.lua | 68 +++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index c0cafc3..354c028 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -58,7 +58,7 @@ dt.preferences.register("executable_paths", "HDRMerge", -- name "HDRMerge" -- default ) local temp -local HDRM = { +local HDRM = { --HDRMerge Program Table name = 'HDRMerge', bin = '', first_run = true, @@ -72,7 +72,7 @@ local HDRM = { gap ={text = '-g ', style = 'integer'} } } -local GUI = { +local GUI = { --GUI Elements Table HDR = { bps ={}, size ={}, @@ -102,9 +102,8 @@ local function InRange(test, low, high) --tests if test value is within range of 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 +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/ @@ -122,24 +121,18 @@ local function GetFileName(full_path) return path, filename, identifier, extension end -local function CleanSpaces(text) --removes spaces from the front and back of the passed in text string - front = string.match(text, '^%s') - back = string.match(text, '%s$') - if front then - text = string.sub(text,2) - end - if back then - text = string.sub(text,1,-2) - 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) - local result = prog_table.bin..' '..prog_table.arg_string..' '..prog_table.images_string +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) +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) @@ -153,30 +146,27 @@ local function PreCall(prog_tbl) end end -local function UpdateActivePreference() +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 HDRMerge_main() - PreCall({HDRM}) +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, please ensure the binary path is proper') + dt.print_error('HDRMerge install issue') + dt.print('HDRMerge install issue, please ensure the binary path is proper') return end - images = dt.gui.selection() - local count = 0 - for _,image in pairs(images) do - count = count + 1 - end - if count < 2 then - dt.print_error('not enough images selected, select at least 2 images to merge') + 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() + 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 = '' @@ -185,7 +175,7 @@ local function HDRMerge_main() local smallest_name = '' local largest_id = 0 local source_raw = {} - for _,image in pairs(images) do + 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 @@ -198,7 +188,6 @@ local function HDRMerge_main() end if source_id > largest_id then largest_id = source_id end end - HDRM.images_string = CleanSpaces(HDRM.images_string) out_path = out_path..os_path_seperator..smallest_name..'-'..largest_id..'.dng' out_path = df.create_unique_filename(out_path) @@ -209,38 +198,40 @@ local function HDRMerge_main() else HDRM.arg_string = HDRM.arg_string..'-o '..df.sanitize_filename(out_path) end - HDRM.arg_string = CleanSpaces(HDRM.arg_string) -- 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) - if GUI.Target.style.selected > 1 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 + 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 + 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' } @@ -347,9 +338,9 @@ GUI.Target.add_tags = dt.new_widget("entry"){ GUI.run = dt.new_widget("button"){ label = 'merge', tooltip ='run HDRMerge with the above specified settings', - clicked_callback = function() HDRMerge_main() end + clicked_callback = function() main() end } -dt.register_lib( +dt.register_lib( -- register HDRMerge module "HDRMerge_Lib", -- Module name "HDRMerge", -- name true, -- expandable @@ -369,4 +360,3 @@ dt.register_lib( GUI.run } ) - From 95968c3d9595ce71e468208e6178558f7da42c63 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:20:55 -0500 Subject: [PATCH 45/61] Add AutoGrouper Adds AutoGrouper script which allows the user to select multiple images (or an entire collection) and automatically group them based on timestamp. The separation between groups is user defined via a slider. --- contrib/AutoGrouper.lua | 155 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) create mode 100644 contrib/AutoGrouper.lua diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua new file mode 100644 index 0000000..2a1bd24 --- /dev/null +++ b/contrib/AutoGrouper.lua @@ -0,0 +1,155 @@ +--[[AutoGrouper plugin for darktable + + copyright (c) 2019 Kevin Ertel + + darktable is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + darktable is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with darktable. If not, see . +]] + +--[[About this Plugin +This plugin adds the module "Auto Group" to darktable's lighttable view + +----REQUIRED SOFTWARE---- +None + +----USAGE---- +Install: (see here for more detail: https://github.com/darktable-org/lua-scripts ) + 1) Copy this file in to your "lua/contrib" folder where all other scripts reside. + 2) Require this file in your luarc file, as with any other dt plug-in + +Set a gap amount in second which will be used to determine when images should no +longer be added to a group. If an image is more then the specified amount of time +from the last image in the group it will not be added. Images without timestamps +in exif data will be ignored. + +There are two buttons. One allows the grouping to be performed only on the currently +selected images, the other button performs grouping on the entire active collection +]] + +local dt = require "darktable" +local mod = 'autogroup' +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) + 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) + 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) + 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 return end + table.sort(images, function(first, second) return CompTime(first,second) end) --sort images into + + for i, image in ipairs(images) do + if i == 1 then + prev_image = image + elseif string.match(image.exif_datetime_taken, '[%d]') ~= nil then + 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, 3600) 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 = 30, + hard_min = 1, + hard_max = 3600, + 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 + } +) From 9e97b8f5a1fcddc074d279fe4e47e67168d3c581 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 4 Mar 2019 12:47:52 -0500 Subject: [PATCH 46/61] Added comments, made the gap inclusive Added comments throughout Change the gap comparison to be inclusive ( from '<' to '<=') --- contrib/AutoGrouper.lua | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 2a1bd24..3125363 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -53,7 +53,7 @@ local function InRange(test, low, high) --tests if test value is within range of end end -local function CompTime(first, second) +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]*','')) @@ -63,7 +63,7 @@ local function CompTime(first, second) return first_time < second_time end -local function SeperateTime(str) +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) @@ -75,7 +75,7 @@ local function SeperateTime(str) return {year = year, month = month, day = day, hour = hour, min = min, sec = sec} end -local function GetTimeDiff(curr_image, prev_image) +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) @@ -93,14 +93,14 @@ local function main(on_collection) end dt.preferences.write(mod, 'active_gap', 'integer', GUI.gap.value) if #images < 2 then return end - table.sort(images, function(first, second) return CompTime(first,second) end) --sort images into + 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 + 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 + if GetTimeDiff(curr_image, prev_image) <= GUI.gap.value then images[i]:group_with(images[i-1]) end prev_image = curr_image From 5d3910da499b7a368ccc1622ad8972cc77508dbf Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 5 Mar 2019 09:14:28 -0500 Subject: [PATCH 47/61] Tab to Space --- contrib/HDRMerge.lua | 472 +++++++++++++++++++++---------------------- 1 file changed, 236 insertions(+), 236 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 354c028..4039350 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -51,312 +51,312 @@ local mod = 'module_HDRMerge' local os_path_seperator = '/' if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end -dt.preferences.register("executable_paths", "HDRMerge", -- name - "file", -- type - 'HDRMerge: binary location', -- label - 'Install location of HDRMerge. Requires restart to take effect.', -- tooltip - "HDRMerge" -- default +dt.preferences.register("executable_paths", "HDRMerge", -- name + "file", -- type + 'HDRMerge: binary location', -- label + 'Install location of HDRMerge. Requires restart to take effect.', -- tooltip + "HDRMerge" -- default ) 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'} - } + 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 = {} + HDR = { + bps ={}, + size ={}, + batch ={}, + gap ={} + }, + Target = { + style ={}, + copy_tags ={}, + add_tags ={} + }, + run = {} } --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 + 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 + 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*") + + 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 + 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 + 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 - else - prog.bin = CleanSpaces(prog.bin) - end - prog.first_run = false - end - end + 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 + else + prog.bin = CleanSpaces(prog.bin) + end + prog.first_run = false + end + 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) + 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 + 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' + 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 + 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 + 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 + 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 + 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' + 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 + 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 + 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 + 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 + 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 + label = 'merge', + tooltip ='run HDRMerge with the above specified settings', + clicked_callback = function() main() 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", - 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 - } + "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", + 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 + } ) From 7a3a9ba559ee0b2f1f6b71a74e4531a07df74fc4 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 12 Mar 2019 11:19:22 -0400 Subject: [PATCH 48/61] Removed preferences for exe The GUI is now made of a stack which has two boxes in it. One box contains all the normal GUI elements, the other contains a file chooser button and a normal button to call an update routine. This box is only displayed if an issue is found. I decided to track the install status via a preference boolean, rather than calling df_check_if_bin_exists at startup every time. I do this because on my windows PC I was starting to get slow startups with too many scripts installed and many many flashing CMD windows at startup as well, which was a nuisance. --- contrib/HDRMerge.lua | 78 ++++++++++++++++++++++++++++++++++---------- 1 file changed, 61 insertions(+), 17 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 4039350..f940907 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -51,12 +51,6 @@ local mod = 'module_HDRMerge' local os_path_seperator = '/' if dt.configuration.running_os == 'windows' then os_path_seperator = '\\' end -dt.preferences.register("executable_paths", "HDRMerge", -- name - "file", -- type - 'HDRMerge: binary location', -- label - 'Install location of HDRMerge. Requires restart to take effect.', -- tooltip - "HDRMerge" -- default -) local temp local HDRM = { --HDRMerge Program Table name = 'HDRMerge', @@ -84,7 +78,12 @@ local GUI = { --GUI Elements Table copy_tags ={}, add_tags ={} }, - run = {} + run = {}, + stack = {}, + options = {}, + exes = {}, + exe_chooser = {}, + exe_update = {}, } --Detect User Styles-- @@ -138,12 +137,29 @@ local function PreCall(prog_tbl) --looks to see if this is the first call, if so 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 end +end + +local function ExeUpdate(prog_tbl) + dt.preferences.write(mod, 'bin_exists', 'bool', true) + for _,prog in pairs(prog_tbl) do + 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 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 @@ -340,6 +356,43 @@ GUI.run = dt.new_widget("button"){ tooltip ='run HDRMerge with the above specified settings', clicked_callback = function() main() end } +GUI.exe_chooser = dt.new_widget('file_chooser_button'){ + title = "Select HDRmerge executable", + value = df.get_executable_path_preference(HDRM.name), + is_directory = false +} +GUI.exe_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 +} +GUI.exes = dt.new_widget('box'){ + orientation = 'vertical', + GUI.exe_chooser, + GUI.exe_update +} +GUI.stack = dt.new_widget('stack'){ + active = 1, + GUI.options, + GUI.exes +} +if not dt.preferences.read(mod, 'bin_exists', 'bool') then + GUI.stack.active = 2 +end + dt.register_lib( -- register HDRMerge module "HDRMerge_Lib", -- Module name "HDRMerge", -- name @@ -348,15 +401,6 @@ dt.register_lib( -- register HDRMerge module {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 99}}, -- containers 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 + GUI.stack } ) From d88af9ffd9a102c68aab6ad9be765a8a4c406dc0 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 12 Mar 2019 11:20:04 -0400 Subject: [PATCH 49/61] Minor tweaks to last commit --- contrib/HDRMerge.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index f940907..d8d8327 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -144,7 +144,10 @@ local function PreCall(prog_tbl) --looks to see if this is the first call, if so prog.first_run = false end end - if not dt.preferences.read(mod, 'bin_exists', 'bool') then GUI.stack.active = 2 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) @@ -159,7 +162,12 @@ local function ExeUpdate(prog_tbl) end prog.first_run = false end - if dt.preferences.read(mod, 'bin_exists', 'bool') then GUI.stack.active = 1 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 @@ -385,11 +393,12 @@ GUI.exes = dt.new_widget('box'){ GUI.exe_update } GUI.stack = dt.new_widget('stack'){ - active = 1, GUI.options, GUI.exes } -if not dt.preferences.read(mod, 'bin_exists', 'bool') then +if dt.preferences.read(mod, 'bin_exists', 'bool') then + GUI.stack.active = 1 +else GUI.stack.active = 2 end From 844e89a652b85cb9a262ef5264ab1c2b4b0fde34 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 12 Mar 2019 12:25:37 -0400 Subject: [PATCH 50/61] Use file chooser to set executable path preference previous commit failed to actually utilize the newly created file chooser to set the path preference --- contrib/HDRMerge.lua | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index d8d8327..57d19d4 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -81,9 +81,10 @@ local GUI = { --GUI Elements Table run = {}, stack = {}, options = {}, - exes = {}, - exe_chooser = {}, - exe_update = {}, + exes = { + HDRMerge = {}, + update = {}, + } } --Detect User Styles-- @@ -152,6 +153,7 @@ end local function ExeUpdate(prog_tbl) dt.preferences.write(mod, 'bin_exists', 'bool', true) + dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value) for _,prog in pairs(prog_tbl) do prog.bin = df.check_if_bin_exists(prog.name) if not prog.bin then @@ -163,11 +165,11 @@ local function ExeUpdate(prog_tbl) 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 + 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 @@ -364,12 +366,12 @@ GUI.run = dt.new_widget("button"){ tooltip ='run HDRMerge with the above specified settings', clicked_callback = function() main() end } -GUI.exe_chooser = dt.new_widget('file_chooser_button'){ +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.exe_update = dt.new_widget('button'){ +GUI.exes.update = dt.new_widget('button'){ label = 'update', tooltip ='update the binary path with current value', clicked_callback = function() ExeUpdate({HDRM}) end @@ -387,17 +389,17 @@ GUI.options = dt.new_widget('box'){ GUI.Target.add_tags, GUI.run } -GUI.exes = dt.new_widget('box'){ +local exes_box = dt.new_widget('box'){ orientation = 'vertical', - GUI.exe_chooser, - GUI.exe_update + GUI.exes.HDRMerge, + GUI.exes.update } GUI.stack = dt.new_widget('stack'){ GUI.options, - GUI.exes + exes_box } if dt.preferences.read(mod, 'bin_exists', 'bool') then - GUI.stack.active = 1 + GUI.stack.active = 1 else GUI.stack.active = 2 end From 4b3b1f7786a602177e620734dbf882714ad6e615 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Tue, 12 Mar 2019 12:42:37 -0400 Subject: [PATCH 51/61] Fix bug when updating exe preference Move the preference.write() call inside the for loop --- contrib/HDRMerge.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 57d19d4..6b45781 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -153,8 +153,8 @@ end local function ExeUpdate(prog_tbl) dt.preferences.write(mod, 'bin_exists', 'bool', true) - dt.preferences.write('executable_paths', prog.name, 'string', GUI.exes[prog.name].value) 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 From 7ffdf1631ce597d2185927bc386728829a5d5f4b Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Fri, 22 Mar 2019 09:05:11 -0400 Subject: [PATCH 52/61] Make text translatable --- contrib/HDRMerge.lua | 111 +++++++++++++++++++++++-------------------- 1 file changed, 59 insertions(+), 52 deletions(-) diff --git a/contrib/HDRMerge.lua b/contrib/HDRMerge.lua index 6b45781..ad7dd3a 100644 --- a/contrib/HDRMerge.lua +++ b/contrib/HDRMerge.lua @@ -17,14 +17,14 @@ ]] --[[About this Plugin -This plugin adds the module "HDRMerge" to darktable's lighttable view +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. + 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 @@ -44,13 +44,20 @@ 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 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', @@ -89,9 +96,9 @@ local GUI = { --GUI Elements Table --Detect User Styles-- local styles = dt.styles -local styles_count = 1 -- "none" = 1 +local styles_count = 1 -- 'none' = 1 for _,i in pairs(dt.styles) do - if type(i) == "userdata" then styles_count = styles_count + 1 end + 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) @@ -114,10 +121,10 @@ local function GetFileName(full_path) --Parses a full path (path/filename_identi 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*") + 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 @@ -147,7 +154,7 @@ local function PreCall(prog_tbl) --looks to see if this is the first call, if so end if not dt.preferences.read(mod, 'bin_exists', 'bool') then GUI.stack.active = 2 - dt.print('please update you binary locatoin') + dt.print(_('please update you binary locatoin')) end end @@ -166,9 +173,9 @@ local function ExeUpdate(prog_tbl) end if dt.preferences.read(mod, 'bin_exists', 'bool') then GUI.stack.active = 1 - dt.print('update successful') + dt.print(_('update successful')) else - dt.print('update unsuccessful, please try again') + dt.print(_('update unsuccessful, please try again')) end end @@ -182,13 +189,13 @@ 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') + 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') + dt.print(_('not enough images selected, select at least 2 images to merge')) return end @@ -203,7 +210,7 @@ local function main() 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).." " + 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) @@ -249,23 +256,23 @@ local function main() dt.tags.attach(tag, imported) end end - dt.print('HDRMerge completed successfully') + dt.print(_('HDRMerge completed successfully')) else - dt.print_error('HDRMerge failed') - dt.print('HDRMerge failed') + dt.print_error(_('HDRMerge failed')) + dt.print(_('HDRMerge failed')) end end -- GUI Elements -- local lbl_hdr = dt.new_widget('section_label'){ - label = 'HDRMerge options' + 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', + label = _('bits per sample'), + tooltip =_('number of bits per sample in the output image'), selected = temp, '16','24','32', changed_callback = function(self) @@ -281,10 +288,10 @@ GUI.HDR.bps = dt.new_widget('combobox'){ 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', + label = _('embedded preview size'), + tooltip =_('size of the embedded preview in output image'), selected = temp, - 'none','half','full', + _('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) @@ -296,9 +303,9 @@ GUI.HDR.size = dt.new_widget('combobox'){ end } GUI.HDR.batch = dt.new_widget('check_button'){ - label = 'batch mode', + label = _('batch mode'), value = dt.preferences.read(mod, 'active_batch', 'bool'), - tooltip = 'enable batch mode operation \nNOTE: resultant files will NOT be auto-imported', + 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 @@ -308,8 +315,8 @@ GUI.HDR.batch = dt.new_widget('check_button'){ 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', + label = _('batch gap [sec.]'), + tooltip = _('gap, in seconds, between batch mode groups'), soft_min = 1, soft_max = 30, hard_min = 1, @@ -323,13 +330,13 @@ GUI.HDR.gap = dt.new_widget('slider'){ end } local lbl_import = dt.new_widget('section_label'){ - label = 'import options' + 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", + label = _('apply style on import'), + tooltip = _('Apply selected style on auto-import to newly created image'), selected = 1, - "none", + _('none'), changed_callback = function(self) dt.preferences.write(mod, 'active_style', 'string', self.value) dt.preferences.write(mod, 'active_style_ind', 'integer', self.selected) @@ -347,33 +354,33 @@ 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', + label = _('copy tags'), value = dt.preferences.read(mod, 'active_copy_tags', 'bool'), - tooltip = 'copy tags from first source image', + 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", +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", + placeholder = _('Enter tags, seperated by commas'), editable = true } -GUI.run = dt.new_widget("button"){ - label = 'merge', - tooltip ='run HDRMerge with the above specified settings', +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", + 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', + label = _('update'), + tooltip =_('update the binary path with current value'), clicked_callback = function() ExeUpdate({HDRM}) end } GUI.options = dt.new_widget('box'){ @@ -399,19 +406,19 @@ GUI.stack = dt.new_widget('stack'){ exes_box } if dt.preferences.read(mod, 'bin_exists', 'bool') then - GUI.stack.active = 1 + GUI.stack.active = 1 else GUI.stack.active = 2 end dt.register_lib( -- register HDRMerge module - "HDRMerge_Lib", -- Module name - "HDRMerge", -- name + '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", + {[dt.gui.views.lighttable] = {'DT_UI_CONTAINER_PANEL_RIGHT_CENTER', 99}}, -- containers + dt.new_widget('box'){ + orientation = 'vertical', GUI.stack } ) From bf6e180291551f7d1b0e60605b8ab83de39b4762 Mon Sep 17 00:00:00 2001 From: Bill Ferguson Date: Thu, 28 Mar 2019 20:13:08 -0400 Subject: [PATCH 53/61] Fixed bug that caused the lua executale path to not split correctly on windows. Preferences are stored internally with forward slashes, not O/S specific path separators. --- tools/executable_manager.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/executable_manager.lua b/tools/executable_manager.lua index 5f79074..03e35f6 100644 --- a/tools/executable_manager.lua +++ b/tools/executable_manager.lua @@ -112,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 From a81d99255249ea64762bb6918227ad2459d5f9cf Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 14:27:17 +0200 Subject: [PATCH 54/61] On Windows we don't need anything to open a file On Windows we don't need any command to open a file. And "start" 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 --- lib/dtutils/system.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/dtutils/system.lua b/lib/dtutils/system.lua index 69a7f69..f202d6d 100644 --- a/lib/dtutils/system.lua +++ b/lib/dtutils/system.lua @@ -122,7 +122,7 @@ dtutils_system.libdoc.functions["launch_default_app"] = { function dtutils_system.launch_default_app(path) local open_cmd = "xdg-open " if (dt.configuration.running_os == "windows") then - open_cmd = "start " + 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 @@ -130,4 +130,4 @@ function dtutils_system.launch_default_app(path) end -return dtutils_system \ No newline at end of file +return dtutils_system From d355095e16a66e0d4f1b580777959165b20aaaeb Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 15:27:53 +0200 Subject: [PATCH 55/61] file.lua copy for windows --- lib/dtutils/file.lua | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 69243e9..90fdf08 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -291,9 +291,13 @@ 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 + os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') + result = true + 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') From a383d1ebf8b7195a6e0ea449b0e926f16c4eb5e2 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 15:48:31 +0200 Subject: [PATCH 56/61] Update kml_export.lua Works with Windows --- contrib/kml_export.lua | 323 +++++++++++++---------------------------- 1 file changed, 102 insertions(+), 221 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 7ba7dbf..4a1d5e2 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -20,17 +20,14 @@ 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 @@ -39,12 +36,14 @@ USAGE 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 local PS = dt.configuration.running_os == "windows" and "\\" or "/" -du.check_min_api_version("3.0.0", kml_export) +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/") @@ -53,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 ) @@ -230,41 +71,34 @@ 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 - end - dt.print_error("Will try to export KML file now") + local magickPath = dt.preferences.read("kml_export","magickPath","string") + 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"..PS - local mkdirCommand = "mkdir -p "..exportDirectory..PS..imageFoldername - dt.control.execute(mkdirCommand) + df.mkdir(df.sanitize_filename(exportDirectory..PS..imageFoldername)) end -- Create the thumbnails @@ -275,15 +109,17 @@ 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..PS..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" + + df.file_copy(exported_image, exportDirectory..PS..imageFoldername..filename.."."..filetype) + dsys.external_command(convertToThumbCommand) else -- Remove exported image if it has no GPS data os.remove(exported_image) @@ -294,7 +130,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" @@ -324,12 +172,12 @@ local function create_kml_file(storage, image_table, extra_data) local image_title, image_description if (image.title and image.title ~= "") then - image_title = string.escapeXmlCharacters(image.title) + image_title = ds.escape_xml_characters(image.title) else image_title = filename..extension end -- Characters should not be escaped in CDATA, but we are using HTML fragment, so we must escape them - image_description = string.escapeXmlCharacters(image.description) + image_description = ds.escape_xml_characters(image.description) kml_file = kml_file.." "..image_title.."\n" kml_file = kml_file.." "..image_description.."\n" @@ -374,7 +222,7 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.." \n" kml_file = kml_file.." \n" - for image,exported_image in spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do + for image,exported_image in du.spairs(image_table, function(t,a,b) return b.exif_datetime_taken > a.exif_datetime_taken end) do if ((image.longitude and image.latitude) and (image.longitude ~= 0 and image.latitude ~= 90) -- Sometimes the north-pole but most likely just wrong data ) then @@ -397,13 +245,15 @@ local function create_kml_file(storage, image_table, extra_data) kml_file = kml_file.."" 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 " @@ -428,40 +278,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..PS.."\""..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..PS.."\""..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", @@ -469,15 +344,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; From f08421a48ca04c12b1586eb29969e748e0721aab Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 15:55:22 +0200 Subject: [PATCH 57/61] Update kml_export.lua Fixed Windows problem --- contrib/kml_export.lua | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index c809c45..4a1d5e2 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -92,12 +92,10 @@ local function create_kml_file(storage, image_table, extra_data) 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"..PS df.mkdir(df.sanitize_filename(exportDirectory..PS..imageFoldername)) @@ -119,6 +117,8 @@ local function create_kml_file(storage, image_table, extra_data) -- profiles that might be present in the input and aren't needed in the thumbnail. local convertToThumbCommand = ds.sanitize(magickPath) .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" + + df.file_copy(exported_image, exportDirectory..PS..imageFoldername..filename.."."..filetype) dsys.external_command(convertToThumbCommand) else -- Remove exported image if it has no GPS data @@ -142,6 +142,7 @@ local function create_kml_file(storage, image_table, extra_data) 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" @@ -292,12 +293,21 @@ local function create_kml_file(storage, image_table, extra_data) 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 ) +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 local defaultDir = '' if dt.configuration.running_os == "windows" then @@ -344,7 +354,11 @@ if dt.configuration.running_os == "linux" then 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; From ffd6abbe95c997cd38ec95ead8d5292d1bb37d03 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 21:28:48 +0200 Subject: [PATCH 58/61] Fixed bugs after Linux testing --- contrib/kml_export.lua | 37 ++++++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/contrib/kml_export.lua b/contrib/kml_export.lua index 4a1d5e2..b091c4f 100644 --- a/contrib/kml_export.lua +++ b/contrib/kml_export.lua @@ -72,7 +72,12 @@ end local function create_kml_file(storage, image_table, extra_data) - local magickPath = dt.preferences.read("kml_export","magickPath","string") + local magickPath + if dt.configuration.running_os == "linux" then + magickPath = 'convert' + else + magickPath = dt.preferences.read("kml_export","magickPath","string") + end if not df.check_if_bin_exists(magickPath) then dt.print_error(_("magick not found")) return @@ -116,10 +121,12 @@ local function create_kml_file(storage, image_table, extra_data) -- 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 = ds.sanitize(magickPath) .. " -size 96x96 "..exported_image.." -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" "..exportDirectory..PS..imageFoldername.."thumb_"..filename..".jpg" + local convertToThumbCommand = ds.sanitize(magickPath) .. " -size 96x96 " .. exported_image .. " -resize 92x92 -mattecolor \"#FFFFFF\" -frame 2x2 +profile \"*\" " .. exportDirectory .. PS .. imageFoldername .. "thumb_" .. filename .. ".jpg" - df.file_copy(exported_image, exportDirectory..PS..imageFoldername..filename.."."..filetype) - dsys.external_command(convertToThumbCommand) + 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) @@ -132,17 +139,17 @@ local function create_kml_file(storage, image_table, extra_data) -- https://github.com/darktable-org/lua-scripts/issues/54 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 + -- 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" From f1d397f908d235d5a1ce3800a611c3d8eec4a373 Mon Sep 17 00:00:00 2001 From: supertobi Date: Mon, 15 Apr 2019 23:09:01 +0200 Subject: [PATCH 59/61] OpenInExplorer Changed from nautilus to org.freedesktop.FileManager1, so it works with all file manager that support this dbus API. --- contrib/OpenInExplorer.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/OpenInExplorer.lua b/contrib/OpenInExplorer.lua index 1dbe4d6..11f004f 100644 --- a/contrib/OpenInExplorer.lua +++ b/contrib/OpenInExplorer.lua @@ -81,7 +81,7 @@ local function open_in_nautilus() --Open in Nautilus elseif #images <= 15 then for _,image in pairs(images) do curr_image = image.path..PS..image.filename - local run_cmd = "nautilus --select " .. df.sanitize_filename(curr_image) + 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 From cadd98dc59358c5eb573bd617e61de9c4481dba9 Mon Sep 17 00:00:00 2001 From: supertobi Date: Tue, 16 Apr 2019 10:58:32 +0200 Subject: [PATCH 60/61] dtutils_file.file_copy: use the result --- lib/dtutils/file.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/dtutils/file.lua b/lib/dtutils/file.lua index 90fdf08..f4306ff 100644 --- a/lib/dtutils/file.lua +++ b/lib/dtutils/file.lua @@ -292,8 +292,7 @@ function dtutils_file.file_copy(fromFile, toFile) local result = nil -- if cp exists, use it if dt.configuration.running_os == "windows" then - os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') - result = true + result = os.execute('copy "' .. fromFile .. '" "' .. toFile .. '"') elseif dtutils_file.check_if_bin_exists("cp") then result = os.execute("cp '" .. fromFile .. "' '" .. toFile .. "'") end From 2c3536afc93867d05a4ca6bfd0703a5964865753 Mon Sep 17 00:00:00 2001 From: BzKevin <36381472+BzKevin@users.noreply.github.com> Date: Mon, 6 May 2019 10:49:30 -0400 Subject: [PATCH 61/61] Changes requestd via PR 1) mod changed to MOD 2) autogroup changed to autogrouper 3) prints "please select at least 2 images" if user attempts to run with fewer than 2 images selected 4) change visible module name to "auto group" (lower case) 5) changed soft max on slider to 60s - I also increase the hard max up to a full day, I saw no reason not to --- contrib/AutoGrouper.lua | 169 ++++++++++++++++++++-------------------- 1 file changed, 86 insertions(+), 83 deletions(-) diff --git a/contrib/AutoGrouper.lua b/contrib/AutoGrouper.lua index 3125363..0d92915 100644 --- a/contrib/AutoGrouper.lua +++ b/contrib/AutoGrouper.lua @@ -37,7 +37,7 @@ selected images, the other button performs grouping on the entire active collect ]] local dt = require "darktable" -local mod = 'autogroup' +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/") @@ -46,110 +46,113 @@ local function _(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 + 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 + 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} + 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) + 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 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 + 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 = {} + gap = {}, + selected = {}, + collection = {} } -temp = dt.preferences.read(mod, 'active_gap', 'integer') -if not InRange(temp, 1, 3600) then temp = 3 end +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 = 30, - hard_min = 1, - hard_max = 3600, - step = 1, - digits = 0, - value = temp, - reset_callback = function(self) - self.value = 3 - end + 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 + 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 + 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 - } + '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 + } )