From fd5922b1cde47a8aa0be9ec0aed69a9c98120aff Mon Sep 17 00:00:00 2001 From: Ethanfel Date: Mon, 1 Jun 2026 13:29:16 +0200 Subject: [PATCH] feat(video): add path-string loader variant UniverSR Load Video Audio (Path) mirrors FoleyTuneVideoLoader: takes an absolute video_path (for files outside input/) and outputs the same (UNIVERSR_VIDEO, AUDIO). Shared load body factored into _load_video_audio; registered for the inline preview (post-run) in the web extension. Co-Authored-By: Claude Opus 4.8 --- README.md | 3 ++ nodes_video.py | 80 ++++++++++++++++++++++++++++++++--------- web/js/UniverSRVideo.js | 3 ++ 3 files changed, 69 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index a5f83cf..fa2fbe3 100644 --- a/README.md +++ b/README.md @@ -162,6 +162,9 @@ button and drag-and-drop, just like a normal video loader. Outputs **`UNIVERSR_V | `start_time` *(opt.)* | float | `0.0` | Trim start, seconds. | | `duration` *(opt.)* | float | `0.0` | Trim length, seconds (`0` = to end). | +There is also a **UniverSR Load Video Audio (Path)** variant that takes an absolute `video_path` string +(for files outside ComfyUI's `input/` folder); it previews after you run it. Both feed the combiner. + ### UniverSR Video Combiner Muxes an `AUDIO` track onto the source video **without re-encoding the video** (`-c:v copy`) and saves diff --git a/nodes_video.py b/nodes_video.py index eb4bfb2..579f819 100644 --- a/nodes_video.py +++ b/nodes_video.py @@ -120,6 +120,27 @@ def _list_input_videos() -> list: return [] +def _load_video_audio(video_path: str, start_time: float, duration: float) -> dict: + """Shared loader body: extract audio + build the (video, audio) result and preview.""" + if not video_path or not os.path.isfile(video_path): + raise FileNotFoundError(f"Video not found: {video_path}") + + waveform, sr = _extract_audio(video_path, start_time, duration) + dur = waveform.shape[-1] / max(sr, 1) + print(f"[UniverSR] Loaded audio from {os.path.basename(video_path)}: " + f"{waveform.shape[1]}ch @ {sr} Hz ({dur:.2f}s)") + + audio = {"waveform": waveform, "sample_rate": sr} + info = {"video_path": os.path.abspath(video_path), "start_time": float(start_time), + "duration": float(duration), "source_sr": sr, "source_channels": int(waveform.shape[1])} + + temp_name = _temp_preview_symlink(video_path) + ext = (os.path.splitext(video_path)[1] or ".mp4").lstrip(".") + return {"ui": {"gifs": [{"filename": temp_name, "subfolder": "", "type": "temp", + "format": f"video/{ext}"}]}, + "result": (info, audio)} + + # --------------------------------------------------------------------------- # # Load Video Audio (mirrors FoleyTuneVideoLoaderUpload; outputs video + audio) # --------------------------------------------------------------------------- # @@ -154,23 +175,7 @@ class UniverSRLoadVideoAudio: def load(self, video, start_time=0.0, duration=0.0): video_path = folder_paths.get_annotated_filepath(video) - if not video_path or not os.path.isfile(video_path): - raise FileNotFoundError(f"Video not found: {video}") - - waveform, sr = _extract_audio(video_path, start_time, duration) - dur = waveform.shape[-1] / max(sr, 1) - print(f"[UniverSR] Loaded audio from {os.path.basename(video_path)}: " - f"{waveform.shape[1]}ch @ {sr} Hz ({dur:.2f}s)") - - audio = {"waveform": waveform, "sample_rate": sr} - info = {"video_path": os.path.abspath(video_path), "start_time": float(start_time), - "duration": float(duration), "source_sr": sr, "source_channels": int(waveform.shape[1])} - - temp_name = _temp_preview_symlink(video_path) - ext = (os.path.splitext(video_path)[1] or ".mp4").lstrip(".") - return {"ui": {"gifs": [{"filename": temp_name, "subfolder": "", "type": "temp", - "format": f"video/{ext}"}]}, - "result": (info, audio)} + return _load_video_audio(video_path, start_time, duration) @classmethod def IS_CHANGED(cls, video, start_time=0.0, duration=0.0): @@ -188,6 +193,45 @@ class UniverSRLoadVideoAudio: return True +# --------------------------------------------------------------------------- # +# Load Video Audio (Path) (mirrors FoleyTuneVideoLoader; outputs video + audio) +# --------------------------------------------------------------------------- # +class UniverSRLoadVideoAudioPath: + """Same as UniverSR Load Video Audio, but takes an absolute file path instead of + an upload — handy for files outside ComfyUI's input/ folder. Previews after running.""" + + DESCRIPTION = "Load a video by file path: outputs its audio (to super-resolve) and a reference (to remux)." + CATEGORY = "audio/UniverSR" + + @classmethod + def INPUT_TYPES(cls): + return { + "required": { + "video_path": ("STRING", {"default": "", "placeholder": "/path/to/video.mp4"}), + }, + "optional": { + "start_time": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 360000.0, "step": 0.1, + "tooltip": "Trim start in seconds."}), + "duration": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 360000.0, "step": 0.1, + "tooltip": "Trim length in seconds (0 = to end)."}), + }, + } + + RETURN_TYPES = ("UNIVERSR_VIDEO", "AUDIO") + RETURN_NAMES = ("video", "audio") + FUNCTION = "load" + OUTPUT_NODE = True + + def load(self, video_path, start_time=0.0, duration=0.0): + return _load_video_audio((video_path or "").strip(), start_time, duration) + + @classmethod + def IS_CHANGED(cls, video_path, start_time=0.0, duration=0.0): + p = (video_path or "").strip() + m = os.path.getmtime(p) if p and os.path.isfile(p) else 0 + return f"{video_path}:{start_time}:{duration}:{m}" + + # --------------------------------------------------------------------------- # # Video Combiner (mirrors FoleyTuneVideoCombiner) # --------------------------------------------------------------------------- # @@ -281,9 +325,11 @@ class UniverSRVideoCombiner: NODE_CLASS_MAPPINGS = { "UniverSRLoadVideoAudio": UniverSRLoadVideoAudio, + "UniverSRLoadVideoAudioPath": UniverSRLoadVideoAudioPath, "UniverSRVideoCombiner": UniverSRVideoCombiner, } NODE_DISPLAY_NAME_MAPPINGS = { "UniverSRLoadVideoAudio": "UniverSR Load Video Audio", + "UniverSRLoadVideoAudioPath": "UniverSR Load Video Audio (Path)", "UniverSRVideoCombiner": "UniverSR Video Combiner", } diff --git a/web/js/UniverSRVideo.js b/web/js/UniverSRVideo.js index abce023..fce0c92 100644 --- a/web/js/UniverSRVideo.js +++ b/web/js/UniverSRVideo.js @@ -150,6 +150,9 @@ app.registerExtension({ addVideoPreview(nodeType); addUploadWidget(nodeType); } + if (nodeData?.name === "UniverSRLoadVideoAudioPath") { + addVideoPreview(nodeType); + } if (nodeData?.name === "UniverSRVideoCombiner") { addVideoPreview(nodeType); }