本文主要说明自托管媒体库(以 Octans 扫描与轨事实层为例)如何把「容器内 stream 事实」和「给 UI / NFO / 选轨用的展示轨」分层建模,如何用语言与标题启发式生成可读轨名,以及视频动态范围(HDR / Dolby Vision 等)为何要按证据优先级保守分类、并在必要时做分层探针。探测工具选型本身见同系列 MediaInfo / ffprobe 文,本文不重复速度与字段面对比。
媒体库用户看到的往往是「国语 5.1」「简中强制字幕」「DV P8.1 (HDR10)」这类展示标签;真正参与播放映射、FFmpeg -map、tone mapping 分流的,却是另一套更贴近 demuxer 的索引与色彩事实。把两者混在同一字段里,会出现选错轨、HDR 标签过宽、或把展示文案误当成播放契约等问题。下面按工程分层整理原则与边界。
先分清两套「轨」
| 概念 | 回答的问题 | 典型用途 | 常见误区 |
|---|---|---|---|
| 容器 stream / 权威 index | 第几条流、类型、codec、disposition | 播放映射、FFmpeg map、诊断日志 | 把 UI 列表序号当 FFmpeg index |
| 展示轨清单 | 用户选哪条、详情页怎么写 | API、前端、NFO、默认偏好 | 把清洗后的标题当唯一语言证据 |
| 外置 sidecar 资产 | 文件在哪、归属谁、是否歧义 | 图谱状态、共享字幕、缺失清理 | 把 sidecar 路径硬塞进容器轨表却无生命周期 |
一句话:
展示轨 = 容器内轨事实 +(可选)外置资产投影 + 语言/标题归一化
播放映射 = 与 demuxer / FFmpeg 对齐的 stream index 与类型事实
两者可以在同一条业务行里共存(例如 StreamIndex + Title),但语义不能互相替代。
事实源分层:容器内、外置图谱、展示投影
轨道识别的稳妥模型是三层职责,而不是「一张表塞所有真相」:
| 层级 | 职责 | 建议来源 |
|---|---|---|
| 容器内轨道事实 | codec / format、语言原值、title 原值、stream index、声道、forced 等 | 扫描主探针(常见为 MediaInfo 展示字段) |
| 外置资产事实 | sidecar 路径、类型、状态、归属、共享与歧义 | 目录级资产图谱(MediaAsset Graph 一类) |
| 轨道展示事实 | API / 前端 / NFO / 选轨统一清单 | 内置轨 + 归属明确的外置投影 |
内置 vs 外置
- 内置轨:有容器 stream;
StreamIndex有意义;外置资产 ID 为空。 - 外置轨:路径与生命周期归图谱;展示行通过
Source=External+ 资产 ID 投影;没有可靠的容器 stream index。 - 共享 sidecar(例如一季共用同一字幕文件)适合在图谱里表达「一资产多文件链接」,展示层仍可为每个媒体文件投影一行,避免选轨模型变成多对多主键。
外置归属要保守
外置字幕 / 外置音轨匹配应以「宁可歧义不写展示,也不乱挂」为原则:
- 规范化 stem(去掉语言尾缀、语义尾缀)。
- 同目录与视频 stem 精确匹配优先。
- 再尝试标题 / 发行后缀等候选。
- 多候选时用兼容性分组(同一作品 / 同一版本;文本字幕时长容差可宽一点,图形字幕与外置音轨更严)。
- 仍无法唯一归属 → 标记歧义,不生成普通展示轨;诊断留在图谱或日志。
VobSub 的 .idx + .sub 应作为一组逻辑字幕处理,缺一半则标记不完整,不装作可用字幕。
为何不必一上来合并统一 MediaTrack 表
AudioTrack / Subtitle 分表在扫描、NFO、播放候选都已沉淀时,首轮更划算的是补齐同一套语义字段(Source、StreamIndex、RawLanguage / LanguageCodes、RawTitle / Title 等)并共用 resolver,而不是把「数据迁移」和「识别修正」绑死。统一表可以等跨类型排序、默认轨、用户偏好真的需要频繁合并时再评估。
Stream index 与展示列表:对齐原则
播放侧真正需要的是「与 FFmpeg 视角一致的全局 stream index」,展示侧需要的是「稳定、可读、可排序的轨清单」。
应对齐什么
| 应对齐 | 说明 |
|---|---|
| 类型与条数 | 视频 / 音频 / 字幕条数与 demux 所见一致,是最低验收 |
| 映射 index | 内置轨的 index 应能对应到 -map 0:<index> 或等价 map 策略 |
| disposition | default / forced 等影响默认选轨与诊断 |
| 主视频轨选择 | 多视频轨时,分辨率、时长、HDR 摘要必须读同一条主轨 |
不要混什么
- UI 序号 ≠ stream index:列表里「音轨 2」可能是展示排序后的第二项,不是
streams[].index == 2。 - MediaInfo 的轨序 ≠ 永远等于 FFmpeg 全局 index:展示字段更宽时,播放映射仍应以 FFmpeg 视角 index 为契约;工具分工见 媒体库探测选型:MediaInfo 与 ffprobe 该怎么分工。
- 外置轨没有 stream index:不要伪造
0或「接在内置轨后面」的假 index 去骗 map。
主视频轨怎么选
固定读「第 0 条视频」在双轨文件上会错(例如 4K HDR10+ 主轨 + 1080p HDR10 副轨)。更稳的规则只依赖封装与基础属性,不要用「谁更 HDR」去偏选:
Default=Yes优先。- 分辨率像素数更大优先。
- 时长更长优先。
- 原始顺序更靠前兜底。
没有可用视频轨时,不要伪造 SDR 掩盖问题;应让主轨选择显式失败,避免非视频文件被标成正常片源。
展示排序要稳定
同一物理文件多次扫描,轨列表顺序应稳定(避免 UI 抖动)。通常:
- 内置音轨 / 字幕按
StreamIndex; - 外置按文件名自然序;
- 语言优先级可用于分组,但不宜默认打乱容器原始顺序;
- 同语言下 forced 可排在普通字幕前。
跨 remux、跨发布源不承诺顺序一致——mux 顺序变了,index 跟着变是合理结果。
语言与标题启发式
真实片库里,MediaInfo / 容器 tags 的 Language 经常缺、错或只是 und;真正有用的线索大量藏在 title、文件名尾缀、外置 logical name 里。识别链路要服务三件事:标准语言码、中文(或当前语言包)展示标签、可读轨标题。
保留原始值,再生成展示值
| 字段 | 角色 |
|---|---|
RawLanguage / RawTitle | 证据,排查误判时回溯 |
LanguageCodes | 标准码列表(如 zh-Hans、en),可再生成 |
Title | 清洗 + 词表 + 缺省生成后的可读标题 |
LanguageLabel | 展示语言包产物,宜在 API 出口生成,不入库 |
不要只存「清洗后的 Title」。白名单一旦误杀,语言证据会一起消失。
顺序:先语言,后标题
错误顺序:
先 CleanTrackTitle → 再用清洗结果猜语言
正确顺序:
1. 读取 RawLanguage、RawTitle、文件名尾缀、外置 logical name
2. 字典 / 规则解析 LanguageCodes 与置信度(Exact / Inferred / Unknown)
3. 标题 normalizer 生成 Title
4. 若 RawTitle 只是纯语言名,不硬当标题正文,而用语言码生成「德语字幕」一类展示名
历史上把 Arabic、Portuguese、Czech 等合法语言标题当「未命中白名单」丢掉,就是典型反例:它们不适合当最终花哨标题,却是语言识别的有效证据。
标准码与多语言
内部宜用可读的 BCP-47 风格,而不是只存容器三字母原值:
| 线索示例 | 标准码 | 展示方向 |
|---|---|---|
chs / 简中 / Simplified Chinese | zh-Hans | 简体中文 |
cht / 繁中 | zh-Hant | 繁体中文 |
国语 / Mandarin | zh(或产品约定码) | 国语 |
粤语 / Cantonese | yue | 粤语 |
CHS&ENG / 中英 / 双语 | ["zh-Hans","en"] | 简体中文 / 英语 |
未知语言输出 und,前端显示「未知语言」;不要默认猜成中文。zh 与 zh-Hans 同时出现时保留更具体的;简繁可共存。
语义标签 ≠ 语言
| 线索 | 处理 |
|---|---|
| Commentary / Director Commentary | 不进语言码;标题生成「评论音轨」「导演评论音轨」 |
| Forced / SDH / CC / Signs | 字幕语义;可组合成「简中强制字幕」「英文听障字幕」 |
| Original / Dub | 原音 / 配音语义,不是语言名 |
| Track 1、完整文件名、hash、单独 Default | 噪声,直接丢弃 |
音轨缺标题时,常见生成形态是「国语 5.1」「粤语 2.0」「英语评论音轨」;字幕则是「简中字幕」「强制字幕」等。词表服务展示,不必首版塞进全量发布组黑话。
字典边界
轨道语言字典与地理归一化(国家/地区别名)应共用「字典根目录 + 默认语言包」配置模式,但不要把语言解析塞进地理服务。展示语言包文件名(如 zh-CN.json)表示「界面语言包」,文件内的 key 才是轨道标准码——避免把 UI locale 和媒体轨语言混成一套。
DV / HDR 分类原则
动态范围字段服务的是:尽量表达真实媒体事实,信息不足时保守。它不负责设备能不能播、浏览器要不要转码、画面 tone mapping 是否正确——那些属于播放决策与色彩管线(见硬转码 / tone mapping 文)。
稳定输出集合(展示向)
| 分类 | 展示示例 | 判定核心 |
|---|---|---|
| SDR | SDR | 无 HDR/DV/PQ/HLG 事实 |
| PQ10 | PQ10 | PQ / ST 2084,但无足够 HDR10 静态 metadata,也无更高优先级格式 |
| HDR10 | HDR10 | 明确 HDR10,或 PQ + ST 2086 / mastering / MaxCLL/MaxFALL 等静态 metadata |
| HDR10+ | HDR10+ | 动态 metadata 或明确 HDR10+ / ST 2094-40 等 token |
| HLG | HLG | transfer 或格式明确 HLG;优先于 ST 2086 抢判 |
| 泛化 HDR | HDR | 只有模糊 HDR 文本,落不到更细分类时的兜底 |
| Dolby Vision | DV P5、DV P8.1 (HDR10)、DV P8.2 (SDR)… | profile +(可选)BL compatibility / fallback |
| 专有 HDR | HDR Vivid、SL-HDR、Advanced HDR | 字段明确时单独输出 |
原则性边界:
- PQ10 ≠ 泛化 HDR:有明确 PQ 信号层时不要糊成
HDR。 - ST 2086 alone ≠ HDR10:mastering display 要结合 PQ / 明确 HDR10 名称等。
- HLG transfer 优先于静态 metadata:避免 HLG 片被 ST 2086 误判成 HDR10。
- DV 优先于普通 HDR 分支:fallback(HDR10/HLG/SDR)只放在括号里,不当成「不是 DV」。
Dolby Vision:别把数字语义搞混
| 写法 | 真正含义 | 常见误读 |
|---|---|---|
Profile 8.1 | Profile=8,BL compatibility id=1(常 HDR10 兼容) | 把 .1 当 level |
Profile 7.6 | Profile=7,compatibility id=6 | 把 .6 当 FEL/MEL |
dvhe.08.06 | profile string 08 + level 06 | 当成 P8.6 |
BL+EL+RPU | 有增强层结构 | 当成 FEL 标志 |
因此:
- 未知 P8 compatibility → 输出
DV P8,不强行P8.1。 P8.2 (SDR)/P8.4 (HLG)/P8.6 (HDR10…)应能与P8.1区分。- MediaInfo 常规字段分不清 P7 FEL/MEL 时,扫描热路径不输出 FEL/MEL;离线工具另说。
- 文件名里的
HDR/DV不参与扫描判定,只作人工排查参考。
展示名与文件名 token 拆开
| 名称 | 用途 |
|---|---|
HdrDisplay | 人类可读;入库、API、NFO、筛选项 |
HdrToken | 文件名安全派生(无空格/括号/点号冲突);不另存为独立媒体事实 |
例如 HDR10+ → HDR10Plus,DV P8.1 (HDR10) → DV-P8_1-HDR10。数据库持久化展示值与结构化字段(kind / profile / compatibility / fallback),token 运行时统一 formatter 生成,避免规范变化后双写回填。
判定优先级(概念序)
- 明确 Dolby Vision(必要时在 DV 分支内补探针,见下节)。
- 明确专有 HDR(HDR Vivid / SL-HDR / Advanced HDR 等)。
- 有效 transfer:Stream 原始 transfer 可覆盖仅来自 Container 的冲突值。
- HLG → HDR10+ → HDR10 → PQ10 → 泛化 HDR → SDR。
冲突信号写日志;展示字段选最能代表主信号的一类,而不是拼成超长混合串。
为什么要分层探针
「每个文件永远跑全量深度探针」和「永远只信一种工具的一句话」都会踩坑。更稳的是按证据充分度分层:
主路径(宽字段、展示友好)
→ 解析 / 分类 / 保守输出
→ 仅当结果模糊且补强字段已知有价值时
→ 窄范围二次探针(例如 ffprobe DOVI side data)
→ 补强失败则保留主路径保守结果,不伪造精度、不拖垮扫描任务
和 MediaInfo / ffprobe 分工的关系
同系列已说明:扫描展示主源与播放补强可以分层,不必全量替换。本文只强调与轨 / 动态范围直接相关的两条:
| 场景 | 主路径 | 补强时机 |
|---|---|---|
| 轨列表、商业名、HDR 文案、轨大小等 | MediaInfo 类展示字段 | 播放 map index、pix_fmt、color、disposition 等用 FFmpeg 视角窄补 |
| Dolby Vision 已明确 profile/compat | MediaInfo 足够则不再二次 | — |
仅 DV P8 / DV (Unknown) 等模糊 | 先保守落盘 | ffprobe 读 DOVI configuration record(如 dv_bl_signal_compatibility_id)再细化 P8.x |
dv_bl_signal_compatibility_id 实用映射大致是:1→HDR10、2→SDR、4→HLG、6→HDR10(与 P8.6 一类展示对齐时要和 level 拆开看)。工具不可用、超时、JSON 异常或没有 side data 时:保留模糊结果 + warning,不要假装已经是 P8.1。
分层探针的工程收益
- 全库成本可控:只有模糊 DV 子集才二次启动进程,而不是每个 SDR 也跑 side data。
- 精度与诚实并存:能细则细,不能细则
DV P8/PQ10/HDR,不靠猜。 - 职责清晰:展示文案、结构化动态范围、播放 map 索引可以分字段演进;业务 resolver 再统一出口。
- 失败可观测:补强失败写摘要,扫描任务不因单文件探针异常整批失败。
深度帧/包扫描、FEL/MEL 离线诊断、ParseSpeed 与全库墙钟等,仍属于探测工具与扫描性能议题,详见 MediaInfo vs ffprobe 文,不在本文展开。
和播放链路怎么接
轨识别与动态范围输出的是媒体事实,不是播放策略本身:
扫描事实(轨清单 + HdrDisplay / 结构化 DR + stream index)
→ 播放 Planner / 能力门禁
→ DirectPlay 或 HLS 转码(含 tone mapping 分流)
→ 客户端选轨与渲染
- 默认音轨 / 字幕偏好、用户上次选择,属于播放会话与客户端偏好,不宜塞进扫描热路径的「猜语言」。
- pure DV、HLG、HDR10 在服务端如何选 VAAPI / OpenCL tone mapping,见 媒体库硬转码与 Tone Mapping。
- 会话建不起来、HLS window、字幕 full-cache 等问题,见 播放排障指南。
注意事项
- 本文是识别与分层原则,不是某一版本扫描器的字段契约或 API 文档。
- 本地片库样本不能证明「某 MediaInfo 字段不存在」;字段存在性要以工具定义 + 多样本共同判断。
- 「轨数量一致」不等于「语言、标题、HDR 文案可互换」。
- 文件名 token、发布组标签、人工备注可用于排查,但不应单独决定扫描阶段动态范围。
- 内网样片路径、原始 CSV 制品目录、私有日志路径不进入公开复现步骤。
相关阅读
同系列还可对照:
- 媒体库探测选型:MediaInfo 与 ffprobe 该怎么分工:展示主源与播放补强的工具分层,本文不重复速度与全量字段对比。
- 媒体库硬转码与 Tone Mapping:软件 / VAAPI / OpenCL 如何选型:动态范围事实如何进入服务端转码与 tone mapping 分流。
- 媒体库播放排障:从会话创建到 HLS / DirectPlay / 字幕链路:会话、交付方式与字幕控制面问题如何对照扫描事实排查。
来源
本文综合改写自 Octans 项目研究文档中的媒体轨道识别与视频动态范围检测结论(含主视频轨选择、语言/标题 resolver 原则、HDR/DV 稳定输出集与 ffprobe DOVI 窄补强边界)。实现细节与内网样本路径已抽象为通用工程口径;具体字段覆盖率以目标环境与工具版本复测为准。