"""Subtitle generation: timed segments -> SRT/VTT text. Works against anything exposing `.segments` of objects with start/end/text (a Transcript or a Translation), keeping with the rule that subtitles are always derived data and never re-recorded from audio. """ from __future__ import annotations from typing import Iterable, Protocol class HasTimedText(Protocol): start: float end: float text: str def _format_srt_timestamp(seconds: float) -> str: seconds = max(0.0, seconds) hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) millis = int(round((seconds - int(seconds)) * 1000)) return f"{hours:02}:{minutes:02}:{secs:02},{millis:03}" def _format_vtt_timestamp(seconds: float) -> str: seconds = max(0.0, seconds) hours = int(seconds // 3600) minutes = int((seconds % 3600) // 60) secs = int(seconds % 60) millis = int(round((seconds - int(seconds)) * 1000)) return f"{hours:02}:{minutes:02}:{secs:02}.{millis:03}" def generate_srt(segments: Iterable[HasTimedText]) -> str: lines = [] for idx, segment in enumerate(segments, start=1): start = _format_srt_timestamp(segment.start) end = _format_srt_timestamp(segment.end) lines.append(f"{idx}\n{start} --> {end}\n{segment.text.strip()}\n") return "\n".join(lines) def generate_vtt(segments: Iterable[HasTimedText]) -> str: lines = ["WEBVTT\n"] for segment in segments: start = _format_vtt_timestamp(segment.start) end = _format_vtt_timestamp(segment.end) lines.append(f"{start} --> {end}\n{segment.text.strip()}\n") return "\n".join(lines)