Androidで各種クラウドストレージやNASの音楽をギャップレス再生する方法

はじめに

クラシック音楽やロックのライブアルバム等では、音楽や拍手が鳴り続いているところでトラックが分割されていることがよくあります。これらを快適に再生するには、無音を一切挿入することなく完全に連続的に複数トラックを連続再生する、いわゆる「ギャップレス再生」の機能が不可欠です。音が切れない箇所でトラックが分割されているアルバムは全く珍しいものではなく、それゆえに「ギャップレス再生」ができないというのは音楽プレイヤーとしては一種のバグのようなものだと言えると思いますが、残念ながら現実にはそのようなバグを含んだ音楽プレイヤーがたくさんあり、むしろそちらのほうが多いくらいです。そもそも「ギャップレス再生などという言葉が存在すること自体が音楽界への冒涜だと思うのですが…。

mpv

愚痴はさておき、実際に我々にはバグのないまともな音楽プレイヤーが必要です。その一つがmpvです。オープンソースであり、元はCUI版のみだったと思うのですが現在は公式のGUIが実装されているほか、他のGUI実装(celluloidなど)や快適なカスタムテーマ(uoscなど)もあります。またAndroid版のmpv-androidというのもあります。iPhoneも詳しくないですがなんか移植版がありそうです。

mpvでは、prefetch-playlist=yescache=yesも要るかも?)というオプションを設定しておけば、とりあえず普通にファイルとして見えているものは問題なくギャップレス再生できます。mpv-androidでもmpv.confが編集できるのでそこで設定できます。

WindowsにおいてはNAS(ネットワークドライブ)はもちろんGoogle Driveとかも公式クライアントとかを使えばエクスプローラーで普通にファイルとして見られます(このような操作を(ファイルシステムへの)マウントといいます)。またLinuxでもrcloneというソフトがあってこれを使うとGoogle Drive含めメジャーどころのクラウドストレージ・ファイルサーバープロトコルなら全てファイルとして閲覧できます(Google Driveならgoogle-drive-ocamlfuseもあります)(rcloneにはWindows版もあります)。ということで、筆者はPCにおいてはこれで問題なくギャップレス再生の手段を手にいれました。

モバイル端末の制限

しかしモバイル端末(筆者はAndroidしか使っていないので以下はAndroidの話題に限定しますが、iPhoneでも多分同様)ではそう簡単にはいきません。というのも、モバイル端末はPCよりもセキュリティ面で保守的なので、NASGoogle Driveのファイルをそのままファイルとして(ローカルのファイルシステムと同様の枠組みで)使うことができないのです(正確にはAndroidをroot化(iPhoneなら脱獄)すればできるのですが、デメリットが大きいので今回は無しとします)。となると、ファイルマネージャーやクラウドストレージのクライアント(Google Driveのアプリなど)側が頑張ってオーディオ再生に対応するか、あるいは逆にメディアプレイヤー側がクラウドストレージに自前で対応するか、といったことが必要になります。

クラウドストレージが閲覧できてオーディオファイルを外部アプリで開いてくれるファイルマネージャー、あるいはクラウド再生に対応した音楽アプリ、ならそこそこあるのですが、その中で複数ファイルをギャップレス再生できるものはありませんでした。一応、クラウドのフォルダを一度開いてスキャン(取り込み的なやつ)をすればその中身をギャップレス再生できるというアプリはあったのですが、聴きたい音楽が大量にある場合はフルスキャンに大量の時間とストレージ容量が必要ですし(それができるなら最初から全部ローカルに入れておけばいい)、かといって各フォルダをいちいちスキャンするような使い方がしやすいようにデザインされているわけでもありませんでした。

あと、実はrcloneにはAndroid版(RCX)もあり、これを使うと(本物のファイルシステムではないのですが)ContentProviderという仕組みを使って他の(ContentProviderに対応した)アプリからGoogle Driveなどのコンテンツが見られるようにできて、さらにmpv-androidはContentProviderに対応している……のですが、現在のmpv-androidだと、この方式では1ファイルごとにしか再生できないようで(Play Folder in document picker · Issue #627 · mpv-android/mpv-android · GitHub)、目的は達成できませんでした。

解決策: HTTPサーバーを立ててそこから再生

という感じで、mpvとrcloneの移植版があっても無理なら、やはり無理なのか……?と諦めムードが漂っていましたが、ふと思い出したのが、mpvネットワーク上のファイルでもプレイリストで指定すればギャップレス再生できる、という事実です(ちなみにこれを活かして、mpvを使ってYouTubeのプレイリストをギャップレス再生する方法をこちらで紹介しています)。このプレイリストは、単純にファイル名を並べたものか、m3u(m3u8)形式のプレイリストを使用することができ、ファイル指定では普通のファイルパスに加えて「http://」や「smb://」や「ytdl://」(内部でyoutube-dlを使う(ただしギャップレス再生不可))など色々な書式が使えます(詳しくはこちらを参照)。

ということは、Androidから見えるところにHTTPサーバーを立てて、そこにあるファイルを指定したプレイリストを作っておけば目的は達成できます。

このHTTPサーバーはAndroid上でも自宅LAN上でもインターネット上でもどこでもよく、またソフトウェアもApacheやnginxなど何を使っても構いません。音声ファイル(.flacとか)が直接HTTPでアクセスできる状態になっていれば問題ありません。m3u8プレイリストの書き方も調べれば出てきます。なので、ここでもう記事を終わりにしてもいいのですが、せっかくなのでAndroid上で完結させよう&最低限実用できる状態にしようということで、もう少し具体的に設定方法を説明してみます。

TermuxのrcloneでHTTPサーバーを立てる

先ほど、HTTPサーバーを立てるだけなのでApacheでもnginxでも何でもいいと書きましたが、Android内でサーバーを立てようとなると普通のHTTPサーバーはあまり役に立ちません。なぜなら、普通のHTTPサーバーはローカルにあるファイルをHTTPを通じて利用可能にするという機能しかなく、今はそもそもNASクラウドストレージのフォルダをローカルにファイルとしてマウントできないからこそ困っていたわけなので、Apacheやnginxを使ってクラウドのファイルをホストすることもできません。

ここで救世主となるのは先ほどでてきたrcloneで、rcloneでは各種クラウドストレージをマウントするのではなくHTTP/FTP/WebDAVなどの各種サーバーとしてホストすることもできます。カッコイイ!ちなみにローカルフォルダとの同期とかもできるみたいです(使ったことありませんが)。

ただし、先ほど言ったAndroid版のrclone(RCX)は、CUI上で動くコマンドではないので、ちょっと普通のrcloneと動作が違います。HTTPサーバーをホストする機能も一応ついているのですが、ポート番号が8080に固定されている(8080が使用中だったらどうなるのかは知らない)という問題があります(Add Possibility to change Server, Port, Username and Password and switch to Public Key auth · Issue #236 · x0b/rcx · GitHub)。それではちょっと不安があるので、もっと本来のrcloneに近いものをということで、今回はTermuxというAndroid上でCUI操作が使えるようにしてくれるアプリを入れて、そこでrcloneを動かしてみます。

Termuxとrcloneのインストール

Termuxのインストール方法はいろいろなところで解説されているのでここではやりません。注意してほしい点として、Termuxは現在ではGoogle Playストアを利用しておらず(一応存在はするがバージョンが古い)、F-Droidというサードパーティーのストアアプリからインストールすることになります。この関係で、インストール時に多少の警告?確認?メッセージみたいなのに出くわすことになります。とはいえroot化などに比べれば相当リスクの低い行動だと思います。

Playストアにあるものがいいという場合は、Debian norootとかUserLAndのように他にもCUI環境を提供してくれるアプリがあるのでそちらでも多分同様にできると思うんですが、調べた感じTermuxがパフォーマンス的に良さそうな気がしました。

で、Termuxのインストールが終わったら、pkg install rcloneでrcloneを入れます。自前でビルドする必要などは無く、簡単です。

rclone configによるセットアップ

rcloneで各種クラウドストレージを使うには、当然ながらログインしたりなど適切なセットアップが必要です。このときはrclone configというコマンドを使います。これに関しても公式サイト含め他のところで色々解説されていると思うのでここでは解説しません。というかセットアップが必要なのはCUI版だけでなくアプリ版のRCXでも同じですね。

ここでは、mydriveという名前のストレージのセットアップが完了したとしましょう(rcloneではこのように、NASGoogle Driveなど各種クラウドストレージに関する設定(認証情報などを含む)に名前をつけて管理できます)。すると、

rclone serve http --addr=localhost:15555 mydrive:/some/folder

とすることで、今設定したmydriveの/some/folderというフォルダがhttp://localhost:15555にて閲覧できるようになります。ブラウザにlocalhost:15555と入れてみて、ちゃんと動いているか確認してみましょう。

ポート番号は15555ではなくても何でもいいですが、root化していないので、1024以下は使えません。localhostのところは、0.0.0.0みたいに全アドレスをリッスンしてしまうとAndroidはデフォルトでファイアウォールがないのでフリーWi-Fiとかで他の人に音楽聴かれちゃいますので気を付けてください。

m3u8ファイルの生成

これで、あとはhttp://localhost:15555にあるファイルを好きに指定してプレイリストを書いてそれをmpvで開くだけです。しかし、聴きたいファイルを決めてからいちいちプレイリストを書くのはあまりにも不便すぎます。現実的なケースでは、連続して聴きたいファイルどうしは同じフォルダにあることがほとんどだと思うので、各フォルダ内に「フォルダにある音声ファイルを順番に全て再生する」だけのm3u8ファイルをあらかじめ作ってしまいましょう(m3uでなくm3u8にしたのは、なんとなく新しそうな気がしたからというだけで、そもそもm3uとm3u8がどう違うのか知らないです)。一度作ってしまえば、あとはそのフォルダ内の曲を聴くときにこのプレイリストを開くだけです。途中から再生したいときは、若干手間ですが全曲プレイリストをまず開いてからmpv-android上で曲を選び直しましょう。

HTTPでアクセスするので、URLはパーセントエンコーディングしておく必要があります。そのへんも考慮するとシェルスクリプトだと微妙そうだったので、pythonで書いてみました。というかChatGPTに書かせました。

#!/usr/bin/python3
import sys
import os
import shutil
import urllib.parse

temp_m3u8 = "/tmp/gen_m3u8_temp"
def urlencode(path):
    """
    Unicode 文字を含むパスを正しくパーセントエンコードする
    """
    return urllib.parse.quote(path)

def generate_m3u8(directory, base_url):
    """
    指定されたディレクトリに .m3u8 プレイリストを生成する
    """
    # 一時ファイルのパスを生成
   
    with open(temp_m3u8, 'w', encoding='utf-8') as f:
        f.write('#EXTM3U\n')
        for filename in os.listdir(directory):
            if filename.endswith('.flac') or filename.endswith('.mkv'):
                filepath = os.path.join(directory, filename)
                if os.path.isfile(filepath):
                    encoded_url = base_url + '/' + urlencode(filepath).replace('./', '',1)
                    f.write('#EXTINF:-1\n')
                    f.write(encoded_url + '\n')

def main():
    base_url = sys.argv[1]   # ベースURL

    # 対象のディレクトリを再帰的に探索し、プレイリストを生成
    for root, dirs, files in os.walk('.'):
        generate_m3u8(root, base_url)
        final_m3u8 = os.path.join(root, '0000LH15555.m3u8')
        shutil.move(temp_m3u8, final_m3u8)

if __name__ == '__main__':
    main()

こんな感じで、カレントディレクトリ(".")の中身を再帰的に検索し、見つかった各ディレクトリにおいてそこにある全てのファイルを羅列した.m3u8を生成しています。なお、クラウドストレージにファイルを作るだけなのでこのスクリプト自体はAndroid上で実行する必要はなく、自分は別のLinux上(rcloneでマウントしたGoogle Drive)で実行しました。rcloneの仕様なのか、( --vfs-cache-mode writesは付けてたんですが)直接書き込みするとうまくいかなかったので、一旦/tmp/gen_m3u8_tempという適当な名前で生成してからコピーしています。ファイル名が0000LH15555.m3u8となっているのはまあ適当ですが、音楽ファイルはすべて01-artist-title.flac的な名前になっているので、必ずプレイリストが先頭に表示されるようにこの名前にしました。LH15555はlocalhost:15555という意味で付けておきました。

コードを見ればわかりますが、localhost:15555という部分はコマンドライン引数として与えるようにしています。具体的には

m3u8.py http://localhost:15555

あるいは

m3u8.py http://localhost:15555/some/folder2

のように実行すればよいです(最後にスラッシュは無し)。さっきのrclone serve httpでの指定内容と整合するように各自で変えてください。

(RCXだと8080で固定なのが微妙と言っておいて)結局15555に固定してしまっていますが、ここは綺麗な代案が思いつかなかったのでとりあえず諦めました。

ファイルマネージャーから開く

これで、Termuxからrclone serve httpした状態でGoogle Driveからm3u8を選んでmpv-androidで開けばギャップレス再生できるのでほぼゴールなのですが、例えばGoogle Driveでm3u8を開くとサポートされていないファイル形式ですなどと言われて、mpv-androidを選ぶまでに2-3クリックかかってしまいます。なので筆者は現在RCXをそのままGoogle Drive用のファイルマネージャーとして使っています(デフォルトのアプリで直接開いてくれる)。Google Driveはメジャーなクラウドストレージなので他のアプリでも対応しているのがあるかなと思います。そちらでもいいでしょう。

まとめ

Google Drive以外では実は(NASさえ)試していないんですが、広範なクラウドストレージをAndroidでギャップレス再生する方法が確立されました。おそらく英語圏ですらほとんど知られていないやり方ではないかと思います。まあ需要がないんですかね…かくいう筆者も基本的にモバイルで音楽を聴くことは無い(外出中に音楽を聴かない)ので、これができたからといって特に日常生活で具体的なありがたみがあるわけではありません。あと、mpv-androidユーザーインターフェースはお世辞にも使いやすいとは言えないので、そのへんに改善があるといいのですが…(参考: [Discussion] UI improvements · Issue #554 · mpv-android/mpv-android · GitHub

とはいえ、理論上は(?)できることがわかったので、個人的には満足しました。あとは実際に誰かの役に立てば幸いです。