YouTubeデータ取得 データ取得

Whisperで許諾済み音声を文字起こしして要約する方法|Pythonで分析前処理を整える

2025年9月29日

この記事では、Whisperを使って音声ファイルを文字起こしし、その結果をPythonで扱いやすい形に整える方法をまとめます。

対象は、自分で録音した音声、自分の動画、利用許諾のある動画・音声、社内や勉強会など権利関係を確認できる素材を想定しています。YouTube動画を扱う場合も、保存や再配布を目的にするのではなく、許諾済み素材の分析前処理として使う前提で進めます。

この記事でできること

  • Whisperで音声ファイルを文字起こしする
  • 文字起こし結果をテキストファイルやCSVに保存する
  • 長い音声を扱うときの前処理を考える
  • 文字起こし結果を要約する前に確認したい点を整理する
  • 分析記事に使うときの注意点を押さえる

想定読者

  • 音声や動画の内容をテキスト化して分析したい方
  • 配信、講義、インタビュー、勉強会などの内容を整理したい方
  • YouTube関連データを扱う前処理として文字起こしを使いたい方
  • WhisperをPythonから試したい方

利用上の注意

音声データには、著作権、肖像権、プライバシー、サービス規約などが関係することがあります。特に第三者の動画・配信・会話を扱う場合は、文字起こししたテキストをそのまま公開しないよう注意が必要です。

  • 自分が権利を持つ音声、または利用許諾のある音声を対象にしてください。
  • 文字起こし結果を公開する場合は、原文の長文転載にならないようにしてください。
  • 個人名、アカウント名、連絡先、住所などの個人情報が含まれる場合は、公開前に削除または匿名化してください。
  • AIによる文字起こしや要約には誤りが含まれるため、重要な判断には原音声や一次情報の確認が必要です。

このサイトでは、文字起こし結果は「内容を理解しやすくするための中間データ」として扱います。分析記事で使う場合も、発言の全文掲載ではなく、傾向や話題の整理に寄せる方針です。

全体の流れ

Whisperを使った文字起こしは、次の流れで進めると整理しやすいです。

  1. 音声ファイルを用意する
  2. 必要に応じてffmpegで音声形式をそろえる
  3. Whisperで文字起こしする
  4. 結果をテキストやCSVに保存する
  5. 要約やキーワード抽出などの分析に使う

最初から要約まで一気に自動化するより、まずは文字起こし結果を確認し、必要に応じて修正・分割してから要約に進むほうが扱いやすいです。

事前準備

Python環境

この記事では、PythonからWhisperを実行します。仮想環境を使う場合は、先に環境を作成して有効化しておきます。

python -m venv .venv
.venv\Scripts\activate

PowerShellで実行ポリシーにより有効化できない場合は、コマンドプロンプトを使うか、実行ポリシーの設定を確認してください。

ffmpeg

Whisperや音声前処理ではffmpegを使う場面があります。先に次のコマンドで確認しておきます。

ffmpeg -version

ffmpegの導入方法は、別記事で整理しています。

ffmpegのインストールとPATH設定(Windows)|音声・動画データ分析の前処理環境を整える

Whisperをインストールする

WhisperをPython環境にインストールします。

pip install -U openai-whisper

GPUを使う場合は、PyTorchの環境構築が別途必要になることがあります。まずはCPUで動作確認し、処理時間が長い場合にGPU環境を検討するくらいでよいと思います。

インストール後、次のコマンドでWhisperが実行できるか確認します。

whisper --help

音声ファイルを用意する

ここでは、次のような音声ファイルを想定します。

data/input_audio.wav

動画ファイルから音声だけを取り出す場合は、ffmpegでWAVに変換しておくと扱いやすいです。

yt-dlp音声保存例を使った場合は、入力の input.mp4 を実際に保存された audio/動画ID.wav に変更します。以下は許諾済み入力を前提とし、出力先dataを先に作成します。CLIとPythonは代替手順です。両方を連続実行する必要はありません。PythonではTXT保存後に同じresultからCSVを保存し、モデル推論は1回だけ行います。

# PowerShell: 作業フォルダーで実行
New-Item -ItemType Directory -Force data
ffmpeg -i input.mp4 -vn -acodec pcm_s16le -ar 16000 -ac 1 data/input_audio.wav

-ar 16000 はサンプルレートを16kHzに、-ac 1 はモノラルにそろえる指定です。必ずこの形式でなければならないわけではありませんが、文字起こし用の前処理として扱いやすい形です。

コマンドラインで文字起こしする

まずはコマンドラインから試します。

whisper data/input_audio.wav --language Japanese --model small --output_dir outputs/transcripts

実行すると、指定した出力フォルダにテキストや字幕形式のファイルが保存されます。最初は small など軽めのモデルで試し、精度や処理時間を見ながら変更するとよいです。

モデル特徴使いどころ
tiny / base軽いが精度は控えめ短い音声で動作確認したいとき
small速度と精度のバランスが取りやすいまず試すモデルとして扱いやすい
medium / large精度は上がりやすいが重い重要な音声や長い音声を丁寧に処理したいとき

モデル選びは、PCの性能、音声の長さ、必要な精度によって変わります。個人的には、まず短い音声で small を試し、固有名詞や専門用語が崩れやすい場合に上位モデルを検討します。

Pythonから文字起こしする

次に、PythonからWhisperを実行します。分析前処理として使う場合は、Python内で結果を受け取れる形にしておくと後続処理につなげやすいです。

from pathlib import Path

import whisper

audio_path = Path("data/input_audio.wav")
output_dir = Path("outputs/transcripts")
output_dir.mkdir(parents=True, exist_ok=True)

model = whisper.load_model("small")
result = model.transcribe(str(audio_path), language="ja")

text = result["text"].strip()

(output_dir / "transcript.txt").write_text(text, encoding="utf-8")

print(text[:500])

result["text"] には、全体の文字起こし結果が入ります。まずはテキストファイルに保存して、内容が大きく崩れていないか確認します。

発話区間ごとにCSVへ保存する

Whisperの結果には、発話区間ごとの情報も含まれます。あとで話題の変化や時間帯ごとの傾向を見たい場合は、CSVにしておくと扱いやすいです。

import csv
from pathlib import Path

# 直前のPython例を同じ実行で続け、result / output_dirを再利用します。

csv_path = output_dir / "segments.csv"

with csv_path.open("w", encoding="utf-8-sig", newline="") as f:
    writer = csv.DictWriter(f, fieldnames=["start", "end", "text"])
    writer.writeheader()

    for segment in result["segments"]:
        writer.writerow({
            "start": round(segment["start"], 2),
            "end": round(segment["end"], 2),
            "text": segment["text"].strip(),
        })

print(f"saved: {csv_path}")

CSVにしておくと、ExcelやGoogle Sheetsでも確認できます。分析用には、全文テキストよりも発話区間ごとのデータのほうが扱いやすい場面があります。

長い音声を扱うときの考え方

長時間の音声をそのまま処理すると、時間がかかったり、途中で失敗したり、要約時に文脈が混ざったりすることがあります。長い音声では、先に分割しておくと扱いやすくなります。

たとえば、10分ごとに音声を分割する場合は次のように実行できます。

ffmpeg -i data/input_audio.wav -f segment -segment_time 600 -c copy data/chunk_%03d.wav

ただし、無音部分や話題の切れ目を考えずに機械的に分割すると、文の途中で切れることがあります。分析目的なら、多少ざっくり分割してから後で人間が確認するくらいの運用でも十分なことが多いです。

分割一覧を残し、元音声の時刻へ戻して結合する

Whisperは内部で音声を区間処理するため、長時間なら常に手動分割が必須というわけではありません。分割は失敗箇所の再確認や処理単位を管理したいときの選択肢です。次は上の単純分割とは代替で、開始時刻を記録する追加例です。既存の単一音声のTXT/CSV保存例はそのまま利用できます。

# 作業フォルダーで実行。既存run1があれば別名に変える
New-Item -ItemType Directory -Path data\chunks-run1 -ErrorAction Stop
ffmpeg -n -i data/input_audio.wav -map 0:a:0 -ar 16000 -ac 1 -c:a pcm_s16le -f segment -segment_time 600 -segment_list data/chunks-run1/chunks.csv -segment_list_type csv -reset_timestamps 1 data/chunks-run1/chunk_%03d.wav
Get-Content data/chunks-run1/chunks.csv

FFmpegの終了コード0と、生成された一覧・各WAVの存在を確認してから進みます。入力は前段で用意した時刻0からのWAVです。chunks.csv はヘッダーなしで「ファイル名、元音声での開始秒、終了秒」を記録します。600秒指定でも境界は実際の音声フレームに依存します。ファイル番号×600ではなく、この一覧の開始秒を発話時刻へ加算します。最後が短い区間でも、一覧の実際の終了秒を使用します。既存の一覧なしchunkファイルから開始時刻を推測しないでください。

以下を transcribe_chunks.py として保存します。

import csv
import json
import math
from pathlib import Path


def transcribe_chunks(list_path, output_dir, model_name="small", language="ja", model=None):
    manifest = Path(list_path).resolve()
    with manifest.open(encoding="utf-8", newline="") as f:
        entries = list(csv.reader(f))  # FFmpeg CSVにはヘッダーがありません
    if not entries:
        raise ValueError("分割一覧が空です")
    chunks, previous_end, seen = [], 0.0, set()
    for name, start, end in entries:
        start, end = float(start), float(end)
        path = (manifest.parent / name).resolve()
        if path.parent != manifest.parent or name in seen:
            raise ValueError("一覧のパスまたは重複を確認してください")
        if not all(math.isfinite(x) for x in (start, end)) or start < 0 or end <= start:
            raise ValueError("一覧の時刻が不正です")
        if abs(start - previous_end) > 0.02:
            raise ValueError("一覧に欠落・重複区間があります")
        chunks.append((path, start, end));seen.add(name);previous_end = end
    out = Path(output_dir)
    out.mkdir(parents=True, exist_ok=False)  # 新規フォルダーで実行
    (out / "chunks.csv").write_bytes(manifest.read_bytes())
    report = {"status":"RUNNING", "model":model_name, "language":language, "chunks":[]}
    def checkpoint():
        (out / "status.json").write_text(json.dumps(report, ensure_ascii=False, indent=2), encoding="utf-8")
    checkpoint()
    if model is None:
        try:
            import whisper
            model = whisper.load_model(model_name)  # ループ外で1回
        except Exception as exc:
            report.update(status="MODEL_FAILED", error=type(exc).__name__);checkpoint();raise
    merged = []
    for path, offset, source_end in chunks:
        item = {"file":path.name, "source_start":offset, "source_end":source_end}
        if not path.is_file():
            item["status"] = "MISSING"
        else:
            try:
                result = model.transcribe(str(path), language=language, fp16=False)
                (out / (path.stem + ".json")).write_text(json.dumps(result, ensure_ascii=False), encoding="utf-8")
                segments = result.get("segments", [])
                local = []
                for s in segments:
                    a, b = float(s["start"]), float(s["end"])
                    if not all(math.isfinite(x) for x in (a,b)) or a < 0 or b < a or b > source_end-offset+0.1:
                        raise ValueError("発話時刻が実区間の範囲外です")
                    if s["text"].strip():
                        local.append({"file":path.name,"start":offset+a,"end":offset+b,"text":s["text"].strip()})
                item["status"] = "OK" if local else "EMPTY"
                merged.extend(local)
            except Exception as exc:
                item.update(status="FAILED", error=type(exc).__name__)
        report["chunks"].append(item);checkpoint()
    complete = all(x["status"] == "OK" for x in report["chunks"])
    stem = "merged" if complete else "partial"
    merged.sort(key=lambda r:(r["start"],r["end"]))
    with (out / (stem + ".csv")).open("w", encoding="utf-8-sig", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=["file","start","end","text"])
        writer.writeheader();writer.writerows(merged)
    (out / (stem + ".txt")).write_text("\n".join(r["text"] for r in merged), encoding="utf-8")
    report.update(status="COMPLETE" if complete else "PARTIAL", rows=len(merged));checkpoint()
    print(report["status"], out.resolve())
    return report


if __name__ == "__main__":
    import argparse
    p=argparse.ArgumentParser()
    p.add_argument("list_path");p.add_argument("output_dir")
    p.add_argument("--model",default="small");p.add_argument("--language",default="ja")
    a=p.parse_args()
    result=transcribe_chunks(a.list_path,a.output_dir,a.model,a.language)
    raise SystemExit(0 if result["status"]=="COMPLETE" else 2)
python transcribe_chunks.py data/chunks-run1/chunks.csv outputs/chunk-transcripts-run1 --model small --language ja

出力は一覧のコピー、各区間の元結果JSON、status.json、結合CSV/TXTです。すべてOKなら merged.csvmerged.txt、欠損MISSING・例外FAILED・空結果EMPTYがあれば partial.csvpartial.txt になり、終了コード2を返します。空結果は無音の可能性もありますが、全件成功にはせず原音を確認します。CSVのstart/endは元音声の秒です。モデルはループ外で1回だけ読み込みます。区切りで文が途切れる問題や音声認識の誤りは残るため、境界付近を原音と照合してください。出力フォルダーは毎回別名にし、部分結果を過去の完成結果と混ぜません。

文字起こし結果を要約する前に確認したいこと

文字起こし結果をそのまま要約にかける前に、最低限次の点を確認します。

  • 話者名、アカウント名、個人情報が含まれていないか
  • 文字起こしの誤認識が多すぎないか
  • 専門用語やゲーム名、アプリ名が崩れていないか
  • 要約対象として長すぎないか
  • 引用ではなく、傾向整理として使える形になっているか

AI要約は便利ですが、元の文字起こしが誤っていると要約もずれます。特に固有名詞、否定表現、数字、日時は確認したほうがよいです。

簡単な要約用プロンプト例

文字起こし結果を要約する場合は、全文をそのまま短くするより、目的を決めて整理すると使いやすくなります。

以下は、許諾済み音声を文字起こししたテキストです。
内容を公開用にそのまま引用するのではなく、分析メモとして整理してください。

出力してほしい内容:
- 主な話題
- 繰り返し出てくる論点
- 判断に使えそうな発言の要旨
- 確認が必要な曖昧な点

注意:
- 個人名やアカウント名は出さない
- 断定しすぎない
- 元の発言と異なる意味に言い換えない

このように、公開文としての要約ではなく、まず分析メモとして整理すると、後から自分の言葉で記事にしやすくなります。

分析記事に使うときの書き方

文字起こし結果を記事に使う場合、全文を載せるよりも、話題の分類や傾向の整理に使うほうが読みやすくなります。

  • 「この音声では、主にA・B・Cの話題が多かった」
  • 「序盤は導入説明が中心で、中盤以降は具体例が増えた」
  • 「固有名詞の誤認識があったため、集計前に手動で補正した」
  • 「自動文字起こしのため、細かな表現は原音声と完全には一致しない」

こうした書き方にしておくと、文字起こしの限界を隠さず、かつ読者にとってもデータの扱い方が見えやすくなります。

よくあるエラーと確認ポイント

ffmpegが見つからない

次のようなエラーが出る場合は、ffmpegがPATHに通っていない可能性があります。

FileNotFoundError: [WinError 2] The system cannot find the file specified

ffmpeg -version がPowerShellで動くか確認し、動かない場合はffmpegのPATH設定を見直します。

処理が遅い

CPU環境では、長い音声や大きいモデルの処理に時間がかかります。まずは短い音声で試し、モデルを tinybasesmall の順に変えながら、精度と速度のバランスを確認するとよいです。

文字起こしが崩れる

音声が小さい、BGMが大きい、複数人が同時に話している、専門用語が多い場合は、文字起こしが崩れやすくなります。重要な部分だけ手動で確認する、音声を短く分ける、モデルを変えるなどで改善することがあります。

この記事の位置づけ

この記事は、YouTubeや音声データを分析する前の「文字起こし前処理」にあたる記事です。動画・字幕・チャットなど、YouTube関連データの取得方法全体は次の記事にまとめています。

YouTubeの情報を取得する方法まとめ|公式API・字幕・チャット・文字起こしの使い分け

動画や音声の準備には、次の記事もあわせて確認すると流れがつかみやすいです。

追加例の確認環境:Python 3.12、yt-dlp 2026.8.19、youtube-transcript-api 1.2.4、openai-whisper 20250625、FFmpeg 8.0。自作文章から合成した13.919秒の日本語音声を実FFmpegで3区間へ分割し、tiny/baseモデルの実推論を行いました。区間外の発話時刻が出たため、tinyは1区間OK・2区間FAILED、baseは2区間OK・1区間FAILEDとなり、どちらもPARTIALです。合成発話結果を使った時刻加算・全件保存は別途確認しています。全区間の実推論成功や認識精度を保証する例ではありません。範囲外時刻は無理に切り詰めず、該当区間の元結果と原音を確認してください。

まとめ

Whisperを使うと、音声ファイルをPythonから文字起こしし、テキストやCSVとして保存できます。音声データを分析する前処理として使う場合は、まず短い音声で動作確認し、結果を確認してから長い音声や要約処理に進むと扱いやすいです。

文字起こし結果は便利な中間データですが、そのまま正解として扱うものではありません。公開記事に使う場合は、権利関係、個人情報、誤認識、長文転載にならないかを確認し、傾向や話題の整理として使うのが安全です。

-YouTubeデータ取得, データ取得