From c646b0d1d79a9de981a556c108c9a8c11f00b982 Mon Sep 17 00:00:00 2001 From: Jony <951565127@qq.com> Date: Sun, 26 Apr 2026 22:18:22 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E8=A7=86=E9=A2=91=E8=8A=82=E7=82=B9?= =?UTF-8?q?=E6=94=B9=E7=94=A8=E4=B8=B4=E6=97=B6=E6=96=87=E4=BB=B6=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E4=B8=8E=E4=B8=8B=E6=B8=B8=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E8=8A=82=E7=82=B9=E9=87=8D=E5=A4=8D=E8=90=BD=E7=9B=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - K3_video.py / K3_video_firstlast.py / K3_motion_control.py - K_video.py - kling_video.py (KlingVideo / KlingFirstLastFrame / KlingMotionControlTest) - seedance_video.py (Seedance / SeedanceMultiModal) 所有视频节点下载视频时改写到 tempfile.mkstemp() 临时路径, 最终落盘完全交给下游保存节点处理,不再重复写入 output/video/。 同步清理已无用的 _get_video_dir / _get_next_counter / import re 等辅助代码。 --- nodes/K3_motion_control.py | 31 +++-------------------- nodes/K3_video.py | 32 +++--------------------- nodes/K3_video_firstlast.py | 32 +++--------------------- nodes/K_video.py | 32 +++--------------------- nodes/kling_video.py | 49 ++++++------------------------------- nodes/seedance_video.py | 42 ++++--------------------------- 6 files changed, 28 insertions(+), 190 deletions(-) diff --git a/nodes/K3_motion_control.py b/nodes/K3_motion_control.py index 7c09a80..1379d32 100644 --- a/nodes/K3_motion_control.py +++ b/nodes/K3_motion_control.py @@ -8,8 +8,8 @@ import asyncio import io import json import os -import re import struct +import tempfile import aiohttp @@ -36,27 +36,6 @@ _POLL_MAX = 15 # ── 工具函数 ─────────────────────────────────────────────────────────────────── -def _get_video_dir() -> str: - if _FOLDER_PATHS_OK: - base = folder_paths.get_output_directory() - else: - plugin = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin)), "output") - d = os.path.join(base, "video") - os.makedirs(d, exist_ok=True) - return d - - -def _next_counter(directory: str, prefix: str) -> int: - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_n = 0 - if os.path.exists(directory): - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_n = max(max_n, int(m.group(1))) - return max_n + 1 - # ── 视频时长检测(纯标准库,跨平台) ────────────────────────────────────────── @@ -219,10 +198,8 @@ class K3MotionControl: if prompt: body["prompt"] = prompt - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_dir() - counter = _next_counter(video_dir, "k3_motion") - save_path = os.path.join(video_dir, f"k3_motion_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + tmp_fd, save_path = tempfile.mkstemp(suffix=".mp4", prefix="k3_motion_") connector = aiohttp.TCPConnector(ssl=False, force_close=True) async with aiohttp.ClientSession(connector=connector) as session: @@ -311,7 +288,7 @@ class K3MotionControl: async with session.get(video_result_url, allow_redirects=True) as resp: if resp.status != 200: raise RuntimeError(f"视频下载失败 ({resp.status})") - os.makedirs(os.path.dirname(save_path), exist_ok=True) + os.close(tmp_fd) with open(save_path, "wb") as f: async for chunk in resp.content.iter_chunked(8192): f.write(chunk) diff --git a/nodes/K3_video.py b/nodes/K3_video.py index 7e8ccf4..c7ce14f 100644 --- a/nodes/K3_video.py +++ b/nodes/K3_video.py @@ -7,7 +7,7 @@ K3 图生视频 自研节点(图生视频 / 多镜头) import asyncio import json import os -import re +import tempfile import aiohttp @@ -47,28 +47,6 @@ _POLL_MAX = 15 # ── 工具函数 ─────────────────────────────────────────────────────────────────── -def _get_video_dir() -> str: - if _FOLDER_PATHS_OK: - base = folder_paths.get_output_directory() - else: - plugin = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin)), "output") - d = os.path.join(base, "video") - os.makedirs(d, exist_ok=True) - return d - - -def _next_counter(directory: str, prefix: str) -> int: - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_n = 0 - if os.path.exists(directory): - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_n = max(max_n, int(m.group(1))) - return max_n + 1 - - def _image_to_base64(tensor) -> str: pil = tensor_to_pil(tensor) return encode_image_to_base64(pil[0], format="PNG") @@ -259,10 +237,8 @@ class K3Video: def _progress(pct: int): if pbar: pbar.update_absolute(5 + int(pct * 0.94), 100) - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_dir() - counter = _next_counter(video_dir, "k3") - save_path = os.path.join(video_dir, f"k3_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + tmp_fd, save_path = tempfile.mkstemp(suffix=".mp4", prefix="k3_") connector = aiohttp.TCPConnector(ssl=False, force_close=True) async with aiohttp.ClientSession(connector=connector) as session: @@ -345,7 +321,7 @@ class K3Video: async with session.get(video_url, allow_redirects=True) as resp: if resp.status != 200: raise RuntimeError(f"视频下载失败 ({resp.status})") - os.makedirs(os.path.dirname(save_path), exist_ok=True) + os.close(tmp_fd) with open(save_path, "wb") as f: async for chunk in resp.content.iter_chunked(8192): f.write(chunk) diff --git a/nodes/K3_video_firstlast.py b/nodes/K3_video_firstlast.py index 87695a9..d19f492 100644 --- a/nodes/K3_video_firstlast.py +++ b/nodes/K3_video_firstlast.py @@ -6,7 +6,7 @@ import asyncio import json import os -import re +import tempfile import aiohttp @@ -36,28 +36,6 @@ _POLL_MAX = 15 # ── 工具函数 ─────────────────────────────────────────────────────────────────── -def _get_video_dir() -> str: - if _FOLDER_PATHS_OK: - base = folder_paths.get_output_directory() - else: - plugin = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin)), "output") - d = os.path.join(base, "video") - os.makedirs(d, exist_ok=True) - return d - - -def _next_counter(directory: str, prefix: str) -> int: - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_n = 0 - if os.path.exists(directory): - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_n = max(max_n, int(m.group(1))) - return max_n + 1 - - def _prepare_image_base64(tensor) -> str: """转换并校验图片,不符合约束时自动等比缩放后返回 base64。""" import io @@ -198,10 +176,8 @@ class K3VideoFirstLast: def _progress(pct: int): if pbar: pbar.update_absolute(5 + int(pct * 0.94), 100) - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_dir() - counter = _next_counter(video_dir, "k3fl") - save_path = os.path.join(video_dir, f"k3fl_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + tmp_fd, save_path = tempfile.mkstemp(suffix=".mp4", prefix="k3fl_") connector = aiohttp.TCPConnector(ssl=False, force_close=True) async with aiohttp.ClientSession(connector=connector) as session: @@ -284,7 +260,7 @@ class K3VideoFirstLast: async with session.get(video_url, allow_redirects=True) as resp: if resp.status != 200: raise RuntimeError(f"视频下载失败 ({resp.status})") - os.makedirs(os.path.dirname(save_path), exist_ok=True) + os.close(tmp_fd) with open(save_path, "wb") as f: async for chunk in resp.content.iter_chunked(8192): f.write(chunk) diff --git a/nodes/K_video.py b/nodes/K_video.py index 31b5be1..2a25ed2 100644 --- a/nodes/K_video.py +++ b/nodes/K_video.py @@ -5,7 +5,7 @@ K26 图生视频节点 import asyncio import json import os -import re +import tempfile import aiohttp @@ -30,28 +30,6 @@ _POLL_INIT = 3 _POLL_MAX = 15 -def _get_video_dir() -> str: - if _FOLDER_PATHS_OK: - base = folder_paths.get_output_directory() - else: - plugin = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin)), "output") - d = os.path.join(base, "video") - os.makedirs(d, exist_ok=True) - return d - - -def _next_counter(directory: str, prefix: str) -> int: - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_n = 0 - if os.path.exists(directory): - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_n = max(max_n, int(m.group(1))) - return max_n + 1 - - def _image_to_base64(tensor) -> str: pil = tensor_to_pil(tensor) return encode_image_to_base64(pil[0], format="PNG") @@ -129,10 +107,8 @@ class KVideo: def _progress(pct: int): if pbar: pbar.update_absolute(5 + int(pct * 0.94), 100) - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_dir() - counter = _next_counter(video_dir, "k26") - save_path = os.path.join(video_dir, f"k26_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + tmp_fd, save_path = tempfile.mkstemp(suffix=".mp4", prefix="k26_") connector = aiohttp.TCPConnector(ssl=False, force_close=True) async with aiohttp.ClientSession(connector=connector) as session: @@ -216,7 +192,7 @@ class KVideo: async with session.get(video_url, allow_redirects=True) as resp: if resp.status != 200: raise RuntimeError(f"视频下载失败 ({resp.status})") - os.makedirs(os.path.dirname(save_path), exist_ok=True) + os.close(tmp_fd) with open(save_path, "wb") as f: async for chunk in resp.content.iter_chunked(8192): f.write(chunk) diff --git a/nodes/kling_video.py b/nodes/kling_video.py index e2c89bb..f37a3e0 100644 --- a/nodes/kling_video.py +++ b/nodes/kling_video.py @@ -3,7 +3,7 @@ Kling 3.0 Video Nodes """ import os -import re +import tempfile from ..clients.kling_client import KlingClient from ..clients.gemini_client import GeminiAPIClient @@ -11,35 +11,6 @@ from ..utils.image_utils import tensor_to_pil, encode_image_to_base64 from comfy_api.latest import InputImpl -try: - import folder_paths - FOLDER_PATHS_AVAILABLE = True -except ImportError: - FOLDER_PATHS_AVAILABLE = False - - -def _get_video_output_dir() -> str: - if FOLDER_PATHS_AVAILABLE: - base = folder_paths.get_output_directory() - else: - plugin_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin_dir)), "output") - video_dir = os.path.join(base, "video") - os.makedirs(video_dir, exist_ok=True) - return video_dir - - -def _get_next_counter(directory: str, prefix: str) -> int: - if not os.path.exists(directory): - return 1 - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_counter = 0 - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_counter = max(max_counter, int(m.group(1))) - return max_counter + 1 - def _tensor_to_base64(tensor) -> str: """ComfyUI IMAGE tensor → base64 PNG 字符串""" @@ -276,10 +247,8 @@ class KlingVideo: body["metadata"] = {"aspect_ratio": aspect_ratio} endpoint_type = "text2video" - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_output_dir() - counter = _get_next_counter(video_dir, "kling") - save_path = os.path.join(video_dir, f"kling_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + _, save_path = tempfile.mkstemp(suffix=".mp4", prefix="kling_") client = KlingClient() @@ -418,10 +387,8 @@ class KlingFirstLastFrame: else: body["prompt"] = prompt - # 保存路径 - video_dir = _get_video_output_dir() - counter = _get_next_counter(video_dir, "kling") - save_path = os.path.join(video_dir, f"kling_{counter:05d}.mp4") + # 保存路径(临时文件,避免与下游保存节点重复落盘) + _, save_path = tempfile.mkstemp(suffix=".mp4", prefix="kling_") client = KlingClient() @@ -589,10 +556,8 @@ class KlingMotionControlTest: "keep_original_sound": "yes" if keep_original_sound == "打开" else "no", } - # ── 保存路径 ────────────────────────────────────────────────── - video_dir = _get_video_output_dir() - counter = _get_next_counter(video_dir, "kling_motion") - save_path = os.path.join(video_dir, f"kling_motion_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + _, save_path = tempfile.mkstemp(suffix=".mp4", prefix="kling_motion_") client = KlingClient() diff --git a/nodes/seedance_video.py b/nodes/seedance_video.py index 72d6a43..13ba73f 100644 --- a/nodes/seedance_video.py +++ b/nodes/seedance_video.py @@ -6,7 +6,7 @@ Seedance 视频生成节点 import io import os -import re +import tempfile import aiohttp import torch @@ -18,12 +18,6 @@ from ..utils.r2_uploader import upload_video, upload_audio from comfy_api.latest import InputImpl -try: - import folder_paths - FOLDER_PATHS_AVAILABLE = True -except ImportError: - FOLDER_PATHS_AVAILABLE = False - # ── 模型列表 ────────────────────────────────────────────────────────────────── @@ -43,29 +37,6 @@ def _supports_camera_fixed(model: str) -> bool: # ── 工具函数 ────────────────────────────────────────────────────────────────── -def _get_video_output_dir() -> str: - if FOLDER_PATHS_AVAILABLE: - base = folder_paths.get_output_directory() - else: - plugin_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - base = os.path.join(os.path.dirname(os.path.dirname(plugin_dir)), "output") - video_dir = os.path.join(base, "video") - os.makedirs(video_dir, exist_ok=True) - return video_dir - - -def _get_next_counter(directory: str, prefix: str) -> int: - if not os.path.exists(directory): - return 1 - pattern = re.compile(rf"^{re.escape(prefix)}_(\d+)") - max_counter = 0 - for f in os.listdir(directory): - m = pattern.match(f) - if m: - max_counter = max(max_counter, int(m.group(1))) - return max_counter + 1 - - def _tensor_to_base64_url(tensor) -> str: """ComfyUI IMAGE tensor → data:image/png;base64,xxx""" pil_images = tensor_to_pil(tensor) @@ -267,9 +238,8 @@ class Seedance: "metadata": metadata, } - video_dir = _get_video_output_dir() - counter = _get_next_counter(video_dir, file_prefix) - save_path = os.path.join(video_dir, f"{file_prefix}_{counter:05d}.mp4") + # 保存路径(临时文件,避免与下游保存节点重复落盘) + _, save_path = tempfile.mkstemp(suffix=".mp4", prefix=f"{file_prefix}_") client = SeedanceClient() pbar = _make_pbar() @@ -440,10 +410,8 @@ class SeedanceMultiModal: if first_image_url: body["image"] = first_image_url - # ── 保存路径 ────────────────────────────────────────────────────── - video_dir = _get_video_output_dir() - counter = _get_next_counter(video_dir, "seedance_mm") - save_path = os.path.join(video_dir, f"seedance_mm_{counter:05d}.mp4") + # ── 保存路径(临时文件,避免与下游保存节点重复落盘)────────────────── + _, save_path = tempfile.mkstemp(suffix=".mp4", prefix="seedance_mm_") client = SeedanceClient() pbar = _make_pbar()