mattrguerra/transcription-pipelinemainPrivate on GitHub, published here in full

render.py

Language Python3.2 KB88 lines

transcription_pipeline/render.py

  1. """Writers: .txt (one timestamped line per sentence), .md for the vault,
  2. .json with everything, and .srt/.vtt subtitles."""
  3. from __future__ import annotations
  4. import json
  5. from .shape import turns
  6. FORMATS = ("txt", "md", "json", "srt", "vtt")
  7. def _pad(n: int, w: int = 2) -> str:
  8. return str(n).zfill(w)
  9. def stamp(seconds: float) -> str:
  10. """[HH:MM:SS], floored."""
  11. total = max(0, int(seconds))
  12. return f"[{_pad(total // 3600)}:{_pad(total // 60 % 60)}:{_pad(total % 60)}]"
  13. def _clock(seconds: float, sep: str) -> str:
  14. ms_total = max(0, round(seconds * 1000))
  15. ms, s = ms_total % 1000, ms_total // 1000
  16. return f"{_pad(s // 3600)}:{_pad(s // 60 % 60)}:{_pad(s % 60)}{sep}{_pad(ms, 3)}"
  17. def to_txt(sentences: list[dict], timestamps: bool = True) -> str:
  18. """One sentence per line: '[00:00:02] Speaker 1: text' (label omitted when unknown)."""
  19. lines = []
  20. for s in sentences:
  21. parts = []
  22. if timestamps:
  23. parts.append(stamp(s["start"]))
  24. parts.append(f"{s['speaker']}: {s['text']}" if s.get("speaker") else s["text"])
  25. lines.append(" ".join(parts))
  26. return "\n".join(lines) + ("\n" if lines else "")
  27. def _yaml_str(v: str) -> str:
  28. return json.dumps(v, ensure_ascii=False)
  29. def to_markdown(sentences: list[dict], meta: dict) -> str:
  30. """A vault note: frontmatter, then speaker turns as paragraphs."""
  31. fm = ["---", "type: transcript",
  32. f"title: {_yaml_str(meta['title'])}",
  33. f"recorded: {meta['recorded']}",
  34. f"transcribed: {meta['transcribed']}",
  35. f"duration: {_yaml_str(meta['duration'])}",
  36. f"engine: {meta['engine']}",
  37. f"language: {meta.get('language') or 'unknown'}",
  38. f"source: {_yaml_str(meta['source'])}"]
  39. if meta.get("speakers"):
  40. fm.append("speakers:")
  41. fm += [f" - {_yaml_str(sp)}" for sp in meta["speakers"]]
  42. fm += ["tags:", " - transcript", "---", ""]
  43. body = [f"# {meta['title']}", ""]
  44. for t in turns(sentences):
  45. head = f"**{t['speaker']}** {stamp(t['start'])}" if t["speaker"] else stamp(t["start"])
  46. body += [head, " ".join(s["text"] for s in t["sentences"]), ""]
  47. return "\n".join(fm + body).rstrip() + "\n"
  48. def to_json(sentences: list[dict], result: dict, meta: dict) -> str:
  49. data = {"meta": meta, "sentences": sentences, "segments": result.get("segments", [])}
  50. return json.dumps(data, indent=2, ensure_ascii=False, default=str) + "\n"
  51. def _cues(sentences: list[dict]):
  52. for i, s in enumerate(sentences):
  53. end = s["end"]
  54. if end <= s["start"]:
  55. nxt = sentences[i + 1]["start"] if i + 1 < len(sentences) else s["start"] + 2.0
  56. end = max(nxt, s["start"] + 0.5)
  57. text = f"{s['speaker']}: {s['text']}" if s.get("speaker") else s["text"]
  58. yield s["start"], end, text
  59. def to_srt(sentences: list[dict]) -> str:
  60. return "\n".join(f"{i}\n{_clock(a, ',')} --> {_clock(b, ',')}\n{t}\n"
  61. for i, (a, b, t) in enumerate(_cues(sentences), 1))
  62. def to_vtt(sentences: list[dict]) -> str:
  63. return "WEBVTT\n\n" + "\n".join(f"{_clock(a, '.')} --> {_clock(b, '.')}\n{t}\n"
  64. for a, b, t in _cues(sentences))