From 8f63ee0b7ab39e4a7dbfa0047127cddb2e08dba7 Mon Sep 17 00:00:00 2001 From: digital-garbage <133010426+digital-garbage@users.noreply.github.com> Date: Wed, 7 Oct 2026 15:50:52 +0300 Subject: [PATCH] fix: FunPack Save Video defaults to H.264 (plays in Firefox); H.265 stays an option Co-Authored-By: Claude Opus 5.5 --- modules/output/save/nodes.py | 24 ++++++++++++------------ modules/output/save/tests/test_save.py | 6 +++--- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/modules/output/save/nodes.py b/modules/output/save/nodes.py index 8743a058..d110730f 100644 --- a/modules/output/save/nodes.py +++ b/modules/output/save/nodes.py @@ -1,10 +1,10 @@ -"""Save a video as MP4 into ComfyUI's output folder, fast: H.265 on the GPU's encoder by default. +"""Save a video as MP4 into ComfyUI's output folder, fast: H.264 on the GPU's encoder by default. Core's SaveVideo encodes with libx264 through PyAV, and FFmpeg's library default is ONE thread -- on a rental's CPU that is where a run's last half-minute went. Here the GPU's own encoder (NVENC) does it when -this PyAV build and the card have one -- H.265 (smaller at the same quality), tagged hvc1 so Chrome/Safari -on a Mac play it; H.264 for a browser that cannot. With no NVENC it is libx264 on every core: CPU H.265 is -several times slower and would undo the point. Same names and metadata as SaveVideo. +this PyAV build and the card have one. H.264 by default: it plays in every browser, Firefox included, and +NVENC makes it as fast as H.265. H.265 (smaller files, tagged hvc1) is an option for Chrome/Safari. With no +NVENC it is libx264 on every core: CPU H.265 is several times slower. Same names and metadata as SaveVideo. """ import json @@ -20,7 +20,7 @@ from comfy_api.latest import io, ui from ..._core import log ENCODERS = ("auto", "gpu", "cpu") -CODECS = ("h265", "h264") +CODECS = ("h264", "h265") # NVENC's constant-quality mode at libx264's default number (crf 23). NVENC = {"h265": ("hevc_nvenc", {"preset": "p5", "rc": "vbr", "cq": "23", "b:v": "0"}), "h264": ("h264_nvenc", {"preset": "p5", "rc": "vbr", "cq": "23", "b:v": "0"})} @@ -55,7 +55,7 @@ def _write(path, frames, fps, audio, metadata, codec): out.mux(sound.encode(None)) -def encode(path, frames, fps, audio=None, metadata=None, encoder="auto", codec="h265"): +def encode(path, frames, fps, audio=None, metadata=None, encoder="auto", codec="h264"): """-> which encoder wrote the file. `frames`: uint8 [T, H, W, 3]. GPU first unless told otherwise; a GPU attempt that fails leaves no half file and falls back to CPU H.264, unless "gpu" was demanded.""" gpu = NVENC[codec] @@ -72,8 +72,8 @@ def encode(path, frames, fps, audio=None, metadata=None, encoder="auto", codec=" elif encoder == "gpu": raise RuntimeError(f"this PyAV build has no NVENC encoder ({gpu[0]}); pick auto or cpu") _write(path, frames, fps, audio, metadata, X264) - if encoder == "auto" and codec == "h265": - log.once("save_no_nvenc", log.ALERT, "FunPack Save Video", f"no NVENC in this PyAV build ({gpu[0]} missing): saved as H.264 on the CPU") + if encoder == "auto": + log.once("save_no_nvenc", log.ALERT, "FunPack Save Video", f"no NVENC in this PyAV build ({gpu[0]} missing): saved on the CPU") return "H.264 on libx264 (CPU, all cores)" @@ -84,14 +84,14 @@ class FunPackSaveVideo(io.ComfyNode): node_id="FunPackSaveVideo", display_name="FunPack Save Video", category="FunPack/Output", - description="Save as MP4: H.265 on the GPU's encoder when there is one, otherwise H.264 on every CPU core.", + description="Save as MP4 (H.264): on the GPU's encoder when there is one, otherwise on every CPU core.", inputs=[ io.Video.Input("video"), io.String.Input("filename_prefix", default="FunPack"), io.Combo.Input("encoder", options=list(ENCODERS), default="auto", optional=True, tooltip="auto: GPU (NVENC) if available, else H.264 on the CPU. gpu: fail rather than use the CPU. cpu: H.264 (libx264) on every core."), - io.Combo.Input("codec", options=list(CODECS), default="h265", optional=True, - tooltip="For the GPU encoder. H.265: smaller files, plays in Chrome/Safari on a Mac. H.264: plays everywhere (Firefox, Linux)."), + io.Combo.Input("codec", options=list(CODECS), default="h264", optional=True, + tooltip="For the GPU encoder. H.264: plays in every browser. H.265: smaller files, but Firefox may not play it."), ], hidden=[io.Hidden.prompt, io.Hidden.extra_pnginfo], is_output_node=True, @@ -99,7 +99,7 @@ class FunPackSaveVideo(io.ComfyNode): ) @classmethod - def execute(cls, video, filename_prefix="FunPack", encoder="auto", codec="h265") -> io.NodeOutput: + def execute(cls, video, filename_prefix="FunPack", encoder="auto", codec="h264") -> io.NodeOutput: parts = video.get_components() frames = (parts.images * 255).clamp(0, 255).byte().cpu().numpy() width, height = frames.shape[2], frames.shape[1] diff --git a/modules/output/save/tests/test_save.py b/modules/output/save/tests/test_save.py index e7907edf..a1bd9831 100644 --- a/modules/output/save/tests/test_save.py +++ b/modules/output/save/tests/test_save.py @@ -25,14 +25,14 @@ def test_cpu_encode_writes_a_playable_h264_mp4_with_sound_and_metadata(tmp_path) def test_auto_falls_back_to_the_cpu_when_there_is_no_gpu_encoder(tmp_path, monkeypatch): - monkeypatch.setattr(nodes, "NVENC", {"h265": ("no_such_encoder_x", {})}) + monkeypatch.setattr(nodes, "NVENC", {"h264": ("no_such_encoder_x", {})}) monkeypatch.setattr(av, "codecs_available", av.codecs_available | {"no_such_encoder_x"}) path = str(tmp_path / "b.mp4") assert "libx264" in nodes.encode(path, _clip(10), 24.0, encoder="auto") def test_gpu_demanded_without_one_says_so(tmp_path, monkeypatch): - monkeypatch.setattr(nodes, "NVENC", {"h265": ("no_such_encoder_x", {})}) + monkeypatch.setattr(nodes, "NVENC", {"h264": ("no_such_encoder_x", {})}) with pytest.raises(RuntimeError, match="NVENC"): nodes.encode(str(tmp_path / "c.mp4"), _clip(5), 24.0, encoder="gpu") @@ -42,6 +42,6 @@ def test_h265_is_tagged_hvc1_so_a_mac_browser_plays_it(tmp_path, monkeypatch): if "libx265" not in av.codecs_available: pytest.skip("no libx265 in this PyAV build") path = str(tmp_path / "d.mp4") - assert "H265" in nodes.encode(path, _clip(10), 24.0, encoder="gpu") + assert "H265" in nodes.encode(path, _clip(10), 24.0, encoder="gpu", codec="h265") with av.open(path) as f: assert f.streams.video[0].codec_context.name == "hevc" and f.streams.video[0].codec_tag == "hvc1"