かわみのメモ帳

趣味に関するメモを書いていきます。

【〇〇の宝庫】YouTubeから字幕データを取得してYouTuberの言語モデルを作ってみた!!

おはようございます。かわみです。
平成最後の夏は過去最高クラスの猛暑となっていますが、皆さんいかがお過ごしでしょうか。
私はそろそろ30℃超えの室温にも適応してきました。
さて、皆さんはYouTubeを観ますか。観ますよね。まだまだテレビ派ですか。

実はそのYouTubeは、〇〇の宝庫なのです。気になる〇〇は、記事の続きで!!

YouTubeから取得できるデータ

実はYouTubeデータの宝庫です。「実は」でもなんでもありません。
YouTubeには動画(音声・画像)やそのメタ情報のほか、字幕だってあります。
日本語の動画であれば、海外の視聴者向けに英語やその他の言語の字幕が付与されているのをしばしば見かけます。 手動で付けられた字幕のほか、音声認識から自動生成された字幕もあります。一部界隈でたまにネタになったりしています(?)。

公式にはYouTube Data APIが用意されており、主にメタ情報を取得することができます。詳しくは公式のリファレンスをご確認ください。

YouTube Data API の概要  |  YouTube Data API (v3)  |  Google Developers

字幕データもこのAPIによって取得できます。早速取得してみましょう。

Captionを取得する

あるチャンネルのすべての動画から字幕を取りたかったので、結果的にAPIを3種類用いて実装しました。

  • まず"Channels: list"APIで、あるチャンネルの動画のリストを取得します。
  • 次に"Captions: list"APIで、動画に付与された字幕のリストを取得します。
  • 最後に"Captions: download"APIで、字幕をダウンロードします。
from urllib import request as urllib
import json
import datetime
import requests
import codecs
from urllib.error import HTTPError

oauthuri = "https://accounts.google.com/o/oauth2/auth?client_id=[client_id]&redirect_uri=[redirect_uri]&scope=https://www.googleapis.com/auth/youtube.force-ssl&response_type=code&access_type=offline"

key = "[key]"
access_token = "[access_token]"
_refresh_token = "[refresh_token]"


def get_access_token():
    url = "https://accounts.google.com/o/oauth2/token"
    s = requests.Session()
    params = {"code": input("code>>"),
              "client_id": input("client_id>>"),
              "client_secret": input("client_secret>>"),
              "redirect_uri": input("redirect_uri>>"),
              "grant_type": "authorization_code"}
    r = s.post(url, data=params)
    print(r.text.encode("utf-8"))


def refresh_token():
    url = "https://accounts.google.com/o/oauth2/token"
    s = requests.Session()
    params = {"code": input("code>>"),
              "client_id": input("client_id>>"),
              "client_secret": input("client_secret>>"),
              "refresh_token": _refresh_token,
              "grant_type": "refresh_token"}
    r = s.post(url, data=params)
    print(r.text.encode("utf-8"))


def get_youtube_caption(ch, term_start, term_end, next):
    # チャンネルの指定期間内の動画一覧を取得
    video_id_list = []
    request = urllib.urlopen(
        "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=" + ch + term_start + term_end +
        "&maxResults=50&key=" + key + next)
    response = request.read() 
    data = json.loads(response) 

    for d in data["items"]:
        if "videoId" in d["id"]:
            video_id_list.append(d["id"]["videoId"])
    if "nextPageToken" in data["items"]:
        npt = data["nextPageToken"]
    else:
        npt = ""

    # 取得した動画の字幕一覧を取得
    caption_id_list = list()
    for video_id in video_id_list:
        request = urllib.urlopen(
            "https://www.googleapis.com/youtube/v3/captions?part=snippet&videoId=" + video_id + "&key=" + key)
        response = request.read()
        data = json.loads(response)
        for item in data["items"]:
            lang = item["snippet"]["language"]
            trackKind = item["snippet"]["trackKind"]  # "standard" -> manual, "ASR" -> auto
            # Sorry, Japanese Only.
            if lang == "ja" and trackKind == "standard":
                caption_id = item["id"]
                caption_id_list.append(caption_id)

    # 字幕をダウンロード
    caption_data = []
    for caption_id in caption_id_list:
        headers = {"Authorization": " Bearer " + access_token}
        request = urllib.Request(
            url="https://www.googleapis.com/youtube/v3/captions/" + caption_id + "?tfmt=ttml&key=" + key,
            headers=headers)
        try:
            request = urllib.urlopen(request)
            response = request.read()
            data = codecs.decode(response)
            caption_data.append((caption_id, data))
        except HTTPError as e:
            print("error{}".format(e.headers))

    return npt, caption_data


def write(outputdir, caption_list):
    for caption_id, caption in caption_list:
        with open("{}/{}.ttml".format(outputdir, caption_id), "wt", encoding="utf-8") as f:
            f.write(caption)
            f.flush()


if __name__ == '__main__':
    pageToken = "&pageToken="
    outputdir = "./resources/kizunaai" # 出力先ディレクトリ
    channel_id = "UC4YaOt1yT-ZeyB0OmxHgolA" # 取得するチャンネルID
    num = 36
    date = datetime.datetime.now()
    for i in range(num):
        before = "&publishedBefore=" + date.strftime("%Y-%m-%dT%H:%M:%S.000Z")
        date = date - datetime.timedelta(weeks=4)
        after = "&publishedAfter=" + date.strftime("%Y-%m-%dT%H:%M:%S.000Z")
        next = ""
        while next != pageToken:
            next, captions = get_youtube_caption(channel_id, after, before, next)
            next = pageToken + next
            write(outputdir, captions)

Captions: list APIでは、手動で付与された字幕および自動字幕の両方が取得されますが、上記ではitems -> snippet -> trackKind属性が"standard"となっている手動字幕のみを取得することにしました。 ちなみにこの属性が"ASR"になっていれば自動字幕です。
また手動字幕かつitems -> snippet -> language属性が"ja"となっている日本語字幕のみを取得することにしました。

大したことはないですが、字幕を取得する処理のほか、Access Tokenを取得する処理やそれを更新する処理も書きました。

50件以上取得するために、以下を非常に参考にしました。いや流用に近い。

qiita.com

qiita.com

実際、このようなデータが取得されます。

f:id:kawamix:20180730221556p:plain

言語モデルを作る

材料が集まったので、料理していきます。今回はRNN言語モデルを作ります。

こちらのチャンネルから取得した手動字幕データを使って言語モデルを作ります。

www.youtube.com

217動画から合計28065発話を取得しました。
学習の際にはMeCab(+mecab-ipadic-NEologd)で形態素解析したものを入力としました。
そしてこの学習モデルを使って、フレーズを自動生成させてみました。

"INPUT>>"が手動で入力したフレーズ、"OUTPUT>>"が入力したフレーズに続いて自動生成させたフレーズです。

f:id:kawamix:20180730222028p:plain

f:id:kawamix:20180730222101p:plain

f:id:kawamix:20180730222115p:plain

f:id:kawamix:20180730222152p:plain

f:id:kawamix:20180730222042p:plain

セーラームーンが好きだという情報が、IoTでビッグデータディープラーニングする ことで明らかになりました。本当は一体"いくつ"なんだ。
ここまでYouTube動画の字幕を取得して、それを用いた言語モデルの構築方法を紹介しました。
YouTube Data APIには更なる種類が用意されていますので、皆さんも活用されてみてはいかがでしょうか。
以上、記事タイトルにもあるように、「やってみた」系記事でした。

おまけ

f:id:kawamix:20180730222325p:plain

来年も期待しています。