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. 文字コードを強制的に指定する

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

サイズの大きなファイルのコピーに対応する

ここまで作成してきた監視プログラム (watch.py) は,監視用フォルダに音声ファイルをダウンロードして保存しようとしたとき,音声ファイルの全てのダウンロードが完了する前に待ち行列用フォルダに移動する処理が動いてしまう可能性があります.この問題を解決するために,前のページではファイルサイズが変わらなくなるまでコピー完了を判断しないという処理を考えました.ここでは監視プログラム (watch.py) にファイルサイズ検証によるコピー完了判断の機能を追加します.

具体的には次のとおり,wait_until_complete という関数を定義して,ファイルサイズの検証によるコピー完了確認を行います.ここで,念には念を入れるという意味で,5秒間のスリープを2回いれ,3回のファイルサイズ判定でいずれも変化がない場合に完了の判断を行っています(実際にはここまでやらなくても良いかもしれません).また,スリープしている間にファイル名を変更したり,削除するというようなイタズラをしたとしてもプログラムが強制終了しないように例外処理も加えています.

watch.py
from pathlib import Path
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import shutil # ファイルのコピー,移動などを行う標準ライブラリ
import time

BASE_DIR = Path(__file__).parent
WATCH_DIR = BASE_DIR / "watch"
QUEUE_DIR = BASE_DIR / "queue"

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

def wait_until_complete(path):
    """
    watch ディレクトリへのコピー,ファイルサイズの変化がない -> コピー(ダウンロード)が終わったと判断
    待っている間にファイル名が変更されたら強制終了
    """

    while True:
        try:
            size1 = path.stat().st_size
            time.sleep(5)
            size2 = path.stat().st_size
            time.sleep(5)
            size3 = path.stat().st_size
            if size1 == size2 == size3:
                print("ファイルの受取りが完了しました")
                return True

        except FileNotFoundError:
            print(f"ファイルが見つかりません: {path}") # 途中でファイル名を変更したり,削除した場合
            return False

class MyHandler(FileSystemEventHandler):
    """
    watchdog が用意している FileSystemEventHandler クラスの継承
    「ファイル監視イベントが発生したときに何をするか」を定義する
    """
    def on_created(self, event):
        if event.is_directory: # ディレクトリは無視
            return
        src = Path(event.src_path)
        print(f"新しいファイルを検出: {src}")
        print(f"新しいファイルの名前: {src.name}")
        if not wait_until_complete(src):
            return
        dst = QUEUE_DIR / src.name # 移動先の指定
        print(f"ファイルの移動開始: {dst}")
        shutil.move(src, dst) # ファイルの移動
        print(f"ファイルの移動完了: {dst}")

observer = Observer() # 「監視員を作成する」処理

# 監視条件設定
observer.schedule(
    MyHandler(),
    path=WATCH_DIR,
    recursive=False # とりあえず直下だけを監視
)

observer.start() # 監視を始める → 別スレッド(observerスレッド)が生成される

try:
    while True:
        time.sleep(1)

except KeyboardInterrupt:
    print("停止します")
    observer.stop()

observer.join() # observerスレッドが終わるまで待つ

次はワーカープログラムです.これまではマークダンファイルだけを走査していましたが,音声ファイルなども処理ができるように,監視対象とする拡張子を変更しました.また音声ファイルの中身を表示させることは現実的ではないため,中身を表示せず「処理中」とだけ表示するように微調整も行っています.

worker.py
from pathlib import Path
import shutil
import time

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

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

try:
    while True:
        for src in QUEUE_DIR.glob("*.m*"): # 監視するファイルの拡張子
            print(f"処理開始: {src.name}")
            working_file = WORKING_DIR / src.name
            shutil.move(src, working_file)
            print(f"作業開始: {working_file}")
            print("処理中...")
            time.sleep(25)
            # with open(working_file, "r", encoding="utf-8") as f:
                # print(f.read())
            dst = DONE_DIR / src.name
            shutil.move(working_file, dst)
            print(f"完了: {dst}")
        time.sleep(1)

except KeyboardInterrupt:
    print("停止します")

ターミナルを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 ⏎

プログラムを起動できたら,4個のマークダウンファイルを監視用の watch フォルダに設置します.サイズの小さなファイルであっても5秒間待ってサイズに変化がないことを確認してから待ち行列用 queue フォルダに移動する様子が分かるはずです.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python watch.py
新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample1.md
新しいファイルの名前: sample1.md
ファイルの受取りが完了しました
ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample1.md
ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample1.md
新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample2.md
新しいファイルの名前: sample2.md
ファイルの受取りが完了しました
ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample2.md
ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample2.md
新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample3.md
新しいファイルの名前: sample3.md
ファイルの受取りが完了しました
ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample3.md
ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample3.md
新しいファイルを検出: C:\Users\rinsaka\Documents\python\watchdog\watch\sample4.md
新しいファイルの名前: sample4.md
ファイルの受取りが完了しました
ファイルの移動開始: C:\Users\rinsaka\Documents\python\watchdog\queue\sample4.md
ファイルの移動完了: C:\Users\rinsaka\Documents\python\watchdog\queue\sample4.md
Ctrl + C
停止します
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

ワーカープログラムは待ち行列用 queue フォルダにあるファイルから一個を取り出して作業用 working フォルダに移動し,ファイル名だけを表示して完了用 done フォルダに格納します.

(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog> python worker.py
処理開始: sample1.md
作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample1.md
処理中...
完了: C:\Users\rinsaka\Documents\python\watchdog\done\sample1.md
処理開始: sample2.md
作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample2.md
処理中...
完了: C:\Users\rinsaka\Documents\python\watchdog\done\sample2.md
処理開始: sample3.md
作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample3.md
処理中...
完了: C:\Users\rinsaka\Documents\python\watchdog\done\sample3.md
処理開始: sample4.md
作業開始: C:\Users\rinsaka\Documents\python\watchdog\working\sample4.md
処理中...
完了: C:\Users\rinsaka\Documents\python\watchdog\done\sample4.md
Ctrl + C
停止します
(py313watchdog) PS C:\Users\rinsaka\Documents\python\watchdog>

全ての作業が終わると done フォルダに格納されます.

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:18             56 sample1.md
-a---          2026/10/25    18:18            115 sample2.md
-a---          2026/10/25    18:19             47 sample3.md
-a---          2026/10/25    18:19             59 sample4.md

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

なお,4つのファイルを監視用 watch フォルダに格納した直後には watch フォルダに4個全てのファイルがあります.

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:18             56 sample1.md
-a---          2026/10/25    18:18            115 sample2.md
-a---          2026/10/25    18:19             47 sample3.md
-a---          2026/10/25    18:19             59 sample4.md

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

数十秒経過後は監視用 watch,待ち行列用 queue,作業用 working,完了用 done フォルダに1個ずつのファイルがある状態になりました.

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:19             59 sample4.md

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:19             47 sample3.md

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:18            115 sample2.md

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:18             56 sample1.md

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

さらに少し経過すると,1個が作業中で,待ち行列に1個,完了が2個という状態になりました.

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:19             59 sample4.md

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

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:19             47 sample3.md

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

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

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

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

すべての処理が終わると,完了用以外のフォルダは空になったことが分かります.

(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> gci done ⏎

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

Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
-a---          2026/10/25    18:18             56 sample1.md
-a---          2026/10/25    18:18            115 sample2.md
-a---          2026/10/25    18:19             47 sample3.md
-a---          2026/10/25    18:19             59 sample4.md

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

目次に戻る