Files
SoundGame/tools/extract_voice_text.py
T
xsl 566241e05d feat(platform/P1): 配对码跨设备 + 认领打磨 + 键盘语音双套资产(两 agent 并行 + 集成收口)
配对码与认领(B):
- POST /api/pair/start|join:6 位码 10 分钟单次有效、仅存内存零泄露面、5 连错 per-IP 封锁 10 分钟;join=新设备档案按桶 LWW 并入老档案+token 改绑(复用 claim-merge);日志脱敏
- 大厅:生成配对码(speechSynthesis 逐位朗读+aria-live+重听)/从其他设备转入(one-time-code 输入框)/从本机移除/401 过期自愈引导/aria 补全/玩家名可听化
- api-smoke 12/12(配对全流程/频控/封锁/过期自愈/移除/digitsToCn)+ CDP 真浏览器 E2E 22/22

键盘语音双套资产(A,路线 a):
- 164 变体句(8 游戏 grep 实测,超出调研预估 153:含轻触/点两下/按住变体)×2 性别=328 生成 656 文件 36.9 分钟;触屏主资产零字节改动(PCM 逐样本比对后还原)
- 引擎 _candidates 三跳路由 key→gender→平铺(inputMode 跟随);缓存键 mode|gender|id;engine-input 新增 K9 路由断言(72 条)
- check_all_assets 增 key 列(两性别一致且 ⊆ 主集)8 游戏全绿;voice_text.json 增 __key__ 表;live control.mjs key 会话对照键盘措辞验收(集成)
- voice-ids 文档:8 游戏键盘变体小节由 KEY_LINES 程序化生成(与台词表逐字断言一致);补齐 storyline/escape/simon 三个历史缺失文档;design README 增 new8 索引
- mock 适配:playtest persona 加 maxTouchPoints=1 保持触屏链断言语义零改动

回归:12 套件全绿(8 playtest + guide 122 + engine-voice 19 + engine-input 72 + api-smoke 12/12)
同批入库:批次 2/3 已完稿设计文档(17-abyss/18-detective/19-cipher 全套,20-22 在途)
2026-09-10 00:43:23 +08:00

71 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""从 tools/gen_voice_dual.py 台词表提取 id -> 台词全文 映射,输出 JSON。
供试玩转写 harness(web/harness/)把语音播放事件转写成人类可读文本。
台词表是唯一事实源:新台词先改进 gen_voice_dual.py,再跑本脚本刷新 JSON。
用法:.venv-tts/bin/python tools/extract_voice_text.py
输出:web/harness/voice_text.json {"game": {"id": "全文", ...}}
"""
import ast
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT / "tools"))
import gen_voice_dual as gvd # noqa: E402
out = {}
for game, lines in gvd.GAMES.items():
table = {}
for fid, _ins, text in lines:
if fid in table:
raise SystemExit(f"重复 id:{game}/{fid}")
table[fid] = text
# NPC 单音色平铺(gen_voice_dual.NPCS:id, speaker, instruct, 台词)
for fid, _sp, _ins, text in gvd.NPCS.get(game, []):
if fid in table:
raise SystemExit(f"重复 id:{game}/{fid}")
table[fid] = text
out[game] = table
# 键盘变体(P1 路线 a):独立顶层表 "__key__",不与主表混放——现有消费者
# (mock.mjs / transcribe.mjs / live/control.mjs 的 VOICE_TEXT[game][id] 查找)
# 行为零改动;键盘措辞会话(input=key)按 VOICE_TEXT.__key__[game][id] 取期望文本。
out["__key__"] = {
game: {fid: text for fid, text in gvd.KEY_LINES[game]}
for game in gvd.GAMES
}
def parse_legacy(py_path: Path) -> dict:
"""AST 解析旧版 gen_voice.py:提取 ('id', ("Speaker", "instruct"), "text") 元组。
不能 import——这些脚本顶层直接执行生成。"""
tree = ast.parse(py_path.read_text(encoding="utf-8"))
found = {}
for node in ast.walk(tree):
if not isinstance(node, ast.Tuple) or len(node.elts) != 3:
continue
a, _b, c = node.elts
if (isinstance(a, ast.Constant) and isinstance(a.value, str)
and isinstance(c, ast.Constant) and isinstance(c.value, str)
and len(c.value) >= 2):
found[a.value] = c.value
return found
# 旧 flavor 脚本补齐角色台词(storyline 林晚等、escape 角色音);主表优先,不覆盖
for game, legacy in [("storyline", ROOT / "app/src/storyline/gen/gen_voice.py"),
("escape", ROOT / "app/src/escape/gen/gen_voice.py")]:
for fid, text in parse_legacy(legacy).items():
out[game].setdefault(fid, text)
dest = ROOT / "web" / "harness" / "voice_text.json"
dest.parent.mkdir(parents=True, exist_ok=True)
dest.write_text(json.dumps(out, ensure_ascii=False, indent=1), encoding="utf-8")
total = sum(len(v) for k, v in out.items() if k != "__key__")
key_total = sum(len(v) for v in out["__key__"].values())
print(f"OK {dest.relative_to(ROOT)} games={len(out) - 1} lines={total} key_variants={key_total}")