Remove course thumbnails and the ffmpeg dependency

Course cards go back to the colour-block art that previously sat under the
captured frame, so the UI is unchanged apart from losing the photo.

- Drop thumb.php, generateThumbnails(), captureThumbnail() and
  ensureThumbnailColumns(), plus the first-video tracking the scanner only
  kept to pick a frame source.
- Drop the ffprobe fallback in probeDuration(); the pure-PHP mvhd parse is
  now the only scan-time probe, with the player's report on first play as
  the remaining fallback.
- Drop THUMBNAILS_DIR/THUMBNAIL_SEEK_PERCENT from config, the
  thumbnail_* columns from schema.sql, the thumbnail counters from scan
  output, .thumb img from the stylesheet, and the matching README parts.

Existing databases keep two now-unused folders.thumbnail_* columns; they
are inert and can be dropped by hand if wanted.

Claude-Session: https://claude.ai/code/session_014oyi9DMdhALrGz989MNNZJ
This commit is contained in:
Harwinder Sekhon
2026-07-27 14:06:06 -04:00
parent c4c88e52da
commit 91edba56d6
9 changed files with 31 additions and 275 deletions
+5 -9
View File
@@ -348,14 +348,10 @@
const bar = (percent, hue) =>
`<div class="bar"${hue ? ` style="--c:${hue}"` : ''}><span style="width:${Math.max(0, Math.min(100, percent))}%"></span></div>`;
// A course thumbnail is a real captured frame when the scanner made one
// (see generateThumbnails() server-side); until then, or if the request
// 404s, onerror drops the broken <img> and the colour block underneath
// (already painted — cat/dur/play/percent all sit above it) shows through.
function thumb(hue, { cat, dur, play, percent, courseId } = {}) {
// Courses have no cover art, so the card art is a colour block in the
// course's own hue, with the cat/dur/play/percent overlays on top.
function thumb(hue, { cat, dur, play, percent } = {}) {
return `<div class="thumb" style="--c:${hue}">
${courseId ? `<img src="thumb.php?course_id=${courseId}" alt="" loading="lazy"
onerror="this.remove()">` : ''}
${cat ? `<span class="thumb-cat">${esc(cat)}</span>` : ''}
${dur ? `<span class="thumb-dur">${esc(dur)}</span>` : ''}
${play ? `<span class="thumb-play"><span>${iconFill('play', 20)}</span></span>` : ''}
@@ -367,7 +363,7 @@
function courseCard(c) {
const hue = hueOf(c.id);
return `<a class="course-card" href="#/folder/${c.id}">
${thumb(hue, { cat: SHELF_NAME[c.state], dur: fmtLong(c.duration), courseId: c.id })}
${thumb(hue, { cat: SHELF_NAME[c.state], dur: fmtLong(c.duration) })}
<div class="course-card-body">
<div class="course-title">${esc(c.name)}</div>
<div class="course-meta">${esc(courseMeta(c))}</div>
@@ -540,7 +536,7 @@
const left = resume.duration ? Math.max(0, resume.duration - resume.position) : 0;
const hue = hueOf(resume.course_id ?? resume.id);
return `<div class="hero">
<div class="hero-art">${thumb(hue, { play: true, percent: pct, courseId: resume.course_id })}</div>
<div class="hero-art">${thumb(hue, { play: true, percent: pct })}</div>
<div class="hero-body">
<span class="hero-eyebrow">Continue watching</span>
<div class="hero-title">${esc(resume.name)}</div>