|
|
@@ -0,0 +1,358 @@
|
|
|
+--[[ Export image for blog, duplicate and set it LOCKED ]]
|
|
|
+local script_path = debug.getinfo(1, "S").source:sub(2):match("(.*/)")
|
|
|
+package.path = script_path .. "?.lua;" .. package.path
|
|
|
+local config = require("config")
|
|
|
+email_1 = config.email_1
|
|
|
+email_2 = config.email_2
|
|
|
+email_3 = config.email_3
|
|
|
+
|
|
|
+local image_max_x = 2000
|
|
|
+local image_max_y = 2000
|
|
|
+
|
|
|
+-- local jpg_quality_str = '92'
|
|
|
+
|
|
|
+-- module name
|
|
|
+local MODULE_NAME = "mail2blog"
|
|
|
+
|
|
|
+local df = require "lib/dtutils.file"
|
|
|
+local dt = require "darktable"
|
|
|
+local du = require "lib/dtutils"
|
|
|
+local log = require 'lib/dtutils.log'
|
|
|
+local dtsys = require "lib/dtutils.system"
|
|
|
+
|
|
|
+local function quote(text)
|
|
|
+ return '"' .. text .. '"'
|
|
|
+end
|
|
|
+
|
|
|
+local charset = {} do -- [0-9a-zA-Z]
|
|
|
+ for c = 48, 57 do table.insert(charset, string.char(c)) end
|
|
|
+ for c = 65, 90 do table.insert(charset, string.char(c)) end
|
|
|
+ for c = 97, 122 do table.insert(charset, string.char(c)) end
|
|
|
+end
|
|
|
+
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+-- V E R S I O N C H E C K
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+du.check_min_api_version("5.0.2", MODULE_NAME) -- darktable 3.0
|
|
|
+
|
|
|
+-- script_manager integration to allow a script to be removed
|
|
|
+-- without restarting darktable
|
|
|
+local function destroy()
|
|
|
+ -- nothing to destroy
|
|
|
+end
|
|
|
+
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+-- C O N S T A N T S
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+local PS = dt.configuration.running_os == "windows" and "\\" or "/"
|
|
|
+
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+-- T R A N S L A T I O N S
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+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
|
|
|
+
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+-- M A I N
|
|
|
+-- - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
+
|
|
|
+-- alias dt.control.sleep to sleep
|
|
|
+local sleep = dt.control.sleep
|
|
|
+
|
|
|
+---------------------------------------------------------------
|
|
|
+-- some helper methods to log information messages
|
|
|
+
|
|
|
+log.log_level(log.info) -- log.info or log.warn or log.debug
|
|
|
+
|
|
|
+local LogCurrentStep = ''
|
|
|
+local LogMajorNr = 0
|
|
|
+local LogMajorMax = 0
|
|
|
+local LogSummaryMessages = {}
|
|
|
+
|
|
|
+local function GetLogInfoText(text)
|
|
|
+ return '[' .. LogMajorNr .. '/' .. LogMajorMax .. '] ' .. LogCurrentStep .. ': ' .. text
|
|
|
+end
|
|
|
+
|
|
|
+local function LogInfo(text)
|
|
|
+ log.msg(log.info, GetLogInfoText(text))
|
|
|
+end
|
|
|
+
|
|
|
+local function LogScreen(text)
|
|
|
+ log.msg(log.screen, text)
|
|
|
+end
|
|
|
+
|
|
|
+local function LogSummaryClear()
|
|
|
+ for k, v in pairs(LogSummaryMessages) do
|
|
|
+ LogSummaryMessages[k] = nil
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function LogSummaryMessage(text)
|
|
|
+ table.insert(LogSummaryMessages, GetLogInfoText(text))
|
|
|
+end
|
|
|
+
|
|
|
+-- ----------------------------------------------------
|
|
|
+local function set_published_tag ( email, image )
|
|
|
+ if email == 'postie_markus_spring.info@markus-spring.de' then
|
|
|
+ local tagnr = dt.tags.find('photography|published|blog')
|
|
|
+ dt.tags.attach(tagnr,image)
|
|
|
+ elseif email == 'postie_vhs_fotogruppe_reichenhall@markus-spring.de' then
|
|
|
+ local tagnr = dt.tags.find('photography|published|vhs-fotogruppe')
|
|
|
+ dt.tags.attach(tagnr,image)
|
|
|
+ -- else
|
|
|
+ -- local tagnr = dt.tags.find('photography|published|instagram')
|
|
|
+ -- dt.tags.attach(tagnr,image)
|
|
|
+ end
|
|
|
+ local tagnr = dt.tags.find('LOCKED')
|
|
|
+ dt.tags.attach(tagnr,image)
|
|
|
+end
|
|
|
+
|
|
|
+local function getPureFilename(path)
|
|
|
+ return string.match(path, "([^/\\]+)$")
|
|
|
+end
|
|
|
+
|
|
|
+local function set_metadata_note ( tmp_exported, image )
|
|
|
+ if image then
|
|
|
+ local current_notes = image.notes or "" -- Retrieve existing notes or set as empty
|
|
|
+ if current_notes ~= "" then
|
|
|
+ current_notes = current_notes .. "; "
|
|
|
+ end
|
|
|
+ image.notes = current_notes .. 'published as ' .. getPureFilename(tmp_exported)
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function set_rating_min_2 ( image )
|
|
|
+ if image.rating < 2 then
|
|
|
+ image.rating = 2
|
|
|
+ end
|
|
|
+end
|
|
|
+
|
|
|
+local function get_executable( binaryname, binarystring )
|
|
|
+ local binary = dt.preferences.read(MODULE_NAME, binaryname, "string")
|
|
|
+ if binary == "" then
|
|
|
+ dt.print(_( binarystring .. " executable not configured"))
|
|
|
+ return
|
|
|
+ end
|
|
|
+ binary = df.sanitize_filename(binary)
|
|
|
+ return binary
|
|
|
+end
|
|
|
+
|
|
|
+local function run_exiftool ( file, tmp_exported, flags )
|
|
|
+ local exiftoolbinary = get_executable("exiftoolbinary", "exiftool")
|
|
|
+ run_cmd = exiftoolbinary..' -TagsFromFile '..file..' '..flags..' '..tmp_exported
|
|
|
+ LogInfo(string.format("Running %s", run_cmd))
|
|
|
+ local job = dt.gui.create_job(string.format("Running %s", run_cmd), true, stop_job)
|
|
|
+ resp = dtsys.external_command(run_cmd)
|
|
|
+ job.valid = false
|
|
|
+end
|
|
|
+
|
|
|
+local function isempty(s)
|
|
|
+ return s == nil or s == ''
|
|
|
+end
|
|
|
+
|
|
|
+---------------------------------------------------------------
|
|
|
+-- helper functions to access darktable feature via user interface
|
|
|
+-- use event handling helper functions to wait for pixel pipe
|
|
|
+-- processing to complete
|
|
|
+
|
|
|
+local function numberToString(number, nilReplacement, nanReplacement)
|
|
|
+ -- convert given number to string
|
|
|
+ -- return 'not a number' and 'nil' as '0/0'
|
|
|
+ -- log output equals to dt.gui.action command and parameters
|
|
|
+ if (number ~= number) then
|
|
|
+ return nanReplacement or '0/0'
|
|
|
+ end
|
|
|
+
|
|
|
+ if (number == nil) then
|
|
|
+ return nilReplacement or '0/0'
|
|
|
+ end
|
|
|
+
|
|
|
+ -- some digits with dot
|
|
|
+ local result = string.format('%.4f', number)
|
|
|
+ result = string.gsub(result, ',', '.')
|
|
|
+
|
|
|
+ return result
|
|
|
+end
|
|
|
+
|
|
|
+-- convert values to boolean, consider not a number and nil
|
|
|
+local function GuiActionValueToBoolean(value)
|
|
|
+ -- NaN
|
|
|
+ if (value ~= value) then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ -- nil
|
|
|
+ if (value == nil) then
|
|
|
+ return false
|
|
|
+ end
|
|
|
+
|
|
|
+ return value ~= 0
|
|
|
+end
|
|
|
+
|
|
|
+function ThreadSleep(milliseconds)
|
|
|
+ -- local timeout = StepTimeout:Value()
|
|
|
+ local timeout = 500
|
|
|
+ local factor = milliseconds / timeout
|
|
|
+ LogInfo(' '..string.format(_("wait for %d ms. (config = %s ms * %s)"), milliseconds, timeout, factor))
|
|
|
+ dt.control.sleep(milliseconds)
|
|
|
+end
|
|
|
+
|
|
|
+-- perform the specified effect on the path and element of an action
|
|
|
+-- see https://docs.darktable.org/lua/stable/lua.api.manual/darktable/gui/action/
|
|
|
+local function GuiActionInternal(path, instance, element, effect, speed, waitForPipeline)
|
|
|
+ LogInfo('dt.gui.action(' ..
|
|
|
+ quote(path) ..
|
|
|
+ ',' .. instance .. ',' .. quote(element) .. ',' .. quote(effect) .. ',' .. numberToString(speed) .. ')')
|
|
|
+
|
|
|
+ local result
|
|
|
+
|
|
|
+ -- if (waitForPipeline) then
|
|
|
+ -- WaitForPixelPipe:Do(function()
|
|
|
+ -- result = dt.gui.action(path, instance, element, effect, speed)
|
|
|
+ -- end)
|
|
|
+ -- else
|
|
|
+ result = dt.gui.action(path, instance, element, effect, speed)
|
|
|
+ -- wait a bit...
|
|
|
+ -- ThreadSleep(StepTimeout:Value() / 2)
|
|
|
+ ThreadSleep(500 / 2)
|
|
|
+ return result
|
|
|
+end
|
|
|
+
|
|
|
+-- wait for 'pixelpipe-processing-complete'
|
|
|
+local function GuiAction(path, instance, element, effect, speed)
|
|
|
+ return GuiActionInternal(path, instance, element, effect, speed, true)
|
|
|
+end
|
|
|
+
|
|
|
+local function randomString(length)
|
|
|
+ if not length or length <= 0 then return '' end
|
|
|
+ math.randomseed(os.clock()//5)
|
|
|
+ return randomString(length - 1) .. charset[math.random(1, #charset)]
|
|
|
+end
|
|
|
+
|
|
|
+-- Function to replace German umlauts and sanitize the filename
|
|
|
+local function create_safe_filename(title)
|
|
|
+ local replacements = {
|
|
|
+ ["ä"] = "ae", ["ö"] = "oe", ["ü"] = "ue",
|
|
|
+ ["Ä"] = "Ae", ["Ö"] = "Oe", ["Ü"] = "Ue",
|
|
|
+ ["ß"] = "ss"
|
|
|
+ }
|
|
|
+ title = title:gsub("[%z\1-\127\194-\244][\128-\191]*", function(char)
|
|
|
+ return replacements[char] or char
|
|
|
+ end)
|
|
|
+ title = title:gsub("[^%w%-]", "_")
|
|
|
+ title = title:gsub("_+", "_")
|
|
|
+ title = title:gsub("^_+", ""):gsub("_+$", "")
|
|
|
+ if title == "" then
|
|
|
+ title = "untitled"
|
|
|
+ end
|
|
|
+ title = title:sub(1, 255)
|
|
|
+ return title
|
|
|
+end
|
|
|
+
|
|
|
+
|
|
|
+-- ----------------------------------------------------
|
|
|
+
|
|
|
+dt.print_log(MODULE_NAME .. ' loaded')
|
|
|
+-- save the configuration
|
|
|
+local current_view = dt.gui.current_view()
|
|
|
+
|
|
|
+local select_publication_target = dt.new_widget("combobox")
|
|
|
+{
|
|
|
+ label = "target",
|
|
|
+ tooltip = "Select blog for image publication",
|
|
|
+ changed_callback = function(w) dt.preferences.write(MODULE_NAME, "target", "string", w.selected) end,
|
|
|
+ email_1, email_2, email_3
|
|
|
+}
|
|
|
+
|
|
|
+local publication_button = dt.new_widget("button")
|
|
|
+{
|
|
|
+ label = "Publish!",
|
|
|
+ clicked_callback = function ()
|
|
|
+ for i_, i in ipairs(dt.gui.action_images) do
|
|
|
+ if isempty(i.title) then
|
|
|
+ local job = dt.gui.create_job("FEHLER: Das Bild " .. i.filename .. " hat keinen Titel.", true, stop_job)
|
|
|
+ os.execute("sleep " .. 3)
|
|
|
+ job.valid = false
|
|
|
+ else
|
|
|
+ set_rating_min_2( i )
|
|
|
+ -- create duplicate
|
|
|
+ local newimg = i.duplicate_with_history(i)
|
|
|
+ local jpeg_exporter = dt.new_format("jpeg")
|
|
|
+ jpeg_exporter.max_height = image_max_y
|
|
|
+ jpeg_exporter.max_width = image_max_x
|
|
|
+ -- local tmp_exported = os.tmpname()..".jpg"
|
|
|
+ local tempname = os.tmpname()
|
|
|
+ os.remove(tempname)
|
|
|
+ os.execute("mkdir " .. tempname)
|
|
|
+ local tmp_exported = tempname .. '/' .. create_safe_filename(i.title) .. '.jpg'
|
|
|
+ local job = dt.gui.create_job(string.format(_("Converting raw file '%s' to jpeg..."), i.filename), true, stop_job)
|
|
|
+ jpeg_exporter:write_image(i, tmp_exported, false)
|
|
|
+ -- copy exif data from original file
|
|
|
+ run_exiftool( df.sanitize_filename(i.path..PS..i.filename), tmp_exported, '-exif:all --subifd:all --Orientation -overwrite_original' )
|
|
|
+ -- copy exif data from xmp file
|
|
|
+ run_exiftool( df.sanitize_filename(i.sidecar) , tmp_exported, '-xmp:all -exif:all --subifd:all -overwrite_original' )
|
|
|
+ -- run mailscript
|
|
|
+ local spring2lifescript = get_executable("spring2lifescript", "spring2life script")
|
|
|
+ run_cmd = spring2lifescript .. " " .. tmp_exported
|
|
|
+ .. ' ' .. select_publication_target.value
|
|
|
+ .. ' ' .. df.sanitize_filename(i.filename) .. ' &'
|
|
|
+ LogInfo(run_cmd)
|
|
|
+ job = false
|
|
|
+ local job = dt.gui.create_job("Running " .. run_cmd, true, stop_job)
|
|
|
+ os.execute(run_cmd)
|
|
|
+ set_published_tag ( select_publication_target.value, i )
|
|
|
+ job = false
|
|
|
+ -- set_metadata_note ( tmp_exported, i )
|
|
|
+ LogScreen("Done")
|
|
|
+ --- dt.styles.delete(tmpstyle)
|
|
|
+ end
|
|
|
+ end
|
|
|
+ end
|
|
|
+}
|
|
|
+local lib_widgets = {}
|
|
|
+
|
|
|
+table.insert(lib_widgets, select_publication_target)
|
|
|
+table.insert(lib_widgets, publication_button)
|
|
|
+
|
|
|
+-- ... and tell dt about it all
|
|
|
+dt.register_lib(
|
|
|
+ MODULE_NAME, -- plugin name
|
|
|
+ MODULE_NAME, -- name
|
|
|
+ true, -- expandable
|
|
|
+ false, -- resetable
|
|
|
+ {[dt.gui.views.lighttable] = {"DT_UI_CONTAINER_PANEL_RIGHT_CENTER", 100}}, -- containers
|
|
|
+ dt.new_widget("box") -- widget
|
|
|
+ {
|
|
|
+ orientation = "vertical",
|
|
|
+ -- sensitive = enfuse_installed,
|
|
|
+ table.unpack(lib_widgets)
|
|
|
+ },
|
|
|
+ nil,-- view_enter
|
|
|
+ nil -- view_leave
|
|
|
+)
|
|
|
+
|
|
|
+-- end
|
|
|
+-- -- register the new preferences -----------------------------------------------
|
|
|
+
|
|
|
+dt.preferences.register(MODULE_NAME, "exiftoolbinary", "file",
|
|
|
+ _(MODULE_NAME .. ": executable for exiftool"),
|
|
|
+ _("select executable for exiftool command line version") , "")
|
|
|
+
|
|
|
+dt.preferences.register(MODULE_NAME, "spring2lifescript", "file",
|
|
|
+ _(MODULE_NAME .. ": executable for mail2spring2life script"),
|
|
|
+ _("select executable for mail2spring2life script") , "")
|
|
|
+
|
|
|
+-- ----------------------------------------------------------------------------------
|
|
|
+-- set the destroy routine so that script_manager can call it when
|
|
|
+-- it's time to destroy the script and then return the data to
|
|
|
+-- script_manager
|
|
|
+local script_data = {}
|
|
|
+script_data.destroy = destroy
|
|
|
+
|
|
|
+return script_data
|