No description
- Go 72.2%
- Python 19.6%
- Shell 7.3%
- Dockerfile 0.3%
- Makefile 0.3%
- Other 0.3%
| cmd | ||
| deployments/docker | ||
| docs | ||
| internal | ||
| legacy/python | ||
| scripts | ||
| .dockerignore | ||
| .gitignore | ||
| .woodpecker.yml | ||
| AGENTS.md | ||
| auto_test.cmd | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
MatchSubtitles (Go Rewrite)
目标:保持核心能力(字幕与媒体自动匹配/重命名),去掉花哨功能,优先代码质量与工程结构。
核心功能
- 按剧集序号匹配 媒体文件 与 字幕文件
- 支持两种输入:
-m普通媒体目录-b蓝光原盘目录(自动读取BDMV/STREAM/*.m2ts)
- 蓝光多盘模式按单盘结果逐盘叠加
- 支持排除整盘或指定 playlist
- 支持输出到目标目录(
-t)或就地处理 -b和-t可同时使用,将识别出的原盘媒体文件输出到目标目录- 支持执行模式:
- 普通媒体模式默认
hardlink - 蓝光模式默认
hardlink --move显式改为移动-c显式改为复制-l显式改为硬链接
- 普通媒体模式默认
- 硬链接失败时会打印警告,并自动回退为移动
- 支持 dry-run(
-p) - 支持最小权限 HTTP 服务(
--port)
构建
go build -o bin/matchsubtitles ./cmd/matchsubtitles
用法
1) 普通媒体 + 字幕匹配
./bin/matchsubtitles -s /path/subtitles -m /path/media
默认会硬链接字幕到媒体目录;如果加 -t,则输出到目标目录。
2) 原盘 + 字幕匹配(自动)
./bin/matchsubtitles -s /path/subtitles -b /path/disc1 -b /path/disc2
默认会硬链接输出;如果加 -t,会把原盘媒体和字幕一起输出到目标目录。
3) 只识别原盘并输出媒体到目标目录
./bin/matchsubtitles -b /path/disc1 -b /path/disc2 -t /path/target
4) 原盘强制分配(每盘固定数量)
./bin/matchsubtitles -s /path/subtitles -b /path/disc1 -b /path/disc2 -f -e 2
5) 原盘排除某一盘
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 --exclude-disc 2
6) 原盘排除指定 playlist
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 --exclude-playlist 1:00012
也支持不带盘号的全局排除:
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 --exclude-playlist 00012
7) 输出到目标目录(只打印计划,不落盘)
./bin/matchsubtitles -s /path/subtitles -m /path/media -t /path/target -p
8) 显式指定文件操作模式
./bin/matchsubtitles -s /path/subtitles -m /path/media --move
./bin/matchsubtitles -s /path/subtitles -m /path/media -c
./bin/matchsubtitles -b /path/disc1 -t /path/target -l
9) 启动 Web 服务
./bin/matchsubtitles --port 8080
不指定 --port 时,不启动 Web 服务。
如果你的数据在当前目录之外,例如上级目录的 ../source,需要显式放宽 Web 根目录:
./bin/matchsubtitles --port 8080 --web-root ..
也支持多个根目录:
./bin/matchsubtitles --port 8080 --web-root .. --web-root /mnt/media
参数
-s, --subtitle字幕目录-m, --media媒体目录(与-b二选一)-b, --bdmv原盘目录,可重复-t, --target目标目录-f, --force强制分配模式(原盘)-e, --each每盘固定数量(需配合-f)-r, --reverse反向分配(原盘平均模式)--exclude-disc排除指定盘号,可重复--exclude-playlist排除指定 playlist,可重复,格式为00012或2:00012-c, --copy复制输出文件-l, --hardlink硬链接输出文件--move移动输出文件-p, --print仅打印计划(dry-run)-pf, --prefix自定义文件名前缀-sf, --suffix自定义文件名后缀--bd-strategy蓝光策略,支持hybrid|size|parser--port启动 HTTP 服务端口;不指定则不启动--web-rootWeb 服务允许访问的根目录,可重复,默认当前工作目录
默认行为
-m -s- 默认硬链接字幕
- 不带
-t时输出到媒体所在目录 - 带
-t时输出到目标目录
-b- 不带
-t且不带-s时,只打印识别到的媒体文件
- 不带
-b -t- 默认硬链接输出识别到的原盘媒体文件
-b -s -t- 默认硬链接输出原盘媒体文件和匹配后的字幕
--move- 显式改为移动
-l- 显式要求硬链接
- 若硬链接失败,会提示并回退为移动
--port- 启动只读/受限导出 HTTP 服务
- 不参与普通 CLI 匹配流程
--web-root- 所有 Web API 的读写边界
- 接口传入的路径必须位于该根目录下
HTTP 接口
接口设计遵循最小权限原则:
- 不提供删除、覆盖、移动接口
- 导出时只允许:
- 媒体文件使用硬链接
- 字幕文件使用复制
- Web 层基于
humi + chi,可直接复用 Huma 的 OpenAPI 输出 - 所有路径都会限制在
--web-root下,并拒绝越界路径与越界 symlink
1) 列出指定路径下所有原盘
健康检查:
curl 'http://127.0.0.1:8080/health'
返回:
{"status":"ok"}
1) 列出指定路径下所有原盘
curl 'http://127.0.0.1:8080/api/discs?path=/path/to/root'
2) 查看单个原盘的所有 m2ts 和解析出的主剧集文件
curl 'http://127.0.0.1:8080/api/disc/inspect?path=/path/to/disc'
返回中还会包含:
selected_playlist_idexcluded- 被排除 playlist 的
playlist_id - 排除原因
reason
- 被排除 playlist 的
3) 解析字幕并按语言分类
curl 'http://127.0.0.1:8080/api/subtitles?path=/path/to/subtitles'
4) 导出选定的媒体和字幕到目标目录
curl -X POST 'http://127.0.0.1:8080/api/export' \
-H 'Content-Type: application/json' \
-d '{
"target_dir": "/path/to/out",
"media_paths": ["/path/a/00000.m2ts", "/path/b/00001.m2ts"],
"subtitle_paths": ["/path/sub/ep1.ass", "/path/sub/ep2.ass"]
}'
约束:
media_paths必填,可跨盘subtitle_paths可选- 如果提供字幕,数量必须与媒体数量一致
- 目标目录不存在时会自动创建
- 已存在目标文件时接口会直接报错,不覆盖
5) 用选定媒体和字幕目录自动配对字幕
curl -X POST 'http://127.0.0.1:8080/api/match' \
-H 'Content-Type: application/json' \
-d '{
"media_paths": [
"/path/a/00000.m2ts",
"/path/b/00001.m2ts"
],
"subtitle_dir": "/path/to/subtitles",
"language": "chs",
"mode": "manual"
}'
说明:
media_paths的顺序就是最终配对顺序subtitle_dir必填language可选mode支持:manual- 按传入媒体顺序和字幕顺序配对
- 适合原盘
m2ts
auto- 尝试按文件名剧集号自动匹配
- 适合普通媒体文件
- 如果字幕目录里存在多种语言且未指定
language,接口会报错要求显式指定
6) 直接导出已配对结果
curl -X POST 'http://127.0.0.1:8080/api/export-matched' \
-H 'Content-Type: application/json' \
-d '{
"target_dir": "/path/to/out",
"prefix": "EP",
"suffix": "_chs",
"mode": "hardlink",
"naming": "order",
"include_media": true,
"pairs": [
{
"media_path": "/path/a/00000.m2ts",
"subtitle_path": "/path/sub/ep1.ass",
"order": "1"
}
]
}'
说明:
prefix/suffix与 CLI 的-pf/-sf对齐,会用order生成输出文件名。mode与 CLI 的--hardlink/--copy/--move对齐,支持hardlink|copy|move。naming: "order"可在未设置 prefix/suffix 时也强制按集数命名。- 多圆盘原盘常见
00002.m2ts等同名媒体;需要导出媒体时推荐显式使用naming: "order"或prefix/suffix,按已配对字幕顺序生成文件名。 include_media默认为true,设为false时只导出字幕。
7) 生成匹配计划
/api/plan 只生成结构化计划,不写入输出文件。它适合无 SSH agent 先判断媒体、字幕和导出预览,再决定是否调用 /api/export-matched。
curl -X POST 'http://127.0.0.1:8080/api/plan' \
-H 'Content-Type: application/json' \
-d '{
"source_root": "/path/to/bdmv-set",
"subtitle_root": "/path/to/subtitles",
"target_dir": "/path/to/out",
"disc_rule": {
"mode": "largest_per_disc",
"count": 2
},
"subtitle_filter": {
"language": "sc",
"include_contains": [".sc."],
"exclude_contains": ["副本"],
"dedupe_by_order": true
},
"prefix": "Show.E",
"naming": "order",
"include_media": true
}'
说明:
disc_rule.mode支持inspect、largest_per_disc、largest_total。subtitle_filter可按语言、路径包含/排除和 order 去重筛选字幕。- 返回包含
pairs、actions、media_candidates、subtitle_choices、confidence、score和warnings。 confidence=high且无关键 warning 时,agent 可以把pairs传给/api/export-matched执行。
当前蓝光规则
- 单盘识别以
INDX/MPLS解析结果为基础 - 先保留可映射到
m2ts的候选 - 优先参考
libbluray风格的主 title 特征,而不是只按最大文件或最长时长 - 使用时长簇、章节数、preferred playlist 等特征筛选主剧集候选
- 过滤明显偏短、结构异常、或像附属内容的候选
- 对已选 playlist 的映射结果再做一次输出收缩,尽量只保留核心媒体文件
- 多盘按单盘识别结果依盘序叠加
- 支持排除整盘或指定 playlist
手动回归命令
普通媒体
go test ./...
go build -o bin/matchsubtitles ./cmd/matchsubtitles
./bin/matchsubtitles -p -s /path/subtitles -m /path/media
单盘蓝光
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1
多盘蓝光
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2
多盘蓝光 + 排除整盘
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 --exclude-disc 2
多盘蓝光 + 排除 playlist
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 --exclude-playlist 1:00012
原盘输出到目标目录
./bin/matchsubtitles -p -b /path/disc1 -b /path/disc2 -t /path/target
./bin/matchsubtitles -p -s /path/subtitles -b /path/disc1 -b /path/disc2 -t /path/target
项目结构
cmd/matchsubtitles/ # CLI 入口
internal/app/ # 应用编排层
internal/cli/ # 参数解析
internal/domain/ # 核心数据模型
internal/parse/ # 剧集号和蓝光候选解析
internal/select/ # 蓝光筛选规则
internal/scan/media/ # 普通媒体扫描
internal/scan/subtitle/ # 字幕扫描与语言识别
internal/scan/root/ # 原盘根目录与 STREAM 发现
internal/match/ # 字幕匹配和原盘分配
internal/httpapi/ # humi + chi HTTP 接口层
internal/service/disc/ # CLI 与 Web 共用的原盘选择用例
internal/service/web/ # Web 共享服务层与路径边界控制
internal/plan/ # 输出路径规划
internal/ops/ # 文件操作执行
internal/bd/ # 蓝光兼容入口与 playlist 解析
legacy/python/ # 旧 Python 参考实现
说明:
internal/match/disc和internal/match/pair为正式匹配实现internal/plan/rename和internal/ops/file为正式执行链路internal/scan/*现在按媒体、字幕、原盘发现拆分,主链路直接使用子包internal/service/disc负责统一原盘发现、inspect 和按策略选媒体