--- title: 商談文字起こし emoji: 🎙️ colorFrom: gray colorTo: indigo sdk: gradio sdk_version: 6.25.0 app_file: app.py pinned: false short_description: 商談録音のZIPを文字起こしして話者ごとにCSVにする --- # 商談文字起こし 録音ZIPを投げると、指定秒数以上のファイルだけを文字起こしし、話者ごとに分けてCSVにするWebアプリ。 - 文字起こし: Whisper large-v3 系(faster-whisper。GPUがあれば自動で使う) - 話者分離: pyannote.audio 3.1(任意。使うにはHuggingFaceのトークンが要る) - 動く場所: HuggingFace Spaces の **ZeroGPU**(URLでどのPCからでも)/ 自分のPC(CPUで完結) 同じコードが両方で動く。GPUがあれば使い、無ければCPUに落ちる。 **ZeroGPU について。** PRO(月$9)に付いてくるGPU枠で、追加費用なしで使える。 呼び出しのたびにGPUが割り当てられ、終わると解放される。 Gradio SDK 専用の仕組みなので、このアプリは Gradio で作ってある(Docker Space では使えない)。 --- ## 1. HuggingFace Spaces に置く(URLで使う) ### 1-1. Space を作る 1. https://huggingface.co/new-space を開く 2. **Space name** に `shodan-mojiokoshi` など。**Owner** は個人でもOrganizationでも可 3. **License** は任意。**Space SDK** は **Gradio** を選ぶ(ZeroGPU は Gradio 専用) 4. **Public** を選ぶ(理由は下記) 5. **Create Space** **Public と Private の選び方** | | 誰が使えるか | 向き不向き | |---|---|---| | Public + アプリのパスワード(推奨) | URLとパスワードを知っている人。**HuggingFaceのアカウントは不要** | 社内の誰でもURLを開けばいい。ソースコードは公開されるが、録音や結果は公開されない | | Private | Space の所有者と Organization メンバーだけ。**全員にHuggingFaceアカウントが必要** | 社内の全員をHuggingFaceのOrganizationに招く前提なら | 「どのパソコンからでもURLで」を優先するなら **Public + 次のパスワード設定**が実用的です。 その場合、**パスワード設定は必須**と考えてください。 ### 1-2. パスワードを設定する(必ず先に) Space の **Settings** → **Variables and secrets** → **New secret** | Name | Value | |---|---| | `APP_PASSWORD` | 好きなパスワード | | `APP_USER` | ログイン名(省略すると `spin`) | | `HF_TOKEN` | 話者分離を使うときだけ。read権限のトークン | `APP_PASSWORD` を設定しないと、**URLを知っている人は誰でも使えます**(その状態のときは画面に赤い警告が出ます)。 Public にするなら、コードを送る前にここを設定しておく。 ### 1-3. ハードウェアを ZeroGPU にする Space の **Settings** → **Space hardware** → **ZeroGPU** を選ぶ。 PRO を契約していれば一覧に出る。追加費用はかからない。 CPU basic のままでも動くが、録音1時間あたり30〜60分かかる。 ### 1-4. コードを送る Space のページに出ている git のURLを使って、このフォルダから送る。 ```bash git remote add space https://huggingface.co/spaces/<オーナー名>/ git push space main ``` 初回はパスワードの代わりに **HuggingFace のアクセストークン(write権限)** を聞かれる。 送ると Space が自動でビルドを始める。**初回は15〜20分かかる**(Whisperのモデルを箱に焼き込むため)。 Building → Running に変われば完成。以後 `git push space main` するたびに自動で更新される。 GitHubにも置きたい場合は、リモートを2つ持たせればいい。 ```bash git remote add origin https://github.com/<アカウント>/<リポジトリ>.git git push origin main ``` --- ## 2. 速度と料金 Spaces の無料枠は 2 vCPU。**動くが、速くはない。** | ハードウェア | 料金 | 1時間の商談を文字起こしする時間 | |---|---|---| | **ZeroGPU(PRO に付属)** | **¥0**($9のPRO内) | **数分** | | CPU Basic(無料) | ¥0 | 1〜2時間 | | CPU Upgrade(8 vCPU) | $0.03/時 | 20〜30分 | **話者分離は既定でオフにしてある。** オンにすると処理時間が倍以上になるため。 どちらが話したかが要るときだけチェックを入れる。 **「速さを優先する」も既定でオン。** さらに1割ほど速くなる。 実測での違いは句読点が少し減る程度で、本文の中身は変わらなかったため。 句読点まで整えたいときだけ外す。 **モデルは起動と同時に読み込む。** 読み込みには数十秒かかるので、頼まれてから読み始めると 短い録音ではそれが待ち時間の大半になる。画面のバッジで「モデル準備済み」かどうかが分かる。 なお、**まとめ処理(BatchedInferencePipeline)は採用していない。** CPUでは実測で1.1倍程度にしかならず、出力も変わるため。GPUに載せるなら再検討の余地がある。 **精度の選択肢について**(51秒の音声・同じCPUでの実測) | 選択肢 | 文字起こし | モデル読込 | 中身 | |---|---|---|---| | 標準(turbo)=既定 | 31.6秒 | 11秒 | 実用水準 | | 速度優先(small) | 23.5秒 | 26秒 | 体温→対応、脈拍→脈白 のような誤変換が増える | | 精度優先(large-v3) | 数倍 | 長い | いちばん正確 | `medium` は選択肢から外した。**実測で turbo の約2倍遅く(59.1秒)、精度も turbo と同等**だったため。 turbo は大きいモデルの高速版なので、medium より速くて正確という関係になっている。 有料ハードウェアは **Settings → Sleep time** で「◯分使われなければ寝る」を設定できる。 寝ている間は課金されないので、月に数時間しか使わないなら、有料でも実際の請求は数十円〜数百円で収まる。 **まず無料で始めて、遅くて使い物にならなければ上げる**のが安全。ハードウェアの変更は Settings から数クリックで、コードの変更は要らない (ただしGPUを使う場合は `Dockerfile` の torch を CUDA版に差し替える必要があるので、そのときは声をかけてください)。 無料枠は **48時間使われないと寝る**。次に開いた人は起動を数分待つことになる。 --- ## 3. 使い方 1. **音声ファイルを選ぶ(何本でも可)/ZIPを選ぶ/その混在**(ドラッグでも可) 2. しきい値(**既定60秒**)と話者人数を設定 3. 「文字起こしを始める」 処理が終わると、ファイルごとの書き起こしが画面に出て、CSVを保存できる。 **扱える音声の形式** wav, mp3, m4a, mp4, aac, flac, ogg, opus, wma, amr, 3gp, mov, aif, aiff。 中で ffmpeg が変換するので、ICレコーダー・スマホ・電話録音、どれでもそのまま入れてよい。 ZIPにまとめる必要はないが、数が多いときはZIPのほうが選ぶのが楽。 ### 音声を聞き返せる 処理が終わると、録音カードの中に**再生機**が出る。 **発話の左の時刻をクリックすると、その場面から録音が流れる**ので、 聞き取りにくい行だけ確かめられる。画面にもその案内を出している (押せることは見ただけでは分からないため)。 - 再生用の音声はサーバー側で mp3 に揃えてある(.amr や .wma もブラウザで鳴る) - **元の音声から作るので、聞き取りやすさは保たれる。** サンプリング周波数もチャンネル数も落とさない。 声の高さ・速さ・抑揚は変わらない(圧縮で高い音の細かさが減るだけ) - 元が mp3 ならそのまま使う。変換を重ねるほど音は悪くなるため - 元の音声は処理が終わった時点で消している - 再生用の音声も**6時間で自動的に消える**。商談の音声を必要以上に置いておかないため ### 設定は覚えている しきい値・精度・話者の人数・話者分離の有無・よく出る言葉は、**触ったその場でブラウザに保存され、 次に開いたときも同じ内容で始まる。** 毎回入れ直す必要はない。 - 保存先はブラウザ(localStorage)。サーバーには置かない - Spaces の保存領域は再起動で消えるため - 使う人ごとに設定を変えられるようにするため(営業ごとに「よく出る言葉」が違う) - 元に戻したいときは、設定欄の下の **「既定に戻す」** - **HuggingFaceトークンだけは保存しない。** 鍵をブラウザに置かないため。 毎回入れたくない場合は、Space の Secret に `HF_TOKEN` を登録する。 登録すると**入力欄そのものが消えて**「サーバー側に設定済み」の表示に変わるので、 誤って別の鍵を入れて原因の分かりにくい失敗をすることがなくなる。 別の鍵を使いたいときだけ「別のトークンを使う」で入力欄を出せる **短い録音は自動で外れる。** 既定では60秒未満のファイルは文字起こしせず、 「60秒未満」という理由付きで一覧にだけ残す。言い間違いの録り直しや操作ミスの数秒ファイルを、 処理時間とCSVから締め出すための仕組み。秒数は画面で変えられる。 ### 話者の見え方 話者分離を使うと `SPEAKER_00` `SPEAKER_01` というラベルで色分けされる。 **既定ではオフ**(処理時間が倍以上になるため)。 **実名に置き換える機能は意図的に持たせていない。** 話者の割り当ては機械の推測でしかなく、 そこに実名を貼ると、確定した事実のように見えてしまうため。 どちらが誰かは、読む人が本文から判断する。 録音ごとに**話者別の発話時間と回数**も出る(`SPEAKER_00 2分30秒・12回` のように)。 商談でどちらが喋っていたかの目安になる。 なお、色は録音ごとに割り当て直される。別の録音の `SPEAKER_00` は別人なので、 色を共有すると同一人物に見えてしまうため。 ### 出力 **出力は「録音ごと.xlsx」だけ。** 1録音=1シートで、先頭に一覧シートが付く。 以前はZIP一式・全発話CSV・処理結果一覧も作っていたが、使わない形式まで作ると そのぶん待たせることになるのでやめた(作る仕組み自体は `pipeline.py` に残してある)。
以前の形式について **録音1本ごとに分ける**のを基本にしている。「一式をZIPで保存」を押すと次の構成で落ちてくる。 ``` 文字起こし_/ ├── 書き起こし/ │ ├── 001_A社_初回訪問.txt ← 人が読む用。そのまま議事録に貼れる │ └── 002_B社_見積提示.txt ├── データ/ │ ├── 001_A社_初回訪問.csv ← システム取り込み用 │ └── 002_B社_見積提示.csv ├── 録音ごと.xlsx ← 1録音=1シート+先頭に一覧シート ├── 処理結果一覧.csv ← 何が処理され何が除外されたかの記録 └── 全発話.csv ← 横断で検索・集計したいとき用 ``` 画面上でも録音ごとの折りたたみカードになっていて、1本だけコピーしたり、 その録音のテキスト/CSVだけを個別に落とすこともできる。
**CSVの列**(`pipeline.py` で作る場合) | 列 | 内容 | |---|---| | 開始 / 終了 | 発話のタイムコード | | 話者 | SPEAKER_00, SPEAKER_01 …(話者分離オフなら空) | | 発話内容 | 書き起こし | | 要確認 | 誤認識の疑いがある行に印 | | 備考 | 疑わしいと判定した理由 | Excelで文字化けしないようBOM付きUTF-8で出力している。 30秒未満で除外したファイルは発話CSVには出ないが、`処理結果一覧.csv` と `書き起こし/` のテキストには理由付きで残る。何が処理されなかったかを黙って消さないため。 --- ## 4. 話者分離を使う(任意) 商談で「どちらが話したか」を分けたいときに使う。設定しなくても文字起こしは動く(話者列が空になるだけ)。 1. https://huggingface.co/pyannote/speaker-diarization-3.1 と https://huggingface.co/pyannote/segmentation-3.0 の**両方**で利用条件に同意する 2. https://huggingface.co/settings/tokens で read 権限のトークンを作る 3. Space の Secret に `HF_TOKEN` として登録する(自分のPCで動かすなら `setx HF_TOKEN "hf_..."`) 1対1の商談なら「話者の人数」に `2` を入れると精度が上がる。 ただし**話者分離は文字起こしと同じかそれ以上の時間がかかる**ので、無料枠では現実的でないことが多い。 --- ## 5. 知っておくべき制約 **話者の名前は自動では分からない。** `SPEAKER_00` のような番号が振られるだけで、 どちらが自社でどちらが先方かは人が対応付ける必要がある。 **精度は録音環境に強く依存する。** レコーダーを机に置いた1対1の商談なら実用的だが、 複数人が同時に話す、店内の騒音が大きい環境では話者の切り替わりを取りこぼす。 「完成品」ではなく「人が直す前提の下書き」として設計してある。 **Whisperは無音区間で幻の文を作る。** 「ご視聴ありがとうございました」のような定型句が典型。 検出した行は削除せず「要確認」を立てるようにした。勝手に消すと、 本当の発話まで消えたときに気づけないため。 **録音は相手の同意を得てから。** 商談相手との会話を録音すること自体は違法ではないが、 文字起こしを社外に出す、AIの学習に使うといった話になると別問題になる。運用ルールは決めておく。 --- ## 6. 自分のPCで動かす(ローカル完結) 外に一切出したくない録音は、こちらを使う。 ```powershell .\setup.ps1 # 初回のみ。Python環境を作る ``` ffmpeg が要る。PowerShell で `winget install Gyan.FFmpeg`。 起動は `start.cmd` をダブルクリック。`127.0.0.1:8000` にのみ待ち受けるので、 同じPC以外からはアクセスできない。パスワードも掛からない(掛けたい場合は `APP_PASSWORD` を設定する)。 --- ## 7. ファイル構成 ``` spinthoughts/ ├── app.py 画面と実行(Gradio)。ZeroGPU の割り当てもここ ├── pipeline.py ZIP展開 → 長さフィルタ → 文字起こし → 話者分離 → 統合 ├── packages.txt Spaces に入れる OS 側のもの(ffmpeg) ├── setup.ps1 自分のPCで動かすための初回セットアップ ├── start.cmd 自分のPCでの起動 ├── requirements.txt ├── tests/ 話者まわりの自動テスト(pytest) └── README.md ``` テストは `.venv\Scripts\python.exe -m pytest tests -q` で走る(`pip install -r requirements-dev.txt` が必要)。 文字起こし本体は重いので動かさず、話者の割り当て・名前の付け替え・出力の中身だけを確かめている。 処理の中身を変えたいときは `pipeline.py` を見る。 `DEFAULT_PROMPT` に自社名・製品名・業界用語を足すと、固有名詞の精度が上がる。 `HALLUCINATION_PATTERNS` には、自分の環境で出やすい誤認識を足せる。 ### 環境変数 | 名前 | 既定 | 用途 | |---|---|---| | `APP_PASSWORD` | 空(=認証なし) | 設定すると閲覧にパスワードが要る | | `APP_USER` | `spin` | ログイン名 | | `HF_TOKEN` | 空 | 話者分離に使う | | `HOST` | `127.0.0.1` | 外から繋ぐときは `0.0.0.0` | | `PORT` | `7860` | 待ち受けるポート | | `GPU_RETRIES` | `2` | GPUが混んでいたときに粘る回数。使い切ったらCPUで続ける | --- ## 8. うまくいかないとき | 症状 | 対処 | |---|---| | Space が Build error | ログの最後を見る。多くは requirements.txt の綴りかネットワーク。再ビルドで直ることもある | | GPUなのに遅い | Settings のハードウェアが ZeroGPU になっているか確認。CPU basic のままだと当然遅い | | `libcudnn` が無いと出る | faster-whisper が GPU を使うのに要るライブラリ。`pipeline.py` が先読みして回避しているが、それでも出るならログを見る | | Space が寝ている | 無料枠は48時間で寝る。開いて数分待つ | | パスワードを聞かれない | `APP_PASSWORD` が未設定。Settings → Secrets を確認 | | 話者分離モデルが読めない | HuggingFaceの2ページ両方で条件に同意したか、トークンが read 権限か確認 | | `unexpected keyword argument 'use_auth_token'` | pyannote 4 で引数名が変わったもの。対応済みなので `pipeline.py` を最新に上げ直す | | `torchcodec is not available` | pyannote 4 の音声読み込み。ffmpeg で変換済みのWAVを自前で渡すよう対応済み。`pipeline.py` を最新に上げ直す | | `403 ... not in the authorized list` | エラー文にURLが出ているモデルの利用条件に未同意。そのページで同意する(`speaker-diarization-community-1` も必要) | | 日本語ファイル名が化ける | Windows製ZIPのCP932は自動判別済み。それでも化けるなら元のZIPの作り方を確認 | | 極端に遅い | 精度を `turbo` に落とす、話者分離をオフにする、ハードウェアを上げる | | 発話が検出されない | 録音の音量が小さい可能性。`ffmpeg -i 元ファイル -af loudnorm 正規化後.wav` で正規化してから再投入 | | ローカルでモデル取得が WinError 1314 | HuggingFaceキャッシュのシンボリックリンク。実体コピーに切替済み。残骸が残っていたら `.cache\huggingface` の該当フォルダを消して再実行 |