| from typing import List, Dict, Any, Union |
|
|
|
|
| |
| |
| |
|
|
| 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) |
|
|
|
|
| |
| |
| |
|
|
| def _normalize_input(data: List[Dict[str, Any]]) -> List[Dict[str, Any]]: |
| """ |
| Converts any supported structure into unified subtitle blocks |
| """ |
|
|
| normalized = [] |
|
|
| |
| 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 |
|
|
| |
| 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) |
|
|
| |
| if len(buffer) >= 10: |
|
|
| normalized.append({ |
| "start": current_start, |
| "end": end, |
| "text": " ".join(buffer) |
| }) |
|
|
| buffer = [] |
| current_start = None |
|
|
| |
| if buffer: |
| normalized.append({ |
| "start": current_start or 0, |
| "end": data[-1].get("end", 0), |
| "text": " ".join(buffer) |
| }) |
|
|
| return normalized |
|
|
|
|
| |
| |
| |
|
|
| 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("") |
|
|
| index += 1 |
|
|
| return "\n".join(output).strip() |
|
|
|
|
| |
| |
| |
|
|
| 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}" |
|
|
|
|
| |
| |
| |
|
|
| def _clean_text(text: str) -> str: |
| """ |
| Sanitizes subtitle text for rendering engines |
| """ |
|
|
| if not text: |
| return "" |
|
|
| text = text.replace("\n", " ") |
| text = text.replace("\r", " ") |
|
|
| |
| text = " ".join(text.split()) |
|
|
| return text.strip() |
|
|
|
|
| |
| |
| |
|
|
| 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 |
| } |