feat: .md download bundles thumbnails, auto-cache on channel add
- Bulk 'Download .md' now also caches thumbnails (one action does both); removed the redundant standalone Thumbnails button from the bulk action bar. - Scrape UX optimization: batch job skips videos whose .md already exists (just caches their thumbnail) instead of re-extracting. - Auto-thumbnails: on addChannel, thumbnails cache in the background so a newly-attached channel's catalog looks populated immediately, even before any .md is generated. - Thumbnail fallback: pending videos without a stored thumbnail URL use the canonical https://i.ytimg.com/vi/<id>/hqdefault.jpg, so every video shows a thumbnail (and /api/thumbnails/{id} never 404s — redirects if uncached). - New helpers in pipeline.py: thumbnail_url_for() + cache_thumbnail().
This commit is contained in:
@@ -140,6 +140,32 @@ def _safe_dirname(name: str) -> str:
|
||||
return safe.strip().strip(".") or "unknown"
|
||||
|
||||
|
||||
def thumbnail_url_for(video_row) -> str:
|
||||
"""Thumbnail URL for a video: stored URL, else the canonical YouTube one derived from its id."""
|
||||
if video_row and getattr(video_row, "thumbnail", None):
|
||||
return video_row.thumbnail
|
||||
vid = getattr(video_row, "video_id", "") if video_row else ""
|
||||
return f"https://i.ytimg.com/vi/{vid}/hqdefault.jpg"
|
||||
|
||||
|
||||
def cache_thumbnail(store, video_id: str, thumbnails_dir) -> bool:
|
||||
"""Download + cache a video's thumbnail to <thumbnails_dir>/<video_id>.jpg. Returns True on success/existing."""
|
||||
import requests as _requests
|
||||
out = Path(thumbnails_dir) / f"{video_id}.jpg"
|
||||
if out.exists():
|
||||
return True
|
||||
v = store.get_video(video_id)
|
||||
url = thumbnail_url_for(v) if v else f"https://i.ytimg.com/vi/{video_id}/hqdefault.jpg"
|
||||
try:
|
||||
r = _requests.get(url, timeout=15, headers={"User-Agent": "Mozilla/5.0"})
|
||||
r.raise_for_status()
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
out.write_bytes(r.content)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def re_render_videos(store: Store, cfg: Config, channel_id: str | None = None) -> int:
|
||||
"""Regenerate .md for done videos that have stored segments. Returns count re-rendered."""
|
||||
import json
|
||||
|
||||
Reference in New Issue
Block a user