YouTube LiveのチャットをDeepL翻訳してリアルタイム表示する

#動作イメージ
DeepL翻訳してリアルタイムで表示します。(右側のウィンドウ)
![メディア1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/798027/13a2d23e-ec22-039a-05a1-f34cb9cb73c8.gif)

#きっかけ
1. YouTube Liveの外国語チャットを翻訳したい
2. Chrome上で「右クリック→日本語に翻訳」するとGoogle翻訳できるけど精度がイマイチ
3. DeepL翻訳はGoogle翻訳より精度が高いらしい(下記グラフ参照)
4. チャットは時々刻々と流れてくるので、いちいち[DeepL翻訳サイト](https://www.deepl.com/ja/translator)にコピペするのは面倒

以上のことから、YouTube LiveのチャットをDeepL翻訳してリアルタイム表示するプログラムを作成しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/798027/e4603ee3-a0a8-92e7-6fab-20aa94c636c2.png)
https://www.deepl.com/quality.html



#使い方
* 準備
    * YouTube APIキーを入手する。
        * [こちらを参照](https://qiita.com/iroiro_bot/items/1016a6a439dfb8d21eca)
    * DeepL APIキーを入手する。(**有料なので注意　月630円+従量制(2,500円/100万字)**)
        * [こちらを参照](https://qiita.com/rihok/items/962890b4b86ea052e54c)
* 実行
    * YouTube APIキーを入力する。
    * DeepL APIキーを入力する。
    * YouTube LiveのURLを入力する。(Liveアーカイブは不可なので注意)
    * どの言語に翻訳するか選択する。(デフォルトは日本語)
    * 「Start」ボタンを押す。
 
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/798027/708b620b-391a-9ed6-a1c9-323a7f8f76b5.png)

#コード
```translate_youtube_live_chat_gui.py
# モジュールを読み込む。
import tkinter  # Tcl/Tk の Python インタフェース
import time

# 関数を読み込む。
from tkinter import messagebox  # メッセージボックス作成
from joblib import Parallel, delayed
from youtube import get_video_id, get_video_data, get_chat_id, get_page_token, get_chat_data  # YouTube
from youtube import get_message_list, get_user_name_list  # YouTube
from translator import deepl_translate  # DeepL翻訳

# ======================================================================================================================
# チャット読み込みの間隔を設定する。(短くし過ぎるとエラーになるので注意)
UPDATE_TIME = 5000  # ミリ秒
# ======================================================================================================================


# ボタンをクリックしたときの処理をする。
def click_button():
    # GUIから情報を取得する。
    youtube_live_url = edit_box1.get()  # YouTubeLiveのURL
    youtube_api_key = edit_box2.get()  # YouTube APIキー

    # YouTubeLiveの情報を取得する。
    video_id = get_video_id(youtube_live_url)  # ビデオID
    video_data = get_video_data(video_id, youtube_api_key)  # ビデオデータ
    chat_id, error_message = get_chat_id(video_data)  # チャットID
    if chat_id is None:  # YouTubeLive以外の場合
        messagebox.showerror('URL Error', error_message)
        return  # 処理を中断する。
    chat_data = get_chat_data(chat_id, None, youtube_api_key)  # チャットデータ(ページトークン初期値=None)
    page_token = get_page_token(chat_data)  # ページトークン

    # テキストボックスにメッセージを追加する。
    root.after(UPDATE_TIME, add_message, chat_id, page_token)


# テキストボックスにメッセージを追加する。
def add_message(chat_id, page_token):
    # GUIから情報を取得する。
    youtube_api_key = edit_box2.get()  # YouTube APIキー
    deepl_api_key = edit_box3.get()  # DeepL APIキー
    target_lang = variable.get()  # 翻訳先の言語

    # YouTubeLiveの情報を取得する。
    chat_data = get_chat_data(chat_id, page_token, youtube_api_key)  # チャットデータ
    page_token = get_page_token(chat_data)  # ページトークン
    message_list = get_message_list(chat_data)  # メッセージリスト
    user_name_list = get_user_name_list(chat_data)  # ユーザー名リスト

    # DeepLで翻訳する。
    start_time = time.time()  # 処理開始時刻
    trans_message_list = Parallel(n_jobs=-1)(
        [delayed(deepl_translate)(message, target_lang, deepl_api_key) for message in message_list]
    )
    end_time = time.time()  # 処理終了時刻
    elapsed_time_s = end_time - start_time  # 処理時間 秒
    elapsed_time_ms = round(elapsed_time_s * 1000)  # 処理時間 ミリ秒

    # テキストボックスに表示するメッセージを設定する。
    display_message_list = []
    for index, trans_message in enumerate(trans_message_list):  # メッセージの数だけ繰り返す。
        text = '[' + user_name_list[index] + '] ' + trans_message  # [ユーザー名]＋翻訳メッセージ
        display_message_list.append(text)

    # テキストボックスにメッセージを追加する。
    for display_message in display_message_list:
        txt_box.insert(tkinter.END, display_message)
        txt_box.insert(tkinter.END, '\n')
        txt_box.insert(tkinter.END, '\n')

    # スクロールバーを一番下にする。
    txt_box.see('end')

    # ウィンドウ表示を更新する。
    root.update()

    # 指定した時間待機する。
    wait_time = max({0, UPDATE_TIME - elapsed_time_ms})
    root.after(wait_time, add_message, chat_id, page_token)


# ======================================================================================================================


# ウィンドウを作成する。
root = tkinter.Tk()
root.title('YouTube Live Chat DeepL Translator')
root.geometry('600x650')
root.resizable(False, False)

# フレームを作成する。
frame = tkinter.Frame(root)
frame.pack()
frame.place(x=0, y=70)

# テキストボックスを作成する。
txt_box = tkinter.Text(frame, font=('Arial', 10), width=82, height=29)
y_scroll = tkinter.Scrollbar(frame, orient=tkinter.VERTICAL, command=txt_box.yview)  # 縦方向スクロールバー
y_scroll.pack(side=tkinter.RIGHT, fill='y')  # 縦方向スクロールバー
txt_box['yscrollcommand'] = y_scroll.set  # 縦方向スクロールバー
txt_box.pack()

# ボタンを作成する。
button = tkinter.Button(root, text='Start', font=('Arial', 15), command=click_button)
button.pack()
button.place(x=500, y=20)

# ラベルを作成する。
label1 = tkinter.Label(root, text='YouTube Live URL', font=('Arial', 12))
label1.pack()
label1.place(x=20, y=5)

# ラベルを作成する。
label2 = tkinter.Label(root, text='YouTube API Key', font=('Arial', 10))
label2.pack()
label2.place(x=140, y=590)

# ラベルを作成する。
label3 = tkinter.Label(root, text='DeepL API Key', font=('Arial', 10))
label3.pack()
label3.place(x=370, y=590)

# ラベルを作成する。
label4 = tkinter.Label(root, text='Target Language', font=('Arial', 10))
label4.pack()
label4.place(x=10, y=590)

# YouTubeLiveのURLを入力するエディットボックスを作成する。
edit_box1 = tkinter.Entry(root, font=('Arial', 12), width=50)
edit_box1.pack()
edit_box1.place(x=20, y=30)

# YouTube APIキーを入力するエディットボックスを作成する。
edit_box2 = tkinter.Entry(root, font=('Arial', 10), width=30, show='*')
edit_box2.pack()
edit_box2.place(x=140, y=610)

# DeepL APIキーを入力するエディットボックスを作成する。
edit_box3 = tkinter.Entry(root, font=('Arial', 10), width=30, show='*')
edit_box3.pack()
edit_box3.place(x=370, y=610)

# 翻訳言語を選択するプルダウンメニューを作成する。
option_list = [
    'German',      # DE
    'English',     # EN
    'French',      # FR
    'Italian',     # IT
    'Japanese',    # JA
    'Spanish',     # ES
    'Dutch',       # NL
    'Polish',      # PL
    'Portuguese',  # PT
    'Russian',     # RU
    'Chinese'      # ZH
]
variable = tkinter.StringVar(root)
variable.set(option_list[4])  # JA
pull_down_menu = tkinter.OptionMenu(root, variable, *option_list)
pull_down_menu.config(width=10, font=('Arial', 10))
pull_down_menu.pack()
pull_down_menu.place(x=10, y=610)

# ウインドウを描画する。
root.mainloop()

```

```youtube.py
# モジュールを読み込む。
import requests  # HTTP通信ライブラリ

# ======================================================================================================================
# APIのURLを設定する。
YOUTUBE_API_VIDEO_URL = 'https://www.googleapis.com/youtube/v3/videos'  # YouTubeビデオ
YOUTUBE_API_CHAT_URL = 'https://www.googleapis.com/youtube/v3/liveChat/messages'  # YouTubeチャット
# ======================================================================================================================


# YouTubeLiveのURLからビデオIDを取得する。
def get_video_id(youtube_live_url):
    video_id = youtube_live_url.replace('https://www.youtube.com/watch?v=', '')
    return video_id


# 指定したビデオIDのビデオデータを取得する。
def get_video_data(video_id, youtube_api_key):
    video_data = requests.get(
        YOUTUBE_API_VIDEO_URL,
        params={
            'key': youtube_api_key,
            'id': video_id,
            'part': 'liveStreamingDetails'}
    ).json()
    return video_data


# チャットIDを取得する。
def get_chat_id(video_data):
    # YouTubeLive以外の場合処理を中断する。
    if len(video_data['items']) == 0:
        chat_id = None
        error_message = 'Not Live'
    elif 'liveStreamingDetails' not in video_data['items'][0].keys():
        chat_id = None
        error_message = 'Not Live'
    elif 'activeLiveChatId' not in video_data['items'][0]['liveStreamingDetails'].keys():
        chat_id = None
        error_message = 'No Chat'
    else:
        chat_id = video_data['items'][0]['liveStreamingDetails']['activeLiveChatId']
        error_message = None
    return chat_id, error_message


# ページトークンを取得する。
def get_page_token(chat_data):
    page_token = chat_data['nextPageToken']
    return page_token


# 指定したチャットIDのチャットデータを取得する。
def get_chat_data(chat_id, page_token, youtube_api_key):
    chat_data = requests.get(
        YOUTUBE_API_CHAT_URL,
        params={
            'key': youtube_api_key,
            'liveChatId': chat_id,
            'part': 'id,snippet,authorDetails',
            'pageToken': page_token}
    ).json()
    return chat_data


# メッセージリストを作成する。
def get_message_list(chat_data):
    message_list = []
    for chat_item in chat_data['items']:  # チャットアイテムの数だけ繰り返す。
        message_list.append(chat_item['snippet']['displayMessage'])
    return message_list


# ユーザー名リストを作成する。
def get_user_name_list(chat_data):
    user_name_list = []
    for chat_item in chat_data['items']:  # チャットアイテムの数だけ繰り返す。
        user_name_list.append(chat_item['authorDetails']['displayName'])
    return user_name_list

```

```translator.py
# モジュールを読み込む。
import requests  # HTTP通信ライブラリ

# ======================================================================================================================
# DeepL APIのURLを設定する。
DEEPL_API_URL = 'https://api.deepl.com/v2/translate'
# ======================================================================================================================


# DeepLで翻訳する。
def deepl_translate(src_text, target_lang, deepl_api_key):
    # 翻訳言語のコードを設定する。
    if target_lang == 'German':
        target_lang_code = 'DE'
    elif target_lang == 'English':
        target_lang_code = 'EN'
    elif target_lang == 'French':
        target_lang_code = 'FR'
    elif target_lang == 'Italian':
        target_lang_code = 'IT'
    elif target_lang == 'Japanese':
        target_lang_code = 'JA'
    elif target_lang == 'Spanish':
        target_lang_code = 'ES'
    elif target_lang == 'Dutch':
        target_lang_code = 'NL'
    elif target_lang == 'Polish':
        target_lang_code = 'PL'
    elif target_lang == 'Portuguese':
        target_lang_code = 'PT'
    elif target_lang == 'Russian':
        target_lang_code = 'RU'
    elif target_lang == 'Chinese':
        target_lang_code = 'ZH'
    else:
        target_lang_code = None

    deepl_trans_result = requests.post(
        DEEPL_API_URL,
        data={
            "auth_key": deepl_api_key,
            "text": src_text,
            "target_lang": target_lang_code}
    ).json()
    trans_text = deepl_trans_result["translations"][0]["text"]

    return trans_text

```