Spotify 里的动画同步歌词 —— Xposed / LSPosed 模块(简体中文增强版)
把 Spotify 自带的歌词界面换成更好用的一个:多个来源并发搜索、逐字歌词、翻译、罗马音、全屏渲染、桌面歌词、Android Auto 歌词。
非官方项目,与 Spotify 及任何歌词服务商均无关联。
Spotify++ 是一个运行在 Spotify 进程内的歌词模块,基于 Spicy EX(作者 amarinne)修改而来,主要面向中文用户。它接管 Spotify 的歌词界面:同时向多个歌词来源发起查询,按一套打分规则挑出最合适的一份,然后渲染出来 —— 逐字或逐词的时间轴、翻译、罗马音,以及全屏、桌面、Android Auto 等显示方式。
选哪一份歌词是这个模块的重点。Spotify 自带的歌词只有一份,中文常常是繁体、行级时间轴;同一首歌在别的来源上可能有简体、逐字、带翻译的版本。模块不预先规定哪个来源最好,而是把每个候选都量一遍,再比总分。
本仓库是修改版,不是原项目。 上游版权归原作者所有,本仓库的修改部分同样以 AGPL-3.0 发布 —— 详见 LICENSE、NOTICE 与 MODIFICATIONS.md。
- 布局可以实时调整 —— 在编辑器里改布局当场生效,不用关掉重开;每个元素都能单独隐藏或显示
- 歌词显示文字(可选,默认「不转换」)—— 可以把中文歌词在显示时统一按简体呈现,按词组判断,所以「頭髮」和「出發」各自正确;默认关闭,不设置的话不会有任何变化
- 全新的歌词排序算法 —— 逐字优先、简体优先、翻译越全越好、时长越贴合越好、语言与歌曲一致,综合打分选出最合适的一份;规则见下一节
- 自动选择会问遍所有来源 —— 不再问到一个就停下;后续来源返回时会重新比较,谁的分高用谁的
- 新增一个可选的机器翻译接口 —— 在设置里填写自己的 AppID 与密钥即可启用;默认仍是不需要账号的在线翻译
- 恢复备份后会提示哪些密钥需要重新填写 —— 凭据在本机加密保存,换了手机或重装之后解不开,恢复时会逐项告诉你
- 悬浮窗歌词在 Spotify 9.1.90 之后弹出来立刻消失
- Spotify 关掉宿主窗口时,悬浮窗被一起关掉
- AI 翻译被打断后,已经保存的结果被当成完整结果,歌词少了几行
- AI 翻译重试时丢掉已经付过费的结果,重复计费
- 歌词渐变和字间距不够平滑
- 歌词位置显示成 Spotify 内部的界面名称,而不是歌词;已经存进缓存的这类内容也会被读出来
- 中文歌曲的歌词翻译被整体丢掉、一句都翻不出来
- 歌词一直卡在「翻译中」,界面上什么都点不动
- 翻译失败时没有任何提示
- 重复出现的歌词行(比如副歌反复唱的那几句)拿不到翻译
- 需要密钥的那个来源一直选不上、也搜不到歌词
- 来源列表里取不到歌词的来源看起来能用,点下去却没有反应
- 恢复备份会覆盖掉本机已经能用的密钥
- 跨来源歌词混合已取消 —— 它把一份歌词的时间轴和另一份的文本拼在一起,行数对不上就会错位,看着没问题但唱着唱着就偏了
- 日文和韩文界面的部分文案补齐了,之前这些地方显示的是英文
- 歌词下面不再显示「歌词来源」那一行
一轮选择的输入,是已启用来源为这一首歌返回的全部候选。每个候选带着自己的信息:时间轴级别、歌词文本、翻译覆盖、文档长度、匹配方式等。规则分两步:先排除“根本不是这首歌”的文档,再给活下来的文档打分,总分最高的当选。
如果一份文档的行一直排到录音结束之后 10 秒以上,它就不是这一版录音的歌词,直接从本轮排除。
这个判断是单向的:提前结束不算问题。尾奏、淡出、器乐收尾都会让歌词比录音短,这是正常现象,不扣分也不排除。
满分 20 分,七项相加。每一项都从候选本身读出来,只有时长一项要看本轮的全部候选(下面说明)。
| 分量 | 满分 | 在看什么 | 为什么看它 |
|---|---|---|---|
| 时间轴精度 | +4 | 行内推进(逐字、逐词)得满分;逐行 +2;没有时间轴 0 | 高亮要贴着唱。行内时间点让高亮一个字一个字走过去,逐行只能整行跳。中文歌词的来源通常一个字给一个时间点 |
| 简体程度 | +5 | 先判断文档是不是中文,再按「简体专用字 ÷(简体专用字 + 繁体专用字)」分档:95% 以上满分,然后 80%、60%、40%、20% 各退一档 | 同一首歌的简体版和繁体版是两个文档,而读者读的是字。整篇没有繁简差异字的文档(例如「我今天很好」)按满分算 —— 里面本来就没有繁体字 |
| 翻译覆盖 | +2 | 带来源自带翻译的行 ÷ 可以翻译的行(间奏、空行不计入分母),再乘以 2 | 翻译是一行一行读的,翻得越多,看得懂的越多。按比例给分,26% 和 74% 不会打平,多翻一行就多拿一点 |
| 时长贴合 | +3 | 文档时间轴终点离录音结尾最近的 +3,其次 +2、+1,再往后 0 | 歌词文档和录音常常不是同一版(现场版、剪辑版、加长版)。越听越偏的根因在这里,不在时间轴精度 |
| 完整度 | +1 | 整首覆盖 +1,只是片段 0 | 只有副歌片段的文档,时间轴再准也不适合当唯一来源 |
| 匹配身份 | +2 | Spotify ID 精确命中 +2;来源自己的歌曲 ID 映射 +1;搜索命中 0 | 精确 ID 说明「这条候选问的就是这首歌」;搜索命中只说明名字像。Spotify 自带的来源不拿这 2 分 —— 它本来就是拿这个 ID 去查的 |
| 语言一致 | +3 | 候选文本的语言与歌曲本身的语言一致 +3;不一致或判不出 0 | 中文歌配中文字幕和英文歌配中文字幕是两回事。歌曲语言从同一轮的 Spotify 自带文档读出来,不需要额外的请求或等待 |
中文判定是启发式的,靠比例而不是靠单个字: 假名超过样本文本的 10% 判为日文(中文歌里夹一句日文仍然是中文歌,所以用比例);汉字少于 10 个无法判断;汉字密度低于 25% 判为非中文文本(英文歌里夹几个中文词就不算中文歌词)。判定不通过只是这一项拿 0 分,不扣分、不排除 —— 所以判错最多让一份文档少拿分,不会把一份错误的文档顶上去。
时长按本轮排名给分,不用固定秒数。 实测过一轮:所有来源的时间轴都比录音短 24 到 39 秒。如果按「差 2 秒内给 3 分」这种固定档位,这一轮每个候选都是 0 分,谁更接近完全看不出来。按排名给分,这一轮读出来就是 3、2、1、0、0 —— 用本轮仅有的证据把这几个候选分开。代价是:一份文档的分数取决于本轮还有谁,换一批候选可能变化。
翻译按行占比给分,不按有没有。 一整份全翻译拿满 2 分,翻译一半约 1 分,没有 0 分。
匹配身份只是一笔加分,不是门槛。 精确命中不会自动获胜:它只说明问对了歌,不说明歌词有时间轴、完整或用了简体;这些由其它几项判断。反过来,搜索命中的文档也不会因为「是搜出来的」而被排除。
- 同分时按设置里的「来源顺序」决定;再相同则按内置的固定顺序,最后按候选 ID。候选 ID 这一层保证同一轮的结果与回调到达顺序无关。
- 已经选中的文档在同分或内容相同时保留,不会因为回调顺序变化或重启而反复横跳。挑战者必须严格更高才能接管。
- 手动指定:手动选了某条候选就固定用它。那条候选暂时取不到时,界面会用自动结果作临时回退并明确标出,不会清掉你的指定。
- 两种选择模式:默认「自动」在各来源之间一起比总分;「按来源顺序」则按你排好的顺序,第一个有歌词的来源获胜,只在该来源内部比总分。某首歌的手动指定在两种模式下都有效。
匹配置信度、背景人声/对唱/版权信息这类「来源能力」数量、时间轴健康度、以及来源的身份 —— 都不参与打分。它们仍然显示在来源列表里供你判断,但不决定这次用哪一份。来源只在同分时才作为顺序出场。
1.4 的规则不是从零开始:v1.1 用一套逐层比较,1.4 换成加权求和。两者都能选出合适的歌词,区别在取舍。
按一个固定顺序逐层问下去,第一层出现差别就定胜负:
越界排除 → 时间轴级别 → 完整度 → 自带翻译/转写 → 简体程度
→ 匹配方式 → 能力数量 → 时间轴健康度 → 匹配置信度 → 时长距离
→ 来源固定顺序 → 候选 ID
每一层都是「等级、布尔值或阈值」,没有权重。它的好处是简单、可预测:决策路径只有一条,同样两份文档在任何一轮里的相对顺序都一样(与其它候选无关),日志读起来就是一条链;「逐字必须赢逐行」这类硬规则也容易表达。代价是只能表达优先级,不能表达差多少:两个信号无法互相补足,而且越靠后的层越容易被前面任一层的一个差别整层跳过 —— 例如置信度、时长距离排在很后面,前面某一层的差别一出现,它们就完全不参与判断。旧规则还把音节级排在词级之上(拉丁文里一个音节一个时间点和词级确实不同);1.4 给两者相同的分数,由平局规则决定。
排除不同版本 → 七项求和 → 同分看来源顺序 → 候选 ID
七项各自有分值,一个方面的优势可以补另一个方面的不足,等级也换成了分档。举个能算清楚的例子:一份逐字 + 简体的文档对一份逐行 + 繁体、但靠精确 ID 命中的文档 —— 时间轴一项 4 比 2,简体一项最多再拉开 5 分,而精确 ID 只值 2 分,所以逐字简体那份可以领先。反过来,如果两者时间轴同级、简体程度接近,那么精确 ID 的 2 分或来源顺序的先后就足以决定归属。
代价也说清楚:总分里有一项是本轮排名,所以分数是候选集合的函数,换一批候选同一份文档的总分可能变化;跨轮对比要看日志里的分项,而不是只看总分。
- 旧规则擅长可预测。 规则少、层次清楚、每一步都能背出来;同样两个候选的相对顺序与其它候选无关。想要「逐字一律压过逐行」这种硬性结论,它表达得最直接。
- 新规则擅长同时权衡。 时间轴、简繁、翻译、时长、完整度、匹配身份、语言七项一起算,程度差别有分档,弱项可以用强项补。时长项改成按本轮排名给分之后,「所有来源都差几十秒」这种轮次也能分出高低。
两条规则是先后关系:v1.1 用前者,1.4 换成后者,不是同时并存。区别只是把哪一件事放在前面:确定性的顺序,还是可加权的取舍。
- 多来源并发搜索 —— 所有已启用的来源同时请求,最快的先显示,只有分数严格更高的候选才会替换它
- 逐字歌词(卡拉 OK 效果),支持逐字、逐词、逐行、无时间轴四档
- 智能匹配 —— 支持精确 Spotify ID、歌名搜索、繁简互搜、别名与拼写变体(例如「反乌托邦 - 拼接版」与「拼接乌托邦」)
- 按名称手动搜索 —— 自动匹配失败时可以自己搜歌名,从返回结果里挑,按 ID 直接取词
- 来源列表 —— 每个来源单独检查,显示匹配状态、时间轴级别、翻译与能力;取不到歌词的来源会明确标出,可手动置顶
- 本地缓存 —— 取回过的歌词存下来,再听同一首歌无需重新请求
- 翻译与罗马音 / 注音
- 全屏歌词界面(接管 Spotify 原生歌词页)
- 桌面歌词与 **PiP(画中画)**歌词
- Android Auto 歌词 —— 全屏播放器与卡片
- HyperGlow 歌词投送
- 对唱视图、背景人声标记、分享卡片
- 打分选优 —— 七项加权,简繁按比例分档,翻译按覆盖比例给分
- 越界文档排除 —— 比录音长 10 秒以上的文档按另一个版本排除
- 毒化文档过滤 —— 内容可疑的响应会被拒收
- 时间轴健康度、匹配置信度等字段仍会显示在来源列表里,供你手动判断
- 完整简体中文界面,另含 English / 日本語 / 한국어 / Русский
- 备份与恢复 —— 设置、来源顺序、每首歌的选择、整个曲库、缓存与字体一次带走
模块同时查询多个歌词来源:Spotify 自带的歌词,加上若干第三方歌词库与平台。同一个来源在不同歌曲上的表现差别很大,有的时间轴更细,有的常带翻译,有的只有繁体 —— 这正是打分规则要处理的问题。你不必知道它们是哪些:自动模式下,所有已启用的来源一起比总分。
有一个来源需要你自己的客户端密钥:
- 按设置里的说明到该服务申请
- 在 设置 → Spotify++ → 客户端密钥 里填入
- 格式不对会拒绝保存并给出提示
没有密钥或密钥失效时,该来源自动跳过,其余来源照常工作。密钥只在本机加密保存,不随备份转移到别的设备(见备份与迁移)。
- 启用哪些来源 —— 不想要的可以关掉,选择轮次里根本不会问它
- 来源顺序 —— 既是自动模式的同分裁决,也是「按来源顺序」模式的优先级
- 选择模式 —— 自动(一起比总分)或按来源顺序(第一个有歌词的获胜)
- 单曲指定 —— 在来源列表里手动钉住某一条候选
| 项目 | 要求 |
|---|---|
| 系统 | Android 8.1 及以上(minSdk 27,targetSdk 35) |
| 模块框架 | Xposed / LSPosed(推荐,需 root)或 LSPatch(无 root,需自行给 Spotify 打补丁);模块使用 Xposed API 102 |
| Spotify | 本版针对 9.1.88 – 9.1.90 适配;更早的版本、Beta 版或第三方改版可能不工作 |
| 模块版本 | 1.5(versionCode 8) |
模块依赖 Xposed 框架注入,未打补丁的 Spotify 上无法工作。
- 从 Releases 下载最新 APK 并安装
- 打开 LSPosed → 模块 → 启用 Spotify++
- 作用域勾选
Spotify- 需要 Android Auto 歌词时再勾
Android Auto
- 需要 Android Auto 歌词时再勾
- 强制停止 Spotify(设置 → 应用 → Spotify → 强制停止)
- 重新打开 Spotify
⚠️ 第 4 步不能跳过。 安装 APK 不会重启 Spotify 进程,而 LSPosed 只在进程启动时加载模块。跳过这步你会测试到旧代码 —— 这是排查问题时最常见的困惑来源。更新版本时同理:直接覆盖安装新 APK,然后同样强制停止并重开 Spotify;使用 LSPatch 的话记得开启「覆盖版本号」。
装好后直接播放一首歌,歌词会自动出现。为了第一首歌就拿到好结果,模块会同时问所有已启用的来源,并且在后续来源返回时重新比较。
两个入口:
- 设置 —— Spotify 设置里的 Spotify++ 分区。所有开关都在这里:歌词来源(启用、顺序、选择模式、客户端密钥)、翻译(引擎、目标语言、缓存)、AI 翻译与读音、界面与渲染、清除缓存、导出与导入
- 来源列表 —— 歌词界面右下角的来源按钮。看这一首歌每个来源的匹配情况、单独重新检查、手动指定
| 想要的效果 | 怎么做 |
|---|---|
| 优先逐字歌词 | 默认已开启,无需设置 |
| 优先某个来源 | 设置 → 来源顺序,把它拖到前面;或在来源列表里手动指定这一首 |
| 中文歌显示简体 | 默认已开启(简体程度参与打分) |
| 歌词不跟随 / 手动滚 | 关闭「跟随播放」 |
| 歌词不同步 | 设置 → 重置歌词同步 |
| 少问一个来源 | 设置 → 关掉那个来源 |
设置 → 导出设置与数据 会写出一个 JSON(默认 下载/Spotify++/SpotifyPlusPlus-backup.json,也可以自己选保存位置);恢复用 导入设置与数据。
| 内容 | 说明 |
|---|---|
| 全部设置 | 界面、渲染、行为、选择模式 |
| 来源顺序与每首歌的指定 | 你调好的优先级和单曲选择 |
| 整个曲库 | 每首歌有哪些来源候选、匹配方式、是否检查过 |
| 歌词与翻译缓存 | 数据库整体复制,恢复后不必重新下载、也不必重新请求翻译 |
| 导入的字体 | 自定义字体文件跟着走,恢复时会指向本机的新路径 |
| 凭据密文 | 客户端密钥、AI 服务密钥、翻译接口的 AppID 与密钥。只能在写出这份备份的安装上解开,换设备后必须重新输入(见下) |
| 少量运行时状态 | 密钥版本与访问状态,避免恢复后旧结果污染 |
- 诊断草稿与抓取缓冲 —— 那是进行中报告的临时状态,不属于要迁移的东西
- 语言模型包 —— 几十 MB、可随时重新下载,设置里会重新指向
- 明文凭据 —— 只有密文,见下
凭据在本机加密保存,加密用的密钥在系统的 Keystore 里、绑定 Spotify 在这台设备上的身份,不写进备份文件。所以:
- 同一台设备上,模块更新、Spotify 更新、卸载重装模块,凭据都还能用
- 换手机、换了 Android 用户、或者清了 Spotify 的数据之后,备份里的密文就解不开了 —— 需要在新设备上重新输入
- 模块不会导入或迁移密钥,任何提示也不会这么说
恢复时如果本机已经有一份能用的凭据,不会被文件里读不出的值覆盖;恢复完成后会逐项提示哪些凭据需要在设置里重新填写。
- 文件里有的项覆盖同名项,文件里没有的项保持不动(旧版本的备份仍然可以导入,不会把没覆盖到的部分清空)
- 比当前版本更新的备份会被拒绝,而不是猜测
- 恢复后曲库与翻译缓存要等 Spotify 重启才生效
相关文档:MODIFICATIONS.md(AGPL 修改声明)、FEATURES_USER.md(功能清单)、READINGS.md(读音与转写指南)。
| 组件 | 版本 |
|---|---|
| JDK | 17 |
| Android SDK | Platform 35(compileSdk 35 / minSdk 27) |
| Gradle | 8.14.2(含 wrapper) |
| Android Gradle Plugin | 8.8.2 |
| Xposed API | 102.0.0(io.github.libxposed:api) |
git clone https://github.com/junxi8686/SpotifyPlusPlus.git
cd SpotifyPlusPlus
./gradlew :app:assembleDebug # 产物在 app/build/outputs/apk/debug/
./gradlew :app:testDebugUnitTest # 单元测试Windows 用 gradlew.bat。
测试可以按组跑,不必每次全量:
./gradlew :app:testDebugUnitTest -PtestGroup=lyrics # 歌词相关
./gradlew :app:testDebugUnitTest -PtestGroup=settings # 设置与字符串
./gradlew :app:testDebugUnitTest -PtestGroup=corpus # 语言黄金样本可用分组:settings、diagnostics、hooks、lyrics、lyrics-ai、lyrics-session、corpus。另有需要显式开启的评估工具(-PtestTools=jp-export、-PtestTools=model-survey),它们会写文件或调用付费服务,不属于回归测试。
装了没反应 / 功能没生效
- 确认 LSPosed 里模块已启用,作用域勾选了 Spotify
- 强制停止 Spotify 再打开 —— 必须
- 确认 Spotify 版本受支持(本版针对 9.1.88 – 9.1.90 适配)
歌词是繁体,但我想要简体
简体程度参与打分,所以简体文档通常会自动胜出。如果还是繁体:
- 打开来源列表,看有没有来源提供简体歌词
- 若有,手动指定它
- 若没有,说明该曲目暂时没有简体歌词可用 —— 打分规则不会凭空造出一份
某个来源一直查不到
在来源列表里点那一行单独检查:
- 打勾 → 该来源有这首歌的歌词
- 红叉 → 该来源确实没有,或网络不通
取不到歌词的来源会明确显示成不可用,而不是看起来能用却点不动。注意上架下架是动态的 —— 今天没有不代表以后没有。
需要密钥的那个来源没有输出
- 在设置里确认密钥已保存成功(保存时会有提示)
- 在来源列表里手动点它一次 —— 手动检查会走「显式恢复」路径,可解除之前的访问限制
- 密钥被服务端拒绝过会导致该来源暂停
换手机 / 刷机后怎么恢复
用导出与导入。注意:
- 设置、来源顺序、曲库、缓存都能恢复
- 凭据的密文虽然跟着文件走,但解密的密钥绑定设备 —— 需要在新设备上重新输入
- 恢复完成后模块会提示具体是哪几项需要重填
歌词不同步 / 越拖越偏
先看来源列表:如果时间轴终点和录音结尾差得远,说明这份文档和录音不是同一版,可以手动换一份。
如果时间轴本身没问题:
设置 → 重置歌词同步。它会清掉偏移量并让模块重新对齐播放时钟。
- 分数与本轮候选有关 —— 时长贴合按排名给分,所以换一批候选,同一份文档的总分可能变化
- 语言判定是启发式 —— 几乎没有假名的日文歌可能被判成中文;大量英文的中文歌可能落到汉字密度门槛以下;韩文里的汉字不会被单独识别。判错只会让这一项少给分,不会额外加分
- 部分曲目在慢来源上匹配较慢 —— 网络原因,不是模块问题
- Android Auto 歌词在部分车机上未经充分测试
- 极少数曲目的歌词与录音长度不符;超过 10 秒的越界会被排除,10 秒以内仍可能有偏差
- 读音是规则与词典驱动 —— 普通话多音字、日文艺术化读音、西里尔转写、希腊语静态表都有已知边界
- 无 root 的 LSPatch 方案依赖「降级—登录—升级」流程,登录或完整性校验可能失败
- HyperGlow 投送需要设备支持该功能
遇到问题请到 Issues 反馈,附上:
- Spotify 版本
- 模块版本
- 复现步骤
- 相关截图
本仓库在本项目的 v1.1 之上持续合并上游 Spicy EX 的改动:1.4 换掉了歌词排序规则,1.5 合并到上游 v2.0.41,并做了以下调整:
- 新增
values-zh-rCN,覆盖全部字符串资源 - 修复上游「Java 引用了但资源里不存在」导致中文界面回退英文的问题
- 歌词署名、错误提示等动态文案的汉化
- 修复某些设备上语言列表只剩当前语言、切过去切不回来的问题
- 加权打分选优(1.4)—— 上游 v2.0.0 改为「来源优先」(先定主来源,只在来源内部比时间轴),导致更好的备用来源永远无法替换主来源;本仓库 v1.1 恢复为质量优先的逐层比较,1.4 进一步换成七项加权求和,见分级比较与打分比较
- 多来源并发请求 —— 上游逐个来源顺序请求,等待时间等于所有来源之和;现在同时发起,最快先显示、分数更高才替换
- 按名称搜索歌词 —— 自动匹配失败时可手动搜索、自行挑选
- 繁简互搜与别名变体(修复「一个字之差就当成两首歌」)
- 来源列表每个曲目只自动检查一次,且成功时不再刷屏提示
- 排除不同版本的文档 —— 比录音长 10 秒以上的候选不参与比较
- 备份范围从 2 个存储扩展到设置、来源选择、曲库、缓存、字体与凭据密文(密文只有写出它的那份安装能解开)—— 上游只备份设置和密钥,导入后曲库为空、来源未选
- 凭据保留规则 —— 恢复时不覆盖本机还能用的密钥,并提示哪些需要重新填写
- 移除一个不再使用的歌词来源;旧设置或旧备份里残留的条目也不会被查询
- 修复三处指向错误地址的 GitHub 链接
- 移除设置里的「报告问题」入口(草稿发往上游,本仓库收不到)
- 适配 Spotify 9.1.88 – 9.1.90 的曲目获取与悬浮窗(上游写法在新版会抛
NoSuchMethodException,导致自动搜索整体失效) - 修复翻译链路:中文翻译被丢弃、卡在「翻译中」、失败无提示、重复行没有翻译
详见 MODIFICATIONS.md 与 FEATURES_USER.md。
- 上游项目:Spicy EX(作者 amarinne)
- 歌词渲染思路参考:Spicy Lyrics(Spicetify 扩展)
- 图标:Lucide(ISC / MIT)
- 歌词来自 Spotify 自带歌词与若干第三方来源;本页不逐一列出
本项目以 AGPL-3.0 发布,详见 LICENSE。
本项目不隶属于 Spotify、Spicy Lyrics 或任何歌词服务商。所有歌词的版权归其各自权利人所有。