@editframe/assets 0.59.4 → 0.59.6
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 +238 -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 +232 -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
|
@@ -76,6 +76,19 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
76
76
|
numberOfChannels: number;
|
|
77
77
|
}>;
|
|
78
78
|
}, "strip", z.ZodTypeAny, {
|
|
79
|
+
audio: {
|
|
80
|
+
codec: string;
|
|
81
|
+
bitrate: number;
|
|
82
|
+
sampleRate: number;
|
|
83
|
+
numberOfChannels: number;
|
|
84
|
+
};
|
|
85
|
+
video: {
|
|
86
|
+
width: number;
|
|
87
|
+
height: number;
|
|
88
|
+
framerate: number;
|
|
89
|
+
codec: string;
|
|
90
|
+
bitrate: number;
|
|
91
|
+
};
|
|
79
92
|
sequenceNumber: number;
|
|
80
93
|
keyframeIntervalMs: number;
|
|
81
94
|
fromMs: number;
|
|
@@ -85,6 +98,15 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
85
98
|
alignedFromUs: number;
|
|
86
99
|
alignedToUs: number;
|
|
87
100
|
isInitSegment: boolean;
|
|
101
|
+
noVideo?: boolean | undefined;
|
|
102
|
+
noAudio?: boolean | undefined;
|
|
103
|
+
}, {
|
|
104
|
+
audio: {
|
|
105
|
+
codec: string;
|
|
106
|
+
bitrate: number;
|
|
107
|
+
sampleRate: number;
|
|
108
|
+
numberOfChannels: number;
|
|
109
|
+
};
|
|
88
110
|
video: {
|
|
89
111
|
width: number;
|
|
90
112
|
height: number;
|
|
@@ -92,15 +114,6 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
92
114
|
codec: string;
|
|
93
115
|
bitrate: number;
|
|
94
116
|
};
|
|
95
|
-
audio: {
|
|
96
|
-
codec: string;
|
|
97
|
-
bitrate: number;
|
|
98
|
-
sampleRate: number;
|
|
99
|
-
numberOfChannels: number;
|
|
100
|
-
};
|
|
101
|
-
noVideo?: boolean | undefined;
|
|
102
|
-
noAudio?: boolean | undefined;
|
|
103
|
-
}, {
|
|
104
117
|
sequenceNumber: number;
|
|
105
118
|
keyframeIntervalMs: number;
|
|
106
119
|
fromMs: number;
|
|
@@ -110,19 +123,6 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
110
123
|
alignedFromUs: number;
|
|
111
124
|
alignedToUs: number;
|
|
112
125
|
isInitSegment: boolean;
|
|
113
|
-
video: {
|
|
114
|
-
width: number;
|
|
115
|
-
height: number;
|
|
116
|
-
framerate: number;
|
|
117
|
-
codec: string;
|
|
118
|
-
bitrate: number;
|
|
119
|
-
};
|
|
120
|
-
audio: {
|
|
121
|
-
codec: string;
|
|
122
|
-
bitrate: number;
|
|
123
|
-
sampleRate: number;
|
|
124
|
-
numberOfChannels: number;
|
|
125
|
-
};
|
|
126
126
|
noVideo?: boolean | undefined;
|
|
127
127
|
noAudio?: boolean | undefined;
|
|
128
128
|
}>;
|
|
@@ -131,6 +131,19 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
131
131
|
mode: "canvas" | "screenshot";
|
|
132
132
|
strategy: "v1" | "v2";
|
|
133
133
|
encoderOptions: {
|
|
134
|
+
audio: {
|
|
135
|
+
codec: string;
|
|
136
|
+
bitrate: number;
|
|
137
|
+
sampleRate: number;
|
|
138
|
+
numberOfChannels: number;
|
|
139
|
+
};
|
|
140
|
+
video: {
|
|
141
|
+
width: number;
|
|
142
|
+
height: number;
|
|
143
|
+
framerate: number;
|
|
144
|
+
codec: string;
|
|
145
|
+
bitrate: number;
|
|
146
|
+
};
|
|
134
147
|
sequenceNumber: number;
|
|
135
148
|
keyframeIntervalMs: number;
|
|
136
149
|
fromMs: number;
|
|
@@ -140,19 +153,6 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
140
153
|
alignedFromUs: number;
|
|
141
154
|
alignedToUs: number;
|
|
142
155
|
isInitSegment: boolean;
|
|
143
|
-
video: {
|
|
144
|
-
width: number;
|
|
145
|
-
height: number;
|
|
146
|
-
framerate: number;
|
|
147
|
-
codec: string;
|
|
148
|
-
bitrate: number;
|
|
149
|
-
};
|
|
150
|
-
audio: {
|
|
151
|
-
codec: string;
|
|
152
|
-
bitrate: number;
|
|
153
|
-
sampleRate: number;
|
|
154
|
-
numberOfChannels: number;
|
|
155
|
-
};
|
|
156
156
|
noVideo?: boolean | undefined;
|
|
157
157
|
noAudio?: boolean | undefined;
|
|
158
158
|
};
|
|
@@ -163,6 +163,19 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
163
163
|
mode: "canvas" | "screenshot";
|
|
164
164
|
strategy: "v1" | "v2";
|
|
165
165
|
encoderOptions: {
|
|
166
|
+
audio: {
|
|
167
|
+
codec: string;
|
|
168
|
+
bitrate: number;
|
|
169
|
+
sampleRate: number;
|
|
170
|
+
numberOfChannels: number;
|
|
171
|
+
};
|
|
172
|
+
video: {
|
|
173
|
+
width: number;
|
|
174
|
+
height: number;
|
|
175
|
+
framerate: number;
|
|
176
|
+
codec: string;
|
|
177
|
+
bitrate: number;
|
|
178
|
+
};
|
|
166
179
|
sequenceNumber: number;
|
|
167
180
|
keyframeIntervalMs: number;
|
|
168
181
|
fromMs: number;
|
|
@@ -172,19 +185,6 @@ declare const VideoRenderOptions: z.ZodObject<{
|
|
|
172
185
|
alignedFromUs: number;
|
|
173
186
|
alignedToUs: number;
|
|
174
187
|
isInitSegment: boolean;
|
|
175
|
-
video: {
|
|
176
|
-
width: number;
|
|
177
|
-
height: number;
|
|
178
|
-
framerate: number;
|
|
179
|
-
codec: string;
|
|
180
|
-
bitrate: number;
|
|
181
|
-
};
|
|
182
|
-
audio: {
|
|
183
|
-
codec: string;
|
|
184
|
-
bitrate: number;
|
|
185
|
-
sampleRate: number;
|
|
186
|
-
numberOfChannels: number;
|
|
187
|
-
};
|
|
188
188
|
noVideo?: boolean | undefined;
|
|
189
189
|
noAudio?: boolean | undefined;
|
|
190
190
|
};
|
package/dist/index.cjs
CHANGED
|
@@ -8,10 +8,13 @@ const require_generateScrubTrack = require("./tasks/generateScrubTrack.cjs");
|
|
|
8
8
|
const require_findOrCreateCaptions = require("./tasks/findOrCreateCaptions.cjs");
|
|
9
9
|
const require_cacheImage = require("./tasks/cacheImage.cjs");
|
|
10
10
|
const require_VideoRenderOptions = require("./VideoRenderOptions.cjs");
|
|
11
|
+
const require_MediabunnyIngest = require("./mediabunny-ingest/MediabunnyIngest.cjs");
|
|
11
12
|
exports.PacketProbe = require_Probe.PacketProbe;
|
|
12
13
|
exports.Probe = require_Probe.Probe;
|
|
13
14
|
exports.VideoRenderOptions = require_VideoRenderOptions.VideoRenderOptions;
|
|
14
15
|
exports.cacheImage = require_cacheImage.cacheImage;
|
|
16
|
+
exports.extractAudioTrack = require_MediabunnyIngest.extractAudioTrack;
|
|
17
|
+
exports.extractVideoTrack = require_MediabunnyIngest.extractVideoTrack;
|
|
15
18
|
exports.findOrCreateCaptions = require_findOrCreateCaptions.findOrCreateCaptions;
|
|
16
19
|
exports.generateCaptionDataFromPath = require_findOrCreateCaptions.generateCaptionDataFromPath;
|
|
17
20
|
exports.generateFragmentIndex = require_generateFragmentIndex.generateFragmentIndex;
|
|
@@ -25,3 +28,5 @@ exports.md5Buffer = require_md5.md5Buffer;
|
|
|
25
28
|
exports.md5Directory = require_md5.md5Directory;
|
|
26
29
|
exports.md5FilePath = require_md5.md5FilePath;
|
|
27
30
|
exports.md5ReadStream = require_md5.md5ReadStream;
|
|
31
|
+
exports.openIngestInput = require_MediabunnyIngest.openIngestInput;
|
|
32
|
+
exports.selectIngestTracks = require_MediabunnyIngest.selectIngestTracks;
|
package/dist/index.d.cts
CHANGED
|
@@ -8,4 +8,5 @@ import { generateScrubTrack, generateScrubTrackFromPath } from "./tasks/generate
|
|
|
8
8
|
import { findOrCreateCaptions, generateCaptionDataFromPath } from "./tasks/findOrCreateCaptions.cjs";
|
|
9
9
|
import { cacheImage } from "./tasks/cacheImage.cjs";
|
|
10
10
|
import { VideoRenderOptions } from "./VideoRenderOptions.cjs";
|
|
11
|
-
|
|
11
|
+
import { IngestSource, SelectedIngestTracks, TrackExtractionResult, extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks } from "./mediabunny-ingest/MediabunnyIngest.cjs";
|
|
12
|
+
export { type AudioStreamSchema, type AudioTrackFragmentIndex, type IngestSource, PacketProbe, type PacketProbeSchema, Probe, type ProbeSchema, type SelectedIngestTracks, type StreamSchema, type TaskResult, type TrackExtractionResult, type TrackFragmentIndex, type TrackSegment, VideoRenderOptions, type VideoStreamSchema, type VideoTrackFragmentIndex, cacheImage, extractAudioTrack, extractVideoTrack, findOrCreateCaptions, generateCaptionDataFromPath, generateFragmentIndex, generateScrubTrack, generateScrubTrackFromPath, generateTrack, generateTrackFragmentIndex, generateTrackFragmentIndexFromPath, generateTrackFromPath, md5Buffer, md5Directory, md5FilePath, md5ReadStream, openIngestInput, selectIngestTracks };
|
package/dist/index.d.ts
CHANGED
|
@@ -8,4 +8,5 @@ import { generateScrubTrack, generateScrubTrackFromPath } from "./tasks/generate
|
|
|
8
8
|
import { findOrCreateCaptions, generateCaptionDataFromPath } from "./tasks/findOrCreateCaptions.js";
|
|
9
9
|
import { cacheImage } from "./tasks/cacheImage.js";
|
|
10
10
|
import { VideoRenderOptions } from "./VideoRenderOptions.js";
|
|
11
|
-
|
|
11
|
+
import { IngestSource, SelectedIngestTracks, TrackExtractionResult, extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks } from "./mediabunny-ingest/MediabunnyIngest.js";
|
|
12
|
+
export { type AudioStreamSchema, type AudioTrackFragmentIndex, type IngestSource, PacketProbe, type PacketProbeSchema, Probe, type ProbeSchema, type SelectedIngestTracks, type StreamSchema, type TaskResult, type TrackExtractionResult, type TrackFragmentIndex, type TrackSegment, VideoRenderOptions, type VideoStreamSchema, type VideoTrackFragmentIndex, cacheImage, extractAudioTrack, extractVideoTrack, findOrCreateCaptions, generateCaptionDataFromPath, generateFragmentIndex, generateScrubTrack, generateScrubTrackFromPath, generateTrack, generateTrackFragmentIndex, generateTrackFragmentIndexFromPath, generateTrackFromPath, md5Buffer, md5Directory, md5FilePath, md5ReadStream, openIngestInput, selectIngestTracks };
|
package/dist/index.js
CHANGED
|
@@ -7,4 +7,5 @@ import { generateScrubTrack, generateScrubTrackFromPath } from "./tasks/generate
|
|
|
7
7
|
import { findOrCreateCaptions, generateCaptionDataFromPath } from "./tasks/findOrCreateCaptions.js";
|
|
8
8
|
import { cacheImage } from "./tasks/cacheImage.js";
|
|
9
9
|
import { VideoRenderOptions } from "./VideoRenderOptions.js";
|
|
10
|
-
|
|
10
|
+
import { extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks } from "./mediabunny-ingest/MediabunnyIngest.js";
|
|
11
|
+
export { PacketProbe, Probe, VideoRenderOptions, cacheImage, extractAudioTrack, extractVideoTrack, findOrCreateCaptions, generateCaptionDataFromPath, generateFragmentIndex, generateScrubTrack, generateScrubTrackFromPath, generateTrack, generateTrackFragmentIndex, generateTrackFragmentIndexFromPath, generateTrackFromPath, md5Buffer, md5Directory, md5FilePath, md5ReadStream, openIngestInput, selectIngestTracks };
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
const require_runtime = require("../_virtual/_rolldown/runtime.cjs");
|
|
2
|
+
const require_bitstream = require("./bitstream.cjs");
|
|
3
|
+
let debug = require("debug");
|
|
4
|
+
debug = require_runtime.__toESM(debug);
|
|
5
|
+
let node_stream = require("node:stream");
|
|
6
|
+
let node_crypto = require("node:crypto");
|
|
7
|
+
node_crypto = require_runtime.__toESM(node_crypto);
|
|
8
|
+
let _mediabunny_server = require("@mediabunny/server");
|
|
9
|
+
let mediabunny = require("mediabunny");
|
|
10
|
+
//#region src/mediabunny-ingest/MediabunnyIngest.ts
|
|
11
|
+
const log = (0, debug.default)("ef:assets:mediabunny-ingest");
|
|
12
|
+
(0, _mediabunny_server.registerMediabunnyServer)();
|
|
13
|
+
/** Opens a mediabunny `Input` over a URL or local file path, auto-detecting the container/HLS format. */
|
|
14
|
+
function openIngestInput(source) {
|
|
15
|
+
return new mediabunny.Input({
|
|
16
|
+
formats: mediabunny.ALL_FORMATS,
|
|
17
|
+
source: source.type === "url" ? new mediabunny.UrlSource(source.url) : new mediabunny.FilePathSource(source.path)
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Selects the best video track and its best pairable audio track from an
|
|
22
|
+
* input (matching the "ingest only the best audio and video track" ingest
|
|
23
|
+
* policy). For plain progressive files this is just "the" video/audio track;
|
|
24
|
+
* for HLS master playlists, mediabunny's `getPrimaryVideoTrack`/
|
|
25
|
+
* `getPrimaryAudioTrack` already implement bitrate-based ABR selection.
|
|
26
|
+
*
|
|
27
|
+
* Throws if the source has no usable tracks, or if any selected track is
|
|
28
|
+
* still live (open-ended) — ingestion only supports VOD sources.
|
|
29
|
+
*/
|
|
30
|
+
async function selectIngestTracks(input) {
|
|
31
|
+
const video = await input.getPrimaryVideoTrack();
|
|
32
|
+
const audio = video ? await video.getPrimaryPairableAudioTrack() ?? await input.getPrimaryAudioTrack() : await input.getPrimaryAudioTrack();
|
|
33
|
+
if (!video && !audio) throw new Error("No video or audio tracks found in source");
|
|
34
|
+
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");
|
|
35
|
+
return {
|
|
36
|
+
video,
|
|
37
|
+
audio
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function bridgeToNodeReadable() {
|
|
41
|
+
const pass = new node_stream.PassThrough();
|
|
42
|
+
return {
|
|
43
|
+
readable: pass,
|
|
44
|
+
target: new mediabunny.AppendOnlyStreamTarget(node_stream.Writable.toWeb(pass))
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Zero-bases a track's packet timestamps around its own decode-order first
|
|
49
|
+
* packet, for two reasons:
|
|
50
|
+
*
|
|
51
|
+
* 1. Mediabunny's MP4 muxer rejects packets with negative timestamps outright.
|
|
52
|
+
* Negative first-packet timestamps are common and legitimate — e.g. AAC
|
|
53
|
+
* encoder priming samples, or B-frame reordering — and ffmpeg silently
|
|
54
|
+
* normalizes them by default (`-avoid_negative_ts make_zero`) when remuxing.
|
|
55
|
+
*
|
|
56
|
+
* 2. For *positive* first-packet timestamps (e.g. a source with a non-zero
|
|
57
|
+
* container-level `format.start_time`, such as Mux's 90kHz start-time
|
|
58
|
+
* offset), mediabunny's fragmented-MP4 muxer does NOT zero-base decode
|
|
59
|
+
* timestamps the way its non-fragmented muxer (and the old ffmpeg-CLI
|
|
60
|
+
* ingest's remux) does — that normalization is explicitly gated behind
|
|
61
|
+
* `!isFragmented` in mediabunny's isobmff muxer. Left unshifted, that
|
|
62
|
+
* container-level offset leaks into the produced track's absolute
|
|
63
|
+
* timestamps, which `generateFragmentIndex` reads assuming DTS starts at
|
|
64
|
+
* 0 (see its "After ffmpeg re-mux, DTS is always 0" comment) — corrupting
|
|
65
|
+
* its detected `startTimeOffsetMs` by exactly that leaked amount.
|
|
66
|
+
*
|
|
67
|
+
* Shifting every packet by `-firstTimestamp` handles both cases and is a
|
|
68
|
+
* no-op for the common case where the track already starts at t=0. It does
|
|
69
|
+
* NOT erase a genuine B-frame reordering offset (CTS > DTS for the first
|
|
70
|
+
* decode-order packet): that offset is a *relative* property between a
|
|
71
|
+
* packet's own timestamp and its decode-order position, which a uniform
|
|
72
|
+
* shift preserves.
|
|
73
|
+
*/
|
|
74
|
+
async function getTimestampOffset(track) {
|
|
75
|
+
return -await track.getFirstTimestamp();
|
|
76
|
+
}
|
|
77
|
+
function md5Digest(hash) {
|
|
78
|
+
const hex = hash.digest("hex");
|
|
79
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extracts a single video track into a fragmented single-track MP4 stream via
|
|
83
|
+
* packet-copy transmux (no re-encoding — matches the previous ffmpeg `-c:v copy`
|
|
84
|
+
* behavior). SEI NAL units (type 6) are stripped from every packet, mirroring the
|
|
85
|
+
* previous `-bsf:v filter_units=remove_types=6` ffmpeg filter.
|
|
86
|
+
*/
|
|
87
|
+
function extractVideoTrack(track) {
|
|
88
|
+
const { readable, target } = bridgeToNodeReadable();
|
|
89
|
+
const hash = node_crypto.default.createHash("md5");
|
|
90
|
+
const digestDeferred = createDeferred();
|
|
91
|
+
(async () => {
|
|
92
|
+
const codec = await track.getCodec();
|
|
93
|
+
if (!codec) throw new Error("Video track has no known codec");
|
|
94
|
+
const decoderConfig = await track.getDecoderConfig();
|
|
95
|
+
const isAnnexB = decoderConfig?.description === void 0;
|
|
96
|
+
const tsOffset = await getTimestampOffset(track);
|
|
97
|
+
const output = new mediabunny.Output({
|
|
98
|
+
format: new mediabunny.Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
99
|
+
target
|
|
100
|
+
});
|
|
101
|
+
const source = new mediabunny.EncodedVideoPacketSource(codec);
|
|
102
|
+
output.addVideoTrack(source);
|
|
103
|
+
await output.start();
|
|
104
|
+
const sink = new mediabunny.EncodedPacketSink(track);
|
|
105
|
+
let isFirst = true;
|
|
106
|
+
for await (const packet of sink.packets()) {
|
|
107
|
+
hash.update(packet.data);
|
|
108
|
+
const strippedData = require_bitstream.stripSeiNalUnits(packet.data, isAnnexB);
|
|
109
|
+
const outPacket = strippedData !== packet.data || tsOffset !== 0 ? packet.clone({
|
|
110
|
+
data: strippedData,
|
|
111
|
+
timestamp: packet.timestamp + tsOffset
|
|
112
|
+
}) : packet;
|
|
113
|
+
await source.add(outPacket, isFirst && decoderConfig ? { decoderConfig } : void 0);
|
|
114
|
+
isFirst = false;
|
|
115
|
+
}
|
|
116
|
+
await output.finalize();
|
|
117
|
+
})().then(() => digestDeferred.resolve(md5Digest(hash))).catch((error) => {
|
|
118
|
+
digestDeferred.reject(error);
|
|
119
|
+
readable.emit("error", error);
|
|
120
|
+
readable.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
121
|
+
});
|
|
122
|
+
return {
|
|
123
|
+
stream: readable,
|
|
124
|
+
digest: digestDeferred.promise
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Extracts a single audio track into a fragmented single-track MP4 stream.
|
|
129
|
+
*
|
|
130
|
+
* - AAC-raw sources (description present) are packet-copied directly.
|
|
131
|
+
* - AAC-ADTS sources (typical of MPEG-TS/HLS demuxed AAC) have their ADTS
|
|
132
|
+
* headers stripped and an AudioSpecificConfig synthesized, mirroring the
|
|
133
|
+
* previous `-bsf:a aac_adtstoasc` ffmpeg filter.
|
|
134
|
+
* - Any other codec is decoded and re-encoded to AAC (192kbps), mirroring the
|
|
135
|
+
* previous `-c:a aac -b:a 192k -ar 48000` ffmpeg re-encode fallback.
|
|
136
|
+
*/
|
|
137
|
+
function extractAudioTrack(track) {
|
|
138
|
+
const { readable, target } = bridgeToNodeReadable();
|
|
139
|
+
const hash = node_crypto.default.createHash("md5");
|
|
140
|
+
const digestDeferred = createDeferred();
|
|
141
|
+
(async () => {
|
|
142
|
+
const codec = await track.getCodec();
|
|
143
|
+
const decoderConfig = await track.getDecoderConfig();
|
|
144
|
+
const isAdtsAac = codec === "aac" && decoderConfig?.description === void 0;
|
|
145
|
+
const needsReencode = codec !== "aac";
|
|
146
|
+
const tsOffset = await getTimestampOffset(track);
|
|
147
|
+
const output = new mediabunny.Output({
|
|
148
|
+
format: new mediabunny.Mp4OutputFormat({ fastStart: "fragmented" }),
|
|
149
|
+
target
|
|
150
|
+
});
|
|
151
|
+
if (needsReencode) {
|
|
152
|
+
const source = new mediabunny.AudioSampleSource({
|
|
153
|
+
codec: "aac",
|
|
154
|
+
bitrate: 192e3
|
|
155
|
+
});
|
|
156
|
+
output.addAudioTrack(source);
|
|
157
|
+
await output.start();
|
|
158
|
+
const hashPromise = (async () => {
|
|
159
|
+
const packetSink = new mediabunny.EncodedPacketSink(track);
|
|
160
|
+
for await (const packet of packetSink.packets()) hash.update(packet.data);
|
|
161
|
+
})();
|
|
162
|
+
const sampleSink = new mediabunny.AudioSampleSink(track);
|
|
163
|
+
for await (const sample of sampleSink.samples()) try {
|
|
164
|
+
if (tsOffset === 0) {
|
|
165
|
+
await source.add(sample);
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
const shifted = mediabunny.AudioSample.fromAudioBuffer(sample.toAudioBuffer(), sample.timestamp + tsOffset);
|
|
169
|
+
for (const out of shifted) try {
|
|
170
|
+
await source.add(out);
|
|
171
|
+
} finally {
|
|
172
|
+
out.close();
|
|
173
|
+
}
|
|
174
|
+
} finally {
|
|
175
|
+
sample.close();
|
|
176
|
+
}
|
|
177
|
+
await hashPromise;
|
|
178
|
+
await output.finalize();
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const source = new mediabunny.EncodedAudioPacketSource("aac");
|
|
182
|
+
output.addAudioTrack(source);
|
|
183
|
+
await output.start();
|
|
184
|
+
const sink = new mediabunny.EncodedPacketSink(track);
|
|
185
|
+
const [sampleRate, numberOfChannels] = await Promise.all([track.getSampleRate(), track.getNumberOfChannels()]);
|
|
186
|
+
let isFirst = true;
|
|
187
|
+
for await (const packet of sink.packets()) {
|
|
188
|
+
hash.update(packet.data);
|
|
189
|
+
if (!isAdtsAac) {
|
|
190
|
+
const outPacket = tsOffset !== 0 ? packet.clone({ timestamp: packet.timestamp + tsOffset }) : packet;
|
|
191
|
+
await source.add(outPacket, isFirst && decoderConfig ? { decoderConfig } : void 0);
|
|
192
|
+
isFirst = false;
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
const raw = require_bitstream.adtsToRawAac(packet.data);
|
|
196
|
+
const outPacket = packet.clone({
|
|
197
|
+
data: raw.data,
|
|
198
|
+
timestamp: packet.timestamp + tsOffset
|
|
199
|
+
});
|
|
200
|
+
await source.add(outPacket, isFirst ? { decoderConfig: {
|
|
201
|
+
codec: raw.codec,
|
|
202
|
+
sampleRate,
|
|
203
|
+
numberOfChannels,
|
|
204
|
+
description: raw.audioSpecificConfig
|
|
205
|
+
} } : void 0);
|
|
206
|
+
isFirst = false;
|
|
207
|
+
}
|
|
208
|
+
await output.finalize();
|
|
209
|
+
})().then(() => digestDeferred.resolve(md5Digest(hash))).catch((error) => {
|
|
210
|
+
digestDeferred.reject(error);
|
|
211
|
+
readable.emit("error", error);
|
|
212
|
+
readable.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
213
|
+
});
|
|
214
|
+
return {
|
|
215
|
+
stream: readable,
|
|
216
|
+
digest: digestDeferred.promise
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
function createDeferred() {
|
|
220
|
+
let resolve;
|
|
221
|
+
let reject;
|
|
222
|
+
return {
|
|
223
|
+
promise: new Promise((res, rej) => {
|
|
224
|
+
resolve = res;
|
|
225
|
+
reject = rej;
|
|
226
|
+
}),
|
|
227
|
+
resolve,
|
|
228
|
+
reject
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
log("Mediabunny ingest module loaded");
|
|
232
|
+
//#endregion
|
|
233
|
+
exports.extractAudioTrack = extractAudioTrack;
|
|
234
|
+
exports.extractVideoTrack = extractVideoTrack;
|
|
235
|
+
exports.openIngestInput = openIngestInput;
|
|
236
|
+
exports.selectIngestTracks = selectIngestTracks;
|
|
237
|
+
|
|
238
|
+
//# sourceMappingURL=MediabunnyIngest.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediabunnyIngest.cjs","names":["Input","ALL_FORMATS","UrlSource","FilePathSource","PassThrough","AppendOnlyStreamTarget","NodeWritable","crypto","Output","Mp4OutputFormat","EncodedVideoPacketSource","EncodedPacketSink","stripSeiNalUnits","AudioSampleSource","AudioSampleSink","AudioSample","EncodedAudioPacketSource","adtsToRawAac"],"sources":["../../src/mediabunny-ingest/MediabunnyIngest.ts"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { Input, InputAudioTrack, InputVideoTrack } from "mediabunny";
|
|
3
|
+
|
|
4
|
+
//#region src/mediabunny-ingest/MediabunnyIngest.d.ts
|
|
5
|
+
type IngestSource = {
|
|
6
|
+
type: "url";
|
|
7
|
+
url: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: "path";
|
|
10
|
+
path: string;
|
|
11
|
+
};
|
|
12
|
+
/** Opens a mediabunny `Input` over a URL or local file path, auto-detecting the container/HLS format. */
|
|
13
|
+
declare function openIngestInput(source: IngestSource): Input;
|
|
14
|
+
interface SelectedIngestTracks {
|
|
15
|
+
video: InputVideoTrack | null;
|
|
16
|
+
audio: InputAudioTrack | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Selects the best video track and its best pairable audio track from an
|
|
20
|
+
* input (matching the "ingest only the best audio and video track" ingest
|
|
21
|
+
* policy). For plain progressive files this is just "the" video/audio track;
|
|
22
|
+
* for HLS master playlists, mediabunny's `getPrimaryVideoTrack`/
|
|
23
|
+
* `getPrimaryAudioTrack` already implement bitrate-based ABR selection.
|
|
24
|
+
*
|
|
25
|
+
* Throws if the source has no usable tracks, or if any selected track is
|
|
26
|
+
* still live (open-ended) — ingestion only supports VOD sources.
|
|
27
|
+
*/
|
|
28
|
+
declare function selectIngestTracks(input: Input): Promise<SelectedIngestTracks>;
|
|
29
|
+
interface TrackExtractionResult {
|
|
30
|
+
/** A single-track fragmented MP4 stream, ready to be written to storage / fed to generateFragmentIndex. */
|
|
31
|
+
stream: Readable;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves to an md5 digest (formatted as a dashed UUID, matching the rest of the
|
|
34
|
+
* codebase's md5 convention) of this track's raw source packet bytes, once fully read.
|
|
35
|
+
* For URL-sourced ingests there is no bulk-downloaded file to hash, so this is used as
|
|
36
|
+
* the file's content fingerprint instead of a whole-file md5.
|
|
37
|
+
*/
|
|
38
|
+
digest: Promise<string>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts a single video track into a fragmented single-track MP4 stream via
|
|
42
|
+
* packet-copy transmux (no re-encoding — matches the previous ffmpeg `-c:v copy`
|
|
43
|
+
* behavior). SEI NAL units (type 6) are stripped from every packet, mirroring the
|
|
44
|
+
* previous `-bsf:v filter_units=remove_types=6` ffmpeg filter.
|
|
45
|
+
*/
|
|
46
|
+
declare function extractVideoTrack(track: InputVideoTrack): TrackExtractionResult;
|
|
47
|
+
/**
|
|
48
|
+
* Extracts a single audio track into a fragmented single-track MP4 stream.
|
|
49
|
+
*
|
|
50
|
+
* - AAC-raw sources (description present) are packet-copied directly.
|
|
51
|
+
* - AAC-ADTS sources (typical of MPEG-TS/HLS demuxed AAC) have their ADTS
|
|
52
|
+
* headers stripped and an AudioSpecificConfig synthesized, mirroring the
|
|
53
|
+
* previous `-bsf:a aac_adtstoasc` ffmpeg filter.
|
|
54
|
+
* - Any other codec is decoded and re-encoded to AAC (192kbps), mirroring the
|
|
55
|
+
* previous `-c:a aac -b:a 192k -ar 48000` ffmpeg re-encode fallback.
|
|
56
|
+
*/
|
|
57
|
+
declare function extractAudioTrack(track: InputAudioTrack): TrackExtractionResult;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { IngestSource, SelectedIngestTracks, TrackExtractionResult, extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks };
|
|
60
|
+
//# sourceMappingURL=MediabunnyIngest.d.cts.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Readable } from "node:stream";
|
|
2
|
+
import { Input, InputAudioTrack, InputVideoTrack } from "mediabunny";
|
|
3
|
+
|
|
4
|
+
//#region src/mediabunny-ingest/MediabunnyIngest.d.ts
|
|
5
|
+
type IngestSource = {
|
|
6
|
+
type: "url";
|
|
7
|
+
url: string;
|
|
8
|
+
} | {
|
|
9
|
+
type: "path";
|
|
10
|
+
path: string;
|
|
11
|
+
};
|
|
12
|
+
/** Opens a mediabunny `Input` over a URL or local file path, auto-detecting the container/HLS format. */
|
|
13
|
+
declare function openIngestInput(source: IngestSource): Input;
|
|
14
|
+
interface SelectedIngestTracks {
|
|
15
|
+
video: InputVideoTrack | null;
|
|
16
|
+
audio: InputAudioTrack | null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Selects the best video track and its best pairable audio track from an
|
|
20
|
+
* input (matching the "ingest only the best audio and video track" ingest
|
|
21
|
+
* policy). For plain progressive files this is just "the" video/audio track;
|
|
22
|
+
* for HLS master playlists, mediabunny's `getPrimaryVideoTrack`/
|
|
23
|
+
* `getPrimaryAudioTrack` already implement bitrate-based ABR selection.
|
|
24
|
+
*
|
|
25
|
+
* Throws if the source has no usable tracks, or if any selected track is
|
|
26
|
+
* still live (open-ended) — ingestion only supports VOD sources.
|
|
27
|
+
*/
|
|
28
|
+
declare function selectIngestTracks(input: Input): Promise<SelectedIngestTracks>;
|
|
29
|
+
interface TrackExtractionResult {
|
|
30
|
+
/** A single-track fragmented MP4 stream, ready to be written to storage / fed to generateFragmentIndex. */
|
|
31
|
+
stream: Readable;
|
|
32
|
+
/**
|
|
33
|
+
* Resolves to an md5 digest (formatted as a dashed UUID, matching the rest of the
|
|
34
|
+
* codebase's md5 convention) of this track's raw source packet bytes, once fully read.
|
|
35
|
+
* For URL-sourced ingests there is no bulk-downloaded file to hash, so this is used as
|
|
36
|
+
* the file's content fingerprint instead of a whole-file md5.
|
|
37
|
+
*/
|
|
38
|
+
digest: Promise<string>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Extracts a single video track into a fragmented single-track MP4 stream via
|
|
42
|
+
* packet-copy transmux (no re-encoding — matches the previous ffmpeg `-c:v copy`
|
|
43
|
+
* behavior). SEI NAL units (type 6) are stripped from every packet, mirroring the
|
|
44
|
+
* previous `-bsf:v filter_units=remove_types=6` ffmpeg filter.
|
|
45
|
+
*/
|
|
46
|
+
declare function extractVideoTrack(track: InputVideoTrack): TrackExtractionResult;
|
|
47
|
+
/**
|
|
48
|
+
* Extracts a single audio track into a fragmented single-track MP4 stream.
|
|
49
|
+
*
|
|
50
|
+
* - AAC-raw sources (description present) are packet-copied directly.
|
|
51
|
+
* - AAC-ADTS sources (typical of MPEG-TS/HLS demuxed AAC) have their ADTS
|
|
52
|
+
* headers stripped and an AudioSpecificConfig synthesized, mirroring the
|
|
53
|
+
* previous `-bsf:a aac_adtstoasc` ffmpeg filter.
|
|
54
|
+
* - Any other codec is decoded and re-encoded to AAC (192kbps), mirroring the
|
|
55
|
+
* previous `-c:a aac -b:a 192k -ar 48000` ffmpeg re-encode fallback.
|
|
56
|
+
*/
|
|
57
|
+
declare function extractAudioTrack(track: InputAudioTrack): TrackExtractionResult;
|
|
58
|
+
//#endregion
|
|
59
|
+
export { IngestSource, SelectedIngestTracks, TrackExtractionResult, extractAudioTrack, extractVideoTrack, openIngestInput, selectIngestTracks };
|
|
60
|
+
//# sourceMappingURL=MediabunnyIngest.d.ts.map
|