3.7 KiB
runner
(no submodules)
Purpose
The runner module defines the FfmpegRunner interface plus a handful of pure helpers used by
every implementation: buildScaleFilter, buildQualityArgs, and classifyRunnerError.
Centralising filter construction means the generator classes stay focused on orchestration
while every implementation (the bundled FluentFfmpegRunner and any future native or browser
variant) shares the exact same algorithmic shape. classifyRunnerError is the single point
where an AbortSignal distinguishes an ABORTED failure from a real ffmpeg crash.
Features
FfmpegRunnerinterface — four methods (isAvailable,extract,tile,probe), each taking an optionalAbortSignal.- Strong typed payloads:
ExtractInput,TileInput,ProbeResult. buildScaleFilter— encodes thecover | contain | fillfit semantics into ffmpeg filter-graph syntax once and only once.buildQualityArgs— translates the 1–100 quality scale into ffmpeg's inverted 1–31-q:vscale (PNG returns an empty arg list).classifyRunnerError— single guarded helper that turns any raw ffmpeg failure into a typedVideoThumbnailsError, distinguishingABORTEDfromEXTRACT_FAILED/PROBE_FAILEDbased on the suppliedAbortSignal.
Usage: Basic
import { buildScaleFilter, buildQualityArgs } from "@suaveplan/video-thumbnails/runner";
buildScaleFilter(320, 180, "cover");
// "scale=320:180:force_original_aspect_ratio=increase,crop=320:180"
buildQualityArgs("jpg", 80);
// ["-q:v", "7"]
Usage: Advanced
import {
classifyRunnerError,
type FfmpegRunner,
} from "@suaveplan/video-thumbnails/runner";
import { VIDEO_THUMBNAILS_CODES } from "@suaveplan/video-thumbnails/error-codes";
class CustomRunner implements FfmpegRunner {
async extract(input, signal) {
try {
await runViaCustomTransport(input, signal);
} catch (error) {
throw classifyRunnerError(
error,
signal,
VIDEO_THUMBNAILS_CODES.EXTRACT_FAILED,
"videoThumbnails.errors.extractFailed",
);
}
}
// ... isAvailable, tile, probe
}
API Reference
FfmpegRunner—{ isAvailable(signal?), extract(input, signal?), tile(input, signal?), probe(inputPath, signal?) }.ExtractInput—{ inputPath, outputPath, timestamp, width, height, fit, format, quality }.TileInput—{ inputPath, outputPath, columns, rows, thumbnailWidth, thumbnailHeight, interval, format }.ProbeResult—{ duration, width, height, codec, fps, bitrate }.buildScaleFilter(width, height, fit): string.buildQualityArgs(format, quality): readonly string[].classifyRunnerError(cause, signal, failureCode, failureKey): VideoThumbnailsError— whensignal.aborted, returnsABORTED; otherwise the suppliedfailureCode.
Implementation Notes
Quality mapping uses ffmpeg's inverted scale where 1 is best and 31 is worst — the
inverse arithmetic (31 - quality/100 × 30) keeps the round-trip stable for the canonical
80% quality (returning -q:v 7, the most commonly cited "good MJPEG" value). The fit
strategies are deliberately limited to ffmpeg-native filter chains so no second-pass image
processing is needed; consumers wanting more exotic compositing (gravity, focal point) can
implement a custom FfmpegRunner and reuse buildQualityArgs while ignoring
buildScaleFilter. classifyRunnerError reads signal.aborted lazily and only after the
underlying call has thrown — this avoids a race where the signal aborts after the work
already finished successfully.