视频制作技能
对于用代码制作的动画动态图形——动态文字、解说视频、发布视频、动画图表、可选视觉风格的下三分之一——请使用
motion-graphics技能。本技能适用于视频素材、照片、音频和 ffmpeg 编辑。
使用 ffmpeg(核心引擎)和 Python(Pillow 处理帧/图形,MoviePy 进行高级编辑)以编程方式构建视频。始终先计划,后构建,最后验证。
1. 构建前先规划
在编写任何命令之前,先确定以下内容:
| 决策 | 默认值 | 备注 |
|---|---|---|
| 宽高比 | 16:9 (1920×1080) | 9:16 (1080×1920) 用于 Reels/Shorts/TikTok,1:1 (1080×1080) 用于信息流 |
| 帧率 | 30 fps | 24 用于电影感,60 用于屏幕录制/游戏录像 |
| 时长 | 按要求 | Reels/Shorts ≤ 60–90 秒 |
| 格式 | MP4 (H.264 + AAC) | 随处可播放;仅按要求使用 WebM;仅用于短循环时才用 GIF |
| 音频 | 可选 | 音乐、画外音或静音 |
编写一个简短的镜头列表:每个场景 → 时长、视觉内容、文字、过渡、音频。
2. 检查工具链
which ffmpeg ffprobe || (apt-get update && apt-get install -y ffmpeg)
python3 -c "import PIL, moviepy" 2>/dev/null || pip install pillow moviepy --break-system-packages
fc-list | head # 查看可用的文字字体
3. 标准编码设置
除非用户另有要求,否则每次最终渲染都使用这些设置:
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart \
-c:a aac -b:a 192k
-pix_fmt yuv420p— 在手机、QuickTime 和浏览器上播放所必需。-movflags +faststart— 让视频在完全下载前即可开始播放。-crf 18= 接近无损(文件较大),-crf 28= 文件较小,质量较低。-preset veryfast保持渲染速度;仅在要求最高质量时使用slow。- 宽度和高度必须为 偶数 才能用于 H.264。
3b. 快速渲染——用户正在等待
完整渲染是最慢的步骤,因此每次请求仅计划 一次 完整渲染:
- 编码前先预览。 以最终尺寸拉取或绘制几帧——最繁忙的时刻、第一帧和最后一帧——检查构图、文字大小和颜色。对于照片视频:
scripts/slideshow.py OUT.mp4 photos… --preview可在一秒内生成一张缩略图。 - 使用场景,而非单一巨图。 将每个场景编码为单独的文件,具有相同的尺寸、帧率和编码器设置,使用 concat demuxer 和
-c copy合并,最后用-c:v copy添加声音。后期修改一个场景只需重新编码该场景。 - 过渡效果位于场景文件内部(在其边缘淡入/淡出)。交叉淡变两个 已完成 的文件会强制进行第二次完整编码。
- 廉价地保持静态画面。 解码图片一次,然后使用
loop滤波器重复它,而不是使用-loop 1(后者会为每一帧重新解码图像):ffmpeg -framerate 30 -i still.png -vf "format=yuv420p,loop=-1:1,trim=duration=4,setpts=PTS-STARTPTS" -c:v libx264 -preset veryfast -crf 20 -pix_fmt yuv420p still.mp4 - 切勿以真实名称留下半成品文件。 写入
name.part.mp4,仅当 ffmpeg 退出码为 0 时才重命名——中断的渲染可能仍可播放,但会缩短。 - 除非另有要求,否则以输出尺寸和 30 fps 绘制帧。当逐帧捕获网页时,保存 JPEG(质量 ~90),而不是 PNG——写入和读取速度都快得多。
在 10 张照片 → 1080p 上测量:旧的单一 -loop 1 + xfade 图耗时 13.2 秒;slideshow.py --crossfade 耗时 6.4 秒,场景文件路径耗时 8.9 秒(包含音乐)。
修改已完成的视频:
| 修改内容 | 操作方法 |
|---|---|
| 一个场景的内容 | 重新渲染该场景文件,使用 -c copy 重新合并 |
| 新音乐 / 修剪结尾 | -c:v copy(新音乐:-map 0:v -map 1:a) |
| 烧录字幕,制作较小的副本 | 对完成的文件重新编码一次 |
| 每帧都变化的内容 | 无法避免完整渲染 |
4. 配方
4.0 一条命令的照片幻灯片
python3 scripts/slideshow.py out.mp4 photos/*.jpg --seconds 3 --fade 0.5 --music song.mp3 # 快速,通过黑色淡变
python3 scripts/slideshow.py out.mp4 photos/*.jpg --crossfade # 真正的交叉淡变
python3 scripts/slideshow.py out.mp4 photos/*.jpg --zoom --size 1080x1920 # Ken Burns 效果,竖屏
4.1 从图片制作幻灯片(带淡变过渡),手动
# 每张图片显示 3 秒,0.5 秒交叉淡变,缩放/填充到 1080p
# 每张静态图像解码一次,并使用 loop 滤波器重复(比 -loop 1 快 2 倍)
F="scale=1920:1080:force_original_aspect_ratio=decrease,pad=1920:1080:(ow-iw)/2:(oh-ih)/2,setsar=1,format=yuv420p,loop=-1:1,trim=duration=3,setpts=PTS-STARTPTS,fps=30"
ffmpeg -framerate 30 -i img1.jpg -framerate 30 -i img2.jpg -framerate 30 -i img3.jpg \
-filter_complex "\
[0]$F[a];[1]$F[b];[2]$F[c];\
[a][b]xfade=transition=fade:duration=0.5:offset=2.5[ab];\
[ab][c]xfade=transition=fade:duration=0.5:offset=5[out]" \
-map "[out]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -movflags +faststart slideshow.mp4
剪辑 n 的偏移量 = (之前所有剪辑的时长之和) − (n × 淡变时长)。
4.2 Ken Burns(慢速缩放)效果
# d = 输出帧数(30 fps 下 150 帧 = 5 秒);不需要 -loop,zoompan 会生成帧
ffmpeg -i photo.jpg -vf "scale=3840:-2,zoompan=z='min(zoom+0.0008,1.2)':d=150:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=1920x1080:fps=30" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p zoom.mp4
先放大到 3840 可以避免 zoompan 在小图像上产生的抖动运动。
4.3 文字叠加 / 标题卡片
ffmpeg -i input.mp4 -vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:\
text='My Title':fontsize=72:fontcolor=white:box=1:[email protected]:boxborderw=20:\
x=(w-text_w)/2:y=h-150:enable='between(t,0,4)'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy titled.mp4
对于包含撇号、表情符号或非拉丁文字(印地语等)的文字,请使用支持该文字的字体,通过 Pillow 将文字渲染为 PNG,然后叠加:
ffmpeg -i input.mp4 -i title.png -filter_complex "overlay=(W-w)/2:H-h-100:enable='between(t,0,4)'" ...
4.4 从空白创建纯色标题卡片
ffmpeg -f lavfi -i color=c=0x111827:s=1920x1080:d=3:r=30 \
-vf "drawtext=fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:text='Chapter 1':fontsize=96:fontcolor=white:x=(w-text_w)/2:y=(h-text_h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p card.mp4
4.5 裁剪片段
# 精确裁剪(重新编码):从 00:00:05 开始,持续 10 秒
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac trimmed.mp4
# 快速裁剪(不重新编码,在最近的关键帧处切割)
ffmpeg -ss 00:00:05 -i input.mp4 -t 10 -c copy trimmed_fast.mp4
4.6 合并剪辑
相同的编解码器/分辨率/帧率 → concat demuxer(无需重新编码):
printf "file 'a.mp4'\nfile 'b.mp4'\nfile 'c.mp4'\n" > list.txt
ffmpeg -f concat -safe 0 -i list.txt -c copy joined.mp4
不同的来源 → 先标准化(缩放、帧率、音频速率)或使用 concat 滤镜:
ffmpeg -i a.mp4 -i b.mp4 -filter_complex \
"[0:v]scale=1920:1080,setsar=1,fps=30[v0];[1:v]scale=1920:1080,setsar=1,fps=30[v1];\
[v0][0:a][v1][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" -c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a aac joined.mp4
(如果某个剪辑没有音轨,则添加静音:-f lavfi -i anullsrc=r=48000:cl=stereo 并使用 -shortest。)
4.7 添加背景音乐
# 视频下的音乐,在结尾淡出,在视频结束时停止
# st = 视频长度减去淡出长度(这里是 30 秒视频,3 秒淡出)——使用 ffprobe 获取长度
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.3,afade=t=out:st=27:d=3[m]" \
-map 0:v -map "[m]" -c:v copy -c:a aac -shortest with_music.mp4
# 将音乐混入现有的语音下
ffmpeg -i video.mp4 -i music.mp3 -filter_complex \
"[1:a]volume=0.15[m];[0:a][m]amix=inputs=2:duration=first[a]" \
-map 0:v -map "[a]" -c:v copy -c:a aac mixed.mp4
4.8 字幕 / 隐藏式字幕
编写一个 .srt 文件:
1
00:00:00,000 --> 00:00:03,000
欢迎观看本视频。
2
00:00:03,000 --> 00:00:06,500
以下是我们将涵盖的内容。
将其烧录:
ffmpeg -i input.mp4 -vf "subtitles=subs.srt:force_style='FontSize=24,PrimaryColour=&HFFFFFF,OutlineColour=&H000000,BorderStyle=1,Outline=2'" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy captioned.mp4
4.9 为社交媒体调整尺寸(竖屏 9:16)
# 使用模糊背景填充而非黑色边框
ffmpeg -i landscape.mp4 -filter_complex \
"[0]scale=1080:1920:force_original_aspect_ratio=increase,crop=1080:1920,boxblur=20[bg];\
[0]scale=1080:-2[fg];[bg][fg]overlay=(W-w)/2:(H-h)/2" \
-c:v libx264 -preset veryfast -crf 23 -pix_fmt yuv420p -c:a copy vertical.mp4
4.10 加速 / 减速
# 2 倍速(视频 + 音频)
ffmpeg -i in.mp4 -filter_complex "[0:v]setpts=0.5*PTS[v];[0:a]atempo=2.0[a]" -map "[v]" -map "[a]" fast.mp4
4.11 视频转 GIF(高质量)
ffmpeg -i in.mp4 -vf "fps=15,scale=640:-1:flags=lanczos,split[s0][s1];[s0]palettegen[p];[s1][p]paletteuse" -loop 0 out.gif
4.12 从代码渲染帧(动画、图表、动态图形)
当 ffmpeg 滤镜不够用时,使用 Python 绘制每一帧并缝合:
from PIL import Image, ImageDraw, ImageFont
import os, subprocess
W, H, FPS, SECONDS = 1920, 1080, 30, 4
os.makedirs("frames", exist_ok=True)
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 96)
for i in range(FPS * SECONDS):
t = i / (FPS * SECONDS)
img = Image.new("RGB", (W, H), (17, 24, 39))
d = ImageDraw.Draw(img)
x = int(-600 + t * (W + 600)) # 文字横向滑动
d.text((x, H // 2 - 48), "Hello!", font=font, fill=(255, 255, 255))
img.save(f"frames/{i:05d}.png")
subprocess.run(["ffmpeg", "-y", "-framerate", str(FPS), "-i", "frames/%05d.png",
"-c:v", "libx264", "-preset", "veryfast", "-crf", "23",
"-pix_fmt", "yuv420p", "-movflags", "+faststart", "animation.mp4"], check=True)
使用缓动函数(例如 t*t*(3-2*t))以获得更平滑的运动。
4.13 MoviePy(快速高级编辑)
from moviepy import VideoFileClip, TextClip, CompositeVideoClip, concatenate_videoclips, AudioFileClip
clip1 = VideoFileClip("a.mp4").subclipped(0, 5)
clip2 = VideoFileClip("b.mp4").subclipped(2, 8)
video = concatenate_videoclips([clip1, clip2], method="compose")
music = AudioFileClip("music.mp3")
video = video.with_audio(music.subclipped(0, min(video.duration, music.duration))) # 音乐可能更短
video.write_videofile("out.mp4", fps=30, codec="libx264", audio_codec="aac", preset="veryfast")
(此处显示的是 MoviePy 2.x API;1.x 使用 subclip/set_audio。使用 pip show moviepy 检查版本。)
5. 交付前验证
切勿不检查视频就交付:
# 时长、分辨率、编解码器、帧率
ffprobe -v error -show_entries format=duration:stream=codec_name,width,height,r_frame_rate -of default=nw=1 out.mp4
# 在关键时刻提取静止帧并查看
ffmpeg -ss 1 -i out.mp4 -frames:v 1 check_1s.png
ffmpeg -i out.mp4 -vf "fps=1/3,scale=480:-1,tile=4x2" -frames:v 1 contact_sheet.png
检查清单:
- 时长与计划一致
- 分辨率和宽高比正确;没有不需要的黑边或拉伸
- 文字可读,位于安全边距内(距离边缘约保留 5%;对于 9:16,避免底部约 15% 的区域,那里是应用 UI 所在位置)
- 过渡效果出现在预期位置
- 音频存在(如果预期有的话),没有削波,结尾干净
- 可在手机上播放:H.264、
yuv420p、偶数尺寸
6. 常见陷阱
| 问题 | 修复 |
|---|---|
| 视频无法在 iPhone/QuickTime 上播放 | 添加 -pix_fmt yuv420p |
| "width not divisible by 2" | 使用 scale=1280:-2 而不是 -1 |
使用 -c copy 裁剪后开头出现黑帧 | 重新编码,或将 -ss 放在 -i 之前并重新编码 |
| 合并后音视频不同步 | 在合并前标准化帧率和音频采样率(-ar 48000) |
| xfade 错误 "inputs differ" | 所有输入需要相同的尺寸、帧率、setsar=1 和像素格式 |
drawtext 在 ' 或 : 上出错 | 对其进行转义,或使用 textfile= / Pillow 渲染的 PNG 叠加 |
| 非英语文字显示为方块 | 使用支持该文字的字体(例如 Noto Sans Devanagari) |
| 文件过大 | 提高 CRF(26–28),降低帧率或分辨率 |
| 渲染速度太慢 | 使用 -preset veryfast 或 ultrafast;草稿阶段降低分辨率 |
7. 工作流程总结
- 规划 — 宽高比、帧率、时长、镜头列表、音频。
- 收集素材 — 图片、剪辑、音乐、字体;使用
ffprobe检查它们。 - 快速草稿 — 低分辨率 /
ultrafast预设以检查时间线。 - 构建最终版本 — 标准编码设置(第 3 节)。
- 验证 — ffprobe + 联系表 + 检查清单(第 5 节)。
- 交付 — 发送最终文件,并附上一行摘要(时长、分辨率、包含内容)。