云盘里有了媒体文件,距离在 Emby 中看到标题、海报和正确的季集,还隔着几件事:生成播放入口、识别作品、整理目录,以及让媒体库发现新文件。

我的做法是把这些工作拆开:CMS 负责生成 STRM,MHTI 处理需要预先整理的分类,Emby 负责正式媒体库和播放。 普通影视可以直接入库;需要特别识别的内容先经过中转区,整理完成后再交给 Emby。

这篇文章记录这套方案的目录设计、容器映射、部署要点和排查顺序。配置核对时间为 2026 年 10 月 5 日。

一、先把三个组件的职责分清

组件 主要职责 不应承担的工作
CMS(Cloud Media Sync) 连接云盘、同步分类、生成 .strm 让尚未识别的特殊分类直接进入正式库
MHTI 识别作品、匹配 TMDB 与季集、生成 NFO 和图片、整理目录 替代所有普通影视的入库流程
Emby 扫描正式目录、读取或补充元数据、展示媒体并提供播放 重复猜测已经由 MHTI 整理好的作品信息

.strm 是保存播放地址的文本文件,本身不是视频。扫描成功只说明 Emby 发现了播放入口;真正播放时,还需要地址和相关代理服务可用。

我的数据流分为两条:

云盘中的媒体文件
        │
        ▼
       CMS
        │
        ├─ 普通影视 → STRM 正式目录 → Emby 扫描与在线刮削
        │
        └─ 里番 → MHTI 中转区
                       │
                       ▼
                识别作品、匹配季集
                       │
                       ▼
                生成 NFO、海报与背景图
                       │
                       ▼
                移入正式目录 → Emby 读取本地元数据

中转目录不加入 Emby 媒体库,也不挂载给 Emby。 这是整套方案的关键:没有完成整理的文件不会提前出现在首页,识别失败的任务也有单独处理的空间。

二、目录设计:同一份文件,不同的容器路径

宿主机目录分成正式区和中转区:

/root/emby/
├── strm/                         # 正式媒体库
│   └── Media/Video/
└── mhti-incoming/                # 中转区
    └── 里番/

容器路径要按用途分别理解:

用途 宿主机 CMS MHTI Emby
正式库根目录 /root/emby/strm /media,另有 /library 映射 /library /media
中转区根目录 /root/emby/mhti-incoming /incoming /incoming 不挂载
特殊分类中转目录 /root/emby/mhti-incoming/里番 /media/Media/Video/里番 /incoming/里番 不可见
特殊分类最终目录 /root/emby/strm/Media/Video/里番 通过 /library/Media/Video/里番 可见 /library/Media/Video/里番 /media/Media/Video/里番

CMS 使用一层根目录挂载,再叠加一层更具体的子目录挂载:

volumes:
  - /root/emby/strm:/media
  - /root/emby/mhti-incoming/里番:/media/Media/Video/里番

第二条挂载覆盖 CMS 容器中对应的子目录。所以 CMS 写入 /media/Media/Video/里番 时,文件实际进入宿主机中转区;其他分类仍然写入正式目录。

MHTI 整理后的输出则进入正式区:

CMS /media/Media/Video/里番
    = 宿主机 /root/emby/mhti-incoming/里番
    = MHTI /incoming/里番

MHTI /library/Media/Video/里番
    = 宿主机 /root/emby/strm/Media/Video/里番
    = Emby /media/Media/Video/里番

当前 CMS 还挂载了 /root/emby/strm:/library。这样,整理流程中引用 /library 路径的文件,也能在 CMS 容器里找到对应位置。判断一条路径是否有效时,要看实际读取它的容器,不能只确认宿主机上存在这个文件。

三、Docker Compose 部署要点

当前环境通过 /root/media-migration/compose.yaml 统一管理 Emby、CMS、MHTI 和 MetaTube;这些服务使用同一个 Docker 网络。下面按服务摘录需要关注的目录和端口设置,合并配置时还要保留各应用要求的镜像、环境变量和启动参数。

1. 准备持久化目录

mkdir -p /root/emby/strm
mkdir -p /root/emby/mhti-incoming/里番
mkdir -p /root/emby/config/emby
mkdir -p /root/cms/config /root/cms/logs /root/cms/cache
mkdir -p /root/mhti/data

当前核对到的版本如下。这是本机部署记录,升级时仍需检查各项目的兼容性:

服务 当前版本
Emby 4.10.0.11
CMS 0.4.9.8
MHTI 2.1.7

2. Emby 只挂载正式库

services:
  emby:
    restart: unless-stopped
    ports:
      - "127.0.0.1:8096:8096"
    volumes:
      - /root/emby/config/emby:/config
      - /root/emby/strm:/media

需要硬件转码时,再根据主机设备配置 /dev/dri 等设备映射。普通播放和目录扫描不需要为了照抄示例而添加不存在的设备。

3. CMS 同时看到正式区和中转区

services:
  cloud-media-sync:
    ports:
      - "127.0.0.1:9527:9527"
      - "127.0.0.1:9096:9096"
    environment:
      EMBY_HOST_PORT: http://emby:8096
      EMBY_API_KEY: ${EMBY_API_KEY}
      ADMIN_USERNAME: ${CMS_ADMIN_USERNAME}
      ADMIN_PASSWORD: ${CMS_ADMIN_PASSWORD}
    volumes:
      - /root/cms/config:/config
      - /root/cms/logs:/logs
      - /root/cms/cache:/var/cache/nginx/emby
      - /root/emby/strm:/media
      - /root/emby/strm:/library
      - /root/emby/mhti-incoming:/incoming
      - /root/emby/mhti-incoming/里番:/media/Media/Video/里番

同一个 Docker 网络中,CMS 应通过 http://emby:8096 这样的服务名访问 Emby,避免依赖可能变化的容器 IP。

密码和 API Key 可以放进 Compose 所在目录的 .env,权限设为 0600。公开配置只保留变量引用:

CMS_ADMIN_USERNAME=替换为管理用户名
CMS_ADMIN_PASSWORD=替换为强密码
EMBY_API_KEY=替换为Emby生成的API密钥

4. MHTI 从中转区读取,向正式区输出

services:
  mhti:
    restart: unless-stopped
    ports:
      - "127.0.0.1:8000:8000"
    environment:
      DATA_DIR: /app/data
      TZ: Asia/Shanghai
    volumes:
      - /root/mhti/data:/app/data
      - /root/mhti/Caddyfile:/etc/caddy/Caddyfile:ro
      - /root/emby/mhti-incoming:/incoming
      - /root/emby/strm:/library

MHTI 镜像内部由 Caddy 提供前端,并转发 API、WebSocket 和健康检查请求。外层使用系统 Nginx,回源到宿主机的 127.0.0.1:8000;因此这套部署不需要原笔记中的独立 caddy-net 网络。

内部 Caddy 路由的核心结构是:

:8000 {
    encode gzip

    handle /api/* {
        reverse_proxy localhost:8001
    }
    handle /ws {
        reverse_proxy localhost:8001
    }
    handle /health* {
        reverse_proxy localhost:8001
    }
    handle {
        root * /app/static
        try_files {path} /index.html
        file_server
    }
}

外层 Nginx 还需要正确转发 WebSocket。排查页面能打开、任务进度却不更新的问题时,应同时检查 /api/ 和 /ws。

5. 启动前先检查配置

完成各服务和共享网络配置后,在我的环境中使用:

docker compose -f /root/media-migration/compose.yaml config --quiet
docker compose -f /root/media-migration/compose.yaml up -d
docker compose -f /root/media-migration/compose.yaml ps

config --quiet 用于校验配置,不会把解析后的密码和密钥打印到终端。

四、MHTI 与 Emby 应该怎样设置

MHTI:明确输入、输出和整理方式

设置项 建议配置
监控 开启实时监控
监控目录 /incoming/里番
自动刮削 开启
文件稳定等待 30 秒
扫描间隔 60 秒
整理目录 /library/Media/Video/里番
整理方式 移动
自动清理源目录 关闭

这些数值沿用原方案,具体选项以当前 MHTI 界面为准。目录必须填写 MHTI 容器内路径,不要把 /root/emby/... 这样的宿主机路径直接填进去。

整理后的标准结构类似:

作品名 (年份)/
├── tvshow.nfo
├── poster.jpg
├── backdrop.jpg
└── Season 1/
    ├── season.nfo
    ├── 作品名 - S01E01.strm
    ├── 作品名 - S01E01.nfo
    └── 作品名 - S01E01.jpg

Emby:不同媒体库使用不同元数据来源

媒体库 内容类型 Emby 容器内路径 元数据策略
电视剧 电视节目 /media/Media/Video/TV/国产剧、/media/Media/Video/TV/欧美剧、/media/Media/Video/TV/日韩剧 TVDB、TMDB
电影 电影 /media/Media/Video/Movie TMDB 等在线提供者
动漫 电视节目 /media/Media/Video/TV/儿童、/media/Media/Video/TV/国漫、/media/Media/Video/TV/日番 TVDB、TMDB
AV 电影 /media/Media/Video/AV MetaTube
里番 电视节目 /media/Media/Video/里番 MHTI 生成的本地 NFO 和图片

普通媒体库根据需要开启在线元数据提供者和 NFO 保存。由 MHTI 整理的媒体库应启用本地元数据读取,关闭在线元数据和图片提供者,避免覆盖已确认的识别结果。

各媒体库开启实时监控;再为「扫描媒体库」计划任务设置每 12 小时执行一次的触发器,作为兜底。手动扫描用于立即验证或处理异常。

不要把相同物理目录重复加入不同媒体库。例如已经添加了某个细分类目录,就要检查其他媒体库是否又包含它的父目录。

五、用一个文件验证完整链路

先检查容器能否看到预期目录:

docker exec emby test -d /media/Media/Video
docker exec mhti test -d /incoming/里番
docker exec mhti test -d /library/Media/Video/里番
docker exec cloud-media-sync test -d /media/Media/Video/里番
docker exec cloud-media-sync test -d /library/Media/Video

随后用一个测试条目观察文件是否依次经过:

CMS 生成 STRM
    ↓
MHTI /incoming/里番/测试.strm
    ↓ 识别、匹配季集、生成 NFO 与图片
MHTI /library/Media/Video/里番/作品名 (年份)/Season 1/...
    ↓
Emby /media/Media/Video/里番/作品名 (年份)/Season 1/...

确认 MHTI 历史记录显示成功、最终目录包含 NFO 和图片、Emby 显示正确季集后,再进行批量同步。最后还要测试播放,因为 STRM 入库正常并不保证播放地址有效。

必要时查看日志:

docker logs --tail 100 cloud-media-sync
docker logs --tail 100 mhti
docker logs --tail 100 emby

六、识别失败和没有入库时怎么排查

MHTI 无法识别时,先保留待处理状态

文件名信息不足、没有可靠 TMDB 匹配、季集无法判断或目标文件冲突时,应由人工确认:

  • 没有匹配:核对作品,再填写正确的 TMDB ID 和季集。
  • 同名冲突:确认是否为不同版本,再选择重命名或跳过。
  • 不需要处理:标记跳过或删除任务,避免反复入队。

中转目录里还有 STRM,不代表任务卡住。 已跳过或删除的任务可能保留源文件,历史记录会抑制重复入队。应先看历史状态,再决定是否处理文件。

没有可靠数据库条目时,可以依据正确的本地 NFO 整理,不要为了通过识别而填一个错误的 TMDB ID。

按照数据流逐层检查

顺序 检查对象 重点
1 CMS 云盘同步是否成功、STRM 是否生成、分类和实际落盘路径是否正确
2 MHTI 需要预处理的分类是否入队、作品与季集是否匹配、最终文件是否完整
3 Emby 文件是否进入正式目录、媒体库路径是否重复、扫描是否发现文件
4 元数据 普通影视查在线提供者;MHTI 整理的分类查本地 NFO 和图片
5 播放 STRM 地址是否可用、代理与路径映射是否正确

需要立即验证时,只扫描对应媒体库。找清问题之前,不要批量重建元数据,也不要让多个刮削工具同时改写同一个目录。

七、备份要覆盖配置、状态和整理结果

至少保留以下目录:

/root/emby/config/emby       # Emby 数据库、用户、插件和媒体库配置
/root/cms/config            # CMS 配置、账号和同步状态
/root/mhti/data             # MHTI 数据库、配置和密钥
/root/emby/strm             # 整理后的 STRM、NFO、字幕和图片

另外保存 Compose、受限权限的凭据文件、MHTI 的 Caddyfile,以及当前部署使用的适配文件。待处理源文件仍在中转区时,也要考虑备份 /root/emby/mhti-incoming,避免丢失尚未完成整理的条目。

MHTI 数据目录中的 .secret_key 也要保留。只备份数据库而丢失配置解密所需的密钥,可能导致恢复后无法读取原有设置。

SQLite 数据库在线复制时要考虑 WAL 状态。可以使用 SQLite 的一致性备份机制,或者短暂停止相关服务后复制完整数据目录;不要只复制一个正在写入的数据库主文件就认为备份已经完整。

八、日常维护时记住这几件事

  • 只有需要预处理的分类经过 MHTI,普通影视继续由 CMS 直接送入正式库。
  • MHTI 整理的结果以正确的本地 NFO 为依据,Emby 不再重复在线猜测。
  • 新文件靠实时监控快速发现,定时全库扫描补漏。
  • 中转目录与正式目录隔离,异常任务留在库外处理。
  • 分类展示错误时,同时检查元数据和媒体库目录是否重叠。
  • 当前自动链路没有接入 JavSP,避免引入额外工具同时改写文件。

遇到问题,沿着「CMS 生成文件 → MHTI 整理 → Emby 扫描 → 实际播放」逐步验证,会比反复全库重扫更容易找到原因。

原始笔记:我的 Emby 自动入库与刮削教程。