本文主要说明自托管媒体库在把刮削结果落到磁盘 NFO 时,应如何看待「标准」:Kodi 方言是事实母版、字段全集与默认可互操作子集不是一回事,以及为何宜用 standard / extended 双版本而不是「一份超集 NFO 打天下」。以 Octans 一类治理型媒体库为背景,结论面向 Kodi / Emby / Jellyfin 兼容心智,不展开某一实现的完整字段表。
NFO 常被当成「随便导出一份 XML」。真正踩坑的地方多半不是 well-formed,而是:弱消费端按全局标签名误读、同一语义多标签互相覆盖、技术轨标题污染集标题、用户观看状态写进库级文件。下面按生态背景、分层原则、四类文档角色与验收口径整理,方便做导出设计或对照现有导出做减负。
NFO 到底「标准」在哪里
没有统一 XSD,Kodi 是事实母标准
媒体库生态里的 NFO,本质是 UTF-8 的 XML + 约定根节点 + 约定字段名,而不是某组织发布的强 schema。Kodi / XBMC 文档覆盖电影、剧集、单集等结构最完整,也是后续工具与服务端兼容的主要参照。
常见消费端大致分层理解即可:
| 角色 | 怎么看待 NFO |
|---|---|
| Kodi | 字段母版;本地 NFO 可优先于远程刮削 |
| Jellyfin | 可读写本地 .nfo;字段更宽、映射更松;多标签同义时后写覆盖前写 |
| Emby | 大体兼容 Kodi 方言;公开完整 tag reference 不如 Kodi / Jellyfin 清晰 |
| Plex NFO Agent | 官方支持电影 / 剧集的 Kodi 子集;不指望全字段、不指望 Music / extras |
| 设备端海报墙(弱解析) | 常按标签名粗解析;跨层级复用 title 等高频词风险最大 |
工程含义:
- 内部可以有最大字段模型(数据库 / 探针 / 人物 / 技术轨都齐全)。
- 默认写出的 XML 必须是保守子集,以「最多软件稳定读」为成功标准。
- 不要为每个客户端单独做一套产品 profile(
kodi/plex/zidoo…);验证目标可以很多,产品层导出版本宜收敛。
为什么还要单独做 NFO 模块
NFO 不是「序列化一下数据库对象」的附属功能,而是媒体库规范化链路里的一环:
扫描(物理事实)
→ 刮削(逻辑身份与元数据骨架)
→ 身份稳定
→ NFO 落盘(磁盘层主权与生态交接)
→(并行)重命名 / 资产规范化
它主要解决三件事:
- 数据主权:元数据不只活在私有库里,磁盘上有可迁移的结构化结果。
- 下游兼容:Kodi、Emby、Jellyfin、设备端海报墙等可以直接接手。
- 识别完成落到物理层:库内身份与目录旁 sidecar 对齐,迁移与重建库才可复现。
扫描负责「文件在哪、是什么容器」;刮削负责「是哪部作品」;NFO 负责「把已稳定身份按约定广播到磁盘」。三者不要互相顶替。
核心原则:全集、默认可写、用户状态拆开
三个易混概念
| 概念 | 回答什么 | 默认策略 |
|---|---|---|
| 字段全集 / 内部模型 | 系统「知道」什么 | 尽量全,服务 UI / 播放 / 管理 |
| 默认可互操作导出 | 别人「读得稳」什么 | 白名单子集,少即是多 |
| 用户状态 | 谁看过、看到哪 | 默认不写进 NFO |
用户状态包括 playcount、watched、lastplayed、resume、userrating 等。多用户系统更不应把某一用户进度写成全局 sidecar。需要迁移观看历史时,用独立开关或独立通道,不要绑在「标准元数据导出」上。
推荐产品形态:standard + extended
实践中比「五个客户端 profile」更可维护的是 两个导出档位:
| 档位 | 目标 | 默认是否写 fileinfo | 典型读者 |
|---|---|---|---|
| standard | 最小可互操作 | 否 | Kodi / Jellyfin / Emby / Plex 子集 / 弱解析设备 |
| extended | 信息完整、语义不污染 | 是(完整技术轨) | 本系统读回、高级整理工具、需要探针事实的场景 |
共同约束(两个档位都要满足):
- 合法 XML:UTF-8、单根节点、禁止手工字符串拼接。
- 根节点与类型匹配:
movie/tvshow/season/episodedetails。 - 空值默认省略,不输出空标签(除非语义明确需要空值)。
- 同一文档最多一个
uniqueid default="true";ratings内最多一个默认评分。 - 日期
YYYY-MM-DD;runtime用分钟整数;时长秒用durationinseconds。
standard 不是「字段少所以低级」,而是「字段少所以能被最多软件稳定读」。extended 不是「随便堆字段」,而是「完整但标签语义明确,不污染主标题 / 主评分路径」。
不建议继续拆 ZidooSafe、PlexSafe、KodiStrict 等第三、第四档:弱解析与子集消费端应作为 standard 的验证目标,而不是无限增生 profile。
根节点、命名与剧集层级
四类文档角色
| 类型 | 根节点 | 常见文件名 | 角色 |
|---|---|---|---|
| 电影 | movie | 优先 <VideoFileName>.nfo;一片一夹才考虑 movie.nfo | 单部电影元数据 |
| 剧集根 | tvshow | 固定 tvshow.nfo(剧集根目录) | 父级身份与剧级元数据 |
| 季 | season | season.nfo | 季级补充;不能当季信息唯一来源 |
| 单集 | episodedetails | 与视频同名 .nfo | 单集事实 |
Kodi 对剧集的经典模型是:1 × tvshow.nfo + N × episode.nfo。season.nfo 在 Jellyfin / Emby 侧更常见,Plex 可有可无;Kodi 更强调在 tvshow.nfo 里用 namedseason / seasonplot 表达季名与季简介。因此:
- 可以写
season.nfo,但季名 / 季简介 / 季海报应 镜像进tvshow.nfo。 - 单集 standard 不要重复堆叠剧级字段(
country/studio/tag/languages/ 剧级premiered等);需要时用showtitle、season、episode、aired、本集plot与本集uniqueid即可。
电影命名建议
- 默认文件级
<VideoFileName>.nfo:多版本、同目录多文件、刷新优先级都更稳(也与 Kodi 推荐一致)。 - 目录级
movie.nfo:仅当确认一片一夹且目录语义清晰时再开。 - 扩展版若写入
fileinfo、片源、edition 等 文件事实,更应坚持文件级 NFO:文件事实不是电影条目事实。
多集同文件
这是生态冲突点,必须产品化处理,而不是 silently 生成「多个顶层 episodedetails」:
- 严格 well-formed 单根 XML 与部分历史 / 子集客户端习惯冲突。
- 新基线(如 Kodi v22 方向)倾向分集命名的独立 NFO / artwork。
- 若短期不支持多集同文件,应明确拒绝或提示,而不是输出非法多根文档。
字段分层:写什么、不写什么
下面用「原则 + 示例」代替完整 allowlist 表;实现侧仍建议按文档类型维护路径白名单,用 linter 卡住回归。
身份:uniqueid 优先
<uniqueid type="tmdb" default="true">12345</uniqueid>
<uniqueid type="imdb" default="false">tt1234567</uniqueid>
原则:
- 有 provider ID 时,
uniqueid是 core;默认 ID 全库策略要稳定(电影常优先 TMDB,剧集跟主刮削源)。 tmdbid/imdbid/tvdbid等 alias 适合 extended 或宽松服务端;standard 不必双写,避免重复语义。- 手写遗留
id不进入 standard;需要时再作子集兼容。
Plex 一类客户端会基于 ID 构建稳定 GUID,从而影响重扫后观看历史是否保留——这是 standard 必须把 ID 写对的原因,而不是因为要堆技术字段。
文本与评分:避免同义竞争
| 优先 | 慎用 / 档位控制 | 原因 |
|---|---|---|
title、plot | 与 name / localtitle / review 同写 | 宽松解析器后写覆盖前写 |
ratings 块 + 单一 default | 同时再写根级 rating(standard) | 双通道竞争 |
mpaa | 同时写 certification(standard) | 同义分级 |
premiered / 单集 aired | 过度依赖 year | 日期主字段更稳;year 可作兼容派生 |
standard 简介以 plot 为主;outline 仅在有真正短简介且与 plot 不同时再写。评分保留 1~3 个主流源足够,不要把所有源塞进默认可互操作输出。
演职员与图片
director、credits(Kodi 侧 writer)、actor(name/role/order/thumb)是跨端最稳的人员结构。writer可作 extended / Emby-Jellyfin 兼容补充,standard 不必与credits双写。- 图片优先 本地 sidecar 相对路径(
poster.jpg、fanart.jpg、<file>-thumb.jpg)。受保护 API URL、短期签名 URL 对离线扫描与设备端几乎无用,standard 应省略。
技术流 fileinfo:收益与风险不对称
Kodi 文档里 streamdetails 核心其实很克制:视频 codec / aspect / width / height / durationinseconds / stereomode / hdrtype;音频 codec / language / channels;字幕 language。
| 判断 | 说明 |
|---|---|
standard 默认不写 fileinfo | 对「识别是哪部片」收益低;对弱解析风险高 |
| 播放器本可自探测 | 技术信息不必靠 NFO 才能播 |
| extended 再写完整探针 | 服务读回、高级整理、减少部分客户端重新分析 |
扩展写入时的硬约束:
- 禁止在
audio/subtitle下写<title>。弱解析器若全局找title,会把「英语字幕」盖成集标题 / 电影标题。流标题用tracktitle或扩展容器。 language走 ISO 639-2 三字母(eng、zho…);BCP-47(zh-Hant、en-US)放languagetag。hdrtype只允许空 /hdr10/dolbyvision/hlg。不要写sdr;HDR10+ 在标准路径降级为hdr10,细节放hdrformat等扩展字段。动态范围分类本身见轨 / HDR 系列文,NFO 只消费已归一化结果。- codec 名称要归一化到消费端可识别集合,不要直接落 MediaInfo 原始商业长串。
锁定字段与「保护本地元数据」
lockdata / lockedfields 是 用户策略,不是作品元数据。仅在用户明确要求「下游不要覆盖本地 NFO」时,在 Emby / Jellyfin 友好输出中写入;不要默认打开。
电影 vs 剧集:导出范围两维度
产品上常混在一起的是「处理哪些文件」与「生成哪些层级」:
| 维度 | 回答 | 例子 |
|---|---|---|
| 应用范围(apply) | 本次命中哪些版本 / 集 | 当前集 → 整季 → 整剧;电影单版本 → 全版本 |
| 导出层级(export) | 为命中对象写哪些 NFO | 只要 episode;或 episode + season + tvshow |
两者不能合成一个开关:用户可能「按整季展开处理,但只生成单集 NFO」。预览(会写到哪、是否冲突、是否覆盖)应是正式能力,而不是事后看磁盘。
剧集目录形态示意:
Show Folder/
tvshow.nfo
poster.jpg
fanart.jpg
Season 01/
season.nfo # 可选补充
Show.S01E01.mkv
Show.S01E01.nfo
Show.S01E01-thumb.jpg
高风险模式清单(验收向)
实现或审查导出结果时,可按下面清单做回归,比对照「是否字段最全」更有用:
| 风险 | 症状 / 后果 | 防护 |
|---|---|---|
流标题用 <title> | 设备端标题变成「繁中字幕」 | extended 改 tracktitle;standard 不写流块 |
| 同义多标签 | 简介 / 分级被后写覆盖 | standard 每语义单通道 |
| 多个 default ID / rating | GUID 与评分抖动 | 全局唯一 default |
| 单集堆剧级字段 | 弱端误用父字段 | episode allowlist 收紧 |
| 远程受保护图片 URL | 离线与第三方扫库失败 | 本地 sidecar |
| 用户状态进 NFO | 污染观看历史 | 默认关闭 |
多根 episodedetails | 非 well-formed | 分集 NFO 或明确不支持 |
| 语言码混用 | 轨语言识别失败 | 三字母 + 可选 languagetag |
| HDR 枚举自创 | hdrtype=sdr / hdr10plus | 标准枚举 + 扩展详述 |
建议至少保留一组 golden 样例:电影 standard/extended、tvshow / season / episode 各一档,覆盖多音轨多字幕、HDR10 / DV / HDR10+、BCP-47 字幕语言,并用弱解析心智检查「全文是否只有主对象语义标题路径」。
和扫描、探针事实的边界
NFO 消费的是 已经稳定的逻辑元数据 +(扩展档)已归一化的技术事实,不是替代扫描:
- 轨清单、语言启发式、HDR / DV 分层探针 → 扫描与轨事实层(见相关阅读)。
- NFO 扩展档的
fileinfo应引用归一化结果,而不是在写盘瞬间再发明一套动态范围文案。 - 扫描器目录拓扑决定「写到哪」;没有可靠物理入口就没有可靠 sidecar 路径。
后续扫描器专题(目录策略、增量、图谱 sidecar 归属等)可与本文的「落盘层级」对照阅读;本文不展开扫描实现。
注意事项
- 本文是 字段标准与导出策略,不是某一版本 writer 的契约或完整 XML 模板全集。
- 消费端行为随版本变化;Plex NFO Agent、Kodi 多集策略、设备固件解析都应以目标版本复测为准。
- 「字段多」不等于「兼容好」;弱解析与后写覆盖会让超集输出比子集更危险。
- 内网样例路径、真实片库 NFO 全文、私有签名 URL 不进入对外文档。
相关阅读
同系列还可对照:
- 媒体库轨识别与动态范围:stream index、语言标题与 HDR/DV 分层探针:NFO 扩展档中的轨语言 / 标题 /
hdrtype应消费哪一层已归一化事实。 - 扫描器专题(目录拓扑、增量扫描、sidecar 归属与图谱边界)待后续成文:决定 NFO「写到哪、和谁冲突」,与本文「写什么字段」互补。
来源
本文综合改写自 Octans 项目研究文档中的 NFO 导出字段标准与扩展标准结论,并参考 NFO 模块架构中的导出范围模型(应用范围与导出层级分离)。字段表与客户端差异已收敛为通用互操作原则;具体 tag 覆盖率以目标消费端版本复测为准。