@@ -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.\n In 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
626663class LoadAudio :
0 commit comments