【ネタ】re:Invent の現地で使えると思ってセッション内容を録音してトランスクライブする術を用意した

記事タイトルとURLをコピーする

結論: 使いませんでした ※本ブログは自戒をこめたネタ記事です

はじめに

こんにちは、マネージドサービス部の駒井です。
re:Invent 2024 お疲れ様でした(遅)

振り返ると、怒涛の 1週間でした。なので私はこんなタイミングでブログをいそいそ書いています。
みなさんはいかがお過ごしでしょうか。

今回は re:Invent の初日に取り組んだ、音声文字起こしのツールについてお話ししたいと思います。

事の発端

それは re:Invent に参加して最初のセッション(こちらの記事のやつ)に参加していた時のことです。

私は re:Invent に向けて、英語(リスニング)については多少の勉強をしていたので、セッションについてはなんとか聞き取れる・理解できるだろうと考えていました。

そのため、高を括っていたつもりはないのですが、特に同時翻訳のアプリ等は用意していませんでした。
(スピーカーの目の前でスマホを見たくなかった + アプリを買いたくなかった)

セッションを聴き始めて数分、特に問題なく聞き取れてはいるものの、ちゃんと集中して聞いていないと不意に頭から内容が飛んでいきそうな自分がいることに気づきました。

これは保険(聞き逃したとき振り返れる仕組み)が必要だな…と思っていると、その瞬間、私の脳内に天啓が降りてきたのです。

セッションを録音するのです…さすれば聴き直せるでしょう…
音声データを用いて文字起こしをするのです…さすればより理解が捗るでしょう…
文字起こしした文章を翻訳にかけるのです…さすれば自分の理解が正しいかの答え合わせもできるでしょう…

いま目の前のセッションの集中を解くわけにはいきません。

私はその天啓を疑うことなく、Macbook を開き、そそくさと標準搭載の Voice Memos.app を起動したのでした…。

実装

環境

項目 バージョン
端末 Macbook M4 Pro
OS Sequoia 15.1.1
Memory 24GB
IDE Visual Studio Code 1.95.3
開発言語 python 3.10
実行環境 uv 0.5.6(執筆時点では 0.5.9)

利用したもの

音声を文字起こしするということで、今回は Whisper という python で使える音声認識モデルを利用することにしました。

入力および出力は英語となるので、その後の日本語翻訳も自動化するために、googletrans(*1) というライブラリを利用することとしました(無料で使えるので)。

*1

  • Google Translate の非公式ライブラリなので、大量翻訳・過度な利用は控える必要があります
  • Google Translate の Web ページの仕様変更によって正常に動作しない恐れがあります
  • あくまで個人利用の範疇とご理解ください。安定的に使うなら有料 API を使うようにしましょう

1時間のセッションの録音データとなると、文字に起こすと数万文字以上の文書となることは容易に想像がつきます。
Google Translate は 5,000文字までの制約があるため、文字起こし後は文章を分割してから翻訳にかける必要があります。
文字数で分割すると文章の途中で中途半端に切れてしまうので、キリの良いところで想定される文末表現 .!?。!? をトリガとします。

下準備

python の実行環境については、uv を利用しています。

1. uv プロジェクトの作成

適当なフォルダを作って、uv を初期化します。

$ uv init audio_transcribe_translate
$ cd audio_transcribe_translate

2. 必要なライブラリのインストール

$ uv add git+https://github.com/openai/whisper.git#egg=openai-whisper
$ uv add googletrans==4.0.0-rc1

(requirements.txt とかで管理しても良いですね。)

3. 実行コードの作成

実際のコード audio_transcribe_translate.py

import whisper
import sys
import os
from googletrans import Translator
import re
import time

# 文章の区切りを考慮して分割する関数
def split_text_by_sentences(text, max_length=5000):
    sentences = re.split(r'(?<=[.!?。!?])\s*', text)
    chunks = []
    current_chunk = ""

    for sentence in sentences:
        if len(current_chunk) + len(sentence) <= max_length:
            current_chunk += sentence + " "
        else:
            chunks.append(current_chunk.strip())
            current_chunk = sentence + " "

    if current_chunk.strip():
        chunks.append(current_chunk.strip())

    return chunks


# 翻訳関数
def translate_text_in_chunks(text, src="en", dest="ja", max_length=5000):
    translator = Translator()
    chunks = split_text_by_sentences(text, max_length)
    translated_text = ""

    for i, chunk in enumerate(chunks):
        try:
            print(f"翻訳中: チャンク {i+1}/{len(chunks)}")
            translation = translator.translate(chunk, src=src, dest=dest)
            translated_text += translation.text + "\n"
            time.sleep(1)  # 翻訳間の遅延
        except Exception as e:
            print(f"翻訳エラー: {e}. リトライ中...")
            time.sleep(2)

    return translated_text


# メイン処理
def main():
    if len(sys.argv) < 2:
        print("使用法: python script_name.py <path/to/your_audio_file>")
        sys.exit(1)

    audio_file = sys.argv[1]

    # ファイルの存在確認
    if not os.path.exists(audio_file):
        print(f"エラー: 指定されたファイルが見つかりません: {audio_file}")
        sys.exit(1)

    # Whisperモデルのロード
    print("Whisperモデルをロード中...")
    model = whisper.load_model("base")

    # 音声ファイルを文字起こし
    print("文字起こし中...")
    result = model.transcribe(audio_file)
    english_text = result["text"]
    print("文字起こしが完了しました。")

    # 日本語に翻訳
    print("日本語に翻訳中...")
    japanese_text = translate_text_in_chunks(english_text)

    # 英語の文字起こし結果を保存
    english_output_file = "transcription_english.txt"
    with open(english_output_file, "w", encoding="utf-8") as file:
        file.write(english_text)
    print(f"英語の文字起こし結果を保存しました: {english_output_file}")

    # 日本語の翻訳結果を保存
    japanese_output_file = "translation_japanese.txt"
    with open(japanese_output_file, "w", encoding="utf-8") as file:
        file.write(japanese_text)
    print(f"日本語の翻訳結果を保存しました: {japanese_output_file}")


if __name__ == "__main__":
    main()

Special Thanks: Chat-GPT4o

Whisper のロードモデルについては、base としていますが、端末のパワーがあったので medium.en とかで実行したと記憶しています。

4. 実行

$ . .venv/bin/activate
(audio_transcribe_translate) $ python audio_transcribe_translate.py <録音したボイスメモ.m4a>

モデルタイプによって、モデルのロードと、トランスクライブに少し時間がかかります。
完了すると、 transcription_english.txttranslation_japanese.txt が出力されます。

5. 確認

  • transcription_english.txt
    • 録音音声の質、スピーカーの発音等によりますが、固有名詞等以外はかなりの精度で文字起こしができています。
  • translation_japanese.txt
    • これはそもそもの翻訳の質が Google Translate のものなので、参考程度だなという感じです
    • DeepL などより適した有料 API を使えば、なお文脈を加味した翻訳となるでしょう

感想とか言い訳とか

はい、冒頭で書いた通り使いませんでした。聴き直す暇がなかったです。
(結局 Youtube にセッションの動画がすぐ上がりましたしね…。)

あと一つだけ罠ポイントがあって、セッションには Silent Session なるものが存在します。
下記のブログでも触れていますが、ヘッドフォンを着用して聞くセッションです。 blog.serverworks.co.jp

この場合、ボイスメモが取れないので、自力で聞くしかないです。

とはいえ、こういった取り組みを現地調達するのも、また技術者っぽいのかもなと思い書き散らしておきます。

来年以降行く人が同じようなことを考えたりするかもしれないし、事前にもっと良い仕組みを用意して社内に配布してくれるかもしれませんしね^^

真面目に振り返ると、結果的には有料のアプリを購入して、同時翻訳を活用すれば済んだ話かもしれません。
付け焼き刃とはいえ、事前に勉強した甲斐あってか、自分のヒアリング力がそれなりにあって助かったというだけなので、事前にどうやって英語のセッションに臨むかは各々考えておくとよいでしょう。

転ばぬ先の杖とはまさにこのこと

まとめ

だいぶタイトル・本筋とズレるまとめになりますが

  • 英語力に自信がある人以外は、きちんとセッションに臨む姿勢を考えていこう!
  • 自分の英語力はまだまだだなと痛感したので、継続して勉強していきます!

という感じで締めさせていただきます。

それでは、また

駒井 基 (記事一覧)

MS2課所属

身体の歪みと姿勢が気になるお年頃