add nodes
This commit is contained in:
@@ -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>
|
||||
|
||||

|
||||
|
||||
###### 参数说明
|
||||
**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>
|
||||
|
||||

|
||||
|
||||
###### 参数说明
|
||||
**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>
|
||||
|
||||

|
||||
|
||||
###### 参数说明
|
||||
**video_path**: 视频路径,比如:`C:\Users\Desktop\111.mp4`<br>
|
||||
**output_path**: 视频保存路径,比如:`C:\Users\Desktop\output`<br>
|
||||
**segment_time**: 切割的每个视频长度,单位为秒,需要注意的是,它是根据关键帧切割视频的,所以时间不能太短。因为不能保证每一段视频都有关键帧,所以每一段视频时长不一定都一样,只是最接近的。<br>
|
||||
|
||||
___
|
||||
|
||||
##### SingleCuttingVideo节点: 作用是切割指定视频中某个时间段的视频<br>
|
||||
|
||||

|
||||
|
||||
###### 参数说明
|
||||
**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
@@ -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>
|
||||
|
||||

|
||||
|
||||
###### 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>
|
||||
|
||||

|
||||
|
||||
###### 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>
|
||||
|
||||

|
||||
|
||||
###### 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>
|
||||
|
||||

|
||||
|
||||
###### 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)
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 124 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 74 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 102 KiB |
@@ -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))
|
||||
@@ -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) # 按比例计算新高度
|
||||
|
||||
@@ -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)
|
||||
|
||||
# 替换为双斜杠
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
import os
|
||||
import torch
|
||||
from PIL import ImageOps
|
||||
import comfy
|
||||
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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,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
@@ -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',
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user