"""End-to-end orchestrator. Two modes: * mode="api" (default) — MuAPI does download / transcribe / LLM / autocrop. Fast, no local deps, pay-per-call. * mode="local" — yt-dlp + faster-whisper + OpenAI or Gemini + ffmpeg/opencv. Self-hosted, LLM_PROVIDER selects OpenAI or Gemini. """ from typing import Dict, List, Optional from .clipper import crop_highlights from .downloader import download_youtube from .highlights import call_muapi_llm, get_highlights from .transcriber import transcribe def _run_local( youtube_url: str, num_clips: int, aspect_ratio: str, download_format: str, language: Optional[str], ) -> Dict: from .local.clipper import crop_highlights_local from .local.downloader import download_youtube_local from .local.llm import call_local_llm from .local.transcriber import transcribe_local source_path = download_youtube_local(youtube_url, fmt=download_format) transcript = transcribe_local(source_path, language=language) if not transcript["segments"]: raise RuntimeError( "Whisper produced no segments. The video may have no detectable speech." ) highlights_result = get_highlights(transcript, num_clips=num_clips, llm_fn=call_local_llm) all_highlights: List[Dict] = highlights_result.get("highlights", []) if not all_highlights: raise RuntimeError("Highlight generator returned zero clips.") top = sorted(all_highlights, key=lambda h: int(h.get("score", 0)), reverse=True)[:num_clips] print(f"[pipeline/local] cropping {len(top)} of {len(all_highlights)} candidates", flush=True) shorts = crop_highlights_local(source_path, top, aspect_ratio=aspect_ratio) return { "mode": "local", "source_video_url": source_path, "transcript": transcript, "highlights": all_highlights, "shorts": shorts, } def _run_api( youtube_url: str, num_clips: int, aspect_ratio: str, download_format: str, language: Optional[str], ) -> Dict: source_url = download_youtube(youtube_url, fmt=download_format) transcript = transcribe(source_url, language=language) if not transcript["segments"]: raise RuntimeError( "Whisper produced no segments. The video may have no detectable speech." ) highlights_result = get_highlights(transcript, num_clips=num_clips, llm_fn=call_muapi_llm) all_highlights: List[Dict] = highlights_result.get("highlights", []) if not all_highlights: raise RuntimeError("Highlight generator returned zero clips.") top = sorted(all_highlights, key=lambda h: int(h.get("score", 0)), reverse=True)[:num_clips] print(f"[pipeline] cropping {len(top)} of {len(all_highlights)} candidates", flush=True) shorts = crop_highlights(source_url, top, aspect_ratio=aspect_ratio) return { "mode": "api", "source_video_url": source_url, "transcript": transcript, "highlights": all_highlights, "shorts": shorts, } def generate_shorts( youtube_url: str, num_clips: int = 3, aspect_ratio: str = "9:16", download_format: str = "720", language: Optional[str] = None, mode: str = "api", ) -> Dict: """Run the full pipeline and return a structured result. Args: youtube_url: source URL. num_clips: how many shorts to render. aspect_ratio: e.g. "9:16", "1:1". download_format: source resolution ("360" / "480" / "720" / "1080"). language: ISO-639-1 to force Whisper language detection. mode: "api" (default, MuAPI) or "local" (yt-dlp + faster-whisper + OpenAI or Gemini + ffmpeg). Returns: { "mode": "api" | "local", "source_video_url": str, # hosted URL (api) or local path (local) "transcript": {...}, "highlights": [...], # all candidates ranked "shorts": [...], # top `num_clips` with clip_url / local path } """ mode = (mode or "api").lower() if mode == "local": return _run_local(youtube_url, num_clips, aspect_ratio, download_format, language) if mode == "api": return _run_api(youtube_url, num_clips, aspect_ratio, download_format, language) raise ValueError(f"Unknown mode: {mode!r}. Use 'api' or 'local'.")