Tidal-Navidrome-Bridge 是一个基于 FastAPI 和 Asyncio 构建的中间件。它通过劫持并重写 Subsonic API 协议,将 Navidrome 与 Tidal 无缝聚合为一个统一的音乐服务节点,并实现了基于前缀 (nav_ 或 tidal_) 的智能路由分发系统。
-
⚡ 异步高并发与智能路由 (Async IO & Smart Routing) 混合数据源聚合: 利用 asyncio.gather 并行请求 Navidrome API 和 Tidal API。搜索请求 (search3) 会同时分发给两端,并在内存中进行去重和排序合并。 ID 前缀命名空间: 本地资源: 自动识别并注入 nav_ 前缀,请求透明转发至 Navidrome 后端。 在线资源: 识别 tidal_ 前缀,直接调用 Tidal API 获取元数据。 连接池管理: 使用 httpx.AsyncClient 配合 Limits(max_keepalive_connections=20) 进行连接复用,并使用 asyncio.Semaphore 限制 Tidal 并发请求数,防止触发 429 Rate Limit。
-
🛡️ 独立的 Token 守护进程 进程隔离: 主服务 (main.py) 启动时,会自动生成一个子进程运行 login.py --daemon。 自动保活: 守护进程每 30 分钟轮询 SQLite 数据库 (app.db),检查 tidal_expiry_time。一旦发现 Token 即将过期(< 1小时),自动调用 Refresh Token 接口刷新,并将新 Token 写回数据库,确保主服务读取到的永远是有效凭证。 WebSocket 交互式登录: 前端 (index.html) 通过 WebSocket 连接 /ws/run-login,实时流式传输 login.py 的标准输出 (stdout),实现了在 Web 页面上完成命令行式的 Device Flow 授权。
-
🔐 自定义鉴权与配置体系 双层鉴权: App 层: 兼容 Subsonic 的 u (username), t (token), s (salt) 签名验证。 Admin 层: 基于 python-jose 的 JWT 鉴权,用于保护 Web 管理面板。 文本化配置数据库: 支持特殊的 config.txt 格式,采用 ------------- 分隔符: 上半部分: username:password 键值对。 下半部分: 允许注册的 邀请码 (Invite Codes) 列表。 代码特性: 每次登录或注册时,自动同步文件配置至 SQLite 数据库。
-
🎵 增强的媒体处理 歌词代理: getLyrics 接口被重写,即使是 Navidrome 的歌曲,也能尝试通过元数据匹配从 Tidal 获取同步歌词。 无损流媒体: 拦截 stream 接口,针对 Tidal 资源请求 playbackinfopostpaywall,解析 LossLess的 FLAC 直链并返回 302 Redirect,大幅降低中间件带宽消耗。 封面图即时代理: 实现了 getCoverArt 路由,支持从 Tidal CDN 获取不同尺寸 (320x320, 1280x1280) 的封面(自行修改),或回源至 Navidrome。 其中专辑名和歌手名前面带 (T) 的为Tidal资源。
-
🖥️ 📱 客户端推荐
- 🖥️ 电脑端:
- feishin 飞信版本1.2.0: https://github.com/jeffvli/feishin
- 音流: https://music.aqzscn.cn/
- 箭头音乐: https://www.amcfy.com
- Python 3.10+
pip install -r requirements.txt --break-system-packages
- 在根目录创建 config.txt,格式必须严格遵守以下规范
- 初始账号密码与邀请码:
admin:admin
-------------
huduiwqey37674632
python3 main.py
- 电脑端:
- Feishin版本1.2.0 -> navidrome & subsonic 协议
- 手机端:
- Ds Cloud -> navidrome 协议
- 箭头音乐 -> navidrome & subsonic 协议
- 音流 -> subsonic 协议
- 服务器: http://ip:8000 或 https://ip:8000
- 账号密码为登录管理页面的账号密码
graph TD
%% 定义样式
classDef base fill:#1f2937,stroke:#4b5563,color:#e5e7eb,stroke-width:2px;
classDef tidal fill:#172554,stroke:#3b82f6,color:#dbeafe,stroke-width:2px;
classDef nav fill:#14532d,stroke:#22c55e,color:#dcfce7,stroke-width:2px;
classDef decision fill:#451a03,stroke:#f59e0b,color:#fef3c7,stroke-width:2px;
classDef success fill:#064e3b,stroke:#10b981,color:white,stroke-width:2px;
classDef action fill:#4c1d95,stroke:#8b5cf6,color:#ede9fe,stroke-width:2px;
%% 节点定义
Start("客户端请求 <br/>🖥️:Feishin <br/> 📱:Ds Cloud <br/> 📱:箭头音乐<br/>📱:音流 APP "):::base
Router{"请求类型 <br/> API 路由分发"}:::decision
%% --- 搜索流程 ---
SearchNode["搜索请求 <br/> search3.view"]:::action
ParNode("并行执行任务"):::base
TidalSearch["调用 Tidal API"]:::tidal
NavSearch["调用 Navidrome API"]:::nav
MergeNode["数据聚合 <br/> 合并、排序"]:::action
RespXML["返回 Subsonic XML"]:::success
%% --- 播放流程 ---
PlayNode["播放请求 <br/> stream.view"]:::action
IDCheck{"检查 ID 前缀"}:::decision
%% Tidal 分支
TidalPath["ID: tidal_xxx"]:::tidal
GetToken["从数据库获取有效 Token "]:::tidal
GetManifest["获取播放数据"]:::tidal
RedirectTidal["HTTP 302 重定向 <br/> 目标: Tidal CDN"]:::success
%% Navidrome 分支 (已修正)
NavPath["ID: nav_xxx"]:::nav
GenSignedURL["生成签名 URL"]:::nav
RedirectNav["HTTP 302 重定向 <br/> 目标: Navidrome 服务器"]:::success
%% 连接线关系
Start --> Router
%% 搜索逻辑
Router --"search3"--> SearchNode
SearchNode --> ParNode
ParNode --> TidalSearch
ParNode --> NavSearch
TidalSearch --> MergeNode
NavSearch --> MergeNode
MergeNode --> RespXML
%% 播放逻辑
Router --"stream"--> PlayNode
PlayNode --> IDCheck
%% Tidal 路径
IDCheck --"前缀: tidal_"--> TidalPath
TidalPath --> GetToken
GetToken --> GetManifest
GetManifest --> RedirectTidal
RedirectTidal -.->|直连 Tidal 服务器| Client1["拉取 Tidal 音频流"]:::base
%% Navidrome 路径
IDCheck --"前缀: nav_"--> NavPath
NavPath --> GenSignedURL
GenSignedURL --> RedirectNav
RedirectNav -.->|直连 Navidrome 服务器| Client2["拉取 Navidrome 音频流"]:::base