@@ -165,12 +165,13 @@ def ffmpeg_process(args, video_format, video_metadata, file_path, env):
165165 if len (res ) > 0 :
166166 print (res .decode (* ENCODE_ARGS ), end = "" , file = sys .stderr )
167167
168- def gifski_process (args , video_format , file_path , env ):
168+ def gifski_process (args , dimensions , video_format , file_path , env ):
169169 frame_data = yield
170170 with subprocess .Popen (args + video_format ['main_pass' ] + ['-f' , 'yuv4mpegpipe' , '-' ],
171171 stderr = subprocess .PIPE , stdin = subprocess .PIPE ,
172172 stdout = subprocess .PIPE , env = env ) as procff :
173173 with subprocess .Popen ([gifski_path ] + video_format ['gifski_pass' ]
174+ + ['-W' , f'{ dimensions [0 ]} ' , '-H' , f'{ dimensions [1 ]} ' ]
174175 + ['-q' , '-o' , file_path , '-' ], stderr = subprocess .PIPE ,
175176 stdin = procff .stdout , stdout = subprocess .PIPE ,
176177 env = env ) as procgs :
@@ -417,12 +418,11 @@ def pad(image):
417418 padded = padfunc (image .to (dtype = torch .float32 ))
418419 return padded .permute ((1 ,2 ,0 ))
419420 images = map (pad , images )
420- new_dims = (- first_image .shape [1 ] % dim_alignment + first_image .shape [1 ],
421- - first_image .shape [0 ] % dim_alignment + first_image .shape [0 ])
422- dimensions = f"{ new_dims [0 ]} x{ new_dims [1 ]} "
421+ dimensions = (- first_image .shape [1 ] % dim_alignment + first_image .shape [1 ],
422+ - first_image .shape [0 ] % dim_alignment + first_image .shape [0 ])
423423 logger .warn ("Output images were not of valid resolution and have had padding applied" )
424424 else :
425- dimensions = f" { first_image .shape [1 ]} x { first_image .shape [0 ]} "
425+ dimensions = ( first_image .shape [1 ], first_image .shape [0 ])
426426 if loop_count > 0 :
427427 loop_args = ["-vf" , "loop=loop=" + str (loop_count )+ ":size=" + str (num_frames )]
428428 else :
@@ -450,7 +450,18 @@ def pad(image):
450450 if bitrate is not None :
451451 bitrate_arg = ["-b:v" , str (bitrate ) + "M" if video_format .get ('megabit' ) == 'True' else str (bitrate ) + "K" ]
452452 args = [ffmpeg_path , "-v" , "error" , "-f" , "rawvideo" , "-pix_fmt" , i_pix_fmt ,
453- "-s" , dimensions , "-r" , str (frame_rate ), "-i" , "-" ] \
453+ # The image data is in an undefined generic RGB color space, which in practice means sRGB.
454+ # sRGB has the same primaries and matrix as BT.709, but a different transfer function (gamma),
455+ # called by the sRGB standard name IEC 61966-2-1. However, video hosting platforms like YouTube
456+ # standardize on full BT.709 and will convert the colors accordingly. This last minute change
457+ # in colors can be confusing to users. We can counter it by lying about the transfer function
458+ # on a per format basis, i.e. for video we will lie to FFmpeg that it is already BT.709. Also,
459+ # because the input data is in RGB (not YUV) it is more efficient (fewer scale filter invocations)
460+ # to specify the input color space as RGB and then later, if the format actually wants YUV,
461+ # to convert it to BT.709 YUV via FFmpeg's -vf "scale=out_color_matrix=bt709".
462+ "-color_range" , "full" , "-colorspace" , "rgb" , "-color_primaries" , "bt709" ,
463+ "-color_trc" , video_format .get ("fake_trc" , "iec61966-2-1" ),
464+ "-s" , f"{ dimensions [0 ]} x{ dimensions [1 ]} " , "-r" , str (frame_rate ), "-i" , "-" ] \
454465 + loop_args
455466
456467 images = map (lambda x : x .tobytes (), images )
@@ -479,8 +490,9 @@ def pad(image):
479490 args = args [:13 ] + video_format ['inputs_main_pass' ] + args [13 :]
480491
481492 if output_process is None :
493+ format = 'image/gif'
482494 if 'gifski_pass' in video_format :
483- output_process = gifski_process (args , video_format , file_path , env )
495+ output_process = gifski_process (args , dimensions , video_format , file_path , env )
484496 else :
485497 args += video_format ['main_pass' ] + bitrate_arg
486498 merge_filter_args (args )
0 commit comments