Amazon Polly を使用して Raspberry Pi に YouTube Live のチャットを読み上げてもらう

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

テントの中から失礼します、CX事業本部のてんとタカハシです!

YouTube Live で配信を行っていると、視聴者の方からチャットにコメントを頂くことがあります。ただ、たま〜にコメントが来る程度だと、なかなか気付けないことが多かったりします。せっかくコメントを頂いたのに、気が付いて返信をした時にはもう配信を見てもらえていない、、、なんてこともあります。

んじゃあ、コメントが来たら読み上げてくれるツールか何かを作れば良いんじゃね?と思いまして、タイトル通りのモノを作ってみました。まあ、似たようなツールは既に存在しているような気がしますが、年末年始の自由研究ネタとしては程良かったのでやってみました。

ソースコードは下記に置いてあります。

GitHub - iam326/read-aloud-youtube-live-chat

作ったもの

YouTube Live のチャットにコメントが書かれると Raspberry Pi ちゃんが読み上げてくれます。

登場人物

Raspberry Pi

緑色のやつです。今回はこの子にチャットを読み上げてもらいます。読むと言っても実際は MP3 ファイルを再生するだけです。

本当は Raspberry Pi 4 を使いたいのですが、Raspberry Pi 2を5台持っている都合で、なかなか購買意欲が高まりません。ただ、今回は2でも3でも4でも関係なく動作すると思います。

YouTube Live Streaming API

この API では、YouTube Live で配信されている(もしくは配信済み)の動画情報を取得したり、チャットのコメントを取得/書き込みを行ったりすることが可能です。詳細は下記のドキュメントをご参照ください。

YouTube Live Streaming API Overview

今回はこの API を使用して、配信中のチャットからコメントを取得します。

尚、YouTube Live Streaming API では、必ず OAuth 2.0 による認証を通す必要があります。

However, all of the methods for the YouTube Live Streaming API require OAuth 2.0 authorization

YouTube Live Streaming API - Obtaining authorization credentials

そのため、GCP で認証情報(OAuth クライアント)を作成する必要があるのですが、手順は YouTube Data API を使用する場合と全く同じになります。

Amazon Polly

テキストを音声に変換してくれる AWS のサービスです。YouTube Live Streaming API で取得したチャットのコメントを Amazon Polly に渡して、MP3 ファイルに変換してもらいます。

私の配信では、ほとんどが英語のコメントなので、今回は英語のみ対応することにします(日本語、韓国語、アラビア語などが飛んでくることもあるので、その辺もその内対応したい)。

環境

Raspberry Pi 上の環境は下記になります。言語は Python を使います。AWS CLI が叩ける状態を前提とさせてください。

$ cat /proc/device-tree/model
Raspberry Pi 2 Model B Rev 1.1

$ lsb_release -a
No LSB modules are available.
Distributor ID: Raspbian
Description: Raspbian GNU/Linux 10 (buster)
Release: 10
Codename: buster

$ python3 --version
Python 3.7.3

$ pip3 --version
pip 18.1 from /usr/lib/python3/dist-packages/pip (python 3.7)

$ aws --version
aws-cli/1.18.199 Python/3.7.3 Linux/5.4.51-v7+ botocore/1.19.39

準備

OAuth クライアントを作成する

YouTube Live Streaming API を叩くために OAuth クライアントを作成する必要があります。下記を参考にして、OAuth クライアントの情報を含む JSON ファイルを作成してください。また、作成した JSON ファイルの名前をclient_secrets.jsonに変更して、後に実装するプログラムと同じディレクトリに格納してください。

イヤホン or スピーカーを取り付ける

音声を確認するため、Raspberry Pi の 3.5 mm ジャックにイヤホンもしくは、スピーカーを接続してください。今回、私が使用したスピーカーは下記になります。

ELECOM - スマホ用モノラルスピーカー - ASP-SMP050YL

必要なパッケージのインストール

下記のコマンドを実行します。

$ pip3 install boto3
$ pip3 install --upgrade google-api-python-client google-auth-httplib2 google-auth-oauthlib

実装

YouTube Live Streaming API 周り

YouTube Live Streaming API をラップするモジュール youtube_live_streaming_api_client.py を実装します。このモジュールでは、API を叩くために OAuth 2.0 認証を行ったり、配信のチャット ID を取得、そのチャット ID を使ってコメントを取得する処理を載せています。

下記のドキュメントを参考にしました。

youtube_live_streaming_api_client.py

#!/usr/bin/env python3

import pickle
import os.path
from googleapiclient.discovery import build
from google_auth_oauthlib.flow import InstalledAppFlow
from google.auth.transport.requests import Request

API_SERVICE_NAME = 'youtube'
API_VERSION = 'v3'


class YoutubeLiveStreamingApiClient():

    def __init__(self, client_secrets_file, scopes):
        self.__client = self.get_authenticated_service(
            client_secrets_file, scopes)

    # OAuth 2.0 による認証を通して、API クライアントを取得する
    def get_authenticated_service(self, client_secrets_file, scopes):
        creds = None
        # The file token.pickle stores the user's access and refresh tokens, and is
        # created automatically when the authorization flow completes for the first
        # time.
        if os.path.exists('token.pickle'):
            with open('token.pickle', 'rb') as token:
                creds = pickle.load(token)
        # If there are no (valid) credentials available, let the user log in.
        if not creds or not creds.valid:
            if creds and creds.expired and creds.refresh_token:
                creds.refresh(Request())
            else:
                flow = InstalledAppFlow.from_client_secrets_file(
                    client_secrets_file, scopes)
                creds = flow.run_local_server(port=0)
            # Save the credentials for the next run
            with open('token.pickle', 'wb') as token:
                pickle.dump(creds, token)

        return build(API_SERVICE_NAME, API_VERSION, credentials=creds)

    # ライブ ID を基にチャット ID を取得する(ライブ ID 自体は配信中の URL から抜き出す)
    def get_live_chat_id(self, live_id):
        live_broadcasts = self.__client.liveBroadcasts().list(
            part='snippet', id=live_id, fields='items(snippet(liveChatId))').execute()

        return live_broadcasts['items'][0]['snippet']['liveChatId']

    # チャット ID を基にコメントの一覧を取得する
    def get_live_chat_messages(self, live_chat_id, next_page_token=None):
        live_chat_messages = self.__client.liveChatMessages().list(
            liveChatId=live_chat_id,
            part='snippet',
            maxResults=200,
            pageToken=next_page_token,
            fields='nextPageToken,items(snippet(displayMessage))'
        ).execute()

        # ここで返す next_page_token を使用して、このメソッドを再度呼び出すことで、以降のコメントを取得できる
        return {
            'next_page_token': live_chat_messages['nextPageToken'],
            'messages': [i['snippet']['displayMessage'] for i in live_chat_messages['items']]
        }

Raspberry Pi にコメントを読ませる

上記のモジュールを使って、定期的にチャットのコメントを取得します。また、取得したチャットのコメントを Amazon Polly に渡して、MP3 ファイルに変換した後、再生してあげます。

main.py

#!/usr/bin/env python3

import boto3
import pygame.mixer
import urllib.parse
from contextlib import closing
from time import sleep

from youtube_live_streaming_api_client import YoutubeLiveStreamingApiClient

YOUTUBE_CLIENT_SECRETS_FILE = 'client_secrets.json'
YOUTUBE_CLIENT_SCOPES = [
    'https://www.googleapis.com/auth/youtube.readonly']
DEST_AUDIO_FILE = 'message.mp3'
WAIT_SEC = 10

polly = boto3.client('polly')
pygame.mixer.init()


# Amazon Polly でテキストを MP3 ファイルに変換する
def convert_to_voice(text, path):
    result = polly.synthesize_speech(Text=text, OutputFormat='mp3',
                                     VoiceId='Joanna', Engine='neural')
    with closing(result['AudioStream']) as stream:
        with open(path, 'wb') as file:
            file.write(stream.read())


# 指定した音声ファイルを再生する
def play_sound(path):
    pygame.mixer.music.load(path)
    pygame.mixer.music.play(1)
    while pygame.mixer.music.get_busy():
        sleep(0.1)


def main():
    youtube = YoutubeLiveStreamingApiClient(
        YOUTUBE_CLIENT_SECRETS_FILE, YOUTUBE_CLIENT_SCOPES)

    # 配信中の URL を入力して、ライブ ID を抜き出す
    url = input('YouTube Live URL: ')
    live_id = urllib.parse.urlparse(url).path[1:]
    live_chat_id = youtube.get_live_chat_id(live_id)

    next_page_token = None

    try:
        while True:
            # 10秒おきにチャットのコメントを取得する
            print('get live chat messages ...')
            live_chat_messages = youtube.get_live_chat_messages(
                live_chat_id, next_page_token)

            for message in live_chat_messages['messages']:
                print(message)
                # テキストを MP3 に変換した後、再生する
                convert_to_voice(message, DEST_AUDIO_FILE)
                play_sound(DEST_AUDIO_FILE)
                sleep(1)

            next_page_token = live_chat_messages['next_page_token']
            sleep(WAIT_SEC)

    except KeyboardInterrupt:
        pass


if __name__ == '__main__':
    main()

プログラムの実行

プログラムを実行すると、ブラウザが起動して、Google アカウントを選択する画面が表示されます(初回のみ)。

$ python3 main.py
Please visit this URL to authorize this application: https://accounts.google.com/hogehoge...

下記記事の「プログラムを実行する」を参考にして、Google アカウントへのアクセスを許可してください。

Google アカウントへのアクセスを許可すると、配信中の URL を入力するように求められます。

$ python3 main.py
Please visit this URL to authorize this application: https://accounts.google.com/hogehoge...

YouTube Live URL:

配信画面下部にあるシェアボタンをクリックして、

動画リンクをコピーしてください。

コピーした URL を貼り付けると、チャットのコメントを取得して、Raspberry Pi が読み上げてくれます。

$ python3 main.py
Please visit this URL to authorize this application: https://accounts.google.com/hogehoge...

YouTube Live URL: https://youtu.be/xxxxxxxxxxx

get live chat messages ...
hello
good morning
...

おまけ - SSH して実行したい場合

Raspberry Pi に繋げるディスプレイやらが無い場合、少し工夫する必要があります。まず、1つは VNC 等を使って Raspberry Pi にリモート接続する方法です。ひとまずはこれで解決するのかなと思います。

が、VNC 等を使わず SSH して実行したい場合、Google アカウントを選択するために開くブラウザが表示できず、先の処理に進めなくなります。

解決策の手順としては下記になります。

  1. Mac 等の環境で、client_secrets.jsonを取得する
  2. その環境で認証の処理だけ走らせる → generate_token.py
  3. client_secrets.jsonと↑の処理で生成されたtoken.pickle を scp で Raspberry Pi にコピーする

これで、ブラウザが開くところのフローはスキップできるので実行可能になります。

おわりに

今回は英語のみの対応としていますが、他の言語にも対応できるように改良していきたいです。

また、今回作ったモノの対になる機能として、自分が喋った言葉を Raspberry Pi が録音 → テキスト化 → チャットにコメントできるようになると、文字ベースのやりとりが実際の会話っぽくなって面白いな〜と思っています。その内、実現できたらいいなあ。

今回は以上になります。最後まで読んで頂きありがとうございました!