3.7 KiB
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}. AbortSignalpropagated through every call into the runner; aborts surface asVIDEO_THUMBNAILS.ABORTED.
Usage: Basic
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
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 ThumbnailGeneratorconstructor(config: ThumbnailConfigInput, runner?: FfmpegRunner)generate(inputPath, options?, signal?): Promise<ThumbnailResult>generateMultiple(inputPath, timestamps, signal?): Promise<ThumbnailResult[]>extractSlidingWindow(inputPath, count, durationSeconds, signal?): Promise<ThumbnailResult[]>getMetadata(inputPath, signal?): Promise<VideoMetadata>isAvailable(signal?): Promise<boolean>
buildEvenlySpacedTimestamps(count, durationSeconds): string[]— utility re-used byextractSlidingWindow.
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.