@catlabtech/webcvt-transcode 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,172 @@
1
+ import { Backend, FormatDescriptor, ConvertOptions, ConvertResult, BackendRegistry, WebcvtError } from '@catlabtech/webcvt-core';
2
+
3
+ /**
4
+ * TranscodeBackend — the WebCodecs-first audio transcode backend.
5
+ *
6
+ * `canHandle` is two-stage + cached: (1) the static {@link TRANSCODE_MATRIX}
7
+ * gate rejects off-matrix pairs in O(1) with no probing; (2) both-sided
8
+ * concrete codec probes (`probeAudioDecoder` for the input, `probeAudioCodec`
9
+ * for the output) confirm the runtime actually supports the pair — this is what
10
+ * makes Safari-audio and no-WebCodecs runtimes fall through to ffmpeg-wasm.
11
+ * Probe results are memoised per session.
12
+ *
13
+ * `convert` runs the four-phase pipeline demux → decode → encode → mux, enforces
14
+ * the input cap first, honours `options.signal`, and closes codecs on exit.
15
+ */
16
+
17
+ declare class TranscodeBackend implements Backend {
18
+ #private;
19
+ readonly name = "webcodecs-transcode";
20
+ readonly priority = 0;
21
+ canHandle(input: FormatDescriptor, output: FormatDescriptor): Promise<boolean>;
22
+ convert(input: Blob, output: FormatDescriptor, options: ConvertOptions): Promise<ConvertResult>;
23
+ }
24
+ /**
25
+ * Construct a TranscodeBackend and register it with the given registry (or
26
+ * core's `defaultRegistry` when omitted). Returns the backend so the caller can
27
+ * later `registry.unregister('webcodecs-transcode')`. Nothing registers on
28
+ * import — call this explicitly.
29
+ */
30
+ declare function registerTranscodeBackend(registry?: BackendRegistry): TranscodeBackend;
31
+
32
+ /**
33
+ * Typed error classes for @catlabtech/webcvt-transcode.
34
+ *
35
+ * All extend core's {@link WebcvtError} with a `TRANSCODE_`-prefixed code, so
36
+ * callers can catch by `instanceof` or match on `.code`. Never throw a bare
37
+ * `Error` from this package.
38
+ */
39
+
40
+ /**
41
+ * Thrown when the requested conversion is not in the transcode backend's
42
+ * capability set (off-matrix pair, or a codec whose runtime probe failed).
43
+ * `convert()` should never be reached for an unsupported pair — `canHandle`
44
+ * gates it — but this guards the defensive path.
45
+ */
46
+ declare class TranscodeUnsupportedError extends WebcvtError {
47
+ constructor(from: string, to: string);
48
+ }
49
+ /**
50
+ * Thrown when the input Blob exceeds the buffer-all input cap. The whole
51
+ * pipeline is buffer-all today (every serializer takes a complete in-memory
52
+ * model), so a hard cap protects against OOM.
53
+ */
54
+ declare class TranscodeInputTooLargeError extends WebcvtError {
55
+ constructor(size: number, max: number);
56
+ }
57
+ /** Thrown when demuxing the input container fails or yields no audio. */
58
+ declare class TranscodeDemuxError extends WebcvtError {
59
+ constructor(message: string, options?: ErrorOptions);
60
+ }
61
+ /** Wraps a decode/encode failure surfaced by codec-webcodecs. */
62
+ declare class TranscodeCodecError extends WebcvtError {
63
+ constructor(message: string, options?: ErrorOptions);
64
+ }
65
+ /** Thrown when muxing the encoded output container fails. */
66
+ declare class TranscodeMuxError extends WebcvtError {
67
+ constructor(message: string, options?: ErrorOptions);
68
+ }
69
+
70
+ /**
71
+ * Static v1 capability matrix + codec/bitrate resolution for the audio
72
+ * transcode paths.
73
+ *
74
+ * `canHandle` is two-stage: this frozen matrix is the cheap O(1) first gate
75
+ * (rejects off-matrix pairs like `→ mp3`/`→ mp4` and every video pair before
76
+ * any `isConfigSupported` round-trip), and the concrete both-sided codec probe
77
+ * is the second gate (see backend.ts). See docs/design-notes/transcode.md §D.
78
+ */
79
+ /** Codec token for a demux/mux side. `'pcm'` = raw PCM (wav), no WebCodecs. */
80
+ type SideCodec = 'pcm' | 'opus' | 'aac' | 'flac' | 'mp3';
81
+ /** Output container family we can mux from scratch. */
82
+ type OutputContainer = 'wav' | 'ogg' | 'webm' | 'aac' | 'flac';
83
+ interface OutputTarget {
84
+ readonly container: OutputContainer;
85
+ /** Codec the WebCodecs encoder must produce (`'pcm'` = interleave, no encode). */
86
+ readonly codec: SideCodec;
87
+ }
88
+ /**
89
+ * MIME → input codec for the containers this stage can demux. `audio/ogg` is
90
+ * assumed to carry Opus (Vorbis-in-Ogg decode needs a multi-packet setup
91
+ * description and is deferred). mp4/m4a/webm/mkv audio-track extraction is
92
+ * deferred to the video stage (shared demuxers).
93
+ */
94
+ declare const INPUT_CODECS: Readonly<Record<string, SideCodec>>;
95
+ /**
96
+ * MIME → output target. `→ mp3` and `→ ogg(vorbis)` are intentionally absent
97
+ * (no WebCodecs encoder); `→ mp4/m4a` is absent (no from-scratch mp4 muxer yet).
98
+ */
99
+ declare const OUTPUT_TARGETS: Readonly<Record<string, OutputTarget>>;
100
+ /**
101
+ * Frozen set of `"${inputMime}|${outputMime}"` keys — the full product of every
102
+ * decodable input against every muxable output. This is the static gate.
103
+ */
104
+ declare const TRANSCODE_MATRIX: ReadonlySet<string>;
105
+ /** Build the matrix key for a pair. */
106
+ declare function matrixKey(inputMime: string, outputMime: string): string;
107
+ /** Lookup the input codec for a MIME, or `undefined` if not decodable here. */
108
+ declare function inputCodecFor(mime: string): SideCodec | undefined;
109
+ /** Lookup the output target for a MIME, or `undefined` if not muxable here. */
110
+ declare function outputTargetFor(mime: string): OutputTarget | undefined;
111
+ /** Demuxable container family (its sample/block iterators feed the pipeline). */
112
+ type ContainerFamily = 'mp4' | 'webm' | 'mkv';
113
+ /** WebCodecs video codec token for a demux/mux side. */
114
+ type VideoSideCodec = 'h264' | 'vp9' | 'vp8' | 'av1' | 'hevc';
115
+ /**
116
+ * Container input MIME → family. Covers the video containers (video/mp4,
117
+ * video/webm, video/x-matroska) and the audio-only projections that share a
118
+ * demuxer (audio/mp4 = m4a, audio/webm). A container input can be transcoded
119
+ * to a video target (its video track) OR have its audio track routed into the
120
+ * existing audio matrix. Kept out of INPUT_CODECS so `inputCodecFor('video/mp4')`
121
+ * stays `undefined` (a container is not one audio codec).
122
+ */
123
+ declare const CONTAINER_INPUTS: Readonly<Record<string, ContainerFamily>>;
124
+ /** Output container family for a video target (VP9 default, VP8 fallback). */
125
+ interface VideoTarget {
126
+ readonly container: 'webm' | 'mkv';
127
+ }
128
+ /**
129
+ * Video output MIME → container. `→ mp4` is intentionally absent (no from-scratch
130
+ * mp4 muxer yet — deferred to a later stage). The encoder codec (VP9 preferred,
131
+ * VP8 fallback) is resolved at convert time by probing, not fixed here.
132
+ */
133
+ declare const VIDEO_TARGETS: Readonly<Record<string, VideoTarget>>;
134
+ /**
135
+ * Assumed audio-track codec per container family, used only by `canHandle`
136
+ * probing (the true codec is known after demux). mp4 audio is ~always AAC and
137
+ * webm/mkv audio is ~always Opus. A mismatch (e.g. Vorbis-in-webm) is rejected
138
+ * at demux — the same optimistic-probe contract the ogg(opus) input already uses.
139
+ */
140
+ declare const CONTAINER_AUDIO_CODEC: Readonly<Record<ContainerFamily, Exclude<SideCodec, 'pcm'>>>;
141
+ /** Assumed video-track codec per family, for `canHandle` decoder probing. */
142
+ declare const CONTAINER_VIDEO_CODEC: Readonly<Record<ContainerFamily, VideoSideCodec>>;
143
+ /** Lookup the container family for an input MIME, or `undefined`. */
144
+ declare function containerFamilyFor(mime: string): ContainerFamily | undefined;
145
+ /** Lookup the video output target for a MIME, or `undefined`. */
146
+ declare function videoTargetFor(mime: string): VideoTarget | undefined;
147
+ /** Default quality when `ConvertOptions.quality` is omitted (mid-ladder). */
148
+ declare const DEFAULT_QUALITY = 0.7;
149
+ /**
150
+ * Buffer-all input cap. Every serializer takes a complete in-memory model, so a
151
+ * hard cap protects against OOM. 256 MiB per the design note's conservative
152
+ * default; revisit when a streaming muxer exists (decoded PCM expands well
153
+ * beyond the compressed input size).
154
+ */
155
+ declare const MAX_INPUT_BYTES: number;
156
+ /**
157
+ * Map `quality` (0–1) to a codec bitrate in bits/s. The ladder is piecewise so
158
+ * the default `quality = 0.7` lands exactly on the mid-ladder figure from the
159
+ * design note (Opus 128 kbps, AAC 128 kbps stereo), with q=0 and q=1 at the
160
+ * ladder ends. Mono is scaled to ~0.6×. Lossless FLAC returns `undefined`
161
+ * (bitrate is not applicable).
162
+ */
163
+ declare function resolveBitrate(codec: SideCodec, quality: number, channels: number): number | undefined;
164
+ /**
165
+ * Map video geometry + `quality` to a VP9/VP8 bitrate in bits/s. Resolution
166
+ * drives the base (design note ladder: 720p ≈ 3 Mbps for VP9), scaled ±40% by
167
+ * quality around the default `0.7`, with VP8 at ≈1.3× VP9. Clamped to a sane
168
+ * floor so tiny test clips still get a positive bitrate.
169
+ */
170
+ declare function resolveVideoBitrate(width: number, height: number, quality: number, codec: 'vp9' | 'vp8'): number;
171
+
172
+ export { CONTAINER_AUDIO_CODEC, CONTAINER_INPUTS, CONTAINER_VIDEO_CODEC, type ContainerFamily, DEFAULT_QUALITY, INPUT_CODECS, MAX_INPUT_BYTES, OUTPUT_TARGETS, type OutputContainer, type OutputTarget, type SideCodec, TRANSCODE_MATRIX, TranscodeBackend, TranscodeCodecError, TranscodeDemuxError, TranscodeInputTooLargeError, TranscodeMuxError, TranscodeUnsupportedError, VIDEO_TARGETS, type VideoSideCodec, type VideoTarget, containerFamilyFor, inputCodecFor, matrixKey, outputTargetFor, registerTranscodeBackend, resolveBitrate, resolveVideoBitrate, videoTargetFor };