suno-cache
把一个 Suno 分享链接的试听音频,离线保存成 320kbps MP3。
纯 Python 实现,整条链路(解析 → 取权 → 下载 → 解密 → 转码)都在用户本机完成,
不需要任何客户端,也不需要用户自己安装 ffmpeg(ffmpeg 由 imageio-ffmpeg 自带静态二进制)。
定位 Skill 根目录
下文命令里的 <SKILL_DIR> 指本 SKILL.md 所在目录,不是当前工作目录。
宿主已提供 Skill 目录时直接用;需要自行定位时:
SKILL_DIR="$(find "$HOME" -maxdepth 4 -type d -name 'suno-cache' 2>/dev/null | head -1)"
不要用相对路径直接调 scripts/suno_cache.py——调用方的工作目录通常不是 Skill 根目录。
前置条件
- 系统有 Python 3.9+(
python3 --version可查)。 - 能访问
suno.com与其 CDN。 - 首次运行若缺依赖,会联网从 PyPI 安装
cryptography==50.0.1与imageio-ffmpeg==0.6.0(约 50MB,仅一次)。 - 若所在网络无法直连 Suno,请设置
HTTPS_PROXY,或用--proxy显式指定。
环境检查(先查,再决定怎么走)
python3 "<SKILL_DIR>/scripts/check_environment.py"
只读检查,不安装、不登录、不改配置。看输出 JSON 的 status:
| status | 含义 | 下一步 |
|---|---|---|
ready |
运行时与依赖齐全 | 跳过所有安装说明,直接执行 |
partial |
核心可用,部分能力受限 | 照常执行,并说明受限项与降级范围 |
needs_setup |
缺 Python 版本或必需依赖 | 只说明缺失项,按 references/setup-guide.md 补齐后重检 |
unavailable |
有依赖但不可用(版本不兼容等) | 按 hint 恢复;无法恢复时停止并说明 |
已经就绪的项不要重复讲解安装步骤。
执行
# 默认保存到「当前工作目录」——即用户打开的项目目录,不是 Skill 安装目录
python3 "<SKILL_DIR>/scripts/suno_cache.py" "<link>"
# 指定输出目录 / 保留中间 fMP4 / 指定代理 / 禁止自动装依赖
python3 "<SKILL_DIR>/scripts/suno_cache.py" "<link>" --out "<目录>" --keep-fmp4
python3 "<SKILL_DIR>/scripts/suno_cache.py" "<link>" --proxy http://127.0.0.1:7890
python3 "<SKILL_DIR>/scripts/suno_cache.py" "<link>" --no-bootstrap
常用参数:--out 输出目录(缺省为当前工作目录,可用 SUNO_CACHE_OUT 覆盖)、
--proxy / --no-proxy 代理控制、--overwrite 覆盖同名文件、
--retries N 瞬时故障重试次数(默认 2)、--timeout N 单次请求超时、
--ffmpeg PATH 指定已有 ffmpeg、--quiet 静默进度。
输出约定:进度与诊断走 stderr,最终结果 JSON 走 stdout,可直接解析。
成功与失败使用同一套字段,因此解析逻辑只需一份。
Agent 工作流
- 从用户消息里取出 Suno 链接(可能夹在自然语言里);没给链接就先要链接,不要猜。
- 用户没指定目录就落在当前工作目录(用户打开的项目目录);执行后把
mp3_path的绝对路径告知用户。 - 跑一次
check_environment.py:needs_setup且用户不允许自动安装时不要绕过,改为如实报告。 - 执行脚本,解析 stdout JSON:
-ok=true→ 回报「标题 / 艺术家 / 时长 / 码率 / 大小 / 落盘路径」。
-ok=false→ 按code分类处理(见下表),必要时把hint转述给用户。 - 多个链接 → 一次一个顺序执行,不要并发、不要批量。
会改变本机状态的两处操作
| 操作 | 触发条件 | 已内置的约束 |
|---|---|---|
| 联网安装依赖 | 依赖缺失且未传 --no-bootstrap |
执行前把目标 venv、包名、锁定版本、联网行为打到 stderr;版本已锁定,安装可复现 |
| 写入 MP3 | 每次成功下载 | 同名文件默认不覆盖,追加 (2) 序号;只有 --overwrite 才替换,并在结果里回报 replaced_existing |
受限环境(无外网、沙箱、企业网络)请显式传 --no-bootstrap:缺依赖时脚本输出
code=setup_required 并退出,不会尝试联网,也不会写任何文件。
结果字段(stdout JSON)
| 字段 | 含义 |
|---|---|
ok |
是否成功 |
content_id / title / artist |
歌曲标识与元信息 |
mp3_path / mp3_size |
产物绝对路径与字节数 |
duration_sec / bitrate_kbps |
时长与码率 |
ctr_key_bits |
会话密钥位数(用于核对协议,正常为 128) |
encrypted_size |
加密流字节数 |
retried |
实际发生的重试次数 |
replaced_existing |
是否替换了已有同名文件 |
error / code / hint |
失败时的原因、分类与建议 |
验收标准:ok=true 且 mp3_path 指向的文件存在、duration_sec > 0。任一项不满足即视为失败。
错误分类与处置
| code | 含义 | 处置建议 |
|---|---|---|
link_invalid |
链接格式不对 | 请用户给 suno.com/s/...、suno.com/song/<uuid> 或裸 UUID;不要重试 |
network_unreachable |
连不上 Suno | 已自动重试;仍失败则确认网络/代理,用 --proxy 或 HTTPS_PROXY |
share_expired |
分享短链失效/被删 | 请用户在 Suno 页面确认链接仍可打开;不要重试 |
rights_failed |
取播放授权失败(可能风控) | 等待片刻后重跑一次;持续失败则换链接,不要高频重试 |
download_failed |
CDN 流下载失败 | 已自动重试;仍失败可稍后再试 |
decrypt_failed |
解密失败(协议可能变更) | 参考 references/protocol.md 的「改动定位表」更新实现;不要重试 |
transcode_failed |
ffmpeg 转码失败 | 检查磁盘空间;可用 --ffmpeg 指定系统 ffmpeg,或删除缓存 venv 让工具重建 |
output_conflict |
同名文件过多,无法避让 | 换 --out 目录,或用 --overwrite |
output_not_writable |
输出目录不可写或无法创建 | 用 --out 指定可写目录,或设置 SUNO_CACHE_OUT |
setup_required |
缺依赖且已禁止自动安装 | 按 references/setup-guide.md 补齐依赖,或允许联网安装 |
setup_failed |
自动安装依赖失败 | 检查能否访问 PyPI;或按 references/setup-guide.md 手工安装后重试 |
unexpected |
未预期错误 | 附上 error 与调用命令复现;不要重试 |
重试策略:只有 network_unreachable 与 download_failed 会自动重试(默认 2 次,指数退避)。
其余错误码重试不会改变结果,应按处置建议行动或如实报告。
边界
- 一次一个链接:顺序处理,不做批量抓取,不转售,不做二次分发。
- 仅用于个人试听缓存,仅处理用户有权访问的公开分享链接。
- 不绕过登录、付费墙或平台规则;不提供无损原始音轨,产物固定为 320kbps MP3。
- 不适用于:非 Suno 平台的音乐下载、需要登录才能访问的私有歌曲、歌单批量下载、
要求去除水印或用于商业分发。
已知的坑(改动前必读)
- CDN 域名末尾是
o3(不是o3g),域名变更时先核对这里。 browser-token用标准 base64 字母表且去掉 padding(不是 urlsafe),改错会导致请求被拒。- 解密出来是 Opus-in-MP4(
ftypisom),必须经 ffmpeg 才能得到通用 MP3。 - 会话密钥实测为 128-bit(AES-128-CTR),
ctr_iv恒 16 字节;代码同时兼容 256-bit。 - rights 接口是全流程唯一的 POST,body 与 headers 的位置极易传错,实现里用 keyword-only 强制隔离。
协议细节、字段长度与排障清单见 references/protocol.md;
依赖安装、代理配置与缓存目录清理见 references/setup-guide.md。