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

test_shape_render.py

Language Python5.1 KB116 lines

tests/test_shape_render.py

  1. import json
  2. from transcription_pipeline import render
  3. from transcription_pipeline.shape import (_majority_speaker, _speaker_runs, rename_speakers,
  4. sentences_from_segments, turns)
  5. from .helpers import fake_result
  6. def shaped(speakers=True):
  7. return rename_speakers(sentences_from_segments(fake_result(speakers)["segments"]))
  8. def test_sentences_take_word_times_and_speakers():
  9. sents, names = shaped()
  10. assert [s["text"] for s in sents] == ["Hi, thanks for calling back.", "No problem.",
  11. "Did the draft come through?"]
  12. assert [s["start"] for s in sents] == [0.5, 2.2, 3.5]
  13. assert all(s["timed"] for s in sents)
  14. # SPEAKER_01 talks first, so it becomes Speaker 1 whatever its raw label.
  15. assert [s["speaker"] for s in sents] == ["Speaker 1", "Speaker 2", "Speaker 2"]
  16. assert names == ["Speaker 1", "Speaker 2"]
  17. def test_turns_merge_consecutive_sentences():
  18. sents, _ = shaped()
  19. t = turns(sents)
  20. assert [x["speaker"] for x in t] == ["Speaker 1", "Speaker 2"]
  21. assert len(t[1]["sentences"]) == 2 and t[1]["end"] == 5.0
  22. def test_unaligned_segment_falls_back_to_interpolation():
  23. seg = {"start": 10.0, "end": 20.0, "text": "First part here. Second part here."}
  24. sents = sentences_from_segments([seg])
  25. assert sents[0]["start"] == 10.0 and not sents[0]["timed"]
  26. # "Second" starts at character 17 of 34, so halfway through the segment.
  27. assert sents[1]["start"] == 15.0
  28. def test_unpunctuated_speech_splits_at_speaker_change():
  29. words = [{"word": x, "start": i * 0.4, "end": i * 0.4 + 0.3, "speaker": s}
  30. for i, (x, s) in enumerate([("so", "A"), ("are", "A"), ("we", "A"), ("done", "A"),
  31. ("yes", "B"), ("all", "B"), ("done", "B")])]
  32. seg = {"start": 0.0, "end": 3.0, "text": "so are we done yes all done", "words": words}
  33. sents, _ = rename_speakers(sentences_from_segments([seg]))
  34. assert [(s["speaker"], s["text"]) for s in sents] == [("Speaker 1", "so are we done"),
  35. ("Speaker 2", "yes all done")]
  36. assert sents[1]["start"] == 1.6
  37. def test_one_word_flicker_is_not_a_speaker_change():
  38. labels = ["A", "A", "A", "B", "A", "A"]
  39. items = [(i, {"word": "w", "speaker": s}) for i, s in enumerate(labels)]
  40. runs = _speaker_runs(items)
  41. assert len(runs) == 1 and runs[0][0] == "A" and len(runs[0][1]) == 6
  42. def test_words_without_speaker_join_their_run():
  43. items = [(0, {"word": "a"}), (1, {"word": "b", "speaker": "A"}),
  44. (2, {"word": "c", "speaker": "A"}), (3, {"word": "d"}),
  45. (4, {"word": "e", "speaker": "B"}), (5, {"word": "f", "speaker": "B"})]
  46. runs = _speaker_runs(items)
  47. assert [(r[0], len(r[1])) for r in runs] == [("A", 4), ("B", 2)]
  48. def test_majority_tie_goes_to_first_speaker():
  49. words = [{"word": "one", "start": 0, "end": 1, "speaker": "X"},
  50. {"word": "two", "start": 1, "end": 2, "speaker": "Y"}]
  51. assert _majority_speaker(words) == "X"
  52. def test_no_diarization_means_no_labels():
  53. sents, names = shaped(speakers=False)
  54. assert names == [] and all(s["speaker"] is None for s in sents)
  55. assert render.to_txt(sents).splitlines()[0] == "[00:00:00] Hi, thanks for calling back."
  56. def test_txt_line_format():
  57. sents, _ = shaped()
  58. lines = render.to_txt(sents).splitlines()
  59. assert lines[0] == "[00:00:00] Speaker 1: Hi, thanks for calling back."
  60. assert lines[1] == "[00:00:02] Speaker 2: No problem."
  61. bare = render.to_txt(sents, timestamps=False).splitlines()
  62. assert bare[0] == "Speaker 1: Hi, thanks for calling back."
  63. assert render.stamp(3725.9) == "[01:02:05]"
  64. def test_markdown_is_a_vault_note():
  65. sents, names = shaped()
  66. md = render.to_markdown(sents, {"title": "Call", "recorded": "2026-09-01",
  67. "transcribed": "2026-09-01T10:00:00", "duration": "0:00:05",
  68. "engine": "local", "language": "en", "source": "call.m4a",
  69. "speakers": names})
  70. head, body = md.split("---\n", 2)[1:]
  71. assert "type: transcript" in head and ' - "Speaker 2"' in head
  72. assert "**Speaker 1** [00:00:00]\nHi, thanks for calling back." in body
  73. assert "**Speaker 2** [00:00:02]\nNo problem. Did the draft come through?" in body
  74. def test_subtitles():
  75. sents, _ = shaped()
  76. srt = render.to_srt(sents)
  77. assert srt.startswith("1\n00:00:00,500 --> 00:00:01,700\n"
  78. "Speaker 1: Hi, thanks for calling back.\n")
  79. vtt = render.to_vtt(sents)
  80. assert vtt.startswith("WEBVTT\n\n00:00:00.500 --> 00:00:01.700\n")
  81. # An untimed sentence with no end still gets a cue that moves forward.
  82. cue = list(render._cues([{"text": "x", "start": 4.0, "end": 4.0, "speaker": None}]))[0]
  83. assert cue[1] > cue[0]
  84. def test_json_carries_everything():
  85. sents, names = shaped()
  86. data = json.loads(render.to_json(sents, fake_result(), {"speakers": names}))
  87. assert data["meta"]["speakers"] == names and len(data["segments"]) == 2
  88. assert data["sentences"][0]["timed"] is True