# Design: enterprise-video ## Architecture `@suaveplan/video` is a universal-tier metadata + transform-planning library. It has no Node-only or browser-only imports in `src/` — every public surface operates on `Uint8Array` inputs and plain JS objects so the same module ships to Bun, Node, and browsers. Heavy codec work (real transcoding, frame extraction, thumbnail render) is delegated to sibling packages (`@suaveplan/video-thumbnails` ffmpeg renderer, `@suaveplan/react-video` HTMLVideoElement-backed playback) that consume this package's planning surface. ## Module layout ``` packages/media/video/video/ package.json tsconfig.lib.json vite.config.ts vitest.config.ts README.md tests/setup/polyfills.ts src/ index.ts # explicit named re-exports error-codes/{error-codes.ts,error-codes.md,error-codes.test.ts,index.ts} errors/{errors.ts,errors.md,errors.test.ts,index.ts} types/video-types/{video-types.ts,video-types.md,video-types.test.ts,index.ts} detection/{detection.ts,detection.md,detection.test.ts,index.ts} resize/{resize.ts,resize.md,resize.test.ts,index.ts} trim/{trim.ts,trim.md,trim.test.ts,index.ts} duration/{duration.ts,duration.md,duration.test.ts,index.ts} format/{format.ts,format.md,format.test.ts,index.ts} instruments/{instruments.ts,instruments.md,index.ts} processor/{processor.ts,processor.md,processor.test.ts,processor.spec-conformance.test.ts,index.ts} pipeline-builder/{pipeline-builder.ts,pipeline-builder.md,pipeline-builder.test.ts,index.ts} ``` ## Public surface (mirrors upstream) - Error layer: `VideoError`, 15 factories, `VIDEO_ERROR_METADATA`, `registerVideoErrorCodes`, `VideoCode` union, `VIDEO_CODES` registry. - Types layer: every upstream type plus Zod schemas. - Helpers: `detectVideoFormat`, `calculateVideoResize`, `validateTrimRange`, `formatDuration`, `parseDuration`, `videoFormatToExtension`. - Core: `VideoProcessor`, `VideoPipelineBuilder`. - Constants: `RESOLUTION_PRESETS`, `VIDEO_MIME_TYPES`. ## Genesis-specific changes 1. **Error base**: `SuaveplanError` (genesis foundation) rather than the upstream `BaseError`. Public discriminants (`err.code`, `err.message`, `err.cause`, `err.context`) are identical. 2. **Event bus**: composes `EventBus` where `TMap = VideoEventMap` and payloads are single values (no `[T]` tuples). The processor exposes `on`/`once`/`off` so call sites read the same. Genesis pattern matches `@suaveplan/image`. 3. **OTel**: meter `@suaveplan/video` with: - counter `video.metadata.extracted_total{outcome}` - histogram `video.metadata.extract_duration_ms{outcome}` - counter `video.transform.planned_total{outcome}` - histogram `video.transform.plan_duration_ms{outcome}` - tracer spans `video.metadata.extract` + `video.transform.plan`. 4. **Validation**: Zod schemas at every public boundary (`VideoProcessorConfigSchema`, `VideoTransformPipelineInputSchema`). Invalid input throws `VideoError(VIDEO.CONFIG_INVALID)` / `VideoError(VIDEO.INVALID_TRANSFORM)`. 5. **AbortSignal**: both `extractMetadata` and `planTransform` accept an optional `AbortSignal` and short-circuit with `VideoError(VIDEO.PROCESSING_FAILED, cause: AbortError)` when aborted. 6. **`pipeline.built` event**: declared upstream but never emitted; genesis fires it from `VideoPipelineBuilder.build()` whenever the builder was created via `VideoProcessor.createPipeline()` (so the builder has a bus reference). ## Algorithm notes ### `detectVideoFormat` Byte-for-byte port of the upstream sniffer (header must be ≥12 bytes): - bytes 4-7 = `ftyp` ASCII → MP4 family; sub-brand bytes 8-11 starting `qt` return `mov`, else `mp4`. - bytes 0-3 = `1A 45 DF A3` → `mkv` (Matroska EBML). - bytes 0-3 = `RIFF`, bytes 8-11 = `AVI ` → `avi`. - bytes 0-2 = `FLV` → `flv`. - bytes 0-3 = `OggS` → `ogg`. - else `null`. ### `calculateVideoResize` Identical to upstream: - if `preset` set: use preset dimensions verbatim. - else if `maintainAspectRatio !== false` and exactly one dimension set: compute the other from `original.width / original.height`. - else: use options as-is (or `original` when both omitted). - finally: round both dims up to the next even number (`x % 2 === 0 ? x : x+1`) because most codecs require even chroma sub-sampling alignment. ### `validateTrimRange` `start ≥ 0 && end > start && end ≤ duration`. ### `formatDuration` / `parseDuration` `HH:MM:SS` ↔ seconds. `parseDuration` accepts `SS`, `MM:SS`, or `HH:MM:SS` and silently returns `0` for empty string. ### `VideoProcessor.extractMetadata` 1. AbortSignal pre-check. 2. Start OTel span + timer. 3. `detectVideoFormat(data)`; if `null` and no `knownMetadata.format`, throw `unknownVideoFormat()` (outcome=`unknown_format`). 4. Compose `VideoMetadata` from `knownMetadata` + detected format, defaulting numeric fields to 0 and `hasAudio` to `true`. 5. If `metadata.duration > maxDuration`, throw `durationExceeded(...)` (outcome=`duration_exceeded`). 6. Emit `metadata.extracted`. 7. On any non-`VideoError` exception: wrap in `videoMetadataParseFailed(cause)`. 8. Finally: record counter + histogram + end span. ### `VideoProcessor.planTransform` 1. AbortSignal pre-check. 2. Start OTel span + timer. 3. Validate pipeline via `VideoTransformPipelineInputSchema`. 4. Walk operations in order, mutating local `(width, height, duration, format, hasAudio)`: - `resize` → `calculateVideoResize`. - `trim` → `validateTrimRange` else `invalidTrimRange`; new duration. - `rotate` → require `degrees % 90 === 0` else `invalidVideoTransform`; swap dims when `degrees % 180 !== 0`. - `flip` → no-op for planning. - `speed` → require `factor > 0` else `invalidVideoTransform`; `duration /= factor`. - `mute` → `hasAudio = false`. - `format` → set output format. - `codec` / `bitrate` → no-op for planning (informational only). 5. Emit `transform.planned`. 6. Return `VideoTransformResult`. ## Error code registry ```ts export const VIDEO_CODES = defineCodes("VIDEO", [ "UNKNOWN_FORMAT", "INVALID_DIMENSIONS", "INVALID_TRIM_RANGE", "METADATA_PARSE_FAILED", "INVALID_TRANSFORM", "DURATION_EXCEEDED", "UNSUPPORTED_CODEC", "NOT_FOUND", "DECODE_FAILED", "ENCODE_FAILED", "FORMAT_UNSUPPORTED", "TOO_LARGE", "PROCESSING_FAILED", "CODEC_UNSUPPORTED", "TIMEOUT", "CONFIG_INVALID", ] as const); ``` `VIDEO_ERROR_METADATA` is a separately-exported `Record` (httpStatus, severity, retryable, userMessageKey, category, description) — used by HTTP error mappers and retry policies. `registerVideoErrorCodes()` returns it (no-op for compatibility). ## File-size budget Every implementation file targets ≤250 lines (300 hard). The processor splits naturally into: - `processor/processor.ts` (orchestration ~200 lines) - `pipeline-builder/pipeline-builder.ts` (fluent builder ~110 lines) - `instruments/instruments.ts` (~60 lines) - helpers each ≤60 lines. ## Test strategy - **Helper tests** (detection, resize, trim, duration, format) — table-driven byte-pattern checks + boundary cases. - **Errors tests** — every factory exercised; `VideoError` instance/code/context/ cause; `VIDEO_ERROR_METADATA` table shape. - **Processor tests** — every branch of `extractMetadata` + `planTransform` (including each `VideoTransformOp` variant + abort + zod-config invalid). - **Pipeline builder tests** — every fluent method + `build()` + `apply()` + `pipeline.built` emission when bound to a processor. - **Spec conformance** — OTel counter/histogram/span observation + AbortSignal propagation + event emission end-to-end via in-test MeterProvider / TracerProvider. 100/100/100/100 istanbul coverage is mandatory.