手里几千个视频怎么盘点?用 ffprobe 做媒体资产清单与自动化管理
做资源库、影视站、或者单纯囤了一堆录像的人,迟早会遇到一个现实问题:硬盘里塞了几千个视频,编码格式、分辨率、时长、码率参差不齐,想整理却无从下手。靠手动打开看是不可能的。ffmpeg 套装里的 ffprobe 就是为这种场景而生的——它能把每个文件的详细技术参数吐成结构化 JSON,我们写个脚本批量扫一遍,资产盘点就一目了然。这篇文章从单文件解析讲起,一直到建成一个可查询的媒体资产数据库。
一、ffprobe 能拿到什么
基本覆盖所有技术参数:
- 容器层:格式、时长、文件大小、总码率;
- 视频流:编码(H.264/H.265/VP9/AV1)、分辨率、帧率、码率、profile、色彩空间、旋转角度;
- 音频流:编码、采样率、声道数、语言、码率;
- 字幕/附件:字幕流数量与语言、内嵌封面等。
二、最基础的 JSON 输出
ffprobe -v error -show_format -show_streams -of json input.mp4
-v error:只打印错误,屏蔽进度噪声;-show_format:容器级信息;-show_streams:每个流的详细信息;-of json:结构化输出,方便程序解析。
三、只取关心的字段(更轻量)
全量 JSON 太啰嗦,用 select_streams + show_entries 精简:
ffprobe -v error -select_streams v:0 \
-show_entries stream=codec_name,width,height,r_frame_rate,bit_rate \
-show_entries format=duration,size \
-of csv=p=0 input.mp4
输出形如 h264,1920,1080,30/1,2500000,120.5,52428800,程序 split 即可。
四、批量扫描整个目录并导出 CSV
写个 Python 脚本,遍历目录、逐文件调 ffprobe、汇总成 CSV:
import subprocess, json, os, csv
def probe(path):
out = subprocess.check_output([
'ffprobe', '-v', 'error',
'-show_format', '-show_streams', '-of', 'json', path
], stderr=subprocess.DEVNULL)
return json.loads(out)
rows = []
for root, _, files in os.walk('D:/videos'):
for f in files:
if not f.lower().endswith(('.mp4', '.mkv', '.webm', '.avi', '.mov')):
continue
p = os.path.join(root, f)
try:
info = probe(p)
except Exception:
continue # 损坏或 ffprobe 打不开的跳过
v = next((s for s in info['streams'] if s['codec_type'] == 'video'), {})
a = next((s for s in info['streams'] if s['codec_type'] == 'audio'), {})
fmt = info.get('format', {})
rows.append({
'file': p,
'vcodec': v.get('codec_name'),
'w': v.get('width'), 'h': v.get('height'),
'fps': v.get('r_frame_rate'),
'vbitrate': int(v.get('bit_rate', 0) or 0),
'acodec': a.get('codec_name'),
'channels': a.get('channels'),
'duration': round(float(fmt.get('duration', 0)), 1),
'size_mb': round(int(fmt.get('size', 0)) / 1048576, 1),
})
with open('inventory.csv', 'w', newline='', encoding='utf-8') as fp:
w = csv.DictWriter(fp, fieldnames=rows[0].keys())
w.writeheader()
w.writerows(rows)
五、进阶:建成可查询的数据库
CSV 只能手动筛,量大了还是慢。把数据写进 SQLite,就能用 SQL 随意查:
import sqlite3
conn = sqlite3.connect('media.db')
conn.execute('''CREATE TABLE IF NOT EXISTS media(
path TEXT, vcodec TEXT, w INT, h INT, fps TEXT,
vbitrate INT, acodec TEXT, channels INT, duration REAL, size_mb REAL)''')
conn.executemany('INSERT INTO media VALUES (?,?,?,?,?,?,?,?,?,?)',
[(r['file'], r['vcodec'], r['w'], r['h'], r['fps'],
r['vbitrate'], r['acodec'], r['channels'],
r['duration'], r['size_mb']) for r in rows])
conn.commit()
# 例如:找出所有 1080p 以上但码率低于 2Mbps 的(疑似压烂了)
for row in conn.execute("SELECT path FROM media WHERE w>=1920 AND vbitrate<2000000"):
print(row[0])
六、基于盘点能做什么
- 找异常大文件:
size_mb > 5000的,看是不是重复或需要转码; - 找老旧编码:
vcodec='mpeg4'的标清老片,统一计划转 H.264; - 找分辨率不一:整理剧集时确认每集是否同一分辨率;
- 查损坏文件:ffprobe 返回非 0 的就是打不开/损坏,直接挑出来;
- 生成统计报表:总时长、总占用、编码分布,一眼看清资产状况。
七、提速:多进程并行
几百个文件串行还行,几千上万个就慢了。用多进程并行探测:
from multiprocessing import Pool
with Pool(8) as pool:
results = pool.map(probe_safe, all_paths)
ffprobe 是单文件 CPU 轻量操作,8~16 路并行提升明显。
八、常见翻车现场
| 现象 | 根因 | 解法 |
|---|---|---|
| ffprobe 卡死 | 个别文件损坏/假死 | 加 timeout 和异常跳过 |
| 时长显示 N/A | 某些封装没写时长 | 用 duration_ts 估算或忽略 |
| 中文路径报错 | 命令行编码问题 | 用 subprocess 传列表参数而非字符串 |
| 扫描极慢 | 单进程逐个调 | 多进程并行 |
| 旋转视频分辨率反了 | 有 rotate 元数据 | 读 tags 里的 rotate 字段 |
九、常见问题
Q:能直接写 Postgres 吗? 能,把 CSV/SQLite 换成 INSERT 进 Postgres 即可,做成完整的资产管理系统(之前博客里提到的 BlogPost 资产同理)。
Q:为什么不用 mediainfo? mediainfo 也很好,但 ffprobe 与 ffmpeg 同源、参数风格统一,自动化流水线里更顺手。
Q:损坏文件怎么批量挑? ffprobe 返回非 0 退出码的,脚本里捕获异常即可归为"损坏/不可读"。
Q:能顺便生成缩略图吗? 能,扫的时候对每个视频 ffmpeg -ss 10 -i ... -frames:v 1 thumb.jpg,配合清单做成带预览的资产页。
小结
ffprobe 是媒体资产盘点的瑞士军刀:从单文件 JSON 到批量 CSV,再到 SQLite/Postgres 资产库,层层递进。掌握 select_streams + show_entries 精简输出,再用脚本汇总,几千个视频也能几分钟理清楚。