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:
urieljareth
2026-07-26 23:48:46 -06:00
parent 0682d2d806
commit 0866c92650
5 changed files with 58 additions and 32 deletions
+26
View File
@@ -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