Skip to content

Repository files navigation

OpenList STRM Bridge

openlist_strm_bridge 是专为 OpenList STRM 引擎更新模式 量身打造的智能防灾同步中间件

它的核心职责是:作为 OpenList 与媒体库(Emby / Jellyfin)之间的协调中枢,打通 STRM 的"生成 -> 刮削消费 -> 重命名整理 -> 删除 -> 云端联动 -> 冗余回收"整条闭环链路,并智能处理字幕文件同步。在此过程中,提供极强的自我保护能力,防止手误或网络异常导致的数据灾难。


程序预览图


程序预览图


程序预览图


🌟 核心特性

  1. API 动态映射(告别死板配置) 启动时主动调用 OpenList Admin API 抓取所有 driver=strm 的存储节点,自动梳理本地路径与云端真实监控路径的分组映射,实现真正的云端配置对齐。

  2. 智能媒体类型识别与字幕同步 自动识别电影/番剧类型,电影字幕保持同目录结构,番剧字幕按 Season XX/S01E01.forced.zho.简体.ass 标准格式归档,与 STRM 文件协同同步到 B 区。

  3. 严防死守的血统鉴权(防越界/防脱群) 任何试图进入媒体库的文件必须接受溯源校验。严禁将番剧提取至引擎根目录,严禁跨库移动。对于单集的脱群改名,引入 30 秒观察期,一旦确认是非法越界操作,直接物理击杀,防止云端被误删。

  4. 优胜劣汰的单实例去重(防重复刮削) 同一个视频源只允许一个可见实例。内置打分器(标准刮削命名 S01E01 绝对优先 > 路径越短越好)。劣质命名会被自动重命名为 .duplicate 进行物理隔离,确保媒体库不仅无重复,且展示的永远是最优命名。

  5. B 区逆向自同步(启动自愈) 启动时先对 B 区进行全量底细盘点:物理磁盘 vs 数据库记录双向比对。发现离线拷入的新 STRM 直接入库;发现失效路径自动清理;发现改名文件自动追踪。确保数据库是物理磁盘的"真实投影"。

  6. 云端回收站智能重建 触发删除联动时,程序会截取云端真实目录结构,通过连续调用 API,在配置的回收站内一比一重建原文件夹树再执行移动,为后续的完美恢复提供退路。

  7. 被破坏文件自动恢复 如果媒体库中的 STRM 文件内容被意外清空或损坏,程序会逆向查库,并从源头自动将其恢复。


🗺️ 系统工作流与架构图

flowchart TD
    %% ================= 核心样式定义 =================
    classDef cloudNode fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px,color:#01579b,rx:8px,ry:8px;
    classDef apiNode fill:#e8eaf6,stroke:#3f51b5,stroke-width:2px,color:#1a237e,rx:8px,ry:8px;
    classDef coreNode fill:#e0f2f1,stroke:#009688,stroke-width:2px,color:#004d40,rx:8px,ry:8px;
    classDef decisionNode fill:#fff3e0,stroke:#ff9800,stroke-width:2px,color:#e65100;
    classDef dangerNode fill:#ffebee,stroke:#f44336,stroke-width:2px,color:#b71c1c,rx:8px,ry:8px;
    classDef dbNode fill:#f3e5f5,stroke:#9c27b0,stroke-width:2px,color:#4a148c;
    classDef areaNode fill:#f1f8e9,stroke:#689f38,stroke-width:2px,color:#33691e,rx:8px,ry:8px;
    classDef ghostNode fill:#fafafa,stroke:#9e9e9e,stroke-width:2px,color:#424242,stroke-dasharray: 5 5,rx:8px,ry:8px;
    classDef subtitleNode fill:#fff8e1, #ffc107,stroke-width:2px,color:#ff6f00,rx:8px,ry:8px;

    %% ================= 结构定义 =================
    subgraph Server [fa:fa-server OpenList / WebDAV 服务端]
        Cloud([fa:fa-cloud 云端真实物理文件]):::cloudNode
        API([fa:fa-plug Admin API / WebDAV 接口]):::apiNode
        Hook([fa:fa-sync 刷新搜索索引钩子]):::apiNode
    end

    subgraph AreaA [fa:fa-inbox A区 - 引擎输出层]
        A_DIR([fa:fa-folder OpenList STRM 自动生成]):::areaNode
        A_SUB([fa:fa-closed-captioning 字幕文件 .ass/.srt/.ssa]):::subtitleNode
    end

    subgraph Core [fa:fa-cogs 核心控制中枢 openlist_strm_bridge]
        Config([fa:fa-sitemap API抓取配置<br>解析 SaveStrmLocalPath]):::coreNode
        Auth{fa:fa-wifi 网盘可用性探活<br>Fail-Safe断路器}:::decisionNode
        Lineage{fa:fa-shield-alt 严格血统校验<br>层级边界与逃逸拦截}:::decisionNode
        Fingerprint([fa:fa-fingerprint 洗白内容<br>计算唯一指纹]):::coreNode
        Score{fa:fa-star 命名打分机制<br>防劣质原名回灌}:::decisionNode
        MediaType{fa:fa-film 媒体类型识别<br>电影/番剧分类}:::decisionNode
        Subtitle([fa:fa-closed-captioning 字幕智能处理<br>电影同目录 / 番剧Season归档]):::subtitleNode
        DB[(fa:fa-database SQLite 状态映射库)]:::dbNode
    end

    subgraph AreaB [fa:fa-play-circle B区 - 媒体库消费层]
        B_DIR([fa:fa-folder-open 用户整理 / 媒体库消费区]):::areaNode
        B_SUB([fa:fa-closed-captioning 标准化字幕<br>S01E01.forced.zho.简体.ass]):::subtitleNode
        B_DUP([fa:fa-ban 后缀隔离区 .duplicate / .invalid]):::dangerNode
    end

    subgraph AreaC [fa:fa-ghost C区 - 幽灵收容层]
        C_DIR([fa:fa-archive 根目录失效/挂载点丢失<br>整体迁移收容所]):::ghostNode
    end

    %% ================= 数据关系流转 =================
    
    Cloud -. OpenList引擎同步 .-> A_DIR
    A_DIR -->|1. 提取真实云路径| Fingerprint
    Fingerprint -->|2. 逆向层级追溯| Lineage
    
    Lineage -->|越界逃逸/脱群单个改名| 物理击毙([fa:fa-skull-crossbones 物理抹除非血统文件]):::dangerNode
    Lineage -->|血统通过| Score
    
    Score -->|B区已有更优重命名| 跳过([fa:fa-forward 跳过A区劣质原名]):::coreNode
    Score -->|指纹不存在于B区| DB
    DB -->|状态入库并复制| B_DIR
    
    B_DIR -->|用户改名/加深层级| Fingerprint
    B_DIR -->|用户删除 STRM| API
    
    API -->|调用 FS Mkdir 递归树 + Move| Cloud
    API -->|触发索引强制更新| Hook
    Hook -->|同步联动删除| A_DIR

    %% 字幕处理流程
    A_SUB -->|电影字幕| MediaType
    A_SUB -->|番剧字幕| MediaType
    MediaType -->|电影: 同目录复制| Subtitle
    MediaType -->|番剧: 提取季集| Subtitle
    Subtitle -->|标准化命名| B_SUB
    
    Auth -->|云盘掉线/网络异常| 阻断([fa:fa-lock 熔断清理 保护媒体库]):::dangerNode
    Auth -->|探活成功| 清理([fa:fa-broom 清理B区死链与空目录]):::coreNode
    清理 -->|大类路径从引擎移除| C_DIR
Loading

📂 目录模型说明 (A / B / C 三分区)

  • A 区 (生肉区):OpenList 引擎更新模式的输出目录。程序在此区提取 WebDAV 映射和建立身份指纹。同时监控同目录下的字幕文件(.ass.srt.ssa)。
  • B 区 (熟肉区):Emby / Jellyfin真正扫描的目录。用户在此区自由改名、整理、删除。程序将用户的操作翻译为云端 API 指令。字幕文件按媒体类型智能归档:电影字幕保持同目录,番剧字幕进入 Season XX/ 子目录。
  • C 区 (幽灵区):用于收容因为云盘根目录大改版、挂载点删除而导致的失效路径。保留历史痕迹,不污染媒体库,也避免直接蒸发导致找不回原文件。

🎬 字幕处理说明

程序自动识别并同步 A 区的字幕文件到 B 区,支持智能媒体类型判断:

媒体类型 检测方式 字幕目标路径 命名示例
电影 路径含"电影/movie"等关键词,或目录下仅1个STRM且无季集信息 与对应STRM同目录 电影名.forced.zho.简体.ass
番剧 路径含"番剧/anime"等关键词,或STRM/文件名可提取季集 Season XX/ 子目录 S01E01.forced.zho.简体.ass
  • 字幕语言自动识别:支持 .sc.chs.tc.cht 等后缀标识,以及"简中""繁体"等关键词
  • 多语种时简中优先标记 forced
  • 无法识别语言时回退为 .forced.und(undetermined)
  • 使用数据库 subtitles 表追踪处理状态,避免重复处理

⚙️ 配置文件

主要配置文件:

  • config.toml (主配置文件)

💡 路径配置:A 区目录、STRM 引擎入口、主动刷新路径等均已迁移至 WebUI 配置页维护,存储在数据库 webui_config 表中,无需 txt 文件。

配置说明

具体配置项及参数请参考项目内的注释文档。


🚀 部署与运行

1. 安装依赖

pip install -r requirements.txt

所需主要依赖:

  • watchdog (文件系统监控)
  • requests (API 请求交互)
  • lxml (WebDAV XML解析)
  • tomli (Python < 3.11 环境下需要)

如需运行测试:

pip install -r src/tests/requirements-dev.txt

开发依赖(详见 src/tests/requirements-dev.txt):

  • pytest (测试框架)
  • pytest-cov (测试覆盖率)
  • flask (测试用 Mock 服务器)

2. 运行程序

# 双击 嵌入式启动.bat(推荐,自带 Python 环境)
# 或 环境变量启动.bat(使用系统 Python)
# 或直接:
python src/webui/server.py

启动后访问 http://127.0.0.1:8579 即可使用 WebUI 管理面板。


📝 日志分级说明

默认日志文件输出至 strm_bridge.log(位于项目根目录),内置按大小截断轮转机制。

级别 用途
INFO 记录启动、API 握手成功、文件联动删除、清理等重要里程碑。
DEBUG 用于排查指纹计算、血统拦截细节、重命名追踪溯源、字幕处理等。
WARNING 可恢复的异常,如单兵脱群观察期、劣质文件隔离、字幕降级处理等。
ERROR API 联动失败、数据库写入失败等严重操作异常。

建议: 大媒体库正常服役时使用 INFO 级别即可保持日志清爽;排查同步问题时临时切换为 DEBUG

错误消息翻译

程序内置错误消息翻译工具,将技术性网络错误(如 ConnectionRefusedError 10061HTTPError 401 等)转换为普通用户能理解的中文描述。

示例对比:

原始错误 翻译后
ConnectionRefusedError: [WinError 10061] 由于其目标计算机主动拒绝,无法建立连接 登录失败 — 无法连接到服务器,请检查:1. OpenList 是否已启动 2. 地址和端口是否正确 3. 防火墙是否阻止了连接
HTTPError: 401 Client Error: Unauthorized 登录失败 — 认证失败,用户名或密码错误
ConnectTimeout: HTTPSConnectionPool... 登录失败 — 连接超时,服务器无响应

翻译覆盖的错误类型:

  • 连接拒绝 / 连接重置 / 连接中断
  • 超时(连接超时 / 读取超时)
  • DNS 解析失败
  • HTTP 状态码(400–504)
  • SSL 证书错误
  • 网络不可达 / 路由失败

开发者可在日志中附加 [技术详情: ...] 后缀用于调试,通过 format_error_for_log(error, context, include_technical=True) 控制。


🖥️ WebUI 管理面板

程序内置 WebUI 管理面板(默认地址 http://localhost:8579),提供可视化的运维管理功能。

功能概览

功能 说明
仪表盘 展示 A/B/C 区文件总数、各模块运行状态
A 区浏览 查看 STRM 引擎生成的原始目录结构,按子类/文件两级展开
B 区浏览 查看媒体库消费区目录,基本和A区一致 支持删除联动操作
C 区浏览 查看幽灵/隔离区内容,基本和A区一致
TMDB 待看列表 对接 TMDB API,展示用户待看列表并与本地已收录内容做对比
日志查看 实时查看程序运行日志,支持 TMDB/主程序日志切换
壁纸 内置水墨风遮罩壁纸效果

TMDB 待看列表

  • 通过 TMDB API 获取用户的待看列表(watchlist)
  • 与本地 STRM 已收录内容自动对比,标记"已收录"或"待下载"
  • 支持自动同步番剧季节数(season_count),卡片以竖杠标识多季番剧
  • 待看数据缓存至本地文件,避免重复 API 调用
  • 配置项通过 WebUI 面板 → TMDB 设置修改,保存至 tmdb_watchlist.db 数据库的 webui_config 表(scope=tmdb

多季番剧标识

多季番剧在 TMDB 待看列表中会以 竖杠 (|) 标识,同时在信息卡片中显示大概的季节数,方便快速判断番剧是否多季节。

访问地址与配置

配置项 默认值 说明
[webui] port 8579 监听端口
[webui] bind 0.0.0.0 监听地址(仅本地和局域网)
access_token TMDB API 访问令牌(通过 WebUI 配置页填写,存储在 tmdb_watchlist.db

🔐 登录密码:WebUI 访问需要管理员密码。首次启动 WebUI 时,程序会自动生成一个随机密码,并仅打印一次到控制台(不写入日志文件),请务必记下;之后再次启动不会再显示该密码。密码以 PBKDF2-HMAC-SHA256 加盐哈希后存储在 tmdb_watchlist.dbwebui_config 表(scope='ui'key='admin_password'),明文不落盘。

忘记密码 / 自定义密码:运行项目根目录的 reset_admin.py

  • python reset_admin.py —— 生成随机新密码并打印。
  • python reset_admin.py 我的密码 —— 在脚本后手动输入自定义密码(支持含空格的密码,最少 4 个字符),即可把登录密码改成你想要的值。
  • 脚本直接写入数据库,登录验证实时读取,无需重启 WebUI 即可用新密码登录。

🔍 中文搜索与分词

程序的中文媒体名搜索依赖 SQLite FTS5 + simple 分词器。simple 是 wangfenjin/simple 项目的 Windows x64 构建,底层封装了 cppjieba 中文分词,当前内置版本为 v0.7.1,资源统一放在 src/tokenizers/simple/ 目录(含 simple.dllVERSIONREADME.md,版本与接入说明见该目录的 README.md)。

  • 加载机制database.py 中的 _load_simple_tokenizertmdb_watchlist_db.py 中的 _load_simple_into 在建立 SQLite 连接时通过 load_extension 加载 simple.dll
  • 软降级:若 simple.dll 缺失或加载失败,程序不会中断,而是降级为 SQLite 内置的 unicode61 分词器(仅记录 WARNING 日志)。
  • 硬依赖提醒unicode61 不对中文产生 token,因此一旦 simple.dll 缺失,中文搜索实际上会完全失效(降级后的搜索对中文名返回空结果)。在中文媒体库场景下,simple 是中文搜索的硬依赖,部署时务必确保 src/tokenizers/simple/simple.dll 存在。
  • 区域搜索中的中文:区域搜索接口(GET /api/area/{area}?q=)走 FTS5,查询串经过 _escape_fts5_query 转义后执行;番剧/电影分类通过 kind 参数(anime / movie / other / all)实现。媒体详情接口(GET /api/area/{area}/detail?media=)数据量小、要求精准匹配,因此走 LIKE 而非 FTS5。

🧭 新手引导(Onboarding)

程序内置 7 步新手引导,登录后在仪表盘自动展示,帮助首次使用的用户按顺序完成关键配置:

  1. 确认管理员密码(首次启动时已自动生成并打印到控制台,遗忘或需自定义请用 reset_admin.py
  2. 配置 TMDB
  3. 配置 OpenList
  4. 启动主程序
  5. 查看 A/B 分区
  6. 刷新 TMDB 待看列表
  7. 检测 TMDB 收录状态
  • 状态存储:引导状态保存在 tmdb_watchlist.dbwebui_config 表(scope=ui,如 onboarding_completed 等键)。前端步骤定义在 dashboard.jssteps 数组中。
  • 单步完成POST /api/onboarding/complete-step 标记某一步完成。
  • 整体完成 / 跳过:通过 POST /api/webui/config/ui 写入 { onboarding_completed: '1' } 标记引导已完成或已跳过。

⚠️ 使用建议与注意事项

详细的使用建议、安全注意事项与最佳实践请参看项目 Wiki:


📄 License

本项目采用 MIT License 协议。

About

删除openlist strm时,对其对应的源文件执行删除 或带原路径移动到挂载根目录回收站

Topics

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages