from typing import List, Dict, Any, Union # ===================================================== # PUBLIC API (USED BY MAIN.PY) # ===================================================== def generate_srt(data: List[Dict[str, Any]]) -> str: """ Universal SRT generator for: - Whisper word output (V1–V7) - Highlight segments (start/end grouped words) - Mixed/partial structures Expected input formats: 1. Word-level: {"text": "...", "start": float, "end": float} 2. Segment-level: [{"start": float, "end": float, "text": "..."}] Returns: SRT formatted string """ if not data: return "" normalized = _normalize_input(data) return _build_srt(normalized) # ===================================================== # NORMALIZATION LAYER (CRITICAL FOR V1–V7 COMPATIBILITY) # ===================================================== def _normalize_input(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: """ Converts any supported structure into unified subtitle blocks """ normalized = [] # CASE 1: Already segment-based if isinstance(data[0], dict) and "start" in data[0] and "end" in data[0] and "text" in data[0]: for item in data: normalized.append({ "start": float(item.get("start", 0)), "end": float(item.get("end", 0)), "text": str(item.get("text", "")).strip() }) return normalized # CASE 2: Whisper word-level output buffer = [] current_start = None for w in data: if not isinstance(w, dict): continue text = str(w.get("text", "")).strip() start = w.get("start", None) end = w.get("end", None) if start is None or end is None: continue if current_start is None: current_start = start buffer.append(text) # Chunking strategy: group every ~8–12 words if len(buffer) >= 10: normalized.append({ "start": current_start, "end": end, "text": " ".join(buffer) }) buffer = [] current_start = None # flush remaining buffer if buffer: normalized.append({ "start": current_start or 0, "end": data[-1].get("end", 0), "text": " ".join(buffer) }) return normalized # ===================================================== # SRT BUILDER # ===================================================== def _build_srt(items: List[Dict[str, Any]]) -> str: """ Converts normalized subtitle blocks → SRT format """ output = [] index = 1 for item in items: start = _format_time(item["start"]) end = _format_time(item["end"]) text = _clean_text(item["text"]) if not text: continue output.append(f"{index}") output.append(f"{start} --> {end}") output.append(f"{text}") output.append("") # blank line separator index += 1 return "\n".join(output).strip() # ===================================================== # TIME FORMATTER # ===================================================== def _format_time(seconds: Union[int, float]) -> str: """ Converts seconds → SRT timestamp format HH:MM:SS,mmm """ try: seconds = float(seconds) except: seconds = 0.0 hrs = int(seconds // 3600) mins = int((seconds % 3600) // 60) secs = int(seconds % 60) ms = int((seconds - int(seconds)) * 1000) return f"{hrs:02}:{mins:02}:{secs:02},{ms:03}" # ===================================================== # TEXT CLEANER (IMPORTANT FOR VIDEO RENDERING STABILITY) # ===================================================== def _clean_text(text: str) -> str: """ Sanitizes subtitle text for rendering engines """ if not text: return "" text = text.replace("\n", " ") text = text.replace("\r", " ") # remove excessive spacing text = " ".join(text.split()) return text.strip() # ===================================================== # OPTIONAL DEBUG HELPER (SAFE IN PRODUCTION) # ===================================================== def debug_srt(data: List[Dict[str, Any]]) -> dict: """ Returns structured preview for debugging pipelines """ normalized = _normalize_input(data) return { "blocks": len(normalized), "sample": normalized[:3], "duration": normalized[-1]["end"] if normalized else 0 }