diff --git a/contrib/ext_editor.lua b/contrib/ext_editor.lua new file mode 100644 index 0000000..9a9c834 --- /dev/null +++ b/contrib/ext_editor.lua @@ -0,0 +1,422 @@ +--[[ + + DESCRIPTION + ext_editor.lua - edit images with external editors + + This script provides helpers to edit image files with programs external to darktable. + It adds: + - a new target storage "collection". Image exported will be reimported to collection for + further edit with external programs + - a new lighttable module "external editors", to select a program from a list of up to + - 9 external editors and run it on a selected image + - a set of lua preferences in order to configure name and path of up to 9 external editors + - a set of lua shortcuts in order to quick launch the external editors + + USAGE + * require this script from main lua file + + -- setup -- + * in "preferences/lua options" configure name and path/command of external programs + * note that if a program name is left empty, that and all following entries will be ignored + * in "preferences/shortcuts/lua" configure shortcuts for external programs (optional) + * whenever programs preferences are changed, in lighttable/external editors, press "update list" + + -- use -- + * in the export dialog choose "collection" and select the format and bit depth for the + exported image + * press "export" + * the exported image will be imported into collection and grouped with the original image + + * select an image for editing with en external program, and: + * in lighttable/external editors, select program and press "edit" + * edit the image with the external editor, overwite the file, quit the external program + * the selected image will be updated + or + * in lighttable/external editors, select program and press "edit a copy" + * edit the image with the external editor, overwite the file, quit the external program + * a copy of the selected image will be created and updated + or + * in lighttable select target storage "collection" + * enter in darkroom + * to create an export or a copy press CRTL+E + * use the shortcut to edit the current image with the corresponding external editor + * overwite the file, quit the external program + * the darkroom view will be updated + + * warning: mouseover on lighttable/filmstrip will prevail on current image + * this is the default DT behavior, not a bug of this script + + CAVEATS + * MAC compatibility not tested + + TODO + * send multiple images to the same program, maybe + + BUGS, COMMENTS, SUGGESTIONS + * send to Marco Carrarini, marco.carrarini@gmail.com + + CHANGES + * 20191224 - initial version + * 20191227 - added button "update list", better error handling, fixed bug with groups/tags in "edit" + +]] + + +local dt = require "darktable" +local du = require "lib/dtutils" +local df = require "lib/dtutils.file" +local dtsys = require "lib/dtutils.system" + + +-- module name +local MODULE_NAME = "ext_editor" + + +-- check API version +du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.0 + + +-- OS compatibility +local PS = dt.configuration.running_os == "windows" and "\\" or "/" + + +-- translation +local gettext = dt.gettext +gettext.bindtextdomain(MODULE_NAME, dt.configuration.config_dir..PS.."lua"..PS.."locale"..PS) + +local function _(msgid) + return gettext.dgettext(MODULE_NAME, msgid) +end + + +-- number of valid entries in the list of external programs +local n_entries = 0 + + +-- last used editor initialization +if not dt.preferences.read(MODULE_NAME,"initialized", "bool") then + dt.preferences.write(MODULE_NAME,"lastchoice", "integer", 1) + dt.preferences.write(MODULE_NAME,"initialized", "bool", true) + end +local lastchoice = 1 + + +-- update lists of program names and paths, as well as combobox --------------- +local function UpdateProgramList(combobox, button_pressed) + + -- initialize lists + program_names = {} + program_paths = {} + + -- build lists from preferences + local name + local last = false + for i = 1, 9 do + name = dt.preferences.read(MODULE_NAME,"program_name_"..i, "string") + if (name == "" or name == nil) then last = true end + if last then + if combobox[n_entries + 1] then combobox[n_entries + 1] = nil end -- remove extra combobox entries + else + combobox[i] = i..": "..name + program_names[i] = name + program_paths[i] = df.sanitize_filename(dt.preferences.read(MODULE_NAME, "program_path_"..i, "string")) + n_entries = i + end + end + lastchoice = dt.preferences.read(MODULE_NAME, "lastchoice", "integer") + if lastchoice > n_entries then + lastchoice = 1 + dt.preferences.write(MODULE_NAME, "lastchoice", "integer", lastchoice) + end + combobox.value = lastchoice + if button_pressed then dt.print(_("list updated")) end + end + + +-- shows export progress ------------------------------------------------------ +local function show_status(storage, image, format, filename, number, total, high_quality, extra_data) + + dt.print(_("exporting image ").. number.." / "..total.." ...") + end + + +-- callback for buttons "edit" and "edit a copy" ------------------------------ +local function OpenWith(images, choice, copy) + + -- check choice is valid, return if not + if choice > n_entries then + dt.print(_("not a valid choice")) + return + end + + -- check if one image is selected, return if not + if #images ~= 1 then + dt.print(_("please select one image")) + return + end + + local bin = program_paths[choice] + local friendly_name = program_names[choice] + + -- check if external program executable exists, return if not + if not df.check_if_bin_exists(bin) then + dt.print(friendly_name.._(" not found")) + return + end + + -- image to be edited + local image + i, image = next(images) + local name = image.path..PS..image.filename + + -- check if image is raw, return if is + if image.is_raw then + dt.print(_("RAW not allowed")) + return + end + + -- save image tags, rating and color + local tags = {} + for i, tag in ipairs(dt.tags.get_tags(image)) do + if not (string.sub(tag.name, 1, 9) == "darktable") then table.insert(tags, tag) end + end + local rating = image.rating + local red = image.red + local blue = image.blue + local green = image.green + local yellow = image.yellow + local purple = image.purple + + -- new image + local new_name = name + local new_image = image + + if copy then + + -- create unique filename + while true do -- dirty solution to workaround issue in lib function check_if_file_exists() + if dt.configuration.running_os == "windows" then + if not df.check_if_file_exists(df.sanitize_filename(new_name)) then break end + else + if not df.check_if_file_exists(new_name) then break end + end + new_name = df.filename_increment(new_name) + -- limit to 50 more exports of the original export + if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end + end + + -- physical copy, check result, return if error + local copy_success = df.file_copy(name, new_name) + if not copy_success then + dt.print(_("error copying file ")..name) + return + end + end + + -- launch the external editor, check result, return if error + local run_cmd = bin.." "..df.sanitize_filename(new_name) + dt.print(_("launching ")..friendly_name.."...") + local result = dtsys.external_command(run_cmd) + if result ~= 0 then + dt.print(_("error launching ")..friendly_name) + return + end + + if copy then + -- import in database and group + new_image = dt.database.import(new_name) + new_image:group_with(image) + else + -- refresh the image view + -- note that only image:drop_cache() is not enough to refresh view in darkroom mode + -- therefore image must be deleted and reimported to force refresh + + -- find the grouping status + local image_leader = image.group_leader + local group_members = image:get_group_members() + local new_leader + local index = nil + local found = false + + -- membership status, three different cases + if image_leader == image then + if #group_members > 1 then + -- case 1: image is leader in a group with more members + while not found do + index, new_leader = next(group_members, index) + if new_leader ~= image_leader then found = true end + end + new_leader:make_group_leader() + image:delete() + new_image = dt.database.import(name) + new_image:group_with(new_leader) + new_image:make_group_leader() + else + -- case 2: image is the only member in group + image:delete() + new_image = dt.database.import(name) + new_image:group_with() + end + else + -- case 3: image is in a group but is not leader + image:delete() + new_image = dt.database.import(name) + new_image:group_with(image_leader) + end + -- refresh darkroom view + if dt.gui.current_view() == dt.gui.views.darkroom then + dt.gui.views.darkroom.display_image(new_image) + end + end + + -- restore image tags, rating and color, must be put after refresh darkroom view + for i, tag in ipairs(tags) do dt.tags.attach(tag, new_image) end + new_image.rating = rating + new_image.red = red + new_image.blue = blue + new_image.green = green + new_image.yellow = yellow + new_image.purple = purple + + -- select the new image + local selection = {} + table.insert(selection, new_image) + dt.gui.selection (selection) + + -- remember the last editor used + dt.preferences.write(MODULE_NAME, "lastchoice", "integer", choice) + end + + +-- callback function for shortcuts -------------------------------------------- +local function program_shortcut(event, shortcut) + OpenWith(dt.gui.action_images, tonumber(string.sub(shortcut, -1)), false) + end + + +-- export images and reimport in collection ----------------------------------- +local function export2collection(storage, image_table, extra_data) + + local new_name, new_image, result + + for image, temp_name in pairs(image_table) do + + -- images are first exported in temp folder then moved to collection folder + + -- create unique filename + new_name = image.path..PS..df.get_filename(temp_name) + while true do -- dirty solution to workaround issue in lib function check_if_file_exists() + if dt.configuration.running_os == "windows" then + if not df.check_if_file_exists(df.sanitize_filename(new_name)) then break end + else + if not df.check_if_file_exists(new_name) then break end + end + new_name = df.filename_increment(new_name) + -- limit to 50 more exports of the original export + if string.match(df.get_basename(new_name), "_%d%d$") == "_50" then break end + end + + -- move image to collection folder, check result, return if error + move_success = df.file_move(temp_name, new_name) + if not move_success then + dt.print(_("error moving file ")..temp_name) + return + end + + -- import in database and group + new_image = dt.database.import(new_name) + new_image:group_with(image.group_leader) + end + end + + +-- register new storage ------------------------------------------------------- +-- note that placing this declaration later makes the export selected module +-- not to remember the choice "collection" when restarting DT, don't know why +dt.register_storage("exp2coll", _("collection"), show_status, export2collection) + + +-- combobox, with variable number of entries ---------------------------------- +local combobox = dt.new_widget("combobox") { + label = _("choose program"), + tooltip = _("select the external editor from the list"), + value = 1, + "" + } +UpdateProgramList(combobox, false) -- initialize list of programs and combobox + + +-- button edit ---------------------------------------------------------------- +local button1 = dt.new_widget("button") { + label = _("edit"), + tooltip = _("open the selected image in external editor"), + clicked_callback = function() + OpenWith(dt.gui.action_images, combobox.selected, false) + end + } + + +-- button edit a copy --------------------------------------------------------- +local button2 = dt.new_widget("button") { + label = _("edit a copy"), + tooltip = _("create a copy of the selected image and open it in external editor"), + clicked_callback = function() + OpenWith(dt.gui.action_images, combobox.selected, true) + end + } + + +-- button update list --------------------------------------------------------- +local button3 = dt.new_widget("button") { + label = _("update list"), + tooltip = _("update list of programs if lua preferences are changed"), + clicked_callback = function() + UpdateProgramList(combobox, true) + end + } + + +-- box for the buttons -------------------------------------------------------- +-- it doesn't seem there is a way to make the buttons equal in size +local box1 = dt.new_widget("box") { + orientation = "horizontal", + button1, + button2, + button3 + } + + +-- register new module "external editors" in lighttable ------------------------ +dt.register_lib( + MODULE_NAME, + _("external editors"), + true, -- expandable + false, -- resetable + {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, + dt.new_widget("box") { + orientation = "vertical", + combobox, + box1 + }, + nil, -- view_enter + nil -- view_leave + ) + + +-- register the new preferences ----------------------------------------------- +for i = 9, 1, -1 do + dt.preferences.register(MODULE_NAME, "program_path_"..i, "string", _("command for external editor ")..i, _("command for external editor, including full path if needed"), "") + dt.preferences.register(MODULE_NAME, "program_name_"..i, "string", _("name of external editor ")..i, _("friendly name of external editor"), "") + end + + +-- register the new shortcuts ------------------------------------------------- +for i = 1, 9 do + dt.register_event("shortcut", program_shortcut, _("edit with program ")..i) + end + + +-- end of script -------------------------------------------------------------- + +-- vim: shiftwidth=4 expandtab tabstop=4 cindent syntax=lua +-- kate: hl Lua; diff --git a/locale/it_IT/LC_MESSAGES/ext_editor.mo b/locale/it_IT/LC_MESSAGES/ext_editor.mo new file mode 100644 index 0000000..1447df4 Binary files /dev/null and b/locale/it_IT/LC_MESSAGES/ext_editor.mo differ diff --git a/locale/it_IT/LC_MESSAGES/ext_editor.po b/locale/it_IT/LC_MESSAGES/ext_editor.po new file mode 100644 index 0000000..490fd33 --- /dev/null +++ b/locale/it_IT/LC_MESSAGES/ext_editor.po @@ -0,0 +1,119 @@ +# Italian translations for ext_editor package. +# Copyright (C) 2019 THE ext_editor'S COPYRIGHT HOLDER +# This file is distributed under the same license as the ext_editor package. +# marco , 2019. +# +msgid "" +msgstr "" +"Project-Id-Version: ext_editor\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-12-29 15:33+0100\n" +"PO-Revision-Date: 2019-12-28 21:45+0100\n" +"Last-Translator: marco \n" +"Language-Team: Italian\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=ASCII\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" + +#: ext_editor.lua:132 +msgid "list updated" +msgstr "Lista aggiornata" + +#: ext_editor.lua:139 +msgid "exporting image " +msgstr "Esportazione immagine " + +#: ext_editor.lua:148 +msgid "not a valid choice" +msgstr "Scelta non valida" + +#: ext_editor.lua:154 +msgid "please select one image" +msgstr "Selezionare un'immagine" + +#: ext_editor.lua:163 +msgid " not found" +msgstr " non trovato" + +#: ext_editor.lua:174 +msgid "RAW not allowed" +msgstr "RAW non consentito" + +#: ext_editor.lua:211 +msgid "error copying file " +msgstr "Errore copia file " + +#: ext_editor.lua:218 +msgid "launching " +msgstr "Lancio " + +#: ext_editor.lua:221 +msgid "error launching " +msgstr "Errore lancio " + +#: ext_editor.lua:322 +msgid "error moving file " +msgstr "Errore spostamento file " + +#: ext_editor.lua:336 +msgid "collection" +msgstr "Collezione" + +#: ext_editor.lua:341 +msgid "choose program" +msgstr "Scegli programma" + +#: ext_editor.lua:342 +msgid "select the external editor from the list" +msgstr "Seleziona l'editor esterno dalla lista" + +#: ext_editor.lua:351 +msgid "edit" +msgstr "Modifica" + +#: ext_editor.lua:352 +msgid "open the selected image in external editor" +msgstr "Apre l'immagine selezionata nell'editor esterno" + +#: ext_editor.lua:361 +msgid "edit a copy" +msgstr "Modifica copia" + +#: ext_editor.lua:362 +msgid "create a copy of the selected image and open it in external editor" +msgstr "apre una copia dell'immagine selezionata nell'editor esterno" + +#: ext_editor.lua:371 +msgid "update list" +msgstr "Aggiorna lista" + +#: ext_editor.lua:372 +msgid "update list of programs if lua preferences are changed" +msgstr "Aggiorna la lista dei programmi se le preferenze di lua sono cambiate" + +#: ext_editor.lua:392 +msgid "external editors" +msgstr "Editor esterni" + +#: ext_editor.lua:408 +msgid "command for external editor " +msgstr "Comando per l'editor esterno " + +#: ext_editor.lua:408 +msgid "command for external editor, including full path if needed" +msgstr "" +"Comando per l'editor esterno, compreso il percorso completo se necessario" + +#: ext_editor.lua:409 +msgid "name of external editor " +msgstr "Nome dell'editor esterno " + +#: ext_editor.lua:409 +msgid "friendly name of external editor" +msgstr "Nome comune dell'editor esterno" + +#: ext_editor.lua:415 +msgid "edit with program " +msgstr "Modifica con programma "