# OpenSpec Change: enterprise-video-thumbnails - **Change ID**: `enterprise-video-thumbnails` - **Package**: `@suaveplan/video-thumbnails` - **Category**: `media` / **Plugin**: `video` - **Target path**: `packages/media/video/video-thumbnails` - **Status**: PROPOSED - **Date**: 2026-05-25 - **Design**: ./design.md - **Spec delta**: ./specs/video-thumbnails/spec.md - **Upstream**: [`../suaveplan/packages/universal/video-thumbnails`](/mnt/s/web/suaveplan/packages/universal/video-thumbnails) - **Enterprise audit**: [`../suaveplan/enterprise/video-thumbnails.md`](/mnt/s/web/suaveplan/enterprise/video-thumbnails.md) ## Summary Migrate `@suaveplan/video-thumbnails` from main into the genesis monorepo at enterprise quality. The package generates video thumbnails (single-frame extraction at arbitrary timestamps, batched time-based extracts, sliding-window sampling) and sprite sheets (NxM grid via ffmpeg `tile` filter) over JPEG/PNG/WebP outputs, on top of an injectable `FfmpegRunner` and the bundled `@ffmpeg-installer/ffmpeg` binary surfaced through `fluent-ffmpeg`. The migration adds OTel histograms/counters, structured `VideoThumbnailsError` (extends `SuaveplanError`) codes via `defineCodes`, Zod-validated option boundaries, `AbortSignal` propagation, a sliding-window extractor that upstream lacked, and 100/100/100/100 coverage using real fixture videos generated on-the-fly with ffmpeg (no mocks). ## Parity Checklist | Capability | Upstream | Migrated | Notes | |---|---|---|---| | Single-frame extract `ThumbnailGenerator.generate` | ✅ | ✅ | Same `timestamp`, `fit`, `outputDir`, `format`, `quality` semantics. | | Multi-timestamp extract `generateMultiple` | ✅ | ✅ | `AbortSignal`-aware. | | `getMetadata` via ffprobe | ✅ | ✅ | Through `FfmpegRunner.probe`. | | `isAvailable()` | ✅ | ✅ | Through `runner.isAvailable()`. | | Sprite-sheet `SpriteSheetGenerator.generate` | ✅ | ✅ | Same N×M tile algorithm; auto-detect duration. | | `CommandExecutor` injection | ✅ | ⬆️ | Replaced with the stronger `FfmpegRunner` interface (typed `extract`, `tile`, `probe`, `isAvailable`); default = `FluentFfmpegRunner`. | | Error codes `THUMBNAIL_*` | ✅ | ⬆️ | Re-issued under `VIDEO_THUMBNAILS.*` namespace via `defineCodes`. Added: `RUNNER_UNAVAILABLE`, `INVALID_TIMESTAMP`, `INVALID_OPTIONS`, `ABORTED`. | | Sliding-window extraction | 🚫 | ⬆️ | New `extractSlidingWindow(count, durationSeconds)` evenly samples N frames. | | OTel counters + histograms | 🚫 | ⬆️ | `video_thumbnails.extract.duration_ms`, `extract.total{outcome,format}`, `sprite_sheet.duration_ms`, `sprite_sheet.frames`. | | Zod option validation | 🚫 | ⬆️ | All public surfaces validate at the boundary. | | `AbortSignal` propagation | 🚫 | ⬆️ | Threaded through `FfmpegRunner` (kills the child process). | | Output jpeg / png / webp | partial | ✅ | All three exercised against real ffmpeg + fixtures. | | 100/100/100/100 coverage | 🚫 | ✅ | Vitest istanbul, no mocks, real fixtures. | ## Current-state audit (genesis) Package does not exist on `genesis`. Status: `–` in `STATUS.md`. Direct upstream consumers in genesis: none. The `video` plugin is added to the `media` super-category in `scripts/folder-reorg/assignment.yml` as part of this change (the closed-vocabulary supercategory list is unchanged). ## Dependencies - Workspace: `@suaveplan/error` - External (via catalog `audio`): `fluent-ffmpeg`, `@ffmpeg-installer/ffmpeg`, `@types/fluent-ffmpeg` - External (other catalogs): `tslib` (`build`), `zod` (`forms`), `@opentelemetry/api` (`observability`) - Dev: `@suaveplan/testing` (`workspace:^`), `vitest` + `@vitest/coverage-istanbul` (`testing`) ## Impact - Adds package `packages/media/video/video-thumbnails`. - Adds spec capability `video-thumbnails`. - Adds `video` plugin to the `media` super-category in `scripts/folder-reorg/assignment.yml`. ### Affected dependents None — fresh genesis migration, no existing consumers.