批量处理脚本:Windows批处理/Bash脚本/Python脚本调用FFmpeg批量处理
说实话,做音视频处理这行,最怕的就是重复劳动。你想想看,一个项目下来,几百个视频文件要转码、要提取音频、要截图……一个个手动敲FFmpeg命令?那不得累死。我早期刚入行的时候,就干过这种傻事——对着几十个文件挨个敲命令,敲到第三个就开始怀疑人生了。
后来我学乖了。批量处理,说白了就是让脚本替我们干活。今天我就把三种最常用的脚本方案——Windows批处理、Bash脚本、Python脚本——都给你捋一遍。每种方案都有它的适用场景,选对了能省一半时间。
为什么需要批量处理?
我遇到过不少新手,觉得写脚本比手动操作还麻烦。嗯,这个想法我理解。但你想过没有:
- 手动处理100个文件,每个文件耗时30秒,总共50分钟
- 写个脚本花10分钟,然后让脚本自动跑完,你喝杯咖啡回来就搞定了
哪个划算?而且脚本还能复用,下次遇到类似任务,改几个参数就能继续用。这笔账,你算算就明白了。
核心思路:批量处理 = 循环遍历文件 + 调用FFmpeg命令 + 输出结果管理
方案一:Windows批处理脚本(.bat)
Windows环境下,批处理是最直接的方式。我个人习惯在Windows开发机上用这个,因为不需要装任何额外环境。
批量转码MP4到H.265
@echo off
setlocal enabledelayedexpansion
set INPUT_DIR=.\input
set OUTPUT_DIR=.\output
set CRF=23
if not exist "%OUTPUT_DIR%" mkdir "%OUTPUT_DIR%"
for %%f in ("%INPUT_DIR%\*.mp4") do (
set "filename=%%~nf"
echo 正在处理: !filename!
ffmpeg -i "%%f" -c:v libx265 -crf %CRF% -c:a copy "%OUTPUT_DIR%\!filename!_h265.mp4"
echo 完成: !filename!
)
echo 全部处理完毕!
pause
这段脚本的逻辑很简单:遍历input文件夹下所有.mp4文件,用FFmpeg转成H.265编码,输出到output文件夹。注意setlocal enabledelayedexpansion这行,它允许我们在循环里使用!变量名!这种延迟扩展语法——这个坑我踩过,不加的话变量值会不对。
小技巧:批处理里用%%~nf获取文件名(不含扩展名),%%~xf获取扩展名。这些修饰符很实用,建议记住。
批量提取音频流
@echo off
setlocal enabledelayedexpansion
for %%f in (*.mp4 *.mov *.avi) do (
ffmpeg -i "%%f" -vn -acodec copy "%%~nf_audio.%%~xf"
echo 已提取: %%f → %%~nf_audio.%%~xf
)
echo 音频提取完成!
pause
这个脚本更简洁,直接在当前目录下处理所有视频文件,提取音频流。注意-vn参数表示不要视频,-acodec copy表示音频直接复制不重新编码——这样速度快,质量无损。
注意:批处理对文件名中的空格和特殊字符很敏感。如果文件名里有空格,一定要用双引号把路径包起来,就像上面代码里那样。我曾经因为一个文件名里多了个空格,脚本跑了一半报错,排查了半天才发现。
方案二:Bash脚本(.sh)
到了Linux或macOS环境,Bash脚本就是标配了。我在服务器上做批量处理时,几乎都用Bash。它的字符串处理和管道操作比批处理灵活得多。
批量截取视频缩略图
#!/bin/bash
INPUT_DIR="./videos"
OUTPUT_DIR="./thumbnails"
TIME="00:00:05"
mkdir -p "$OUTPUT_DIR"
for video in "$INPUT_DIR"/*.mp4; do
filename=$(basename "$video" .mp4)
echo "正在生成缩略图: $filename"
ffmpeg -i "$video" -ss "$TIME" -vframes 1 "$OUTPUT_DIR/${filename}_thumb.jpg"
done
echo "全部缩略图生成完毕!"
这个脚本从每个视频的第5秒截取一帧作为缩略图。basename命令用来提取文件名,-vframes 1表示只输出一帧。速度很快,几百个视频几分钟就能跑完。
批量合并音频和视频
#!/bin/bash
for video in *.mp4; do
base="${video%.mp4}"
audio="${base}.aac"
if [ -f "$audio" ]; then
echo "合并: $video + $audio"
ffmpeg -i "$video" -i "$audio" -c:v copy -c:a aac -map 0:v:0 -map 1:a:0 "${base}_merged.mp4"
else
echo "跳过 $video:未找到对应的音频文件 $audio"
fi
done
这段脚本会检查每个视频文件是否有同名的.aac音频文件,有的话就合并,没有就跳过。我在做多语言配音项目时经常用这个——视频是同一份,音频有多个语言版本,批量合并效率极高。
Bash小技巧:用${variable%pattern}可以去掉变量末尾匹配的部分,比如${filename%.mp4}就去掉.mp4后缀。这个语法比用basename更灵活,建议掌握。
方案三:Python脚本
说实话,如果任务比较复杂,我首选Python。批处理和Bash脚本适合简单的循环调用,但一旦涉及条件判断、异常处理、并行执行,Python的优势就出来了。
批量处理框架(带日志和错误处理)
import subprocess
import os
import logging
from concurrent.futures import ThreadPoolExecutor, as_completed
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[
logging.FileHandler('batch_process.log'),
logging.StreamHandler()
]
)
INPUT_DIR = "./input"
OUTPUT_DIR = "./output"
MAX_WORKERS = 4 # 并行任务数
def process_video(filename):
"""处理单个视频文件"""
input_path = os.path.join(INPUT_DIR, filename)
output_path = os.path.join(OUTPUT_DIR, f"processed_{filename}")
# 构建FFmpeg命令
cmd = [
'ffmpeg',
'-i', input_path,
'-c:v', 'libx264',
'-preset', 'medium',
'-crf', '23',
'-c:a', 'aac',
'-b:a', '128k',
'-y', # 覆盖输出文件
output_path
]
try:
# 执行命令
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=300 # 5分钟超时
)
if result.returncode == 0:
logging.info(f"成功: {filename}")
return True
else:
logging.error(f"失败: {filename}\n{result.stderr}")
return False
except subprocess.TimeoutExpired:
logging.error(f"超时: {filename}")
return False
except Exception as e:
logging.error(f"异常: {filename} - {str(e)}")
return False
def main():
# 创建输出目录
os.makedirs(OUTPUT_DIR, exist_ok=True)
# 获取所有视频文件
videos = [f for f in os.listdir(INPUT_DIR)
if f.lower().endswith(('.mp4', '.mov', '.avi'))]
logging.info(f"找到 {len(videos)} 个视频文件")
# 使用线程池并行处理
success_count = 0
with ThreadPoolExecutor(max_workers=MAX_WORKERS) as executor:
futures = {executor.submit(process_video, v): v for v in videos}
for future in as_completed(futures):
if future.result():
success_count += 1
logging.info(f"处理完成: 成功 {success_count}/{len(videos)}")
if __name__ == "__main__":
main()
这个脚本我用了很久,基本框架一直没变。几个关键点:
- 日志系统:同时输出到控制台和文件,方便事后排查问题
- 异常处理:每个文件独立处理,一个失败不影响其他文件
- 超时控制:防止某个文件卡死导致整个任务挂起
- 并行处理:用
ThreadPoolExecutor同时处理多个文件,充分利用CPU
注意:并行数不是越多越好。FFmpeg本身就会用多核,如果你同时跑太多进程,反而会因为CPU争抢导致性能下降。我一般建议并行数不超过CPU物理核心数的一半。比如8核CPU,设4个并行任务比较合适。
三种方案对比
| 特性 | Windows批处理 | Bash脚本 | Python脚本 |
|---|---|---|---|
| 学习成本 | 低 | 中 | 中高 |
| 跨平台 | 仅Windows | Linux/macOS | 全平台 |
| 错误处理 | 弱 | 中 | 强 |
| 并行处理 | 困难 | 较困难 | 简单 |
| 适用场景 | 简单批量、Windows环境 | 服务器、Linux环境 | 复杂逻辑、生产环境 |
实战建议
我个人经验是:
- 临时任务:用批处理或Bash,写起来快,用完就删
- 定期任务:用Python,加上日志和错误通知,出了问题能及时发现
- 生产环境:Python + 任务队列(比如Celery),支持断点续传和任务调度
嗯,说到断点续传,我踩过一个坑。有一次处理2000多个视频,跑到第1800个的时候服务器重启了……没有断点续传,只能从头再来。后来我学乖了,每次处理前先记录已完成的文件列表,脚本启动时先检查哪些已经处理过了,跳过它们。
核心原则:批量处理脚本要具备「幂等性」——同一个脚本跑多次,结果应该是一样的。这样即使中途失败,重新跑一次也不会出问题。
知识体系图
这张图把三种方案的核心逻辑和特点都串起来了。你看,不管用哪种脚本,底层逻辑都是一样的——遍历文件、调用FFmpeg、输出结果。区别在于每种方案在错误处理、并行能力、跨平台支持上的强弱。
最后说一句:别纠结于「哪种脚本最好」。没有最好的,只有最适合当前场景的。Windows桌面环境用批处理,Linux服务器用Bash,复杂生产环境用Python——各司其职,这才是工程思维。