简介:video_parser是一个基于TypeScript实现的视频解析库,面向需要处理MP4、FLV、MKV等多媒体容器,并从中提取元数据、帧率、编码格式等关键信息的Web或Node.js开发者。它将解析能力拆分为services、interfaces、entity、controllers等模块,既保证类型安全,也便于按业务场景复用,适合在视频处理工具、媒资系统或自动化分析流程中直接集成。压缩包共13个文件,以TypeScript源码、JSON配置和依赖锁文件为主,并内置了编译配置与依赖管理入口,整体仅61KB,结构紧凑,便于快速审阅与工程化落地。目前已有197人学习/下载,适合具备一定TypeScript基础、希望避免从底层手写解析逻辑的中级开发者。通过这套源码可以获得可运行的解析库骨架、模块划分思路、构建脚本以及对常见容器格式的解析思路,能显著缩短视频格式适配和元数据提取功能的开发周期。 如果你和我一样,本地攒了上千个视频文件,你一定清楚:存储从来不是最头疼的事,最头疼的是管理。文件名风格五花八门,从标准的"Movie.Name.2021.1080p.BluRay.x264"到"第12集.mp4",再到手机导出的"VID_20240301_153012.mp4",想快速定位一部片子,基本靠挨个打开看。我写 video_parser 这个工具,就是想解决这个问题:读取视频文件和内嵌元数据,再结合文件名里的有效信息,自动产出一份结构化的视频档案。
这个工具适合两类人。一类是本地视频库比较庞大、想做自动化归档和整理的爱好者;另一类是做内容分析、需要批量拉取视频技术参数(分辨率、编码、时长、音轨字幕轨道情况)的开发者。下面我把设计思路、核心实现、踩过的坑都写下来,方便你复现,或者改成符合自己需求的版本。
1. 我为什么写video_parser:从手工整理视频库的痛点说起
1.1 手里的现成工具为什么不够用
不写这个工具之前,我试过好几种方案。最笨的方法是用 Python 调 ffprobe 拿媒体信息,然后自己写正则从文件名里抠标题、年份、清晰度。听起来可行,实际跑一遍就发现问题了:
- ffprobe 输出的 JSON 字段虽然完整,但不同格式的视频,字段层级和命名不完全一致。有的文件流信息嵌套两层,有的嵌套三层,写解析代码时全是 if 分支,越写越恶心。
- 文件名正则只能覆盖你自己见过的命名习惯。今天遇到一个 "xxx.2023.2160p.WEB-DL.DDP5.1.x264" 还能处理,明天来一个 "【4KHDR】电影名(2022)第3季合集" 就彻底歇菜。
- 没有统一的数据出口。脚本输出结果每次都不一样,想接进 Emby、Jellyfin 或者自己的数据库,还得再写一遍字段映射。
这些痛点叠加起来,就是一句话:我需要一个工具,它能稳定地把"一个视频文件路径"变成"一份不依赖具体文件格式的标准化信息结构"。
1.2 video_parser要解决的三个核心问题
在动手之前,我把需求收敛成三件事。
第一,元数据读取要稳。不管输入的是 MP4、MKV、AVI、TS 还是 MOV,都要能拿到容器格式、视频流编码、分辨率、帧率、码率、时长、音轨列表、字幕轨列表这些基础信息,而且字段结构必须统一。
第二,文件名解析要聪明但不能莽撞。能识别常见的命名规律,提取出标题、年份、清晰度、来源、季集编号等字段。如果识别不出来,宁可留空,也不能瞎猜,避免"把标题切得乱七八糟"这种更糟的结果。
第三,对外接口要简单。命令行人人都能用,Python API 方便二次开发,输出默认 JSON,看一眼就能用,不用再解释。
这个工具在我的设计定位里,不是一个"下载器",也不是一个"播放器",它就是一个纯粹的"视频档案提取器":输入路径,输出结构化信息,仅此而已。
2. video_parser的解析流程:一条命令如何拆出一部视频的完整档案
2.1 解析三步走:文件名 → 媒体信息 → 结构化合并
整个解析流程不复杂,就三步:文件名解析、媒体流探测、信息合并。但每一步的细节比想象中多。
第一步,拿到文件路径后,先丢给文件名解析器。它会剥离扩展名,把纯文件名切分成若干 token,然后尝试匹配不同的模式。比如用分隔符(点、空格、下划线、中文方括号等)拆分,再逐个判断 token 属于哪一类:是年份、清晰度标签、编码标签、还是视频标题的组成部分。
第二步,调用 ffprobe 读取多媒体文件信息。这块我直接用 subprocess 调用系统安装的 ffprobe,然后解析返回的 JSON。之所以没有用纯 Python 的纯解析库,是因为市面上成熟的开源 ffprobe 封装已经非常稳定,没必要重复造轮子。但我在外层又包了一层映射逻辑,把所有可能的字段结构统一成自己定义的 schema。
第三步,把前两步得到的信息合并成一份最终输出。合并规则是:技术参数以 ffprobe 为准,文件名字段只作为补充。比如文件名里有 1080p,但 ffprobe 实际读出来分辨率是 1920x1080,那就以 ffprobe 的宽度和高度为准;文件名里没写年份,而 ffprobe 的元数据标签里有 creation_time,就把这个时间补进 year 字段。
如果你只是想快速看一部视频的信息,命令行直接输:
video_parser "/path/to/your/video.mp4"输出的 JSON 大概是这样的:
{ "file": { "path": "/path/to/your/video.mp4", "name": "video.mp4", "size_mb": 2453.28, "container": "matroska" }, "video_stream": { "codec": "h264", "profile": "High", "width": 1920, "height": 1080, "frame_rate": "23.976", "bit_rate_kbps": 8492, "pixel_format": "yuv420p" }, "audio_streams": [ {"index": 0, "codec": "aac", "channels": 6, "language": "eng"}, {"index": 1, "codec": "ac3", "channels": 2, "language": "chi"} ], "subtitle_streams": [], "duration_sec": 7420.5, "title": "video", "year": null, "resolution_tag": "1080p" }2.2 内部数据模型
为了不让自己在各处维护零散的字典,我定义了几个简单类:VideoInfo、VideoStreamInfo、AudioStreamInfo、SubtitleStreamInfo。每个类只负责持有和自身相关的字段,并提供to_dict()方法。
字段全部用 snake_case,方便映射到 Python 代码,也方便 JSON 序列化。类设计得比较扁平,没有搞多重继承,解析逻辑放到了独立模块里,类和解析器解耦,这样后续想加新格式支持,只需要扩展解析器,不动数据模型。
3. 文件名智能解析:正则之外的那些设计取舍
3.1 视频文件命名的常见规律
文件名解析是整个工具里最容易翻车的地方,也是花时间最多的地方。我先把常见视频文件命名规律撸了一遍,发现再多样,也能归成几类模式:
- 电影类:标题 + 年份 + 清晰度 + 来源 + 编码,比如 "The.Matrix.1999.1080p.BluRay.x264"
- 剧集类:标题 + SxxExx + 集名 + 清晰度来源,比如 "Breaking.Bad.S01E01.BluRay.1080p" 或者中文 "《狂飙》第05集"
- 综艺/番剧类:标题 + 集数 + 格式,比如 "SomeShow.EP12.720p" 或 "【合集】某某节目第3期"
- 设备导出类:无规律纯时间戳或序号,比如 "VID_20240301_153012.mp4"
这个规律一旦理清,解析器的架构思路就变清晰了:不能一上来就写一套"万能正则",而应该定义一套分级规则,让不同命名模式的解析器按优先级依次尝试。
3.2 解析规则的优先级:从具体到模糊
我的规则引擎从最具体的模式开始匹配,匹配失败再降级尝试下一个:
- 剧集模式:先找 SxxExx 或 第x集 等特征,命中后就按剧集逻辑拆。
- 电影模式:找年份四位数(通常 1900-2100 之间),找到后再往前截标题。
- 清晰度优先模式:找 4K、1080p、720p、REMUX 等清晰度标签,确定分辨率标签和来源标签(BluRay、WEB-DL、HDTV)。
- 全标题兜底:上述都没命中,就把文件名去掉前后缀后整体当作标题。
这样设计有一个好处:先拿最确定的锚点,再把剩余部分按逻辑拆分,避免了在模糊段落里到处套正则导致误伤。
3.3 几个容易误判的真实案例
踩过的坑比预想多。举三个例子:
第一个是"标题里含年份"。"The.1999.Project.2021.1080p" 这种,年份前后都有数字。如果无脑找第一个四位数,就会把片名里的 1999 当发行年份。我的处理方式是:在电影模式下,优先选择最后一个四位数作为年份,因为按常见命名习惯,发行年份更接近文件名尾部。
第二个是混合分隔符。有的文件是 "电影名.2021.1080p [BDRemux]",既有英文点又有中文方括号。简单按点切分就会把 "[BDRemux]" 带方括号的 token 和其他 token 混在一起。所以切分时我保留原始分隔符信息,不是简单拆字符串,而是把文件名拆成一个 token 列表,每个 token 带上自己前面的分隔符类型,再交给规则引擎。
第三个是"第2021期"这种集数。中文里"第xxxx期"的数字并不一定是年份,这类节目集数字段和年份字段要分开对待。我目前的对策是:一旦命中"第x期/第x集"模式,优先按剧集处理,年份字段只从显式四位数里提取。
4. 元数据读取的底层逻辑:容器、编码流与比特率计算
4.1 容器层和编码层是两个概念
很多刚开始解析视频的读者会在一个点上绕晕:容器格式和编码格式不是一个东西。MKV、MP4、AVI 是容器格式,H.264、H.265、VP9 是编码格式。容器负责把视频流、音轨、字幕轨、章节信息打包到一起;编码则是压缩图像和声音的算法本身。
这个区分非常重要,因为视频解析出的技术参数里,容器信息来自文件头部,编码信息来自流数据。如果只调 ffprobe 而不关心这两层,很可能出现一种情况:一个 MKV 文件容器是 MKV,但视频流编码是 AV1。如果你在归档脚本里拿扩展名判断编码格式,就永远得不到正确结果。
4.2 时长、帧率、比特率这些参数是怎么算出来的
大部分元数据直接来自 ffprobe 的format和streams字段,但有几个参数需要额外处理。
时长(duration):直接取format.duration,但某些损坏文件或录制流,这个字段可能是空。遇到这种情况,我会尝试用视频流中的duration字段替代,还不行就标记为null,绝不填 0。
帧率(frame_rate):ffprobe 返回的是一个分数形式字符串,比如 "24000/1001",这才是准确形式。我拿到后会把它转成浮点数,同时保留原始分数,供需要精确计算的场景使用。
比特率(bit_rate):需要注意,format.bit_rate是整个文件的平均比特率,包含视频、音频以及所有附加数据。而streams里每个流有自己的bit_rate。如果只看文件总比特率来判断画质,会被多音轨拉高,产生误判。所以我默认展示视频流的bit_rate,总比特率作为参考字段。
4.3 为什么要做多数据源校验
解析过程中经常遇到"文件名和元数据打架"的情况。比如文件名叫 "xxx.1080p",但 ffprobe 实际读出分辨率是 1280x720,显然是文件名写错了,或者片源被重新压过。video_parser 的处理逻辑是:技术字段优先信元数据,文件名标签只作为附加参考,两者不一致时在输出的warnings字段里给出提示,而不是直接报错。
这种"宁可多给一条警告,也不替用户做决定"的设计思路,在工具开发中很实用。自动解析系统最怕的就是在不确定的环节硬猜,猜对了是运气,猜错了用户没法排查。
5. 两种使用姿势:命令行输出与Python API嵌入
5.1 命令行:单文件查看与批量输出
命令行设计参照了 Unix 工具哲学:默认输出人类可读的摘要,--json输出完整 JSON,方便管道处理。我实际最常用的命令是两个:
# 查看单文件摘要 video_parser video.mp4 # 批量解析目录下所有视频,输出 JSON 到文件 video_parser --batch /path/to/folder --json > result.json批量模式下我加了进度条和失败日志两个开关。一个大目录几百个视频,跑起来最怕的就是中途某文件损坏导致整个进程退出。所以批量处理时我对每个文件做异常捕获,失败的写入独立错误日志,最后汇总打印一句"成功 N 个,失败 M 个",不打断整体流程。
5.2 Python API:作为管道接入自动化脚本
命令行做交互够用,但真正要自动化,还是得靠 Python API。用法很直接:
from video_parser import parse_file info = parse_file("/path/to/video.mkv") print(info.video_stream.codec) # h264 print(info.title) # 从文件名提取的标题 print(info.duration_sec) # 7420.5我在设计 API 时刻意保持极简:对外就几个核心函数——parse_file、parse_folder、parse_name。前两个管文件解析,第三个单独暴露文件名解析能力,方便用户不想碰 ffprobe 时单独使用。
内部实现里,ffprobe 的调用结果会做一层缓存。同一个文件短时间内重复解析,直接从缓存取结果,避免反复 IO 拖慢批量任务。这个缓存默认关,在传入use_cache=True时启用。
5.3 输出格式怎么选
JSON 是我默认推荐的输出格式,结构清晰、前后端通用。但为了照顾"快速看一眼"的场景,我也做了默认的文本摘要输出,类似这样:
File: video.mp4 (2.4 GB) Container: matroska Video: h264 (High) 1920x1080 @ 23.976 fps, ~8492 kbps Audio: aac 6ch (eng), ac3 2ch (chi) Subtitle: (none) Duration: 2h03m40s如果接了--yaml,还会输出 YAML 格式,专门给喜欢把参数直接写成配置文件的人用。YAML 和 JSON 内容一样,只是序列化方式不同,内部没有额外维护两套数据。
6. 实测表现、踩坑记录和后续扩展想法
6.1 1000个文件的批量实测
我把工具放到自己一个存了 1137 个视频文件的测试目录里跑了一遍,测试环境是 MacBook Pro M1,Python 3.11。结果如下:
| 指标 | 数值 |
|---|---|
| 总耗时 | 约 6 分 40 秒 |
| 平均单文件耗时 | 约 0.35 秒 |
| 成功解析 | 1105 个 |
| 文件名解析成功(提取到标题) | 986 个 |
| 元数据读取失败 | 32 个(主要是损坏文件) |
大部分耗时花在 ffprobe 对整个文件的头部和索引信息的读取上。视频文件越大,耗时越明显,尤其是一些 4K 高码率 MKV,单文件可能耗时 2 秒以上。如果追求速度,可以考虑只读容器头部的参数(比如用更轻量的工具),但信息完整度会打折。我的取舍是:默认完整解析,后续再考虑轻量模式。
6.2 值得记录的三个坑
第一个坑:文件路径里的中文字符导致解析失败。在 macOS 和 Windows 上,Python 传给 subprocess 参数时如果路径包含特殊字符或全角空格,偶发编码问题。解决方式是所有路径操作统一转成绝对路径,并显式指定编码参数,避免系统默认编码差异。
第二个坑:可变帧率(VFR)文件的时长对不上。某些录制文件是可变帧率,ffprobe 返回的帧率只是一个平均参考值,视频流的 duration 和 format 的 duration 可能差零点几秒。对于这种文件,我选择在warnings里提示"检测到可变帧率,部分时间参数仅供参考",而不是强行统一时间轴。
第三个坑:多音轨和字幕轨的语言标签缺失。很多老片源音轨没有 language 字段,ffprobe 返回空字符串。我一开始把它们标成 "und"(undefined),后来发现用户体验不好。改成:有标签读标签,没标签时按轨道序号显示"track 1",并额外加一个language_guessed布尔字段,告诉用户这个语言信息是猜的。
6.3 下一步:字幕流、章节信息和媒体服务器对接
目前的版本已经能稳定处理视频流、音轨和字幕轨信息,但我觉得还有三个很实际的扩展方向。
第一个是把字幕流和章节信息也纳入详细输出。很多 MKV 自带章节,文件名不一定能看到,但章节能帮助快速定位片段,对做剪辑的人很有用。
第二个是直接生成 Emby / Jellyfin 可识别的图片信息文件或者 NFO 文件。这样批量解析后直接丢进媒体服务器,能省掉刮削器的时间。这个功能我正在写,基本思路是把解析结果映射到 NFO 的 XML 结构上。
第三个是提供一个简单的 Web 服务接口,POST 一个文件路径就返回 JSON,方便其他工具通过 HTTP 调用。技术栈打算用 FastAPI,内部还是调用现有的解析核心,不做额外改动。
工具的核心思路就一句话:把读取视频信息这件事从"为单个文件写命令"升级成"批量结构化的自动流程"。如果你也遇到过类似的管理痛点,直接用这个思路写一个自己的版本,应该能省下不少时间。我个人实际使用中的体会是:一个工具用得顺手,关键不在于功能多,而在于它在一个场景里足够可靠。video_parser 的目标就是让"视频信息提取"这个场景变可靠,后面的扩展都只是顺水推舟。
本文还有配套的精品资源,点击获取