Files
suaveplan/packages/media/video/video-thumbnails/src/runner/runner.md
2026-05-27 23:47:53 +01:00

3.7 KiB
Raw Blame History

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

  • FfmpegRunner interface — four methods (isAvailable, extract, tile, probe), each taking an optional AbortSignal.
  • Strong typed payloads: ExtractInput, TileInput, ProbeResult.
  • buildScaleFilter — encodes the cover | contain | fill fit semantics into ffmpeg filter-graph syntax once and only once.
  • buildQualityArgs — translates the 1–100 quality scale into ffmpeg's inverted 1–31 -q:v scale (PNG returns an empty arg list).
  • classifyRunnerError — single guarded helper that turns any raw ffmpeg failure into a typed VideoThumbnailsError, distinguishing ABORTED from EXTRACT_FAILED / PROBE_FAILED based on the supplied AbortSignal.

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 — when signal.aborted, returns ABORTED; otherwise the supplied failureCode.

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.