前言

云音乐

相信大家都有过这样的经历,在各个音乐平台之间挣扎的经历。常常是平台 A 下架了某一首歌,平台 B 依靠独占吸收来自平台 A 的难民。

这种现象很普遍,倒不如说在版权时代这是再正常不过的风景了。没有版权就不能上架,天经地义的事情。平台不过是希望吸引流量又不希望承担流量带来的法律风险罢了,否则一开始又为何要上架呢?

罢了,下载到本地听吧。

本地音乐

本地音乐的来源就非常广泛了。

一是从各大云音乐平台上下载得到的音频。其是否存在加密并不是什么麻烦的事情,因为音乐终究是要播放的,最不济你从操作系统层面把送给音频 API 的每一个采样都截下来不就拿到 wav 了吗(笑)

二是各大公网音乐包,比如 [Nemuri] 的合集。这种合集的特点是音乐全,格式整齐,通常以统一的格式进行分享(如 FLACMP3 等)。

有了音频文件,再配上音乐播放器就可以和云音乐一样听歌了。本地音乐的一大缺点在于更新,没有了云音乐运营,一切的一切都要靠你自己了。

当然了,这并不是本地音乐唯一的问题。本地音乐最大的问题恰恰在于本地。且不论本地音乐增多后多端的存储成本,单单是音乐的同步就够你喝一壶的。就以基本的 PC、手机、平板三端同步为例,你需要在这三台设备之间反复横跳——一旦某一台设备没有同步,而你恰巧想要听某一张新发售的专辑——噩梦就降临了。

于是,我们发现,云音乐恰巧是为了解决这些痛点而存在的。那我们要回到云音乐平台吗?

自建音乐平台

既希望得到本地音乐的便利,又想要云音乐的便利,这就是自建音乐平台的意义所在了。

最常见的自建音乐平台应该就是 Airsonic 了,这是一个用 Java 写成,基于 Subsonic 的最后一个开源版本 fork 而出的项目,而 Subsonic 则是著名的自建音乐平台。

Airsonic 的存在已经能够满足很多人的需求了,但它还是无法完全满足我当前的需求,原因有以下几点:

  1. Airsonic 的编写语言是 Java,其需要的资源对小型 VPS 而言还是太多了。以我目前运行的 Docker 为例,没有任何人使用的情况下,基本内存占用维持在 800MiB 左右。
  2. 自建音乐平台需要服务器有较大的硬盘空间,而这一点面对只会增多不会减少的音乐资源难以永久实现。目前我整理的音乐资源有 185GiB,这对于我部署 Airsonic20G 小鸡肯定是不现实的。
  3. 为了应对硬盘问题,我使用了 rclone 挂载。但在音乐更新后,Airsonic 需要花费大量的时间(小时级别)重新扫描整个音频目录,读取每一个音频文件的元数据。这么长的时间差完全不能满足「马上就听」的需求。
  4. Airsonic 支持部分更新,根据目录和文件的 lastModifiedTime 决定是否扫描。但 Google Drive 对于这部分的实现并不遵循一般文件系统的行为,对内层文件的更新并不会导致外层目录的修改时间变化,宣告了 Airsonic 的这个特性完全无法使用。
  5. Airsonic 诸如音乐上传、下载、last.fm 的功能是我不需要的,而且没有办法完全关闭。使用这些功能可能会导致未定义行为(如上传到 rclone 挂载的目录可能会打乱原本的目录结构)。尽管我可以不使用这些功能,但我不能要求和我一起整理资源,共用平台的朋友永远都不使用这些功能。
  6. 更新缓慢,Bug 丛生。尽管有另一个 fork 但问题还是很多,且对我而言维护起来过于困难。(更新:现在只有这个 fork 在积极维护,原仓库已经 Archive 了)

当然了,多多少少还有一些没有列出来的问题。由于这些问题的存在,最终促使我决定在另一个意义上自建音乐平台:自己造轮子。

我们需要什么?

Project Anni 发起半年后,我再次回头来看这个问题:我们需要什么?

从听音乐的角度来看,最基础的需求是我们需要听音乐,在此之上的是我们需要听高音质的音乐。更进一步,我们希望在不同的时间根据不同的需求听不同种类的音乐

从信息的角度来看,最基础的是我们需要知道音乐的标题,然后是艺术家、封面等其他信息。更进一步,我们希望在专辑、艺术家等信息之间快速跳转。最重要的是,我们不希望信息消失(点名批评网易云音乐)。

理想的 Anni 部署结构

Anni 在设计之初是针对资源所有者的。理想的部署模型是资源所有者通过 Annil 共享自己已有的资源,用户通过添加所有者的 Annil 地址以访问资源所有者共享的资源。资源所有者通过凭据限制可以访问资源者的身份、人数等。

但随着资源整理的深入,Annil 对个人呈中心化的趋势已日趋明显。Anni 逐渐成为我个人从各资源平台获取音乐资源、整理后收听的唯一途径。在这种变化下,各设备只与一个 Annil 通信并获取音频已成为目前我个人使用中的事实架构。

与辅种的矛盾?

Anni 的设计目标是没有考虑到辅种的,或者换句话说,Anni 的目标是将通过 Anni 整理后的音频作为做种的目标。Anni 整理后的音频资源包含相对统一的音频标签和内嵌的封面,并以统一的文件名和格式呈现。Anni 本身也提供了对标签等信息进行检查的能力,确保结果音频在元数据这一层面的质量相对较高。

这样的设计考量意味着如果你希望在使用 Anni 的同时保种,那你就需要在硬盘上存放两份音频数据。不过一般音频相比其他资源而言占用空间还是较小的,我整理到现在近 1000 张单曲/专辑的空间占用也只有 300GiB 不到(不含 Hi-res),个人认为空间问题还是相对较小的。

当然了,如果你整理的目标是 TLMC 这样的巨型合集,那就是另一个故事了(93661 个项目,总计 1.7 TiB,笑)。

Airsonic 对比

Airsonic 相比,Anni 的优势如下:

  • 扫描速度快。对于以 Google Drive 形式存储在远端的 370G 音频资源,Annil 目前的扫描只需要 20 秒左右就可以完成。相比之下,在资源只有不到 100G 的时候,Airsonic 就需要扫描两小时以上了。这是因为 Anni 的扫描只需要扫描目录结构,并以专辑为单位定位其所在的目录,并不需要实际读取音频。如果将所有专辑都存放于单一目录下,或使用严格目录格式的情况下,扫描的时间理论上可以基本无视。
  • 元数据更新方便。由于元数据和音频文件脱钩,因此修改元数据只需要对元数据仓库中的文本进行修改即可。将错误修正写回音频文件的过程并不是必须的(尽管一般个人因为强迫症会选择写回)。
  • 歌单等附加性功能(Anniv)与音频分发(Annil)脱钩。这意味着如果我们不需要多余的功能,只想专心听歌,完全可以只使用 Annil。而分拆出来的 Anniv 则可以放手实现各种附加功能。并且数据还可以跨 Annil 存在。
  • 资源占用小。个人实际使用下 Annil 的待机内存占用在 15MiB 以内,客户端访问时(在不转码的情况下,转码调用的是 ffmpeg)也不超过 20MiB。考虑到 Annil 目前的部署场景基本是 Self Host,这样的资源消耗对我个人而言可以说是完美的。

而目前的劣势也很明显:

  • 需要手动整理。这是 Anni 存在的基石,Anni 只会提供更便于整理资源的工具,一定不会跳过资源整理这一步。
  • 对收藏、歌单等功能的支持还没有开始。Anniv 的协议还没有完全敲定,也没有可用的前端、后端、客户端。目前的客户端是实现了 Airsonic 协议后借用的 DSubAirsonic 客户端,只是还算能用的状态。