本文所有命令都在 Windows 11 + ffmpeg 8.1.2(gyan build)上实测跑过,报错信息是原文粘贴。目标是让你把"20 条视频剪一天"压缩成"提交一个清单,去干别的"。
自动化剪辑不是"AI 帮你剪片",它是把重复的机械动作写成一条流水线。判断标准很粗暴:
| 情况 | 结论 | 原因 |
|---|---|---|
| 结构固定、只换素材和文案(口播/带货/图文成片) | 值得自动化 | 每条的差异只是数据,不是决策 |
| 条数 ≥ 10,或需要每周重复 | 值得自动化 | 一次性写脚本的时间能被摊薄 |
| 需要跟着音乐切、卡点、创意转场 | 只自动化前半段 | 节奏判断留给人工 |
| 每条都要重新设计版式 | 不值得 | 你在写脚本上的时间会超过剪辑 |
一个务实的中间态:脚本负责 80% 的机械活(拼段、对齐、烧字幕、压响度、导出规范),人工只做最后一遍抽查和发布。
manifest.jsonl → ①分段合成 → ②合轨拼接 → ③字幕烧录+响度 → ④质量门 → 发布
(清单) seg_0001.mp4 joined.mp4 final_0001.mp4 pass/fail
最关键的不是 ffmpeg 命令,是清单驱动。清单是唯一的事实来源,脚本是无状态的,随时可以重跑:
{"id":"ep001","scenes":[{"img":"s001.png","audio":"a001.mp3","sec":6.5},{"img":"s002.png","audio":"a002.mp3","sec":7.2}],"sub":"ep001.srt","title":"第1集"}
{"id":"ep002","scenes":[{"img":"s003.png","audio":"a003.mp3","sec":5.8}],"sub":"ep002.srt","title":"第2集"}
三条约定:
-c copy(快,但每段必须自带音频)前提是每段在生成时就把音频 mux 进去。实测:两段 2 秒的片段(各自带音轨)拼接后 duration=4.023220s,流是 video,audio,全程解码为零。
# 每段:画面 + 该段自己的音频,一次生成
ffmpeg -y -loop 1 -i s001.png -i a001.mp3 \
-c:v libx264 -pix_fmt yuv420p -r 30 \
-c:a aac -ar 44100 -ac 2 -shortest seg_0001.mp4
# 拼接(-c copy 不重编码,秒级完成)
printf "file 'seg_0001.mp4'\nfile 'seg_0002.mp4'\n" > list.txt
ffmpeg -y -f concat -safe 0 -i list.txt -c copy joined.mp4
这里有个最容易踩的坑:如果各段只有画面、没有音轨,直接 -c copy 拼出来的文件在部分播放器/平台上会出现无声、时长错乱、进度条乱跳。音频必须先随段 mux,再拼。
filter_complex concat(参数不一致时用)分辨率、帧率、采样率不完全一致时,demuxer 拼接会出问题,改用滤镜做真正的重编码拼接:
ffmpeg -y -i seg_0001.mp4 -i seg_0002.mp4 \
-filter_complex "[0:v][0:a][1:v][1:a]concat=n=2:v=1:a=1[v][a]" \
-map "[v]" -map "[a]" joined.mp4
代价是慢,好处是它会把所有段强制统一到同一套参数。所以策略是:流水线先统一参数(路线 B 兜底),能 copy 就 copy。
统一参数建议值(短视频平台通吃):
| 项 | 建议 | 备注 |
|---|---|---|
| 分辨率 | 1080×1920 竖版 / 1920×1080 横版 | 中间产物可降一半提速 |
| 帧率 | 30 fps | 与源一致,别混 25/30 |
| 像素格式 | yuv420p |
不加这行 iPhone 相册里可能只有声音没有画面 |
| 音频 | AAC / 44100 Hz / 双声道 | 单声道也能发,但混音时容易左右不平衡 |
一图一段的图文视频,最省事的运镜就是缓慢推近。实测可用(PNG 静图 → 3 秒 30fps 动态片段):
ffmpeg -y -loop 1 -i s001.png \
-vf "zoompan=z='min(zoom+0.0015,1.2)':d=90:x='iw/2-(iw/zoom/2)':y='ih/2-(ih/zoom/2)':s=640x360:fps=30" \
-t 3 -c:v libx264 -pix_fmt yuv420p o_kb.mp4
三个参数的含义:
z='min(zoom+0.0015,1.2)':每帧放大 0.15%,最多到 1.2 倍。数值越大越"急",超过 1.5 倍会有明显糊。d=90:该图持续的帧数,和 fps 一起决定秒数(90 ÷ 30 = 3 秒)。fps 改了就要同步改 d,否则时长会飘。s=640x360:输出尺寸。这里建议和最终交付分辨率一致,避免后面二次缩放变糊。每段的推近方向错开(左推/右推/上推)观感会好很多,避免十几段全是同一种运动。
subtitles= 不能直接吃 C:/... 路径实测报错原文:
[Parsed_subtitles_0 @ ...] Unable to parse "original_size" option value "/Users/..."
原因:filter 参数里 : 是选项分隔符,C:/Users/... 里的冒号被当成了"下一个选项"的开始。两种可用写法:
# 写法1:转义冒号(推荐,绝对路径最稳)
-vf "subtitles='C\:/work/ep001.srt'"
# 写法2:先把工作目录切到字幕所在目录,用相对文件名
cd /c/work && ffmpeg -i joined.mp4 -vf "subtitles=ep001.srt" ...
实测结论:转义写法 rc=0 正常输出,未转义写法必失败。顺手记一条:ass= 滤镜对同样的转义更挑剔(实测直接报 Could not create a libass track),所以能统一用 subtitles= 就不要用 ass=。
不指定字体时,中文可能渲染成方块或直接回落成宋体小字。可用写法(实测渲染出 3000+ 个字形像素,字幕确实烧进去了):
-vf "subtitles='C\:/work/ep001.srt':fontsdir='C\:/Windows/Fonts':force_style='FontName=Microsoft YaHei,FontSize=28,PrimaryColour=&H00FFFFFF,OutlineColour=&H00000000,BorderStyle=1,Outline=2,MarginV=40'"
force_style 里几个实用项:
| 项 | 作用 | 短视频建议 |
|---|---|---|
FontName |
字体名 | Microsoft YaHei(Windows 自带,别用花体) |
FontSize |
字号 | 竖版 1080 宽用 40–48;横版 24–28 |
Outline + BorderStyle=1 |
描边 | 必须开,白字没有黑描边在亮背景上会消失 |
MarginV |
底部边距 | 竖版留 100 以上,避开平台底部按钮 |
Alignment |
对齐 | 2 居中底部 |
| 路线 | 命令 | 适用 | 代价 |
|---|---|---|---|
| 烧录(硬字幕) | -vf subtitles=... |
平台分发、跨播放器一致 | 不可关闭,需重新编码(慢) |
| 内挂(软字幕) | -i ep.srt -c copy -c:s mov_text |
自己存档、要改字 | 部分平台不显示 |
批量交付一律走烧录:你在自己电脑上看到的和观众看到的必须一样,别赌平台。
ffmpeg -y -i voice.mp4 -i bgm.mp3 \
-filter_complex "[0:a]volume=1.0[voice];[1:a]volume=0.15[bgm];[voice][bgm]amix=inputs=2:duration=first:dropout_transition=0[a]" \
-map "0:v" -map "[a]" -c:v copy o_mix.mp4
要点:duration=first 让混音长度跟着人声走(BGM 短了不会被截断人声);BGM 音量 0.10–0.20 是安全区,0.3 以上人声就糊了;人声段之间留 0.3 秒空隙比硬切自然。
一遍式 loudnorm 是动态的,音量起伏大的素材会被压得忽大忽小。两遍法:先测,再套测量值。
第一遍(测):
ffmpeg -i joined.mp4 -af "loudnorm=I=-16:TP=-1.5:LRA=11:print_format=json" -f null -
实测某素材的输出:
{ "input_i": "-21.58", "input_tp": "-13.55", "input_lra": "0.20", "input_thresh": "-31.58" }
第二遍(套用测量值,linear=true 走线性增益,不动动态):
ffmpeg -y -i joined.mp4 \
-af "loudnorm=I=-16:TP=-1.5:LRA=11:measured_I=-21.58:measured_TP=-13.55:measured_LRA=0.20:measured_thresh=-31.58:linear=true" \
-c:v copy o_loud.mp4
实测结果:输出 input_i = -16.00,正好命中目标。目标值怎么定:抖音/视频号等平台按 -16 LUFS 左右统一,B 站长视频可按 -14 到 -16;TP=-1.5 是防止转码时削波。
配音长于画面,或想让 15 秒素材撑满 20 秒时:
# 画面放慢到 1.3333 倍时长,音频同步放慢
ffmpeg -y -i base.mp4 -filter_complex "[0:v]setpts=1.3333*PTS[v];[0:a]atempo=0.75[a]" \
-map "[v]" -map "[a]" o_stretch.mp4
实测:3.0 秒素材 → 4.014 秒,音画同步无漂移。
规则:幅度控制在 ±15% 以内。放慢超过 20% 声音会明显发闷,加快超过 15% 会出现"电子音"。超过这个范围就改文案或补素材,别硬拉。若只是画面要拉长而音频不变(音频单独叠),改用 -t 配合静帧:
ffmpeg -y -loop 1 -i last_frame.png -t 2 -f lavfi -i anullsrc -shortest tail.mp4
批量发布的真正风险不是"剪得不好看",是有一条坏片悄悄发出去了。每条片子出厂前必须过一道自动检查,不达标直接拦。
import json, subprocess
def probe(path):
out = subprocess.run(["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height,r_frame_rate,nb_frames",
"-show_entries", "format=duration,size", "-of", "json", path],
capture_output=True, text=True).stdout
d = json.loads(out)
v = d["streams"][0]
return {"w": v["width"], "h": v["height"], "fps": v["r_frame_rate"],
"dur": float(d["format"]["duration"]), "size": int(d["format"]["size"])}
def gate(path, want_ratio=9/16, min_dur=8, max_dur=90, min_size=200_000):
m = probe(path)
problems = []
if abs(m["w"] / m["h"] - want_ratio) > 0.02: problems.append(f"比例不符 {m['w']}x{m['h']}")
if m["fps"] not in ("30/1", "25/1", "60/1"): problems.append(f"帧率异常 {m['fps']}")
if not (min_dur <= m["dur"] <= max_dur): problems.append(f"时长越界 {m['dur']:.1f}s")
if m["size"] < min_size: problems.append(f"体积过小 {m['size']}B(疑似空片)")
return problems
六项检查(对应真实事故):
| 检查 | 拦截的事故 |
|---|---|
| 分辨率与比例 | 竖版被裁成正方形,平台加黑边 |
| 帧率 | 混了 25/30fps,拼接后音画漂移 |
| 时长区间 | 某段音频缺失导致成片只有 3 秒 |
| 文件体积 | 编码失败产出 0–10KB "空片",照样上传成功 |
| 响度(可选但建议) | 一条响一条哑,完播率腰斩 |
| 首帧非纯黑 | 封面位是一帧黑屏 |
关键纪律:抓到的坏片不要自动重试同一条命令——同因失败两次就停下来看原因(绝大多数是清单里的素材路径或时长字段写错了)。重试只会把 5 分钟能修的问题拖成 40 分钟。
批量生成最贵的不是 ffmpeg 的时间,是重跑。做法和长任务通用套路一样:
seg_0001.mp4),存在即跳过;seg_0001.mp4.tmp → seg_0001.mp4),避免"半个文件"被当成成品。一集 5 段、每段 30 秒的视频,第一次跑 3 分钟,改第 3 段文案后重跑只要 40 秒。
如果你要的成片需要大量手动精修(关键帧、花字、调色、转场),更划算的路线是让脚本生成"剪映草稿",人工在剪映里收尾,而不是用 ffmpeg 硬拼。
剪映专业版的草稿就是一堆 JSON。实测本机草稿目录(%LOCALAPPDATA%\JianyingPro\User Data\Projects\com.lveditor.draft\<项目名>\)的关键文件:
| 文件/目录 | 作用 |
|---|---|
draft_content.json |
主时间轴:轨道、片段、时间单位、字幕、音频参数全在这里 |
draft_meta_info.json |
素材引用与元信息(时长/路径/素材 ID) |
draft_settings / draft_biz_config.json |
分辨率、帧率、导出设置 |
Resources/ |
素材副本与缩略图 |
subdraft/、Timelines/ |
子草稿与多时间轴 |
三条实操经验:
version/new_version)不匹配时剪映会弹"草稿版本过低"。分工建议:模板化批量 → ffmpeg 管线;需要创意精修 → 剪映草稿 + 人工。别指望一条脚本包打天下。
| 现象 | 报错/表现 | 原因 | 修法 |
|---|---|---|---|
| 字幕加不上 | Unable to parse "original_size" option value |
Windows 路径冒号被当选项分隔符 | subtitles='C\:/...' 转义 |
ass= 读不到文件 |
Could not create a libass track |
ass 滤镜转义规则不同 | 一律用 subtitles= |
| 中文变方块 | 渲染出来是空心框 | 未指定字体/字库目录 | fontsdir='C\:/Windows/Fonts' + FontName=Microsoft YaHei |
| 拼接后无声 | 播放器只有画面 | 各段自身无音轨却 -c copy 拼接 |
先随段 mux 音频,或改用 filter_complex concat |
| 手机相册无画面 | 只有声音 | 缺 -pix_fmt yuv420p |
输出统一加 yuv420p |
| 音画漂移 | 越到后面越不同步 | 各段帧率/采样率不一致 | 统一 30fps / 44100Hz;用路线 B 兜底 |
| 声音忽大忽小 | 一条响一条哑 | 用了一遍式 loudnorm | 改两遍法 + linear=true |
| 运镜方向每段一样 | 观感机械 | zoompan 参数批量复用 | 按段错开推近方向 |
| 拼出来的时长对不上 | 4.02s 变 3.4s | 帧数 d 与 fps 不匹配 |
d = 秒数 × fps 同步改 |
| 上传成功但没人看 | 成片是 0KB–10KB 空片 | 编码失败但退出码为 0 | 质量门查体积(<200KB 直接拦) |
d = 秒数 × fps 已核对,无时长漂移MarginV ≥ 100,未被平台底部按钮遮挡input_i 命中目标(如 -16.00)一句话总结:批量剪辑的难点从来不是 ffmpeg 命令,而是清单驱动 + 统一参数 + 质量门 + 断点续跑这四件事。命令可以抄,纪律得自己守。
📚 更多 AI 自动化与创作资源:https://afdian.com/a/felix007
📚 更多创作资源: https://afdian.com/a/felix007