transcribe.py 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. #!/usr/bin/env python3
  2. """transcribe.py — 提取音频并用 fmode-listen 转写(讯飞 LFASR, 分段时间戳).
  3. 用法:
  4. python3 transcribe.py --workdir /tmp/lapian
  5. 前置: workdir/video.mp4 (fetch_video.py 产物)
  6. 产物: workdir/audio.wav + workdir/transcript.json
  7. transcript.json = {code, data:{text, segments:[{bg,ed,speaker,text}]}}, 毫秒
  8. 环境: FEME_NEWAPI_TOKEN(或 FMODE_API_KEY) — 传给 fmode-listen 的 FMODE_API_TOKEN
  9. """
  10. import argparse
  11. import json
  12. import os
  13. import subprocess
  14. import sys
  15. sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
  16. from common import get_api_token # noqa: E402
  17. def main():
  18. ap = argparse.ArgumentParser()
  19. ap.add_argument("--workdir", required=True)
  20. ap.add_argument("--language", default="autodialect",
  21. help="autodialect(中英+方言自动)/zh/en, 默认 autodialect")
  22. args = ap.parse_args()
  23. video = os.path.join(args.workdir, "video.mp4")
  24. wav = os.path.join(args.workdir, "audio.wav")
  25. out_json = os.path.join(args.workdir, "transcript.json")
  26. if not os.path.exists(video):
  27. raise SystemExit(f"[transcribe] 缺少 {video}, 先跑 fetch_video.py")
  28. if not (os.path.exists(wav) and os.path.getsize(wav) > 1000):
  29. print("[transcribe] 提取音频: 16kHz 单声道 wav")
  30. r = subprocess.run(["ffmpeg", "-y", "-i", video, "-vn", "-ar", "16000",
  31. "-ac", "1", "-f", "wav", wav],
  32. capture_output=True, text=True)
  33. if r.returncode != 0:
  34. raise SystemExit(f"[transcribe] ffmpeg 失败: {r.stderr[-500:]}")
  35. else:
  36. print("[transcribe] 复用已提取的 audio.wav")
  37. # 短路: 已有转写直接复用(转写计费, 别重复花钱)
  38. if os.path.exists(out_json):
  39. try:
  40. old = json.load(open(out_json, encoding="utf-8"))
  41. segs = (old.get("data") or {}).get("segments") or []
  42. if segs:
  43. print(f"[transcribe] 复用已有转写({len(segs)}段), 跳过计费")
  44. return
  45. except Exception:
  46. pass
  47. env = dict(os.environ, FMODE_API_TOKEN=get_api_token())
  48. cmd = ["npx", "--yes", "fmode-listen@latest", "transcribe", "--", wav,
  49. "--language", args.language, "--out", out_json]
  50. print("[transcribe]", " ".join(cmd))
  51. r = subprocess.run(cmd, env=env, capture_output=True, text=True, timeout=1800)
  52. print(r.stdout[-800:])
  53. if r.returncode != 0:
  54. raise SystemExit(f"[transcribe] fmode-listen 失败: {r.stderr[-800:]}")
  55. if not os.path.exists(out_json):
  56. raise SystemExit("[transcribe] 转写产物缺失")
  57. data = json.load(open(out_json, encoding="utf-8"))
  58. segs = (data.get("data") or {}).get("segments") or []
  59. print(f"[transcribe] 完成: {len(segs)} 段, 全文 {len((data.get('data') or {}).get('text',''))} 字")
  60. if __name__ == "__main__":
  61. main()