moviepy(Python 视频合成)
最适合简短、脚本化的视频,以及在素材上精确叠加文本。对于长篇、设计密集型、可复用的视频,建议使用 Remotion。
| 使用 moviepy 的场景 | 使用 Remotion 的场景 |
|---|---|
| 项目是单个 Python 脚本 | 需要 React 组件和设计系统 |
| 在现有或 AI 生成的片段上叠加文本 | 复杂的动画图形和图表 |
| 对多个文件进行批量编辑 | 热重载预览很重要 |
| 没有 Node 工具链可用 | 长篇、模板化的视频 |
安装
pip install "moviepy>=2" pillow
# 或:uv add moviepy pillow
本技能针对 moviepy 2.x。其 API 与 1.x 不同:导入直接来自 moviepy,方法使用 with_*/subclipped/resized,而不是 set_*/subclip/resize。
基础操作
from moviepy import (VideoFileClip, AudioFileClip, ImageClip, TextClip,
CompositeVideoClip, CompositeAudioClip, concatenate_videoclips, vfx, afx)
clip = VideoFileClip("in.mp4").subclipped(5, 20) # 截取 15 秒片段
clip = clip.resized(height=1080) # 保持宽高比
clip = clip.with_effects([vfx.FadeIn(0.5), vfx.FadeOut(0.5)])
joined = concatenate_videoclips([clip, VideoFileClip("b.mp4")], method="compose")
joined.write_videofile("out/result.mp4", fps=30, codec="libx264", audio_codec="aac")
在长脚本中始终调用 .close() 关闭剪辑,或者使用 with 块,以释放文件句柄。
文本叠加(在素材上确定性文本)
title = (TextClip(font="C:/Windows/Fonts/arial.ttf", # 真实的字体文件路径
text="Dr. Jane Doe\nChief Scientist",
font_size=56, color="white",
stroke_color="black", stroke_width=2,
method="caption", size=(900, None))
.with_start(1.0).with_duration(4.0)
.with_position(("left", 860))
.with_effects([vfx.CrossFadeIn(0.3), vfx.CrossFadeOut(0.3)]))
final = CompositeVideoClip([clip, title])
注意事项:
TextClip需要字体文件路径(TTF/OTF)。将品牌字体文件包含在项目中。- 剪辑时间以秒为单位,相对于合成视频的开始时间。
- 对于高度样式化的文本(圆角标签、渐变),使用 Pillow 渲染 PNG 并用
ImageClip加载。 - 将文本保持在安全区域内:距离每个边缘至少 5%。
音频
voice = AudioFileClip("voiceover.mp3")
music = AudioFileClip("music.mp3").with_effects([afx.MultiplyVolume(0.12), afx.AudioFadeOut(2)])
music = music.with_duration(voice.duration + 1)
final = clip.with_audio(CompositeAudioClip([music, voice.with_start(0.5)]))
让配音决定时间线:将剪辑时长设置为 voice.duration + padding,而不是相反。
音频锚定模式
scenes = [("scene1.mp3", "intro.png"), ("scene2.mp3", "chart.png")]
parts = []
for audio_path, image_path in scenes:
a = AudioFileClip(audio_path)
v = ImageClip(image_path).with_duration(a.duration + 0.6).with_audio(a)
parts.append(v)
concatenate_videoclips(parts).write_videofile("out/explainer.mp4", fps=30)
静态图片的 Ken Burns 效果
img = ImageClip("photo.jpg").with_duration(6)
img = img.resized(lambda t: 1.0 + 0.04 * t).with_position("center")
CompositeVideoClip([img], size=(1920, 1080)).write_videofile("out/kb.mp4", fps=30)
性能
- 预览时使用
fps=12和preset="ultrafast";最终渲染时使用 30 fps。 write_videofile(..., threads=4)。- 避免在长剪辑上使用逐帧 Python lambda。尽可能先用 FFmpeg 预处理。
常见问题
| 问题 | 原因与修复 |
|---|---|
ImportError: cannot import name 'editor' | moviepy 2 移除了 moviepy.editor;从 moviepy 直接导入 |
AttributeError: set_duration | 使用 with_duration;同样适用于 with_start, with_position |
| 文本剪辑崩溃 | 提供真实的字体文件路径 |
| 拼接后音画不同步 | 确保所有剪辑共享 fps;在拼接后应用音频 |
| 渲染速度慢 | 草稿使用较低分辨率;避免大型 resized lambda |