当你的 YouTube 频道每月需要产出 8-12 集 40 到 80 分钟的长篇视频时,每一集的压制、字幕合成和上传前检查都是重复劳动。作为一个信奉”能用脚本解决的事情绝不动手”的效率主义者,我建立了一套基于 Shell 和 FFmpeg 的全自动视频后处理管线。

管线概览

整个管线包含四个阶段:

  1. 输入检查:验证所有素材文件(视频、字幕、封面)是否存在且格式正确
  2. 预处理:SRT 字幕清洗、时间轴修复、简繁转换
  3. 多码率压制:同时生成 1080p 和 720p 两个版本
  4. 归档与通知:移至成品目录、更新数据库、发送完成通知

阶段一:输入检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# preflight.sh — 起飞前检查

EPISODE_DIR="$1"
REQUIRED_FILES=("raw.mp4" "subtitle.srt" "cover.png")

for file in "${REQUIRED_FILES[@]}"; do
if [ ! -f "$EPISODE_DIR/$file" ]; then
echo "❌ 缺少文件: $file"
exit 1
fi
done

DURATION=$(ffprobe -v error -show_entries format=duration -of csv=p=0 "$EPISODE_DIR/raw.mp4")
echo "✅ 素材完整,视频时长: ${DURATION}秒"

阶段二:字幕预处理

原始字幕文件常有格式问题:时间轴重叠、空行、编码错误。用一个 Python 脚本做预处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import re, sys

def clean_srt(filepath):
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()

# 移除多余空行
content = re.sub(r"\n{3,}", "\n\n", content)
# 修复时间轴格式
content = re.sub(r"(\d{2}:\d{2}:\d{2}),(\d{3})", r"\1.\2", content)

with open(filepath, "w", encoding="utf-8") as f:
f.write(content)
print(f"✅ 字幕清洗完成: {filepath}")

if __name__ == "__main__":
clean_srt(sys.argv[1])

阶段三:多码率并行压制

利用 GNU Parallel 同时压制 1080p 和 720p 两个版本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#!/bin/bash
# parallel-render.sh

INPUT="$1/raw.mp4"
SUBTITLE="$1/subtitle_clean.srt"
OUTPUT_DIR="$1/output"
mkdir -p "$OUTPUT_DIR"

render() {
local res="$1"
local height="$2"
local bitrate="$3"

ffmpeg -hwaccel cuda -hwaccel_output_format cuda \
-i "$INPUT" \
-vf "scale_npp=-2:$height,subtitles=$SUBTITLE:force_style='FontSize=$((height/36))'" \
-c:v h264_nvenc -preset p4 -cq 23 -b:v "$bitrate" \
-c:a aac -b:a 128k \
-movflags +faststart \
"$OUTPUT_DIR/${res}.mp4"
}

export -f render
export INPUT SUBTITLE OUTPUT_DIR

parallel --line-buffer render ::: "1080p 1080 6M" "720p 720 3M"

echo "✅ 压制完成"

GNU Parallel 让两个版本的压制同时进行,充分利用多核 CPU + GPU,总耗时接近单版本时间而非两倍。

阶段四:归档与通知

1
2
3
4
5
6
7
8
9
10
#!/bin/bash
# archive-and-notify.sh

EPISODE_DIR="$1"
EPISODE_NAME=$(basename "$EPISODE_DIR")

# 移动到成品目录
mv "$EPISODE_DIR/output" "/mnt/media/final/$EPISODE_NAME"

echo "✅ 归档完成: $EPISODE_NAME"

如需通知,可以接入 Telegram Bot 或企业微信 Webhook。

统一调度入口

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
# pipeline.sh — 一键启动完整管线

EPISODE_DIR="$1"
set -e # 任一环节失败即停止

./preflight.sh "$EPISODE_DIR"
python3 subtitle_cleaner.py "$EPISODE_DIR/subtitle.srt"
./parallel-render.sh "$EPISODE_DIR"
./archive-and-notify.sh "$EPISODE_DIR"

echo "🎉 管线执行完毕!"

使用时只需:bash pipeline.sh ./episodes/ep42-chapter-5

管线监控与异常处理

生产环境中的批量管线不能是”一锤子买卖”——你必须知道它是什么时候失败的、为什么失败。建议在每个阶段出口添加状态码检查和告警。

pipeline.sh 中加入错误捕获和日志记录,每条管线执行都留下完整轨迹。当某一集处理失败时,不是整条管线崩溃,而是将该集标记为”失败”并继续处理下一集——这样不会因为一集的问题阻塞整个生产排期。

字幕质量的最后一道防线

自动生成的英文字幕虽然有 PowerShell 脚本做断句优化,但在正式压制之前,建议跑一遍拼写检查。Linux 上的 aspell 或 Python 的 pyspellchecker 都能做批量检查。我通常会在字幕清洗脚本末尾加入拼写验证环节,自动标记可疑单词供人工复核——这比纯手动逐行校对效率高出 10 倍以上。

素材管理的数据库化

随着集数越来越多,分散在文件夹里的视频素材会变成灾难。建议用一个轻量级 SQLite 数据库来管理所有素材的元数据:文件名、时长、状态、关联字幕文件、最后处理时间。这样当你需要”找到所有包含某个历史人物的视频”或”重新压制最近 30 天的失败集数”时,一条 SQL 查询就能搞定,而不是在文件管理器里大海捞针。

总结

这套管线从手工每集 2 小时的工作量,压缩到了 15 分钟全自动(主要是 GPU 压制时间)。投入前期写脚本的一个下午,换来的是后续无数个”不用操心”的日日夜夜。这就是自动化真正的价值——不是替代创造力,而是让创造力不再被琐碎的重复劳动所消耗。