@editframe/assets 0.59.5 → 0.59.7
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.
- package/dist/Probe.d.cts +31 -30
- package/dist/Probe.d.ts +31 -30
- package/dist/VideoRenderOptions.d.cts +48 -48
- package/dist/VideoRenderOptions.d.ts +48 -48
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/mediabunny-ingest/MediabunnyIngest.cjs +230 -0
- package/dist/mediabunny-ingest/MediabunnyIngest.cjs.map +1 -0
- package/dist/mediabunny-ingest/MediabunnyIngest.d.cts +60 -0
- package/dist/mediabunny-ingest/MediabunnyIngest.d.ts +60 -0
- package/dist/mediabunny-ingest/MediabunnyIngest.js +224 -0
- package/dist/mediabunny-ingest/MediabunnyIngest.js.map +1 -0
- package/dist/mediabunny-ingest/bitstream.cjs +118 -0
- package/dist/mediabunny-ingest/bitstream.cjs.map +1 -0
- package/dist/mediabunny-ingest/bitstream.js +117 -0
- package/dist/mediabunny-ingest/bitstream.js.map +1 -0
- package/dist/tasks/generateScrubTrack.d.cts +2 -1
- package/dist/tasks/generateScrubTrack.d.ts +2 -1
- package/dist/tasks/generateTrack.d.cts +2 -1
- package/dist/tasks/generateTrack.d.ts +2 -1
- package/package.json +3 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { adtsToRawAac, stripSeiNalUnits } from "./bitstream.js";
|
|
2
|
+
import debug from "debug";
|
|
3
|
+
import { PassThrough, Writable } from "node:stream";
|
|
4
|
+
import crypto from "node:crypto";
|
|
5
|
+
import { registerMediabunnyServer } from "@mediabunny/server";
|
|
6
|
+
import { ALL_FORMATS, AppendOnlyStreamTarget, AudioSampleSink, AudioSampleSource, EncodedAudioPacketSource, EncodedPacketSink, EncodedVideoPacketSource, FilePathSource, Input, Mp4OutputFormat, Output, UrlSource } from "mediabunny";
|
|
7
|
+
//#region src/mediabunny-ingest/MediabunnyIngest.ts
|
|
8
|
+
const log = debug("ef:assets:mediabunny-ingest");
|
|
9
|
+
registerMediabunnyServer();
|
|
10
|
+
/** Opens a mediabunny `Input` over a URL or local file path, auto-detecting the container/HLS format. */
|
|
11
|
+
function openIngestInput(source) {
|
|
12
|
+
return new Input({
|
|
13
|
+
formats: ALL_FORMATS,
|
|
14
|
+
source: source.type === "url" ? new UrlSource(source.url) : new FilePathSource(source.path)
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Selects the best video track and its best pairable audio track from an
|
|
19
|
+
* input (matching the "ingest only the best audio and video track" ingest
|
|
20
|
+
* policy). For plain progressive files this is just "the" video/audio track;
|
|
21
|
+
* for HLS master playlists, mediabunny's `getPrimaryVideoTrack`/
|
|
22
|
+
* `getPrimaryAudioTrack` already implement bitrate-based ABR selection.
|
|
23
|
+
*
|
|
24
|
+
* Throws if the source has no usable tracks, or if any selected track is
|
|
25
|
+
* still live (open-ended) — ingestion only supports VOD sources.
|
|
26
|
+
*/
|
|
27
|
+
async function selectIngestTracks(input) {
|
|
28
|
+
const video = await input.getPrimaryVideoTrack();
|
|
29
|
+
const audio = video ? await video.getPrimaryPairableAudioTrack() ?? await input.getPrimaryAudioTrack() : await input.getPrimaryAudioTrack();
|
|
30
|
+
if (!video && !audio) throw new Error("No video or audio tracks found in source");
|
|
31
|
+
for (const track of [video, audio]) if (track && await track.isLive()) throw new Error("Live (open-ended) sources are not supported for ingestion — only VOD sources can be processed");
|
|
32
|
+
return {
|
|
33
|
+
video,
|
|
34
|
+
audio
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function bridgeToNodeReadable() {
|
|
38
|
+
const pass = new PassThrough();
|
|
39
|
+
return {
|
|
40
|
+
readable: pass,
|
|
41
|
+
target: new AppendOnlyStreamTarget(Writable.toWeb(pass))
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Zero-bases a track's packet timestamps around its own decode-order first
|
|
46
|
+
* packet, for two reasons:
|
|
47
|
+
*
|
|
48
|
+
* 1. Mediabunny's MP4 muxer rejects packets with negative timestamps outright.
|
|
49
|
+
* Negative first-packet timestamps are common and legitimate — e.g. AAC
|
|
50
|
+
* encoder priming samples, or B-frame reordering — and ffmpeg silently
|
|
51
|
+
* normalizes them by default (`-avoid_negative_ts make_zero`) when remuxing.
|
|
52
|
+
*
|
|
53
|
+
* 2. For *positive* first-packet timestamps (e.g. a source with a non-zero
|
|
54
|
+
* container-level `format.start_time`, such as Mux's 90kHz start-time
|
|
55
|
+
* offset), mediabunny's fragmented-MP4 muxer does NOT zero-base decode
|
|
56
|
+
* timestamps the way its non-fragmented muxer (and the old ffmpeg-CLI
|
|
57
|
+
* ingest's remux) does — that normalization is explicitly gated behind
|
|
58
|
+
* `!isFragmented` in mediabunny's isobmff muxer. Left unshifted, that
|
|
59
|
+
* container-level offset leaks into the produced track's absolute
|
|
60
|
+
* timestamps, which `generateFragmentIndex` reads assuming DTS starts at
|
|
61
|
+
* 0 (see its "After ffmpeg re-mux, DTS is always 0" comment) — corrupting
|
|
62
|
+
* its detected `startTimeOffsetMs` by exactly that leaked amount.
|
|
63
|
+
*
|
|
64
|
+
* Shifting every packet by `-firstTimestamp` handles both cases and is a
|
|
65
|
+
* no-op for the common case where the track already starts at t=0. It does
|
|
66
|
+
* NOT erase a genuine B-frame reordering offset (CTS > DTS for the first
|
|
67
|
+
* decode-order packet): that offset is a *relative* property between a
|
|
68
|
+
* packet's own timestamp and its decode-order position, which a uniform
|
|
69
|
+
* shift preserves.
|
|
70
|
+
*/
|
|
71
|
+
async function getTimestampOffset(track) {
|
|
72
|
+
return -await track.getFirstTimestamp();
|
|
73
|
+
}
|
|
74
|
+
function md5Digest(hash) {
|
|
75
|
+
const hex = hash.digest("hex");
|
|
76
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Extracts a single video track into a fragmented single-track MP4 stream via
|
|
80
|
+
* packet-copy transmux (no re-encoding — matches the previous ffmpeg `-c:v copy`
|
|
81
|
+
* behavior). SEI NAL units (type 6) are stripped from every packet, mirroring the
|
|
82
|
+
* previous `-bsf:v filter_units=remove_types=6` ffmpeg filter.
|
|
83
|
+
*/
|
|
84
|
+
function extractVideoTrack(track) {
|
|
85
|
+
const { readable, target } = bridgeToNodeReadable();
|
|
86
|
+
const hash = crypto.createHash("md5");
|
|
87
|
+
const digestDeferred = createDeferred();
|
|
88
|
+
(async () => {
|
|
89
|
+
const codec = await track.getCodec();
|
|
90
|
+
if (!codec) throw new Error("Video track has no known codec");
|
|
91
|
+
const decoderConfig = await track.getDecoderConfig();
|
|
92
|
+
const isAnnexB = decoderConfig?.description === void 0;
|
|
93
|
+
const tsOffset = await getTimestampOffset(track);
|
|
94
|
+
const output = new Output({
|
|
95
|
+
format: new Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
96
|
+
target
|
|
97
|
+
});
|
|
98
|
+
const source = new EncodedVideoPacketSource(codec);
|
|
99
|
+
output.addVideoTrack(source);
|
|
100
|
+
await output.start();
|
|
101
|
+
const sink = new EncodedPacketSink(track);
|
|
102
|
+
let isFirst = true;
|
|
103
|
+
for await (const packet of sink.packets()) {
|
|
104
|
+
hash.update(packet.data);
|
|
105
|
+
const strippedData = stripSeiNalUnits(packet.data, isAnnexB);
|
|
106
|
+
const outPacket = strippedData !== packet.data || tsOffset !== 0 ? packet.clone({
|
|
107
|
+
data: strippedData,
|
|
108
|
+
timestamp: packet.timestamp + tsOffset
|
|
109
|
+
}) : packet;
|
|
110
|
+
await source.add(outPacket, isFirst && decoderConfig ? { decoderConfig } : void 0);
|
|
111
|
+
isFirst = false;
|
|
112
|
+
}
|
|
113
|
+
await output.finalize();
|
|
114
|
+
})().then(() => digestDeferred.resolve(md5Digest(hash))).catch((error) => {
|
|
115
|
+
digestDeferred.reject(error);
|
|
116
|
+
readable.emit("error", error);
|
|
117
|
+
readable.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
118
|
+
});
|
|
119
|
+
return {
|
|
120
|
+
stream: readable,
|
|
121
|
+
digest: digestDeferred.promise
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Extracts a single audio track into a fragmented single-track MP4 stream.
|
|
126
|
+
*
|
|
127
|
+
* - AAC-raw sources (description present) are packet-copied directly.
|
|
128
|
+
* - AAC-ADTS sources (typical of MPEG-TS/HLS demuxed AAC) have their ADTS
|
|
129
|
+
* headers stripped and an AudioSpecificConfig synthesized, mirroring the
|
|
130
|
+
* previous `-bsf:a aac_adtstoasc` ffmpeg filter.
|
|
131
|
+
* - Any other codec is decoded and re-encoded to AAC (192kbps), mirroring the
|
|
132
|
+
* previous `-c:a aac -b:a 192k -ar 48000` ffmpeg re-encode fallback.
|
|
133
|
+
*/
|
|
134
|
+
function extractAudioTrack(track) {
|
|
135
|
+
const { readable, target } = bridgeToNodeReadable();
|
|
136
|
+
const hash = crypto.createHash("md5");
|
|
137
|
+
const digestDeferred = createDeferred();
|
|
138
|
+
(async () => {
|
|
139
|
+
const codec = await track.getCodec();
|
|
140
|
+
const decoderConfig = await track.getDecoderConfig();
|
|
141
|
+
const isAdtsAac = codec === "aac" && decoderConfig?.description === void 0;
|
|
142
|
+
const needsReencode = codec !== "aac";
|
|
143
|
+
const tsOffset = await getTimestampOffset(track);
|
|
144
|
+
const output = new Output({
|
|
145
|
+
format: new Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
146
|
+
target
|
|
147
|
+
});
|
|
148
|
+
if (needsReencode) {
|
|
149
|
+
const source = new AudioSampleSource({
|
|
150
|
+
codec: "aac",
|
|
151
|
+
bitrate: 192e3
|
|
152
|
+
});
|
|
153
|
+
output.addAudioTrack(source);
|
|
154
|
+
await output.start();
|
|
155
|
+
const hashPromise = (async () => {
|
|
156
|
+
const packetSink = new EncodedPacketSink(track);
|
|
157
|
+
for await (const packet of packetSink.packets()) hash.update(packet.data);
|
|
158
|
+
})();
|
|
159
|
+
const sampleSink = new AudioSampleSink(track);
|
|
160
|
+
for await (const sample of sampleSink.samples()) try {
|
|
161
|
+
if (tsOffset !== 0) sample.setTimestamp(sample.timestamp + tsOffset);
|
|
162
|
+
await source.add(sample);
|
|
163
|
+
} finally {
|
|
164
|
+
sample.close();
|
|
165
|
+
}
|
|
166
|
+
await hashPromise;
|
|
167
|
+
await output.finalize();
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
const source = new EncodedAudioPacketSource("aac");
|
|
171
|
+
output.addAudioTrack(source);
|
|
172
|
+
await output.start();
|
|
173
|
+
const sink = new EncodedPacketSink(track);
|
|
174
|
+
const [sampleRate, numberOfChannels] = await Promise.all([track.getSampleRate(), track.getNumberOfChannels()]);
|
|
175
|
+
let isFirst = true;
|
|
176
|
+
for await (const packet of sink.packets()) {
|
|
177
|
+
hash.update(packet.data);
|
|
178
|
+
if (!isAdtsAac) {
|
|
179
|
+
const outPacket = tsOffset !== 0 ? packet.clone({ timestamp: packet.timestamp + tsOffset }) : packet;
|
|
180
|
+
await source.add(outPacket, isFirst && decoderConfig ? { decoderConfig } : void 0);
|
|
181
|
+
isFirst = false;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const raw = adtsToRawAac(packet.data);
|
|
185
|
+
const outPacket = packet.clone({
|
|
186
|
+
data: raw.data,
|
|
187
|
+
timestamp: packet.timestamp + tsOffset
|
|
188
|
+
});
|
|
189
|
+
await source.add(outPacket, isFirst ? { decoderConfig: {
|
|
190
|
+
codec: raw.codec,
|
|
191
|
+
sampleRate,
|
|
192
|
+
numberOfChannels,
|
|
193
|
+
description: raw.audioSpecificConfig
|
|
194
|
+
} } : void 0);
|
|
195
|
+
isFirst = false;
|
|
196
|
+
}
|
|
197
|
+
await output.finalize();
|
|
198
|
+
})().then(() => digestDeferred.resolve(md5Digest(hash))).catch((error) => {
|
|
199
|
+
digestDeferred.reject(error);
|
|
200
|
+
readable.emit("error", error);
|
|
201
|
+
readable.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
stream: readable,
|
|
205
|
+
digest: digestDeferred.promise
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
function createDeferred() {
|
|
209
|
+
let resolve;
|
|
210
|
+
let reject;
|
|
211
|
+
return {
|
|
212
|
+
promise: new Promise((res, rej) => {
|
|
213
|
+
resolve = res;
|
|
214
|
+
reject = rej;
|
|
215
|
+
}),
|
|
216
|
+
resolve,
|
|
217
|
+
reject
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
log("Mediabunny ingest module loaded");
|
|
221
|
+
//#endregion
|
|
222
|
+
export { extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=MediabunnyIngest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediabunnyIngest.js","names":["NodeWritable"],"sources":["../../src/mediabunny-ingest/MediabunnyIngest.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//#region src/mediabunny-ingest/bitstream.ts
|
|
2
|
+
/**
|
|
3
|
+
* Bitstream-level helpers used by the mediabunny-based ingest pipeline.
|
|
4
|
+
*
|
|
5
|
+
* These replace two ffmpeg bitstream filters that the old ffmpeg-CLI ingest
|
|
6
|
+
* path relied on:
|
|
7
|
+
*
|
|
8
|
+
* -bsf:v filter_units=remove_types=6 (strip SEI NAL units — see stripSeiNalUnits)
|
|
9
|
+
* -bsf:a aac_adtstoasc (ADTS -> raw AAC — see adtsToRawAac)
|
|
10
|
+
*/
|
|
11
|
+
const SEI_NAL_UNIT_TYPE = 6;
|
|
12
|
+
function concatUint8Arrays(chunks, totalLength) {
|
|
13
|
+
const out = new Uint8Array(totalLength);
|
|
14
|
+
let offset = 0;
|
|
15
|
+
for (const chunk of chunks) {
|
|
16
|
+
out.set(chunk, offset);
|
|
17
|
+
offset += chunk.length;
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Strips SEI (Supplemental Enhancement Information, NAL unit type 6) NAL units
|
|
23
|
+
* from an AVC access unit. Some WebCodecs VideoDecoder implementations hang or
|
|
24
|
+
* crash on certain SEI payloads, so these are dropped — mirroring the
|
|
25
|
+
* `-bsf:v filter_units=remove_types=6` ffmpeg filter used by the previous
|
|
26
|
+
* ffmpeg-CLI ingest path.
|
|
27
|
+
*
|
|
28
|
+
* @param data - The packet's encoded data, as produced by mediabunny's
|
|
29
|
+
* `EncodedPacketSink`.
|
|
30
|
+
* @param isAnnexB - Whether the AVC bitstream is in Annex B (start-code
|
|
31
|
+
* delimited) format rather than canonical (4-byte length-prefixed) format.
|
|
32
|
+
* Per the Mediabunny Codec Registry, this is true iff the track's decoder
|
|
33
|
+
* config `description` is `undefined`.
|
|
34
|
+
*/
|
|
35
|
+
function stripSeiNalUnits(data, isAnnexB) {
|
|
36
|
+
return isAnnexB ? stripSeiAnnexB(data) : stripSeiLengthPrefixed(data);
|
|
37
|
+
}
|
|
38
|
+
function stripSeiLengthPrefixed(data) {
|
|
39
|
+
const kept = [];
|
|
40
|
+
let keptLength = 0;
|
|
41
|
+
let offset = 0;
|
|
42
|
+
while (offset + 4 <= data.length) {
|
|
43
|
+
const len = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
|
|
44
|
+
const nalStart = offset + 4;
|
|
45
|
+
const nalEnd = nalStart + len;
|
|
46
|
+
if (len < 0 || nalEnd > data.length || nalStart >= data.length) return data;
|
|
47
|
+
if ((data[nalStart] & 31) !== SEI_NAL_UNIT_TYPE) {
|
|
48
|
+
const nalu = data.subarray(offset, nalEnd);
|
|
49
|
+
kept.push(nalu);
|
|
50
|
+
keptLength += nalu.length;
|
|
51
|
+
}
|
|
52
|
+
offset = nalEnd;
|
|
53
|
+
}
|
|
54
|
+
if (offset !== data.length) return data;
|
|
55
|
+
return concatUint8Arrays(kept, keptLength);
|
|
56
|
+
}
|
|
57
|
+
function stripSeiAnnexB(data) {
|
|
58
|
+
const starts = findAnnexBNaluStarts(data);
|
|
59
|
+
if (starts.length === 0) return data;
|
|
60
|
+
const kept = [];
|
|
61
|
+
let keptLength = 0;
|
|
62
|
+
for (let i = 0; i < starts.length; i++) {
|
|
63
|
+
const { prefixStart, nalStart } = starts[i];
|
|
64
|
+
const nalEnd = i + 1 < starts.length ? starts[i + 1].prefixStart : data.length;
|
|
65
|
+
if ((data[nalStart] & 31) !== SEI_NAL_UNIT_TYPE) {
|
|
66
|
+
const chunk = data.subarray(prefixStart, nalEnd);
|
|
67
|
+
kept.push(chunk);
|
|
68
|
+
keptLength += chunk.length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return concatUint8Arrays(kept, keptLength);
|
|
72
|
+
}
|
|
73
|
+
/** Finds every Annex B start code (0x000001 or 0x00000001) and the NALU byte right after it. */
|
|
74
|
+
function findAnnexBNaluStarts(data) {
|
|
75
|
+
const starts = [];
|
|
76
|
+
for (let i = 0; i + 2 < data.length; i++) if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 1) {
|
|
77
|
+
const isFourByte = i > 0 && data[i - 1] === 0;
|
|
78
|
+
starts.push({
|
|
79
|
+
prefixStart: isFourByte ? i - 1 : i,
|
|
80
|
+
nalStart: i + 3
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return starts;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Converts a single ADTS-framed AAC packet (as produced by mediabunny when
|
|
87
|
+
* reading AAC out of an MPEG-TS/HLS source) into raw AAC frame data plus the
|
|
88
|
+
* AudioSpecificConfig an MP4 muxer needs out-of-band. Mirrors ffmpeg's
|
|
89
|
+
* `aac_adtstoasc` bitstream filter.
|
|
90
|
+
*
|
|
91
|
+
* Only single-AAC-frame ADTS packets (`number_of_raw_data_blocks == 0`) are
|
|
92
|
+
* supported — this covers the overwhelming majority of real-world HLS/Mux
|
|
93
|
+
* output. Multi-block ADTS packets throw so the caller can fall back to a
|
|
94
|
+
* decode/re-encode path instead of silently mis-parsing audio.
|
|
95
|
+
*/
|
|
96
|
+
function adtsToRawAac(data) {
|
|
97
|
+
if (data.length < 7) throw new Error("ADTS packet too short to contain a header");
|
|
98
|
+
if (data[0] !== 255 || (data[1] & 240) !== 240) throw new Error("Data does not start with an ADTS sync word");
|
|
99
|
+
const headerLength = (data[1] & 1) === 1 ? 7 : 9;
|
|
100
|
+
const profile = data[2] >> 6 & 3;
|
|
101
|
+
const samplingFrequencyIndex = data[2] >> 2 & 15;
|
|
102
|
+
const channelConfiguration = (data[2] & 1) << 2 | data[3] >> 6 & 3;
|
|
103
|
+
const numberOfRawDataBlocks = data[6] & 3;
|
|
104
|
+
if (numberOfRawDataBlocks !== 0) throw new Error(`Multi-block ADTS packets (number_of_raw_data_blocks=${numberOfRawDataBlocks}) are not supported`);
|
|
105
|
+
const audioObjectType = profile + 1;
|
|
106
|
+
const ascValue = audioObjectType << 11 | samplingFrequencyIndex << 7 | channelConfiguration << 3;
|
|
107
|
+
const audioSpecificConfig = new Uint8Array([ascValue >> 8 & 255, ascValue & 255]);
|
|
108
|
+
return {
|
|
109
|
+
data: data.subarray(headerLength),
|
|
110
|
+
audioSpecificConfig,
|
|
111
|
+
codec: `mp4a.40.${audioObjectType}`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
exports.adtsToRawAac = adtsToRawAac;
|
|
116
|
+
exports.stripSeiNalUnits = stripSeiNalUnits;
|
|
117
|
+
|
|
118
|
+
//# sourceMappingURL=bitstream.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitstream.cjs","names":[],"sources":["../../src/mediabunny-ingest/bitstream.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//#region src/mediabunny-ingest/bitstream.ts
|
|
2
|
+
/**
|
|
3
|
+
* Bitstream-level helpers used by the mediabunny-based ingest pipeline.
|
|
4
|
+
*
|
|
5
|
+
* These replace two ffmpeg bitstream filters that the old ffmpeg-CLI ingest
|
|
6
|
+
* path relied on:
|
|
7
|
+
*
|
|
8
|
+
* -bsf:v filter_units=remove_types=6 (strip SEI NAL units — see stripSeiNalUnits)
|
|
9
|
+
* -bsf:a aac_adtstoasc (ADTS -> raw AAC — see adtsToRawAac)
|
|
10
|
+
*/
|
|
11
|
+
const SEI_NAL_UNIT_TYPE = 6;
|
|
12
|
+
function concatUint8Arrays(chunks, totalLength) {
|
|
13
|
+
const out = new Uint8Array(totalLength);
|
|
14
|
+
let offset = 0;
|
|
15
|
+
for (const chunk of chunks) {
|
|
16
|
+
out.set(chunk, offset);
|
|
17
|
+
offset += chunk.length;
|
|
18
|
+
}
|
|
19
|
+
return out;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Strips SEI (Supplemental Enhancement Information, NAL unit type 6) NAL units
|
|
23
|
+
* from an AVC access unit. Some WebCodecs VideoDecoder implementations hang or
|
|
24
|
+
* crash on certain SEI payloads, so these are dropped — mirroring the
|
|
25
|
+
* `-bsf:v filter_units=remove_types=6` ffmpeg filter used by the previous
|
|
26
|
+
* ffmpeg-CLI ingest path.
|
|
27
|
+
*
|
|
28
|
+
* @param data - The packet's encoded data, as produced by mediabunny's
|
|
29
|
+
* `EncodedPacketSink`.
|
|
30
|
+
* @param isAnnexB - Whether the AVC bitstream is in Annex B (start-code
|
|
31
|
+
* delimited) format rather than canonical (4-byte length-prefixed) format.
|
|
32
|
+
* Per the Mediabunny Codec Registry, this is true iff the track's decoder
|
|
33
|
+
* config `description` is `undefined`.
|
|
34
|
+
*/
|
|
35
|
+
function stripSeiNalUnits(data, isAnnexB) {
|
|
36
|
+
return isAnnexB ? stripSeiAnnexB(data) : stripSeiLengthPrefixed(data);
|
|
37
|
+
}
|
|
38
|
+
function stripSeiLengthPrefixed(data) {
|
|
39
|
+
const kept = [];
|
|
40
|
+
let keptLength = 0;
|
|
41
|
+
let offset = 0;
|
|
42
|
+
while (offset + 4 <= data.length) {
|
|
43
|
+
const len = data[offset] << 24 | data[offset + 1] << 16 | data[offset + 2] << 8 | data[offset + 3];
|
|
44
|
+
const nalStart = offset + 4;
|
|
45
|
+
const nalEnd = nalStart + len;
|
|
46
|
+
if (len < 0 || nalEnd > data.length || nalStart >= data.length) return data;
|
|
47
|
+
if ((data[nalStart] & 31) !== SEI_NAL_UNIT_TYPE) {
|
|
48
|
+
const nalu = data.subarray(offset, nalEnd);
|
|
49
|
+
kept.push(nalu);
|
|
50
|
+
keptLength += nalu.length;
|
|
51
|
+
}
|
|
52
|
+
offset = nalEnd;
|
|
53
|
+
}
|
|
54
|
+
if (offset !== data.length) return data;
|
|
55
|
+
return concatUint8Arrays(kept, keptLength);
|
|
56
|
+
}
|
|
57
|
+
function stripSeiAnnexB(data) {
|
|
58
|
+
const starts = findAnnexBNaluStarts(data);
|
|
59
|
+
if (starts.length === 0) return data;
|
|
60
|
+
const kept = [];
|
|
61
|
+
let keptLength = 0;
|
|
62
|
+
for (let i = 0; i < starts.length; i++) {
|
|
63
|
+
const { prefixStart, nalStart } = starts[i];
|
|
64
|
+
const nalEnd = i + 1 < starts.length ? starts[i + 1].prefixStart : data.length;
|
|
65
|
+
if ((data[nalStart] & 31) !== SEI_NAL_UNIT_TYPE) {
|
|
66
|
+
const chunk = data.subarray(prefixStart, nalEnd);
|
|
67
|
+
kept.push(chunk);
|
|
68
|
+
keptLength += chunk.length;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return concatUint8Arrays(kept, keptLength);
|
|
72
|
+
}
|
|
73
|
+
/** Finds every Annex B start code (0x000001 or 0x00000001) and the NALU byte right after it. */
|
|
74
|
+
function findAnnexBNaluStarts(data) {
|
|
75
|
+
const starts = [];
|
|
76
|
+
for (let i = 0; i + 2 < data.length; i++) if (data[i] === 0 && data[i + 1] === 0 && data[i + 2] === 1) {
|
|
77
|
+
const isFourByte = i > 0 && data[i - 1] === 0;
|
|
78
|
+
starts.push({
|
|
79
|
+
prefixStart: isFourByte ? i - 1 : i,
|
|
80
|
+
nalStart: i + 3
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return starts;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Converts a single ADTS-framed AAC packet (as produced by mediabunny when
|
|
87
|
+
* reading AAC out of an MPEG-TS/HLS source) into raw AAC frame data plus the
|
|
88
|
+
* AudioSpecificConfig an MP4 muxer needs out-of-band. Mirrors ffmpeg's
|
|
89
|
+
* `aac_adtstoasc` bitstream filter.
|
|
90
|
+
*
|
|
91
|
+
* Only single-AAC-frame ADTS packets (`number_of_raw_data_blocks == 0`) are
|
|
92
|
+
* supported — this covers the overwhelming majority of real-world HLS/Mux
|
|
93
|
+
* output. Multi-block ADTS packets throw so the caller can fall back to a
|
|
94
|
+
* decode/re-encode path instead of silently mis-parsing audio.
|
|
95
|
+
*/
|
|
96
|
+
function adtsToRawAac(data) {
|
|
97
|
+
if (data.length < 7) throw new Error("ADTS packet too short to contain a header");
|
|
98
|
+
if (data[0] !== 255 || (data[1] & 240) !== 240) throw new Error("Data does not start with an ADTS sync word");
|
|
99
|
+
const headerLength = (data[1] & 1) === 1 ? 7 : 9;
|
|
100
|
+
const profile = data[2] >> 6 & 3;
|
|
101
|
+
const samplingFrequencyIndex = data[2] >> 2 & 15;
|
|
102
|
+
const channelConfiguration = (data[2] & 1) << 2 | data[3] >> 6 & 3;
|
|
103
|
+
const numberOfRawDataBlocks = data[6] & 3;
|
|
104
|
+
if (numberOfRawDataBlocks !== 0) throw new Error(`Multi-block ADTS packets (number_of_raw_data_blocks=${numberOfRawDataBlocks}) are not supported`);
|
|
105
|
+
const audioObjectType = profile + 1;
|
|
106
|
+
const ascValue = audioObjectType << 11 | samplingFrequencyIndex << 7 | channelConfiguration << 3;
|
|
107
|
+
const audioSpecificConfig = new Uint8Array([ascValue >> 8 & 255, ascValue & 255]);
|
|
108
|
+
return {
|
|
109
|
+
data: data.subarray(headerLength),
|
|
110
|
+
audioSpecificConfig,
|
|
111
|
+
codec: `mp4a.40.${audioObjectType}`
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { adtsToRawAac, stripSeiNalUnits };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=bitstream.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bitstream.js","names":[],"sources":["../../src/mediabunny-ingest/bitstream.ts"],"mappings":";;;;;;;;;;AAUA,MAAM,oBAAoB;AAE1B,SAAS,kBAAkB,QAAsB,aAAiC;CAChF,MAAM,MAAM,IAAI,WAAW,YAAY;CACvC,IAAI,SAAS;AACb,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,IAAI,OAAO,OAAO;AACtB,YAAU,MAAM;;AAElB,QAAO;;;;;;;;;;;;;;;;AAiBT,SAAgB,iBAAiB,MAAkB,UAA+B;AAChF,QAAO,WAAW,eAAe,KAAK,GAAG,uBAAuB,KAAK;;AAGvE,SAAS,uBAAuB,MAA8B;CAC5D,MAAM,OAAqB,EAAE;CAC7B,IAAI,aAAa;CACjB,IAAI,SAAS;AAEb,QAAO,SAAS,KAAK,KAAK,QAAQ;EAChC,MAAM,MACH,KAAK,WAAY,KACjB,KAAK,SAAS,MAAO,KACrB,KAAK,SAAS,MAAO,IACtB,KAAK,SAAS;EAChB,MAAM,WAAW,SAAS;EAC1B,MAAM,SAAS,WAAW;AAI1B,MAAI,MAAM,KAAK,SAAS,KAAK,UAAU,YAAY,KAAK,OACtD,QAAO;AAIT,OADgB,KAAK,YAAa,QAClB,mBAAmB;GACjC,MAAM,OAAO,KAAK,SAAS,QAAQ,OAAO;AAC1C,QAAK,KAAK,KAAK;AACf,iBAAc,KAAK;;AAGrB,WAAS;;AAGX,KAAI,WAAW,KAAK,OAGlB,QAAO;AAGT,QAAO,kBAAkB,MAAM,WAAW;;AAG5C,SAAS,eAAe,MAA8B;CACpD,MAAM,SAAS,qBAAqB,KAAK;AACzC,KAAI,OAAO,WAAW,EACpB,QAAO;CAGT,MAAM,OAAqB,EAAE;CAC7B,IAAI,aAAa;AAEjB,MAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,EAAE,aAAa,aAAa,OAAO;EACzC,MAAM,SAAS,IAAI,IAAI,OAAO,SAAS,OAAO,IAAI,GAAI,cAAc,KAAK;AAEzE,OADgB,KAAK,YAAa,QAClB,mBAAmB;GACjC,MAAM,QAAQ,KAAK,SAAS,aAAa,OAAO;AAChD,QAAK,KAAK,MAAM;AAChB,iBAAc,MAAM;;;AAIxB,QAAO,kBAAkB,MAAM,WAAW;;;AAI5C,SAAS,qBAAqB,MAAoE;CAChG,MAAM,SAA2D,EAAE;AACnE,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,KAAK,QAAQ,IACnC,KAAI,KAAK,OAAO,KAAK,KAAK,IAAI,OAAO,KAAK,KAAK,IAAI,OAAO,GAAG;EAC3D,MAAM,aAAa,IAAI,KAAK,KAAK,IAAI,OAAO;AAC5C,SAAO,KAAK;GAAE,aAAa,aAAa,IAAI,IAAI;GAAG,UAAU,IAAI;GAAG,CAAC;;AAGzE,QAAO;;;;;;;;;;;;;AA2BT,SAAgB,aAAa,MAA+B;AAC1D,KAAI,KAAK,SAAS,EAChB,OAAM,IAAI,MAAM,4CAA4C;AAE9D,KAAI,KAAK,OAAO,QAAS,KAAK,KAAM,SAAU,IAC5C,OAAM,IAAI,MAAM,6CAA6C;CAI/D,MAAM,gBADoB,KAAK,KAAM,OAAU,IACP,IAAI;CAE5C,MAAM,UAAW,KAAK,MAAO,IAAK;CAClC,MAAM,yBAA0B,KAAK,MAAO,IAAK;CACjD,MAAM,wBAAyB,KAAK,KAAM,MAAS,IAAO,KAAK,MAAO,IAAK;CAC3E,MAAM,wBAAwB,KAAK,KAAM;AAEzC,KAAI,0BAA0B,EAC5B,OAAM,IAAI,MACR,uDAAuD,sBAAsB,qBAC9E;CAGH,MAAM,kBAAkB,UAAU;CAClC,MAAM,WACH,mBAAmB,KAAO,0BAA0B,IAAM,wBAAwB;CACrF,MAAM,sBAAsB,IAAI,WAAW,CAAE,YAAY,IAAK,KAAM,WAAW,IAAK,CAAC;AAErF,QAAO;EACL,MAAM,KAAK,SAAS,aAAa;EACjC;EACA,OAAO,WAAW;EACnB"}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { TrackFragmentIndex } from "../Probe.cjs";
|
|
2
2
|
import { TaskResult } from "../idempotentTask.cjs";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
3
4
|
|
|
4
5
|
//#region src/tasks/generateScrubTrack.d.ts
|
|
5
6
|
declare const generateScrubTrackFromPath: (absolutePath: string) => Promise<{
|
|
6
|
-
stream:
|
|
7
|
+
stream: Readable;
|
|
7
8
|
fragmentIndex: Promise<Record<number, TrackFragmentIndex>>;
|
|
8
9
|
}>;
|
|
9
10
|
declare const generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { TrackFragmentIndex } from "../Probe.js";
|
|
2
2
|
import { TaskResult } from "../idempotentTask.js";
|
|
3
|
+
import { Readable } from "node:stream";
|
|
3
4
|
|
|
4
5
|
//#region src/tasks/generateScrubTrack.d.ts
|
|
5
6
|
declare const generateScrubTrackFromPath: (absolutePath: string) => Promise<{
|
|
6
|
-
stream:
|
|
7
|
+
stream: Readable;
|
|
7
8
|
fragmentIndex: Promise<Record<number, TrackFragmentIndex>>;
|
|
8
9
|
}>;
|
|
9
10
|
declare const generateScrubTrack: (cacheRoot: string, absolutePath: string) => Promise<TaskResult>;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TaskResult } from "../idempotentTask.cjs";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
2
3
|
|
|
3
4
|
//#region src/tasks/generateTrack.d.ts
|
|
4
|
-
declare const generateTrackFromPath: (absolutePath: string, trackId: number) => Promise<
|
|
5
|
+
declare const generateTrackFromPath: (absolutePath: string, trackId: number) => Promise<Readable>;
|
|
5
6
|
declare const generateTrack: (cacheRoot: string, absolutePath: string, url: string) => Promise<TaskResult>;
|
|
6
7
|
//#endregion
|
|
7
8
|
export { generateTrack, generateTrackFromPath };
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { TaskResult } from "../idempotentTask.js";
|
|
2
|
+
import { Readable } from "node:stream";
|
|
2
3
|
|
|
3
4
|
//#region src/tasks/generateTrack.d.ts
|
|
4
|
-
declare const generateTrackFromPath: (absolutePath: string, trackId: number) => Promise<
|
|
5
|
+
declare const generateTrackFromPath: (absolutePath: string, trackId: number) => Promise<Readable>;
|
|
5
6
|
declare const generateTrack: (cacheRoot: string, absolutePath: string, url: string) => Promise<TaskResult>;
|
|
6
7
|
//#endregion
|
|
7
8
|
export { generateTrack, generateTrackFromPath };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/assets",
|
|
3
|
-
"version": "0.59.
|
|
3
|
+
"version": "0.59.7",
|
|
4
4
|
"description": "",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
"author": "",
|
|
18
18
|
"license": "SEE LICENSE IN LICENSE-FULL.md",
|
|
19
19
|
"dependencies": {
|
|
20
|
+
"@mediabunny/server": "^1.52.0",
|
|
20
21
|
"debug": "^4.3.5",
|
|
22
|
+
"mediabunny": "^1.50.8",
|
|
21
23
|
"ora": "^8.0.1",
|
|
22
24
|
"zod": "^3.23.8"
|
|
23
25
|
},
|