From 4b183cb3a82158da9f34d55562cc5564e42c5dec Mon Sep 17 00:00:00 2001 From: Huy Hoang Date: Sat, 5 Mar 2022 16:03:08 -0600 Subject: [PATCH 1/3] RL_out_sharp: copy EXIF, add variable output path (copy from rename_images.lua), work around GMIC long filename problem --- contrib/RL_out_sharp.lua | 279 +++++++++++++++++++++++++++++++++------ 1 file changed, 241 insertions(+), 38 deletions(-) diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index 40cfb204..d8f165e2 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -19,19 +19,20 @@ DESCRIPTION RL_out_sharp.lua - Richardson-Lucy output sharpening using GMic - This script provides a new target storage "RL output sharpen". + This script provides a new target storage "RL output sharpen". Images exported will be sharpened using GMic (RL deblur algorithm) REQUIRED SOFTWARE GMic command line interface (CLI) https://gmic.eu/download.shtml - + USAGE * require this script from main lua file - * in lua preferences, select the GMic cli executable + * in lua preferences, select the GMic cli executable, and optionally + select the exiftool cli executable * from "export selected", choose "RL output sharpen" - * configure output folder + * configure output path or folder * configure RL parameters with sliders - * configure temp files format and quality, jpg 8bpp (good quality) + * configure temp files format and quality, jpg 8bpp (good quality) and tif 16bpp (best quality) are supported * configure other export options (size, etc.) * export, images will be first exported in the temp format, then sharpened @@ -43,10 +44,10 @@ CAVEATS MAC compatibility not tested - Although Darktable can handle file names containing spaces, GMic cli cannot, + Although Darktable can handle file names containing spaces, GMic cli cannot, so if you want to use this script please make sure that your images do not have spaces in the file name and path - + BUGS, COMMENTS, SUGGESTIONS send to Marco Carrarini, marco.carrarini@gmail.com @@ -63,7 +64,7 @@ local dtsys = require "lib/dtutils.system" local MODULE_NAME = "RL_out_sharp" -- check API version -du.check_min_api_version("7.0.0", MODULE_NAME) +du.check_min_api_version("7.0.0", MODULE_NAME) -- return data structure for script_manager @@ -81,34 +82,128 @@ 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 +end -- initialize module preferences if not dt.preferences.read(MODULE_NAME, "initialized", "bool") then + dt.preferences.write(MODULE_NAME, "output_path", "string", "$(FILE_FOLDER)/darktable_exported/$(FILE_NAME)") dt.preferences.write(MODULE_NAME, "sigma", "string", "0.7") dt.preferences.write(MODULE_NAME, "iterations", "string", "10") dt.preferences.write(MODULE_NAME, "jpg_quality", "string", "95") dt.preferences.write(MODULE_NAME, "initialized", "bool", true) - end +end +-- namespace variable +local RL_out_sharp = { + substitutes = {}, + placeholders = {"ROLL_NAME","FILE_FOLDER","FILE_NAME","FILE_EXTENSION","ID","VERSION","SEQUENCE","YEAR","MONTH","DAY", + "HOUR","MINUTE","SECOND","EXIF_YEAR","EXIF_MONTH","EXIF_DAY","EXIF_HOUR","EXIF_MINUTE","EXIF_SECOND", + "STARS","LABELS","MAKER","MODEL","TITLE","CREATOR","PUBLISHER","RIGHTS","USERNAME","PICTURES_FOLDER", + "HOME","DESKTOP","EXIF_ISO","EXIF_EXPOSURE","EXIF_EXPOSURE_BIAS","EXIF_APERTURE","EXIF_FOCUS_DISTANCE", + "EXIF_FOCAL_LENGTH","LONGITUDE","LATITUDE","ELEVATION","LENS","DESCRIPTION","EXIF_CROP"} +} + -- setup export --------------------------------------------------------------- local function setup_export(storage, img_format, image_table, high_quality, extra_data) -- set 16bpp if format is tif if img_format.extension == "tif" then img_format.bpp = 16 - end end +end -- temp export formats: jpg and tif are supported ----------------------------- local function supported(storage, img_format) return (img_format.extension == "jpg") or (img_format.extension == "tif") +end + + + +-- shamelessly copied the pattern-replacement functions from rename_images.lua +local function build_substitution_list(image, sequence, datetime, username, pic_folder, home, desktop) + -- build the argument substitution list from each image + -- local datetime = os.date("*t") + local colorlabels = {} + if image.red then table.insert(colorlabels, "red") end + if image.yellow then table.insert(colorlabels, "yellow") end + if image.green then table.insert(colorlabels, "green") end + if image.blue then table.insert(colorlabels, "blue") end + if image.purple then table.insert(colorlabels, "purple") end + local labels = #colorlabels == 1 and colorlabels[1] or du.join(colorlabels, ",") + local eyear,emon,eday,ehour,emin,esec = string.match(image.exif_datetime_taken, "(%d-):(%d-):(%d-) (%d-):(%d-):(%d-)$") + local replacements = {image.film, + image.path, + df.get_filename(image.filename), + string.upper(df.get_filetype(image.filename)), + image.id,image.duplicate_index, + string.format("%04d", sequence), + datetime.year, + string.format("%02d", datetime.month), + string.format("%02d", datetime.day), + string.format("%02d", datetime.hour), + string.format("%02d", datetime.min), + string.format("%02d", datetime.sec), + eyear, + emon, + eday, + ehour, + emin, + esec, + image.rating, + labels, + image.exif_maker, + image.exif_model, + image.title, + image.creator, + image.publisher, + image.rights, + username, + pic_folder, + home, + desktop, + image.exif_iso, + image.exif_exposure, + image.exif_exposure_bias, + image.exif_aperture, + image.exif_focus_distance, + image.exif_focal_length, + image.longitude, + image.latitude, + image.elevation, + image.exif_lens, + image.description, + image.exif_crop + } + + for i=1,#RL_out_sharp.placeholders,1 do + RL_out_sharp.substitutes[RL_out_sharp.placeholders[i]] = replacements[i] + end +end + +local function substitute_list(str) + -- replace the substitution variables in a string + for match in string.gmatch(str, "%$%(.-%)") do + local var = string.match(match, "%$%((.-)%)") + if RL_out_sharp.substitutes[var] then + str = string.gsub(str, "%$%("..var.."%)", RL_out_sharp.substitutes[var]) + else + dt.print_error(_("unrecognized variable " .. var)) + dt.print(_("unknown variable " .. var .. ", aborting...")) + return -1 + end end + return str +end + +local function clear_substitute_list() + for i=1,#RL_out_sharp.placeholders,1 do RL_out_sharp.substitutes[RL_out_sharp.placeholders[i]] = nil end +end + -- export and sharpen images -------------------------------------------------- -local function export2RL(storage, image_table, extra_data) +local function export2RL(storage, image_table, extra_data) local temp_name, new_name, run_cmd, result local input_file, output_file, options @@ -118,59 +213,159 @@ local function export2RL(storage, image_table, extra_data) if gmic == "" then dt.print(_("GMic executable not configured")) return - end + end gmic = df.sanitize_filename(gmic) + + local exiftool = dt.preferences.read(MODULE_NAME, "exiftool_exe", "string") + if exiftool == "" then + dt.print(_("exiftool executable not configured")) + end + + -- determine output path local output_folder = output_folder_selector.value + local output_path = output_folder_path.text + local sigma_str = string.gsub(string.format("%.2f", sigma_slider.value), ",", ".") local iterations_str = string.format("%.0f", iterations_slider.value) local jpg_quality_str = string.format("%.0f", jpg_quality_slider.value) -- save preferences + dt.preferences.write(MODULE_NAME, "output_path", "string", output_path) dt.preferences.write(MODULE_NAME, "sigma", "string", sigma_str) dt.preferences.write(MODULE_NAME, "iterations", "string", iterations_str) dt.preferences.write(MODULE_NAME, "jpg_quality", "string", jpg_quality_str) local gmic_operation = " -deblur_richardsonlucy "..sigma_str..","..iterations_str..",1" - + local i = 0 for image, temp_name in pairs(image_table) do i = i + 1 - dt.print(_("sharpening image ")..i.." ...") - -- create unique filename - new_name = output_folder..PS..df.get_basename(temp_name)..".jpg" - new_name = df.create_unique_filename(new_name) + dt.print(_("Applying RL deconvolution to image ")..i.." ...") + + -- work around GMIC's long/space filename problem by renaming/moving file later + local tmp_rl_name = df.create_unique_filename(df.get_path(temp_name)..PS..df.get_basename(temp_name).."_rl.jpg") -- build the GMic command string input_file = df.sanitize_filename(temp_name) - output_file = df.sanitize_filename(new_name) + output_file = df.sanitize_filename(tmp_rl_name) options = " cut 0,255 round " if df.get_filetype(temp_name) == "tif" then options = " -/ 256"..options end - + run_cmd = gmic.." "..input_file..gmic_operation..options.."o "..output_file..","..jpg_quality_str - + + dt.print_log(run_cmd) + result = dtsys.external_command(run_cmd) if result ~= 0 then dt.print(_("sharpening error")) return + end + + -- copy exif + if exiftool ~= "" then + exiftool = df.sanitize_filename(exiftool) + dt.print(_("copying EXIF to image ")..i.." ...") + run_cmd = exiftool.." -writeMode cg -TagsFromFile "..input_file.." -all:all -overwrite_original "..output_file + + result = dtsys.external_command(run_cmd) + if result ~= 0 then + dt.print(_("error copying exif")) + return end + end + + -- move the tmp file to final destination + local new_name = output_folder..PS..df.get_basename(temp_name)..".jpg" + + if output_path ~= "" then + -- override output folder if needed + local datetime = os.date("*t") + build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP) + output_path = substitute_list(output_path) + + if output_path == -1 then + dt.print(_("unable to do variable substitution, exiting...")) + return + end + clear_substitute_list() + + new_name = df.get_path(output_path)..df.get_basename(output_path)..".jpg" + end + + new_name = df.create_unique_filename(new_name) + df.file_move(tmp_rl_name, new_name) -- delete temp image - os.remove(temp_name) + os.remove(temp_name) - end - - dt.print(_("finished exporting")) end - -- script_manager integration + dt.print(_("finished exporting")) +end + +-- script_manager integration + +local function destroy() + dt.destroy_storage("exp2RL") +end + + + + - local function destroy() - dt.destroy_storage("exp2RL") - end -- new widgets ---------------------------------------------------------------- +output_folder_path = dt.new_widget("entry"){ + tooltip = _("$(ROLL_NAME) - film roll name\n") .. + _("$(FILE_FOLDER) - image file folder\n") .. + _("$(FILE_NAME) - image file name\n") .. + _("$(FILE_EXTENSION) - image file extension\n") .. + _("$(ID) - image id\n") .. + _("$(VERSION) - version number\n") .. + _("$(SEQUENCE) - sequence number of selection\n") .. + _("$(YEAR) - current year\n") .. + _("$(MONTH) - current month\n") .. + _("$(DAY) - current day\n") .. + _("$(HOUR) - current hour\n") .. + _("$(MINUTE) - current minute\n") .. + _("$(SECOND) - current second\n") .. + _("$(EXIF_YEAR) - EXIF year\n") .. + _("$(EXIF_MONTH) - EXIF month\n") .. + _("$(EXIF_DAY) - EXIF day\n") .. + _("$(EXIF_HOUR) - EXIF hour\n") .. + _("$(EXIF_MINUTE) - EXIF minute\n") .. + _("$(EXIF_SECOND) - EXIF seconds\n") .. + _("$(EXIF_ISO) - EXIF ISO\n") .. + _("$(EXIF_EXPOSURE) - EXIF exposure\n") .. + _("$(EXIF_EXPOSURE_BIAS) - EXIF exposure bias\n") .. + _("$(EXIF_APERTURE) - EXIF aperture\n") .. + _("$(EXIF_FOCAL_LENGTH) - EXIF focal length\n") .. + _("$(EXIF_FOCUS_DISTANCE) - EXIF focus distance\n") .. + _("$(EXIF_CROP) - EXIF crop\n") .. + _("$(LONGITUDE) - longitude\n") .. + _("$(LATITUDE) - latitude\n") .. + _("$(ELEVATION) - elevation\n") .. + _("$(STARS) - star rating\n") .. + _("$(LABELS) - color labels\n") .. + _("$(MAKER) - camera maker\n") .. + _("$(MODEL) - camera model\n") .. + _("$(LENS) - lens\n") .. + _("$(TITLE) - title from metadata\n") .. + _("$(DESCRIPTION) - description from metadata\n") .. + _("$(CREATOR) - creator from metadata\n") .. + _("$(PUBLISHER) - publisher from metadata\n") .. + _("$(RIGHTS) - rights from metadata\n") .. + _("$(USERNAME) - username\n") .. + _("$(PICTURES_FOLDER) - pictures folder\n") .. + _("$(HOME) - user's home directory\n") .. + _("$(DESKTOP) - desktop directory"), + placeholder = _("leave blank to use the location selected below"), + editable = true, +} + + output_folder_selector = dt.new_widget("file_chooser_button"){ title = _("select output folder"), tooltip = _("select output folder"), @@ -178,8 +373,8 @@ output_folder_selector = dt.new_widget("file_chooser_button"){ is_directory = true, changed_callback = function(self) dt.preferences.write(MODULE_NAME, "output_folder", "string", self.value) - end - } + end +} sigma_slider = dt.new_widget("slider"){ label = _("sigma"), @@ -191,7 +386,7 @@ sigma_slider = dt.new_widget("slider"){ step = 0.05, digits = 2, value = 1.0 - } +} iterations_slider = dt.new_widget("slider"){ label = _("iterations"), @@ -203,7 +398,7 @@ iterations_slider = dt.new_widget("slider"){ step = 5, digits = 0, value = 10.0 - } +} jpg_quality_slider = dt.new_widget("slider"){ label = _("output jpg quality"), @@ -215,23 +410,31 @@ jpg_quality_slider = dt.new_widget("slider"){ step = 2, digits = 0, value = 95.0 - } +} local storage_widget = dt.new_widget("box"){ orientation = "vertical", + output_folder_path, output_folder_selector, sigma_slider, iterations_slider, jpg_quality_slider - } +} -- register new storage ------------------------------------------------------- dt.register_storage("exp2RL", _("RL output sharpen"), nil, export2RL, supported, save_preferences, storage_widget) -- register the new preferences ----------------------------------------------- -dt.preferences.register(MODULE_NAME, "gmic_exe", "file", -_("executable for GMic CLI"), -_("select executable for GMic command line version") , "") +dt.preferences.register(MODULE_NAME, "gmic_exe", "file", +_ ("executable for GMic CLI"), +_ ("select executable for GMic command line version"), "") + +dt.preferences.register(MODULE_NAME, "exiftool_exe", "file", +_ ("executable for exiftool"), +_ ("select executable for GMic command line version"), "") + +-- set output_folder_path to the last used value at startup ------------------ +output_folder_path.text = dt.preferences.read(MODULE_NAME, "output_path", "string") -- set sliders to the last used value at startup ------------------------------ sigma_slider.value = dt.preferences.read(MODULE_NAME, "sigma", "float") From 24d17db421adf35741bd861a9b068aadd4f6a347 Mon Sep 17 00:00:00 2001 From: Huy Hoang Date: Sun, 6 Mar 2022 01:48:45 -0600 Subject: [PATCH 2/3] RL_out_sharp: switch from "finalize()" to "initialize()" and "store()" to avoid large temp storage (especially for TIFF16) --- contrib/RL_out_sharp.lua | 177 ++++++++++++++++++++++----------------- 1 file changed, 98 insertions(+), 79 deletions(-) diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index d8f165e2..745beca7 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -105,11 +105,47 @@ local RL_out_sharp = { } -- setup export --------------------------------------------------------------- -local function setup_export(storage, img_format, image_table, high_quality, extra_data) - -- set 16bpp if format is tif - if img_format.extension == "tif" then - img_format.bpp = 16 +local function initialize(storage, img_format, image_table, high_quality, extra) + local tmp_rl_name, new_name, run_cmd, result + local input_file, output_file, options + + -- read parameters + extra.gmic = dt.preferences.read(MODULE_NAME, "gmic_exe", "string") + if extra.gmic == "" then + dt.print(_("ERROR: GMic executable not configured")) + -- not returning {} here as that can crash darktable if user clicks the export button repeatedly + end + extra.gmic = df.sanitize_filename(extra.gmic) + + extra.exiftool = dt.preferences.read(MODULE_NAME, "exiftool_exe", "string") + if extra.exiftool == "" then + dt.print(_("exiftool executable not configured")) + else + extra.exiftool = df.sanitize_filename(extra.exiftool) + end + + -- since we cannot change the bpp, inform user + if img_format.extension == "tif" and img_format.bpp ~= 16 and img_format.bpp ~= 8 then + dt.print_log(_("ERROR: Please set TIFF bit depth to 8 or 16")) + dt.print(_("ERROR: Please set TIFF bit depth to 8 or 16")) + -- not returning {} here as that can crash darktable if user clicks the export button repeatedly end + + -- determine output path + extra.output_folder = output_folder_selector.value + extra.output_path = output_folder_path.text + + extra.sigma_str = string.gsub(string.format("%.2f", sigma_slider.value), ",", ".") + extra.iterations_str = string.format("%.0f", iterations_slider.value) + extra.jpg_quality_str = string.format("%.0f", jpg_quality_slider.value) + + -- save preferences + dt.preferences.write(MODULE_NAME, "output_path", "string", extra.output_path) + dt.preferences.write(MODULE_NAME, "sigma", "string", extra.sigma_str) + dt.preferences.write(MODULE_NAME, "iterations", "string", extra.iterations_str) + dt.preferences.write(MODULE_NAME, "jpg_quality", "string", extra.jpg_quality_str) + + extra.gmic_operation = " -deblur_richardsonlucy "..extra.sigma_str..","..extra.iterations_str..",1" end @@ -202,108 +238,91 @@ end --- export and sharpen images -------------------------------------------------- -local function export2RL(storage, image_table, extra_data) +-- perform GMIC RL-decon on a single exported image ---------------------------------- - local temp_name, new_name, run_cmd, result - local input_file, output_file, options - - -- read parameters - local gmic = dt.preferences.read(MODULE_NAME, "gmic_exe", "string") - if gmic == "" then - dt.print(_("GMic executable not configured")) +local function store(storage, image, img_format, temp_name, img_num, total, hq, extra) + if extra.gmic == "" then + dt.print(_("ERROR: GMic executable not configured")) return end - gmic = df.sanitize_filename(gmic) - local exiftool = dt.preferences.read(MODULE_NAME, "exiftool_exe", "string") - if exiftool == "" then - dt.print(_("exiftool executable not configured")) - end + local tmp_rl_name, new_name, run_cmd, result + local input_file, output_file, options - -- determine output path - local output_folder = output_folder_selector.value - local output_path = output_folder_path.text + new_name = extra.output_folder..PS..df.get_basename(temp_name)..".jpg" - local sigma_str = string.gsub(string.format("%.2f", sigma_slider.value), ",", ".") - local iterations_str = string.format("%.0f", iterations_slider.value) - local jpg_quality_str = string.format("%.0f", jpg_quality_slider.value) + -- override output path/filename as needed + if extra.output_path ~= "" then + local output_path = extra.output_path + local datetime = os.date("*t") - -- save preferences - dt.preferences.write(MODULE_NAME, "output_path", "string", output_path) - dt.preferences.write(MODULE_NAME, "sigma", "string", sigma_str) - dt.preferences.write(MODULE_NAME, "iterations", "string", iterations_str) - dt.preferences.write(MODULE_NAME, "jpg_quality", "string", jpg_quality_str) + build_substitution_list(image, img_num, datetime, USER, PICTURES, HOME, DESKTOP) + output_path = substitute_list(output_path) - local gmic_operation = " -deblur_richardsonlucy "..sigma_str..","..iterations_str..",1" + if output_path == -1 then + dt.print(_("ERROR: unable to do variable substitution")) + return + end - local i = 0 - for image, temp_name in pairs(image_table) do + clear_substitute_list() + new_name = df.get_path(output_path)..df.get_basename(output_path)..".jpg" + end - i = i + 1 - dt.print(_("Applying RL deconvolution to image ")..i.." ...") - -- work around GMIC's long/space filename problem by renaming/moving file later - local tmp_rl_name = df.create_unique_filename(df.get_path(temp_name)..PS..df.get_basename(temp_name).."_rl.jpg") + dt.print(_("Applying RL deconvolution to image ")..temp_name.." ...") - -- build the GMic command string - input_file = df.sanitize_filename(temp_name) - output_file = df.sanitize_filename(tmp_rl_name) - options = " cut 0,255 round " - if df.get_filetype(temp_name) == "tif" then options = " -/ 256"..options end + -- work around GMIC's long/space filename problem by renaming/moving file later + local tmp_rl_name = df.create_unique_filename(df.get_path(temp_name)..PS..df.get_basename(temp_name).."_rl.jpg") - run_cmd = gmic.." "..input_file..gmic_operation..options.."o "..output_file..","..jpg_quality_str + -- build the GMic command string + input_file = df.sanitize_filename(temp_name) + output_file = df.sanitize_filename(tmp_rl_name) + options = " cut 0,255 round " - dt.print_log(run_cmd) + if img_format.extension == "tif" then + if img_format.bpp == 16 then + options = " -/ 256"..options + end - result = dtsys.external_command(run_cmd) - if result ~= 0 then - dt.print(_("sharpening error")) + if img_format.bpp == 32 then + dt.print(_("ERROR: please set TIFF bit depth to 8 or 16")) return end + end - -- copy exif - if exiftool ~= "" then - exiftool = df.sanitize_filename(exiftool) - dt.print(_("copying EXIF to image ")..i.." ...") - run_cmd = exiftool.." -writeMode cg -TagsFromFile "..input_file.." -all:all -overwrite_original "..output_file - - result = dtsys.external_command(run_cmd) - if result ~= 0 then - dt.print(_("error copying exif")) - return - end - end + run_cmd = extra.gmic.." "..input_file..extra.gmic_operation..options.." -o "..output_file..","..extra.jpg_quality_str - -- move the tmp file to final destination - local new_name = output_folder..PS..df.get_basename(temp_name)..".jpg" + dt.print_log(run_cmd) - if output_path ~= "" then - -- override output folder if needed - local datetime = os.date("*t") - build_substitution_list(image, i, datetime, USER, PICTURES, HOME, DESKTOP) - output_path = substitute_list(output_path) + result = dtsys.external_command(run_cmd) + if result ~= 0 then + dt.print(_("Error applying RL-deconvolution")) + return + end - if output_path == -1 then - dt.print(_("unable to do variable substitution, exiting...")) - return - end - clear_substitute_list() + -- copy exif + if extra.exiftool ~= "" then + dt.print(_("copying EXIF to image: ")..temp_name.." ...") + run_cmd = extra.exiftool.." -writeMode cg -TagsFromFile "..input_file.." -all:all -overwrite_original "..output_file - new_name = df.get_path(output_path)..df.get_basename(output_path)..".jpg" + result = dtsys.external_command(run_cmd) + if result ~= 0 then + dt.print(_("error copying exif")) + return end + end - new_name = df.create_unique_filename(new_name) - df.file_move(tmp_rl_name, new_name) + -- move the tmp file to final destination + new_name = df.create_unique_filename(new_name) + df.file_move(tmp_rl_name, new_name) - -- delete temp image - os.remove(temp_name) + -- delete temp image + os.remove(temp_name) - end - - dt.print(_("finished exporting")) + dt.print(_("finished exporting image ")..new_name) end + -- script_manager integration local function destroy() @@ -422,7 +441,7 @@ local storage_widget = dt.new_widget("box"){ } -- register new storage ------------------------------------------------------- -dt.register_storage("exp2RL", _("RL output sharpen"), nil, export2RL, supported, save_preferences, storage_widget) +dt.register_storage("exp2RL", _("RL output sharpen"), store, nil, supported, initialize, storage_widget) -- register the new preferences ----------------------------------------------- dt.preferences.register(MODULE_NAME, "gmic_exe", "file", From 8af015e036d018c3c14d2e1faf0fe86580c2bd8b Mon Sep 17 00:00:00 2001 From: Huy Hoang Date: Mon, 7 Mar 2022 12:47:44 -0600 Subject: [PATCH 3/3] RL_out_sharp: create the output directory as needed before moving the final file --- contrib/RL_out_sharp.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/contrib/RL_out_sharp.lua b/contrib/RL_out_sharp.lua index 745beca7..11ae0597 100644 --- a/contrib/RL_out_sharp.lua +++ b/contrib/RL_out_sharp.lua @@ -314,6 +314,7 @@ local function store(storage, image, img_format, temp_name, img_num, total, hq, -- move the tmp file to final destination new_name = df.create_unique_filename(new_name) + df.mkdir(df.sanitize_filename(df.get_path(new_name))) df.file_move(tmp_rl_name, new_name) -- delete temp image