feat: implement vertical crop, local LLM deepseek-v4-flash, multi-threading, word-level pink box highlight subtitles, and Nextcloud scan sync
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
"""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'.")
|
||||
Reference in New Issue
Block a user