本文主要说明自托管媒体库(以 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(例如一季共用同一字幕文件)适合在图谱里表达「一资产多文件链接」,展示层仍可为每个媒体文件投影一行,避免选轨模型变成多对多主键。

外置归属要保守

外置字幕 / 外置音轨匹配应以「宁可歧义不写展示,也不乱挂」为原则:

  1. 规范化 stem(去掉语言尾缀、语义尾缀)。
  2. 同目录与视频 stem 精确匹配优先。
  3. 再尝试标题 / 发行后缀等候选。
  4. 多候选时用兼容性分组(同一作品 / 同一版本;文本字幕时长容差可宽一点,图形字幕与外置音轨更严)。
  5. 仍无法唯一归属 → 标记歧义,不生成普通展示轨;诊断留在图谱或日志。

VobSub 的 .idx + .sub 应作为一组逻辑字幕处理,缺一半则标记不完整,不装作可用字幕。

为何不必一上来合并统一 MediaTrack 表

AudioTrack / Subtitle 分表在扫描、NFO、播放候选都已沉淀时,首轮更划算的是补齐同一套语义字段SourceStreamIndexRawLanguage / LanguageCodesRawTitle / Title 等)并共用 resolver,而不是把「数据迁移」和「识别修正」绑死。统一表可以等跨类型排序、默认轨、用户偏好真的需要频繁合并时再评估。

Stream index 与展示列表:对齐原则

播放侧真正需要的是「与 FFmpeg 视角一致的全局 stream index」,展示侧需要的是「稳定、可读、可排序的轨清单」。

应对齐什么

应对齐说明
类型与条数视频 / 音频 / 字幕条数与 demux 所见一致,是最低验收
映射 index内置轨的 index 应能对应到 -map 0:<index> 或等价 map 策略
dispositiondefault / 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」去偏选:

  1. Default=Yes 优先。
  2. 分辨率像素数更大优先。
  3. 时长更长优先。
  4. 原始顺序更靠前兜底。

没有可用视频轨时,不要伪造 SDR 掩盖问题;应让主轨选择显式失败,避免非视频文件被标成正常片源。

展示排序要稳定

同一物理文件多次扫描,轨列表顺序应稳定(避免 UI 抖动)。通常:

  • 内置音轨 / 字幕按 StreamIndex
  • 外置按文件名自然序;
  • 语言优先级可用于分组,但不宜默认打乱容器原始顺序;
  • 同语言下 forced 可排在普通字幕前。

跨 remux、跨发布源不承诺顺序一致——mux 顺序变了,index 跟着变是合理结果。

语言与标题启发式

真实片库里,MediaInfo / 容器 tags 的 Language 经常缺、错或只是 und;真正有用的线索大量藏在 title、文件名尾缀、外置 logical name 里。识别链路要服务三件事:标准语言码、中文(或当前语言包)展示标签、可读轨标题。

保留原始值,再生成展示值

字段角色
RawLanguage / RawTitle证据,排查误判时回溯
LanguageCodes标准码列表(如 zh-Hansen),可再生成
Title清洗 + 词表 + 缺省生成后的可读标题
LanguageLabel展示语言包产物,宜在 API 出口生成,不入库

不要只存「清洗后的 Title」。白名单一旦误杀,语言证据会一起消失。

顺序:先语言,后标题

错误顺序:

先 CleanTrackTitle → 再用清洗结果猜语言

正确顺序:

1. 读取 RawLanguage、RawTitle、文件名尾缀、外置 logical name
2. 字典 / 规则解析 LanguageCodes 与置信度(Exact / Inferred / Unknown)
3. 标题 normalizer 生成 Title
4. 若 RawTitle 只是纯语言名,不硬当标题正文,而用语言码生成「德语字幕」一类展示名

历史上把 ArabicPortugueseCzech 等合法语言标题当「未命中白名单」丢掉,就是典型反例:它们不适合当最终花哨标题,却是语言识别的有效证据。

标准码与多语言

内部宜用可读的 BCP-47 风格,而不是只存容器三字母原值:

线索示例标准码展示方向
chs / 简中 / Simplified Chinesezh-Hans简体中文
cht / 繁中zh-Hant繁体中文
国语 / Mandarinzh(或产品约定码)国语
粤语 / Cantoneseyue粤语
CHS&ENG / 中英 / 双语["zh-Hans","en"]简体中文 / 英语

未知语言输出 und,前端显示「未知语言」;不要默认猜成中文zhzh-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 文)。

稳定输出集合(展示向)

分类展示示例判定核心
SDRSDR无 HDR/DV/PQ/HLG 事实
PQ10PQ10PQ / ST 2084,但无足够 HDR10 静态 metadata,也无更高优先级格式
HDR10HDR10明确 HDR10,或 PQ + ST 2086 / mastering / MaxCLL/MaxFALL 等静态 metadata
HDR10+HDR10+动态 metadata 或明确 HDR10+ / ST 2094-40 等 token
HLGHLGtransfer 或格式明确 HLG;优先于 ST 2086 抢判
泛化 HDRHDR只有模糊 HDR 文本,落不到更细分类时的兜底
Dolby VisionDV P5DV P8.1 (HDR10)DV P8.2 (SDR)profile +(可选)BL compatibility / fallback
专有 HDRHDR VividSL-HDRAdvanced 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.1Profile=8,BL compatibility id=1(常 HDR10 兼容).1 当 level
Profile 7.6Profile=7,compatibility id=6.6 当 FEL/MEL
dvhe.08.06profile 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+HDR10PlusDV P8.1 (HDR10)DV-P8_1-HDR10。数据库持久化展示值与结构化字段(kind / profile / compatibility / fallback),token 运行时统一 formatter 生成,避免规范变化后双写回填。

判定优先级(概念序)

  1. 明确 Dolby Vision(必要时在 DV 分支内补探针,见下节)。
  2. 明确专有 HDR(HDR Vivid / SL-HDR / Advanced HDR 等)。
  3. 有效 transfer:Stream 原始 transfer 可覆盖仅来自 Container 的冲突值。
  4. HLG → HDR10+ → HDR10 → PQ10 → 泛化 HDR → SDR。

冲突信号写日志;展示字段选最能代表主信号的一类,而不是拼成超长混合串。

为什么要分层探针

「每个文件永远跑全量深度探针」和「永远只信一种工具的一句话」都会踩坑。更稳的是按证据充分度分层

主路径(宽字段、展示友好)
  → 解析 / 分类 / 保守输出
  → 仅当结果模糊且补强字段已知有价值时
      → 窄范围二次探针(例如 ffprobe DOVI side data)
  → 补强失败则保留主路径保守结果,不伪造精度、不拖垮扫描任务

和 MediaInfo / ffprobe 分工的关系

同系列已说明:扫描展示主源与播放补强可以分层,不必全量替换。本文只强调与轨 / 动态范围直接相关的两条:

场景主路径补强时机
轨列表、商业名、HDR 文案、轨大小等MediaInfo 类展示字段播放 map index、pix_fmt、color、disposition 等用 FFmpeg 视角窄补
Dolby Vision 已明确 profile/compatMediaInfo 足够则不再二次
DV P8 / DV (Unknown) 等模糊先保守落盘ffprobe 读 DOVI configuration record(如 dv_bl_signal_compatibility_id)再细化 P8.x

dv_bl_signal_compatibility_id 实用映射大致是:1→HDR102→SDR4→HLG6→HDR10(与 P8.6 一类展示对齐时要和 level 拆开看)。工具不可用、超时、JSON 异常或没有 side data 时:保留模糊结果 + warning,不要假装已经是 P8.1

分层探针的工程收益

  1. 全库成本可控:只有模糊 DV 子集才二次启动进程,而不是每个 SDR 也跑 side data。
  2. 精度与诚实并存:能细则细,不能细则 DV P8 / PQ10 / HDR,不靠猜。
  3. 职责清晰:展示文案、结构化动态范围、播放 map 索引可以分字段演进;业务 resolver 再统一出口。
  4. 失败可观测:补强失败写摘要,扫描任务不因单文件探针异常整批失败。

深度帧/包扫描、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 等问题,见 播放排障指南

注意事项

  1. 本文是识别与分层原则,不是某一版本扫描器的字段契约或 API 文档。
  2. 本地片库样本不能证明「某 MediaInfo 字段不存在」;字段存在性要以工具定义 + 多样本共同判断。
  3. 「轨数量一致」不等于「语言、标题、HDR 文案可互换」。
  4. 文件名 token、发布组标签、人工备注可用于排查,但不应单独决定扫描阶段动态范围。
  5. 内网样片路径、原始 CSV 制品目录、私有日志路径不进入公开复现步骤。

相关阅读

同系列还可对照:

来源

本文综合改写自 Octans 项目研究文档中的媒体轨道识别与视频动态范围检测结论(含主视频轨选择、语言/标题 resolver 原则、HDR/DV 稳定输出集与 ffprobe DOVI 窄补强边界)。实现细节与内网样本路径已抽象为通用工程口径;具体字段覆盖率以目标环境与工具版本复测为准。