PowerShellでHiBiKi Radio Stationと音泉とRadikoタイムフリーを保存してPodcast形式で配る
ググったら響を保存する記事が見つかったのでPodcastアプリ経由で聴けるようにしました。
成果物
maeda577/VoiceActorRadioDownloader
- 機能
- WebラジオをダウンロードしてPodcast用RSSを生成する
- 対象サービス
- 響 - HiBiKi Radio Station -
- インターネットラジオステーション<音泉>
音泉は無料で視聴できるもののみ対応- (2021/01/23 追記)v0.3でPREMIUMも対応
- Radiko
- 2021/05/23 タイムフリーのみ対応
- エリアフリー機能とリアルタイム放送は非対応
- RadioTalk
- 2021/08/21 対応
- 放送によっては既にPodcastとして公開されているものがあります。公開済みの場合はそちらを参照してください。
- AG-ON Premium
- 2021/11/13 対応
- アカウントは必須です。事前に作成してください。
- 処理中にプレイリスト機能を使用します。最低1つはプレイリストの空きを確保してください。
- 無料放送のみテストしています。有料放送のダウンロードが可能かどうかは未検証です。
- 動作環境
- OS:Ubuntu20.04
- ソフトウェア:PowerShell, ffmpeg, Apache(Webサーバなら何でもいい)
- 2021/05/23 Windows10 + PowerShell 5.1環境での動作に対応
- 導入方法
- 上記URLのREADMEを参照
実装メモ:発端
- 手元環境だと響の再生がちょっと不調
- 半分ぐらい再生したところで音声が止まり、リロードすると復活する
- PowerShellで響をダウンロードする CannoHarito/save-hibiki-radio.bat を見つけた
- これだけでも十分なものの、せっかくならPodcast形式で聴けたら視聴位置の管理やらiPhoneへの自動転送やらできるなーって思い立つ
実装メモ:響
基本的には元となったGistをベースに作成
- 放送ページを開き
https://hibiki-radio.jp/description/<ここの文字列>/detail
をaccess_idとして使う https://vcms-api.hibiki-radio.jp/api/v1/programs/<access_id>
を叩くと放送データがjsonで返ってくる- ヘッダとして
'X-Requested-With', 'XMLHttpRequest'
が必須 - jsonの episode -> video -> id をvideo_idとして後続のStream取得APIに投げる
- 楽屋裏パートがあれば episode -> additional_video -> id に値があるので同じようにStream取得APIに投げる
- ヘッダとして
https://vcms-api.hibiki-radio.jp/api/v1/videos/play_check?video_id=<video_id>
を叩くとHLSのURLが得られる- ヘッダは同様に必須
- URLをffmpegに投げるとmp4が得られる
- 適宜metadataに放送データjsonで得られた情報を入れておく
- 映像部分が何かおかしい?っぽいので映像を無視する-vnオプションが要る
実装メモ:音泉
- 2020年7月ぐらいにリニューアルがあり、API仕様が変わったっぽい
- 以前は http://www.onsen.ag/api/shownMovie/shownMovie.json とかが使えたらしい
- MP3がそのままダウンロードできたらしいが今はHLS
- 色々変わったせいで既存の解説記事があんまり使えない
- 放送ページを開き
https://www.onsen.ag/program/<ここの文字列>
をdirectory_nameとして使う- 全放送が https://www.onsen.ag/web_api/programs/ で取れるが微妙に使いづらい
https://www.onsen.ag/web_api/programs/<directory_name>
を叩くと放送データがjsonで返ってくる- contents配列 -> streaming_url にHLSのURLが入っている
- 配列なので、過去の放送が公開されていればそのURLも得られる
- URLをffmpegに投げるとmp4が得られる
- 適宜metadataに放送データjsonで得られた情報を入れておく
- contents配列 -> media_type を見ると放送ごとに映像つき(movie)なのか音声のみ(sound)なのか判別できるので適宜ffmpegのパラメータを変える
(2021/01/23 追記) 実装メモ:音泉PREMIUM
- 必要なものは音泉PREMIUMを契約しているアカウント
- メールアドレスで作成したもの。ソーシャルアカウント連携で作成したアカウントは未検証
ログインの流れは以下の通り
- ログインJSONを次のような感じで準備
{"session":{"email":"<ログイン用メールアドレス>","password":"<パスワード>"}}
- ヘッダ準備。Content-Typeは多分必須
Access-Control-Allow-Origin: *
Content-Type: application/json; charset=utf-8
X-Client: onsen-web
- https://www.onsen.ag/web_api/signin にヘッダつけてログインJSONをPOST
- ログインJSONはUTF8で送る
- ログイン成功するとユーザー情報JSONが返され、同時にCookieが作成される
- JSONのpremiumを見ると契約状況が分かる
- Cookieには _session_id が入っている
- ログアウトはCookieをつけたまま https://www.onsen.ag/web_api/signout にPOST
- ヘッダは同様につける。POSTするデータは空でいい(メールアドレス等は不要)
- POSTするデータが無いからと言ってGETで叩くとエラーになる
ログイン後の処理はだいたい無償公開部分と同じ
- Cookieをつけたまま
https://www.onsen.ag/web_api/programs/<directory_name>
を叩くと放送データがjsonで返ってくる- contents配列 -> streaming_url にHLSのURLが入っている
- PREMIUM限定放送も同じように入っている
- URLをffmpegに投げるとmp4が得られる
- PREMIUM限定放送のHLSも無償放送と同じように認証かかっていないので気にせずffmpegに投げていい
- 認証なしってええんか…
- PREMIUM限定放送のHLSも無償放送と同じように認証かかっていないので気にせずffmpegに投げていい
(2021/05/23 追記) 実装メモ:Radikoタイムフリー
- 初めにAuthTokenを取得する。なぜか認証が2段階ある
https://radiko.jp/v2/api/auth1
に以下のヘッダをつけてGETする(値は割と何でもいいらしい?)- ‘X-Radiko-App’ = ‘pc_html5’;
- ‘X-Radiko-App-Version’ = ‘0.0.1’;
- ‘X-Radiko-User’ = ‘dummy_user’;
- ‘X-Radiko-Device’ = ‘pc’;
- レスポンスのヘッダに以下が入っている
- X-Radiko-AuthToken:今後使うトークン。まだ有効ではない
- X-Radiko-KeyOffset と X-Radiko-KeyLength:2段目の認証に使うもの。どっちも整数値
- パーシャルキーを準備する
- 固定文字列
bcd151073c03b352e1ef2fd66c32209da9ca0afa
の「KeyOffset文字目から、KeyLength個の文字」を切り取る - その文字をUTF8のバイト配列にしてからbase64でエンコードするとパーシャルキーになる
- 固定文字列
https://radiko.jp/v2/api/auth2
に以下のヘッダをつけてGETする- ‘X-Radiko-AuthToken’ = 1段目で取得したAuthToken
- ‘X-Radiko-PartialKey’ = 準備したパーシャルキー
- ‘X-Radiko-User’ = ‘dummy_user’;
- ‘X-Radiko-Device’ = ‘pc’;
- レスポンスに聴取エリア(TokyoとかNiigataとか)が入っていれば認証が通りAuthTokenが有効化されている
- 一緒にエリアIDも入っている。仮にJP13だったら東京エリア
- 現在のエリアで聴取できる放送局のIDを取得
http://radiko.jp/v3/station/list/<エリアID>.xml
をヘッダ無しで普通にGETする。AuthTokenは不要- XMLのstations -> station -> id が放送局のID
- ダウンロードする
https://radiko.jp/v2/api/ts/playlist.m3u8?station_id=<放送局ID>&l=15&ft=<開始時間>&to=<終了時間>
がHLSのURLになっているのでffmpegに渡す- ヘッダは ‘X-Radiko-AuthToken’ = 1段目で取得したAuthToken のみ指定
- 開始時間と終了時間はyyyyMMddHHmmss形式
- これだけだと不便なので番組表を組み合わせる
https://radiko.jp/v3/program/station/weekly/<放送局ID>.xml
をヘッダ無しで普通にGETすると週刊番組表が得られる- XMLのradiko -> stations -> station -> progs -> prog のアトリビュートに開始時間ftと終了時間toがあるので、これをHLSのURLに使うと便利
- PowerShell固有の話
- XMLを取得する際にInvoke-RestMethodを使うと豪快に文字化けする。Radikoではcharsetが指定されていないかららしい
- 以下URLの方法で対策する
(2021/08/21 追記) 実装メモ:RadioTalk
- 放送のURLを開き
https://radiotalk.jp/program/<ここの文字列>
をprogram_idとして使う https://radiotalk.jp/api/programs/<program_id>/talks
を叩くと放送データがjsonで返ってくる- 配列になっていて、各要素のaudioFileUrlにMP4のURLが入っている
- 配信データはHLSではなく普通のm4aなので、普通にInvoke-WebRequestでダウンロードできる
(2021/11/13 追記) 実装メモ:AG-ON Premium
- 放送のURLを開き
https://agonp.jp/programs/view/<ここの文字列>
をprogram_idとして使う - このURLを頑張ってスクレイピングしてリンク一覧を取得し
https://agonp.jp/play/<ここの文字列>
をepisode_idとして使う https://agonp.jp/api/v2/episodes/info/<episode_id>.json
を叩くと放送データがjsonで返ってくる- data -> episode -> video_id をダウンロードに使う
- ダウンロードにはログインが必要なので
https://agonp.jp/auth/login
にPOSTする- POSTする情報は
email=<ログイン用メールアドレス>&password=<パスワード>
みたいな感じ - User-Agentはそれっぽいものを指定する。PowerShell標準のだとログインは成功するがダウンロードがダメだった
- ログイン成功するとCookieに情報が入っているはず
- POSTする情報は
https://agonp.jp/api/v2/media/info.json?id=<video_id>&size=<サイズ指定>&type=<タイプ指定>
にCookieつけて叩けばダウンロードできる- サイズ指定は
large
かsmall
で、再生ページの[高画質]と[低画質]に相当 - タイプ指定は
mp4
かmp3
で、再生ページの[動画]と[音声のみ]に相当
- サイズ指定は
- ログアウトは
https://agonp.jp/auth/logout
にCookieつけてGETすれば良さそう
実装メモ:PodcastのRSS
- Podcast プロバイダのための RSS ガイドを見ると必須タグが分かるので頑張って埋める
- enclosureのType属性は真面目に埋める。適当に全部video/mp4にすると全部動画として認識されMacのPodcastアプリで挙動が変わってしまう
- 音声のみならaudio/x-m4a、動画つきならvideo/x-m4vが良さそう
- Google向けだと2. RSS フィードを作成するあたりが参考になりそう(未検証)
既知の不具合
- https://github.com/maeda577/VoiceActorRadioDownloader/issues
- iPhoneとmacのPodcastアプリだと、エピソードごとのタイトルが正しく出ない
- 音泉の過去放送分の日付情報とコメント部分がちゃんと入っていない
- contents配列に日付情報が入っていないのでどこかから取ってこないといけない