Widen thumbnail sampling window past platform bumpers

Many course platforms (Pluralsight, Packt, ...) open with a generic
branded bumper animation running 10-20s before the actual course
title card. The 8s candidate window landed squarely inside that
bumper and grabbed the platform logo instead of anything
course-specific. Candidates now spread across the first ~25s.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-24 20:33:37 -04:00
co-authored by Claude Sonnet 5
parent de9c05d0d0
commit 8220bb542a
3 changed files with 19 additions and 12 deletions
+1
View File
@@ -1,3 +1,4 @@
2026-08-25 00:33 UTC — Widen thumbnail sampling window past platform bumpers
2026-08-25 00:26 UTC — Fix race condition in thumbnail candidate generation 2026-08-25 00:26 UTC — Fix race condition in thumbnail candidate generation
2026-08-25 00:20 UTC — Add autoplay, progress rings, duplicate-lesson detection, title-card thumbnails 2026-08-25 00:20 UTC — Add autoplay, progress rings, duplicate-lesson detection, title-card thumbnails
2026-08-24 23:22 UTC — Add Favorites, storage drill-down, shortcuts, and What's New 2026-08-24 23:22 UTC — Add Favorites, storage drill-down, shortcuts, and What's New
+1 -1
View File
@@ -1 +1 @@
2026-08-25 00:26 UTC — fix race condition in thumbnail candidate generation 2026-08-25 00:33 UTC — widen thumbnail sampling window past platform bumpers
+17 -11
View File
@@ -926,15 +926,16 @@ def _generate_course_thumbnail(course_dir: Path) -> Optional[str]:
added later is still picked up on the next request past the short added later is still picked up on the next request past the short
in-memory cache above. in-memory cache above.
Course intros typically show a title card (course/lesson name, maybe Course intros typically run a branded bumper animation (10-20s on
a logo) for the first several seconds before cutting to the some platforms) followed by a title card before cutting to the
presenter - a single frame at a fixed offset can easily land past presenter - a single frame at a fixed offset can easily land inside
that cut and grab someone mid-sentence instead. Rather than guess one the bumper or past the cut into someone mid-sentence instead. Rather
offset, this samples a handful of candidates in the first ~8 seconds than guess one offset, this samples candidates spread across the
and keeps the one with the largest resulting JPEG: a title card (text, first ~25 seconds and keeps the one with the largest resulting JPEG:
graphics, a logo) compresses to a noticeably bigger file than a blank a title card or logo (text, graphics) compresses to a noticeably
fade-in or a plain talking-head frame, which is a cheap enough proxy bigger file than a blank fade-in or a plain talking-head frame, which
for "has more going on" without any real image analysis. is a cheap enough proxy for "has more going on" without any real
image analysis.
""" """
video_files = sorted( video_files = sorted(
f for f in course_dir.rglob('*') f for f in course_dir.rglob('*')
@@ -945,8 +946,13 @@ def _generate_course_thumbnail(course_dir: Path) -> Optional[str]:
source = video_files[0] source = video_files[0]
duration = _probe_media_duration_seconds(source) or 0 duration = _probe_media_duration_seconds(source) or 0
max_offset = min(8.0, duration * 0.5) if duration else 6.0 # Many course platforms (Pluralsight, Packt, ...) open with a
candidate_offsets = sorted({o for o in (1.0, 2.5, 4.0, 6.0) if o <= max_offset}) or [1.0] # generic branded bumper animation running 10-20s before the actual
# course title card - an 8s window landed squarely inside that
# bumper and grabbed the platform logo instead. Spread candidates
# further in to reach past it.
max_offset = min(25.0, duration * 0.5) if duration else 15.0
candidate_offsets = sorted({o for o in (2.0, 5.0, 8.0, 12.0, 16.0, 20.0, 25.0) if o <= max_offset}) or [2.0]
output_path = course_dir / AUTO_THUMBNAIL_FILENAME output_path = course_dir / AUTO_THUMBNAIL_FILENAME
best_candidate = None best_candidate = None