3939]] local dt = require " darktable"
4040local du = require " lib/dtutils"
4141local df = require " lib/dtutils.file"
42+ local ds = require " lib/dtutils.string"
4243local log = require " lib/dtutils.log"
4344local dtsys = require " lib/dtutils.system"
4445local dd = require " lib/dtutils.debug"
@@ -65,8 +66,9 @@ local GUI = {
6566 encoding_settings_box = {},
6667 output_settings_label = {},
6768 output_settings_box = {},
68- use_original_directory = {},
69- output_directory_widget = {},
69+ output_filepath_label = {},
70+ output_filepath_widget = {},
71+ overwrite_on_conflict = {},
7072 copy_exif = {},
7173 import_to_darktable = {},
7274 min_content_boost = {},
@@ -110,6 +112,12 @@ local SELECTION_TYPE_GROUP_BY_FNAME = 2
110112local DT_COLORSPACE_PQ_P3 = 24
111113local DT_COLORSPACE_DISPLAY_P3 = 26
112114
115+ -- 1-based position of a colorspace in export profile combobox.
116+ local COLORSPACE_TO_GUI_ACTION = {
117+ [DT_COLORSPACE_PQ_P3 ] = 9 ,
118+ [DT_COLORSPACE_DISPLAY_P3 ] = 11
119+ }
120+
113121local function generate_metadata_file (settings )
114122 local metadata_file_fmt = [[ --maxContentBoost %f
115123--minContentBoost %f
135143local function save_preferences ()
136144 dt .preferences .write (namespace , " encoding_variant" , " integer" , GUI .optionwidgets .encoding_variant_combo .selected )
137145 dt .preferences .write (namespace , " selection_type" , " integer" , GUI .optionwidgets .selection_type_combo .selected )
138- dt .preferences .write (namespace , " use_original_directory" , " bool" , GUI .optionwidgets .use_original_directory .value )
139- if GUI .optionwidgets .output_directory_widget .value then
140- dt .preferences .write (namespace , " output_directory" , " string" , GUI .optionwidgets .output_directory_widget .value )
141- end
146+ dt .preferences .write (namespace , " output_filepath_pattern" , " string" , GUI .optionwidgets .output_filepath_widget .text )
147+ dt .preferences .write (namespace , " overwrite_on_conflict" , " bool" , GUI .optionwidgets .overwrite_on_conflict .value )
142148 dt .preferences .write (namespace , " import_to_darktable" , " bool" , GUI .optionwidgets .import_to_darktable .value )
143149 dt .preferences .write (namespace , " copy_exif" , " bool" , GUI .optionwidgets .copy_exif .value )
144150 if GUI .optionwidgets .min_content_boost .value then
@@ -151,7 +157,7 @@ local function save_preferences()
151157 dt .preferences .write (namespace , " gainmap_downsampling" , " integer" ,
152158 GUI .optionwidgets .gainmap_downsampling_widget .value )
153159 dt .preferences .write (namespace , " target_display_peak_nits" , " integer" ,
154- (GUI .optionwidgets .target_display_peak_nits_widget .value + 0.5 )// 1 )
160+ (GUI .optionwidgets .target_display_peak_nits_widget .value + 0.5 ) // 1 )
155161
156162end
157163
@@ -169,11 +175,8 @@ local function load_preferences()
169175 GUI .optionwidgets .selection_type_combo .selected = math.max (
170176 dt .preferences .read (namespace , " selection_type" , " integer" ), SELECTION_TYPE_ONE_STACK )
171177
172- GUI .optionwidgets .output_directory_widget .value = dt .preferences .read (namespace , " output_directory" , " string" )
173- GUI .optionwidgets .use_original_directory .value = dt .preferences .read (namespace , " use_original_directory" , " bool" )
174- if not GUI .optionwidgets .output_directory_widget .value then
175- GUI .optionwidgets .use_original_directory .value = true
176- end
178+ GUI .optionwidgets .output_filepath_widget .text = dt .preferences .read (namespace , " output_filepath_pattern" , " string" )
179+ GUI .optionwidgets .overwrite_on_conflict .value = dt .preferences .read (namespace , " overwrite_on_conflict" , " bool" )
177180 GUI .optionwidgets .import_to_darktable .value = dt .preferences .read (namespace , " import_to_darktable" , " bool" )
178181 GUI .optionwidgets .copy_exif .value = dt .preferences .read (namespace , " copy_exif" , " bool" )
179182 GUI .optionwidgets .min_content_boost .value = default_to (dt .preferences .read (namespace , " min_content_boost" , " float" ),
@@ -191,10 +194,25 @@ local function load_preferences()
191194 dt .preferences .read (namespace , " gainmap_downsampling" , " integer" ), 0 )
192195end
193196
197+ local function set_profile (colorspace )
198+ local set_directly = true
199+
200+ if set_directly then
201+ -- New method, with hardcoded export profile values.
202+ local old = dt .gui .action (" lib/export/profile" , 0 , " selection" , " " , " " ) * - 1
203+ local new = COLORSPACE_TO_GUI_ACTION [colorspace ] or colorspace
204+ log .msg (log .debug , string.format (" %d %d %d %d" , colorspace , new , old , new - old ))
205+ dt .gui .action (" lib/export/profile" , 0 , " selection" , " next" , new - old )
206+ return old
207+ else
208+ -- Old method, timing-dependent
209+ return set_combobox (" lib/export/profile" , 0 , " plugins/lighttable/export/icctype" , colorspace )
210+ end
211+ end
212+
194213-- Changes the combobox selection blindly until a paired config value is set.
195214-- Workaround for https://github.com/darktable-org/lua-scripts/issues/522
196215local function set_combobox (path , instance , config_name , new_config_value )
197-
198216 local pref = dt .preferences .read (" darktable" , config_name , " integer" )
199217 if pref == new_config_value then
200218 return new_config_value
@@ -223,8 +241,8 @@ local function assert_settings_correct(encoding_variant)
223241 exiftool = df .check_if_bin_exists (" exiftool" ),
224242 ffmpeg = df .check_if_bin_exists (" ffmpeg" )
225243 },
226- output = GUI .optionwidgets .output_directory_widget .value ,
227- use_original_dir = GUI .optionwidgets .use_original_directory . value ,
244+ overwrite_on_conflict = GUI .optionwidgets .overwrite_on_conflict .value ,
245+ output_filepath_pattern = GUI .optionwidgets .output_filepath_widget . text ,
228246 import_to_darktable = GUI .optionwidgets .import_to_darktable .value ,
229247 copy_exif = GUI .optionwidgets .copy_exif .value ,
230248 metadata = {
@@ -234,16 +252,13 @@ local function assert_settings_correct(encoding_variant)
234252 hdr_capacity_max = GUI .optionwidgets .hdr_capacity_max .value
235253 },
236254 quality = GUI .optionwidgets .quality_widget .value ,
237- target_display_peak_nits = (GUI .optionwidgets .target_display_peak_nits_widget .value + 0.5 )// 1 ,
255+ target_display_peak_nits = (GUI .optionwidgets .target_display_peak_nits_widget .value + 0.5 ) // 1 ,
238256 downsample = 2 ^ GUI .optionwidgets .gainmap_downsampling_widget .value ,
239257 tmpdir = dt .configuration .tmp_dir ,
240- skip_cleanup = false -- keep temporary files around, for debugging.
258+ skip_cleanup = false , -- keep temporary files around, for debugging.
259+ force_export = true -- if false, will copy source files instead of exporting if the file extension matches the format expectation.
241260 }
242261
243- if not settings .use_original_dir and (not settings .output or not df .check_if_file_exists (settings .output )) then
244- table.insert (errors , string.format (_ (" output directory (%s) not found" ), settings .output ))
245- end
246-
247262 for k , v in pairs (settings .bin ) do
248263 if not v then
249264 table.insert (errors , string.format (_ (" %s binary not found" ), k ))
@@ -387,10 +402,11 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total
387402 end
388403
389404 function copy_or_export (src_image , dest , format , colorspace , props )
390- if df .get_filetype (src_image .filename ) == df .get_filetype (dest ) and not src_image .is_altered then
405+ if not settings .force_export and df .get_filetype (src_image .filename ) == df .get_filetype (dest ) and
406+ not src_image .is_altered then
391407 return df .file_copy (src_image .path .. PS .. src_image .filename , dest )
392408 else
393- local prev = set_combobox ( " lib/export/profile " , 0 , " plugins/lighttable/export/icctype " , colorspace )
409+ local prev = set_profile ( colorspace )
394410 if not prev then
395411 return false
396412 end
@@ -405,7 +421,7 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total
405421 end
406422
407423 if prev then
408- set_combobox ( " lib/export/profile " , 0 , " plugins/lighttable/export/icctype " , prev )
424+ set_profile ( prev )
409425 end
410426 return ok
411427 end
@@ -655,8 +671,10 @@ local function generate_ultrahdr(encoding_variant, images, settings, step, total
655671 update_job_progress ()
656672 end
657673
658- local output_dir = settings .use_original_dir and best_source_image .path or settings .output
659- local output_file = df .create_unique_filename (output_dir .. PS .. df .get_filename (uhdr ))
674+ local output_file = ds .substitute (best_source_image , step + 1 , settings .output_filepath_pattern ) .. " .jpg"
675+ if not settings .overwrite_on_conflict then
676+ output_file = df .create_unique_filename (output_file )
677+ end
660678 ok = df .file_move (uhdr , output_file )
661679 if not ok then
662680 table.insert (errors , string.format (_ (" Error generating UltraHDR for %s" ), best_source_image .filename ))
@@ -733,17 +751,20 @@ GUI.optionwidgets.output_settings_label = dt.new_widget("section_label") {
733751 label = _ (" output" )
734752}
735753
736- GUI .optionwidgets .output_directory_widget = dt .new_widget (" file_chooser_button " ) {
737- title = _ (" select directory to write UltraHDR image files to " ),
738- is_directory = true
754+ GUI .optionwidgets .output_filepath_label = dt .new_widget (" label " ) {
755+ label = _ (" file path pattern " ),
756+ tooltip = ds . get_substitution_tooltip ()
739757}
740758
741- GUI .optionwidgets .use_original_directory = dt .new_widget (" check_button" ) {
742- label = _ (" export to original directory" ),
743- tooltip = _ (" Write UltraHDR images to the same directory as their original images" ),
744- clicked_callback = function (self )
745- GUI .optionwidgets .output_directory_widget .sensitive = not self .value
746- end
759+ GUI .optionwidgets .output_filepath_widget = dt .new_widget (" entry" ) {
760+ tooltip = ds .get_substitution_tooltip (),
761+ placeholder = _ (" e.g. $(FILE_FOLDER)/$(FILE_NAME)_ultrahdr" )
762+ }
763+
764+ GUI .optionwidgets .overwrite_on_conflict = dt .new_widget (" check_button" ) {
765+ label = _ (" overwrite if exists" ),
766+ tooltip = _ (
767+ " If the output file already exists, overwrite it. If unchecked, a unique filename will be created instead." )
747768}
748769
749770GUI .optionwidgets .import_to_darktable = dt .new_widget (" check_button" ) {
@@ -759,8 +780,9 @@ GUI.optionwidgets.copy_exif = dt.new_widget("check_button") {
759780GUI .optionwidgets .output_settings_box = dt .new_widget (" box" ) {
760781 orientation = " vertical" ,
761782 GUI .optionwidgets .output_settings_label ,
762- GUI .optionwidgets .use_original_directory ,
763- GUI .optionwidgets .output_directory_widget ,
783+ GUI .optionwidgets .output_filepath_label ,
784+ GUI .optionwidgets .output_filepath_widget ,
785+ GUI .optionwidgets .overwrite_on_conflict ,
764786 GUI .optionwidgets .import_to_darktable ,
765787 GUI .optionwidgets .copy_exif
766788}
@@ -845,7 +867,9 @@ This will determine the method used to generate UltraHDR.
845867
846868By default, the first image in a stack is treated as SDR, and the second one is a gain map/HDR.
847869You can force the image into a specific stack slot by attaching "hdr" / "gainmap" tags to it.
848- ]] ), _ (" SDR + gain map" ), _ (" SDR + HDR" ), _ (" SDR only" ), _ (" HDR only" )),
870+
871+ For HDR source images, apply a log2(203 nits/10000 nits) = -5.62 EV exposure correction
872+ before generating UltraHDR.]] ), _ (" SDR + gain map" ), _ (" SDR + HDR" ), _ (" SDR only" ), _ (" HDR only" )),
849873 selected = 0 ,
850874 changed_callback = function (self )
851875 GUI .run .sensitive = self .selected and self .selected > 0
0 commit comments