mirror of
https://github.com/linyqh/NarratoAI.git
synced 2025-12-12 11:22:51 +00:00
feat(test): 添加 video 处理测试脚本
- 新增 test_moviepy_merge.py,实现视频合并功能 - 新增 test_moviepy_speed.py,实现视频加速处理- 添加 VideoMerger 和 VideoSpeedProcessor 类,分别用于视频合并和速度处理 - 测试脚本展示了如何使用 moviepy 进行视频编辑
This commit is contained in:
parent
cc44aab181
commit
3d4a58e5cf
143
app/test/test_moviepy_merge.py
Normal file
143
app/test/test_moviepy_merge.py
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
"""
|
||||||
|
使用 moviepy 合并视频、音频、字幕和背景音乐
|
||||||
|
"""
|
||||||
|
|
||||||
|
from moviepy.editor import (
|
||||||
|
VideoFileClip,
|
||||||
|
AudioFileClip,
|
||||||
|
TextClip,
|
||||||
|
CompositeVideoClip,
|
||||||
|
concatenate_videoclips
|
||||||
|
)
|
||||||
|
# from moviepy.config import change_settings
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 设置字体文件路径(用于中文字幕显示)
|
||||||
|
FONT_PATH = "../../resource/fonts/STHeitiMedium.ttc" # 请确保此路径下有对应字体文件
|
||||||
|
# change_settings(
|
||||||
|
# {"IMAGEMAGICK_BINARY": r"C:\Program Files\ImageMagick-7.1.1-Q16\magick.exe"}) # Windows系统需要设置 ImageMagick 路径
|
||||||
|
|
||||||
|
|
||||||
|
class VideoMerger:
|
||||||
|
"""视频合并处理类"""
|
||||||
|
|
||||||
|
def __init__(self, output_path: str = "../../resource/videos/merged_video.mp4"):
|
||||||
|
"""
|
||||||
|
初始化视频合并器
|
||||||
|
参数:
|
||||||
|
output_path: 输出文件路径
|
||||||
|
"""
|
||||||
|
self.output_path = output_path
|
||||||
|
self.video_clips = []
|
||||||
|
self.background_music = None
|
||||||
|
self.subtitles = []
|
||||||
|
|
||||||
|
def add_video(self, video_path: str, start_time: str = None, end_time: str = None) -> None:
|
||||||
|
"""
|
||||||
|
添加视频片段
|
||||||
|
参数:
|
||||||
|
video_path: 视频文件路径
|
||||||
|
start_time: 开始时间 (格式: "MM:SS")
|
||||||
|
end_time: 结束时间 (格式: "MM:SS")
|
||||||
|
"""
|
||||||
|
video = VideoFileClip(video_path)
|
||||||
|
if start_time and end_time:
|
||||||
|
video = video.subclip(self._time_to_seconds(start_time),
|
||||||
|
self._time_to_seconds(end_time))
|
||||||
|
self.video_clips.append(video)
|
||||||
|
|
||||||
|
def add_audio(self, audio_path: str, volume: float = 1.0) -> None:
|
||||||
|
"""
|
||||||
|
添加背景音乐
|
||||||
|
参数:
|
||||||
|
audio_path: 音频文件路径
|
||||||
|
volume: 音量大小 (0.0-1.0)
|
||||||
|
"""
|
||||||
|
self.background_music = AudioFileClip(audio_path).volumex(volume)
|
||||||
|
|
||||||
|
def add_subtitle(self, text: str, start_time: str, end_time: str,
|
||||||
|
position: tuple = ('center', 'bottom'), fontsize: int = 24) -> None:
|
||||||
|
"""
|
||||||
|
添加字幕
|
||||||
|
参数:
|
||||||
|
text: 字幕文本
|
||||||
|
start_time: 开始时间 (格式: "MM:SS")
|
||||||
|
end_time: 结束时间 (格式: "MM:SS")
|
||||||
|
position: 字幕位置
|
||||||
|
fontsize: 字体大小
|
||||||
|
"""
|
||||||
|
subtitle = TextClip(
|
||||||
|
text,
|
||||||
|
font=FONT_PATH,
|
||||||
|
fontsize=fontsize,
|
||||||
|
color='white',
|
||||||
|
stroke_color='black',
|
||||||
|
stroke_width=2
|
||||||
|
)
|
||||||
|
|
||||||
|
subtitle = subtitle.set_position(position).set_duration(
|
||||||
|
self._time_to_seconds(end_time) - self._time_to_seconds(start_time)
|
||||||
|
).set_start(self._time_to_seconds(start_time))
|
||||||
|
|
||||||
|
self.subtitles.append(subtitle)
|
||||||
|
|
||||||
|
def merge(self) -> None:
|
||||||
|
"""合并所有媒体元素并导出视频"""
|
||||||
|
if not self.video_clips:
|
||||||
|
raise ValueError("至少需要添加一个视频片段")
|
||||||
|
|
||||||
|
# 合并视频片段
|
||||||
|
final_video = concatenate_videoclips(self.video_clips)
|
||||||
|
|
||||||
|
# 如果有背景音乐,设置其持续时间与视频相同
|
||||||
|
if self.background_music:
|
||||||
|
self.background_music = self.background_music.set_duration(final_video.duration)
|
||||||
|
final_video = final_video.set_audio(self.background_music)
|
||||||
|
|
||||||
|
# 添加字幕
|
||||||
|
if self.subtitles:
|
||||||
|
final_video = CompositeVideoClip([final_video] + self.subtitles)
|
||||||
|
|
||||||
|
# 导出最终视频
|
||||||
|
final_video.write_videofile(
|
||||||
|
self.output_path,
|
||||||
|
fps=24,
|
||||||
|
codec='libx264',
|
||||||
|
audio_codec='aac'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 释放资源
|
||||||
|
final_video.close()
|
||||||
|
for clip in self.video_clips:
|
||||||
|
clip.close()
|
||||||
|
if self.background_music:
|
||||||
|
self.background_music.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _time_to_seconds(time_str: str) -> float:
|
||||||
|
"""将时间字符串转换为秒数"""
|
||||||
|
minutes, seconds = map(int, time_str.split(':'))
|
||||||
|
return minutes * 60 + seconds
|
||||||
|
|
||||||
|
|
||||||
|
def test_merge_video():
|
||||||
|
"""测试视频合并功能"""
|
||||||
|
merger = VideoMerger()
|
||||||
|
|
||||||
|
# 添加两个视频片段
|
||||||
|
merger.add_video("../../resource/videos/cut_video.mp4", "00:00", "01:00")
|
||||||
|
merger.add_video("../../resource/videos/demo.mp4", "00:00", "00:30")
|
||||||
|
|
||||||
|
# 添加背景音乐
|
||||||
|
merger.add_audio("../../resource/songs/output000.mp3", volume=0.3)
|
||||||
|
|
||||||
|
# 添加字幕
|
||||||
|
merger.add_subtitle("第一个精彩片段", "00:00", "00:05")
|
||||||
|
merger.add_subtitle("第二个精彩片段", "01:00", "01:05")
|
||||||
|
|
||||||
|
# 合并并导出
|
||||||
|
merger.merge()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_merge_video()
|
||||||
142
app/test/test_moviepy_speed.py
Normal file
142
app/test/test_moviepy_speed.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
"""
|
||||||
|
使用 moviepy 优化视频处理速度的示例
|
||||||
|
包含:视频加速、多核处理、预设参数优化等
|
||||||
|
"""
|
||||||
|
|
||||||
|
from moviepy.editor import VideoFileClip
|
||||||
|
from moviepy.video.fx.speedx import speedx
|
||||||
|
import multiprocessing as mp
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class VideoSpeedProcessor:
|
||||||
|
"""视频速度处理器"""
|
||||||
|
|
||||||
|
def __init__(self, input_path: str, output_path: str):
|
||||||
|
self.input_path = input_path
|
||||||
|
self.output_path = output_path
|
||||||
|
# 获取CPU核心数
|
||||||
|
self.cpu_cores = mp.cpu_count()
|
||||||
|
|
||||||
|
def process_with_optimization(self, speed_factor: float = 1.0) -> None:
|
||||||
|
"""
|
||||||
|
使用优化参数处理视频
|
||||||
|
参数:
|
||||||
|
speed_factor: 速度倍数 (1.0 为原速, 2.0 为双倍速)
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 加载视频时使用优化参数
|
||||||
|
video = VideoFileClip(
|
||||||
|
self.input_path,
|
||||||
|
audio=True, # 如果不需要音频可以设为False
|
||||||
|
target_resolution=(720, None), # 可以降低分辨率加快处理
|
||||||
|
resize_algorithm='fast_bilinear' # 使用快速的重置算法
|
||||||
|
)
|
||||||
|
|
||||||
|
# 应用速度变化
|
||||||
|
if speed_factor != 1.0:
|
||||||
|
video = speedx(video, factor=speed_factor)
|
||||||
|
|
||||||
|
# 使用优化参数导出视频
|
||||||
|
video.write_videofile(
|
||||||
|
self.output_path,
|
||||||
|
codec='libx264', # 使用h264编码
|
||||||
|
audio_codec='aac', # 音频编码
|
||||||
|
temp_audiofile='temp-audio.m4a', # 临时音频文件
|
||||||
|
remove_temp=True, # 处理完成后删除临时文件
|
||||||
|
write_logfile=False, # 关闭日志文件
|
||||||
|
threads=self.cpu_cores, # 使用多核处理
|
||||||
|
preset='ultrafast', # 使用最快的编码预设
|
||||||
|
ffmpeg_params=[
|
||||||
|
'-brand', 'mp42',
|
||||||
|
'-crf', '23', # 压缩率,范围0-51,数值越大压缩率越高
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 释放资源
|
||||||
|
video.close()
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"处理完成!用时: {end_time - start_time:.2f} 秒")
|
||||||
|
|
||||||
|
def batch_process_segments(self, segment_times: list, speed_factor: float = 1.0) -> None:
|
||||||
|
"""
|
||||||
|
批量处理视频片段(并行处理)
|
||||||
|
参数:
|
||||||
|
segment_times: 列表,包含多个(start, end)时间元组
|
||||||
|
speed_factor: 速度倍数
|
||||||
|
"""
|
||||||
|
start_time = time.time()
|
||||||
|
|
||||||
|
# 创建进程池
|
||||||
|
with mp.Pool(processes=self.cpu_cores) as pool:
|
||||||
|
# 准备参数
|
||||||
|
args = [(self.input_path, start, end, speed_factor, i)
|
||||||
|
for i, (start, end) in enumerate(segment_times)]
|
||||||
|
|
||||||
|
# 并行处理片段
|
||||||
|
pool.starmap(self._process_segment, args)
|
||||||
|
|
||||||
|
end_time = time.time()
|
||||||
|
print(f"批量处理完成!总用时: {end_time - start_time:.2f} 秒")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _process_segment(video_path: str, start: str, end: str,
|
||||||
|
speed_factor: float, index: int) -> None:
|
||||||
|
"""处理单个视频片段"""
|
||||||
|
# 转换时间格式
|
||||||
|
start_sec = VideoSpeedProcessor._time_to_seconds(start)
|
||||||
|
end_sec = VideoSpeedProcessor._time_to_seconds(end)
|
||||||
|
|
||||||
|
# 加载并处理视频片段
|
||||||
|
video = VideoFileClip(
|
||||||
|
video_path,
|
||||||
|
audio=True,
|
||||||
|
target_resolution=(720, None)
|
||||||
|
).subclip(start_sec, end_sec)
|
||||||
|
|
||||||
|
# 应用速度变化
|
||||||
|
if speed_factor != 1.0:
|
||||||
|
video = speedx(video, factor=speed_factor)
|
||||||
|
|
||||||
|
# 保存处理后的片段
|
||||||
|
output_path = f"../../resource/videos/segment_{index}.mp4"
|
||||||
|
video.write_videofile(
|
||||||
|
output_path,
|
||||||
|
codec='libx264',
|
||||||
|
audio_codec='aac',
|
||||||
|
preset='ultrafast',
|
||||||
|
threads=2 # 每个进程使用的线程数
|
||||||
|
)
|
||||||
|
|
||||||
|
video.close()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _time_to_seconds(time_str: str) -> float:
|
||||||
|
"""将时间字符串(MM:SS)转换为秒数"""
|
||||||
|
minutes, seconds = map(int, time_str.split(':'))
|
||||||
|
return minutes * 60 + seconds
|
||||||
|
|
||||||
|
|
||||||
|
def test_video_speed():
|
||||||
|
"""测试视频加速处理"""
|
||||||
|
processor = VideoSpeedProcessor(
|
||||||
|
"../../resource/videos/best.mp4",
|
||||||
|
"../../resource/videos/speed_up.mp4"
|
||||||
|
)
|
||||||
|
|
||||||
|
# 测试1:简单加速
|
||||||
|
processor.process_with_optimization(speed_factor=1.5) # 1.5倍速
|
||||||
|
|
||||||
|
# 测试2:并行处理多个片段
|
||||||
|
segments = [
|
||||||
|
("00:00", "01:00"),
|
||||||
|
("01:00", "02:00"),
|
||||||
|
("02:00", "03:00")
|
||||||
|
]
|
||||||
|
processor.batch_process_segments(segments, speed_factor=2.0) # 2倍速
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
test_video_speed()
|
||||||
Loading…
x
Reference in New Issue
Block a user