avbridge 2.2.1 → 2.5.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 +153 -1
- package/NOTICE.md +2 -2
- package/README.md +2 -3
- package/THIRD_PARTY_LICENSES.md +2 -2
- package/dist/avi-2JPBSHGA.js +183 -0
- package/dist/avi-2JPBSHGA.js.map +1 -0
- package/dist/avi-F6WZJK5T.cjs +185 -0
- package/dist/avi-F6WZJK5T.cjs.map +1 -0
- package/dist/{avi-GCGM7OJI.js → avi-NJXAXUXK.js} +9 -3
- package/dist/avi-NJXAXUXK.js.map +1 -0
- package/dist/{avi-6SJLWIWW.cjs → avi-W6L3BTWU.cjs} +10 -4
- package/dist/avi-W6L3BTWU.cjs.map +1 -0
- package/dist/chunk-2IJ66NTD.cjs +212 -0
- package/dist/chunk-2IJ66NTD.cjs.map +1 -0
- package/dist/{chunk-ILKDNBSE.js → chunk-2XW2O3YI.cjs} +55 -10
- 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-5YAWWKA3.js +18 -0
- package/dist/chunk-5YAWWKA3.js.map +1 -0
- package/dist/chunk-CPJLFFCC.js +189 -0
- 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-WD2ZNQA7.js → chunk-DCSOQH2N.js} +7 -4
- package/dist/chunk-DCSOQH2N.js.map +1 -0
- package/dist/{chunk-HZLQNKFN.cjs → chunk-E76AMWI4.js} +40 -15
- package/dist/chunk-E76AMWI4.js.map +1 -0
- package/dist/chunk-F3LQJKXK.cjs +20 -0
- package/dist/chunk-F3LQJKXK.cjs.map +1 -0
- package/dist/chunk-IAYKFGFG.js +200 -0
- package/dist/chunk-IAYKFGFG.js.map +1 -0
- package/dist/{chunk-DMWARSEF.js → chunk-KY2GPCT7.js} +788 -697
- package/dist/chunk-KY2GPCT7.js.map +1 -0
- package/dist/chunk-LUFA47FP.js +19 -0
- package/dist/chunk-LUFA47FP.js.map +1 -0
- package/dist/chunk-NNVOHKXJ.cjs +204 -0
- package/dist/chunk-NNVOHKXJ.cjs.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-UF2N5L63.cjs → chunk-TBW26OPP.cjs} +800 -710
- package/dist/chunk-TBW26OPP.cjs.map +1 -0
- package/dist/chunk-X2K3GIWE.js +235 -0
- package/dist/chunk-X2K3GIWE.js.map +1 -0
- package/dist/{chunk-L4NPOJ36.cjs → chunk-Z33SBWL5.cjs} +7 -4
- package/dist/chunk-Z33SBWL5.cjs.map +1 -0
- package/dist/chunk-ZCUXHW55.cjs +242 -0
- package/dist/chunk-ZCUXHW55.cjs.map +1 -0
- package/dist/element-browser.js +1282 -503
- package/dist/element-browser.js.map +1 -1
- package/dist/element.cjs +59 -5
- package/dist/element.cjs.map +1 -1
- package/dist/element.d.cts +39 -1
- package/dist/element.d.ts +39 -1
- package/dist/element.js +58 -4
- package/dist/element.js.map +1 -1
- package/dist/index.cjs +605 -327
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +48 -4
- package/dist/index.d.ts +48 -4
- package/dist/index.js +528 -319
- package/dist/index.js.map +1 -1
- package/dist/libav-demux-H2GS46GH.cjs +27 -0
- package/dist/{libav-http-reader-NQJVY273.js.map → libav-demux-H2GS46GH.cjs.map} +1 -1
- package/dist/libav-demux-OWZ4T2YW.js +6 -0
- package/dist/{libav-http-reader-FPYDBMYK.cjs.map → libav-demux-OWZ4T2YW.js.map} +1 -1
- package/dist/libav-http-reader-AZLE7YFS.cjs +16 -0
- package/dist/libav-http-reader-AZLE7YFS.cjs.map +1 -0
- package/dist/libav-http-reader-WXG3Z7AI.js +3 -0
- package/dist/libav-http-reader-WXG3Z7AI.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-U2NPmFvA.d.cts → player-B6WB74RD.d.cts} +62 -3
- package/dist/{player-U2NPmFvA.d.ts → player-B6WB74RD.d.ts} +62 -3
- package/dist/player.cjs +5631 -0
- package/dist/player.cjs.map +1 -0
- package/dist/player.d.cts +699 -0
- package/dist/player.d.ts +699 -0
- package/dist/player.js +5629 -0
- package/dist/player.js.map +1 -0
- 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-FFZ7TW2B.js.map → source-4TZ6KMNV.js.map} +1 -1
- package/dist/source-7YLO6E7X.cjs +29 -0
- package/dist/{source-CN43EI7Z.cjs.map → source-7YLO6E7X.cjs.map} +1 -1
- package/dist/source-MTX5ELUZ.js +4 -0
- package/dist/source-MTX5ELUZ.js.map +1 -0
- package/dist/source-VFLXLOCN.cjs +29 -0
- package/dist/source-VFLXLOCN.cjs.map +1 -0
- 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/dist/variant-routing-434STYAB.js +3 -0
- package/dist/{variant-routing-JOBWXYKD.js.map → variant-routing-434STYAB.js.map} +1 -1
- package/dist/variant-routing-HONNAA6R.cjs +12 -0
- package/dist/{variant-routing-GOHB2RZN.cjs.map → variant-routing-HONNAA6R.cjs.map} +1 -1
- package/package.json +9 -1
- package/src/classify/rules.ts +27 -5
- package/src/convert/remux.ts +9 -35
- package/src/convert/transcode-libav.ts +691 -0
- package/src/convert/transcode.ts +53 -12
- package/src/element/avbridge-player.ts +861 -0
- package/src/element/avbridge-video.ts +54 -0
- package/src/element/player-icons.ts +25 -0
- package/src/element/player-styles.ts +472 -0
- package/src/errors.ts +53 -0
- package/src/index.ts +23 -0
- package/src/player-element.ts +18 -0
- package/src/player.ts +118 -27
- package/src/plugins/builtin.ts +2 -2
- package/src/probe/avi.ts +4 -0
- package/src/probe/index.ts +40 -10
- package/src/strategies/fallback/audio-output.ts +31 -0
- package/src/strategies/fallback/decoder.ts +179 -175
- package/src/strategies/fallback/index.ts +48 -6
- package/src/strategies/fallback/libav-import.ts +9 -1
- package/src/strategies/fallback/variant-routing.ts +7 -13
- package/src/strategies/fallback/video-renderer.ts +231 -32
- package/src/strategies/hybrid/decoder.ts +219 -200
- package/src/strategies/hybrid/index.ts +48 -7
- package/src/strategies/native.ts +6 -3
- package/src/strategies/remux/index.ts +14 -2
- package/src/strategies/remux/mse.ts +12 -2
- package/src/strategies/remux/pipeline.ts +72 -12
- package/src/subtitles/index.ts +7 -3
- package/src/subtitles/render.ts +8 -0
- package/src/types.ts +53 -1
- package/src/util/libav-demux.ts +405 -0
- package/src/util/libav-http-reader.ts +5 -1
- package/src/util/source.ts +28 -8
- package/src/util/transport.ts +26 -0
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.mjs +1 -1
- package/vendor/libav/avbridge/libav-6.8.8.0-avbridge.wasm.wasm +0 -0
- package/dist/avi-6SJLWIWW.cjs.map +0 -1
- package/dist/avi-GCGM7OJI.js.map +0 -1
- package/dist/chunk-DMWARSEF.js.map +0 -1
- package/dist/chunk-HZLQNKFN.cjs.map +0 -1
- package/dist/chunk-ILKDNBSE.js.map +0 -1
- package/dist/chunk-J5MCMN3S.js +0 -27
- package/dist/chunk-J5MCMN3S.js.map +0 -1
- package/dist/chunk-L4NPOJ36.cjs.map +0 -1
- package/dist/chunk-NZU7W256.cjs +0 -29
- package/dist/chunk-NZU7W256.cjs.map +0 -1
- package/dist/chunk-UF2N5L63.cjs.map +0 -1
- package/dist/chunk-WD2ZNQA7.js.map +0 -1
- package/dist/libav-http-reader-FPYDBMYK.cjs +0 -16
- package/dist/libav-http-reader-NQJVY273.js +0 -3
- package/dist/source-CN43EI7Z.cjs +0 -28
- package/dist/source-FFZ7TW2B.js +0 -3
- package/dist/variant-routing-GOHB2RZN.cjs +0 -12
- package/dist/variant-routing-JOBWXYKD.js +0 -3
package/dist/index.cjs
CHANGED
|
@@ -1,346 +1,523 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
4
|
-
var
|
|
5
|
-
var
|
|
3
|
+
var chunkQ2VUO52Z_cjs = require('./chunk-Q2VUO52Z.cjs');
|
|
4
|
+
var chunkTBW26OPP_cjs = require('./chunk-TBW26OPP.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
|
-
require('./chunk-
|
|
12
|
+
require('./chunk-F3LQJKXK.cjs');
|
|
8
13
|
|
|
9
|
-
// src/convert/
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"mov",
|
|
13
|
-
"mkv",
|
|
14
|
-
"webm",
|
|
15
|
-
"ogg",
|
|
16
|
-
"wav",
|
|
17
|
-
"mp3",
|
|
18
|
-
"flac",
|
|
19
|
-
"adts"
|
|
20
|
-
]);
|
|
21
|
-
async function remux(source, options = {}) {
|
|
22
|
-
const outputFormat = options.outputFormat ?? "mp4";
|
|
23
|
-
options.signal?.throwIfAborted();
|
|
24
|
-
const ctx = await chunkUF2N5L63_cjs.probe(source);
|
|
25
|
-
options.signal?.throwIfAborted();
|
|
26
|
-
validateRemuxEligibility(ctx, options.strict ?? false);
|
|
27
|
-
if (MEDIABUNNY_CONTAINERS.has(ctx.container)) {
|
|
28
|
-
return remuxViaMediAbunny(ctx, outputFormat, options);
|
|
29
|
-
}
|
|
30
|
-
return remuxViaLibav(ctx, outputFormat, options);
|
|
14
|
+
// src/convert/transcode-libav.ts
|
|
15
|
+
function isLibavTranscodeContainer(container) {
|
|
16
|
+
return container === "avi" || container === "asf" || container === "flv" || container === "rm";
|
|
31
17
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
`Cannot remux: video codec "${video.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
if (audio) {
|
|
44
|
-
const mbCodec = chunkUF2N5L63_cjs.avbridgeAudioToMediabunny(audio.codec);
|
|
45
|
-
if (!mbCodec) {
|
|
46
|
-
throw new Error(
|
|
47
|
-
`Cannot remux: audio codec "${audio.codec}" is not supported for remuxing. Use transcode() to re-encode to a modern codec.`
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
if (strict && video?.codec === "h264" && audio?.codec === "mp3") {
|
|
52
|
-
throw new Error(
|
|
53
|
-
`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.`
|
|
18
|
+
async function transcodeViaLibav(ctx, options) {
|
|
19
|
+
const outputFormat = options.outputFormat ?? "mp4";
|
|
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".`
|
|
54
25
|
);
|
|
55
26
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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.
|
|
75
49
|
});
|
|
76
|
-
if (!conversion.isValid) {
|
|
77
|
-
const reasons = conversion.discardedTracks.map((d) => `${d.track.type} track discarded: ${d.reason}`).join("; ");
|
|
78
|
-
throw new Error(`Cannot remux: mediabunny rejected the conversion. ${reasons}`);
|
|
79
|
-
}
|
|
80
|
-
if (options.onProgress) {
|
|
81
|
-
const onProgress = options.onProgress;
|
|
82
|
-
conversion.onProgress = (p) => {
|
|
83
|
-
onProgress({ percent: p * 100, bytesWritten: 0 });
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
let abortHandler;
|
|
87
|
-
if (options.signal) {
|
|
88
|
-
options.signal.throwIfAborted();
|
|
89
|
-
abortHandler = () => void conversion.cancel();
|
|
90
|
-
options.signal.addEventListener("abort", abortHandler, { once: true });
|
|
91
|
-
}
|
|
92
50
|
try {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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");
|
|
97
73
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
container: outputFormat,
|
|
110
|
-
videoCodec: ctx.videoTracks[0]?.codec,
|
|
111
|
-
audioCodec: ctx.audioTracks[0]?.codec,
|
|
112
|
-
duration: ctx.duration,
|
|
113
|
-
filename
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
async function remuxViaLibav(ctx, outputFormat, options) {
|
|
117
|
-
let loadLibav;
|
|
118
|
-
let pickLibavVariant;
|
|
119
|
-
try {
|
|
120
|
-
const loader = await import('./libav-loader-IV4AJ2HW.cjs');
|
|
121
|
-
const routing = await import('./variant-routing-GOHB2RZN.cjs');
|
|
122
|
-
loadLibav = loader.loadLibav;
|
|
123
|
-
pickLibavVariant = routing.pickLibavVariant;
|
|
124
|
-
} catch {
|
|
125
|
-
throw new Error(
|
|
126
|
-
`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.`
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
const variant = pickLibavVariant(ctx);
|
|
130
|
-
const libav = await loadLibav(variant);
|
|
131
|
-
const normalized = await chunkHZLQNKFN_cjs.normalizeSource(ctx.source);
|
|
132
|
-
const filename = ctx.name ?? `remux-input-${Date.now()}`;
|
|
133
|
-
const handle = await chunkL4NPOJ36_cjs.prepareLibavInput(libav, filename, normalized);
|
|
134
|
-
try {
|
|
135
|
-
return await doLibavRemux(libav, filename, ctx, outputFormat, options);
|
|
136
|
-
} finally {
|
|
137
|
-
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
|
|
138
85
|
});
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
186
|
-
|
|
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
|
+
);
|
|
187
263
|
}
|
|
188
|
-
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
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;
|
|
287
|
+
}
|
|
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) {
|
|
192
299
|
sanitizePacketTimestamp(pkt, () => {
|
|
193
300
|
const ts = syntheticVideoUs;
|
|
194
301
|
syntheticVideoUs += videoFrameStepUs;
|
|
195
302
|
return ts;
|
|
196
303
|
}, videoTimeBase);
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
+
);
|
|
313
|
+
}
|
|
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
|
|
201
338
|
);
|
|
202
|
-
firstVideoMeta = false;
|
|
203
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");
|
|
204
416
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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, () => {
|
|
208
450
|
const ts = syntheticAudioUs;
|
|
209
|
-
const
|
|
210
|
-
|
|
451
|
+
const samples = f.nb_samples ?? 1024;
|
|
452
|
+
const sampleRate = f.sample_rate ?? 44100;
|
|
453
|
+
syntheticAudioUs += Math.round(samples * 1e6 / sampleRate);
|
|
211
454
|
return ts;
|
|
212
455
|
}, audioTimeBase);
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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);
|
|
219
466
|
}
|
|
220
467
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
const currentUs = Math.max(lastVideoTs, lastAudioTs);
|
|
226
|
-
const percent = Math.min(99, currentUs / durationUs * 100);
|
|
227
|
-
options.onProgress({ percent, bytesWritten: 0 });
|
|
228
|
-
}
|
|
229
|
-
if (readErr === libav.AVERROR_EOF) break;
|
|
230
|
-
if (readErr && readErr !== 0 && readErr !== -libav.EAGAIN) {
|
|
231
|
-
console.warn("[avbridge] remux: ff_read_frame_multi returned", readErr);
|
|
232
|
-
break;
|
|
468
|
+
} finally {
|
|
469
|
+
try {
|
|
470
|
+
await demux.destroy();
|
|
471
|
+
} catch {
|
|
233
472
|
}
|
|
234
473
|
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
await libav.av_packet_free?.(readPkt);
|
|
238
|
-
} catch {
|
|
239
|
-
}
|
|
474
|
+
}
|
|
475
|
+
async function loadBridge() {
|
|
240
476
|
try {
|
|
241
|
-
await libav.
|
|
242
|
-
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
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}`);
|
|
246
481
|
}
|
|
247
|
-
const mimeType = mimeForFormat(outputFormat);
|
|
248
|
-
const blob = new Blob([target.buffer], { type: mimeType });
|
|
249
|
-
const outputFilename = generateFilename(ctx.name, outputFormat);
|
|
250
|
-
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
251
|
-
return {
|
|
252
|
-
blob,
|
|
253
|
-
mimeType,
|
|
254
|
-
container: outputFormat,
|
|
255
|
-
videoCodec: videoTrackInfo?.codec,
|
|
256
|
-
audioCodec: audioTrackInfo?.codec,
|
|
257
|
-
duration: ctx.duration,
|
|
258
|
-
filename: outputFilename
|
|
259
|
-
};
|
|
260
482
|
}
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return;
|
|
272
|
-
}
|
|
273
|
-
const tb = fallbackTimeBase ?? [1, 1e6];
|
|
274
|
-
const pts64 = hi * 4294967296 + lo;
|
|
275
|
-
const us = Math.round(pts64 * 1e6 * tb[0] / tb[1]);
|
|
276
|
-
if (Number.isFinite(us) && Math.abs(us) <= Number.MAX_SAFE_INTEGER) {
|
|
277
|
-
pkt.pts = us;
|
|
278
|
-
pkt.ptshi = us < 0 ? -1 : 0;
|
|
279
|
-
pkt.time_base_num = 1;
|
|
280
|
-
pkt.time_base_den = 1e6;
|
|
281
|
-
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";
|
|
282
493
|
}
|
|
283
|
-
const fallback = nextUs();
|
|
284
|
-
pkt.pts = fallback;
|
|
285
|
-
pkt.ptshi = 0;
|
|
286
|
-
pkt.time_base_num = 1;
|
|
287
|
-
pkt.time_base_den = 1e6;
|
|
288
494
|
}
|
|
289
|
-
function
|
|
290
|
-
switch (
|
|
291
|
-
case "
|
|
292
|
-
return
|
|
293
|
-
case "
|
|
294
|
-
return
|
|
295
|
-
case "
|
|
296
|
-
return
|
|
297
|
-
default:
|
|
298
|
-
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";
|
|
299
503
|
}
|
|
300
504
|
}
|
|
301
|
-
function
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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;
|
|
311
516
|
}
|
|
312
517
|
}
|
|
313
|
-
function generateFilename(originalName, format) {
|
|
314
|
-
const ext = format === "mkv" ? "mkv" : format;
|
|
315
|
-
if (!originalName) return `output.${ext}`;
|
|
316
|
-
const base = originalName.replace(/\.[^.]+$/, "");
|
|
317
|
-
return `${base}.${ext}`;
|
|
318
|
-
}
|
|
319
|
-
var _seqCounter = 0;
|
|
320
|
-
function libavPacketToMediAbunny(mb, pkt) {
|
|
321
|
-
const KEY_FRAME_FLAG = 1;
|
|
322
|
-
const timestampSec = (pkt.pts ?? 0) / 1e6;
|
|
323
|
-
const durationSec = (pkt.duration ?? 0) / 1e6;
|
|
324
|
-
const type = pkt.flags & KEY_FRAME_FLAG ? "key" : "delta";
|
|
325
|
-
return new mb.EncodedPacket(pkt.data, type, timestampSec, durationSec, _seqCounter++);
|
|
326
|
-
}
|
|
327
|
-
function buildVideoDecoderConfig(track) {
|
|
328
|
-
return {
|
|
329
|
-
codec: track.codecString ?? track.codec,
|
|
330
|
-
codedWidth: track.width,
|
|
331
|
-
codedHeight: track.height
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
function buildAudioDecoderConfig(track) {
|
|
335
|
-
return {
|
|
336
|
-
codec: track.codecString ?? track.codec,
|
|
337
|
-
numberOfChannels: track.channels,
|
|
338
|
-
sampleRate: track.sampleRate
|
|
339
|
-
};
|
|
340
|
-
}
|
|
341
518
|
|
|
342
519
|
// src/convert/transcode.ts
|
|
343
|
-
var
|
|
520
|
+
var MEDIABUNNY_CONTAINERS = /* @__PURE__ */ new Set([
|
|
344
521
|
"mp4",
|
|
345
522
|
"mov",
|
|
346
523
|
"mkv",
|
|
@@ -358,11 +535,16 @@ async function transcode(source, options = {}) {
|
|
|
358
535
|
const quality = options.quality ?? "medium";
|
|
359
536
|
validateCodecCompatibility(outputFormat, videoCodec, audioCodec);
|
|
360
537
|
options.signal?.throwIfAborted();
|
|
361
|
-
const ctx = await
|
|
538
|
+
const ctx = await chunkZCUXHW55_cjs.probe(source);
|
|
362
539
|
options.signal?.throwIfAborted();
|
|
363
|
-
if (
|
|
364
|
-
|
|
365
|
-
|
|
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.`
|
|
366
548
|
);
|
|
367
549
|
}
|
|
368
550
|
return doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options);
|
|
@@ -370,17 +552,26 @@ async function transcode(source, options = {}) {
|
|
|
370
552
|
async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, options) {
|
|
371
553
|
const mb = await import('mediabunny');
|
|
372
554
|
const input = new mb.Input({
|
|
373
|
-
source: await
|
|
555
|
+
source: await chunkZCUXHW55_cjs.buildMediabunnySourceFromInput(mb, ctx.source),
|
|
374
556
|
formats: mb.ALL_FORMATS
|
|
375
557
|
});
|
|
376
|
-
|
|
558
|
+
let bytesWritten = 0;
|
|
559
|
+
const useStream = !!options.outputStream;
|
|
560
|
+
const bufferTarget = useStream ? null : new mb.BufferTarget();
|
|
561
|
+
const streamTarget = useStream ? new mb.StreamTarget(new WritableStream({
|
|
562
|
+
write(chunk) {
|
|
563
|
+
bytesWritten += chunk.data.byteLength;
|
|
564
|
+
const writer = options.outputStream.getWriter();
|
|
565
|
+
return writer.write(chunk.data).then(() => writer.releaseLock());
|
|
566
|
+
}
|
|
567
|
+
})) : null;
|
|
377
568
|
const output = new mb.Output({
|
|
378
|
-
format: createOutputFormat(mb, outputFormat),
|
|
379
|
-
target
|
|
569
|
+
format: chunkQ2VUO52Z_cjs.createOutputFormat(mb, outputFormat),
|
|
570
|
+
target: streamTarget ?? bufferTarget
|
|
380
571
|
});
|
|
381
572
|
const videoOptions = options.dropVideo ? { discard: true } : {
|
|
382
573
|
codec: avbridgeVideoToMediabunny2(videoCodec),
|
|
383
|
-
bitrate: options.videoBitrate ??
|
|
574
|
+
bitrate: options.videoBitrate ?? qualityToMediabunny2(mb, quality),
|
|
384
575
|
forceTranscode: true,
|
|
385
576
|
...options.width !== void 0 ? { width: options.width } : {},
|
|
386
577
|
...options.height !== void 0 ? { height: options.height } : {},
|
|
@@ -390,7 +581,7 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
|
|
|
390
581
|
};
|
|
391
582
|
const audioOptions = options.dropAudio ? { discard: true } : {
|
|
392
583
|
codec: avbridgeAudioToMediabunny2(audioCodec),
|
|
393
|
-
bitrate: options.audioBitrate ??
|
|
584
|
+
bitrate: options.audioBitrate ?? qualityToMediabunny2(mb, quality),
|
|
394
585
|
forceTranscode: true
|
|
395
586
|
};
|
|
396
587
|
const conversion = await mb.Conversion.init({
|
|
@@ -425,10 +616,13 @@ async function attemptTranscode(ctx, outputFormat, videoCodec, audioCodec, quali
|
|
|
425
616
|
options.signal.removeEventListener("abort", abortHandler);
|
|
426
617
|
}
|
|
427
618
|
}
|
|
428
|
-
if (
|
|
619
|
+
if (useStream) {
|
|
620
|
+
return null;
|
|
621
|
+
}
|
|
622
|
+
if (!bufferTarget.buffer) {
|
|
429
623
|
throw new Error("Transcode failed: mediabunny produced no output buffer.");
|
|
430
624
|
}
|
|
431
|
-
return
|
|
625
|
+
return bufferTarget.buffer;
|
|
432
626
|
}
|
|
433
627
|
function isLikelyEncoderInitError(err) {
|
|
434
628
|
if (!err) return false;
|
|
@@ -463,12 +657,25 @@ async function doTranscode(ctx, outputFormat, videoCodec, audioCodec, quality, o
|
|
|
463
657
|
await new Promise((r) => setTimeout(r, 50 * (attempt + 1)));
|
|
464
658
|
}
|
|
465
659
|
}
|
|
660
|
+
const mimeType = chunkQ2VUO52Z_cjs.mimeForFormat(outputFormat);
|
|
661
|
+
const filename = chunkQ2VUO52Z_cjs.generateFilename(ctx.name, outputFormat);
|
|
662
|
+
if (options.outputStream) {
|
|
663
|
+
options.onProgress?.({ percent: 100, bytesWritten: 0 });
|
|
664
|
+
return {
|
|
665
|
+
blob: new Blob([], { type: mimeType }),
|
|
666
|
+
mimeType,
|
|
667
|
+
container: outputFormat,
|
|
668
|
+
videoCodec: options.dropVideo ? void 0 : videoCodec,
|
|
669
|
+
audioCodec: options.dropAudio ? void 0 : audioCodec,
|
|
670
|
+
duration: ctx.duration,
|
|
671
|
+
filename,
|
|
672
|
+
...notes.length > 0 ? { notes } : {}
|
|
673
|
+
};
|
|
674
|
+
}
|
|
466
675
|
if (!buffer) {
|
|
467
676
|
throw new Error("Transcode failed: no buffer produced (this should be unreachable).");
|
|
468
677
|
}
|
|
469
|
-
const mimeType = mimeForFormat(outputFormat);
|
|
470
678
|
const blob = new Blob([buffer], { type: mimeType });
|
|
471
|
-
const filename = generateFilename(ctx.name, outputFormat);
|
|
472
679
|
options.onProgress?.({ percent: 100, bytesWritten: blob.size });
|
|
473
680
|
return {
|
|
474
681
|
blob,
|
|
@@ -537,7 +744,7 @@ function avbridgeAudioToMediabunny2(c) {
|
|
|
537
744
|
return "flac";
|
|
538
745
|
}
|
|
539
746
|
}
|
|
540
|
-
function
|
|
747
|
+
function qualityToMediabunny2(mb, quality) {
|
|
541
748
|
switch (quality) {
|
|
542
749
|
case "low":
|
|
543
750
|
return mb.QUALITY_LOW;
|
|
@@ -550,27 +757,98 @@ function qualityToMediabunny(mb, quality) {
|
|
|
550
757
|
}
|
|
551
758
|
}
|
|
552
759
|
|
|
760
|
+
Object.defineProperty(exports, "remux", {
|
|
761
|
+
enumerable: true,
|
|
762
|
+
get: function () { return chunkQ2VUO52Z_cjs.remux; }
|
|
763
|
+
});
|
|
764
|
+
Object.defineProperty(exports, "FALLBACK_AUDIO_CODECS", {
|
|
765
|
+
enumerable: true,
|
|
766
|
+
get: function () { return chunkTBW26OPP_cjs.FALLBACK_AUDIO_CODECS; }
|
|
767
|
+
});
|
|
768
|
+
Object.defineProperty(exports, "FALLBACK_VIDEO_CODECS", {
|
|
769
|
+
enumerable: true,
|
|
770
|
+
get: function () { return chunkTBW26OPP_cjs.FALLBACK_VIDEO_CODECS; }
|
|
771
|
+
});
|
|
772
|
+
Object.defineProperty(exports, "NATIVE_AUDIO_CODECS", {
|
|
773
|
+
enumerable: true,
|
|
774
|
+
get: function () { return chunkTBW26OPP_cjs.NATIVE_AUDIO_CODECS; }
|
|
775
|
+
});
|
|
776
|
+
Object.defineProperty(exports, "NATIVE_VIDEO_CODECS", {
|
|
777
|
+
enumerable: true,
|
|
778
|
+
get: function () { return chunkTBW26OPP_cjs.NATIVE_VIDEO_CODECS; }
|
|
779
|
+
});
|
|
553
780
|
Object.defineProperty(exports, "UnifiedPlayer", {
|
|
554
781
|
enumerable: true,
|
|
555
|
-
get: function () { return
|
|
782
|
+
get: function () { return chunkTBW26OPP_cjs.UnifiedPlayer; }
|
|
556
783
|
});
|
|
557
784
|
Object.defineProperty(exports, "classify", {
|
|
558
785
|
enumerable: true,
|
|
559
|
-
get: function () { return
|
|
786
|
+
get: function () { return chunkTBW26OPP_cjs.classifyContext; }
|
|
560
787
|
});
|
|
561
788
|
Object.defineProperty(exports, "createPlayer", {
|
|
562
789
|
enumerable: true,
|
|
563
|
-
get: function () { return
|
|
790
|
+
get: function () { return chunkTBW26OPP_cjs.createPlayer; }
|
|
791
|
+
});
|
|
792
|
+
Object.defineProperty(exports, "srtToVtt", {
|
|
793
|
+
enumerable: true,
|
|
794
|
+
get: function () { return chunkS4WAZC2T_cjs.srtToVtt; }
|
|
564
795
|
});
|
|
565
796
|
Object.defineProperty(exports, "probe", {
|
|
566
797
|
enumerable: true,
|
|
567
|
-
get: function () { return
|
|
798
|
+
get: function () { return chunkZCUXHW55_cjs.probe; }
|
|
568
799
|
});
|
|
569
|
-
Object.defineProperty(exports, "
|
|
800
|
+
Object.defineProperty(exports, "AvbridgeError", {
|
|
801
|
+
enumerable: true,
|
|
802
|
+
get: function () { return chunk2IJ66NTD_cjs.AvbridgeError; }
|
|
803
|
+
});
|
|
804
|
+
Object.defineProperty(exports, "ERR_ALL_STRATEGIES_EXHAUSTED", {
|
|
805
|
+
enumerable: true,
|
|
806
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_ALL_STRATEGIES_EXHAUSTED; }
|
|
807
|
+
});
|
|
808
|
+
Object.defineProperty(exports, "ERR_CODEC_NOT_SUPPORTED", {
|
|
809
|
+
enumerable: true,
|
|
810
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_CODEC_NOT_SUPPORTED; }
|
|
811
|
+
});
|
|
812
|
+
Object.defineProperty(exports, "ERR_FETCH_FAILED", {
|
|
813
|
+
enumerable: true,
|
|
814
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_FETCH_FAILED; }
|
|
815
|
+
});
|
|
816
|
+
Object.defineProperty(exports, "ERR_LIBAV_NOT_REACHABLE", {
|
|
817
|
+
enumerable: true,
|
|
818
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_LIBAV_NOT_REACHABLE; }
|
|
819
|
+
});
|
|
820
|
+
Object.defineProperty(exports, "ERR_MSE_CODEC_NOT_SUPPORTED", {
|
|
821
|
+
enumerable: true,
|
|
822
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_CODEC_NOT_SUPPORTED; }
|
|
823
|
+
});
|
|
824
|
+
Object.defineProperty(exports, "ERR_MSE_NOT_SUPPORTED", {
|
|
825
|
+
enumerable: true,
|
|
826
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_MSE_NOT_SUPPORTED; }
|
|
827
|
+
});
|
|
828
|
+
Object.defineProperty(exports, "ERR_PLAYER_NOT_READY", {
|
|
829
|
+
enumerable: true,
|
|
830
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PLAYER_NOT_READY; }
|
|
831
|
+
});
|
|
832
|
+
Object.defineProperty(exports, "ERR_PROBE_FAILED", {
|
|
833
|
+
enumerable: true,
|
|
834
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FAILED; }
|
|
835
|
+
});
|
|
836
|
+
Object.defineProperty(exports, "ERR_PROBE_FETCH_FAILED", {
|
|
837
|
+
enumerable: true,
|
|
838
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_FETCH_FAILED; }
|
|
839
|
+
});
|
|
840
|
+
Object.defineProperty(exports, "ERR_PROBE_UNKNOWN_CONTAINER", {
|
|
841
|
+
enumerable: true,
|
|
842
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_PROBE_UNKNOWN_CONTAINER; }
|
|
843
|
+
});
|
|
844
|
+
Object.defineProperty(exports, "ERR_RANGE_NOT_SUPPORTED", {
|
|
845
|
+
enumerable: true,
|
|
846
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_RANGE_NOT_SUPPORTED; }
|
|
847
|
+
});
|
|
848
|
+
Object.defineProperty(exports, "ERR_STRATEGY_FAILED", {
|
|
570
849
|
enumerable: true,
|
|
571
|
-
get: function () { return
|
|
850
|
+
get: function () { return chunk2IJ66NTD_cjs.ERR_STRATEGY_FAILED; }
|
|
572
851
|
});
|
|
573
|
-
exports.remux = remux;
|
|
574
852
|
exports.transcode = transcode;
|
|
575
853
|
//# sourceMappingURL=index.cjs.map
|
|
576
854
|
//# sourceMappingURL=index.cjs.map
|