Python入門トップページ


目次

  1. 仮想環境を構築し,watchdog をインストールする
  2. Watchdog でフォルダを監視する
  3. 新しいファイルを検知する
  4. 検知した新しいファイルを待ち行列フォルダに移動する
  5. 文字起こし処理用のワーカープログラムのひな形を作成する
  6. ワーカープログラムを常駐化する
  7. フォルダ監視とワーカープログラムでパイプライン処理する
  8. 処理用フォルダを設置してそこで処理を行う
  9. ファイルサイズの変化を監視する
  10. サイズの大きなファイルのコピーに対応する
  11. タイムスタンプを表示する
  12. Subprocess で外部コマンドを起動する
  13. WhisperX の設定を行う
  14. ワーカープログラムで音声データからの文字起こしを実行する
  15. 文字起こし結果を完了用フォルダに移動する
  16. ログを保存し例外を処理する
  17. サブフォルダに結果を格納する
  18. Ollama をインストールする
  19. Python から Ollama にプロンプトを送る
  20. Ollama 処理を関数化する
  21. 文字起こしデータから議事録を作成する
  22. サイズの大きなテキストファイルにも対応する
  23. ワーカープログラムで文字起こし結果から議事録を生成する
  24. 議事録をマークダウンファイルに出力する
  25. 文字コードを強制的に指定する

フォルダ監視により,音声ファイルから文字起こしと議事録生成を自動化しよう

ログを保存し例外を処理する

前のページにおいてワーカープログラムの出力には WhisperX のログや文字起こし結果が表示されていました.ここではログをファイルに出力することを考えます.さらに,WhisperXでエラーが発生し文字起こしに失敗した場合は,例外処理を行い当該ファイルとログファイルをエラー用の error フォルダに移動することで,プログラムが終了せず,次のファイルの処理が始まるような機能も理も追加します.

worker.py
from pathlib import Path
import shutil
import time
import datetime
import subprocess

BASE_DIR = Path(__file__).parent
QUEUE_DIR = BASE_DIR / "queue"
WORKING_DIR = BASE_DIR / "working"
DONE_DIR = BASE_DIR / "done"
ERROR_DIR = BASE_DIR / "error"

# ディレクトリを作っておく
QUEUE_DIR.mkdir(parents=True, exist_ok=True)
WORKING_DIR.mkdir(parents=True, exist_ok=True)
DONE_DIR.mkdir(parents=True, exist_ok=True)
ERROR_DIR.mkdir(parents=True, exist_ok=True)

try:
    while True:
        for src in QUEUE_DIR.glob("*.m*"): # 監視するファイルの拡張子
            print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 処理開始: {src.name}")
            working_file = WORKING_DIR / src.name
            transcription_dir = WORKING_DIR / src.stem
            transcription_dir.mkdir(parents=True, exist_ok=True) # ディレクトリを作っておく
            log_file = transcription_dir / "whisperx.log"
            shutil.move(src, working_file)
            print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 作業開始: {working_file}")
            print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 出力先 : {transcription_dir}")
            print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 処理中...")
            try:
                with open(log_file, "w", encoding="utf-8") as f:
                    subprocess.run([
                        "whisperx",
                        "--model", "turbo", # tubo / large ...
                        "--language", "ja",
                        # "--device", "cuda",  # NVIDIA GPU を利用できる場合
                        "-o", str(transcription_dir),
                        "--diarize",  # 話者分離
                        str(working_file)
                    ],
                    stdout=f,
                    stderr=subprocess.STDOUT,
                    check=True)
            except subprocess.CalledProcessError:
                print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] WhisperXの処理に失敗しました")
                err_dst = ERROR_DIR / src.name
                err_transcription_dir = ERROR_DIR / src.stem
                shutil.move(working_file, err_dst)
                shutil.move(transcription_dir, err_transcription_dir)
                print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 失敗: {err_dst}")
                continue
            time.sleep(3)
            dst = DONE_DIR / src.name
            done_transcription_dir = DONE_DIR / src.stem
            shutil.move(working_file, dst)
            shutil.move(transcription_dir, done_transcription_dir)
            print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 完了: {dst}")
        time.sleep(1)

except KeyboardInterrupt:
    print(f"[{datetime.datetime.now().strftime('%H:%M:%S.%f')}] 停止します")

ターミナルを2個起動し,それぞれで watch.py と worker.py を実行します.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python watch.py ⏎
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python worker.py ⏎

2つのプログラムを起動後に監視用の watch フォルダに2個のマークダウンファイルと2個の音声ファイルを設置します.監視プログラムはファイルタイプの検証をせずにフォルダ直下に置かれた全てのファイルを待ち行列に送ります.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python watch.py
[18:00:01.713344] 新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\voice-sample02.m4a
[18:00:01.713580] 新しいファイルの名前: voice-sample02.m4a
[18:00:11.714753] ファイルの受取りが完了しました
[18:00:11.714893] ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\voice-sample02.m4a
[18:00:11.715407] ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\voice-sample02.m4a
[18:00:11.715592] 新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample1.md
[18:00:11.715686] 新しいファイルの名前: sample1.md
[18:00:21.717199] ファイルの受取りが完了しました
[18:00:21.717452] ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample1.md
[18:00:21.718993] ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample1.md
[18:00:21.719228] 新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample2.md
[18:00:21.719450] 新しいファイルの名前: sample2.md
[18:00:31.720796] ファイルの受取りが完了しました
[18:00:31.720978] ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample2.md
[18:00:31.721552] ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample2.md
[18:00:31.721694] 新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\voice-sample01.m4a
[18:00:31.721756] 新しいファイルの名前: voice-sample01.m4a
[18:00:41.722884] ファイルの受取りが完了しました
[18:00:41.723029] ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\voice-sample01.m4a
[18:00:41.723820] ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\voice-sample01.m4a
Ctrl + C
[18:01:40.111840] 停止します
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

ワーカープログラムは拡張子が .m で始まる全てのファイルについて WhisperX による文字起こし処理を実行します.もちろんマークダウンファイル(つまりテキスト形式のファイル)では文字起こし処理に失敗します.WhisperXのログをファイルに書き出したことでターミナルへの出力がスッキリしました.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python worker.py
[18:00:12.476224] 処理開始: voice-sample02.m4a
[18:00:12.477121] 作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\voice-sample02.m4a
[18:00:12.477184] 出力先 : C:\Users\rinsaka\Documents\python\watchdog\working\voice-sample02
[18:00:12.477210] 処理中...
[18:00:35.708352] 完了: C:\Users\rinsaka\Documents\python\watchdog\done\voice-sample02.m4a
[18:00:36.709409] 処理開始: sample1.md
[18:00:36.711010] 作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample1.md
[18:00:36.711089] 出力先 : C:\Users\rinsaka\Documents\python\watchdog\working\sample1
[18:00:36.711118] 処理中...
[18:00:49.129976] WhisperXの処理に失敗しました
[18:00:49.131247] 失敗: C:\Users\rinsaka\Documents\python\watchdog\error\sample1.md
[18:00:49.131378] 処理開始: sample2.md
[18:00:49.132037] 作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample2.md
[18:00:49.132091] 出力先 : C:\Users\rinsaka\Documents\python\watchdog\working\sample2
[18:00:49.132133] 処理中...
[18:00:59.629126] WhisperXの処理に失敗しました
[18:00:59.630227] 失敗: C:\Users\rinsaka\Documents\python\watchdog\error\sample2.md
[18:01:00.630858] 処理開始: voice-sample01.m4a
[18:01:00.632298] 作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\voice-sample01.m4a
[18:01:00.632385] 出力先 : C:\Users\rinsaka\Documents\python\watchdog\working\voice-sample01
[18:01:00.632453] 処理中...
[18:01:19.877177] 完了: C:\Users\rinsaka\Documents\python\watchdog\done\voice-sample01.m4a
Ctrl + C
[18:01:42.143140] 停止します
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

全ての処理が終わったので,完了用 done フォルダの内容を確認します.音声ファイルとその結果が格納されていることが分かります.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> gci done -Recurse ⏎

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\done

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2026/11/02    18:01                voice-sample01
d----          2026/11/02    18:00                voice-sample02
-a---          2026/10/27    12:36         329637 voice-sample01.m4a
-a---          2026/10/27    12:39         554089 voice-sample02.m4a

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\done\voice-sample01

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/11/02    18:01          28431 voice-sample01.json
-a---          2026/11/02    18:01            566 voice-sample01.srt
-a---          2026/11/02    18:01            505 voice-sample01.tsv
-a---          2026/11/02    18:01            494 voice-sample01.txt
-a---          2026/11/02    18:01            558 voice-sample01.vtt
-a---          2026/11/02    18:01           2994 whisperx.log

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\done\voice-sample02

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/11/02    18:00          58602 voice-sample02.json
-a---          2026/11/02    18:00           1113 voice-sample02.srt
-a---          2026/11/02    18:00           1014 voice-sample02.tsv
-a---          2026/11/02    18:00           1005 voice-sample02.txt
-a---          2026/11/02    18:00           1096 voice-sample02.vtt
-a---          2026/11/02    18:00           3355 whisperx.log

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

エラー用の error フォルダの内容を確認します.文字起こし処理に失敗したマークダウンファイルとそのログファイルが格納されていることが分かります.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> gci error -Recurse ⏎

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\error

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d----          2026/11/02    18:00                sample1
d----          2026/11/02    18:00                sample2
-a---          2026/10/25    18:18             56 sample1.md
-a---          2026/10/25    18:18            115 sample2.md

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\error\sample1

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/11/02    18:00           4412 whisperx.log

    ディレクトリ:C:\Users\rinsaka\Documents\python\watchdog\error\sample2

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/11/02    18:00           4412 whisperx.log

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

監視用 watch フォルダ,待ち行列用 queue フォルダと処理用 working フォルダはいずれも空になっていることが分かります.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> gci watch ⏎
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> gci queue ⏎
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> gci working ⏎
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

現時点では,同じファイル名の音声ファイルを繰り返し処理したときに,出力用の done フォルダで結果が上書きされてしまいます.例えば WhisperX のモデルを turbo から large に変更するなど,結果の比較を行いたい場合にはこの設計は不便に感じる可能性があります.次のページでは完了用フォルダやエラーフォルダに日時情報を含んだサブフォルダを作成し,その中に結果を保存するようにしてみます.

目次に戻る