Store mp4 metadata at the beginning of the file when possible. (#15195)

This commit is contained in:
comfyanonymous
2026-08-01 00:21:28 -07:00
committed by GitHub
parent 235b466a0c
commit 2881e61610
3 changed files with 22 additions and 5 deletions

View File

@@ -36,13 +36,15 @@ def get_open_write_kwargs(
dest: str | io.BytesIO, container_format: str, to_format: str | None dest: str | io.BytesIO, container_format: str, to_format: str | None
) -> dict: ) -> dict:
"""Get kwargs for writing a `VideoFromFile` to a file/stream with `av.open`""" """Get kwargs for writing a `VideoFromFile` to a file/stream with `av.open`"""
is_write_to_buffer = isinstance(dest, io.BytesIO)
is_mp4_file = not is_write_to_buffer and os.path.splitext(dest)[1].lower() == ".mp4"
movflags = "use_metadata_tags+faststart" if is_mp4_file else "use_metadata_tags"
open_kwargs = { open_kwargs = {
"mode": "w", "mode": "w",
# If isobmff, preserve custom metadata tags (workflow, prompt, extra_pnginfo) # If isobmff, preserve custom metadata tags (workflow, prompt, extra_pnginfo)
"options": {"movflags": "use_metadata_tags"}, "options": {"movflags": movflags},
} }
is_write_to_buffer = isinstance(dest, io.BytesIO)
if is_write_to_buffer: if is_write_to_buffer:
# Set output format explicitly, since it cannot be inferred from file extension # Set output format explicitly, since it cannot be inferred from file extension
if to_format == VideoContainer.AUTO: if to_format == VideoContainer.AUTO:
@@ -103,7 +105,9 @@ def mp4_output_open_kwargs(path: str | io.BytesIO, format: VideoContainer, codec
raise ValueError("Only MP4 format is supported for now") raise ValueError("Only MP4 format is supported for now")
if codec != VideoCodec.AUTO and codec != VideoCodec.H264: if codec != VideoCodec.AUTO and codec != VideoCodec.H264:
raise ValueError("Only H264 codec is supported for now") raise ValueError("Only H264 codec is supported for now")
open_kwargs = {"mode": "w", "options": {"movflags": "use_metadata_tags"}} # FFmpeg's faststart pass reopens the output by filename, so it cannot be used with file-like objects.
movflags = "use_metadata_tags+faststart" if isinstance(path, (str, os.PathLike)) else "use_metadata_tags"
open_kwargs = {"mode": "w", "options": {"movflags": movflags}}
if isinstance(format, VideoContainer) and format != VideoContainer.AUTO: if isinstance(format, VideoContainer) and format != VideoContainer.AUTO:
open_kwargs["format"] = format.value open_kwargs["format"] = format.value
elif isinstance(path, io.BytesIO): elif isinstance(path, io.BytesIO):

View File

@@ -36,6 +36,7 @@ def test_get_open_write_kwargs_filepath_no_format():
kwargs_specific = get_open_write_kwargs("output.avi", "mp4", "avi") kwargs_specific = get_open_write_kwargs("output.avi", "mp4", "avi")
fail_msg = "Format should not be set for file paths (Specific)" fail_msg = "Format should not be set for file paths (Specific)"
assert "format" not in kwargs_specific, fail_msg assert "format" not in kwargs_specific, fail_msg
assert kwargs_specific["options"]["movflags"] == "use_metadata_tags"
def test_get_open_write_kwargs_base_options_mode(): def test_get_open_write_kwargs_base_options_mode():
@@ -43,9 +44,9 @@ def test_get_open_write_kwargs_base_options_mode():
kwargs = get_open_write_kwargs("output.mp4", "mp4", VideoContainer.AUTO) kwargs = get_open_write_kwargs("output.mp4", "mp4", VideoContainer.AUTO)
assert kwargs["mode"] == "w", "mode should be set to write" assert kwargs["mode"] == "w", "mode should be set to write"
fail_msg = "movflags should be set to preserve custom metadata tags" fail_msg = "movflags should preserve custom metadata tags and enable faststart for MP4 files"
assert "movflags" in kwargs["options"], fail_msg assert "movflags" in kwargs["options"], fail_msg
assert kwargs["options"]["movflags"] == "use_metadata_tags", fail_msg assert kwargs["options"]["movflags"] == "use_metadata_tags+faststart", fail_msg
def test_get_open_write_kwargs_bytesio_auto_format(): def test_get_open_write_kwargs_bytesio_auto_format():

View File

@@ -258,6 +258,18 @@ def test_save_to_h264_crf_controls_quality(tmp_path):
assert os.path.getsize(transcoded) < os.path.getsize(high_quality) assert os.path.getsize(transcoded) < os.path.getsize(high_quality)
def test_save_to_mp4_writes_metadata_before_media(video_components, tmp_path):
encoded = tmp_path / "encoded.mp4"
remuxed = tmp_path / "remuxed.mp4"
VideoFromComponents(video_components).save_to(str(encoded), metadata={"prompt": {"test": "value"}})
VideoFromFile(str(encoded)).save_to(str(remuxed), metadata={"prompt": {"test": "value"}})
for path in (encoded, remuxed):
data = path.read_bytes()
assert data.index(b"moov") < data.index(b"mdat")
def create_transcode_source( def create_transcode_source(
width=64, height=64, frames=30, fps=30, audio_streams=1, undecodable_audio=0, rotation=False, width=64, height=64, frames=30, fps=30, audio_streams=1, undecodable_audio=0, rotation=False,
container_format="mov", audio_codec="pcm_s16le", container_format="mov", audio_codec="pcm_s16le",