云盘里有了媒体文件,距离在 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 自动入库与刮削教程。