first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
# `SpriteSheetGenerator`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`SpriteSheetGenerator` renders an N×M grid of thumbnails into a single image using ffmpeg's
`tile` filter. A sprite sheet is the canonical artefact for video scrubbers and timeline
previews — one HTTP fetch, one decode, then arbitrary in-page hovers slide a `background-
position`. The generator delegates the actual ffmpeg invocation to an injectable
`FfmpegRunner` (default: `FluentFfmpegRunner` with bundled ffmpeg/ffprobe binaries) and
auto-detects video duration via the runner's `probe` when the caller does not pass one
explicitly.
## Features
- Configurable grid: `columns`, `rows`, `thumbnailWidth`, `thumbnailHeight`, `interval`,
`format`.
- Auto-detection of video duration via `runner.probe` when caller omits `duration`.
- Frame count is capped at `columns × rows` and at `floor(duration / interval)` — whichever
is smaller — so short videos do not produce padded grids.
- Zod boundary validation with code-specific failures (`INVALID_DIMENSIONS`,
`FORMAT_UNSUPPORTED`, `INVALID_OPTIONS`).
- OTel: `video_thumbnails.sprite_sheet.duration_ms{outcome,format}` plus
`video_thumbnails.sprite_sheet.frames{format}`.
- `AbortSignal` propagated through every call; aborts surface as `VIDEO_THUMBNAILS.ABORTED`.
## Usage: Basic
```ts
import { SpriteSheetGenerator } from "@suaveplan/video-thumbnails/sprite-sheet";
const generator = new SpriteSheetGenerator();
const result = await generator.generate("/data/clip.mp4", "/tmp/sprite.jpg");
console.log(result.frameCount, result.columns, result.rows);
```
## Usage: Advanced
```ts
import { SpriteSheetGenerator } from "@suaveplan/video-thumbnails/sprite-sheet";
const generator = new SpriteSheetGenerator({
columns: 8,
rows: 6,
thumbnailWidth: 192,
thumbnailHeight: 108,
interval: 2,
format: "jpg",
});
const controller = new AbortController();
const result = await generator.generate(
"/data/long-movie.mp4",
"/tmp/movie-sprite.jpg",
7200,
controller.signal,
);
// result.width === 8 * 192, result.height === 6 * 108, result.frameCount === 48
```
## API Reference
- `class SpriteSheetGenerator`
- `constructor(config?: SpriteSheetConfigInput, runner?: FfmpegRunner)`
- `generate(inputPath, outputPath, duration?, signal?): Promise<SpriteSheetResult>`
Throws `VideoThumbnailsError` on `INVALID_DIMENSIONS`, `FORMAT_UNSUPPORTED`,
`INVALID_OPTIONS`, `VIDEO_INVALID`, `EXTRACT_FAILED`, `PROBE_FAILED`, or `ABORTED`.
## Implementation Notes
`rows` is recomputed against the actual `frameCount` (rather than blindly using the configured
upper bound) so a 5-second clip with a 10-frame grid does not produce four trailing black
cells. The generator never directly invokes ffmpeg — every call routes through the supplied
runner so consumers can swap in a hardware-accelerated or hosted variant without touching
this class. OTel records before throwing on failure so dashboards distinguish counts from
timings even on errors. `duration` is validated when passed explicitly: non-finite or
non-positive values are rejected with `VIDEO_INVALID`, mirroring the probe path.