From cc0d9ffc5d55f544e10110ae2e82f9bb3ea48cff Mon Sep 17 00:00:00 2001 From: tcpsyn Date: Fri, 14 Aug 2026 03:34:14 -0500 Subject: [PATCH] Add transcript parser for episode page generation --- tests/test_transcript_parser.py | 37 +++++++++++++++++++++++++++++++++ website_gen/__init__.py | 0 website_gen/transcript.py | 29 ++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) create mode 100644 tests/test_transcript_parser.py create mode 100644 website_gen/__init__.py create mode 100644 website_gen/transcript.py diff --git a/tests/test_transcript_parser.py b/tests/test_transcript_parser.py new file mode 100644 index 0000000..8b40f07 --- /dev/null +++ b/tests/test_transcript_parser.py @@ -0,0 +1,37 @@ +import sys +from pathlib import Path +sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) + +from website_gen.transcript import parse_transcript + + +def test_splits_speaker_turns(): + raw = "LUKE: Welcome back.\n\nSLIM: Hey Luke, thanks for taking my call." + turns = parse_transcript(raw) + assert turns == [("LUKE", "Welcome back."), + ("SLIM", "Hey Luke, thanks for taking my call.")] + + +def test_unlabeled_paragraph_carries_previous_speaker(): + raw = "LUKE: First thing.\n\nStill Luke talking." + assert parse_transcript(raw) == [("LUKE", "First thing."), + ("LUKE", "Still Luke talking.")] + + +def test_ignores_blank_and_whitespace_paragraphs(): + raw = "LUKE: One.\n\n \n\nSLIM: Two." + assert len(parse_transcript(raw)) == 2 + + +def test_speaker_name_with_spaces_is_not_treated_as_label(): + """A colon mid-sentence must not be mistaken for a speaker label.""" + raw = "LUKE: Here's the thing: it was the alternator." + turns = parse_transcript(raw) + assert len(turns) == 1 + assert turns[0][0] == "LUKE" + assert "the thing: it was" in turns[0][1] + + +def test_empty_input_returns_empty_list(): + assert parse_transcript("") == [] + assert parse_transcript(" \n\n ") == [] diff --git a/website_gen/__init__.py b/website_gen/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/website_gen/transcript.py b/website_gen/transcript.py new file mode 100644 index 0000000..913ff5c --- /dev/null +++ b/website_gen/transcript.py @@ -0,0 +1,29 @@ +import re + +SPEAKER_RE = re.compile(r"^([A-Z][A-Z0-9 .'-]{0,30}):\s*(.*)$", re.DOTALL) + + +def parse_transcript(raw: str) -> list[tuple[str, str]]: + """Split a transcript into (speaker, text) turns. + + Paragraphs are blank-line separated. A paragraph that does not open with a + SPEAKER: label is attributed to whoever spoke last, which is how the + transcriber emits long turns that wrap. + """ + turns: list[tuple[str, str]] = [] + current = None + for para in re.split(r"\n\s*\n", raw or ""): + para = para.strip() + if not para: + continue + m = SPEAKER_RE.match(para) + if m: + current = m.group(1).strip() + text = m.group(2).strip() + else: + text = para + if current is None: + current = "LUKE" + if text: + turns.append((current, text)) + return turns