見出し画像

YouTube Live 24/7配信システムの構築

24時間365日 Pizuya's Cell の楽曲を垂れ流し続ける「Pizuya's Cell Arrange Radio」 の配信システムを構築しました.

ボーカルとインストの二種類あります.作業用BGMに最適ですので,ぜひお使いください.

これまで

24/7配信は特に海外リスナーが多く,Stay Home の背景もあってかこの1年でかなり数が増えたのではないかと思います.

Pizuyaもこの流れに乗るべく,2020年5月頃よりPizuyaさんが自宅のサーバーで24/7配信を開始しました.

しかし,だいたい数週間おきぐらいに配信が停止してしまい,その都度再起動などしていたそうです(おそらくネットワーク起因だと推測しています).

随時数人程度はリスナーがおり,配信が終了してしまったアーカイブ動画には,「なんてことだ,また止まっちまったぜ」みたいな海外リスナーからのコメントがよく残されていました.

思いつき

この配信システムをVPSに上げるだけなら簡単だし,特に面白みもなかったのですが,こちらの記事を見て「チャットと連携する配信にしたら面白いのでは?」と思い,

https://gigazine.net/news/20201225-twitch-controls-minecraft/

「楽曲リクエスト機能」を追加した24/7配信システムを構築することにしました.

画像9

(一日でできると思っているアホ)(実際は新年にギリギリ間に合った)

楽曲リクエスト機能

「psyc-0017/1」のように,CDのナンバリングとトラック番号をチャットに投げると,次に流れる曲がその曲になります.

画像10
画像1

複数のリクエストが投稿された場合は,ランダムにどれか1つが選ばれます.どの楽曲が選ばれたかは,公式アカウントからのチャットで確認できます.

コメントと共に楽曲がリクエストされることで,リスナー間で交流が生まれればいいなと思っています.

仕組み

大まかな構成はこのようになっています.

アートボード 1

まず,楽曲の再生が終わるごとに,Youtube Data API からチャットのデータを取得します.

画像3

チャットの取得はこちらの記事を参考にしました.

https://qiita.com/iroiro_bot/items/ad0f3901a2336fe48e8f

その後,チャットの中に「psyc-0017/1」といったリクエストがあるかチェックします.リクエストがなかったら単純に次のトラックに進み,複数あった場合はそのうちの1つをランダムに選びます.

次に,選ばれた楽曲のタイトルを,リクエストしてくれた方の名前とともにチャットに投稿します.

画像4

APIを使ったチャットの投稿は,こちらの公式リファレンスのサンプルコードをそのまま使いました.

https://developers.google.com/youtube/v3/live/docs/liveChatMessages/insert

次に,リクエストに従ってOBSの「曲名」「音源」「ジャケット画像」を差し替えます.

画像5

pythonからのOBSの制御には,こちらの記事を参考に「obs-websocket-py」を使用しました.

https://mokerdiary.hatenablog.com/entry/2019/09/03/000000

(obs-ws-rc は私の環境で上手く動かず,githubでのメンテも3年前ぐらいから停止しているようだったのでobs-websocket-pyにしました.)

obs-websocket-pyは実装の情報が少なく,かなり手探りでした.参考までに,私が実装したOBSを制御する部分の関数を載せます.OBSのソース名に対して,設定パラメータのオブジェクトを投げるイメージです.

from obswebsocket import obsws, requests as obsreq
from mutagen.mp3 import MP3 as mp3

def obs_control(track_list, cd_id, track_no):

   # track_list : DBから取得した楽曲情報の辞書リスト
   # cd_id : 'psyc-0017'など
   # track_no : int型の数値

   # mp3,ジャケットjpgのファイルパス
   mp3_filepath = CONTENT_DIR + '/' + cd_id + '/' + str(track_no).zfill(2) + '.mp3'
   jacket_filepath = CONTENT_DIR + '/' + cd_id + '/jacket.jpg'
   
   # 辞書リストから楽曲名を取り出す関数
   cd_title_text, track_no_text, track_title_text = get_title_text(track_list, cd_id, track_no)

   # 接続
   ws = obsws("localhost", OBS_WS_PORT, OBS_WS_PASS)
   ws.connect()

   # mp3,ジャケットjpgの差し替え
   ws.call(obsreq.SetSourceSettings('audio', {'local_file': mp3_filepath}))
   ws.call(obsreq.SetSourceSettings('jacket', {'file': jacket_filepath}))
   
   # トラック名の差し替え
   track_title_settings = ws.call(obsreq.GetSourceSettings('track_title')).getSourceSettings()
   track_title_settings['text'] = track_title_text
   ws.call(obsreq.SetSourceSettings('track_title', track_title_settings))
   
   # CD情報の差し替え
   cd_title_settings = ws.call(obsreq.GetSourceSettings('cd_title')).getSourceSettings()
   cd_title_settings['text'] = 'from ' + cd_title_text + ' (' + cd_id + ') / Tr. ' + str(track_no_text).zfill(2)
   ws.call(obsreq.SetSourceSettings('cd_title', cd_title_settings))

   # 切断
   ws.disconnect()

   # mp3の長さだけsleep
   mp3_length = mp3(mp3_filepath).info.length
   time.sleep(mp3_length)
 

OBSのソースはこんな感じです.

画像6

あとはひたすら一連の処理をループするだけですが,実は処理の一番最初に一回だけ楽曲のデータベースをスプレッドシートから取得しています.

画像7

楽曲のリストはこんな感じです.チームぴずやの皆にも情報入力を手伝ってもらいました(データメンテと楽曲・ジャケットデータの整理で丸一日つぶれました・・・).

画像8

pythonからスプレッドシートを見に行く際には「gspread」というライブラリを使用しました.

https://gspread.readthedocs.io/en/latest/

死活監視システム

これまでは,配信が何らかの理由で止まってしまったときに,なかなか気づくことができないという課題がありました.

私も新システムが稼働した最初の夜は気が気で眠れず,これは死活監視を構築しないと精神が死ぬと思い,実装を決めました.

スクリプトのエラーは検知できますが,配信が止まる理由はほぼほぼOBSとyoutube間のエラーなので,配信映像の最終出力から放送事故を検知できないか考えました.

詳細は割愛しますが,実装した内容は以下の通りです.

・youtube-dlを用いてライブ配信のURLを取得(視聴画面のURLではない.ここのレスポンスの形式でライブ配信の終了が検知できるので,その場合はslackにアラートを発信)

・先ほどのURLをffmpegの引数にして,30秒だけmp3で配信の音声を取得

・mp3の振幅を確認し,無音だったらslackにアラート

・一つ前のmp3と波形を比較し,まったく同一だった場合はOBSからのストリームが切れているので(画面がぐるぐるしている状態)slackにアラート

・繰り返し

この死活監視スクリプトは先ほどの配信用 windows server とは別の linuxサーバで動作させています.

これで安心して夜眠ることができます.

おわりに

youtubeというプラットフォームを使って,インタラクティブなコンテンツを作ってみたいと以前から思っており,とても楽しく実装できました.

ここまで読んでいただきありがとうございました.

ぜひ「Pizuya's Cell Arrange Radio」に遊びに来てくださいね!



いいなと思ったら応援しよう!