Skip to content

Transcode when the output container cannot store the source codec - #15367

Open
christian-byrne wants to merge 4 commits into
masterfrom
fix/video-remux-codec-container-mismatch
Open

Transcode when the output container cannot store the source codec#15367
christian-byrne wants to merge 4 commits into
masterfrom
fix/video-remux-codec-container-mismatch

Conversation

@christian-byrne

@christian-byrne christian-byrne commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Load Video -> Save Video on a VP8 WebM crashes, because format=auto names the output after the destination extension (.mp4) but skips the container check that decides whether the source streams can be copied, so VP8 packets get aimed at a muxer that refuses them. Every stream is added before the first packet is muxed, so this attempts the copy and treats PyAV's own refusal as the answer: nothing has been written yet, and the save falls back to re-encoding. Handling the refusal per stream also means a subtitle track the destination cannot store is dropped on its own instead of dragging an otherwise copyable video into a needless re-encode, and codecs the destination does accept still remux losslessly. One caveat: when the refused stream is audio, the whole file is re-encoded and extra audio and subtitle tracks are dropped, since the transcode path carries one video and one audio stream; that is now logged at warning rather than passing silently. Repro is any VP8 WebM through Load Video into Save Video with the default format=auto:

  File "ComfyUI/comfy_extras/nodes_video.py", line 141, in execute
    video.save_to(
  File "ComfyUI/comfy_api/latest/_input_impl/video_types.py", line 507, in save_to
    out_stream = output_container.add_stream_from_template(template=stream, opaque=True)
  File "av/container/output.py", line 162, in av.container.output.OutputContainer.add_stream_from_template
ValueError: 'mp4' format does not support 'vp8' codec

Saving a VP8 WebM with format=auto named the file .mp4 and then stream
copied VP8 into it, which PyAV rejects. Check the destination muxer's
supported codecs before copying and fall back to re-encoding when any
source stream cannot be stored.
@coderabbitai

coderabbitai Bot commented Aug 7, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: c4078885-046c-4626-acfe-de58d2b12e0e

📥 Commits

Reviewing files that changed from the base of the PR and between 16ce6f4 and ad81529.

📒 Files selected for processing (2)
  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
📝 Walkthrough

Walkthrough

VideoFromFile.save_to now checks whether source streams are supported by the target container before remuxing. Unsupported video, audio, or subtitle codecs cause remuxing to stop and trigger transcoding. Compatible streams retain the existing metadata and packet handling. Tests cover WebM and Matroska inputs, video and audio transcoding, compatible VP9 remuxing, and MP4 buffer output.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: transcoding when the destination container does not support the source codec.
Description check ✅ Passed The description directly explains the codec mismatch, remuxing failure, transcoding fallback, and affected video workflow.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@comfy_api/latest/_input_impl/video_types.py`:
- Around line 508-513: Update the automatic fallback in the surrounding video
save method before calling _save_transcoded: either preserve all subtitle
streams by passing them through in a supported target format, or reject the
fallback with a clear error before truncating or writing path. Ensure
subtitle-bearing inputs cannot silently lose streams, and add coverage for a
subtitle stream combined with an incompatible audio or video codec.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 08ef9ab3-bf8f-4bca-8981-2bc3a7e39206

📥 Commits

Reviewing files that changed from the base of the PR and between 88fec4b and 16ce6f4.

📒 Files selected for processing (2)
  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
📜 Review details
⏰ Context from checks skipped due to timeout. (9)
  • GitHub Check: test (windows-latest)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: Run Pylint
  • GitHub Check: test (windows-2022)
  • GitHub Check: test (macos-latest)
  • GitHub Check: test (ubuntu-latest)
  • GitHub Check: test
  • GitHub Check: Run Pylint
🧰 Additional context used
📓 Path-based instructions (5)
**/*

📄 CodeRabbit inference engine (AGENTS.md)

**/*: Keep changes small, direct, and limited to the narrowest necessary code path and smallest number of files.
Prefer practical fixes, minimal dependencies, and existing repository patterns; remove obsolete, dead, unreachable, or unused code.
Preserve existing APIs, node names, model-loading behavior, file layout, and workflow compatibility unless replacement is explicitly intended.
Core ComfyUI must not add outbound internet requests, telemetry, tracking, reporting, remote configuration, or background network activity. User-authorized model downloads are limited to the requested artifact and must exclude telemetry and unrelated metadata.

Files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: Keep state and capability flags on the object that owns the behavior. Prefer explicit parent-owned attributes over probing child objects with getattr; use child checks only when the child owns the delegated behavior.
Preserve shared method signatures, argument order, return shapes, side effects, and error behavior unless every affected caller and interface is intentionally updated.
Do not add unused compatibility parameters, flags, attributes, constructor options, or model-specific options to shared helpers; keep one-off behavior at the integration boundary.
Normalize third-party return conventions at integration boundaries so core code receives the expected type and shape; avoid undocumented caller-side unwrapping.
Do not add torch.no_grad, torch.inference_mode, or inference-mode wrappers. Do not add model freeze/unfreeze toggles; only disable globally enabled inference mode when a training path requires gradients.
Remove inference-only training behavior such as dropout while preserving checkpoint and state-dict compatibility; use nn.Identity when deleting a module would alter keys or ordering.
Keep imports at module scope except established optional-backend probes or imports required to avoid cycles; avoid unnecessary try/except blocks and use specific exceptions with useful fallbacks.
Do not add workarounds for unsupported library versions, especially PyTorch exception-and-float-cast retries, unless a comment names the exact versions still requiring them.
Let unsupported model formats, invalid quantization metadata, and bad states fail with clear errors instead of silently degrading output.
Match local style, keep comments sparse and useful, and remove comments that merely restate obvious code.
Treat dtype, device placement, VRAM use, and offloading as correctness concerns across CPU, CUDA, ROCm, MPS, DirectML, XPU, NPU, and low-VRAM environments.
Prefer existing ComfyUI and Comfy Kitchen operations, quantization helpers, cast/offload helpe...

Files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
**/*.{py,json}

📄 CodeRabbit inference engine (AGENTS.md)

Treat legacy combo, io.Combo, and io.DynamicCombo values affecting filesystem access as untrusted; revalidate them at load/save boundaries with folder_paths, containment checks, or fixed allowlists.

Files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
**/*.{py,md,txt,json}

📄 CodeRabbit inference engine (AGENTS.md)

Keep warning and info messages short and actionable, remove noisy or misleading logging, and make documentation edits concise, factual, and tied to changed behavior.

Files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
**

⚙️ CodeRabbit configuration file

**: IMPORTANT: Only comment on issues directly introduced by this PR's code changes.
Treat AGENTS.md as mandatory repository policy, not optional style guidance.
Flag PR changes that violate AGENTS.md even when the code is otherwise functional.
In particular, enforce architecture boundaries, dtype/device/memory rules,
interface contracts, import style, no unnecessary try/except blocks, no inline
imports, no outbound internet paths in core ComfyUI, and narrow scoped fixes.
Prefer direct findings over suggestions when a rule is violated. Only ignore
AGENTS.md when it clearly conflicts with a newer explicit maintainer instruction
in the PR.
Do NOT flag pre-existing issues in code that was merely moved, re-indented,
de-indented, or reformatted without logic changes. If code appears in the diff
only due to whitespace or structural reformatting (e.g., removing a with: block),
treat it as unchanged. Contributors should not feel obligated to address
pre-existing issues outside the scope of their contribution.

Files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
🧠 Learnings (1)
📚 Learning: 2026-02-21T14:01:41.482Z
Learnt from: pythongosssss
Repo: Comfy-Org/ComfyUI PR: 12555
File: comfy_extras/nodes_glsl.py:719-724
Timestamp: 2026-02-21T14:01:41.482Z
Learning: In PyOpenGL, bare Python scalars can be accepted for 1-element array parameters by NumberHandler. This means you can pass an int/float directly to OpenGL texture deletion (e.g., glDeleteTextures(tex)) without wrapping in a list. Verify function-specific expectations and ensure types match what the OpenGL call expects; use explicit lists only when the API requires an array.

Applied to files:

  • comfy_api/latest/_input_impl/video_types.py
  • tests-unit/comfy_api_test/video_types_test.py
🔇 Additional comments (2)
tests-unit/comfy_api_test/video_types_test.py (1)

273-304: LGTM!

Also applies to: 307-315, 318-328, 331-339, 341-349

comfy_api/latest/_input_impl/video_types.py (1)

104-104: 🩺 Stability & Availability

No dependency change is required.

The repository already declares av>=16.0.0, which satisfies the stated OutputContainer.supported_codecs requirement.

			> Likely an incorrect or invalid review comment.

Comment on lines +508 to +513
if bit_depth is None:
bit_depth = source_bit_depth
if isinstance(path, io.BytesIO):
path.seek(0)
path.truncate()
return self._save_transcoded(container, path, format=format, codec=codec, metadata=metadata, bit_depth=bit_depth, crf=crf)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Preserve subtitles when automatic fallback transcodes.

If any video or audio codec is unsupported, this path calls _save_transcoded. _save_transcoded has no subtitle stream output path. The fallback therefore drops every subtitle stream, including compatible subtitle streams, without an error.

Preserve subtitles in a supported target format, or reject this automatic fallback with a clear error before writing output. Add coverage for a subtitle stream when an incompatible audio or video stream triggers fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@comfy_api/latest/_input_impl/video_types.py` around lines 508 - 513, Update
the automatic fallback in the surrounding video save method before calling
_save_transcoded: either preserve all subtitle streams by passing them through
in a supported target format, or reject the fallback with a clear error before
truncating or writing path. Ensure subtitle-bearing inputs cannot silently lose
streams, and add coverage for a subtitle stream combined with an incompatible
audio or video codec.

Source: Coding guidelines

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.
The transcode fallback needs a video stream, so an audio-only source
whose codec the destination cannot store failed with "No video stream
found in file", hiding the codec that actually stopped the save.
A WebM source with format=MP4 never entered the remux path: "mp4" is
absent from the source demuxer's name, so the pre-existing
reuse_streams guard already forced a transcode and the test passed
against an unfixed tree. A mov source carrying pcm_u8 keeps
reuse_streams true and is refused by the mp4 muxer, which is the case
the test was meant to describe, and it is the only test that reaches
the BytesIO truncate.

Also decode the outputs rather than trusting stream headers, encode the
audio-fallback source as mpeg4 so the h264 assertion distinguishes a
re-encode from a copy, stop pinning the mp4 demuxer's full alias list,
and skip rather than error when a build lacks an encoder the sources
need.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant