fix(stream): /critico review fixes for the sidecar cache

- ExtractSubtitlesVTTMulti: distrust output when ffmpeg is killed by signal
  (45-min timeout on a too-big remux) — a truncated WebVTT passed the len>0
  check and got cached as a silently-incomplete track until the media mtime
  changed. Skip all output on signal-kill; keep it on a clean non-zero exit.
- stream handlers: read the sidecar cache BEFORE the ffmpegPath guard so a
  pre-warmed sub/thumbnail still serves if ffmpeg was removed after the cache
  was filled.
- scan: log when the prewarm is skipped because ffmpeg is unavailable (matches
  the daemon; CLAUDE.md wants bootstrap to log on every branch).
- unexport sidecarDir/subtitleCachePath/thumbnailCachePath (no external callers).
- prewarm: surface a sample error in the summary so a systemic ffmpeg failure
  is distinguishable from one corrupt file.
- add unit tests: codec whitelist, cache paths, mtime freshness, atomic write,
  thumb-position dedup.
This commit is contained in:
Deivid Soto 2026-06-02 13:46:07 +02:00
parent 1c8cc1c409
commit bc6f85bf39
6 changed files with 228 additions and 37 deletions

View file

@ -60,6 +60,8 @@ func PrewarmSidecars(ctx context.Context, cache *LibraryCache, opts PrewarmOptio
var wg sync.WaitGroup
var mu sync.Mutex
subCached, thumbCached, failed := 0, 0, 0
var sampleErr string // first extraction error, surfaced in the summary so a
// systemic ffmpeg failure (vs one corrupt file) is diagnosable from "N failed".
for i := 0; i < workers; i++ {
wg.Add(1)
@ -77,9 +79,12 @@ func PrewarmSidecars(ctx context.Context, cache *LibraryCache, opts PrewarmOptio
jctx, cancel := context.WithTimeout(ctx, 60*time.Second)
img, err := mediainfo.ExtractThumbnailJPEG(jctx, opts.FFmpegPath, j.path, j.posSec, j.width)
cancel()
if err != nil { // seek past EOF / corrupt → skip silently
if err != nil { // seek past EOF / corrupt → skip
mu.Lock()
failed++
if sampleErr == "" {
sampleErr = err.Error()
}
mu.Unlock()
continue
}
@ -120,6 +125,9 @@ func PrewarmSidecars(ctx context.Context, cache *LibraryCache, opts PrewarmOptio
if err != nil {
mu.Lock()
failed += len(todo)
if sampleErr == "" {
sampleErr = err.Error()
}
mu.Unlock()
continue
}
@ -174,7 +182,11 @@ func PrewarmSidecars(ctx context.Context, cache *LibraryCache, opts PrewarmOptio
wg.Wait()
if subCached > 0 || thumbCached > 0 || failed > 0 {
log.Printf("[prewarm] %d subtitles, %d thumbnails cached, %d failed", subCached, thumbCached, failed)
if failed > 0 && sampleErr != "" {
log.Printf("[prewarm] %d subtitles, %d thumbnails cached, %d failed (e.g. %s)", subCached, thumbCached, failed, sampleErr)
} else {
log.Printf("[prewarm] %d subtitles, %d thumbnails cached, %d failed", subCached, thumbCached, failed)
}
}
}