前回、聴き逃しサービスの番組へのリンクからJSONデータを取り出し、そこに記されたタイトルや配信元URLを使ってffmpegで番組をダウンロードするためのスクリプトを書いてみました。
このスクリプトでいくつかの番組をダウンロードしてみたところ、スクリプト自体はそれなりに動いて必要な情報を取れてはいるものの、番組のダウンロードはしばしば失敗してしまいます。
$ python ./json_01.py 'p=0308_01_3844917' ffmpeg version 4.3.3 Copyright (c) 2000-2021 the FFmpeg developers ... Input #0, hls, from 'https://vod-stream.nhk.jp/radioondemand/r/308/s/ stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8': Duration: 00:10:00.06, start: 1.999989, bitrate: 0 kb/s Program 0 Metadata: variant_bitrate : 48601 Stream #0:0: Audio: aac (HE-AAC), 48000 Hz, stereo, fltp, 47 kb/s ... Output #0, mp4, to '名曲スケッチ「悲愴交響曲_第2楽章」_「“眠りの森の美女” から“アダージョ”」.mp4': Metadata: encoder : Lavf58.45.100 Stream #0:0: Audio: aac (HE-AAC) (mp4a / 0x6134706D), 48000 Hz, stereo, fltp, 47 kb/s ... [aac_adtstoasc @ 0x16ffa80] Multiple RDBs per frame with CRC is not implemented. Update your FFmpeg version to the newest one from Git. If the problem still occurs, it means that your file has a feature which has not been implemented. [mp4 @ 0x1734080] Error applying bitstream filters to an output packet for stream #0: Not yet implemented in FFmpeg, patches welcome av_interleaved_write_frame(): Not yet implemented in FFmpeg, patches welcome size= 278kB time=00:00:48.12 bitrate= 47.3kbits/s speed=74.9x video:0kB audio:283kB subtitle:0kB other streams:0kB global headers:0kB muxing overhead: unknown Conversion failed!
あれこれオプションを指定したり、ffmpegの最新版を試してみても結果は同じだったので、どうやらエラーメッセージにあるようにffmpegが想定しているHLSの仕様
マルチメディア・フレームワーク GStreamer
ffmpeg以外のマルチメディアツールは…… と考えて、思いついたのがGStreamerです。GStreamerは
GStreamerでは、FLAC形式の音声データを扱う"libgstflac.
GStreamerでどのような機能を使えるかはgst-inspect-1.
$ gst-inspect-1.0 vaapi: vaapijpegdec: VA-API JPEG decoder vaapi: vaapimpeg2dec: VA-API MPEG2 decoder vaapi: vaapih264dec: VA-API H264 decoder ... staticelements: bin: Generic bin staticelements: pipeline: Pipeline object Total count: 239 plugins (1 blacklist entry not shown), 1484 features
さて、まずはGStreamerで聴き逃しサービスが利用できるか試してみましょう。あるファイルやURLがGStreamerで利用できるかはgst-play-1.
$ gst-play-1.0 https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8 Press 'k' to see a list of keyboard shortcuts. Now playing https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8 Redistribute latency... 0:00:08.3 / 0:10:00.0
GStreamerで聴き逃しサービスの配信に対応できそうなので、次はどのモジュールを組み合わせてURIから音声データをダウンロードするかを考えます。そのために配信元の情報をgst-discover-1.
$ gst-discover-1.0 https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8 Analyzing https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8 Done discovering https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8 Properties: Duration: 0:10:00.064000000 Seekable: yes Live: no container: application/x-hls container: MPEG-4 AAC audio: MPEG-4 AAC Stream ID: 04a7001f96985b910726b6f477b215306525d31d14dbf07508e8c8ac47336f3a/src_0 Language: <unknown> Channels: 2 (front-left, front-right) Sample rate: 48000 Depth: 32 Bitrate: 48617 Max bitrate: 0
"gst-discover-1.
GStreamerの使い方
それでは必要なモジュール類を考えてみます。今回はURLを指定してデータをダウンロードするので、まずはHTTPを処理するためのモジュールが必要です。"gst-inspect-1.
$ gst-inspect-1.0 | grep http curl: curlhttpsink: Curl http sink curl: curlhttpsrc: HTTP Client Source using libcURL neonhttpsrc: neonhttpsrc: HTTP client source soup: souphttpsrc: HTTP client source soup: souphttpclientsink: HTTP client sink
HLS回りを処理するモジュールも確認しておきましょう。こちらはHLS形式で送られてきたデータを処理するhlsdemux一択のようです。
$ gst-inspect-1.0 | grep hls libav: avmux_hls: libav Apple HTTP Live Streaming muxer typefindfunctions: application/x-hls: m3u8 hls: hlsdemux: HLS Demuxer hls: hlssink: HTTP Live Streaming sink hls: hlssink2: HTTP Live Streaming sink
pulseaudioやGStreamerといったマルチメディア用のソフトウェアでは、source
一方、mux
GStreamerでは、gst-launch-1.
$ gst-launch-1.0 curlhttpsrc location='https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8' \ ! hlsdemux ! decodebin ! audioconvert ! pulsesink パイプラインを一時停止 (PAUSED) にしています... Pipeline is PREROLLING ... Got context from element 'souphttpsrc1': gst.soup.session=context, session=(SoupSession)NULL, force=(boolean)false; Redistribute latency... Pipeline is PREROLLED ... パイプラインを再生中 (PLAYING) にしています... New clock: GstPulseSinkClock 0:00:05.1 / 0:10:00.0 (0.9 %)
使ったモジュールを簡単に紹介すると、curlhttpsrcは前述のようにlibcurlを使って"location=..."で指定したURIからデータをダウンロードするモジュールで、hlsdemuxでそこから動画や音声を取りだし、decodebinで取り出した動画や音声をGStremaerが使う汎用的な形式
$ gst-inspect-1.0 curlhttpsrc Factory Details: Rank secondary (128) Long-name HTTP Client Source using libcURL Klass Source/Network Description Receiver data as a client over a network via HTTP using cURL ... location : URI of resource to read flags: 読み込み可能, 書き込み可能 String. Default: null ... user-pw : HTTP location URI password for authentication flags: 読み込み可能, 書き込み可能 String. Default: null
上記コマンドで音声データの再生ができたので、後は出力先をファイルにすれば何とかなりそうです。そのためには"pulsesink"の代わりにfilesinkを使い、"location=..."の指定でファイルに落します。その際、"AAC"形式の音声データを"MP4"なコンテナに格納することにします。
"AAC
そのためには、音声データをfaacでAAC形式に変換し、mp4muxでMP4形式のコンテナに格納してから、filesinkでファイルに出力することになり、先のコマンドラインの"pulsesink"の部分をこんな風に書き変えました。
$ gst-launch-1.0 curlhttpsrc location='https://vod-stream.nhk.jp/radioondemand/r/308/s/stream_308_5725b4a0be55d9c3c4d52e4c954d77c4/index.m3u8' \ ! hlsdemux ! decodebin ! audioconvert ! faac ! mp4mux ! filesink location='testfile.mp4' パイプラインを一時停止 (PAUSED) にしています... Pipeline is PREROLLING ... Got context from element 'souphttpsrc1': gst.soup.session=context, session=(SoupSession)NULL, force=(boolean)false; Pipeline is PREROLLED ... パイプラインを再生中 (PLAYING) にしています... New clock: GstSystemClock Got EOS from element "pipeline0". Execution ended after 0:00:08.330292008 Setting pipeline to NULL ... Freeing pipeline ...
上記コマンドラインでダウンロードしたファイルを調べてみると、正しくMP4形式になっていて、愛用している音楽プレイヤーAudaciousでも再生できました。
$ file testfile.mp4 testfile.mp4: ISO Media, MP4 v2 [ISO 14496-14]
後はこのコマンドを前回のスクリプトに組み込んで、番組へのリンクから配信データをダウンロードできるようにすればいいわけですが、だいぶ長くなってしまったので、そのあたりは次回に回しましょう。
本文中でも触れたように、今回取りあげたGStreamerは
以前紹介した"ffmpeg"は出力先の拡張子に応じて入力データを自動的に変換してくれるのに対し、GStreamerでは
しかし多少慣れてくると、状況や目的に応じたモジュールの組み合わせを考えるのが謎解きパズルのように感じられ、あれこれ試行錯誤するのが楽しくなってきます。このあたりの感覚は