はじめに
自宅ベランダに巣を作ったコシアカツバメを、ラズベリーパイ+USBカメラで24時間ライブ配信しています。2年目のシーズンに入り、配信システムをかなり作り込んできたのですが、「運用の手間」という大きな壁にずっと悩まされていました。
このあたりにまとめてます。
https://take1bit.com/category/tsubameこの記事は、その手間を解消するために YouTube Live Streaming API を導入し、Telegramボットでスマホからの遠隔操作を実現するまでの記録です。最終的にできあがった仕組みはこんな感じです。
- 毎朝5:00に自動で配信開始、19:00に自動終了
- YouTube Liveの12時間制限を回避するため、8時間ごとに配信枠を自動分割
- Telegramボットでスマホからいつでも配信の開始・停止・状態確認
- 画面には時刻、トピック、気象情報(気温・湿度・気圧)をリアルタイム表示
- Tailscale VPN経由で出先からでも操作可能
何もしなくても毎日勝手に配信され、保存用のアーカイブ動画もできるので、かなり快適になりました。

ソースコードはGitHubで公開しています:https://github.com/hiirofish/bird-watching-youtube-streamer
#1 困りごと
それまでの配信システムは、Python+ffmpegでYouTubeにRTMP配信するという構成で、映像自体は問題なく動いていました。しかし運用面でいくつかの困りごとがありました。
困りごと① 12時間以上の配信はアーカイブをダウンロードできない
YouTube Liveには「12時間を超えた配信はアーカイブのダウンロードが不可になる」という仕様があります。観察記録として手元に残したいので、配信を分割する必要がありました。
困りごと② 配信終了の手動操作がストレス
ラズパイ側から終了通知は送っているものの、YouTube Studioで「ライブ配信を終了」ボタンを押さないと終了処理が完了しないことがありました。押さずに済むときもあるのですが、押し忘れると翌日の配信が始まらない。結局、毎日手動でボタンを押す運用に落ち着いていました。小さな手間ですが、毎日となると地味にストレスです。
つまり、配信開始はラズパイ側(cronで自動化)、配信終了はスマホからYouTube Studioを開いて手動という、起点がバラバラの運用です。しかもこの終了ボタンはスマホアプリ版のYouTube Studioには無く、スマホのブラウザでStudioを開く必要があります。タイミングもシビアで、押すチャンスを逃すとその日の配信が崩れてしまう。出先でこのタイミングを取り損ね、その日のアーカイブをまるごとダメにした苦い経験もあります。
困りごと③ 出先で帰れないときに詰む
手動操作が必要ということは、出先から帰れない日は配信を制御できないということです。これが最大の問題でした。
#2 解決の方針
問題を整理した結果、2つの軸で攻めることにしました。
軸1:YouTube Live Streaming APIで配信を完全自動化
YouTube Studioでポチポチやっていた操作をすべてAPIで制御します。配信枠(Broadcast)の作成・終了、ストリームキーの取得まで全部APIから行い、手動操作を完全になくします。
軸2:Telegramボットでスマホから遠隔操作
出先からでもスマホのボタン一発で操作できるように、Telegramボットを作ります。メッセージアプリのボットですが、IoT操作と非常に相性が良いです(詳しくは#6で)。
#3 YouTube API基盤の構築
GCPプロジェクトの作成
まずGoogle Cloud Platform(GCP)でプロジェクトを作り、YouTube Data API v3を有効化します。

手順は以下の通りです。
- Google Cloud Console でプロジェクトを新規作成
- 「APIとサービス」→「ライブラリ」→「YouTube Data API v3」を有効化
- 「OAuth同意画面」を設定(次項で詳述)
- 「認証情報」→「OAuthクライアントID」を作成(種類は「デスクトップアプリ」)
client_secret.jsonをダウンロードし、ラズパイに配置
OAuth同意画面:「内部」と「外部」の違い
ここは一般ユーザーにとって最もつまずきやすいポイントです。OAuthの同意画面設定で「内部」と「外部」を選ぶ必要がありますが、選択によってトークンの有効期限が大きく変わります。
「内部」(Internal)を選べる場合
Google Workspaceアカウント(有料の独自ドメインGoogleアカウント)を使っている場合のみ選択可能です。メリットは大きく、テストユーザーの追加が不要、Googleのアプリ審査も不要、そしてリフレッシュトークンが無期限です。私はWorkspaceアカウントで運用しているため「内部」を選びました。
「外部」(External)しか選べない場合(一般のGmailアカウント)
ほとんどの個人ユーザーはこちらです。「外部」で「テスト」ステータスのままだと、リフレッシュトークンが7日で失効します。つまり1週間ごとにブラウザで再認証が必要になり、自動化の意味がなくなります。
回避策は2つあります。
- アプリを「公開」ステータスに昇格させる:Googleの審査(プライバシーポリシーの用意、OAuth検証プロセス)を通す必要がありますが、通ればトークンは無期限になります。自分しか使わないアプリでも公開申請は可能です。
- サービスアカウント+ドメイン全体の委任:これはWorkspace環境が必要なので、一般ユーザー向けではありません。
個人のGmailアカウントで運用する場合は、公開審査を通すのが現実的な選択肢です。審査には数日〜数週間かかるので、早めに申請しておくことをおすすめします。
初回認証のコツ
初回のOAuth認証はブラウザでGoogleにログインする必要があります。ラズパイにSSHで接続しているとブラウザが使えないので、VNCでラズパイのデスクトップにログインし、GUIブラウザで認証します。ここだけは物理(またはVNC)アクセスが必要です。
必要なPythonパッケージは以下の通りです。
pip install google-auth google-auth-oauthlib google-auth-httplib2 google-api-python-client
#4 enableAutoStart / enableAutoStop の設計判断
YouTube Live Streaming APIで配信枠(Broadcast)を作成するとき、enableAutoStart と enableAutoStop の2つのフラグを設定します。この組み合わせで配信の挙動が大きく変わるのですが、APIドキュメントだけでは実際の挙動が分かりにくいので、整理しておきます。
4パターンの組み合わせ
| autoStart | autoStop | 動作 | 向いている用途 |
|---|---|---|---|
| true | true | ffmpegが映像を送ると自動で公開、止めると自動で終了 | シンプルな配信。ffmpegの再起動がない場合 |
| true | false | ffmpegが映像を送ると自動で公開、終了はAPI経由で明示的に行う | 今回の採用パターン。ffmpegの再起動がある場合 |
| false | true | APIで明示的に公開、止めると自動で終了 | プレビュー確認してから公開したい場合 |
| false | false | 公開も終了もAPI経由 | 完全手動制御 |
なぜ autoStart=true, autoStop=false にしたか
autoStart=true を選んだ理由はシンプルさです。ffmpegが起動して映像を送り始めれば自動で公開されるので、「プレビュー → 公開」という段階を挟む制御が不要になります。
autoStop=false にしたのは、このシステム固有の事情です。配信中に以下のタイミングでffmpegプロセスを再起動する設計になっています。
- 8時間ごとの自動再接続(YouTube 12時間制限対策)
- テロップ更新時(トピック・訪問情報などのテキストファイルが変更されたらffmpegを再起動して反映)
autoStop=true だと、この再起動中の数秒〜十数秒のデータ途切れでYouTubeが「配信終了」と判断し、Broadcast枠がcloseされてしまいます。鳥が来るたびに訪問情報が更新され、そのたびに配信が切れる——これでは使い物になりません。
autoStop=false にすれば、ffmpegが再起動してもBroadcast枠はliveのまま待機し、数秒後に映像が復帰します。配信の終了は、API(end_broadcast)を明示的に呼んで確実に行う設計にしました(実装は GitHub の streamer.py を参照)。
ただし autoStop=false にはリスクがあります。スクリプトが end_broadcast を呼ばずに死ぬと、枠が永遠にliveで残ります。この問題は実際に起きたので、#8で詳しく書きます。
#5 外部アクセス:Tailscaleの導入
出先からラズパイを操作するには、外部からアクセスする手段が必要です。TailscaleというVPNサービスを導入しました。
なぜTailscaleか
- ルーターのポート開放が不要(NAT越えを自動で処理)
- 固定IPもDDNSも要らない
- WireGuardベースで暗号化
- 個人利用は無料(最大100デバイス)
- セットアップが5分で終わる
セットアップ手順
ラズパイ側:
# Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
# Start and authenticate
sudo tailscale up
# Enable Tailscale SSH (optional but convenient)
sudo tailscale set --ssh
tailscale up を実行するとURLが表示されるので、ブラウザで開いてGoogleアカウント等でログインします。

#6 スマホ遠隔操作:Telegram Botの構築
なぜTelegramか
IoTデバイスの遠隔操作手段として、Telegramは非常に相性が良いです。Bot APIが充実しており、ボタン付きのインタラクティブなUIをメッセージとして送れます。LINEのBotと違ってサーバーを立てる必要がなく(ポーリング方式で動く)、ラズパイの中だけで完結します。
https://core.telegram.org/bots/api
Bot作成手順
1. @BotFather でBotを作成
Telegramアプリで @BotFather を検索してチャットを開き、/newbot と送信します。Bot名とユーザー名を聞かれるので入力すると、APIトークンが発行されます。このトークンをラズパイのスクリプトで使います。

2. 自分のチャットIDを取得
@userinfobot にメッセージを送ると、自分のチャットIDが返ってきます。このIDをスクリプトに設定し、自分以外が操作できないように制限します。
3. python-telegram-bot のインストール
pip install python-telegram-bot --break-system-packages
4. ボタン付きメニューの実装
最終的に以下のボタンを実装しました。
- 🚀 配信開始(今すぐ手動配信)
- ⏹ 配信停止(ffmpeg停止+YouTube API経由で枠も終了)
- 📅 自動予約確認(スケジュール設定の確認)
- 📊 状態確認(配信中かどうか、現在の枠ID)
- 📋 ログ確認(直近のログをスマホに表示)
「サービスが裏で動いていると状態が見えない」という不安を解消するために、ログと状態をスマホから確認できるようにしたのがお気に入りポイントです。

セキュリティ上の注意
チャットIDによる制限は最低限入れてください。BotのAPIトークンが漏れた場合でも、チャットID制限があれば第三者がBotを操作できません。
# Example: restrict bot access by chat ID
ALLOWED_CHAT_ID = 123456789
async def handle_command(update, context):
if update.effective_chat.id != ALLOWED_CHAT_ID:
return # ignore unauthorized users
# ... process command
#7 気象テロップのリアルタイム更新
配信画面に気温・湿度・気圧をリアルタイム表示するために、気象センサー(SHT30 + BMP180)のデータを表示する仕組みを追加しました。ハードウェアやセンサーの詳細は別記事にまとめています。
👉 https://take1bit.com/computer-ja/post-2372/
当初はffmpegの drawtext フィルタの textfile + reload=1(フレームごとにファイルを再読み込み)で実装しましたが、約2時間後にffmpegがフリーズする問題が発生しました。ffmpeg 5.1.6の drawtext reload 実装にファイル置換との競合がある、既知の問題系統です。
解決策として zmqフィルタ を導入しました。ffmpegの実行中にZeroMQソケット経由でテキストを差し替える仕組みで、ファイルI/Oを一切使いません。Python側から drawtext@weather text '21°C 82% 1002hPa' を送ると、ffmpegを止めずにテキストだけが即座に切り替わります。
なお、テロップの区切りには全角スペースを使っています。zmqのコマンドパーサが半角スペースを単語の区切りと解釈してしまうためです。
#8 運用トラブル:孤児Broadcast問題
enableAutoStop=false の運用で実際に起きた事故と、その対策を記録しておきます。
何が起きたか
配信の自動起動(cron)がffmpegの起動に失敗し、リトライを繰り返しているうちにBroadcast枠だけが大量に作られました。その後、手動でTelegram Botから配信を再開したのですが、自動起動側が作った枠のうち1つがlive状態のまま残っていたことに気づきませんでした。
手動配信は同じストリームキーに映像を送りますが、孤児枠がそのキーを握っているため、全データが孤児枠に流入。手動側が作った枠にはデータが届かず、スクリプトのログでは「8時間分割→19:00終了」と正常に見えるのに、YouTube側の実態は12.5時間ぶっ通しの1本配信になっていました。
原因の特定には、YouTube APIで枠の lifeCycleStatus を直接照会する必要がありました。スクリプトのログだけでは原因を特定できませんでした。
なぜ起きたか
3つの条件が重なった結果です。
enableAutoStop=false:枠を閉じ忘れると永遠にliveで残る- ストリームキーが1本:複数の枠が同じキーにバインドされうる
- 自動(cron)と手動(Bot)の切替時に枠の後始末がなかった:killされた自動streamerが
end_broadcastを呼べなかった
対策
enableAutoStop=false はffmpegの再起動に耐えるために必要なので、設定は維持したまま「閉じ忘れ」を仕組みで防ぐ方針です。
- 起動時クリーンアップ:配信開始前に
mine=Trueで全Broadcast枠を走査し、live/testing状態のものはcomplete、created/ready状態のものはdeleteする。ストリームキーを確実に解放してから新規枠を作成します。 - SIGTERMハンドラ:Telegram Botの停止ボタンはSIGTERMを送ります。従来はSIGTERMで即死して
finallyブロック(枠を閉じる処理)が走りませんでした。SIGTERMをSystemExitに変換するハンドラを追加し、どの終了経路でもend_broadcastが実行されるようにしました。 - 早期失敗ガード:ffmpegが短時間で連続失敗した場合にループを中断し、空のBroadcast枠が大量生産されるのを防止します。
#9 その他のハマりポイント
YouTube Studioの「エンコーダ配信」ページに映像が表示されない
APIで作成した配信は、YouTube Studioの「エンコーダ配信」ダッシュボードには表示されません。Studioを開くとStudio独自の配信枠が自動生成され、APIの枠と二重になるためです。配信自体は正常に動いているので、確認は「コンテンツ」→「ライブ配信」タブから行います。ダッシュボードに黄色い帯の誘導メッセージが出たら、それをクリックしても配信画面に飛べます。
★ YouTube Studio「コンテンツ→ライブ配信」からの確認画面
ready 状態の配信枠は complete に遷移できない
停止処理で全枠を終了させるとき、まだ開始していない ready 状態の枠を complete(終了)にしようとすると403エラーになります。ready のものは「終了」ではなく「削除」で処理を分けます。
#10 今後の予定
現在、配信の自動起動はcron、手動操作はTelegram Botという2つの起点から streamer.py を起動する構成です。今回の孤児Broadcast問題を通じて、この「起点が2つある」設計自体が構造的なリスクだと感じています。
Telegram Botは24時間常駐しているので、ここにスケジューラを内蔵してcronを廃止し、起点を一本化するのが次のステップです。ログも一か所に集約でき、状態管理も単純になります。BotをsystemdのRestart=alwaysで管理すれば、cronと同等以上の起動信頼性も確保できるはずです。
また、現在はトピックや訪問情報の更新時にffmpegを再起動していますが、これらもzmq化すれば再起動自体が不要になり、enableAutoStop=true に切り替えるという選択肢も出てきます。
このプロジェクトの配信システムのソースコードはGitHubで公開しています:https://github.com/hiirofish/bird-watching-youtube-streamer
気象センサー(SHT30 + BMP180)の構成やハードウェアの詳細はこちら:https://take1bit.com/computer-ja/post-2372/


