direct download via youtube links

This commit is contained in:
2026-07-22 09:53:27 -04:00
parent 518b48e395
commit e5643590ee
4 changed files with 106 additions and 2 deletions

View File

@@ -54,6 +54,17 @@ async def search_youtube(q: str = Query(..., min_length=1)) -> list[dict[str, An
raise HTTPException(status_code=502, detail=f"YouTube search failed: {exc}")
@app.get("/api/youtube/resolve")
async def resolve_youtube(url: str = Query(..., min_length=1)) -> dict[str, Any]:
"""Resolve a pasted YouTube URL to video metadata (no download)."""
try:
return await youtube.resolve_youtube_url(url)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Couldn't load that video: {exc}")
# ── Download ──────────────────────────────────────────────────────────────────
@app.post("/api/download")

View File

@@ -65,6 +65,50 @@ def _format_duration(seconds: int | None) -> str:
return f"{m}:{s:02d}"
# ── Resolve pasted URL ───────────────────────────────────────────────────────
_VIDEO_ID_RE = re.compile(
r"(?:youtube(?:-nocookie)?\.com/(?:watch\?(?:.*&)?v=|embed/|shorts/|v/)|youtu\.be/)"
r"([A-Za-z0-9_-]{11})"
)
_BARE_ID_RE = re.compile(r"^[A-Za-z0-9_-]{11}$")
def extract_video_id(url: str) -> str | None:
"""Pull an 11-char YouTube video ID out of a pasted URL (or bare ID)."""
url = url.strip()
if _BARE_ID_RE.match(url):
return url
match = _VIDEO_ID_RE.search(url)
return match.group(1) if match else None
async def resolve_youtube_url(url: str) -> dict[str, Any]:
"""Fetch metadata for a single pasted YouTube URL, without downloading it."""
video_id = extract_video_id(url)
if not video_id:
raise ValueError("Couldn't find a YouTube video ID in that URL.")
watch_url = f"https://www.youtube.com/watch?v={video_id}"
ydl_opts = {"quiet": True, "no_warnings": True, "skip_download": True}
loop = asyncio.get_event_loop()
entry = await loop.run_in_executor(None, _yt_resolve, watch_url, ydl_opts)
return {
"videoId": video_id,
"title": entry.get("title", ""),
"channel": entry.get("uploader") or entry.get("channel", ""),
"duration": _format_duration(entry.get("duration")),
"thumbnailUrl": f"https://i.ytimg.com/vi/{video_id}/mqdefault.jpg",
"watchUrl": watch_url,
}
def _yt_resolve(url: str, opts: dict) -> dict[str, Any]:
with yt_dlp.YoutubeDL(opts) as ydl:
return ydl.extract_info(url, download=False)
# ── Download ──────────────────────────────────────────────────────────────────
async def start_download(