support merge video

This commit is contained in:
jax
2025-06-22 22:53:13 +08:00
parent 9970d3aec0
commit fc1c673c75
2 changed files with 215 additions and 3 deletions
+3 -3
View File
@@ -1,21 +1,21 @@
from .nodes.SaveVideo import VideoBasicSaveVideo
from .nodes.VideoUpload import VideoBasicUploadVideo
from .nodes.upscaleNode import VideoUpscaleWithModel
from .nodes.MergeVideo import VideoBasicMergeVideo
NODE_CLASS_MAPPINGS = {
"VideoBasicLoadVideo": VideoBasicUploadVideo,
"VideoBasicVideoSave": VideoBasicSaveVideo,
"VideoBasicVideoUpscaleWithModel": VideoUpscaleWithModel,
"VideoBasicMergeVideo": VideoBasicMergeVideo,
}
NODE_DISPLAY_NAME_MAPPINGS = {
"VideoBasicLoadVideo": "VideoBasic Load Video",
"VideoBasicVideoSave": "VideoBasic Save Video",
"VideoBasicVideoUpscaleWithModel": "VideoBasic Video Upscale with Model",
"VideoBasicMergeVideo": "VideoBasic Merge Video",
}
WEB_DIRECTORY = "./web"
+212
View File
@@ -0,0 +1,212 @@
import os
import subprocess
import tempfile
import folder_paths
class VideoBasicMergeVideo:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"video_path_1": ("STRING",),
"video_path_2": ("STRING",),
},
"optional": {
"video_path_3": ("STRING", {"default": ""}),
"video_path_4": ("STRING", {"default": ""}),
"video_path_5": ("STRING", {"default": ""}),
"video_path_6": ("STRING", {"default": ""}),
"video_path_7": ("STRING", {"default": ""}),
"video_path_8": ("STRING", {"default": ""}),
"video_path_9": ("STRING", {"default": ""}),
"video_path_10": ("STRING", {"default": ""}),
"output_filename": ("STRING", {"default": "merged_video.mp4"}),
"use_gpu": ("BOOLEAN", {"default": False}),
},
}
CATEGORY = "VideoBasic"
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("merged_video_path",)
FUNCTION = "merge_videos"
def merge_videos(self, video_path_1, video_path_2, video_path_3="", video_path_4="",
video_path_5="", video_path_6="", video_path_7="", video_path_8="",
video_path_9="", video_path_10="", output_filename="merged_video.mp4",
use_gpu=False):
# 收集所有非空的视频路径
video_paths = [video_path_1, video_path_2]
optional_paths = [video_path_3, video_path_4, video_path_5, video_path_6,
video_path_7, video_path_8, video_path_9, video_path_10]
for path in optional_paths:
if path and path.strip():
video_paths.append(path)
print(f"Merging {len(video_paths)} videos using FFmpeg: {video_paths}")
# 验证所有视频文件是否存在
for i, path in enumerate(video_paths):
if not os.path.exists(path):
raise ValueError(f"Video file {i+1} not found: {path}")
# 获取输出目录
output_dir = folder_paths.get_output_directory()
output_path = os.path.join(output_dir, output_filename)
# 检查FFmpeg是否可用
try:
subprocess.run(['ffmpeg', '-version'], capture_output=True, check=True)
except (subprocess.CalledProcessError, FileNotFoundError):
raise RuntimeError("FFmpeg not found. Please install FFmpeg to use this node.")
# 使用FFmpeg的concat filter进行合并
# 这种方法比逐帧处理快得多,因为它直接在编码层面操作
try:
# 方法1: 使用concat filter (适用于不同格式的视频)
# 构建filter_complex参数
inputs = []
filter_parts = []
for i, video_path in enumerate(video_paths):
inputs.extend(['-i', video_path])
filter_parts.append(f'[{i}:v][{i}:a]')
# 构建concat filter
concat_filter = f"{''.join(filter_parts)}concat=n={len(video_paths)}:v=1:a=1[outv][outa]"
# 构建完整的FFmpeg命令
cmd = ['ffmpeg', '-y'] + inputs + [
'-filter_complex', concat_filter,
'-map', '[outv]',
'-map', '[outa]',
'-c:v', 'libx264', # 使用H.264编码
'-preset', 'medium', # 平衡速度和质量
'-crf', '23', # 质量设置
'-c:a', 'aac', # 音频编码
'-b:a', '128k', # 音频比特率
output_path
]
# 如果启用GPU加速
if use_gpu:
# 尝试使用NVIDIA GPU加速
cmd[cmd.index('-c:v')+1] = 'h264_nvenc'
cmd.extend(['-preset', 'fast'])
print("Using GPU acceleration (NVIDIA)")
print(f"Running FFmpeg command: {' '.join(cmd)}")
# 执行FFmpeg命令
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
# 如果GPU加速失败,回退到CPU
if use_gpu:
print("GPU acceleration failed, falling back to CPU...")
cmd[cmd.index('h264_nvenc')] = 'libx264'
# 移除GPU特定参数
if '-preset' in cmd and 'fast' in cmd:
preset_idx = cmd.index('-preset')
cmd[preset_idx+1] = 'medium'
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg failed: {result.stderr}")
print(f"Successfully merged {len(video_paths)} videos to: {output_path}")
return (output_path,)
except Exception as e:
# 如果concat filter失败,尝试使用文件列表方法
print(f"Concat filter failed ({e}), trying file list method...")
return self._merge_with_filelist(video_paths, output_path, use_gpu)
def _merge_with_filelist(self, video_paths, output_path, use_gpu=False):
"""使用文件列表方法合并视频(适用于相同格式的视频)"""
# 创建临时文件列表
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
filelist_path = f.name
for video_path in video_paths:
# 转义文件路径中的特殊字符
escaped_path = video_path.replace("'", "'\"'\"'")
f.write(f"file '{escaped_path}'\n")
try:
# 使用concat demuxer
cmd = [
'ffmpeg', '-y',
'-f', 'concat',
'-safe', '0',
'-i', filelist_path,
'-c', 'copy', # 直接复制,不重新编码(最快)
output_path
]
print(f"Running FFmpeg with file list: {' '.join(cmd)}")
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
print(f"File list method failed: {result.stderr}")
# 如果直接复制失败,尝试重新编码
cmd = [
'ffmpeg', '-y',
'-f', 'concat',
'-safe', '0',
'-i', filelist_path,
'-c:v', 'libx264',
'-preset', 'medium',
'-crf', '23',
'-c:a', 'aac',
output_path
]
if use_gpu:
cmd[cmd.index('-c:v')+1] = 'h264_nvenc'
result = subprocess.run(cmd, capture_output=True, text=True)
if result.returncode != 0:
raise RuntimeError(f"FFmpeg failed: {result.stderr}")
return (output_path,)
finally:
# 清理临时文件
try:
os.unlink(filelist_path)
except:
pass
@classmethod
def VALIDATE_INPUTS(s, video_path_1, video_path_2, **kwargs):
# 验证必需的视频文件
if not os.path.exists(video_path_1):
return f"Video file 1 not found: {video_path_1}"
if not os.path.exists(video_path_2):
return f"Video file 2 not found: {video_path_2}"
# 验证可选的视频文件
optional_paths = [
kwargs.get('video_path_3', ''),
kwargs.get('video_path_4', ''),
kwargs.get('video_path_5', ''),
kwargs.get('video_path_6', ''),
kwargs.get('video_path_7', ''),
kwargs.get('video_path_8', ''),
kwargs.get('video_path_9', ''),
kwargs.get('video_path_10', ''),
]
for i, path in enumerate(optional_paths, 3):
if path and path.strip() and not os.path.exists(path):
return f"Video file {i} not found: {path}"
return True