cubesugar-music
by fangtang0206
README.md
# 🎵 方糖音乐 (cubesugar-music)
> **NAS 音乐库下载壳子** — 配合 Navidrome / Plex / Emby / Subsonic 等自托管音乐服务器,**提供壳子 + 多源聚合 + MCP 协议**,让你下载的歌曲能正确写 ID3 tag、被播放器识别。



---
## ⚠️ 重要声明
**本项目不内置任何插件。** 支持 LX Music 插件格式(用户自行导入)。
- 用户需要**自行**通过 Web UI 导入或下载到 `sources/` 目录
- 本项目仅提供:
- ✅ 壳子(HTTP API + 数据库 + 队列)
- ✅ 多源聚合上游
- ✅ MCP 协议(供 AI Agent 接入)
- ✅ ID3 tag 写入(Navidrome 识别用)
- ✅ 批量歌单导入
- ✅ 4 层防重
- ✅ 本地音乐库索引
- ❌ **不内置**任何音源插件
---
## ✨ 核心功能
| 功能 | 说明 |
|---|---|
| 🔍 **跨源搜歌** | 默认走多源聚合 |
| 📦 **插件支持** | 支持 LX Music 插件格式,通过 Web UI 导入(Node VM 沙箱执行)|
| 📥 **下载队列** | 2 worker 后台线程,原子抢任务,音质降级(hires → master → atmos → flac24bit → flac → 320k → 128k) |
| 🎵 **批量歌单** | 歌单 URL 一键导入,自动跳过已下载/已存在任务 |
| 🚫 **4 层防重** | ①文件系统 ②数据库精确 ③文件模糊匹配 ④本地索引库 |
| 🎼 **本地索引** | 自动扫描下载目录建立索引,存 PostgreSQL,跨源跨 ID 查重 |
| 🆔 **ID3 tag** | 下载后自动写 FLAC Vorbis Comments / MP3 ID3v2 — Navidrome/Plex 直接识别 |
| 🔌 **MCP 服务** | 10 个工具供 Claude / Hermes / Cursor 等 AI Agent 接入(SSE 模式 + 30 天 token) |
| 🌙 **主题切换** | 白天/夜间双主题,CSS 变量切换,localStorage 持久化 |
| 🔑 **用户管理** | JWT 鉴权、修改密码、退出登录 |
---
## 🏗️ 架构
```
┌─ Web UI (浏览器/外网)
│ ├─ 登录页 (admin/admin)
│ ├─ 搜索/下载/任务
│ └─ 设置 (插件导入 / 音乐库 / MCP / 服务状态)
│
├─ MCP (Claude/Hermes AI Agent 接入, SSE 模式)
│
├─ REST API (8765 端口)
│
└─ Backend
├─ upstream 多源聚合 (公开 API)
├─ PostgreSQL 5 表
├─ Worker threads × 2 (原子抢任务, FOR UPDATE SKIP LOCKED)
├─ 插件 (Node VM 沙箱) — 需用户自行导入
└─ 4 层防重 + ID3 tag 自动写入
```
---
## 🚀 快速开始
### 方式 1: Docker Compose (推荐)
```bash
git clone https://github.com/fangtang0206/cubesugar-music.git
cd cubesugar-music
# 配置环境变量 (可选)
export PG_PASSWORD=your_secure_password
export JWT_SECRET=$(openssl rand -hex 32)
export DOWNLOAD_DIR=/path/to/music # 默认 ./music
# 启动 (首次会构建镜像,需要 2-3 分钟)
docker compose up -d
# 等 30 秒让服务起来
curl http://127.0.0.1:8765/api/health
# → {"status": "ok", "runtime": "ok", "upstream": "ok", "postgres": "ok", "worker_running": true}
# 打开 Web UI
open http://127.0.0.1:8765/
# 默认账号: admin / admin (登录后立即修改!)
```
**端口占用**:
- 8765 (Web UI + API)
- 8098 (Node runtime)
- 54321 (PostgreSQL)
如有冲突,修改 `docker-compose.yml` 里的端口映射。
### 方式 2: 直接运行 (开发)
```bash
# 1. 准备 PostgreSQL
psql -U root -c "CREATE DATABASE fangtang_music"
# 2. 安装依赖
pip3 install mutagen psycopg[binary]
# 3. 启动 Node runtime
cd node-runtime && node server.js &
# 4. 启动主服务
cd app && python3 server.py
```
---
## 🔌 导入 LX Music 插件
**项目不内置任何插件**。支持 LX Music 插件格式(.js 文件),首次使用需要导入:
### 通过 Web UI
1. 登录 → 右上角 ⚙ 设置 → 📦 插件 tab
2. 填入插件 JS 文件的 URL → 点击"URL 导入"
3. 或下载到本地 → 用"文件上传"按钮
### 通过文件系统
```bash
# 复制插件 JS 到 sources 目录
cp /path/to/plugin.js /vol1/1000/docker/cubesugar-music/sources/
# 重启服务 (或点 Web UI 刷新)
```
### 推荐插件来源
**重要提醒**:
- 插件上游 API 可能失效(Cloudflare 拦截、KEY 过期等),**多源聚合已能覆盖大部分歌曲**
- 导入前请检查插件代码安全性
---
## ⚙️ 配置
### 环境变量
| 变量 | 默认值 | 说明 |
|---|---|---|
| `PORT` | `8765` | HTTP 服务端口 |
| `DOWNLOAD_DIR` | `/vol2/1000/music` | 下载目录(host 路径, **必须可写**) |
| `SOURCES_DIR` | `/vol1/1000/docker/cubesugar-music/sources` | 插件目录(**需用户填充**) |
| `WORKER_COUNT` | `2` | 后台 worker 线程数 |
| `MAX_RETRIES` | `3` | 任务最大重试次数 |
| `PG_HOST` | `127.0.0.1` | PostgreSQL 主机 |
| `PG_PORT` | `54321` | PostgreSQL 端口 |
| `PG_USER` | `root` | 数据库用户 |
| `PG_PASSWORD` | `cubeSugar_postgresql` | 数据库密码 |
| `PG_DATABASE` | `fangtang_music` | 数据库名 |
| `LX_RUNTIME_BASE` | `http://127.0.0.1:8098` | Node runtime 地址 |
| `SELF_BASE_URL` | `http://127.0.0.1:8765` | MCP 内部调用自己时用 |
| `JWT_SECRET` | `cubesugar-music-default-secret-change-me` | **生产环境必须改** |
---
## 📚 API 文档
### 鉴权
```bash
# 登录
curl -X POST http://127.0.0.1:8765/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"admin"}'
# → {"token": "eyJhbG...","user": {...}}
# 后续请求带 token
curl http://127.0.0.1:8765/api/tasks/list \
-H "Authorization: Bearer eyJhbG..."
# 签发 MCP 长 token (30 天)
curl -X POST http://127.0.0.1:8765/api/auth/mcp-token \
-H "Authorization: Bearer <token>"
# → {"token": "eyJhbG...","expires_in": 2592000}
```
### 搜歌
```bash
# 默认跨源
curl "http://127.0.0.1:8765/search?name=海阔天空&artist=Beyond" \
-H "Authorization: Bearer <token>"
# 指定源
curl "http://127.0.0.1:8765/search?name=test&source=tx"
```
### 提交下载任务
```bash
curl -X POST http://127.0.0.1:8765/api/tasks/submit \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"source": "wy",
"song_id": "1357375695",
"name": "海阔天空",
"artist": "Beyond",
"quality": "flac"
}'
```
### 批量歌单导入
```bash
curl -X POST http://127.0.0.1:8765/api/playlist/import \
-H "Authorization: Bearer <token>" \
-H "Content-Type: application/json" \
-d '{
"url": "https://music.163.com/playlist?id=12345",
"quality": "flac"
}'
```
### 任务列表 (分页 + 状态筛选)
```bash
# 默认 status=all, page=1, page_size=20
curl "http://127.0.0.1:8765/api/tasks/list" -H "Authorization: Bearer <token>"
# 筛选下载成功 / 失败 / 待重试
curl "http://127.0.0.1:8765/api/tasks/list?status=success" -H "Authorization: Bearer <token>"
curl "http://127.0.0.1:8765/api/tasks/list?status=error" -H "Authorization: Bearer <token>"
curl "http://127.0.0.1:8765/api/tasks/list?status=retry" -H "Authorization: Bearer <token>"
# 分页
curl "http://127.0.0.1:8765/api/tasks/list?page=2&page_size=50" -H "Authorization: Bearer <token>"
```
### 本地音乐库索引
```bash
# 状态
curl "http://127.0.0.1:8765/api/library/stats" -H "Authorization: Bearer <token>"
# 查询
curl "http://127.0.0.1:8765/api/library/lookup?artist=Beyond&name=海阔天空" \
-H "Authorization: Bearer <token>"
# 触发重建 (后台 4-5 分钟)
curl -X POST "http://127.0.0.1:8765/api/library/rescan" -H "Authorization: Bearer <token>"
```
---
## 🔌 MCP 集成 (Claude / Hermes / Cursor)
```yaml
# ~/.hermes/config.yaml 或 ~/.claude/mcp_servers.json
mcp_servers:
cubesugar-music:
url: http://your-host:8765/mcp/sse
transport: sse
headers:
Authorization: Bearer <从 /api/auth/mcp-token 拿到的 30 天 token>
X-Client: hermes-agent
```
### MCP 工具列表 (10 个)
| 工具 | 说明 |
|---|---|
| `search_music` | 搜歌 (跨源) |
| `download_music` | 下载单首到队列 |
| `import_playlist` | 批量导入歌单 |
| `list_tasks` | 列下载任务 (带状态过滤) |
| `get_task_status` | 单个任务状态 |
| `list_plugins` | 列插件 |
| `import_plugin` | 导入插件 (URL/文件) |
| `library_stats` | 本地音乐库统计 |
| `library_lookup` | 查本地库 (artist + name) |
| `library_rescan` | 触发重建索引 |
---
## 🗂️ 数据库结构
| 表 | 说明 |
|---|---|
| `users` | 用户 (默认 admin) |
| `plugins` | 插件元数据 |
| `download_tasks` | 下载任务 (status: pending/processing/success/error) |
| `local_library` | 本地音乐索引 (artist, name, album, file_path) |
| `index_status` | 索引状态 (最后扫描时间) |
---
## 🛠️ 开发
### 项目结构
```
cubesugar-music/
├── app/ # Python 主服务
│ ├── server.py # 主 HTTP 路由
│ ├── fetcher.py # 多源聚合上游
│ ├── library_index.py # 本地音乐库扫描器 + 索引查重
│ ├── id3_writer.py # FLAC/MP3 ID3 tag 写入
│ ├── worker/worker.py # 后台下歌 worker
│ ├── auth/ # JWT + PBKDF2 密码哈希
│ ├── db/ # PostgreSQL 连接池
│ ├── api/mcp.py # MCP server (SSE)
│ └── playlist.py # 歌单 URL 解析
├── node-runtime/ # Node 沙箱 (8098 端口)
├── sources/ # (空) 插件目录 - 用户自行填充
├── docker-compose.yml # 一键启动
├── Dockerfile
└── README.md
```
### 启动开发模式
```bash
# 1. 启动 postgres
docker run -d --name cubesugar-db \
-e POSTGRES_PASSWORD=cubeSugar_postgresql \
-e POSTGRES_DB=fangtang_music \
-p 54321:5432 \
postgres:15-alpine
# 2. 启动 runtime
cd node-runtime && node server.js &
# 3. 启动主服务
cd app
PORT=8765 DOWNLOAD_DIR=/vol2/1000/music \
PG_HOST=127.0.0.1 PG_PORT=54321 \
python3 server.py
```
---
## 🐛 常见问题
### Q: 启动后 runtime 提示 500 / plugins 列表空?
A: 项目不内置插件。需要手动导入 LX Music 格式的 .js 插件文件。
A: 正常, 仓内**不内置插件**, `sources/` 目录是空的, 需要自己导入插件 JS 文件。
### Q: 搜歌结果显示的歌曲都搜不到?
A: 多源聚合已能覆盖大部分歌曲。如果某首歌搜不到, 检查:
- upstream 是否在线 (`/api/health` 状态)
- 插件是否需要登录账号
- 公开 API 限流 (稍等再试)
### Q: 下载的文件 Navidrome/Plex 显示 "Unknown Artist"?
A: 老文件没 ID3 tag, 手动补:
```bash
cd app/
python3 -c "from id3_writer import backfill_existing_files; print(backfill_existing_files('/vol2/1000/music'))"
```
会扫所有 FLAC/MP3, 从数据库元数据 + 文件路径回推写入 ID3 (4199 首约 30 分钟)
### Q: 怎么给 Navidrome/Plex 用?
A: 两者都扫 `/vol2/1000/music/` 目录:
- 库目录指向 `/vol2/1000/music/`
- 启用"扫描 ID3 tag"选项
- 几分钟后歌名/歌手/专辑全部就位
---
## 📜 License
MIT License - 见 [LICENSE](LICENSE) 文件
---
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues