Custom sort order in music libraries: macOS and Android (en, zh-hans, ja)

在 macOS 和 Android 平台实现音乐库中的自定义排序
macOS と Android での音楽ライブラリーのカスタムソート順

Custom sort order in music libraries is a rather rare need. Most major languages use phonograms in their scripts, where the natural sort order is more or less identical to what is seen in Unicode (probably after some normalizations). On the other hand, languages using logograms (logosyllabic scripts, mainly Chinese characters in our context) does not have their characters sorted in their primary natural (usually phonetic) order in Unicode.
This causes a problem where a list of text sorted in Unicode code point order can be odd and difficult to look up from in these languages. Custom sort order in music libraries is thus useful when you have songs in one of these languages, or even a mix of them.

As this article involves mainly with concepts common among Chinese and Japanese language users, this article is also written in zh-hans and ja.
本文末尾附有中文版。
この記事の最後には日本語バージョンがあります。

Current situations

Currently for speakers of Chinese and Japanese using each platform, there is a different strategy tackling this problem.

Subscription-based platforms. The content provider attaches a sort order key for each name in their music library, and using a (probably proprietary) way to associate them with the music file, and apply them accordingly on web or native platforms. “Sort order keys” are alternative names to title, artist or album that is used in sorting in place of the original name, but never affects how the name is displayed.

Local music in Chinese-made players. Chinese-made music players usually have a built-in Pinyin converter which is used when importing music library. The downside of this is, sometime it would perform badly when dealing with Japanese titles. Some of these players ignores any characters that are neither Simplified Chinese nor Latin alphabet. When you want to look for a Japanese title, you need to first try to think of which character comes first in the title that is also in Simplified Chinese.

Local music in generic players aware of localization problems. (This includes the latest Android when set to the correct language.) These platforms utilizes localized collation to solve this problem. Database systems usually includes localized collation table from the International Components for Unicode (ICU) library. Collation may be a tentative solution for the most, but it doesn’t work if you have a mixed library of both languages.

In Chinese, most character has only one pronunciation. The ICU Pinyin collation is similar to the GB16386 Chinese character encoding, which uses the most common Pinyin of each character to sort alphabetically. This works decently most of the time.

In Japanese, almost all Kanji have multiple pronunciations, and it could change when multiple Kanji is grouped together. Deciding pronunciations for Japanese text is a non-trivial task, and usually involves Natural Language Processing (NLP) techniques. ICU has followed the JIS X 4061 standard (Collation for Japanese Character String), which has defined one common On-yomi for each Kanji. Despite being better than raw Unicode code-point order, this is still considered as against intuition.

Local music in other generic players. Other players that only aware of the English context tend to use the most natural sort order for computers – Unicode code point order – to sort the list. Common Chinese characters are sorted in Unicode by a shape-based language-neutral way called “Kangxi Radical” order. But this order is counter-intuitive for speakers of both languages.

Generating sort keys

As suggested by many previous articles on this matter, tagging manually is the most straightforward way for most of the people, despite introducing a large amount of work when tagging a large library. iTunes has provided a simple interface to add sort keys tags in the “Song Info” menu.

If you have library too large to tags manually, there are still ways to tag it semi-automatically. Both Chinese and Japanese has the case where the same character can be pronounced differently depend on context, and computer generated pronunciation could be incorrect when it can’t detect the right context, or when it simply doesn’t aware of the context at all. Thus, manual review is still necessary for correct tagging.

As a Pythonista, I have written a script to do tagging and reviews in Python. Pinyin is generated using the Python library PyPinyin, while Japanese Yomigana is produced with Yahoo Japanese Morphological Analysis API which requires internet connection at runtime. If you want a local solution, MeCab with mecab-ipadic-NEologd is a really good one, and may perform even better than Yahoo’s API.

Extending a step further, I wrote a simple Flask+Vue+MongoDB web app to enable tagging to be reviewed remotely. Source code of it can be found on GitHub.

Attaching tags to music files

Despite being a minor need, most major music tagging formats has thankfully included property fields for custom sorting order keys. JAudiotagger has provided a comprehensive list of tags used by different formats that represents sort orders.

In my Python script, I have used Mutagen which has a good support to both ID3v2 tags (for MP3 files) and Vorbis Comment (for FLAC files), and even more. For Java/Kotlin users, JAudiotagger is also a good choice. I used that in my Android solution (mentioned later) too.

Use these tags in macOS (and iOS)

Nothing much to highlight here. iTunes in macOS comes with excellent support to those sort key tags, it allow you to edit these tags within itself. One thing to note is that you might want to re-import your music library to refresh if you have done edits outside of iTunes.

iOS, I believe, should also honor these tags as well, since they also use iTunes as the player.

* In later versions of macOS, “iTunes” is renamed to “Music”. I haven’t tried it, but it shouldn’t affect much on the tagging side.

Use these tags in Android

Unlike apps in the Apple ecosystem, Android system has zero awareness of custom tag order. Most music players on Android platform uses the Media Storage API provided by Android. Inside the Media Storage component, it maintains a database that records all media files discovered by the system, including music files. It does have columns like title_key, artist_key and album_key, but what it does is not more than just stripping off stop words (“a”, “an”, “the”, etc.) from the beginning of titles and convert them to collation keys. It would use the localized collation according to the system language, but that is not good enough as explained above.

I have previously tried to find a way to patch the AOSP to add support to these tags, but the media metadata extraction code (in libstagefright) is written in C, and I was kinda lost wrapping my head around it. After that, I have also tried to explore the option of using Xposed Framework to hook on the Java part of updating the Media Storage database. Sadly the section of code calling for collation key generation and writing to database is in the same function. To hook in between them, I have to rewrite the function as a whole which is not a good practice.

In the end, I decided to exploit the fact that Android media storage strongly relies on the *_key columns and to use Root permission to directly update the database with the sort keys we wanted to use.

I then started to write an app to update the database. How the app works is rather simple:

  1. Get Root permission and storage permission to read and write to the media storage database, and to read sort tags from music files.
  2. Copy out the database file to the data folder of the app for processing.
  3. Load the list of music files to check from database.
  4. Use JAudiotagger to extract sort key tags from music files.
  5. Update the collation keys from database accordingly.
  6. Write updated database back to the system.

After updating the database, you can force close your music player and restart it to see the result.

Shortcoming of this method is that the database needs to be updated manually every time when a media file is changed.

Source code and compiled version of this app is also available on GitHub.

在 macOS 和 Android 平台实现音乐库中的自定义排序 (zh-hans)

歌曲名称、歌手以及专辑的自定义排序顺序常被认为非常罕见的需求。大多数主要语言使用的是表音文字。它们的自然顺序通常与 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 上找到。

搜索关键词:Android 拼音 排序 音乐 歌曲 拼音序 ID3 TSOT TSOA TSOP

macOS と Android での音楽ライブラリーのカスタムソート順 (ja)

音楽ライブラリにおいてのカスタムソート順は、かなりまれなニーズだとされている。 ほとんどの主要言語は表音文字を使って、その自然な並べ替えが Unicode での順序とほぼ同じである(場合によっては正規化は必要)。一方、表意文字(主に漢字)を使う言語では、Unicode での並び方が自然な並び方(通常は読み順)と異なる。これにより、Unicode コードポイント順で並べ替えられたリストがおかしいな順番たと見られ、調べづらくなる。したがって、音楽ライブラリ中のタイトル、アーティスト、アルバム名などのカスタムソート順は、これら一つ、または複数の言語で構成している場合に役に立つ。

今までの現状

現在、各プラットフォームを使用する中国語と日本語の話者に向けて、この問題を解決する現存の方法も異なる。

【会員登録制のストリーミング・プラットフォーム】 これらのプラットフォームは、音楽ライブラリ内の名前ごとにソートキーを添付し、(おそらく独自の)方法を使ってソートキーを音楽ファイルに関連付け、Webまたはネイティブプラットフォームに適用する。「ソートキー」とは、タイトル、アーティスト、またはアルバムの代替名であり、元の名前の代わりに並べ替えに使用される。元の名前の表示には影響しない。

【中国製の音楽プレーヤーでローカル音楽ファイル】 一般的に、中国製の音楽プレーヤーにはピンイン変換ツールを内蔵し、音楽をインポートする度に名前をピンインに変換して、ソートする際に基準とする。この方法の欠点は、日本語のタイトルを変な所にされがちである。 これら一部のプレーヤーは、簡体字でもラテン文字でもない文字を無視してソートすることがある。日本語のタイトルを探したいときは、まずどの文字が簡体字にも入ることを考える必要がある。

【ローカライズ問題をよく扱う普通な音楽プレーヤーでローカル音楽ファイル】 (適切な言語に設定された最新の Android はこれに含まれる。) これらのプレーヤーは、ローカライズされた照合 (Collation) を使用してこの問題を解決しようとする。 通常、データベースには、International Components for Unicode (ICU) ライブラリのローカライズされた照合テーブルが含まれています。照合はほとんどの人には仮の解決策になれるが、複数の言語が音楽ライブラリーのなかで混ざっている場合はあまりにはよく機能しない。

中国語では、ほとんどの漢字の読み方は一つだけである。ICU のピンイン照合は、GB16386 中国語文字エンコーディングの順番に似ていて、各漢字の最もよく使われるピンインを元にアルファベット順にソートすることである。ほとんどの場合ではこれで十分です。

その一方、日本語では、ほぼすべての漢字には複数の読み方があり、複数の漢字を組み合わせてさらに新しい読み方が産んでしまう。日本語文字列の読み方を確定するのは簡単な作業ではなく、一般的には自然言語処理(NLP)技術を運用している。 ICU は、JIS X 4061 規格(日本語文字列照合順番)に準拠して、全ての漢字に最も一般的な音読みを基準として指定してソートする。(例えば、「初音ミク」の「初」は「しょ」と扱って、「し」の位置に配置すること。)Unicode の符号位置順よりは優れていますが、これもまた直観に反する順番である。

【その他の音楽プレーヤーでローカル音楽ファイル】 英語しか配慮してない他の音楽プレーヤーは、コンピューターにとって最も自然なソート順——Unicode 符号位置順——を利用してプレイリストをソートする傾向がある。 頻出漢字は Unicode のなかで、部首と画数を元に康熙字典こうきじてん順で並んでいた。この順序は日中韓越いずれの言語にも偏ってないが、どちらの言語の話者にとっても直感に反する順番である。

ソートキーの生成

これに関する今までの記事で提案されている通り、手動でタグ付けすることは最も簡単な方法である。大規模な音楽ライブラリにタグ付けするときに大量の作業になるのだが。 iTunes では、「プロパティー」メニューに読みがなを簡単に追加する機能を提供している。

音楽ライブラリが大きすぎて自分でタグ付けできない場合でも、半自動的にタグを付ける方法がある。 日本語も中国語も、同じ文字を文脈によって異なる読みかたになる場合が多くあり、コンピューターが生成した読みがなは、正しい文脈を検出できない場合、またはその文脈をまったく認識できない場合では、間違い読みがなをつけることがある。 したがって、正しいタグをつけようなら、人力で検査するのが必要である。

Pythonista として、Python でタグ付けと検査をするスクリプトを作った。中国語ピンインの生成は Python ライブラリの PyPinyin を使い、日本語の読みがなは実行時にはインタネットへの接続が必要な Yahoo の形態素解析 API を利用した。ローカルで読みがなを生成したいなら、MeCabmecab-mepad-ipadic-NEologd 辞書を合わせれば、Yahoo の API にも負けない良い結果が出せる。

プラスαとして、タグ付けをリモートで検査できるようにするための簡単な Web アプリを Flask + Vue + MongoDB で作成した。 ソースコードは GitHub に公開した。

音楽ファイルにタグをつける

マイナーなニーズだと見なされたが、主要な音楽タグ形式には、ありがたいことにカスタムソートキーのプロパティフィールドがある。 JAudiotagger のサイトには、ソートキーに対応したタグ形式で使用されるタグ名の網羅的一覧がある。

前述の Python スクリプトでは、ID3v2 タグ (MP3 ファイル)や Vorbis Comment (FLAC ファイル)など数多くのフォーマットをよく対応している Mutagen を利用した。 Java や Kotlin を使いたい場合は、 JAudiotagger も良いパッケージだと思う。後述の Android アプリにも JAudiotagger を使った。

macOS(と iOS)で付けたタグを利用する

強調すべきものはあまりない。 macOS の iTunes には、ソートキータグの対応が内蔵していたため、タグを編集や利用することが難なくできる。注意すべきことは、iTunes 以外でタグを編集した場合は、音楽ライブラリを再インポートして更新することが必要。

iOS も、iTunes プレーヤーがあるため、ソートタグも同じく認識できるだと思う。

* macOS の最新バージョンでは、「iTunes」が「Music」に名前が変更された。 使ったことはないが、タグを認識するには大きな影響はないと思う。

Android で付けたタグを利用する

Apple エコシステムのアプリとは違って、Android OS はカスタムソートキーのタグを全く認識しない。 Android の大抵の音楽プレーヤーは、OS が提供した Media Storage API を依頼する。 Media Storage コンポでは、音楽ファイルを含む、OS が検出したすべてのメディアファイルを記録するデータベースが管理している。title_keyartist_keyalbum_key などの列は確かにあるだが、タイトルの最初から “a”、“an”、“the” などのストップワードを外し、照合文字列に変換することしかしてない。システム言語と揃うローカライズされた照合を使用しますが、前で述べたように十分な解決策ではない。

以前、AOSP にソートタグを対応するようパッチを投稿してみたかったが、メディアメタデータ抽出するのコード(libstagefright)が C で書かれていて、僕にとって難しすぎてパッチすることを諦めた。その後、Xposed Framework を通して、Media Storage データベースの更新の Java 部分を Hook することも試してみたが、あいにく照合キーの生成とデータベースに書き込みは、同じメソッドに入った。その間に Hook するには、メソッド全体を書き直しなきゃいけないので、いい解決法だと思わない。

難儀の末、Android の Media Storage の並び替えは *_key の列に依頼することから手に入れ、Root 権限で音楽ファイルのソートキーを直接データベースに書き込むことにした。

そして、データベースを更新するアプリを作り始めた。 仕組みは難しくではない:

  1. Media Storage データベースの読み書きと音楽ファイルからのソートタグの読み取りをするため、Root 権限とストレージ権限を取得する。
  2. データベースファイルをアプリのデータフォルダーにコピーする。
  3. データベースから音楽ファイルのリストをロードする。
  4. JAudiotagger で音楽ファイルからソートキータグを抽出する。
  5. 適切な照合キーをデータベースに書きこむ。
  6. 更新されたデータベースを OS に書き戻す。

データベースを更新した後、音楽プレーヤーを強制的に再起動してから処理結果がでる。

この方法の欠点は、音楽ファイルが変更されるたびに手動的データベースを更新する必要があることである。

このアプリとソースコードは、全て GitHub に公開した。

検索キーワード:Android、アンドロイド、スマホ、携帯、スマートホン、音楽、曲、並び替え、順番、順序、五十音順、読みがな順、読み順、曲名順、アーティスト順、ふりがな、漢字、ID3、TSOT、TSOA、TSOP。