分类
技术 中文文章

在 macOS 和 Android 平台实现音乐库中的自定义排序

歌曲名称、歌手以及专辑的自定义排序顺序常被认为非常罕见的需求。大多数主要语言使用的是表音文字。它们的自然顺序通常与 Unicode 中的排序的大致相同(有些文字可能需要进行规范化处理)。 而在使用表意文字(主要是汉字)的语言中,它们的自然顺序(通常是读音顺序)与 Unicode 中的编码顺序相当不同。这会导致这类语言以 Unicode 编码顺序时会看起来很奇怪,并且很难从中查找。因此,当歌曲库中存在着一种或多种这样的语言时,自定义排序顺序则是一个很有用功能。

目前状况

对于不同平台的中文和日文用户,目前解决该问题的方法也各有不同。

【订阅制音乐平台】 内容提供商为其音乐库中的名称附加一个排序顺序键值,并使用一种(可能是专有的)方式将它们与音乐文件相关联,并相应地在 Web 或原生平台上进行应用。「排序顺序键值」是用来替代曲目标题,艺术家或专辑的替代名称,用于在排序过程中代替原始的名称,而不影响名称的显示方式。

【国产本地音乐播放器】 国产音乐播放器通常会内置一个拼音转换器,在导入音乐库时会对每首歌曲在播放器内进行注标音。 然而这种策略在处理日语标题时效果会很差。 一些播放器会忽略任何既不是简体字也不是拉丁字母的字符。当想要查找日语标题时,还需要考虑标题里哪个字是简体字。

【本地化实施较好的一般播放器,播放本机音乐】 (包括语言设定正确的最新版本 Android) 这些播放器会利用本地化排序规则 (Collation) 来解决这个问题。数据库系统通常包括来自 Unicode 国际组件(ICU)库的本地化排序规则表。大多数情况下,排序规则可能是一种较为可行的解决方案,但如果音乐库中同时使用多种种语言,本地化的排序规则则不会起到太大作用。

在中文里,大多数汉字只有一个发音。ICU 的拼音排序规则类似于 GB16386 汉字编码中的顺序——使用每个字符中最常见的拼音对汉字进行排序。在大多数情况下这种拼音顺序都能够产生理想的结果。

而在日语里,几乎所有的汉字都有多种发音。甚至多个汉字组合在一起时发音还会有所不同。 确定日语文字的准确发音并非易事。实现这项功能通常要涉及自然语言处理(NLP)技术。ICU 遵循 JIS X 4061 标准(日语字符串排序规则)。该标准为每个汉字指定里一个常用的音读方式。尽管比原始 Unicode 代码顺序有更好的效果,但这仍被认为是违反直觉的。

【其他一般播放器中播放本机音乐】 其他一些播放器仅为了英语语境进行设计,它们常常倾向于使用对于计算机最自然的排序顺序——Unicode 码位顺序——对列表进行排序。Unicode 中的常用汉字是以基于偏旁和笔画的「康熙字典序」进行排列的。虽然这种方式不依赖各个使用汉字语言的特征,但是这种顺序对于两种语言的使用者来说都是违反直觉的。

生成排序键值

正如以前许多其他相关文章中所述,手动标记是对于大多数人来说最直接的方法,尽管为规模较大音乐库标签时会引入大量工作。iTunes 提供了一个简单的界面,可以直接在「歌曲信息」界面中添加排序键值标签。

如果音乐库规模过大而不能一一手动标记,半自动的标记方法仍不失为一种解决方案。中文和日语都会有「多音字」的情况——汉字的读音会根据上下文而发生变化。当计算机无法识别正确的上下文或完全不了解上下文时,其所生成的发音可能会不正确。 因此,手动检查标记对于正确的标记结果也是有必要的。

作为一个 Pythonista,我首先想到的是用 Python 写了一个脚本来标记和检查排序键值标签。中文拼音是使用 PyPinyin 来进行生成,而日文的假名表音是通过 Yahoo 的日语形态素解析 API,这个 API 是在线 API,需要在运行时连接互联网。如果需要本地化方案,MeCab 加上 mecab-ipadic-NEologd 也是一个不错的选择,而且在有时会比 Yahoo API 表现更好。

更进一步,我在此基础上写了一个基于 Flask + Vue + MongoDB 的 web app 来进行远程加标签和检查。Web app 的源代码可以在 GitHub 上面找到。

将标签添加到音乐文件

尽管作为一个罕见需求,但大多数主要音乐标签格式都有提供用于自定义排序键值的属性字段。 JAudiotagger 提供了一个十分全面的标签列表,其中提到了各主要标签格式中对应排序键值的字段名称。

在我的 Python 脚本中,我使用了 Mutagen 来识别和编辑标签。Mutagen 对于 ID3v2 标签(对应 MP3 文件)、 Vorbis Comment 标签(对应 FLAC 文件)以及其他更多的文件都提供了较好的支持。对于 Java/Kotlin 用户来说 JAudiotagger 也不失为一个好的选择。我也在之后的 Android 解决方案中使用了 JAudiotagger。

在 macOS(和 iOS)中使用排序键值标签

没有什么特别值得强调的。 macOS 中的 iTunes 对排键值标签提供了十分出色的支持。iTunes 中可以直接运用这类标签。在「曲目信息」的「排序」页面里面也可以直接对这些标签进行编辑。需要注意的一点是,如果在 iTunes 之外对音乐文件进行了编辑,则还需要重新导入音乐库以刷新排序。

iOS 我猜也应该会识别这些标签,毕竟 iTunes 在 iOS 上也是作为音乐播放器使用的。

* 在更高版本的 macOS 中,「iTunes」被更名为「音乐」。 虽然我还没有尝试过,但是对标签方面的影响应该不大。

在 Android 中使用排序键值标签

与 Apple 生态系统中的应用不同,Android 系统完全没有识别自定义排序键值标签。Android 平台上许多音乐播放器都依赖 Android 提供的媒体存储 API。媒体存储组件维护着一个媒体信息数据库。这个数据库记录着系统发现的包括音乐在内的所有媒体文件。虽然数据库中确实含有像 title_keyartist_keyalbum_key 之类的列,但其中的内容只不过是把名称开头的「停用词」(stop words,“a”, “an”, “the” 等)然后转换到排序规则键值(collation key)而已。虽然生成的排序键值会根据系统语言而使用本地化的排序规则,但是正如上面所述的那样,这仍不是一个理想的方案。

此前,我也曾尝试着在 AOSP 源码上增加对此类标签的支持,但提取媒体元数据的代码(在 libstagefright 中)是用 C 写成的。我没太能跟得上它的逻辑,也就放弃了。之后,我还尝试着用 Xposed Framework 来 hook 到更新媒体存储数据库的那部分 Java 代码。但遗憾的是,生成排序规则键值(collation key)和写入数据库的代码是在同一函数中。要把钩子挂在这两段代码之间必须要重写整个函数。这样并不理想。

最终,我决定利用 Android 媒体存储数据库依赖 *_key 列来为歌曲进行排序这一情况,使用 Root 权限直接操作数据库,把排序键值更新成我们想要的值。

于是乎我就开始动手写这个更新数据库的 app。 工作原理非常简单:

  1. 获得 Root 权限和存储权限来读写媒体存储数据库,以及从音乐文件中读取排序键值标签。
  2. 将数据库文件复制到 app 的数据文件夹中进行处理。
  3. 从数据库里加载音乐文件列表。
  4. 通过 JAudiotagger 从音乐文件中提取排序键值标签。
  5. 根据提取到的标签在数据库中更新排序规则键。
  6. 将更新好的数据库写回系统。

更新数据库后,可以强制重启音乐播放器来查看更新结果。

这个方法的缺点是每次对音乐文件变更时都需要手动更新数据库。

App 的源码和编译好的 APK 文件也可以在 GitHub 上找到。

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*