youtube-dlというCLIツールがあります。Pythonで記述されたプログラムで、pip経由でもパッケージマネージャー経由でもインストールできます。名前はYouTubeとついていますが、YouTube以外の動画配信サービスにも対応しています。

youtube-dlを使用していると、時々困ることがあります。それはダウンロードされる動画の形式です。webmだったりmkvだったり、少なくとも私には見慣れないファイル形式でダウンロードされることがあります。もちろんyoutube-dl -f mp4 hogeのように書けばフォーマットは指定できますが、webm形式の謎は残ります。ということで今回はファイル形式に着目し、youtube-dlがどのような仕組みでyoutubeにアクセスしているかを調査したいと思います。

準備

macユーザーの場合はbrewを使うのが簡単です。
# インストール
$ brew install youtube-dl
# 念のためアップデート
$ youtube-dl -U
# バージョン確認
$ youtube-dl --version

執筆時点(2021年1月9日)での最新版は2021.01.08です。頻繁にメンテナンスされていることが分かります。

次に動画、音声ファイルの変換のために ffmpegをインストールします。

# インストール
$ brew install ffmpeg
$ ffmpeg -version

実行方法

バージョンが確認できたらまずは動画をダウンロードしてみます。コマンドは簡単で、 youtube-dl "ダウンロードしたい動画のURL"でダウンロードできます。今回はYoutube最古の動画「Me at the zoo」を指定して実行してみます。

$ youtube-dl "https://www.youtube.com/watch?v=jNQXAC9IVRw"

ダウンロードできたらファイル形式を見てみます。

Me at the zoo-jNQXAC9IVRw.webm
のようになっていると思います。ん?webm?見慣れない形式ですね。webmについて調べると、こんな記事を見つけました。YouTube、全動画をWebM形式に変換すると発表 どうやらYoutubeでは10年前から動画の保存形式としてwebmを採用しているようです。 他にはどのような形式でダウンロードできるのかを--list-formatオプションで調べてみます。

$ youtube-dl --list-format "https://www.youtube.com/watch?v=jNQXAC9IVRw"

出力は以下のようになりました。

format code  extension  resolution note
249          webm       audio only tiny   51k , opus @ 50k (48000Hz), 118.67KiB
250          webm       audio only tiny   67k , opus @ 70k (48000Hz), 153.83KiB
140          m4a        audio only tiny   95k , m4a_dash container, mp4a.40.2@128k (44100Hz), 221.02KiB
251          webm       audio only tiny   98k , opus @160k (48000Hz), 225.23KiB
394          mp4        192x144    144p   62k , av01.0.00M.08, 15fps, video only, 137.63KiB
160          mp4        192x144    144p   66k , avc1.4d400b, 15fps, video only, 141.17KiB
278          webm       192x144    144p   70k , webm container, vp9, 15fps, video only, 158.42KiB
133          mp4        320x240    240p  124k , avc1.4d400c, 15fps, video only, 265.85KiB
395          mp4        320x240    240p  138k , av01.0.00M.08, 15fps, video only, 316.81KiB
242          webm       320x240    240p  165k , vp9, 15fps, video only, 378.64KiB
18           mp4        320x240    240p  334k , avc1.42001E, 15fps, mp4a.40.2@ 96k (44100Hz), 772.00KiB (best)

webmの他にもmp4、m4aの形式でダウンロードできることが分かります。youtube-dlでは特にオプションを指定しないと、最高音質、最高画質の動画をダウンロードするようになっています。しかし、今回は一番下にある(best)と表示されたmp4形式ではなく、webm形式の動画がダウンロードされました。これは何故でしょうか。もう一度先ほどの動画をダウンロードするコマンドを叩き、今度はターミナルの出力に注目してみます。

$ youtube-dl "https://www.youtube.com/watch?v=jNQXAC9IVRw"
[youtube] jNQXAC9IVRw: Downloading webpage
 Destination: Me at the zoo-jNQXAC9IVRw.f242.webm
 100% of 378.64KiB in 00:00
 Destination: Me at the zoo-jNQXAC9IVRw.f251.webm
 100% of 225.23KiB in 00:00
[ffmpeg] Merging formats into "Me at the zoo-jNQXAC9IVRw.webm"
Deleting original file Me at the zoo-jNQXAC9IVRw.f242.webm (pass -k to keep)
Deleting original file Me at the zoo-jNQXAC9IVRw.f251.webm (pass -k to keep)

ログを読むと、どうやら

Me at the zoo-jNQXAC9IVRw.f242.webm
Me at the zoo-jNQXAC9IVRw.f251.webm
の2つのファイルをダウンロードしてそれらをffmpegで合成して一つのファイルにしていることが分かります。.webmの前のf241などの番号は--list-formatで表示されたformat codeに対応しています。

242          webm       320x240    240p  165k , vp9, 15fps, video only, 378.64KiB
251          webm       audio only tiny   98k , opus @160k (48000Hz), 225.23KiB

今回選ばれた二つのファイルはそれぞれvideo only、audio onlyの出力があります。youtube-dlは(best)のmp4を選択するよりも、webm形式のこれら二つの動画、音声ファイルを結合した方が最高音質、最高画質の動画になると判断したようです。実際に(best)と表示されているmp4形式と比べて、音質は48100Hzと44100Hz、画質も高画質なvp9、低画質なavc1とそれぞれの項目で最善な形式が選択されていることが分かります。

youtube-dlが裏で何をやっているのか

動画のダウンロードの仕組みが分かったところで、youtube-dlがこれらの形式をどのように取得しているかを調査します。まずは--list-formatで表示されたファイルをどのように取得しているかです。Youtubeの動画URLを取得する方法を探していると、以下の記事を発見しました。Youtubeの動画URLを取得するSwiftコードを書いた

http://www.youtube.com/get_video_info?video_id=
のようなURLを叩くと、動画の情報を取得でき、そこからその動画に対応しているフォーマットの一覧が取得できるようです。youtube_dlのGithubリポジトリで「get_video_info」を検索してみると、https://github.com/ytdl-org/youtube-dl/blob/4759543f6e5d532795eb1d5434692bb6d5e1f0ec/youtube_dl/extractor/youtube.py#L1705 でこのURLが使われていることがわかります。どうやらyoutube_dlでもこの方法で動画のURLを取得しているようです。動画URLの取得方法が分かったところで、先ほどの動画に対して叩いてみます。

http://www.youtube.com/get_video_info?video_id=jNQXAC9IVRw

すると、「get_video_info」という名前のテキストファイルがダウンロードされました。このファイルはパーセントエンコードされているため、デコードします。デコードしたファイルの中を見ると、どうやらitagというキーが--list-formatオプションのformat codeに対応し、複数の動画URLが入っているようです。ファイル内検索で「itag」を検索し、出てきた部分を一部抜粋します。

"adaptiveFormats":[
{"itag":133,"url":"https://r2---sn-ogul7n76.googlevideo.com/videoplayback?expire=1610521134\u0026ei=zkX-X9aRGsGWgQOCnpDQCQ\u0026ip=2404%3A7a80%3A97c0%3A1c00%3Ac1d1%3A7380%3A26bc%3Ad37c\u0026id=o-ABmGDNYqh6Zt1aNYpMRyvIlNdbpSHqQicgz1ATTcAPVy\u0026itag=133\u0026aitags=133%2C160%2C242%2C278%2C394%2C395\u0026source=youtube\u0026requiressl=yes\u0026mh=VD\u0026mm=31%2C29\u0026mn=sn-ogul7n76%2Csn-ogueln7r\u0026ms=au%2Crdu\u0026mv=m\u0026mvi=2\u0026pl=29\u0026initcwndbps=1533750\u0026vprv=1\u0026mime=video%2Fmp4\u0026ns=EEDqAGwOdl_KBuYDSUzYdVcF\u0026gir=yes\u0026clen=272229\u0026dur=18.933\u0026lmt=1524502649984547\u0026mt=1610499396\u0026fvip=2\u0026keepalive=yes\u0026c=WEB\u0026n=3xPqXwoziDQ0lQ\u0026sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt\u0026sig=AOq0QJ8wRQIhAKaJEQAIx9GOUGn6NqJZhuvsF51V_DrFANVAhv0ikSF0AiACNqb0k6K2rI_vBNVhNK9u0PJ3tGG_sLgchxa5Wv8giw%3D%3D\u0026lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps\u0026lsig=AG3C_xAwRAIgSWwYQsjOFqLGRDUr_vxElCDEhKZTdA-N3waawslUH1oCIGGLtcvYenjTBr3C-5Fk3idMuHV8qMYMTCcPZce2-Xm0","mimeType":"video/mp4; codecs=\"avc1.4d400c\"","bitrate":124891,"width":320,"height":240,"initRange":{"start":"0","end":"712"},"indexRange":{"start":"713","end":"792"},"lastModified":"1524502649984547","contentLength":"272229","quality":"small","fps":15,"qualityLabel":"240p","projectionType":"RECTANGULAR","averageBitrate":115028,"approxDurationMs":"18933"},
{"itag":242,"url":"https://r2---sn-ogul7n76.googlevideo.com/videoplayback?expire=1610521134\u0026ei=zkX-X9aRGsGWgQOCnpDQCQ\u0026ip=2404%3A7a80%3A97c0%3A1c00%3Ac1d1%3A7380%3A26bc%3Ad37c\u0026id=o-ABmGDNYqh6Zt1aNYpMRyvIlNdbpSHqQicgz1ATTcAPVy\u0026itag=242\u0026aitags=133%2C160%2C242%2C278%2C394%2C395\u0026source=youtube\u0026requiressl=yes\u0026mh=VD\u0026mm=31%2C29\u0026mn=sn-ogul7n76%2Csn-ogueln7r\u0026ms=au%2Crdu\u0026mv=m\u0026mvi=2\u0026pl=29\u0026initcwndbps=1533750\u0026vprv=1\u0026mime=video%2Fwebm\u0026ns=EEDqAGwOdl_KBuYDSUzYdVcF\u0026gir=yes\u0026clen=387725\u0026dur=18.933\u0026lmt=1524503121231492\u0026mt=1610499396\u0026fvip=2\u0026keepalive=yes\u0026c=WEB\u0026n=3xPqXwoziDQ0lQ\u0026sparams=expire%2Cei%2Cip%2Cid%2Caitags%2Csource%2Crequiressl%2Cvprv%2Cmime%2Cns%2Cgir%2Cclen%2Cdur%2Clmt\u0026sig=AOq0QJ8wRAIgcxhQ_wt5n7PudDeYhpxuMSO24HhmgNz2BdUq6PGQeFoCIBQhWAdDIGUu5SkY0OubQSrWAPHM_m83FqFisDd65eAB\u0026lsparams=mh%2Cmm%2Cmn%2Cms%2Cmv%2Cmvi%2Cpl%2Cinitcwndbps\u0026lsig=AG3C_xAwRAIgSWwYQsjOFqLGRDUr_vxElCDEhKZTdA-N3waawslUH1oCIGGLtcvYenjTBr3C-5Fk3idMuHV8qMYMTCcPZce2-Xm0","mimeType":"video/webm; codecs=\"vp9\"","bitrate":165017,"width":320,"height":240,"initRange":{"start":"0","end":"198"},"indexRange":{"start":"199","end":"264"},"lastModified":"1524503121231492","contentLength":"387725","quality":"small","fps":15,"qualityLabel":"240p","projectionType":"RECTANGULAR","averageBitrate":163830,"approxDurationMs":"18933"},
(以下略)...

JSON形式でadaptive_formatsに配列で格納されているデータがYoutube動画のURLになっています。これでダウンロードする動画のURL取得方法が分かりました!ちなみに、youtube-dlには--write-info-jsonオプションがあり、ダウンロードした動画の情報を別途jsonファイルにしてダウンロードしてくれます。このjsonファイルを見ると、youtube-dlがget_video_infoで取得したデータをどのようにパースしてコーデックや解像度の情報を表示しているかが分かります。

まとめ

youtube-dlが実行されるとき

  1. get_video_infoのURLを叩いて動画の情報が入ったファイルを取得する。
  2. 取得したファイルをデコード、パースする。
  3. ユーザーの指定するオプションに応じて最適なファイルを決定してダウンロードする。

という手順を踏んでいるようです。

参考

Pocket