@horizon_works/banto 0.7.0 → 0.8.1

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 (31) hide show
  1. package/add_to_config.js +107 -0
  2. package/gate_core.js +220 -1
  3. package/law_bridge.js +183 -0
  4. package/license/client.js +20 -1
  5. package/package.json +3 -2
  6. package/server.js +140 -37
  7. package/payload/compliance/SKILL.md +0 -113
  8. package/payload/compliance/dict/00_/345/213/225/344/275/234/347/242/272/350/252/215.json +0 -14
  9. package/payload/compliance/dict/06_house_rules.json +0 -16
  10. package/payload/compliance/dict/_sample/04_common_keihyo.json +0 -270
  11. package/payload/compliance/dict/_sample/README.md +0 -72
  12. package/payload/compliance/lint.js +0 -265
  13. package/payload/disciplines//347/225/252/351/240/255/343/201/256/344/275/234/346/263/225.md +0 -64
  14. package/payload/disciplines//350/207/252/345/267/261/345/201/245/350/250/272.md +0 -74
  15. package/payload/disciplines//351/200/261/346/254/241/343/203/241/343/203/263/343/203/206.md +0 -60
  16. package/payload/migration/protocol.md +0 -35
  17. package/payload/skill-maker/SKILL_base_anthropic.md +0 -357
  18. package/payload/skill-maker/skill-maker-v1.0.md +0 -305
  19. 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
  20. package/payload/skills/image-gen/SKILL.md +0 -147
  21. package/payload/skills/image-gen/gen.js +0 -225
  22. package/payload/skills/remember/SETUP_Google/351/200/243/346/220/272.md +0 -106
  23. package/payload/skills/remember/SKILL.md +0 -158
  24. package/payload/skills/remember/handoff.js +0 -149
  25. package/payload/skills/remember/tasks.js +0 -320
  26. package/payload/skills/slide-deck/SKILL.md +0 -160
  27. package/payload/skills/transcribe/README.md +0 -182
  28. package/payload/skills/transcribe/dict.json +0 -10
  29. package/payload/skills/transcribe/enroll_speaker.py +0 -76
  30. package/payload/skills/transcribe/identify_speakers.py +0 -153
  31. package/payload/skills/transcribe/transcribe.py +0 -126
@@ -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()