# 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` 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` to a `SuaveplanError` subclass that retains a typed `context` field; codes move from `ErrorCodes.registerMany` to `defineCodes("VIDEO", […])`. 5. `VideoProcessor` migrates from Node-style `EventEmitter` (array-tuple payloads) to composing `EventBus` (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`. 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`; 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/"`.