# `ThumbnailGenerator` _(no submodules)_ ## Purpose `ThumbnailGenerator` is the primary class consumers reach for to extract still frames from a video file. It accepts a Zod-validated `ThumbnailConfig` (output directory, dimensions, format, quality) and delegates the actual ffmpeg invocation to an injectable `FfmpegRunner`. The default runner — `FluentFfmpegRunner` — bundles ffmpeg/ffprobe binaries so the package runs unmodified on any host. Every public method honours an `AbortSignal`, validates input at the boundary, and records OTel histograms + counters keyed by `format`, `fit`, and `outcome`. ## Features - Single-frame extract at any `HH:MM:SS(.mmm)?` timestamp (`generate`). - Multi-timestamp batch with abort-aware iteration (`generateMultiple`). - Sliding-window extraction — N evenly-spaced frames across a duration (`extractSlidingWindow`). - ffprobe-backed metadata (`getMetadata`). - Runner-availability probe (`isAvailable`). - Zod boundary validation with code-specific failures (`INVALID_DIMENSIONS`, `INVALID_TIMESTAMP`, `FORMAT_UNSUPPORTED`, `INVALID_OPTIONS`). - OTel: `video_thumbnails.extract.duration_ms{format,fit,outcome}` + `video_thumbnails.extract.total{outcome,format}`. - `AbortSignal` propagated through every call into the runner; aborts surface as `VIDEO_THUMBNAILS.ABORTED`. ## Usage: Basic ```ts import { ThumbnailGenerator } from "@suaveplan/video-thumbnails/thumbnail-generator"; const generator = new ThumbnailGenerator({ outputDir: "/tmp/thumbs" }); const result = await generator.generate("/data/clip.mp4"); console.log(result.outputPath, result.sizeBytes); ``` ## Usage: Advanced ```ts import { ThumbnailGenerator } from "@suaveplan/video-thumbnails/thumbnail-generator"; import { FluentFfmpegRunner } from "@suaveplan/video-thumbnails/runner"; const generator = new ThumbnailGenerator( { outputDir: "/tmp/thumbs", width: 640, height: 360, format: "webp", quality: 90, }, new FluentFfmpegRunner({ ffmpegPath: "/opt/ffmpeg/bin/ffmpeg" }), ); const controller = new AbortController(); const metadata = await generator.getMetadata("/data/clip.mp4"); const frames = await generator.extractSlidingWindow( "/data/clip.mp4", 8, metadata.duration, controller.signal, ); ``` ## API Reference - `class ThumbnailGenerator` - `constructor(config: ThumbnailConfigInput, runner?: FfmpegRunner)` - `generate(inputPath, options?, signal?): Promise` - `generateMultiple(inputPath, timestamps, signal?): Promise` - `extractSlidingWindow(inputPath, count, durationSeconds, signal?): Promise` - `getMetadata(inputPath, signal?): Promise` - `isAvailable(signal?): Promise` - `buildEvenlySpacedTimestamps(count, durationSeconds): string[]` — utility re-used by `extractSlidingWindow`. Throws `VideoThumbnailsError` for every failure mode (`INVALID_*`, `FORMAT_UNSUPPORTED`, `EXTRACT_FAILED`, `PROBE_FAILED`, `VIDEO_INVALID`, `ABORTED`). ## Implementation Notes `extractSlidingWindow` divides the duration into `count + 1` slots and picks the boundary at each interior slot — i.e. for `count = 4, duration = 5s` the timestamps are `1.0s`, `2.0s`, `3.0s`, `4.0s`. This avoids the degenerate case of putting a frame at `0.0` (often a black intro frame) or at the exact duration (frequently past the last decodable frame). The generator never writes files itself — `stat()` reads back the file ffmpeg produced so the returned `sizeBytes` reflects the actual on-disk size, not the requested dimensions multiplied by an assumed bit depth. OTel records before throwing on failure so dashboards distinguish counts from timings.