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
+17 -7
View File
@@ -108,12 +108,14 @@ class JobManager:
return (name, row.channel_id, url)
def _run_batch(self, job_id: str, opts: dict[str, Any]) -> None:
from ..pipeline import process_video as _process_video, cache_thumbnail
video_ids = opts.get("video_ids") or []
cookie_path = opts.get("cookies_file") or resolve_active_path(self.store)
thumb_dir = Path(self.cfg.output_dir_resolved).parent / "thumbnails"
total = len(video_ids)
self.store.update_job(job_id, status="running", total=total, completed=0)
self._emit(job_id, "progress", {"completed": 0, "total": total})
self._emit(job_id, "log", {"msg": f"processing {total} videos"})
self._emit(job_id, "log", {"msg": f"processing {total} videos (.md + thumbnails)"})
cfg = _clone_config(self.cfg)
if opts.get("languages"):
cfg.languages = opts["languages"]
@@ -129,12 +131,20 @@ class JobManager:
completed += 1
self.store.update_job(job_id, completed=completed)
continue
channel_name, channel_id, channel_url = self._resolve_channel_for_video(vid)
status = process_video(
row, cfg, self.store, channel_name, channel_id, channel_url,
cookies_file=cookie_path,
on_log=lambda m: self._emit(job_id, "log", {"msg": m}),
)
# UX optimization: skip re-extracting videos whose .md already exists — just ensure thumbnail cached.
md_path = Path(cfg.output_dir_resolved).parent / row.markdown_path if row.markdown_path else None
if row.status == "done" and row.markdown_path and md_path and md_path.exists():
cache_thumbnail(self.store, vid, thumb_dir)
status = "done"
self._emit(job_id, "log", {"msg": f"cached {vid} (md already present)"})
else:
channel_name, channel_id, channel_url = self._resolve_channel_for_video(vid)
status = _process_video(
row, cfg, self.store, channel_name, channel_id, channel_url,
cookies_file=cookie_path,
on_log=lambda m: self._emit(job_id, "log", {"msg": m}),
)
cache_thumbnail(self.store, vid, thumb_dir)
completed += 1
self.store.update_job(job_id, completed=completed)
self._emit(job_id, "progress", {"completed": completed, "total": total, "video_id": vid, "status": status})