太陽がまぶしかったから

C'etait a cause du soleil.

Youtube動画をOpen AIで文字起こし&要約で観ていない動画について堂々と語る方法

Youtube動画をOpen AIで文字起こし&要約

 字幕や書き起こしがAIによって自動的にできるようになり、AIに調査や要約をさせることまで可能になっていく中では「早送り」もまた過渡期のかけ金節約方法なのかもしれず、さらにコスパやタイパを追求するチートスキルが開拓されていくだろう。できるのにやらないことを情弱と責め立てる残酷な世界においては、「編集権の簒奪」というチートスキルに覚醒せざるを得ない。

 これまで『稲田豊史『映画を早送りで観る人たち』感想〜万人の万人に対する象徴闘争から覚醒させられる「編集権の簒奪」というチートスキル - 太陽がまぶしかったから』や『ピエール バイヤール『読んでいない本について堂々と語る方法』感想〜読書感想という書物と書物の外側にある実と虚の皮膜 - 太陽がまぶしかったから』について語っていたのは、技術的にそれができることが分かってしまったことが大きい。

 Youtubeで様々な動画が無料公開されているのはありがたいことだが、1時間のセミナー動画のために1時間座りっぱなしはしんどいし、倍速で見ても30分はかかる。それでいて得られるものは限定的だったりもするが、要約であれば3分もあれば読むことができるし、理解できない話があるならちゃんと観れば良いという選択ができる。今回はYoutube動画をOpen AIで文字起こし&要約して観ていない動画について堂々と語るために利用している具体的なプログラムについて解説する。

利用する技術要素

 今回作成するプログラムは技術的には以下の要素を利用している。

  • pytube ... PythonYoutube の情報を取得したり、動画のダウンロードするライブラリ
  • ffmpeg ... 動画・音声ファイルの変換ツール
  • Whisper ... Open AI の音声文字起こしサービス。Open AIの有料API登録が必要
  • ChatGPT API ... Open AI のチャットAPI。Open AIの有料API登録が必要

 つまるところ、Pythonで指定されたYoutube動画の情報や音声を取得して、ffmpegで必要に応じて変換して、Whisperで文字起こし。文字起こしされた後はChatGPTの領域だ。実際に活用するにはサーバー環境にデプロイしたり、認証したりといった話もあるが今回はローカルで動くところまでを対象とする。

利用イメージ

youtube-abstract % python3 youtube_abstract.py "https://youtu.be/xxx"
video_id: xxx
title: タイトル
url: https://www.youtube.com/watch?v=xxx
atuthor: チャンネル作者
video_length: 00:33:52
suumary:
## 動画の概要

今回の動画では、〜

## 動画の登場人物

- XXXさん(講師)
- XXXさん(ゲスト)

## 動画内で挙げられている論点

- XXXの形が変化している
- XXXはお役御免になり、新しい表現が現れる可能性がある

## 論点の説明

色々大変だよね

## 特に印象的かつ示唆に富んだ発言10選

XXXは絶妙なバランスなので、余地がないんですよ。

## この動画から得られる教訓、示唆、未来への展望

この動画からは、〜が学べます。

 実際はもっとちゃんと出てくるが、著作権的なアレがあるので掲載は控える。後ほど説明するが、「特に印象的かつ示唆に富んだ発言10選」をプロンプトに入れておくことで観ていない動画について堂々と語れる可能性がグッと高まる〜。

Youtube動画URLからビデオIDを取得する

 まずは、入力されたYoutube動画URLからビデオIDを取得するため、 urllib.parse.urlparse でよしなに解析する。

def extract_youtube_video_id(youtube_link: str) -> str:
    urlobj: urllib.ParseResult = urllib.parse.urlparse(youtube_link)
    v: str = None

    if urlobj.netloc == "youtu.be":
        v = urlobj.path[1:]
    elif urlobj.netloc == "www.youtube.com":
        query: dict = urllib.parse.parse_qs(urlobj.query)
        if query.get("v") is not None and len(query.get("v")) > 0:
            v = str(query.get("v")[0])

    if v is None:
        raise ValueError("YouTube link must be provided.")
    else:
        return v

 youtu.be 形式は Twitter などからリンクされた時に使われる短縮リンクである。

if __name__ == "__main__":
    dotenv.load_dotenv()
    openai.api_key = os.getenv("OPENAI_API_KEY")
    if openai.api_key is None:
        raise ValueError("OPENAI_API_KEY environment variable must be set.")

    youtube_link = ""
    if len(sys.argv) < 2:
        youtube_link = input("Enter YouTube link: ")
    else:
        youtube_link = sys.argv[1]
    video_id = extract_youtube_video_id(youtube_link)
    print(f"video_id: {video_id}")
    info = download_youtube(video_id)
    convert_audio(video_id)
    text = transcribe(video_id)
    summary = abstract(info, text)
    print(
        f"""title: {info["title"]}
url: {info["link"]}
atuthor: {info["author"]}
video_length: {info["time"]}
suumary:
{summary}"""
    )

 関数を起動していくメイン処理はこちら。起動の段階で .env から Open AIのAPIキーを取得し、そこから順を追って関数を実行することで処理フローを完成する。

Youtube から音声形式でダウンロード

 pytube を利用してファイルをダウンロード。

def download_youtube(youtube_videoid: str) -> dict:
    youtube_link = f"https://www.youtube.com/watch?v={youtube_videoid}"
    download_path = f"content/audio/src/{youtube_videoid}.m4a"
    youtube = pytube.YouTube(youtube_link)

    time = youtube.length
    hours = time // 3600
    minutes = time % 3600 // 60
    seconds = time % 3600 % 60

    info: dict = {
        "link": youtube_link,
        "title": youtube.title,
        "keywords": youtube.keywords,
        "author": youtube.author,
        "length": youtube.length,
        "time": f"{hours:02d}:{minutes:02d}:{seconds:02d}",
        "channel_id": youtube.channel_id,
        "channel_url": youtube.channel_url,
    }
    # 実行するたびにダウンロードしないようにファイル存在チェック
    if not os.path.exists(download_path):
        youtube.streams.filter(only_audio=True)[0].download(filename=download_path)
    return info

 pytube はライブラリ非依存で python から YouTube のダウンロードや情報取得を行えるライブラリ。

 おそらくスクレイピングなどを利用しているため、Youtubeの規約などを考えるとよろしくないが個人の検証用途として利用させてもらっている。length に動画再生時間の秒数が入ってくるため、00:00:00 表記を生成している。この手のライブラリがありそうなものだが、いったん愚直に作成。

ffmpeg で音声を変換する

 ダウンロードした音声ファイルをモノラルにして1.2倍速に変換。

def convert_audio(youtube_videoid: str) -> str:
    download_path = f"content/audio/src/{youtube_videoid}.m4a"
    output_path = f"content/audio/dest/{youtube_videoid}.m4a"

    if not os.path.exists(output_path):
        ffmpeg.input(download_path).audio.filter("atempo", 1.2).output(
            output_path, ab="24k", ar=44100, ac=1
        ).run()
    return output_path

  python から ffmpeg を利用するラッパーを用いて音声を変換している。

 元々は whisper のファイルサイズ制限に対応してモノラル化などを試していたが、whisper は秒数課金でもあるため精度を犠牲に音声再生速度をあげるという選択肢もあることに気がついたので記載。atempo の数値を調整して良い塩梅を探しても良いし、この処理自体を省略しても良い。

Whisperで音声書き起こし

 いよいよ Whisperで音声書き起こし。

def transcribe(youtube_videoid: str) -> str:
    audio_path = f"content/audio/{youtube_videoid}.m4a"
    text_path = f"content/text/{youtube_videoid}.txt"
    result_text: str = ""
    if os.path.exists(text_path):
        with open(text_path, "r") as f:
            result_text = f.read()
    else:
        with open(audio_path, "rb") as f:
            result = openai.Audio.transcribe("whisper-1", f)
            result_text = result["text"]
            with open(text_path, "w") as f:
                f.write(result_text)
    return result_text

  といっても、実装は非常に簡単で、対象の音声ファイルをバイナリで開いて openai.Audio.transcribe を実行するだけだ。前提として、 openai.api_keyAPIキーを入れておく必要がある。

 API利用料金がそれなりにかかるため、書き起こし結果をテキストファイルに出力して次回実行時に再利用している。有料とはいえ安価なAPI利用料でこれだけの精度の書き起こしテキストが出てくるのは驚異的だ。

文字起こしした情報を要約

 ChatGPTを使って書き起こし文章を要約する。

def abstract(info, text) -> str:
    text = text.strip()[0:10000]
    prompt = f"""# 指示
以下の動画情報と動画書き起こし文章を処理して出力形式通りに出力してください。

# 出力形式
* 日本語のMarkdown形式で出力
* 処理ごとに見出しを「## 見出し」形式で出力

# 処理
* この動画の概要を300字以内で出力
* この動画の登場人物を箇条書きで出力
* この動画内で挙げられている論点をネスト箇条書きで出力
  * 必要に応じて論点の説明を掘り下げて出力
  * 発言に具体例や固有名詞があれば優先的に出力
  * なぜその論点が重要なのかも出力
  * 真似できそうなことがあれば出力
  * 専門用語には補足を入れる
* 特に印象的かつ示唆に富んだ発言10選をMarkdownの > 引用形式で出力
* この動画から得られる教訓、示唆、未来への展望等を300字以内でまとめる

# 動画情報
title: {info["title"]}
atuthor: {info["author"]}

# 動画書き起こし文章
{text}"""
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo-16k-0613",
        messages=[{"role": "user", "content": prompt.strip()}],
        temperature=0.0,
    )
    content: str = response.get("choices")[0]["message"]["content"]  # type: ignore
    return content.strip()

 書き起こし文章はそれなりに長くなることが想定されるため、gpt-3.5-turbo-16k-0613 をモデルに指定して利用可能トークン数を16000に拡張。その上で、入力する書き起こしの文字数も冒頭10000字で切り取る。長大な動画はテキストファイルを分割してそれぞれを要約したりもできなくはないが、概要を知るには最初の10000字で十分だろう。またタイトルなどもChatGPTの手掛かりにする。gpt-4 の方が精度が良いが利用料金が(ry

 プロンプトエンジニアリングについても以前書いたが、この辺りの雨乞いに試行錯誤するのは本質的でないと思いつつ、なんとなく意図した回答が出てくるようになると楽しい。特に良かったのは前述の通りに、「特に印象的かつ示唆に富んだ発言10選をMarkdownの > 引用形式で出力」で、それなりに精度高くパンチラインやリリックを選んできてくれる。概要と発言引用さえあれば観ていない動画について堂々と語れる。

ファスト教養の新たな姿

 以上までで、Youtubeの音声を文字起こして要約するプログラムが完成できる。

ある本についての語られる際に、その本を読んで思ったことなのか、Youtuberなどの他人の感想をなぞっているだけなのかの判断は非常に難しく、ある種のチューリングテストや中国語部屋問題が発生する。そもそも読んだところで一言一句は覚えていないし、全てを理解できるはずもないのだから「読んだ」と「読んでない」には実と虚との皮膜の間にある。

 読んでない本について語られている動画を観ないでAIの要約だけを自分の体験のように語る。映画を倍速で見る倫理的な人々はまだまだゆっくりしているというか。なんにせよ、簡単なプログラムと安価なAPI利用料金でとんでもないことができるようになってしまった現代における教養や知識のあり方みたいなものに思いを馳せたりもする。