add nodes

This commit is contained in:
yueliang
2024-10-17 17:10:37 +08:00
parent ff4153667d
commit ae034f021b
18 changed files with 528 additions and 28 deletions
+53 -1
View File
@@ -98,9 +98,61 @@ ___
###### 参数说明
**video_path**: 本地视频路径,比如:`C:\Users\Desktop\222.mp4`<br>
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
**output_path**: 音频保存路径,比如:`C:\Users\Desktop\output`<br>
**audio_format**: 保存音频格式,包括 **.m4a**,**.mp3**,**.wav**,**.aac**,**.flac**,**.wma**,**.ogg**,**.ac3**,**.amr**,**.aiff**,**.opus**,**.m4b**,**.caf**,**.dts** 等等。<br>
___
##### MergingVideo节点: 作用是合并两个视频,比如把两个一小时的视频合并成一个时长为2小时的视频<br>
![](./assets/7.png)
###### 参数说明
**video1_path**: 视频路径,比如:`C:\Users\Desktop\111.mp4`<br>
**video2_path**: 视频路径,比如:`C:\Users\Desktop\222.mp4`<br>
**device**: 分为CPU和GPU,如果你用CPU合并两个视频出错的话,可以尝试用GPU。<br>
**resolution_reference**: 合并后的视频尺寸是多少,可以参考第一个视频或者第二个视频,即video1或者video2。<br>
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
___
##### StitchingVideo节点: 作用是拼接两个视频,分成水平拼接和垂直拼接两种拼接方式<br>
![](./assets/8.png)
###### 参数说明
**video1_path**: 视频路径,比如:`C:\Users\Desktop\111.mp4`<br>
**video2_path**: 视频路径,比如:`C:\Users\Desktop\222.mp4`<br>
**device**: 分为CPU和GPU,如果你用CPU拼接两个视频出错的话,可以尝试用GPU。<br>
**use_audio**: 拼接后的视频使用哪个视频的音频,可以选择第一个视频的音频或者第二个视频的音频,即video1或者video2。<br>
**stitching_type**: 拼接视频方式,分为水平拼接(horizontal)和垂直拼接(vertical)两种方式。<br>
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
___
##### MultiCuttingVideo节点: 作用是把一个视频切割成若干个视频<br>
![](./assets/9.png)
###### 参数说明
**video_path**: 视频路径,比如:`C:\Users\Desktop\111.mp4`<br>
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
**segment_time**: 切割的每个视频长度,单位为秒,需要注意的是,它是根据关键帧切割视频的,所以时间不能太短。因为不能保证每一段视频都有关键帧,所以每一段视频时长不一定都一样,只是最接近的。<br>
___
##### SingleCuttingVideo节点: 作用是切割指定视频中某个时间段的视频<br>
![](./assets/10.png)
###### 参数说明
**video_path**: 视频路径,比如:`C:\Users\Desktop\111.mp4`<br>
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
**start_time**: 设置切割的开始时间点,设置为00:00:10的话就表示从视频中的第10秒开始切割。<br>
**end_time**: 设置切割的结束时间点,设置为00:05:00,表示切割到视频中的第5分钟为止。<br>
___
## 社交账号
- Bilibili:[我的B站主页](https://space.bilibili.com/1303099255)
+51 -1
View File
@@ -98,9 +98,59 @@ ___
###### Parameter Description
**video_path**: local video path,e.g.:`C:\Users\Desktop\222.mp4`<br>
**output_path**: video save path,e.g.:`C:\Users\Desktop\output`<br>
**output_path**: audio save path,e.g.:`C:\Users\Desktop\output`<br>
**audio_format**: Save audio formats, including **.m4a** , **.mp3** , **.wav** , **.aac** , **.flac** , **.wma** , **.ogg** , **.ac3** , **.amr** , **.aiff** , **.opus** , **.m4b** , **.caf** , **.dts** etc. <br>
___
##### MergingVideo Node: The purpose is to merge two videos, for example, to combine two one-hour videos into a single two-hour video.<br>
![](./assets/7.png)
###### Parameter Description
**video1_path**: local video path,e.g.:`C:\Users\Desktop\111.mp4`<br>
**video2_path**: local video path,e.g.:`C:\Users\Desktop\222.mp4`<br>
**device**: There are two options: CPU and GPU,if you encounter an error while merging two videos using the CPU option, you can try using the GPU instead.<br>
**resolution_reference**: What is the size of the merged video? You can refer to either the first video or the second video, that is, video1 or video2.<br>
**output_path**: video save path,e.g.:`C:\Users\Desktop\output`<br>
___
##### StitchingVideo Node: The purpose is to stitching two videos, which can be done in two ways: horizontal stitching and vertical stitching.<br>
![](./assets/8.png)
###### Parameter Description
**video1_path**: local video path,e.g.:`C:\Users\Desktop\111.mp4`<br>
**video2_path**: local video path,e.g.:`C:\Users\Desktop\222.mp4`<br>
**device**: There are two options: CPU and GPU, if you encounter an error while stitching two videos using the CPU, you can try using the GPU instead.<br>
**use_audio**: Which audio will be used in the stitched video? You can choose the audio from either the first video or the second video, that is, from video1 or video2.<br>
**stitching_type**: The methods for stitching videos are divided into two types: horizontal stitching and vertical stitching.<br>
**output_path**: video save path,e.g.:`C:\Users\Desktop\output`<br>
___
##### MultiCuttingVideo Node: The purpose is to split one video into several smaller videos.<br>
![](./assets/9.png)
###### Parameter Description
**video_path**: local video path,e.g.:`C:\Users\Desktop\111.mp4`<br>
**output_path**: video save path,e.g.:`C:\Users\Desktop\output`<br>
**segment_time**: The length of each cut video is measured in seconds. It’s important to note that the video is cut based on keyframes, so the duration cannot be too short. Since there is no guarantee that each segment of the video will have a keyframe, the duration of each segment may not be the same, but it will be the closest possible.<br>
___
##### SingleCuttingVideo Node: The purpose is to cut a specific time segment from a designated video.<br>
![](./assets/10.png)
###### Parameter Description
**video_path**: local video path,e.g.:`C:\Users\Desktop\111.mp4`<br>
**output_path**: video save path,e.g.:`C:\Users\Desktop\output`<br>
**start_time**: Set the start time for the cut; for example, setting it to 00:00:10 means cutting from the 10th second of the video.<br>
**end_time**: Set the end time for the cut; for example, setting it to 00:05:00 means cutting until the 5th minute of the video.<br>
## Social Account Homepage
- Bilibili:[My BILIBILI Homepage](https://space.bilibili.com/1303099255)
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 107 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 102 KiB

+29 -1
View File
@@ -6,6 +6,7 @@ import json
import re
import os
import shutil
import time
from concurrent.futures import ThreadPoolExecutor,as_completed
def copy_image(image_path, destination_directory):
@@ -108,4 +109,31 @@ def get_image_size(image_path):
with Image.open(image_path) as img:
# 获取图像的宽度和高度
width, height = img.size
return width, height
return width, height
def has_audio(video_path):
cmd = [
'ffprobe',
'-v', 'error',
'-select_streams', 'a:0',
'-show_entries', 'stream=codec_type',
'-of', 'default=noprint_wrappers=1:nokey=1',
video_path
]
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
return result.stdout.decode().strip() == 'audio'
def set_file_name(video_path):
file_name = os.path.basename(video_path)
file_extension = os.path.splitext(file_name)[1]
#文件名根据年月日时分秒来命名
file_name = time.strftime("%Y%m%d%H%M%S", time.localtime()) + file_extension
return file_name
def video_type():
return ('.mp4', '.avi', '.mov', '.mkv','.rmvb','.wmv','.flv')
def validate_time_format(time_str):
pattern = r'^([0-1][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|\d{1,2})$'
return bool(re.match(pattern, time_str))
+3 -8
View File
@@ -1,8 +1,6 @@
import os
from PIL import Image
import subprocess
import time
from ..func import get_image_size
from ..func import get_image_size,set_file_name,video_type
class AddImgWatermark:
@@ -36,7 +34,7 @@ class AddImgWatermark:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv','.rmvb')):
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.exists(video_path):
@@ -53,10 +51,7 @@ class AddImgWatermark:
if not os.path.exists(watermark_image):
raise ValueError("watermark_image:"+watermark_image+"不存在(watermark_image :"+watermark_image+" does not exist)")
file_name = os.path.basename(video_path)
file_extension = os.path.splitext(file_name)[1]
#文件名根据年月日时分秒来命名
file_name = time.strftime("%Y%m%d%H%M%S", time.localtime()) + file_extension
file_name = set_file_name(video_path)
output_path = os.path.join(output_path, file_name)
width,height = get_image_size(watermark_image)
watermark_img_height = int(height * watermark_img_width / width) # 按比例计算新高度
+3 -6
View File
@@ -1,7 +1,7 @@
import os
import subprocess
import folder_paths
import time
from ..func import set_file_name,video_type
current_path = os.path.abspath(__file__)
font_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.normpath(__file__))), 'fonts')
@@ -39,7 +39,7 @@ class AddTextWatermark:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv','.rmvb')):
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.exists(video_path):
@@ -49,10 +49,7 @@ class AddTextWatermark:
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
file_name = os.path.basename(video_path)
file_extension = os.path.splitext(file_name)[1]
#文件名根据年月日时分秒来命名
file_name = time.strftime("%Y%m%d%H%M%S", time.localtime()) + file_extension
file_name = set_file_name(video_path)
output_path = os.path.join(output_path, file_name)
# 替换为双斜杠
+3 -3
View File
@@ -1,6 +1,6 @@
import os
import time
import subprocess
from ..func import video_type
class ExtractAudio:
def __init__(self):
@@ -17,7 +17,7 @@ class ExtractAudio:
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_complete_path",)
RETURN_NAMES = ("audio_complete_path",)
FUNCTION = "extract_audio"
OUTPUT_NODE = True
CATEGORY = "🔥FFmpeg"
@@ -27,7 +27,7 @@ class ExtractAudio:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv','.rmvb')):
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.isfile(video_path):
raise ValueError("video_path:"+video_path+"不存在(video_path:"+video_path+" does not exist)")
-1
View File
@@ -1,4 +1,3 @@
import os
import torch
from PIL import ImageOps
import comfy
+121
View File
@@ -0,0 +1,121 @@
import os
import subprocess
from ..func import has_audio,getVideoInfo,set_file_name,video_type
import torch
device = "cuda" if torch.cuda.is_available() else "cpu"
class MergingVideo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video1_path": ("STRING", {"default":"C:/Users/Desktop/video1.mp4",}),
"video2_path": ("STRING", {"default":"C:/Users/Desktop/video2.mp4",}),
"device": (["cpu","cuda"], {"default":device,}),
"resolution_reference": (["video1","video2"], {"default":"video1",}),
"output_path": ("STRING", {"default": "C:/Users/Desktop/output"}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_complete_path",)
FUNCTION = "merging_video"
OUTPUT_NODE = True
CATEGORY = "🔥FFmpeg"
def merging_video(self, video1_path, video2_path,device,resolution_reference,output_path):
try:
video1_path = os.path.abspath(video1_path).strip()
video2_path = os.path.abspath(video2_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video1_path.lower().endswith(video_type()):
raise ValueError("video1_path:"+video1_path+"不是视频文件(video1_path:"+video1_path+" is not a video file)")
if not os.path.isfile(video1_path):
raise ValueError("video1_path:"+video1_path+"不存在(video1_path:"+video1_path+" does not exist)")
if not video2_path.lower().endswith(video_type()):
raise ValueError("video2_path:"+video2_path+"不是视频文件(video2_path:"+video2_path+" is not a video file)")
if not os.path.isfile(video2_path):
raise ValueError("video2_path:"+video2_path+"不存在(video2_path:"+video2_path+" does not exist)")
#判断output_path是否是一个目录
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
video1_audio = has_audio(video1_path)
video2_audio = has_audio(video2_path)
final_output = set_file_name(video1_path)
#文件名根据年月日时分秒来命名
output_path = os.path.join(output_path, final_output)
video = {
'video1': video1_path,
'video2': video2_path,
}.get(resolution_reference, video1_path)
video_info = getVideoInfo(video)
width = video_info['width']
height = video_info['height']
use_cuvid = ""
use_encoder = "-c:v libx264" #默认用CPU编码
if device == "cuda":
use_cuvid = "-hwaccel cuda"
use_encoder = "-c:v h264_nvenc"
if video1_audio and video2_audio: #两个视频都有音频
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex \
"[0:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v0]; \
[1:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v1]; \
[v0][v1]concat=n=2:v=1:a=0[outv]; \
[0:a][1:a]concat=n=2:v=0:a=1[outa]" \
-map "[outv]" -map "[outa]" -r 30 {use_encoder} -c:a aac -ar 44100 -b:a 128k {output_path}'
elif video1_audio and not video2_audio: #第一个视频有音频,第二个没有
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex \
"[0:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v0]; \
[1:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v1]; \
[v0][v1]concat=n=2:v=1:a=0[outv]" \
-map "[outv]" -map "0:a" -r 30 {use_encoder} -c:a aac -ar 44100 -b:a 128k {output_path}'
elif not video1_audio and video2_audio: #第一个视频没有音频,第二个有
video_info = getVideoInfo(video1_path)
duration = video_info['duration']
delay_time = int(duration * 1000) # 转换为毫秒
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex \
"[0:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v0]; \
[1:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v1]; \
[v0][v1]concat=n=2:v=1:a=0[outv]; \
[1:a]adelay={delay_time}|{delay_time}[a1]; \
[a1]concat=n=1:v=0:a=1[outa]" \
-map "[outv]" -map "[outa]" -r 30 {use_encoder} -c:a aac -ar 44100 -b:a 128k {output_path}'
else: #两个视频都没有音频
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex \
"[0:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v0]; \
[1:v]scale={width}:{height},setsar=1,setpts=PTS-STARTPTS[v1]; \
[v0][v1]concat=n=2:v=1:a=0[outv]" \
-map "[outv]" -r 30 {use_encoder} -an {output_path}'
# 执行命令并检查错误
result = subprocess.run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
# 检查返回码
if result.returncode != 0:
# 如果有错误,输出错误信息
print(f"Error: {result.stderr.decode('utf-8')}")
raise ValueError(f"Error: {result.stderr.decode('utf-8')}")
else:
# 输出标准输出信息
print(result.stdout)
return (output_path,)
except Exception as e:
raise ValueError(e)
+65
View File
@@ -0,0 +1,65 @@
import os
import subprocess
from ..func import video_type
class MultiCuttingVideo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video_path": ("STRING", {"default":"C:/Users/Desktop/video.mp4",}),
"output_path": ("STRING", {"default":"C:/Users/Desktop/output",}),
"segment_time": ("INT",{"default":10,"min":1,}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_complete_path",)
FUNCTION = "multi_cutting_video"
OUTPUT_NODE = True
CATEGORY = "🔥FFmpeg"
# 视频切割,根据关键帧切割,所以时间不能太短,不能保证每一段视频都有关键帧,所以每一段时长不一定是segment_time,只是最接近的
def multi_cutting_video(self, video_path, output_path,segment_time):
try:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.isfile(video_path):
raise ValueError("video_path:"+video_path+"不存在(video_path:"+video_path+" does not exist)")
#判断output_path是否是一个目录
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
file_full_name = os.path.basename(video_path)
file_name = os.path.splitext(file_full_name)[0]
file_extension = os.path.splitext(file_full_name)[1]
#ffmpeg -i input.mp4 -f segment -segment_time 30 -c copy output%03d.mp4
command = [
'ffmpeg', '-i', video_path, # 输入视频路径
'-f', 'segment','-reset_timestamps','1',"-segment_time",str(segment_time), # 使用scale滤镜缩放帧
'-c','copy',output_path+os.sep+file_name+"_%08d"+file_extension, # 输出视频路径
]
# 执行命令并检查错误
result = subprocess.run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
# 检查返回码
if result.returncode != 0:
# 如果有错误,输出错误信息
print(f"Error: {result.stderr.decode('utf-8')}")
raise ValueError(f"Error: {result.stderr.decode('utf-8')}")
else:
# 输出标准输出信息
print(result.stdout)
return (output_path,)
except Exception as e:
raise ValueError(e)
+74
View File
@@ -0,0 +1,74 @@
import os
import subprocess
from datetime import datetime
from ..func import video_type,set_file_name,validate_time_format
class SingleCuttingVideo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video_path": ("STRING", {"default":"C:/Users/Desktop/video.mp4",}),
"output_path": ("STRING", {"default":"C:/Users/Desktop/output",}),
"start_time": ("STRING", {"default":"00:00:00",}),
"end_time": ("STRING", {"default":"00:00:10",}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("cutting_video_single",)
FUNCTION = "single_cutting_video"
OUTPUT_NODE = True
CATEGORY = "🔥FFmpeg"
# 视频切割,根据关键帧切割,所以时间不能太短,不能保证每一段视频都有关键帧,所以每一段时长不一定是segment_time,只是最接近的
def single_cutting_video(self, video_path, output_path,start_time,end_time):
try:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.isfile(video_path):
raise ValueError("video_path:"+video_path+"不存在(video_path:"+video_path+" does not exist)")
#判断output_path是否是一个目录
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
if not validate_time_format(start_time) or not validate_time_format(end_time):
raise ValueError("start_time或者end_time时间格式不对(start_time or end_time is not in time format)")
time_format = "%H:%M:%S"
start_dt = datetime.strptime(start_time, time_format)
end_dt = datetime.strptime(end_time, time_format)
if start_dt >= end_dt:
raise ValueError("start_time必须小于end_time(start_time must be less than end_time)")
file_name = set_file_name(video_path)
output_path = os.path.join(output_path, file_name)
#ffmpeg -i input.mp4 -ss START_TIME -to END_TIME -c copy output.mp4
command = [
'ffmpeg', '-i', video_path, # 输入视频路径
'-ss', start_time,'-to', end_time,
'-c','copy',output_path
]
# 执行命令并检查错误
result = subprocess.run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
# 检查返回码
if result.returncode != 0:
# 如果有错误,输出错误信息
print(f"Error: {result.stderr.decode('utf-8')}")
raise ValueError(f"Error: {result.stderr.decode('utf-8')}")
else:
# 输出标准输出信息
print(result.stdout)
return (output_path,)
except Exception as e:
raise ValueError(e)
+107
View File
@@ -0,0 +1,107 @@
import os
import subprocess
from ..func import has_audio,getVideoInfo,set_file_name,video_type
import torch
import math
device = "cuda" if torch.cuda.is_available() else "cpu"
class StitchingVideo:
def __init__(self):
pass
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"video1_path": ("STRING", {"default":"C:/Users/Desktop/video1.mp4",}),
"video2_path": ("STRING", {"default":"C:/Users/Desktop/video2.mp4",}),
"device": (["cpu","cuda"], {"default":device,}),
"use_audio": (["video1","video2"], {"default":"video1",}),
"stitching_type":(["horizontal","vertical"], {"default":"horizontal",}),
"output_path": ("STRING", {"default": "C:/Users/Desktop/output"}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("video_complete_path",)
FUNCTION = "stitching_video"
OUTPUT_NODE = True
CATEGORY = "🔥FFmpeg"
def stitching_video(self, video1_path, video2_path,device,use_audio,stitching_type,output_path):
try:
video1_path = os.path.abspath(video1_path).strip()
video2_path = os.path.abspath(video2_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video1_path.lower().endswith(video_type()):
raise ValueError("video1_path:"+video1_path+"不是视频文件(video1_path:"+video1_path+" is not a video file)")
if not os.path.isfile(video1_path):
raise ValueError("video1_path:"+video1_path+"不存在(video1_path:"+video1_path+" does not exist)")
if not video2_path.lower().endswith(video_type()):
raise ValueError("video2_path:"+video2_path+"不是视频文件(video2_path:"+video2_path+" is not a video file)")
if not os.path.isfile(video2_path):
raise ValueError("video2_path:"+video2_path+"不存在(video2_path:"+video2_path+" does not exist)")
#判断output_path是否是一个目录
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
video1_audio = has_audio(video1_path)
video2_audio = has_audio(video2_path)
final_output = set_file_name(video1_path)
#文件名根据年月日时分秒来命名
output_path = os.path.join(output_path, final_output)
use_cuvid = ""
use_encoder = "-c:v libx264" #默认用CPU编码
if device == "cuda":
use_cuvid = "-hwaccel cuda"
use_encoder = "-c:v h264_nvenc"
video_info = getVideoInfo(video1_path)
# libx264、libx265等编码器要求宽高必须是2的倍数,如果报错,可以把device换为GPU
width = math.ceil(video_info['width']/2)*2
height = math.ceil(video_info['height']/2)*2
use_audio = {
'video1': '0',
'video2': '1',
}.get(use_audio, '0')
tack_type = {
'horizontal': 'hstack',
'vertical': 'vstack',
}.get(stitching_type, 'hstack')
scale = {
'horizontal':f'-1:{height}',
'vertical': f'{width}:-1',
}.get(stitching_type, f'{width}:-1')
if video1_audio or video2_audio:
#-map 1:a 指定使用第二个视频的音频流
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex "[1:v]scale={scale}[vid2];[0:v][vid2]{tack_type}=inputs=2[v]" -map "[v]" -map {use_audio}:a? {use_encoder} -c:a aac -strict experimental {output_path}'
else:
command = f'ffmpeg {use_cuvid} -i {video1_path} -i {video2_path} -filter_complex "[1:v]scale={scale}[vid2];[0:v][vid2]{tack_type}=inputs=2[v]" -map "[v]" {use_encoder} {output_path}'
# 执行命令并检查错误
result = subprocess.run(command, stderr=subprocess.PIPE, stdout=subprocess.PIPE)
# 检查返回码
if result.returncode != 0:
# 如果有错误,输出错误信息
print(f"Error: {result.stderr.decode('utf-8')}")
raise ValueError(f"Error: {result.stderr.decode('utf-8')}")
else:
# 输出标准输出信息
print(result.stdout)
return (output_path,)
except Exception as e:
raise ValueError(e)
+2 -1
View File
@@ -2,6 +2,7 @@ import json
import math
import os
import subprocess
from ..func import video_type
class Video2Frames:
def __init__(self):
@@ -30,7 +31,7 @@ class Video2Frames:
# 提取音频
audio_path = os.path.join(output_path, 'audio.mp3')
# 视频不存在
if not video_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv','.rmvb')):
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
print("视频文件路径:"+video_path)
if not os.path.isfile(video_path):
+5 -6
View File
@@ -1,6 +1,6 @@
import os
import time
import subprocess
from ..func import set_file_name,video_type
class VideoFlip:
def __init__(self):
@@ -27,7 +27,7 @@ class VideoFlip:
video_path = os.path.abspath(video_path).strip()
output_path = os.path.abspath(output_path).strip()
# 视频不存在
if not video_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv','.rmvb')):
if not video_path.lower().endswith(video_type()):
raise ValueError("video_path:"+video_path+"不是视频文件(video_path:"+video_path+" is not a video file)")
if not os.path.isfile(video_path):
raise ValueError("video_path:"+video_path+"不存在(video_path:"+video_path+" does not exist)")
@@ -35,10 +35,9 @@ class VideoFlip:
#判断output_path是否是一个目录
if not os.path.isdir(output_path):
raise ValueError("output_path:"+output_path+"不是目录(output_path:"+output_path+" is not a directory)")
file_name = os.path.basename(video_path)
file_extension = os.path.splitext(file_name)[1]
#文件名根据年月日时分秒来命名
file_name = time.strftime("%Y%m%d%H%M%S", time.localtime()) + file_extension
file_name = set_file_name(video_path)
output_path = os.path.join(output_path, file_name)
flip = {
'horizontal': 'hflip',
+12
View File
@@ -7,6 +7,10 @@ from .nodes.extractAudio import *
from .nodes.loadImageFromDir import *
from .nodes.imageCopy import *
from .nodes.imagePath2Tensor import *
from .nodes.mergingVideo import *
from .nodes.stitchingVideo import *
from .nodes.multiCuttingVideo import *
from .nodes.singleCuttingVideo import *
NODE_CLASS_MAPPINGS = {
"Video2Frames": Video2Frames,
@@ -18,6 +22,10 @@ NODE_CLASS_MAPPINGS = {
"LoadImageFromDir": LoadImageFromDir,
"ImageCopy": ImageCopy,
"ImagePath2Tensor": ImagePath2Tensor,
"MergingVideo": MergingVideo,
"StitchingVideo": StitchingVideo,
"MultiCuttingVideo": MultiCuttingVideo,
"SingleCuttingVideo": SingleCuttingVideo,
}
# A dictionary that contains the friendly/humanly readable titles for the nodes
@@ -31,4 +39,8 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"LoadImageFromDir": "🔥LoadImageFromDir",
"ImageCopy": "🔥ImageCopy",
"ImagePath2Tensor": "🔥ImagePath2Tensor",
"MergingVideo": "🔥MergingVideo",
"StitchingVideo": "🔥StitchingVideo",
"MultiCuttingVideo": "🔥MultiCuttingVideo",
"SingleCuttingVideo": "🔥SingleCuttingVideo",
}