112 lines
7.4 KiB
Markdown
112 lines
7.4 KiB
Markdown
# Change: enterprise-video
|
|
|
|
- **Package**: `@suaveplan/video`
|
|
- **Status**: PROPOSED
|
|
- **Design**: ./design.md
|
|
- **Spec delta**: ./specs/video/spec.md
|
|
- **Layout**: `packages/media/video/video/`
|
|
- **Tier**: universal (no Node-only / browser-only APIs in `src/`)
|
|
- **Genesis deps**: `@suaveplan/error` shipped, `@suaveplan/events` shipped
|
|
|
|
## Why
|
|
|
|
`@suaveplan/video` is the canonical universal-tier video-metadata and
|
|
transform-planning capability on the suaveplan platform. It underpins the
|
|
pending `@suaveplan/video-thumbnails` (ffmpeg-backed thumbnail renderer) and
|
|
`@suaveplan/react-video` (browser playback) siblings, both of which read
|
|
`VideoMetadata`/`VideoTransformPipeline`/`VideoTransformResult` from this
|
|
package. Upstream ships 1517 LOC covering magic-byte format detection,
|
|
metadata extraction with optional caller-supplied probe data, resolution
|
|
presets, an evented `VideoProcessor` + fluent `VideoPipelineBuilder`, and a
|
|
15-factory typed error registry with HTTP/severity/retry metadata.
|
|
|
|
Genesis must own this capability before the siblings can land. The legacy
|
|
package uses the deprecated `BaseError` / `ErrorCodes.registerMany` registry
|
|
and the Node-style `EventEmitter<[T]>` array-tuple shape — genesis migrates
|
|
these to `SuaveplanError` + `defineCodes("VIDEO", …)` + `EventBus<TMap>` while
|
|
preserving the full public surface and adding the enterprise quality bar the
|
|
audit calls for: Zod boundary validation, OTel counters + histograms + spans,
|
|
`AbortSignal` propagation, schema-validated config, deterministic clocks,
|
|
100/100/100/100 coverage, and tier-pure imports.
|
|
|
|
## What changes
|
|
|
|
1. New package `packages/media/video/video/` at `@suaveplan/video@0.1.0`.
|
|
2. `assignment.yml` `__categories__:` row updated: `media` now includes the
|
|
`video` plugin folder (alongside `audio`/`image`/`fonts`/etc.).
|
|
3. Full upstream public surface preserved — every type, every constant, every
|
|
error factory, every method on `VideoProcessor` / `VideoPipelineBuilder`.
|
|
4. `VideoError` migrates from `BaseError<Code, Context>` to a
|
|
`SuaveplanError<VideoCode>` subclass that retains a typed `context` field;
|
|
codes move from `ErrorCodes.registerMany` to `defineCodes("VIDEO", […])`.
|
|
5. `VideoProcessor` migrates from Node-style `EventEmitter<VideoEventMap>`
|
|
(array-tuple payloads) to composing `EventBus<VideoEventMap>` (single
|
|
payload per topic, matching `@suaveplan/image`'s convention) and exposes
|
|
`on` / `once` / `off`.
|
|
6. Enterprise enhancements: Zod-validated config + pipeline boundaries, OTel
|
|
meter `@suaveplan/video` exposing 2 counters + 2 histograms + named spans,
|
|
`AbortSignal` on every public entrypoint that can do work, injectable
|
|
`Clock`.
|
|
7. Co-located `.md` docs per implementation file (≥200 words, 6 required
|
|
sections), package-root `README.md` with a Modules index.
|
|
8. Subpath exports per feature module so consumers can tree-shake.
|
|
|
|
## Parity Checklist
|
|
|
|
| Capability | Upstream | Migrated | Notes |
|
|
|---|---|---|---|
|
|
| `VideoFormat` / `VideoCodec` / `AudioCodec` / `ResolutionPreset` enums | x | x | Re-expressed as Zod enums + inferred types. |
|
|
| `VideoDimensions` / `VideoMetadata` / `TrimRange` / `VideoResizeOptions` | x | x | Mirrored with Zod schemas (`VideoMetadataSchema` etc.). |
|
|
| `VideoTransformOp` discriminated union (9 variants) | x | x | Migrated to `z.discriminatedUnion` for boundary validation. |
|
|
| `VideoTransformPipeline` / `VideoTransformResult` | x | x | Schemas + inferred types. |
|
|
| `VideoEventMap` | x | up | Re-shaped from `[T]` tuples to single-payload `T` to match `EventBus<TMap>`. Semantic `metadata.extracted` / `transform.planned` / `pipeline.built` keys preserved. |
|
|
| `detectVideoFormat` (MP4/MOV/MKV/AVI/FLV/OGG magic bytes) | x | x | Byte-for-byte identical sniffer. |
|
|
| `calculateVideoResize` (preset + maintainAspectRatio + even-dimensions) | x | x | Identical algorithm; Zod-validated input. |
|
|
| `validateTrimRange` | x | x | Identical. |
|
|
| `formatDuration` / `parseDuration` round-trip | x | x | Identical. |
|
|
| `videoFormatToExtension` | x | x | Identical. |
|
|
| `RESOLUTION_PRESETS` (6 presets) | x | x | Identical. |
|
|
| `VIDEO_MIME_TYPES` (8 formats) | x | x | Identical. |
|
|
| `VideoError` typed error | x | up | Migrated to `SuaveplanError<VideoCode>`; codes namespaced via `defineCodes("VIDEO", …)`. |
|
|
| 15 error-factory functions | x | x | All preserved: `unknownVideoFormat`, `invalidVideoDimensions`, `invalidTrimRange`, `videoMetadataParseFailed`, `invalidVideoTransform`, `durationExceeded`, `unsupportedCodec`, `videoNotFound`, `videoDecodeFailed`, `videoEncodeFailed`, `videoFormatUnsupported`, `videoTooLarge`, `videoProcessingFailed`, `videoCodecUnsupported`, `videoTimeout`. |
|
|
| `VIDEO_ERROR_METADATA` HTTP/severity/retry table | x | x | Preserved as a typed const for problem+json / retry consumers. |
|
|
| `registerVideoErrorCodes` callable | x | up | Kept as a callable; genesis `defineCodes` is module-local so the function is a no-op alias returning the metadata table — same name, same shape, callers keep compiling. |
|
|
| `VideoProcessor.extractMetadata` | x | up | Adds `AbortSignal`, OTel counter + histogram + span, Zod-validated config, deterministic clock. |
|
|
| `VideoProcessor.planTransform` (9 op types) | x | up | Adds `AbortSignal`, Zod-validated pipeline, OTel counter + histogram + span. |
|
|
| `VideoProcessor.createPipeline()` | x | x | Identical. |
|
|
| `VideoPipelineBuilder` (resize/trim/rotate/flip/speed/mute/toFormat/codec/bitrate/build/apply) | x | x | Identical fluent surface. |
|
|
| `VideoProcessorConfig` (`maxDuration`, `defaultFormat`) | x | up | Zod-validated; configs that fail validation throw `VideoError(VIDEO.CONFIG_INVALID)`. |
|
|
| `pipeline.built` event | x | up | Now actually emitted from `VideoPipelineBuilder.build()` (upstream declared but never fired). |
|
|
| Side-effect `import "./error-codes.js"` registration | x | down | Genesis `defineCodes` is module-local and side-effect-free; equivalent metadata is exposed via `VIDEO_ERROR_METADATA` re-export and `registerVideoErrorCodes()` is retained as a no-op alias. |
|
|
|
|
The single `down` row (the side-effect import) is structurally replaced by
|
|
the `defineCodes` model — same callers, same outcome, no functional drop.
|
|
|
|
## Impact
|
|
|
|
### Affected dependents
|
|
|
|
`bunx turbo ls --filter=...@suaveplan/video` — package is brand-new on genesis,
|
|
so no genesis consumers exist yet. The future `@suaveplan/video-thumbnails`
|
|
(ffmpeg renderer) and `@suaveplan/react-video` (browser playback) packages
|
|
will consume this; both are still `-` (pending) in `STATUS.md` and will be
|
|
migrated in follow-up changes against this stable interface.
|
|
|
|
No dependents (verified by `bunx turbo ls --filter=...@suaveplan/video`).
|
|
|
|
## Migration
|
|
|
|
For consumers porting from the upstream `@suaveplan/video` shape:
|
|
|
|
- **VideoError**: same import path, same `err.code` string values, same
|
|
`err.message`, `err.cause`, `err.context`. Base class moved from
|
|
`BaseError` to `SuaveplanError`; `instanceof VideoError` keeps working.
|
|
- **Event listeners**: upstream listeners on Node `EventEmitter` received the
|
|
payload as the first element of a tuple (`(meta) => …` since `args[0]`
|
|
destructures the tuple); under `EventBus` the listener receives the payload
|
|
directly. Idiomatic call sites are unchanged.
|
|
- **`registerVideoErrorCodes()`**: still callable, still side-effect-safe;
|
|
registration is now driven by `defineCodes` at module-load time.
|
|
- Search command for upstream consumers (none on genesis today):
|
|
`grep -rln "@suaveplan/video" packages/ | grep -v "/dist/\|/coverage/"`.
|