Skip to content

Commit 33d9896

Browse files
committed
Fix proper generated file paths in metadata
1 parent 3234937 commit 33d9896

File tree

1 file changed

+79
-42
lines changed

1 file changed

+79
-42
lines changed

videohelpersuite/nodes.py

Lines changed: 79 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -339,19 +339,7 @@ def batched_encode(images, vae, frames_per_batch):
339339
) = folder_paths.get_save_image_path(filename_prefix, output_dir)
340340
output_files = []
341341

342-
metadata = PngInfo()
343-
video_metadata = {}
344-
if prompt is not None:
345-
metadata.add_text("prompt", json.dumps(prompt))
346-
video_metadata["prompt"] = json.dumps(prompt)
347-
if extra_pnginfo is not None:
348-
for x in extra_pnginfo:
349-
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
350-
video_metadata[x] = extra_pnginfo[x]
351-
extra_options = extra_pnginfo.get('workflow', {}).get('extra', {})
352-
else:
353-
extra_options = {}
354-
metadata.add_text("CreationTime", datetime.datetime.now().isoformat(" ")[:19])
342+
extra_options = extra_pnginfo.get('workflow', {}).get('extra', {}) if extra_pnginfo else {}
355343

356344
if meta_batch is not None and unique_id in meta_batch.outputs:
357345
(counter, output_process) = meta_batch.outputs[unique_id]
@@ -375,18 +363,74 @@ def batched_encode(images, vae, frames_per_batch):
375363
counter = max_counter + 1
376364
output_process = None
377365

378-
# save first frame as png to keep metadata
366+
# We must determine the final filenames FIRST,
367+
# update the extra_pnginfo in memory, and only THEN create the metadata objects that will be saved.
368+
format_type, format_ext = format.split("/")
369+
370+
# This has to be done early to get the correct file extension for video formats.
371+
if format_type != "image":
372+
if manual_format_widgets is not None:
373+
logger.warn("Format args can now be passed directly. The manual_format_widgets argument is now deprecated")
374+
kwargs.update(manual_format_widgets)
375+
has_alpha = first_image.shape[-1] == 4
376+
kwargs["has_alpha"] = has_alpha
377+
video_format = apply_format_widgets(format_ext, kwargs)
378+
video_format_extension = video_format['extension']
379+
else:
380+
video_format_extension = format_ext
381+
video_format = {} # Ensure video_format exists
382+
383+
# 1. Determine all final output filenames and paths *before* creating metadata.
384+
file = f"{filename}_{counter:05}.{video_format_extension}"
385+
file_path = os.path.join(full_output_folder, file)
379386
first_image_file = f"{filename}_{counter:05}.png"
380-
file_path = os.path.join(full_output_folder, first_image_file)
387+
388+
# 2. Create the 'preview' dictionary with the fresh, correct data for the current run.
389+
preview = {
390+
"filename": file,
391+
"subfolder": subfolder,
392+
"type": "output" if save_output else "temp",
393+
"format": format,
394+
"frame_rate": frame_rate,
395+
"workflow": first_image_file,
396+
"fullpath": file_path,
397+
}
398+
399+
# 3. Update the extra_pnginfo dictionary IN-MEMORY before it is used to create file metadata.
400+
if extra_pnginfo and 'workflow' in extra_pnginfo and unique_id is not None:
401+
workflow = extra_pnginfo['workflow']
402+
node = next((n for n in workflow['nodes'] if str(n['id']) == str(unique_id)), None)
403+
if node:
404+
if 'widgets_values' not in node:
405+
node['widgets_values'] = {}
406+
# Update the 'videopreview' widget's value with the correct params for this run.
407+
if 'videopreview' not in node['widgets_values']:
408+
node['widgets_values']['videopreview'] = {}
409+
node['widgets_values']['videopreview']['params'] = preview
410+
411+
# 4. Now, create the metadata objects using the UPDATED extra_pnginfo.
412+
metadata = PngInfo()
413+
video_metadata = {}
414+
if prompt is not None:
415+
metadata.add_text("prompt", json.dumps(prompt))
416+
video_metadata["prompt"] = json.dumps(prompt)
417+
if extra_pnginfo is not None:
418+
for x in extra_pnginfo:
419+
# The 'workflow' object within extra_pnginfo is now up-to-date
420+
metadata.add_text(x, json.dumps(extra_pnginfo[x]))
421+
video_metadata[x] = extra_pnginfo[x]
422+
metadata.add_text("CreationTime", datetime.datetime.now().isoformat(" ")[:19])
423+
424+
# 5. Save the first frame as a PNG with the now-correct metadata.
425+
png_file_path = os.path.join(full_output_folder, first_image_file)
381426
if extra_options.get('VHS_MetadataImage', True) != False:
382427
Image.fromarray(tensor_to_bytes(first_image)).save(
383-
file_path,
428+
png_file_path,
384429
pnginfo=metadata,
385430
compress_level=4,
386431
)
387-
output_files.append(file_path)
432+
output_files.append(png_file_path)
388433

389-
format_type, format_ext = format.split("/")
390434
if format_type == "image":
391435
if meta_batch is not None:
392436
raise Exception("Pillow('image/') formats are not compatible with batched output")
@@ -399,8 +443,7 @@ def batched_encode(images, vae, frames_per_batch):
399443
exif[ExifTags.IFD.Exif] = {36867: datetime.datetime.now().isoformat(" ")[:19]}
400444
image_kwargs['exif'] = exif
401445
image_kwargs['lossless'] = kwargs.get("lossless", True)
402-
file = f"{filename}_{counter:05}.{format_ext}"
403-
file_path = os.path.join(full_output_folder, file)
446+
# Filename and path are already correctly defined above
404447
if pingpong:
405448
images = to_pingpong(images)
406449
def frames_gen(images):
@@ -424,14 +467,8 @@ def frames_gen(images):
424467
# Use ffmpeg to save a video
425468
if ffmpeg_path is None:
426469
raise ProcessLookupError(f"ffmpeg is required for video outputs and could not be found.\nIn order to use video outputs, you must either:\n- Install imageio-ffmpeg with pip,\n- Place a ffmpeg executable in {os.path.abspath('')}, or\n- Install ffmpeg and add it to the system path.")
427-
428-
if manual_format_widgets is not None:
429-
logger.warn("Format args can now be passed directly. The manual_format_widgets argument is now deprecated")
430-
kwargs.update(manual_format_widgets)
431-
432-
has_alpha = first_image.shape[-1] == 4
433-
kwargs["has_alpha"] = has_alpha
434-
video_format = apply_format_widgets(format_ext, kwargs)
470+
471+
# All format widget logic has been moved up
435472
dim_alignment = video_format.get("dim_alignment", 2)
436473
if (first_image.shape[1] % dim_alignment) or (first_image.shape[0] % dim_alignment):
437474
#output frames must be padded
@@ -473,8 +510,7 @@ def pad(image):
473510
i_pix_fmt = 'rgba'
474511
else:
475512
i_pix_fmt = 'rgb24'
476-
file = f"{filename}_{counter:05}.{video_format['extension']}"
477-
file_path = os.path.join(full_output_folder, file)
513+
# Filename and path are already correctly defined above
478514
bitrate_arg = []
479515
bitrate = video_format.get('bitrate')
480516
if bitrate is not None:
@@ -529,6 +565,7 @@ def pad(image):
529565
else:
530566
args += video_format['main_pass'] + bitrate_arg
531567
merge_filter_args(args)
568+
# The 'video_metadata' object used here is now correct because it was created after updating extra_pnginfo.
532569
output_process = ffmpeg_process(args, video_format, video_metadata, file_path, env)
533570
#Proceed to first yield
534571
output_process.send(None)
@@ -604,23 +641,23 @@ def pad(image):
604641
output_files.append(output_file_with_audio_path)
605642
#Return this file with audio to the webui.
606643
#It will be muted unless opened or saved with right click
607-
file = output_file_with_audio
644+
preview['filename'] = output_file_with_audio
645+
preview['fullpath'] = output_file_with_audio_path
646+
608647
if extra_options.get('VHS_KeepIntermediate', True) == False:
609-
for intermediate in output_files[1:-1]:
610-
if os.path.exists(intermediate):
648+
# We determine the final output file to avoid deleting it if it's the only one.
649+
final_output_file = preview['fullpath']
650+
for intermediate in output_files:
651+
if intermediate != final_output_file and os.path.exists(intermediate):
611652
os.remove(intermediate)
612-
preview = {
613-
"filename": file,
614-
"subfolder": subfolder,
615-
"type": "output" if save_output else "temp",
616-
"format": format,
617-
"frame_rate": frame_rate,
618-
"workflow": first_image_file,
619-
"fullpath": output_files[-1],
620-
}
653+
# Update output_files to only contain the final file if intermediates are removed.
654+
output_files = [f for f in output_files if f == final_output_file or not os.path.exists(f)]
655+
656+
621657
if num_frames == 1 and 'png' in format and '%03d' in file:
622658
preview['format'] = 'image/png'
623659
preview['filename'] = file.replace('%03d', '001')
660+
624661
return {"ui": {"gifs": [preview]}, "result": ((save_output, output_files),)}
625662

626663
class LoadAudio:

0 commit comments

Comments
 (0)