avbridge 2.3.0 → 2.6.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.
- package/CHANGELOG.md +114 -0
- package/dist/{chunk-6UUT4BEA.cjs → chunk-2IJ66NTD.cjs} +13 -20
- package/dist/chunk-2IJ66NTD.cjs.map +1 -0
- package/dist/{chunk-XKPSTC34.cjs → chunk-2XW2O3YI.cjs} +5 -20
- package/dist/chunk-2XW2O3YI.cjs.map +1 -0
- package/dist/chunk-5KVLE6YI.js +167 -0
- package/dist/chunk-5KVLE6YI.js.map +1 -0
- package/dist/{chunk-7RGG6ME7.cjs → chunk-6SOFJV44.cjs} +422 -688
- package/dist/chunk-6SOFJV44.cjs.map +1 -0
- package/dist/{chunk-2PGRFCWB.js → chunk-CPJLFFCC.js} +8 -18
- package/dist/chunk-CPJLFFCC.js.map +1 -0
- package/dist/chunk-CPZ7PXAM.cjs +240 -0
- package/dist/chunk-CPZ7PXAM.cjs.map +1 -0
- package/dist/{chunk-QQXBPW72.js → chunk-E76AMWI4.js} +4 -18
- package/dist/chunk-E76AMWI4.js.map +1 -0
- package/dist/chunk-LUFA47FP.js +19 -0
- package/dist/chunk-LUFA47FP.js.map +1 -0
- package/dist/{chunk-NV7ILLWH.js → chunk-OGYHFY6K.js} +404 -665
- package/dist/chunk-OGYHFY6K.js.map +1 -0
- package/dist/chunk-Q2VUO52Z.cjs +374 -0
- package/dist/chunk-Q2VUO52Z.cjs.map +1 -0
- package/dist/chunk-QDJLQR53.cjs +22 -0
- package/dist/chunk-QDJLQR53.cjs.map +1 -0
- package/dist/chunk-S4WAZC2T.cjs +173 -0
- package/dist/chunk-S4WAZC2T.cjs.map +1 -0
- package/dist/chunk-SMH6IOP2.js +368 -0
- package/dist/chunk-SMH6IOP2.js.map +1 -0
- package/dist/chunk-SR3MPV4D.js +237 -0
- package/dist/chunk-SR3MPV4D.js.map +1 -0
- package/dist/chunk-X2K3GIWE.js +235 -0
- package/dist/chunk-X2K3GIWE.js.map +1 -0
- package/dist/chunk-ZCUXHW55.cjs +242 -0
- package/dist/chunk-ZCUXHW55.cjs.map +1 -0
- package/dist/element-browser.js +883 -492
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +88 -6
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +51 -1
- package/dist/element.d.ts +51 -1
- package/dist/element.js +87 -5
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +523 -393
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +494 -366
- package/dist/index.js.map +1 -1
- package/dist/libav-demux-H2GS46GH.cjs +27 -0
- package/dist/libav-demux-H2GS46GH.cjs.map +1 -0
- package/dist/libav-demux-OWZ4T2YW.js +6 -0
- package/dist/libav-demux-OWZ4T2YW.js.map +1 -0
- package/dist/{libav-import-GST2AMPL.cjs → libav-import-2ZVKV2E7.cjs} +2 -2
- package/dist/{libav-import-GST2AMPL.cjs.map → libav-import-2ZVKV2E7.cjs.map} +1 -1
- package/dist/{libav-import-2JURFHEW.js → libav-import-6MGLCXVQ.js} +2 -2
- package/dist/{libav-import-2JURFHEW.js.map → libav-import-6MGLCXVQ.js.map} +1 -1
- package/dist/{player-B6WB74RD.d.ts → player-DGXeCNfD.d.cts} +41 -1
- package/dist/{player-B6WB74RD.d.cts → player-DGXeCNfD.d.ts} +41 -1
- package/dist/player.cjs +731 -472
- package/dist/player.cjs.map +1 -1
- package/dist/player.d.cts +229 -120
- package/dist/player.d.ts +229 -120
- package/dist/player.js +710 -451
- package/dist/player.js.map +1 -1
- package/dist/remux-OBSMIENG.cjs +35 -0
- package/dist/remux-OBSMIENG.cjs.map +1 -0
- package/dist/remux-WBYIZBBX.js +10 -0
- package/dist/remux-WBYIZBBX.js.map +1 -0
- package/dist/source-4TZ6KMNV.js +4 -0
- package/dist/{source-F656KYYV.js.map → source-4TZ6KMNV.js.map} +1 -1
- package/dist/source-7YLO6E7X.cjs +29 -0
- package/dist/{source-73CAH6HW.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
- package/dist/source-MTX5ELUZ.js +4 -0
- package/dist/{source-QJR3OHTW.js.map → source-MTX5ELUZ.js.map} +1 -1
- package/dist/source-VFLXLOCN.cjs +29 -0
- package/dist/{source-VB74JQ7Z.cjs.map → source-VFLXLOCN.cjs.map} +1 -1
- package/dist/subtitles-4T74JRGT.js +4 -0
- package/dist/subtitles-4T74JRGT.js.map +1 -0
- package/dist/subtitles-QUH4LPI4.cjs +29 -0
- package/dist/subtitles-QUH4LPI4.cjs.map +1 -0
- package/package.json +1 -1
- package/src/convert/remux.ts +1 -35
- package/src/convert/transcode-libav.ts +691 -0
- package/src/convert/transcode.ts +12 -4
- package/src/element/avbridge-player.ts +100 -0
- package/src/element/avbridge-video.ts +140 -3
- package/src/element/player-styles.ts +12 -0
- package/src/errors.ts +6 -0
- package/src/player.ts +15 -16
- package/src/strategies/fallback/decoder.ts +96 -173
- package/src/strategies/fallback/index.ts +46 -2
- package/src/strategies/fallback/libav-import.ts +9 -1
- package/src/strategies/fallback/video-renderer.ts +107 -0
- package/src/strategies/hybrid/decoder.ts +88 -180
- package/src/strategies/hybrid/index.ts +35 -2
- package/src/strategies/native.ts +6 -3
- package/src/strategies/remux/index.ts +14 -2
- package/src/strategies/remux/pipeline.ts +72 -12
- package/src/subtitles/render.ts +8 -0
- package/src/types.ts +32 -0
- package/src/util/libav-demux.ts +405 -0
- package/src/util/time-ranges.ts +40 -0
- package/dist/chunk-2PGRFCWB.js.map +0 -1
- package/dist/chunk-6UUT4BEA.cjs.map +0 -1
- package/dist/chunk-7RGG6ME7.cjs.map +0 -1
- package/dist/chunk-NV7ILLWH.js.map +0 -1
- package/dist/chunk-QQXBPW72.js.map +0 -1
- package/dist/chunk-XKPSTC34.cjs.map +0 -1
- package/dist/source-73CAH6HW.cjs +0 -28
- package/dist/source-F656KYYV.js +0 -3
- package/dist/source-QJR3OHTW.js +0 -3
- package/dist/source-VB74JQ7Z.cjs +0 -28
package/dist/index.cjs
CHANGED
|
@@ -1,401 +1,523 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkQ2VUO52Z_cjs = require('./chunk-Q2VUO52Z.cjs');
|
|
4
|
+
var chunk6SOFJV44_cjs = require('./chunk-6SOFJV44.cjs');
|
|
5
|
+
var chunkS4WAZC2T_cjs = require('./chunk-S4WAZC2T.cjs');
|
|
6
|
+
var chunkZCUXHW55_cjs = require('./chunk-ZCUXHW55.cjs');
|
|
7
|
+
var chunk2IJ66NTD_cjs = require('./chunk-2IJ66NTD.cjs');
|
|
8
|
+
require('./chunk-QDJLQR53.cjs');
|
|
9
|
+
require('./chunk-CPZ7PXAM.cjs');
|
|
10
|
+
require('./chunk-Z33SBWL5.cjs');
|
|
6
11
|
require('./chunk-G4APZMCP.cjs');
|
|
7
12
|
require('./chunk-F3LQJKXK.cjs');
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
if (bytes.length >= 4 && bytes[0] === 0 && bytes[1] === 0 && bytes[2] === 0 && bytes[3] === 1) return true;
|
|
13
|
-
return false;
|
|
14
|
+
// src/convert/transcode-libav.ts
|
|
15
|
+
function isLibavTranscodeContainer(container) {
|
|
16
|
+
return container === "avi" || container === "asf" || container === "flv" || container === "rm";
|
|
14
17
|
}
|
|
15
|
-
function
|
|
16
|
-
const length = bytes.length;
|
|
17
|
-
let i = 0;
|
|
18
|
-
let nalStart = -1;
|
|
19
|
-
while (i < length) {
|
|
20
|
-
let scLen = 0;
|
|
21
|
-
if (i + 3 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 0 && bytes[i + 3] === 1) {
|
|
22
|
-
scLen = 4;
|
|
23
|
-
} else if (i + 2 < length && bytes[i] === 0 && bytes[i + 1] === 0 && bytes[i + 2] === 1) {
|
|
24
|
-
scLen = 3;
|
|
25
|
-
}
|
|
26
|
-
if (scLen > 0) {
|
|
27
|
-
if (nalStart >= 0) {
|
|
28
|
-
yield bytes.subarray(nalStart, i);
|
|
29
|
-
}
|
|
30
|
-
nalStart = i + scLen;
|
|
31
|
-
i += scLen;
|
|
32
|
-
} else {
|
|
33
|
-
i += 1;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
if (nalStart >= 0 && nalStart < length) {
|
|
37
|
-
yield bytes.subarray(nalStart, length);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function annexBToAvcc(annexB) {
|
|
41
|
-
const nalus = [];
|
|
42
|
-
let total = 0;
|
|
43
|
-
for (const nal of iterateAnnexBNalus(annexB)) {
|
|
44
|
-
nalus.push(nal);
|
|
45
|
-
total += 4 + nal.length;
|
|
46
|
-
}
|
|
47
|
-
const out = new Uint8Array(total);
|
|
48
|
-
let off = 0;
|
|
49
|
-
for (const nal of nalus) {
|
|
50
|
-
const len = nal.length;
|
|
51
|
-
out[off++] = len >>> 24 & 255;
|
|
52
|
-
out[off++] = len >>> 16 & 255;
|
|
53
|
-
out[off++] = len >>> 8 & 255;
|
|
54
|
-
out[off++] = len & 255;
|
|
55
|
-
out.set(nal, off);
|
|
56
|
-
off += len;
|
|
57
|
-
}
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// src/convert/remux.ts
|
|
62
|
-
var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
63
|
-
"mp4",
|
|
64
|
-
"mov",
|
|
65
|
-
"mkv",
|
|
66
|
-
"webm",
|
|
67
|
-
"ogg",
|
|
68
|
-
"wav",
|
|
69
|
-
"mp3",
|
|
70
|
-
"flac",
|
|
71
|
-
"adts"
|
|
72
|
-
]);
|
|
73
|
-
async function remux(source, options = {}) {
|
|
18
|
+
async function transcodeViaLibav(ctx, options) {
|
|
74
19
|
const outputFormat = options.outputFormat ?? "mp4";
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
return remuxViaMediAbunny(ctx, outputFormat, options);
|
|
81
|
-
}
|
|
82
|
-
return remuxViaLibav(ctx, outputFormat, options);
|
|
83
|
-
}
|
|
84
|
-
function validateRemuxEligibility(ctx, strict) {
|
|
85
|
-
const video = ctx.videoTracks[0];
|
|
86
|
-
const audio = ctx.audioTracks[0];
|
|
87
|
-
if (video) {
|
|
88
|
-
const mbCodec = chunk7RGG6ME7_cjs.avbridgeVideoToMediabunny(video.codec);
|
|
89
|
-
if (!mbCodec) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
if (audio) {
|
|
96
|
-
const mbCodec = chunk7RGG6ME7_cjs.avbridgeAudioToMediabunny(audio.codec);
|
|
97
|
-
if (!mbCodec) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
`Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
if (strict && video?.codec === "h264" && audio?.codec === "mp3") {
|
|
104
|
-
throw new Error(
|
|
105
|
-
`Cannot remux in strict mode: H.264 + MP3 is a best-effort combination that may produce playback issues in some browsers. Set strict: false to allow, or use transcode() to re-encode audio to AAC.`
|
|
20
|
+
if (outputFormat !== "mp4" && outputFormat !== "webm" && outputFormat !== "mkv") {
|
|
21
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
22
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
|
|
23
|
+
`legacy-container transcode supports MP4, WebM, and MKV output (got "${outputFormat}").`,
|
|
24
|
+
`Use outputFormat: "mp4", "webm", or "mkv".`
|
|
106
25
|
);
|
|
107
26
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
27
|
+
const videoCodec = options.videoCodec ?? (outputFormat === "webm" ? "vp9" : "h264");
|
|
28
|
+
const audioCodec = options.audioCodec ?? (outputFormat === "webm" ? "opus" : "aac");
|
|
29
|
+
const quality = options.quality ?? "medium";
|
|
30
|
+
options.signal?.throwIfAborted();
|
|
31
|
+
const [
|
|
32
|
+
mb,
|
|
33
|
+
{ openLibavDemux, sanitizePacketTimestamp, sanitizeFrameTimestamp, libavFrameToInterleavedFloat32 },
|
|
34
|
+
{ normalizeSource },
|
|
35
|
+
{ createOutputFormat: createOutputFormat2, mimeForFormat: mimeForFormat2, generateFilename: generateFilename2 }
|
|
36
|
+
] = await Promise.all([
|
|
37
|
+
import('mediabunny'),
|
|
38
|
+
import('./libav-demux-H2GS46GH.cjs'),
|
|
39
|
+
import('./source-VFLXLOCN.cjs'),
|
|
40
|
+
import('./remux-OBSMIENG.cjs')
|
|
41
|
+
]);
|
|
42
|
+
const normalized = await normalizeSource(ctx.source);
|
|
43
|
+
const demux = await openLibavDemux({
|
|
44
|
+
source: normalized,
|
|
45
|
+
filename: ctx.name ?? "input.bin",
|
|
46
|
+
context: ctx
|
|
47
|
+
// transport config is not yet threaded through ConvertOptions; add
|
|
48
|
+
// later if URL-source transcode with signed URLs becomes a need.
|
|
127
49
|
});
|
|
128
|
-
if (!conversion.isValid) {
|
|
129
|
-
const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
|
|
130
|
-
throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);
|
|
131
|
-
}
|
|
132
|
-
if (options.onProgress) {
|
|
133
|
-
const onProgress = options.onProgress;
|
|
134
|
-
conversion.onProgress = (p) => {
|
|
135
|
-
onProgress({ percent: p * 100, bytesWritten: 0 });
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
let abortHandler;
|
|
139
|
-
if (options.signal) {
|
|
140
|
-
options.signal.throwIfAborted();
|
|
141
|
-
abortHandler = () => void conversion.cancel();
|
|
142
|
-
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
143
|
-
}
|
|
144
50
|
try {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
51
|
+
let throwIfAborted2 = function() {
|
|
52
|
+
if (ac?.aborted) {
|
|
53
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
54
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_ABORTED,
|
|
55
|
+
"transcode: aborted by caller.",
|
|
56
|
+
void 0
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
}, throwIfDrainError2 = function() {
|
|
60
|
+
if (drainError) {
|
|
61
|
+
const msg = drainError.message;
|
|
62
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
63
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
|
|
64
|
+
`transcode: video decoder error: ${msg}`,
|
|
65
|
+
"This usually indicates the WebCodecs decoder rejected a malformed packet."
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
var throwIfAborted = throwIfAborted2, throwIfDrainError = throwIfDrainError2;
|
|
70
|
+
options.signal?.throwIfAborted();
|
|
71
|
+
if (!demux.videoStream && !demux.audioStream) {
|
|
72
|
+
throw new Error("transcode: source has no decodable tracks");
|
|
149
73
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
container: outputFormat,
|
|
162
|
-
videoCodec: ctx.videoTracks[0]?.codec,
|
|
163
|
-
audioCodec: ctx.audioTracks[0]?.codec,
|
|
164
|
-
duration: ctx.duration,
|
|
165
|
-
filename
|
|
166
|
-
};
|
|
167
|
-
}
|
|
168
|
-
async function remuxViaLibav(ctx, outputFormat, options) {
|
|
169
|
-
let loadLibav;
|
|
170
|
-
let pickLibavVariant;
|
|
171
|
-
try {
|
|
172
|
-
const loader = await import('./libav-loader-IV4AJ2HW.cjs');
|
|
173
|
-
const routing = await import('./variant-routing-HONNAA6R.cjs');
|
|
174
|
-
loadLibav = loader.loadLibav;
|
|
175
|
-
pickLibavVariant = routing.pickLibavVariant;
|
|
176
|
-
} catch {
|
|
177
|
-
throw new Error(
|
|
178
|
-
`Cannot remux ${ctx.container.toUpperCase()} source: libav.js is not available. Install @libav.js/variant-webcodecs and libavjs-webcodecs-bridge, or build the custom avbridge variant with scripts/build-libav.sh.`
|
|
179
|
-
);
|
|
180
|
-
}
|
|
181
|
-
const variant = pickLibavVariant(ctx);
|
|
182
|
-
const libav = await loadLibav(variant);
|
|
183
|
-
const normalized = await chunk6UUT4BEA_cjs.normalizeSource(ctx.source);
|
|
184
|
-
const filename = ctx.name ?? `remux-input-${Date.now()}`;
|
|
185
|
-
const handle = await chunkZ33SBWL5_cjs.prepareLibavInput(libav, filename, normalized);
|
|
186
|
-
try {
|
|
187
|
-
return await doLibavRemux(libav, filename, ctx, outputFormat, options);
|
|
188
|
-
} finally {
|
|
189
|
-
await handle.detach().catch(() => {
|
|
74
|
+
if (options.outputStream) {
|
|
75
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
76
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
|
|
77
|
+
"outputStream is not yet supported for the libav-backed transcode path.",
|
|
78
|
+
"Remove the outputStream option to receive the transcoded blob in memory. Streaming output for this path is on the roadmap."
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
const bufferTarget = new mb.BufferTarget();
|
|
82
|
+
const output = new mb.Output({
|
|
83
|
+
format: createOutputFormat2(mb, outputFormat),
|
|
84
|
+
target: bufferTarget
|
|
190
85
|
});
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
|
|
86
|
+
const bridge = await loadBridge();
|
|
87
|
+
let videoDecoder = null;
|
|
88
|
+
let videoSoftDec = null;
|
|
89
|
+
let videoSource = null;
|
|
90
|
+
let videoBsfCtx = null;
|
|
91
|
+
let videoBsfPkt = null;
|
|
92
|
+
let videoWidth = 0;
|
|
93
|
+
let videoHeight = 0;
|
|
94
|
+
let videoTimeBase;
|
|
95
|
+
const frameQueue = [];
|
|
96
|
+
const MAX_QUEUE = 16;
|
|
97
|
+
let draining = false;
|
|
98
|
+
let drainError = null;
|
|
99
|
+
let activeDrain = null;
|
|
100
|
+
const drain = () => {
|
|
101
|
+
if (draining) return activeDrain ?? Promise.resolve();
|
|
102
|
+
draining = true;
|
|
103
|
+
const run = (async () => {
|
|
104
|
+
try {
|
|
105
|
+
while (frameQueue.length > 0 && !drainError) {
|
|
106
|
+
const frame = frameQueue.shift();
|
|
107
|
+
try {
|
|
108
|
+
const sample = new mb.VideoSample(frame, {
|
|
109
|
+
timestamp: (frame.timestamp ?? 0) / 1e6
|
|
110
|
+
// µs → s
|
|
111
|
+
});
|
|
112
|
+
await videoSource.add(sample);
|
|
113
|
+
} finally {
|
|
114
|
+
frame.close();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
drainError = err;
|
|
119
|
+
while (frameQueue.length > 0) {
|
|
120
|
+
try {
|
|
121
|
+
frameQueue.shift().close();
|
|
122
|
+
} catch {
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} finally {
|
|
126
|
+
draining = false;
|
|
127
|
+
activeDrain = null;
|
|
128
|
+
}
|
|
129
|
+
})();
|
|
130
|
+
activeDrain = run;
|
|
131
|
+
return run;
|
|
132
|
+
};
|
|
133
|
+
if (demux.videoStream && !options.dropVideo) {
|
|
134
|
+
try {
|
|
135
|
+
const bitDepth = ctx.videoTracks[0]?.bitDepth ?? 8;
|
|
136
|
+
if (bitDepth > 8) {
|
|
137
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
138
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
|
|
139
|
+
`transcode: 10-bit video is not supported in this release (source bit depth: ${bitDepth}).`,
|
|
140
|
+
`Phase 1 transcode handles 8-bit video only. 10-bit support is on the roadmap.`
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
if (demux.videoStream.time_base_num && demux.videoStream.time_base_den) {
|
|
144
|
+
videoTimeBase = [demux.videoStream.time_base_num, demux.videoStream.time_base_den];
|
|
145
|
+
}
|
|
146
|
+
let config = null;
|
|
147
|
+
try {
|
|
148
|
+
config = await bridge.videoStreamToConfig(demux.libav, demux.videoStream);
|
|
149
|
+
} catch {
|
|
150
|
+
config = null;
|
|
151
|
+
}
|
|
152
|
+
const supported = config ? await VideoDecoder.isConfigSupported(config).catch(() => ({ supported: false })) : { supported: false };
|
|
153
|
+
videoWidth = config?.codedWidth ?? ctx.videoTracks[0]?.width ?? 0;
|
|
154
|
+
videoHeight = config?.codedHeight ?? ctx.videoTracks[0]?.height ?? 0;
|
|
155
|
+
if (config && supported.supported) {
|
|
156
|
+
videoDecoder = new VideoDecoder({
|
|
157
|
+
output: (frame) => {
|
|
158
|
+
if (frameQueue.length >= MAX_QUEUE) {
|
|
159
|
+
frame.close();
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
frameQueue.push(frame);
|
|
163
|
+
void drain();
|
|
164
|
+
},
|
|
165
|
+
error: (err) => {
|
|
166
|
+
drainError = err;
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
videoDecoder.configure(config);
|
|
170
|
+
} else {
|
|
171
|
+
const libavSoft = demux.libav;
|
|
172
|
+
const [, c, pkt, frame] = await libavSoft.ff_init_decoder(
|
|
173
|
+
demux.videoStream.codec_id,
|
|
174
|
+
{ codecpar: demux.videoStream.codecpar }
|
|
175
|
+
);
|
|
176
|
+
videoSoftDec = { c, pkt, frame };
|
|
177
|
+
}
|
|
178
|
+
videoSource = new mb.VideoSampleSource({
|
|
179
|
+
codec: avbridgeVideoToMediabunny(videoCodec),
|
|
180
|
+
bitrate: qualityToMediabunny(mb, quality, options.videoBitrate),
|
|
181
|
+
...options.frameRate !== void 0 ? { frameRate: options.frameRate } : {},
|
|
182
|
+
...options.hardwareAcceleration !== void 0 ? { hardwareAcceleration: options.hardwareAcceleration } : {},
|
|
183
|
+
// Progress reporting: media-time-based via each encoded packet.
|
|
184
|
+
onEncodedPacket: options.onProgress ? (packet) => {
|
|
185
|
+
const t = packet.timestamp;
|
|
186
|
+
if (Number.isFinite(t) && ctx.duration && ctx.duration > 0) {
|
|
187
|
+
const pct = Math.min(100, t / ctx.duration * 100);
|
|
188
|
+
options.onProgress({ percent: pct, bytesWritten: 0 });
|
|
189
|
+
}
|
|
190
|
+
} : void 0
|
|
191
|
+
});
|
|
192
|
+
const videoMeta = {};
|
|
193
|
+
if (options.width !== void 0) videoMeta.width = options.width;
|
|
194
|
+
else if (videoWidth > 0) videoMeta.width = videoWidth;
|
|
195
|
+
if (options.height !== void 0) videoMeta.height = options.height;
|
|
196
|
+
else if (videoHeight > 0) videoMeta.height = videoHeight;
|
|
197
|
+
if (options.frameRate !== void 0) videoMeta.frameRate = options.frameRate;
|
|
198
|
+
output.addVideoTrack(videoSource, videoMeta);
|
|
199
|
+
if (ctx.videoTracks[0]?.codec === "mpeg4") {
|
|
200
|
+
const runtime = demux.libav;
|
|
201
|
+
try {
|
|
202
|
+
videoBsfCtx = await runtime.av_bsf_list_parse_str_js("mpeg4_unpack_bframes");
|
|
203
|
+
if (videoBsfCtx != null && videoBsfCtx >= 0) {
|
|
204
|
+
const parIn = await runtime.AVBSFContext_par_in(videoBsfCtx);
|
|
205
|
+
await runtime.avcodec_parameters_copy(parIn, demux.videoStream.codecpar);
|
|
206
|
+
await runtime.av_bsf_init(videoBsfCtx);
|
|
207
|
+
videoBsfPkt = await demux.libav.av_packet_alloc();
|
|
208
|
+
} else {
|
|
209
|
+
videoBsfCtx = null;
|
|
210
|
+
}
|
|
211
|
+
} catch {
|
|
212
|
+
videoBsfCtx = null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
} catch (err) {
|
|
216
|
+
if (err instanceof chunk2IJ66NTD_cjs.AvbridgeError) throw err;
|
|
217
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
218
|
+
chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED,
|
|
219
|
+
`transcode: video decoder init failed: ${err.message}`,
|
|
220
|
+
`The source's video codec may not be supported by this browser's WebCodecs implementation.`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
let audioDec = null;
|
|
225
|
+
let audioSource = null;
|
|
226
|
+
let audioTimeBase;
|
|
227
|
+
const includeAudio = demux.audioStream && !options.dropAudio;
|
|
228
|
+
if (includeAudio) {
|
|
229
|
+
try {
|
|
230
|
+
const libav = demux.libav;
|
|
231
|
+
const [, c, pkt, frame] = await libav.ff_init_decoder(
|
|
232
|
+
demux.audioStream.codec_id,
|
|
233
|
+
{ codecpar: demux.audioStream.codecpar }
|
|
234
|
+
);
|
|
235
|
+
audioDec = { c, pkt, frame };
|
|
236
|
+
if (demux.audioStream.time_base_num && demux.audioStream.time_base_den) {
|
|
237
|
+
audioTimeBase = [
|
|
238
|
+
demux.audioStream.time_base_num,
|
|
239
|
+
demux.audioStream.time_base_den
|
|
240
|
+
];
|
|
241
|
+
}
|
|
242
|
+
audioSource = new mb.AudioSampleSource({
|
|
243
|
+
codec: avbridgeAudioToMediabunny(audioCodec),
|
|
244
|
+
bitrate: qualityToMediabunny(mb, quality, options.audioBitrate)
|
|
245
|
+
});
|
|
246
|
+
output.addAudioTrack(audioSource);
|
|
247
|
+
} catch (err) {
|
|
248
|
+
const codecName = ctx.audioTracks[0]?.codec ?? "unknown";
|
|
249
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
250
|
+
chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED,
|
|
251
|
+
`transcode: no decoder available for audio codec "${codecName}" in this libav variant (${err.message}).`,
|
|
252
|
+
`The file may still play via createPlayer() (fallback strategy). Pass { dropAudio: true } to transcode video-only.`
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
} else if (options.dropAudio) {
|
|
256
|
+
}
|
|
257
|
+
if (!videoSource && !audioSource) {
|
|
258
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
259
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_UNSUPPORTED_COMBO,
|
|
260
|
+
"transcode: no video or audio track to encode (did you set both dropVideo and dropAudio?).",
|
|
261
|
+
"Remove dropVideo or dropAudio to include at least one track."
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
await output.start();
|
|
265
|
+
const videoFps = ctx.videoTracks[0]?.fps && ctx.videoTracks[0].fps > 0 ? ctx.videoTracks[0].fps : 30;
|
|
266
|
+
const videoFrameStepUs = Math.max(1, Math.round(1e6 / videoFps));
|
|
267
|
+
let syntheticVideoUs = 0;
|
|
268
|
+
let syntheticAudioUs = 0;
|
|
269
|
+
const libavFull = demux.libav;
|
|
270
|
+
async function applyBSF(packets) {
|
|
271
|
+
if (!videoBsfCtx || !videoBsfPkt) return packets;
|
|
272
|
+
const out = [];
|
|
273
|
+
for (const pkt of packets) {
|
|
274
|
+
await libavFull.ff_copyin_packet(videoBsfPkt, pkt);
|
|
275
|
+
const sendErr = await libavFull.av_bsf_send_packet(videoBsfCtx, videoBsfPkt);
|
|
276
|
+
if (sendErr < 0) {
|
|
277
|
+
out.push(pkt);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
while (true) {
|
|
281
|
+
const recvErr = await libavFull.av_bsf_receive_packet(videoBsfCtx, videoBsfPkt);
|
|
282
|
+
if (recvErr < 0) break;
|
|
283
|
+
out.push(await libavFull.ff_copyout_packet(videoBsfPkt));
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
return out;
|
|
239
287
|
}
|
|
240
|
-
const
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
|
|
288
|
+
const ac = options.signal;
|
|
289
|
+
const onVideoPacketsWebCodecs = videoDecoder ? async (pkts) => {
|
|
290
|
+
throwIfAborted2();
|
|
291
|
+
throwIfDrainError2();
|
|
292
|
+
while (!ac?.aborted && (videoDecoder.decodeQueueSize > 16 || frameQueue.length >= MAX_QUEUE - 2)) {
|
|
293
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
294
|
+
}
|
|
295
|
+
throwIfAborted2();
|
|
296
|
+
const processed = await applyBSF(pkts);
|
|
297
|
+
const bridgeAny = bridge;
|
|
298
|
+
for (const pkt of processed) {
|
|
244
299
|
sanitizePacketTimestamp(pkt, () => {
|
|
245
300
|
const ts = syntheticVideoUs;
|
|
246
301
|
syntheticVideoUs += videoFrameStepUs;
|
|
247
302
|
return ts;
|
|
248
303
|
}, videoTimeBase);
|
|
249
|
-
|
|
250
|
-
|
|
304
|
+
try {
|
|
305
|
+
const chunk = bridgeAny.packetToEncodedVideoChunk(pkt, demux.videoStream);
|
|
306
|
+
videoDecoder.decode(chunk);
|
|
307
|
+
} catch (err) {
|
|
308
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
309
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
|
|
310
|
+
`transcode: packet \u2192 EncodedVideoChunk failed: ${err.message}`,
|
|
311
|
+
void 0
|
|
312
|
+
);
|
|
251
313
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
314
|
+
}
|
|
315
|
+
} : void 0;
|
|
316
|
+
const onVideoPacketsSoftware = videoSoftDec ? async (pkts) => {
|
|
317
|
+
throwIfAborted2();
|
|
318
|
+
throwIfDrainError2();
|
|
319
|
+
while (!ac?.aborted && frameQueue.length >= MAX_QUEUE - 2) {
|
|
320
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
321
|
+
}
|
|
322
|
+
throwIfAborted2();
|
|
323
|
+
const libavSoft = demux.libav;
|
|
324
|
+
let frames;
|
|
325
|
+
try {
|
|
326
|
+
frames = await libavSoft.ff_decode_multi(
|
|
327
|
+
videoSoftDec.c,
|
|
328
|
+
videoSoftDec.pkt,
|
|
329
|
+
videoSoftDec.frame,
|
|
330
|
+
pkts,
|
|
331
|
+
{ ignoreErrors: true }
|
|
332
|
+
);
|
|
333
|
+
} catch (err) {
|
|
334
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
335
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
|
|
336
|
+
`transcode: software video decode failed: ${err.message}`,
|
|
337
|
+
void 0
|
|
256
338
|
);
|
|
257
|
-
firstVideoMeta = false;
|
|
258
339
|
}
|
|
340
|
+
for (const f of frames) {
|
|
341
|
+
sanitizeFrameTimestamp(f, () => {
|
|
342
|
+
const ts = syntheticVideoUs;
|
|
343
|
+
syntheticVideoUs += videoFrameStepUs;
|
|
344
|
+
return ts;
|
|
345
|
+
}, videoTimeBase);
|
|
346
|
+
try {
|
|
347
|
+
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
348
|
+
if (frameQueue.length >= MAX_QUEUE) {
|
|
349
|
+
vf.close();
|
|
350
|
+
} else {
|
|
351
|
+
frameQueue.push(vf);
|
|
352
|
+
void drain();
|
|
353
|
+
}
|
|
354
|
+
} catch (err) {
|
|
355
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
356
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
|
|
357
|
+
`transcode: laFrameToVideoFrame failed: ${err.message}`,
|
|
358
|
+
void 0
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
} : void 0;
|
|
363
|
+
await demux.pump({
|
|
364
|
+
signal: ac,
|
|
365
|
+
onVideoPackets: onVideoPacketsWebCodecs ?? onVideoPacketsSoftware,
|
|
366
|
+
onAudioPackets: audioDec ? async (pkts) => {
|
|
367
|
+
throwIfAborted2();
|
|
368
|
+
await decodeAudioBatch(pkts, false);
|
|
369
|
+
} : void 0,
|
|
370
|
+
onEof: async () => {
|
|
371
|
+
if (videoDecoder && videoDecoder.state === "configured") {
|
|
372
|
+
try {
|
|
373
|
+
await videoDecoder.flush();
|
|
374
|
+
} catch {
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
if (videoSoftDec) {
|
|
378
|
+
const libavSoft = demux.libav;
|
|
379
|
+
try {
|
|
380
|
+
const tail = await libavSoft.ff_decode_multi(
|
|
381
|
+
videoSoftDec.c,
|
|
382
|
+
videoSoftDec.pkt,
|
|
383
|
+
videoSoftDec.frame,
|
|
384
|
+
[],
|
|
385
|
+
{ fin: true, ignoreErrors: true }
|
|
386
|
+
);
|
|
387
|
+
for (const f of tail) {
|
|
388
|
+
sanitizeFrameTimestamp(f, () => {
|
|
389
|
+
const ts = syntheticVideoUs;
|
|
390
|
+
syntheticVideoUs += videoFrameStepUs;
|
|
391
|
+
return ts;
|
|
392
|
+
}, videoTimeBase);
|
|
393
|
+
try {
|
|
394
|
+
const vf = bridge.laFrameToVideoFrame(f, { timeBase: [1, 1e6] });
|
|
395
|
+
frameQueue.push(vf);
|
|
396
|
+
void drain();
|
|
397
|
+
} catch {
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
} catch {
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
await drain();
|
|
404
|
+
if (audioDec) {
|
|
405
|
+
await decodeAudioBatch([], true);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
throwIfAborted2();
|
|
410
|
+
throwIfDrainError2();
|
|
411
|
+
videoSource?.close();
|
|
412
|
+
audioSource?.close();
|
|
413
|
+
await output.finalize();
|
|
414
|
+
if (!bufferTarget.buffer) {
|
|
415
|
+
throw new Error("transcode: mediabunny produced no output buffer");
|
|
259
416
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
417
|
+
const mimeType = mimeForFormat2(outputFormat);
|
|
418
|
+
const blob = new Blob([bufferTarget.buffer], { type: mimeType });
|
|
419
|
+
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
420
|
+
return {
|
|
421
|
+
blob,
|
|
422
|
+
mimeType,
|
|
423
|
+
container: outputFormat,
|
|
424
|
+
videoCodec: videoSource ? videoCodec : void 0,
|
|
425
|
+
audioCodec: audioSource ? audioCodec : void 0,
|
|
426
|
+
duration: ctx.duration,
|
|
427
|
+
filename: generateFilename2(ctx.name, outputFormat)
|
|
428
|
+
};
|
|
429
|
+
async function decodeAudioBatch(pkts, flush) {
|
|
430
|
+
if (!audioDec || !audioSource) return;
|
|
431
|
+
const libav = demux.libav;
|
|
432
|
+
let frames;
|
|
433
|
+
try {
|
|
434
|
+
frames = await libav.ff_decode_multi(
|
|
435
|
+
audioDec.c,
|
|
436
|
+
audioDec.pkt,
|
|
437
|
+
audioDec.frame,
|
|
438
|
+
pkts,
|
|
439
|
+
flush ? { fin: true, ignoreErrors: true } : { ignoreErrors: true }
|
|
440
|
+
);
|
|
441
|
+
} catch (err) {
|
|
442
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
443
|
+
chunk2IJ66NTD_cjs.ERR_TRANSCODE_DECODE,
|
|
444
|
+
`transcode: audio decode failed: ${err.message}`,
|
|
445
|
+
void 0
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
for (const f of frames) {
|
|
449
|
+
sanitizeFrameTimestamp(f, () => {
|
|
263
450
|
const ts = syntheticAudioUs;
|
|
264
|
-
const
|
|
265
|
-
|
|
451
|
+
const samples = f.nb_samples ?? 1024;
|
|
452
|
+
const sampleRate = f.sample_rate ?? 44100;
|
|
453
|
+
syntheticAudioUs += Math.round(samples * 1e6 / sampleRate);
|
|
266
454
|
return ts;
|
|
267
455
|
}, audioTimeBase);
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
456
|
+
const pcm = libavFrameToInterleavedFloat32(f);
|
|
457
|
+
if (!pcm) continue;
|
|
458
|
+
const sample = new mb.AudioSample({
|
|
459
|
+
data: pcm.data,
|
|
460
|
+
format: "f32",
|
|
461
|
+
numberOfChannels: pcm.channels,
|
|
462
|
+
sampleRate: pcm.sampleRate,
|
|
463
|
+
timestamp: (f.pts ?? 0) / 1e6
|
|
464
|
+
});
|
|
465
|
+
await audioSource.add(sample);
|
|
274
466
|
}
|
|
275
467
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const currentUs = Math.max(lastVideoTs, lastAudioTs);
|
|
281
|
-
const percent = Math.min(99, currentUs / durationUs * 100);
|
|
282
|
-
options.onProgress({ percent, bytesWritten: 0 });
|
|
283
|
-
}
|
|
284
|
-
if (readErr === libav.AVERROR_EOF) break;
|
|
285
|
-
if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {
|
|
286
|
-
console.warn("[avbridge] remux: ff_read_frame_multi returned", readErr);
|
|
287
|
-
break;
|
|
468
|
+
} finally {
|
|
469
|
+
try {
|
|
470
|
+
await demux.destroy();
|
|
471
|
+
} catch {
|
|
288
472
|
}
|
|
289
473
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
await libav.av_packet_free?.(readPkt);
|
|
293
|
-
} catch {
|
|
294
|
-
}
|
|
474
|
+
}
|
|
475
|
+
async function loadBridge() {
|
|
295
476
|
try {
|
|
296
|
-
await libav.
|
|
297
|
-
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
throw new Error("Remux failed: mediabunny produced no output buffer.");
|
|
477
|
+
const wrapper = await import('./libav-import-2ZVKV2E7.cjs');
|
|
478
|
+
return wrapper.libavBridge;
|
|
479
|
+
} catch (err) {
|
|
480
|
+
throw new Error(`failed to load libavjs-webcodecs-bridge: ${err.message}`);
|
|
301
481
|
}
|
|
302
|
-
const mimeType = mimeForFormat(outputFormat);
|
|
303
|
-
const blob = new Blob([target.buffer], { type: mimeType });
|
|
304
|
-
const outputFilename = generateFilename(ctx.name, outputFormat);
|
|
305
|
-
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
306
|
-
return {
|
|
307
|
-
blob,
|
|
308
|
-
mimeType,
|
|
309
|
-
container: outputFormat,
|
|
310
|
-
videoCodec: videoTrackInfo?.codec,
|
|
311
|
-
audioCodec: audioTrackInfo?.codec,
|
|
312
|
-
duration: ctx.duration,
|
|
313
|
-
filename: outputFilename
|
|
314
|
-
};
|
|
315
482
|
}
|
|
316
|
-
function
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
const tb = fallbackTimeBase ?? [1, 1e6];
|
|
329
|
-
const pts64 = hi * 4294967296 + lo;
|
|
330
|
-
const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
|
|
331
|
-
if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
|
|
332
|
-
pkt.pts = us;
|
|
333
|
-
pkt.ptshi = us < 0 ? -1 : 0;
|
|
334
|
-
pkt.time_base_num = 1;
|
|
335
|
-
pkt.time_base_den = 1e6;
|
|
336
|
-
return;
|
|
483
|
+
function avbridgeVideoToMediabunny(c) {
|
|
484
|
+
switch (c) {
|
|
485
|
+
case "h264":
|
|
486
|
+
return "avc";
|
|
487
|
+
case "h265":
|
|
488
|
+
return "hevc";
|
|
489
|
+
case "vp9":
|
|
490
|
+
return "vp9";
|
|
491
|
+
case "av1":
|
|
492
|
+
return "av1";
|
|
337
493
|
}
|
|
338
|
-
const fallback = nextUs();
|
|
339
|
-
pkt.pts = fallback;
|
|
340
|
-
pkt.ptshi = 0;
|
|
341
|
-
pkt.time_base_num = 1;
|
|
342
|
-
pkt.time_base_den = 1e6;
|
|
343
494
|
}
|
|
344
|
-
function
|
|
345
|
-
switch (
|
|
346
|
-
case "
|
|
347
|
-
return
|
|
348
|
-
case "
|
|
349
|
-
return
|
|
350
|
-
case "
|
|
351
|
-
return
|
|
352
|
-
default:
|
|
353
|
-
return new mb.Mp4OutputFormat({ fastStart: "in-memory" });
|
|
495
|
+
function avbridgeAudioToMediabunny(c) {
|
|
496
|
+
switch (c) {
|
|
497
|
+
case "aac":
|
|
498
|
+
return "aac";
|
|
499
|
+
case "opus":
|
|
500
|
+
return "opus";
|
|
501
|
+
case "flac":
|
|
502
|
+
return "flac";
|
|
354
503
|
}
|
|
355
504
|
}
|
|
356
|
-
function
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
505
|
+
function qualityToMediabunny(mb, quality, override) {
|
|
506
|
+
if (override !== void 0) return override;
|
|
507
|
+
switch (quality) {
|
|
508
|
+
case "low":
|
|
509
|
+
return mb.QUALITY_LOW;
|
|
510
|
+
case "medium":
|
|
511
|
+
return mb.QUALITY_MEDIUM;
|
|
512
|
+
case "high":
|
|
513
|
+
return mb.QUALITY_HIGH;
|
|
514
|
+
case "very-high":
|
|
515
|
+
return mb.QUALITY_VERY_HIGH;
|
|
366
516
|
}
|
|
367
517
|
}
|
|
368
|
-
function generateFilename(originalName, format) {
|
|
369
|
-
const ext = format === "mkv" ? "mkv" : format;
|
|
370
|
-
if (!originalName) return `output.${ext}`;
|
|
371
|
-
const base = originalName.replace(/\.[^.]+$/, "");
|
|
372
|
-
return `${base}.${ext}`;
|
|
373
|
-
}
|
|
374
|
-
var _seqCounter = 0;
|
|
375
|
-
function libavPacketToMediAbunny(mb, pkt) {
|
|
376
|
-
const KEY_FRAME_FLAG = 1;
|
|
377
|
-
const timestampSec = (pkt.pts ?? 0) / 1e6;
|
|
378
|
-
const durationSec = (pkt.duration ?? 0) / 1e6;
|
|
379
|
-
const type = pkt.flags & KEY_FRAME_FLAG ? "key" : "delta";
|
|
380
|
-
return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);
|
|
381
|
-
}
|
|
382
|
-
function buildVideoDecoderConfig(track) {
|
|
383
|
-
return {
|
|
384
|
-
codec: track.codecString ?? track.codec,
|
|
385
|
-
codedWidth: track.width,
|
|
386
|
-
codedHeight: track.height
|
|
387
|
-
};
|
|
388
|
-
}
|
|
389
|
-
function buildAudioDecoderConfig(track) {
|
|
390
|
-
return {
|
|
391
|
-
codec: track.codecString ?? track.codec,
|
|
392
|
-
numberOfChannels: track.channels,
|
|
393
|
-
sampleRate: track.sampleRate
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
518
|
|
|
397
519
|
// src/convert/transcode.ts
|
|
398
|
-
var
|
|
520
|
+
var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
399
521
|
"mp4",
|
|
400
522
|
"mov",
|
|
401
523
|
"mkv",
|
|
@@ -413,11 +535,16 @@ async function transcode(source, options = {}) {
|
|
|
413
535
|
const quality = options.quality ?? "medium";
|
|
414
536
|
validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
|
|
415
537
|
options.signal?.throwIfAborted();
|
|
416
|
-
const ctx = await
|
|
538
|
+
const ctx = await chunkZCUXHW55_cjs.probe(source);
|
|
417
539
|
options.signal?.throwIfAborted();
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
|
|
540
|
+
if (isLibavTranscodeContainer(ctx.container)) {
|
|
541
|
+
return transcodeViaLibav(ctx, options);
|
|
542
|
+
}
|
|
543
|
+
if (!MEDIABUNNY_CONTAINERS.has(ctx.container)) {
|
|
544
|
+
throw new chunk2IJ66NTD_cjs.AvbridgeError(
|
|
545
|
+
chunk2IJ66NTD_cjs.ERR_CONTAINER_NOT_SUPPORTED,
|
|
546
|
+
`Cannot transcode "${ctx.container}" sources. transcode() supports mediabunny-readable containers (MP4, MKV, WebM, OGG, MP3, FLAC, WAV, MOV) and legacy containers via the libav path (AVI, ASF, FLV).`,
|
|
547
|
+
`If this is a legacy container we don't yet support, use createPlayer() to play it. Transcode support for more containers is on the roadmap.`
|
|
421
548
|
);
|
|
422
549
|
}
|
|
423
550
|
return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
|
|
@@ -425,7 +552,7 @@ async function transcode(source, options = {}) {
|
|
|
425
552
|
async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
|
|
426
553
|
const mb = await import('mediabunny');
|
|
427
554
|
const input = new mb.Input({
|
|
428
|
-
source: await
|
|
555
|
+
source: await chunkZCUXHW55_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
429
556
|
formats: mb.ALL_FORMATS
|
|
430
557
|
});
|
|
431
558
|
let bytesWritten = 0;
|
|
@@ -439,12 +566,12 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
|
|
|
439
566
|
}
|
|
440
567
|
})) : null;
|
|
441
568
|
const output = new mb.Output({
|
|
442
|
-
format: createOutputFormat(mb, outputFormat),
|
|
569
|
+
format: chunkQ2VUO52Z_cjs.createOutputFormat(mb, outputFormat),
|
|
443
570
|
target: streamTarget ?? bufferTarget
|
|
444
571
|
});
|
|
445
572
|
const videoOptions = options.dropVideo ? { discard: true } : {
|
|
446
573
|
codec: avbridgeVideoToMediabunny2(videoCodec),
|
|
447
|
-
bitrate: options.videoBitrate ??
|
|
574
|
+
bitrate: options.videoBitrate ?? qualityToMediabunny2(mb, quality),
|
|
448
575
|
forceTranscode: true,
|
|
449
576
|
...options.width !== void 0 ? { width: options.width } : {},
|
|
450
577
|
...options.height !== void 0 ? { height: options.height } : {},
|
|
@@ -454,7 +581,7 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
|
|
|
454
581
|
};
|
|
455
582
|
const audioOptions = options.dropAudio ? { discard: true } : {
|
|
456
583
|
codec: avbridgeAudioToMediabunny2(audioCodec),
|
|
457
|
-
bitrate: options.audioBitrate ??
|
|
584
|
+
bitrate: options.audioBitrate ?? qualityToMediabunny2(mb, quality),
|
|
458
585
|
forceTranscode: true
|
|
459
586
|
};
|
|
460
587
|
const conversion = await mb.Conversion.init({
|
|
@@ -530,8 +657,8 @@ async function doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, o
|
|
|
530
657
|
await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
|
|
531
658
|
}
|
|
532
659
|
}
|
|
533
|
-
const mimeType = mimeForFormat(outputFormat);
|
|
534
|
-
const filename = generateFilename(ctx.name, outputFormat);
|
|
660
|
+
const mimeType = chunkQ2VUO52Z_cjs.mimeForFormat(outputFormat);
|
|
661
|
+
const filename = chunkQ2VUO52Z_cjs.generateFilename(ctx.name, outputFormat);
|
|
535
662
|
if (options.outputStream) {
|
|
536
663
|
options.onProgress?.({ percent: 100, bytesWritten: 0 });
|
|
537
664
|
return {
|
|
@@ -617,7 +744,7 @@ function avbridgeAudioToMediabunny2(c) {
|
|
|
617
744
|
return "flac";
|
|
618
745
|
}
|
|
619
746
|
}
|
|
620
|
-
function
|
|
747
|
+
function qualityToMediabunny2(mb, quality) {
|
|
621
748
|
switch (quality) {
|
|
622
749
|
case "low":
|
|
623
750
|
return mb.QUALITY_LOW;
|
|
@@ -630,95 +757,98 @@ function qualityToMediabunny(mb, quality) {
|
|
|
630
757
|
}
|
|
631
758
|
}
|
|
632
759
|
|
|
760
|
+
Object.defineProperty(exports, "remux", {
|
|
761
|
+
enumerable: true,
|
|
762
|
+
get: function () { return chunkQ2VUO52Z_cjs.remux; }
|
|
763
|
+
});
|
|
633
764
|
Object.defineProperty(exports, "FALLBACK_AUDIO_CODECS", {
|
|
634
765
|
enumerable: true,
|
|
635
|
-
get: function () { return
|
|
766
|
+
get: function () { return chunk6SOFJV44_cjs.FALLBACK_AUDIO_CODECS; }
|
|
636
767
|
});
|
|
637
768
|
Object.defineProperty(exports, "FALLBACK_VIDEO_CODECS", {
|
|
638
769
|
enumerable: true,
|
|
639
|
-
get: function () { return
|
|
770
|
+
get: function () { return chunk6SOFJV44_cjs.FALLBACK_VIDEO_CODECS; }
|
|
640
771
|
});
|
|
641
772
|
Object.defineProperty(exports, "NATIVE_AUDIO_CODECS", {
|
|
642
773
|
enumerable: true,
|
|
643
|
-
get: function () { return
|
|
774
|
+
get: function () { return chunk6SOFJV44_cjs.NATIVE_AUDIO_CODECS; }
|
|
644
775
|
});
|
|
645
776
|
Object.defineProperty(exports, "NATIVE_VIDEO_CODECS", {
|
|
646
777
|
enumerable: true,
|
|
647
|
-
get: function () { return
|
|
778
|
+
get: function () { return chunk6SOFJV44_cjs.NATIVE_VIDEO_CODECS; }
|
|
648
779
|
});
|
|
649
780
|
Object.defineProperty(exports, "UnifiedPlayer", {
|
|
650
781
|
enumerable: true,
|
|
651
|
-
get: function () { return
|
|
782
|
+
get: function () { return chunk6SOFJV44_cjs.UnifiedPlayer; }
|
|
652
783
|
});
|
|
653
784
|
Object.defineProperty(exports, "classify", {
|
|
654
785
|
enumerable: true,
|
|
655
|
-
get: function () { return
|
|
786
|
+
get: function () { return chunk6SOFJV44_cjs.classifyContext; }
|
|
656
787
|
});
|
|
657
788
|
Object.defineProperty(exports, "createPlayer", {
|
|
658
789
|
enumerable: true,
|
|
659
|
-
get: function () { return
|
|
790
|
+
get: function () { return chunk6SOFJV44_cjs.createPlayer; }
|
|
660
791
|
});
|
|
661
|
-
Object.defineProperty(exports, "
|
|
792
|
+
Object.defineProperty(exports, "srtToVtt", {
|
|
662
793
|
enumerable: true,
|
|
663
|
-
get: function () { return
|
|
794
|
+
get: function () { return chunkS4WAZC2T_cjs.srtToVtt; }
|
|
664
795
|
});
|
|
665
|
-
Object.defineProperty(exports, "
|
|
796
|
+
Object.defineProperty(exports, "probe", {
|
|
666
797
|
enumerable: true,
|
|
667
|
-
get: function () { return
|
|
798
|
+
get: function () { return chunkZCUXHW55_cjs.probe; }
|
|
668
799
|
});
|
|
669
800
|
Object.defineProperty(exports, "AvbridgeError", {
|
|
670
801
|
enumerable: true,
|
|
671
|
-
get: function () { return
|
|
802
|
+
get: function () { return chunk2IJ66NTD_cjs.AvbridgeError; }
|
|
672
803
|
});
|
|
673
804
|
Object.defineProperty(exports, "ERR_ALL_STRATEGIES_EXHAUSTED", {
|
|
674
805
|
enumerable: true,
|
|
675
|
-
get: function () { return
|
|
806
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_ALL_STRATEGIES_EXHAUSTED; }
|
|
676
807
|
});
|
|
677
808
|
Object.defineProperty(exports, "ERR_CODEC_NOT_SUPPORTED", {
|
|
678
809
|
enumerable: true,
|
|
679
|
-
get: function () { return
|
|
810
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED; }
|
|
680
811
|
});
|
|
681
812
|
Object.defineProperty(exports, "ERR_FETCH_FAILED", {
|
|
682
813
|
enumerable: true,
|
|
683
|
-
get: function () { return
|
|
814
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_FETCH_FAILED; }
|
|
684
815
|
});
|
|
685
816
|
Object.defineProperty(exports, "ERR_LIBAV_NOT_REACHABLE", {
|
|
686
817
|
enumerable: true,
|
|
687
|
-
get: function () { return
|
|
818
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_LIBAV_NOT_REACHABLE; }
|
|
688
819
|
});
|
|
689
820
|
Object.defineProperty(exports, "ERR_MSE_CODEC_NOT_SUPPORTED", {
|
|
690
821
|
enumerable: true,
|
|
691
|
-
get: function () { return
|
|
822
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_CODEC_NOT_SUPPORTED; }
|
|
692
823
|
});
|
|
693
824
|
Object.defineProperty(exports, "ERR_MSE_NOT_SUPPORTED", {
|
|
694
825
|
enumerable: true,
|
|
695
|
-
get: function () { return
|
|
826
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_NOT_SUPPORTED; }
|
|
696
827
|
});
|
|
697
828
|
Object.defineProperty(exports, "ERR_PLAYER_NOT_READY", {
|
|
698
829
|
enumerable: true,
|
|
699
|
-
get: function () { return
|
|
830
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY; }
|
|
700
831
|
});
|
|
701
832
|
Object.defineProperty(exports, "ERR_PROBE_FAILED", {
|
|
702
833
|
enumerable: true,
|
|
703
|
-
get: function () { return
|
|
834
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FAILED; }
|
|
704
835
|
});
|
|
705
836
|
Object.defineProperty(exports, "ERR_PROBE_FETCH_FAILED", {
|
|
706
837
|
enumerable: true,
|
|
707
|
-
get: function () { return
|
|
838
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FETCH_FAILED; }
|
|
708
839
|
});
|
|
709
840
|
Object.defineProperty(exports, "ERR_PROBE_UNKNOWN_CONTAINER", {
|
|
710
841
|
enumerable: true,
|
|
711
|
-
get: function () { return
|
|
842
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_UNKNOWN_CONTAINER; }
|
|
712
843
|
});
|
|
713
844
|
Object.defineProperty(exports, "ERR_RANGE_NOT_SUPPORTED", {
|
|
714
845
|
enumerable: true,
|
|
715
|
-
get: function () { return
|
|
846
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_RANGE_NOT_SUPPORTED; }
|
|
716
847
|
});
|
|
717
848
|
Object.defineProperty(exports, "ERR_STRATEGY_FAILED", {
|
|
718
849
|
enumerable: true,
|
|
719
|
-
get: function () { return
|
|
850
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_STRATEGY_FAILED; }
|
|
720
851
|
});
|
|
721
|
-
exports.remux = remux;
|
|
722
852
|
exports.transcode = transcode;
|
|
723
853
|
//# sourceMappingURL=index.cjs.map
|
|
724
854
|
//# sourceMappingURL=index.cjs.map
|