Use PyAV's own predicate instead of querying supported codecs

OutputContainer.supported_codecs only keeps codecs for which
avformat_query_codec returns 1, while add_stream_from_template rejects
only a falsy result. Muxers that implement neither a query_codec
callback nor a codec_tag table return AVERROR_PATCHWELCOME, so they
accept a stream copy that supported_codecs reports as impossible: an
h264 mpegts source saved with format=auto was fully re-encoded and
silently rehoused in mp4.

Attempt the copy and let PyAV answer instead. All streams are added
before the first packet is muxed, so a stream the destination rejects
is caught before anything is written. Handling it per stream also means
a subtitle track the destination cannot store is dropped on its own
rather than diverting the video alongside it into a needless re-encode.

Log the whole-file re-encode at warning, since it is lossy and discards
the extra audio and subtitle streams the transcode path does not carry.
This commit is contained in:
bymyself
2026-08-08 20:28:57 -07:00
parent 16ce6f4852
commit 14dbb924e1

View File

@@ -100,17 +100,6 @@ def write_output_metadata(container: InputContainer, output, metadata: dict | No
output.metadata[key] = value if isinstance(value, str) else json.dumps(value)
def unsupported_remux_codecs(streams, output_container) -> list[str]:
supported = output_container.supported_codecs
return [
stream.codec_context.name
for stream in streams
if isinstance(stream, (av.VideoStream, av.AudioStream, SubtitleStream))
and stream.codec_context is not None
and stream.codec_context.name not in supported
]
def mp4_output_open_kwargs(path: str | io.BytesIO, format: VideoContainer, codec: VideoCodec) -> dict:
if format != VideoContainer.AUTO and format != VideoContainer.MP4:
raise ValueError("Only MP4 format is supported for now")
@@ -521,15 +510,6 @@ class VideoFromFile(VideoInput):
) -> bool:
streams = container.streams
with av.open(path, **open_kwargs) as output_container:
unsupported = unsupported_remux_codecs(streams, output_container)
if unsupported:
logging.info(
"Cannot copy %s into a %s container; re-encoding instead.",
", ".join(sorted(set(unsupported))),
output_container.format.name,
)
return False
# Add metadata before writing any streams
write_output_metadata(container, output_container, metadata)
@@ -540,7 +520,23 @@ class VideoFromFile(VideoInput):
if stream.codec_context is None:
logging.warning("Skipping %s stream %d with unsupported codec", stream.type, stream.index)
continue
out_stream = output_container.add_stream_from_template(template=stream, opaque=True)
try:
out_stream = output_container.add_stream_from_template(template=stream, opaque=True)
except ValueError:
codec_name = stream.codec_context.name
format_name = output_container.format.name
if isinstance(stream, SubtitleStream):
logging.warning(
"Dropping %s subtitle stream %d: the %s container cannot store it.",
codec_name, stream.index, format_name,
)
continue
logging.warning(
"The %s container cannot store %s, so the whole file is being re-encoded to H.264/AAC. "
"Any additional audio or subtitle streams will be dropped.",
format_name, codec_name,
)
return False
stream_map[stream] = out_stream
# Write packets to the new container