# OpenSpec Change: enterprise-audio-codec-ffmpeg-native - **Change ID**: enterprise-audio-codec-ffmpeg-native - **Package**: `@suaveplan/audio-codec-ffmpeg-native` - **Category**: server - **Target path**: `packages/media/audio/audio-codec-ffmpeg-native/` - **Status**: PROPOSED - **Date**: 2026-05-15 - **Design**: ./design.md - **Spec delta**: ./specs/audio-codec-ffmpeg-native/spec.md --- ## Summary Deliver `@suaveplan/audio-codec-ffmpeg-native` as a server-tier concrete codec plugin that implements the `AudioCodec` contract from `@suaveplan/audio-codecs` by shelling out to the system `ffmpeg` binary via the `fluent-ffmpeg` CLI wrapper. The package bundles a static binary via `@ffmpeg-installer/ffmpeg` (declared as `optionalDependencies` so consumers may override) and streams audio through ffmpeg's stdin/stdout to support decode, encode, and true chunked streaming for wav, mp3, ogg, opus, flac, aac/m4a, webm/matroska, and wma — without ever buffering the full file in memory. Concurrency is bounded by an in-package semaphore, every spawn carries an `AbortSignal` that escalates `SIGTERM` → `SIGKILL`, and the package exposes a one-line `register(registry)` helper plus `@suaveplan/streams`-based `TransformStream` adapters for pipeline composition. --- ## Current-state audit (genesis) The package does not exist on genesis yet — this is a fresh build. No prior source, no test results, no coverage data. The package is genesis-original (no upstream parity checklist required); the `AudioCodec` contract it plugs into is defined by the in-flight `enterprise-audio-codecs` change. --- ## Available workspace deps on genesis | Package | Role | |---|---| | `@suaveplan/audio-codecs` (`workspace:^`) | `AudioCodec`, `CodecRegistry`, `DecodeResult`, `StreamingDecoder`, `StreamingEncoder` contracts | | `@suaveplan/audio-core` (`workspace:^`) | `AudioPage`, `AudioFormat` aliases | | `@suaveplan/streams` (`workspace:^`) | `TransformStream`, `ReadableStream`, `WritableStream` adapters with backpressure | | `@suaveplan/error` (`workspace:^`) | `SuaveplanError` base class, `defineCodes`, structured error registry | | `@suaveplan/types` (`workspace:^`) | `Clock`, `LoggerLike` injectables | | `fluent-ffmpeg` (`catalog:audio`) | CLI wrapper used to compose ffmpeg invocations | | `@ffmpeg-installer/ffmpeg` (`catalog:audio`, `optionalDependencies`) | Bundled static ffmpeg binary, default `binaryPath` | | `zod` (`catalog:forms`) | Public options validation | | `@opentelemetry/api` (`catalog:observability`) | Spans, counters, histograms, gauges | | `tslib` (`catalog:build`) | TS runtime helpers | Dev deps: `vitest`, `@vitest/coverage-istanbul`, `fast-check`, `mitata` (all `catalog:testing`), `@suaveplan/testing` (`workspace:^`). Note: catalog entries for `fluent-ffmpeg` and `@ffmpeg-installer/ffmpeg` must be added to root `package.json` `catalog.audio` if not already present — this is tracked in `tasks.md` step 1. --- ## Public API ### Core types ```ts import type { AudioCodec, CodecRegistry, DecodeResult, EncodeOpts, StreamingDecoder, StreamingEncoder, } from "@suaveplan/audio-codecs"; import type { Clock, LoggerLike } from "@suaveplan/types"; export interface FfmpegNativeCodecOptions { binaryPath?: string; binaryProvider?: () => string; concurrencyLimit?: number; timeoutMs?: number; logger?: LoggerLike; clock?: Clock; } export class FfmpegNativeCodec implements AudioCodec { readonly name: string; readonly extensions: readonly string[]; readonly mimeTypes: readonly string[]; constructor(options?: FfmpegNativeCodecOptions); canDecode(bytes: Uint8Array): boolean; decode(bytes: Uint8Array, opts?: { signal?: AbortSignal }): Promise; encode( pcm: Float32Array[], opts: EncodeOpts & { signal?: AbortSignal }, ): Promise; createDecoder(opts?: { format: string; signal?: AbortSignal }): StreamingDecoder; createEncoder(opts: EncodeOpts & { signal?: AbortSignal }): StreamingEncoder; } export function register(registry: CodecRegistry, options?: FfmpegNativeCodecOptions): void; ``` ### Error codes ```ts export type AudioCodecFfmpegNativeErrorCode = | "AUDIO_CODEC_FFMPEG_NATIVE.BINARY_MISSING" | "AUDIO_CODEC_FFMPEG_NATIVE.SPAWN_FAILED" | "AUDIO_CODEC_FFMPEG_NATIVE.DECODE_FAILED" | "AUDIO_CODEC_FFMPEG_NATIVE.ENCODE_FAILED" | "AUDIO_CODEC_FFMPEG_NATIVE.TIMEOUT" | "AUDIO_CODEC_FFMPEG_NATIVE.ABORTED" | "AUDIO_CODEC_FFMPEG_NATIVE.UNSUPPORTED_CODEC" | "AUDIO_CODEC_FFMPEG_NATIVE.INVALID_PCM"; export class AudioCodecFfmpegNativeError extends SuaveplanError {} export const AUDIO_CODEC_FFMPEG_NATIVE_ERROR_METADATA: Readonly< Record >; export function registerAudioCodecFfmpegNativeErrorCodes(): void; ``` ### Zod schemas (sibling `*.schemas.ts`) ```ts export const FfmpegNativeCodecOptionsSchema: z.ZodType; export const EncodeOptsSchema: z.ZodType; ``` --- ## Module layout Five-file pattern per feature module (`feature.ts`, `feature.test.ts`, `feature.types.ts`, `feature.md`, `index.ts`); barrels use explicit named re-exports (rule 10 / §6a). Every test file imports runner primitives from `@suaveplan/testing/runner` (rule 11 / §7). ```text packages/media/audio/audio-codec-ffmpeg-native/ package.json tsconfig.json vitest.config.ts README.md src/ index.ts error-codes.ts error-codes.md error-codes.test.ts constants/ index.ts codec/ codec.ts codec.md codec.test.ts codec.types.ts codec.schemas.ts index.ts binary/ binary.ts binary.md binary.test.ts binary.types.ts index.ts runner/ runner.ts runner.md runner.test.ts runner.types.ts index.ts semaphore/ semaphore.ts semaphore.md semaphore.test.ts semaphore.types.ts index.ts decode/ decode.ts decode.md decode.test.ts decode.types.ts index.ts encode/ encode.ts encode.md encode.test.ts encode.types.ts index.ts stream/ stream-decoder.ts stream-decoder.md stream-decoder.test.ts stream-decoder.types.ts stream-encoder.ts stream-encoder.md stream-encoder.test.ts stream-encoder.types.ts index.ts abort/ abort.ts abort.md abort.test.ts abort.types.ts index.ts otel/ otel.ts otel.md otel.test.ts otel.types.ts index.ts register/ register.ts register.md register.test.ts register.types.ts index.ts __benchmarks__/ codec.bench.ts tests/ purity.test.ts integration.test.ts exports.test.ts ``` --- ## Impact ### Affected dependents Run `bunx turbo ls --filter=...@suaveplan/audio-codec-ffmpeg-native` to enumerate. Package does not exist yet: - **No dependents — fresh package.** No downstream consumers exist on genesis at proposal time; future consumers (`@suaveplan/audio-codecs` default-registry server bundle, application-side `register(registry, ...)` call sites) will pick this up by direct workspace dep when published. Breaking changes: none — fresh build. Prerequisites: `@suaveplan/audio-codecs`, `@suaveplan/audio-core`, `@suaveplan/streams`, `@suaveplan/error`, `@suaveplan/types` published (or workspace-resolvable) on genesis; catalog entries for `fluent-ffmpeg` and `@ffmpeg-installer/ffmpeg` declared in the root `package.json` `catalog.audio` block.