"""smoke: transcribe_audio / core.asr_lfasr 的纯本地部分(不联讯飞)。 验证: 1. asr_lfasr.parse_transcript:json_1best 为串(lattice)与对象(lattice2)两形态、 空文本段跳过、rl 角色提取; 2. asr_lfasr.format_transcript:多说话人按连续同角色合并「【说话人N】」段落, 单说话人纯文本; 3. TranscribeAudioTool 入参错误路径(路径不存在 / 扩展名不对 / 视频提示 / 空文件 / 越界 / 坏 language),全部在上传前早返,不触网。 跑法: .venv/Scripts/python.exe scripts/smoke_transcribe_audio.py """ from __future__ import annotations import json import sys import tempfile from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from core.asr_lfasr import format_transcript, parse_transcript # noqa: E402 from tools.transcribe_audio import TranscribeAudioTool # noqa: E402 FAIL = 0 def check(name: str, ok: bool, detail: str = "") -> None: global FAIL tag = "PASS" if ok else "FAIL" print(f"[{tag}] {name}" + (f" -- {detail}" if detail else "")) if not ok: FAIL = 1 def seg(text: str, rl: str = "0") -> dict: """构造一个 lattice 项(json_1best 为再编码 JSON 串,官方返回形态)。""" st = {"rl": rl, "rt": [{"ws": [{"cw": [{"w": ch}]} for ch in text]}]} return {"json_1best": json.dumps({"st": st}, ensure_ascii=False)} def main() -> int: # ---- 1. parse_transcript ---- order = json.dumps({"lattice": [seg("今天开会", "1"), seg("好的", "2"), seg("", "1")]}, ensure_ascii=False) segs = parse_transcript(order) check("parse: 2 segments (empty skipped)", len(segs) == 2, str(segs)) check("parse: roles extracted", segs == [("1", "今天开会"), ("2", "好的")], str(segs)) # lattice2 形态:json_1best 已是对象 obj_item = json.loads(seg("测试", "0")["json_1best"]) segs2 = parse_transcript(json.dumps({"lattice2": [{"json_1best": obj_item}]}, ensure_ascii=False)) check("parse: lattice2 object form", segs2 == [("", "测试")], str(segs2)) # ---- 2. format_transcript ---- multi = [("1", "大家好。"), ("1", "开始吧。"), ("2", "收到。"), ("1", "散会。")] out = format_transcript(multi) check("format: multi-speaker merged", out == "【说话人1】大家好。开始吧。\n\n【说话人2】收到。\n\n【说话人1】散会。", repr(out)) single = [("1", "自言"), ("1", "自语")] check("format: single speaker plain", format_transcript(single) == "自言自语") check("format: no-role plain", format_transcript([("", "甲"), ("", "乙")]) == "甲乙") # ---- 3. tool 早返错误路径(不触网) ---- with tempfile.TemporaryDirectory(prefix="zcbot-smoke-ta-") as td: root = Path(td) wd = root / "taskdir" wd.mkdir() tool = TranscribeAudioTool(working_dir=wd, base_dir=wd, user_root=root) r = tool.execute(audio="inbound/nope.m4a") check("missing file -> [Error]", r.startswith("[Error]") and "找不到" in r, r[:60]) (wd / "notes.docx").write_bytes(b"PK\x03\x04fake") r = tool.execute(audio="notes.docx") check("non-audio ext -> [Error]", r.startswith("[Error]") and "扩展名" in r, r[:60]) (wd / "clip.mp4").write_bytes(b"\x00\x00\x00\x18ftyp") r = tool.execute(audio="clip.mp4") check("video ext -> hint to extract audio", r.startswith("[Error]") and "音轨" in r, r[:80]) (wd / "empty.mp3").write_bytes(b"") r = tool.execute(audio="empty.mp3") check("empty file -> [Error]", r.startswith("[Error]") and "空" in r, r[:60]) outside = root.parent / "zcbot-smoke-outside.mp3" try: outside.write_bytes(b"xx") r = tool.execute(audio=str(outside)) check("out-of-root abs path -> [Error]", r.startswith("[Error]") and "越界" in r, r[:60]) finally: outside.unlink(missing_ok=True) r = tool.execute(audio="a.mp3", language="fr") check("bad language -> [Error]", r.startswith("[Error]") and "language" in r, r[:60]) print("RESULT:", "FAIL" if FAIL else "ALL PASS") return FAIL if __name__ == "__main__": raise SystemExit(main())