@horizon_works/banto 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/gate_core.js +220 -1
  2. package/license/client.js +20 -1
  3. package/package.json +1 -2
  4. package/server.js +110 -36
  5. package/payload/compliance/SKILL.md +0 -113
  6. package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +0 -14
  7. package/payload/compliance/dict/06_house_rules.json +0 -16
  8. package/payload/compliance/dict/_sample/04_common_keihyo.json +0 -270
  9. package/payload/compliance/dict/_sample/README.md +0 -72
  10. package/payload/compliance/lint.js +0 -265
  11. package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +0 -64
  12. package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +0 -74
  13. package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +0 -60
  14. package/payload/migration/protocol.md +0 -35
  15. package/payload/skill-maker/SKILL_base_anthropic.md +0 -357
  16. package/payload/skill-maker/skill-maker-v1.0.md +0 -305
  17. package/payload/skills/_/343/201/202/343/201/250/343/201/247/350/266/263/343/201/233/343/202/213/343/202/202/343/201/256.md +0 -47
  18. package/payload/skills/image-gen/SKILL.md +0 -147
  19. package/payload/skills/image-gen/gen.js +0 -225
  20. package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +0 -106
  21. package/payload/skills/remember/SKILL.md +0 -158
  22. package/payload/skills/remember/handoff.js +0 -149
  23. package/payload/skills/remember/tasks.js +0 -320
  24. package/payload/skills/slide-deck/SKILL.md +0 -160
  25. package/payload/skills/transcribe/README.md +0 -182
  26. package/payload/skills/transcribe/dict.json +0 -10
  27. package/payload/skills/transcribe/enroll_speaker.py +0 -76
  28. package/payload/skills/transcribe/identify_speakers.py +0 -153
  29. package/payload/skills/transcribe/transcribe.py +0 -126
@@ -1,182 +0,0 @@
1
- # ローカル文字起こしキット(Whisper+辞書+話者識別)
2
-
3
- Zoom録画などの音声・動画ファイルを、**PCの中だけで**文字起こしするための道具一式です。
4
- 外部サービスに音声を送らないので、機密性の高い会議でも安心して使えます。
5
-
6
- 3つの道具が入っています。
7
-
8
- | ファイル | 役割 |
9
- |---|---|
10
- | `transcribe.py` | 音声・動画 → タイムスタンプ付きテキスト(Whisperローカル版) |
11
- | `dict.json` | 誤変換の補正辞書。使うほど育つ |
12
- | `enroll_speaker.py` / `identify_speakers.py` | 話者識別。「誰が話したか」を声紋で判定 |
13
-
14
- ---
15
-
16
- ## ★Windows で使うとき(初回のみ)
17
-
18
- このREADMEの下の方は Mac 向けに書かれています。Windows では次の順で入れます。
19
-
20
- ```powershell
21
- # 1. Python と ffmpeg を入れる(→ 必要なプログラム.md の 5・6)
22
- # Python は「Add python.exe to PATH」に必ずチェック
23
-
24
- # 2. 専用の環境を作る(キットのフォルダで。1回だけ)
25
- python -m venv .venv
26
-
27
- # 3. その環境に入る(PCを再起動したら毎回)
28
- .venv\Scripts\activate
29
-
30
- # 4. 部品を入れる
31
- pip install faster-whisper
32
- ```
33
-
34
- ★話者識別(誰が話したか)を使うときだけ、追加で `pip install resemblyzer` を入れます。
35
- うまく入らないときは `pip install webrtcvad-wheels` を先に実行してください。
36
-
37
- ★**手順が多い部分です。** 詰まったら、画面のメッセージをそのまま番頭に見せてください。
38
-
39
- ---
40
-
41
- ## セットアップ(Mac・初回のみ)
42
-
43
- このREADMEごとAIに渡して「セットアップして」と伝えるのが一番早いです。
44
- 手動でやる場合は以下の3ステップです。
45
-
46
- ```bash
47
- # 1. ffmpeg(動画・音声の変換に使う。導入済みならスキップ)
48
- brew install ffmpeg
49
-
50
- # 2. Python仮想環境(このフォルダの中に作る)
51
- cd transcribe-kit
52
- python3 -m venv venv
53
- source venv/bin/activate
54
-
55
- # 3. ライブラリ
56
- pip install faster-whisper resemblyzer
57
- ```
58
-
59
- ※ `resemblyzer` の依存 `webrtcvad` のインストールに失敗したら
60
- `pip install webrtcvad-wheels` を先に実行してから再度試してください。
61
-
62
- ---
63
-
64
- ## 1. 文字起こし
65
-
66
- ```bash
67
- python transcribe.py 会議録画.mp4
68
- ```
69
-
70
- - 初回だけ認識モデル(約500MB)を自動ダウンロードします
71
- - 出力は入力と同じ場所に2つ:
72
- - `会議録画.txt` … タイムスタンプ付き文字起こし(人が読む用)
73
- - `会議録画.json` … 詳細データ(話者識別の入力になる)
74
-
75
- 精度を上げたいときはモデルを指定します(大きいほど正確・遅い):
76
-
77
- ```bash
78
- python transcribe.py 会議録画.mp4 --model medium
79
- python transcribe.py 会議録画.mp4 --model large-v3
80
- ```
81
-
82
- 目安:60分の会議で small=10〜20分、medium=30分前後(Apple Silicon想定)。
83
- まずは small で試して、固有名詞の誤りが気になったら medium に上げるのがおすすめです。
84
-
85
- ---
86
-
87
- ## 2. 辞書の育て方(ここが一番効きます)
88
-
89
- Whisperは会社名・商品名・業界用語をよく間違えます。
90
- 間違いに気づいたら `dict.json` の `entries` に1行足すだけで、**次回から自動で直ります**。
91
-
92
- ```json
93
- {
94
- "entries": {
95
- "誤変換された表記": "正しい表記",
96
- "クロード": "Claude"
97
- }
98
- }
99
- ```
100
-
101
- 運用のコツ:
102
-
103
- - 文字起こしが終わったらAIに「この文字起こしを校正して。繰り返し出てくる誤変換があれば dict.json に追記して」と頼むと、辞書が勝手に育ちます
104
- - **固有名詞・専門用語を中心に**登録してください。短い一般語(「はい」「時」など)を登録すると、正しい文章まで置換される事故が起きます
105
- - 置換は長い表記から先に適用されるので、似た誤変換は両方登録してOKです
106
-
107
- ---
108
-
109
- ## 3. 話者識別(誰が話したか)
110
-
111
- Whisper単体は「何を話したか」しか分かりません。
112
- そこで**声紋(声の特徴を数値化したもの)**を使って「誰が話したか」を判定します。
113
-
114
- ### 仕組み
115
-
116
- 1. 各参加者の「その人だけが話している音声」から声紋を登録する(初回のみ)
117
- 2. 会議の発話ひとつひとつを声紋と照合し、一番似ている人の名前を付ける
118
-
119
- ### 手順1:声紋の登録(人ごとに初回のみ)
120
-
121
- その人だけが話している10秒〜1分程度の音声を用意します。
122
- 過去のZoom録画から切り出すのが手軽です:
123
-
124
- ```bash
125
- # 例:録画の5分10秒から60秒間を切り出す
126
- ffmpeg -i 過去の会議.mp4 -ss 00:05:10 -t 60 sample_yamada.m4a
127
- ```
128
-
129
- 切り出した音声で登録:
130
-
131
- ```bash
132
- python enroll_speaker.py 山田 sample_yamada.m4a
133
- ```
134
-
135
- → `voiceprints/山田_voiceprint.npy` が作られます。参加者の人数分やってください。
136
- 一度登録すれば、以降の会議で使い回せます。
137
-
138
- ### 手順2:識別の実行
139
-
140
- 文字起こし(手順1の `.json`)ができている状態で:
141
-
142
- ```bash
143
- python identify_speakers.py 会議録画.mp4 会議録画.json
144
- ```
145
-
146
- → `会議録画_speakers.txt` が出ます。こんな形式です:
147
-
148
- ```
149
- 山田 0:12
150
- おはようございます。今日はよろしくお願いします。
151
-
152
- 参加者A 0:45
153
- こちらこそ。早速ですが先週の件から…
154
- ```
155
-
156
- - 声紋との類似度が低い発話は「不明」になります(登録していない人・音質が悪い区間)
157
- - 「不明」が多いときは、その人の声紋をもっと長い音源で登録し直すと改善します
158
-
159
- ---
160
-
161
- ## よくあるつまずき
162
-
163
- | 症状 | 対処 |
164
- |---|---|
165
- | `ffmpeg: command not found` | `brew install ffmpeg` |
166
- | webrtcvadのビルドエラー | `pip install webrtcvad-wheels` |
167
- | 文字起こしが遅い | まず `--model small`(既定)で。会議が長い場合は分割も有効 |
168
- | 話者識別で「不明」が多い | 声紋登録の音源を長く・クリアなものに差し替えて再登録 |
169
- | 同じ人が別人判定される | マイク・環境が大きく違う録音は声紋がズレます。実際の会議と近い環境の音源で登録 |
170
-
171
- ---
172
-
173
- ## この文字起こしの使い道(商談・社内面談)
174
-
175
- タイムスタンプ+話者名付きのテキストは、そのまま
176
- **関係性管理スキル(relationship-manager)の入力**として使えます。
177
-
178
- - **商談**:終わった直後に「この文字起こしを読んで、○○社の担当者のプロファイルを更新して。次回の商談で押さえるべき点も整理して」とAIに渡す
179
- - **社内面談**:「この面談の文字起こしから、本人の関心事と気がかりを整理して」と渡せば、1on1の記録と次回準備が一度にできます
180
-
181
- 商談相手・社員それぞれの声紋を一度登録しておけば、
182
- 以降は「録画を置いて2コマンド」で話者名付きの記録が積み上がっていきます。
@@ -1,10 +0,0 @@
1
- {
2
- "_readme": "Whisperの誤変換補正辞書。entries に「誤変換された表記: 正しい表記」を追記すると、次回の文字起こしから自動で直ります。固有名詞(会社名・商品名・人名・業界用語)を中心に登録してください。短い一般語の登録は誤爆のもとになるので避けてください。",
3
- "entries": {
4
- "クロード": "Claude",
5
- "ジェミニ": "Gemini",
6
- "チャットGPT": "ChatGPT",
7
- "ノーションLM": "NotebookLM",
8
- "ノートブックLM": "NotebookLM"
9
- }
10
- }
@@ -1,76 +0,0 @@
1
- """話者の声紋を登録する(初回のみ・人ごと)
2
-
3
- 使い方:
4
- python enroll_speaker.py 山田 sample_yamada.m4a
5
-
6
- 音声は「その人だけが話している」10秒〜1分程度のもの。
7
- 過去の録画から切り出す例:
8
- ffmpeg -i 過去の会議.mp4 -ss 00:05:10 -t 60 sample_yamada.m4a
9
-
10
- 出力:
11
- voiceprints/山田_voiceprint.npy
12
- """
13
- import subprocess
14
- import sys
15
- from pathlib import Path
16
-
17
- import numpy as np
18
-
19
- # Windowsの既定はcp932で、日本語を出そうとすると化ける/落ちる。出力をUTF-8に固定する
20
- try:
21
- sys.stdout.reconfigure(encoding="utf-8")
22
- sys.stderr.reconfigure(encoding="utf-8")
23
- except Exception:
24
- pass
25
-
26
-
27
-
28
- def load_audio_16k(path):
29
- """ffmpegで16kHzモノラルに変換して読み込む(mp4/m4a/mp3/wav対応)"""
30
- cmd = ["ffmpeg", "-v", "quiet", "-i", str(path),
31
- "-ac", "1", "-ar", "16000", "-f", "f32le", "-"]
32
- result = subprocess.run(cmd, capture_output=True)
33
- if result.returncode != 0 or len(result.stdout) == 0:
34
- print(f"[error] 音声の読み込みに失敗しました: {path}")
35
- print(" ffmpegがインストールされているか確認してください(brew install ffmpeg)")
36
- sys.exit(1)
37
- return np.frombuffer(result.stdout, dtype=np.float32)
38
-
39
-
40
- def main():
41
- if len(sys.argv) < 3:
42
- print(__doc__)
43
- sys.exit(1)
44
-
45
- name, audio_path = sys.argv[1], sys.argv[2]
46
- if not Path(audio_path).exists():
47
- print(f"[error] ファイルが見つかりません: {audio_path}")
48
- sys.exit(1)
49
-
50
- from resemblyzer import VoiceEncoder, preprocess_wav
51
-
52
- print(f"[1/3] 音声読み込み: {audio_path}")
53
- wav = load_audio_16k(audio_path)
54
- duration = len(wav) / 16000
55
- print(f" {duration:.0f}秒")
56
- if duration < 5:
57
- print("[warn] 5秒未満です。精度が落ちるので10秒以上を推奨します")
58
-
59
- print(f"[2/3] 声紋を抽出中")
60
- wav = preprocess_wav(wav, source_sr=16000)
61
- encoder = VoiceEncoder()
62
- embed = encoder.embed_utterance(wav)
63
-
64
- out_dir = Path(__file__).parent / "voiceprints"
65
- out_dir.mkdir(exist_ok=True)
66
- out_path = out_dir / f"{name}_voiceprint.npy"
67
- np.save(str(out_path), embed)
68
-
69
- print(f"[3/3] 登録完了: {out_path}")
70
- print(f"\n現在の登録者:")
71
- for f in sorted(out_dir.glob("*_voiceprint.npy")):
72
- print(f" - {f.stem.replace('_voiceprint', '')}")
73
-
74
-
75
- if __name__ == "__main__":
76
- main()
@@ -1,153 +0,0 @@
1
- """会議音声の話者を識別する
2
-
3
- 使い方:
4
- python identify_speakers.py 会議録画.mp4 会議録画.json
5
-
6
- 前提:
7
- - 会議録画.json は transcribe.py の出力
8
- - voiceprints/ に enroll_speaker.py で参加者の声紋を登録済み
9
-
10
- 出力:
11
- 会議録画_speakers.txt 話者名+タイムスタンプ付き文字起こし
12
- """
13
- import json
14
- import subprocess
15
- import sys
16
- from pathlib import Path
17
-
18
- import numpy as np
19
-
20
- # Windowsの既定はcp932で、日本語を出そうとすると化ける/落ちる。出力をUTF-8に固定する
21
- try:
22
- sys.stdout.reconfigure(encoding="utf-8")
23
- sys.stderr.reconfigure(encoding="utf-8")
24
- except Exception:
25
- pass
26
-
27
-
28
- # この類似度未満の発話は「不明」にする(0〜1。上げると厳しく、下げると甘く)
29
- THRESHOLD = 0.60
30
-
31
- # これより短い発話は声紋照合せず、直前の話者を引き継ぐ(秒)
32
- MIN_SEG_SEC = 0.8
33
-
34
-
35
- def load_audio_16k(path):
36
- """ffmpegで16kHzモノラルに変換して読み込む"""
37
- cmd = ["ffmpeg", "-v", "quiet", "-i", str(path),
38
- "-ac", "1", "-ar", "16000", "-f", "f32le", "-"]
39
- result = subprocess.run(cmd, capture_output=True)
40
- if result.returncode != 0 or len(result.stdout) == 0:
41
- print(f"[error] 音声の読み込みに失敗しました: {path}")
42
- sys.exit(1)
43
- return np.frombuffer(result.stdout, dtype=np.float32)
44
-
45
-
46
- def load_voiceprints():
47
- vp_dir = Path(__file__).parent / "voiceprints"
48
- prints = {}
49
- if vp_dir.exists():
50
- for f in sorted(vp_dir.glob("*_voiceprint.npy")):
51
- name = f.stem.replace("_voiceprint", "")
52
- prints[name] = np.load(str(f))
53
- return prints
54
-
55
-
56
- def fmt_time(sec):
57
- sec = int(sec)
58
- h, m, s = sec // 3600, (sec % 3600) // 60, sec % 60
59
- return f"{h}:{m:02d}:{s:02d}" if h else f"{m}:{s:02d}"
60
-
61
-
62
- def main():
63
- if len(sys.argv) < 3:
64
- print(__doc__)
65
- sys.exit(1)
66
-
67
- audio_path, json_path = Path(sys.argv[1]), Path(sys.argv[2])
68
- for p in (audio_path, json_path):
69
- if not p.exists():
70
- print(f"[error] ファイルが見つかりません: {p}")
71
- sys.exit(1)
72
-
73
- voiceprints = load_voiceprints()
74
- if not voiceprints:
75
- print("[error] 声紋が1件も登録されていません。先に enroll_speaker.py で登録してください")
76
- sys.exit(1)
77
- print(f"登録済み声紋: {', '.join(voiceprints.keys())}")
78
-
79
- from resemblyzer import VoiceEncoder, preprocess_wav
80
-
81
- print(f"[1/3] 音声読み込み: {audio_path.name}")
82
- wav = load_audio_16k(audio_path)
83
-
84
- data = json.loads(json_path.read_text(encoding="utf-8"))
85
- segments = data.get("segments", [])
86
- print(f"[2/3] {len(segments)}セグメントを照合中")
87
-
88
- encoder = VoiceEncoder()
89
- names = list(voiceprints.keys())
90
- vp_matrix = np.stack([voiceprints[n] for n in names])
91
-
92
- results = []
93
- prev_speaker = "不明"
94
- stats = {}
95
-
96
- for i, seg in enumerate(segments):
97
- start, end = seg["start"], seg["end"]
98
- speaker = prev_speaker
99
- score = None
100
-
101
- if end - start >= MIN_SEG_SEC:
102
- chunk = wav[int(start * 16000):int(end * 16000)]
103
- try:
104
- processed = preprocess_wav(chunk, source_sr=16000)
105
- if len(processed) >= 1600:
106
- embed = encoder.embed_utterance(processed)
107
- sims = vp_matrix @ embed # 声紋はL2正規化済み=内積がコサイン類似度
108
- best = int(np.argmax(sims))
109
- score = float(sims[best])
110
- speaker = names[best] if score >= THRESHOLD else "不明"
111
- except Exception:
112
- pass # 無音・短すぎる区間は直前の話者を引き継ぐ
113
-
114
- results.append({"speaker": speaker, "start": start, "text": seg["text"], "score": score})
115
- prev_speaker = speaker
116
- s = stats.setdefault(speaker, {"count": 0, "scores": []})
117
- s["count"] += 1
118
- if score is not None:
119
- s["scores"].append(score)
120
-
121
- if (i + 1) % 100 == 0:
122
- print(f" ... {i + 1}/{len(segments)}")
123
-
124
- # 連続する同一話者の発話をまとめて出力
125
- print(f"[3/3] 保存中")
126
- out_lines = []
127
- block_speaker, block_start, block_texts = None, None, []
128
- for r in results:
129
- if r["speaker"] != block_speaker:
130
- if block_texts:
131
- out_lines.append(f"{block_speaker} {fmt_time(block_start)}")
132
- out_lines.append("".join(block_texts))
133
- out_lines.append("")
134
- block_speaker, block_start, block_texts = r["speaker"], r["start"], []
135
- block_texts.append(r["text"])
136
- if block_texts:
137
- out_lines.append(f"{block_speaker} {fmt_time(block_start)}")
138
- out_lines.append("".join(block_texts))
139
-
140
- out_path = audio_path.with_name(audio_path.stem + "_speakers.txt")
141
- out_path.write_text("\n".join(out_lines), encoding="utf-8")
142
-
143
- print(f"\n完了: {out_path}")
144
- print(f"\n話者別の内訳:")
145
- for name, s in sorted(stats.items(), key=lambda x: -x[1]["count"]):
146
- avg = f"(平均類似度 {np.mean(s['scores']):.2f})" if s["scores"] else ""
147
- print(f" {name}: {s['count']}セグメント {avg}")
148
- if stats.get("不明", {}).get("count", 0) > len(results) * 0.3:
149
- print("\n[hint] 「不明」が3割を超えています。声紋の登録音源を長く・クリアなものに差し替えると改善します")
150
-
151
-
152
- if __name__ == "__main__":
153
- main()
@@ -1,126 +0,0 @@
1
- """ローカル文字起こし(faster-whisper)
2
-
3
- 使い方:
4
- python transcribe.py 会議録画.mp4
5
- python transcribe.py 会議録画.mp4 --model medium
6
-
7
- 出力(入力ファイルと同じ場所):
8
- 会議録画.txt タイムスタンプ付き文字起こし(人が読む用)
9
- 会議録画.json 詳細データ(identify_speakers.py の入力)
10
-
11
- dict.json(同じフォルダ)があれば、誤変換を自動補正します。
12
- """
13
- import argparse
14
- import json
15
- import sys
16
- from pathlib import Path
17
-
18
- # Windowsの既定はcp932で、日本語を出そうとすると化ける/落ちる。出力をUTF-8に固定する
19
- try:
20
- sys.stdout.reconfigure(encoding="utf-8")
21
- sys.stderr.reconfigure(encoding="utf-8")
22
- except Exception:
23
- pass
24
-
25
-
26
-
27
- def load_dict():
28
- """dict.json の entries を読む(なければ空)"""
29
- dict_path = Path(__file__).parent / "dict.json"
30
- if not dict_path.exists():
31
- return {}
32
- try:
33
- data = json.loads(dict_path.read_text(encoding="utf-8"))
34
- return data.get("entries", {})
35
- except Exception as e:
36
- print(f"[warn] dict.json が読めませんでした(補正なしで続行): {e}")
37
- return {}
38
-
39
-
40
- def apply_dict(text, entries):
41
- """長い表記から先に置換(部分一致の誤爆を減らす)"""
42
- for wrong in sorted(entries, key=len, reverse=True):
43
- text = text.replace(wrong, entries[wrong])
44
- return text
45
-
46
-
47
- def fmt_time(sec):
48
- sec = int(sec)
49
- h, m, s = sec // 3600, (sec % 3600) // 60, sec % 60
50
- return f"{h}:{m:02d}:{s:02d}" if h else f"{m}:{s:02d}"
51
-
52
-
53
- def main():
54
- parser = argparse.ArgumentParser(description="ローカル文字起こし(faster-whisper)")
55
- parser.add_argument("input", help="音声・動画ファイル(mp4/m4a/mp3/wav など)")
56
- parser.add_argument("--model", default="small",
57
- help="モデルサイズ: tiny/base/small/medium/large-v3(既定: small)")
58
- args = parser.parse_args()
59
-
60
- src = Path(args.input)
61
- if not src.exists():
62
- print(f"[error] ファイルが見つかりません: {src}")
63
- sys.exit(1)
64
-
65
- # ★部品が入っていないときに、Pythonの生のエラーを見せない。
66
- # 何をすればよいか分からない画面は、そこで人を止めてしまうので。
67
- try:
68
- from faster_whisper import WhisperModel
69
- except ImportError:
70
- print("")
71
- print(" 文字起こしの部品(faster-whisper)が入っていません。")
72
- print("")
73
- print(" キットのフォルダで、上から順に打ってください。")
74
- print(" python -m venv .venv")
75
- print(" .venv" + chr(92) + "Scripts" + chr(92) + "activate ← Windows")
76
- print(" source .venv/bin/activate ← Mac")
77
- print(" pip install faster-whisper")
78
- print("")
79
- print(" 詳しくは kit/skills/transcribe/README.md を見てください。")
80
- print(" 番頭に「文字起こしの準備をしたい」と言えば、順番に案内します。")
81
- print("")
82
- sys.exit(1)
83
-
84
- print(f"[1/3] モデル読み込み: {args.model}(初回はダウンロードに数分かかります)")
85
- model = WhisperModel(args.model, device="cpu", compute_type="int8")
86
-
87
- print(f"[2/3] 文字起こし中: {src.name}")
88
- segments, info = model.transcribe(
89
- str(src),
90
- language="ja",
91
- beam_size=5,
92
- vad_filter=True,
93
- )
94
-
95
- entries = load_dict()
96
- if entries:
97
- print(f" 辞書補正: {len(entries)}語を適用します")
98
-
99
- out_txt = src.with_suffix(".txt")
100
- out_json = src.with_suffix(".json")
101
-
102
- seg_list = []
103
- lines = []
104
- for seg in segments:
105
- text = apply_dict(seg.text.strip(), entries)
106
- seg_list.append({"start": round(seg.start, 2), "end": round(seg.end, 2), "text": text})
107
- lines.append(f"[{fmt_time(seg.start)}] {text}")
108
- # 進捗表示(5分ごと)
109
- if len(seg_list) % 100 == 0:
110
- print(f" ... {fmt_time(seg.start)} まで完了")
111
-
112
- print(f"[3/3] 保存中")
113
- out_txt.write_text("\n".join(lines), encoding="utf-8")
114
- out_json.write_text(
115
- json.dumps({"duration": round(info.duration, 1), "segments": seg_list},
116
- ensure_ascii=False, indent=2),
117
- encoding="utf-8",
118
- )
119
-
120
- print(f"\n完了: {len(seg_list)}セグメント / {fmt_time(info.duration)}")
121
- print(f" {out_txt}")
122
- print(f" {out_json}")
123
-
124
-
125
- if __name__ == "__main__":
126
- main()