Files
suaveplan/openspec/archive/2026-05-25-enterprise-video/proposal.md
2026-05-27 23:29:53 +01:00

7.4 KiB

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/".