@bendyline/squisq-video-react 2.4.20 → 2.4.22
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/README.md +30 -0
- package/dist/chunk-2W6JQSIY.js +51 -0
- package/dist/{chunk-MI4EJXTD.js → chunk-47ODXCBA.js} +33 -44
- package/dist/{chunk-QJWEZRZS.js → chunk-A52VG7RA.js} +1 -1
- package/dist/{chunk-IFQJAW4V.js → chunk-BNNLPKT2.js} +1 -1
- package/dist/{chunk-4GFZ3GUE.js → chunk-C7NEHV5X.js} +20507 -17327
- package/dist/chunk-FMQI6WK2.js +157 -0
- package/dist/{chunk-GBIYYDMY.js → chunk-ISYJJJXN.js} +67 -1
- package/dist/{chunk-KJQVUVOG.js → chunk-JDIMPME5.js} +1 -1
- package/dist/{chunk-JKWCZION.js → chunk-OLFWEKY5.js} +1 -1
- package/dist/{chunk-A6ED7EVY.js → chunk-TUZLVOSP.js} +6 -3
- package/dist/components/index.js +6 -6
- package/dist/cover-image/index.js +3 -3
- package/dist/dashboard-image/index.js +2 -2
- package/dist/encoder/index.d.ts +126 -2
- package/dist/encoder/index.js +12 -1
- package/dist/hooks/index.js +4 -4
- package/dist/index.d.ts +2 -2
- package/dist/index.js +18 -7
- package/dist/lossyEncoder-EIXBAOLG.js +236 -0
- package/dist/media-edit/index.js +1 -1
- package/dist/{mediaEditEngine-ZLG7D3TB.js → mediaEditEngine-JLZ4Z5BB.js} +2 -2
- package/dist/workers/mediaEdit.worker.js +2 -2
- package/package.json +4 -4
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LOSSY_FORMATS,
|
|
3
|
+
assertOpen,
|
|
4
|
+
blockFrames,
|
|
5
|
+
createSerialQueue
|
|
6
|
+
} from "./chunk-2W6JQSIY.js";
|
|
7
|
+
|
|
8
|
+
// src/audioFile/wavStream.ts
|
|
9
|
+
var HEADER_BYTES = 44;
|
|
10
|
+
var BYTES_PER_SAMPLE = 2;
|
|
11
|
+
var MAX_WAV_DATA_BYTES = 4294967295 - 36;
|
|
12
|
+
var DEFAULT_CHUNK_BYTES = 1 << 20;
|
|
13
|
+
function wavHeader(sampleRate, channels, dataBytes) {
|
|
14
|
+
const header = new Uint8Array(HEADER_BYTES);
|
|
15
|
+
const view = new DataView(header.buffer);
|
|
16
|
+
const ascii = (offset, text) => {
|
|
17
|
+
for (let i = 0; i < text.length; i++) view.setUint8(offset + i, text.charCodeAt(i));
|
|
18
|
+
};
|
|
19
|
+
const blockAlign = channels * BYTES_PER_SAMPLE;
|
|
20
|
+
ascii(0, "RIFF");
|
|
21
|
+
view.setUint32(4, 36 + dataBytes, true);
|
|
22
|
+
ascii(8, "WAVE");
|
|
23
|
+
ascii(12, "fmt ");
|
|
24
|
+
view.setUint32(16, 16, true);
|
|
25
|
+
view.setUint16(20, 1, true);
|
|
26
|
+
view.setUint16(22, channels, true);
|
|
27
|
+
view.setUint32(24, sampleRate, true);
|
|
28
|
+
view.setUint32(28, sampleRate * blockAlign, true);
|
|
29
|
+
view.setUint16(32, blockAlign, true);
|
|
30
|
+
view.setUint16(34, BYTES_PER_SAMPLE * 8, true);
|
|
31
|
+
ascii(36, "data");
|
|
32
|
+
view.setUint32(40, dataBytes, true);
|
|
33
|
+
return header;
|
|
34
|
+
}
|
|
35
|
+
function toPcm16(sample) {
|
|
36
|
+
if (!(sample > -1)) return sample <= -1 ? -32768 : 0;
|
|
37
|
+
if (sample >= 1) return 32767;
|
|
38
|
+
return Math.round(sample < 0 ? sample * 32768 : sample * 32767);
|
|
39
|
+
}
|
|
40
|
+
function createWavStreamWriter(sampleRate, channels, options = {}) {
|
|
41
|
+
const blockAlign = channels * BYTES_PER_SAMPLE;
|
|
42
|
+
const chunkFrames = Math.max(
|
|
43
|
+
1,
|
|
44
|
+
Math.floor((options.chunkBytes ?? DEFAULT_CHUNK_BYTES) / blockAlign)
|
|
45
|
+
);
|
|
46
|
+
const scratch = new ArrayBuffer(chunkFrames * blockAlign);
|
|
47
|
+
const view = new DataView(scratch);
|
|
48
|
+
const parts = [];
|
|
49
|
+
let pendingFrames = 0;
|
|
50
|
+
let dataBytes = 0;
|
|
51
|
+
let finished = false;
|
|
52
|
+
const flushScratch = () => {
|
|
53
|
+
if (pendingFrames === 0) return;
|
|
54
|
+
parts.push(new Blob([new Uint8Array(scratch, 0, pendingFrames * blockAlign)]));
|
|
55
|
+
pendingFrames = 0;
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
get dataBytes() {
|
|
59
|
+
return dataBytes;
|
|
60
|
+
},
|
|
61
|
+
append(planar) {
|
|
62
|
+
if (finished) throw new Error("The WAV writer is already finished.");
|
|
63
|
+
const frames = planar[0]?.length ?? 0;
|
|
64
|
+
if (frames === 0) return;
|
|
65
|
+
if (dataBytes + frames * blockAlign > MAX_WAV_DATA_BYTES) {
|
|
66
|
+
throw new RangeError("WAV files cannot exceed 4 GiB; use a compressed format instead.");
|
|
67
|
+
}
|
|
68
|
+
for (let frame = 0; frame < frames; frame++) {
|
|
69
|
+
let offset = pendingFrames * blockAlign;
|
|
70
|
+
for (let c = 0; c < channels; c++) {
|
|
71
|
+
view.setInt16(offset, toPcm16(planar[c][frame]), true);
|
|
72
|
+
offset += BYTES_PER_SAMPLE;
|
|
73
|
+
}
|
|
74
|
+
pendingFrames += 1;
|
|
75
|
+
if (pendingFrames === chunkFrames) flushScratch();
|
|
76
|
+
}
|
|
77
|
+
dataBytes += frames * blockAlign;
|
|
78
|
+
},
|
|
79
|
+
finish() {
|
|
80
|
+
if (finished) throw new Error("The WAV writer is already finished.");
|
|
81
|
+
finished = true;
|
|
82
|
+
flushScratch();
|
|
83
|
+
const blob = new Blob([wavHeader(sampleRate, channels, dataBytes), ...parts], {
|
|
84
|
+
type: "audio/wav"
|
|
85
|
+
});
|
|
86
|
+
parts.length = 0;
|
|
87
|
+
return blob;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/audioFile/audioFileEncoder.ts
|
|
93
|
+
var lossyModule = null;
|
|
94
|
+
function loadLossyModule() {
|
|
95
|
+
lossyModule ?? (lossyModule = import("./lossyEncoder-EIXBAOLG.js"));
|
|
96
|
+
return lossyModule;
|
|
97
|
+
}
|
|
98
|
+
function validateOptions(options) {
|
|
99
|
+
if (options.format !== "wav" && !(options.format in LOSSY_FORMATS)) {
|
|
100
|
+
throw new RangeError(`Unknown audio file format: ${String(options.format)}`);
|
|
101
|
+
}
|
|
102
|
+
if (!Number.isInteger(options.sampleRate) || options.sampleRate < 3e3 || options.sampleRate > 768e3) {
|
|
103
|
+
throw new RangeError(`Unsupported sample rate: ${options.sampleRate}`);
|
|
104
|
+
}
|
|
105
|
+
if (options.channels !== 1 && options.channels !== 2) {
|
|
106
|
+
throw new RangeError(`Audio files support 1 or 2 channels (got ${String(options.channels)}).`);
|
|
107
|
+
}
|
|
108
|
+
if (options.bitrate !== void 0 && (!Number.isFinite(options.bitrate) || options.bitrate < 6e3 || options.bitrate > 512e3)) {
|
|
109
|
+
throw new RangeError(`Unsupported bitrate: ${options.bitrate}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async function supportedAudioFileFormats() {
|
|
113
|
+
const formats = [];
|
|
114
|
+
try {
|
|
115
|
+
const lossy = await loadLossyModule();
|
|
116
|
+
for (const info of [LOSSY_FORMATS.m4a, LOSSY_FORMATS["opus-webm"]]) {
|
|
117
|
+
if (await lossy.canEncodeLossyFormat(info)) formats.push(info.format);
|
|
118
|
+
}
|
|
119
|
+
} catch {
|
|
120
|
+
}
|
|
121
|
+
formats.push("wav");
|
|
122
|
+
return formats;
|
|
123
|
+
}
|
|
124
|
+
async function createAudioFileEncoder(options) {
|
|
125
|
+
validateOptions(options);
|
|
126
|
+
if (options.format === "wav") return createWavEncoder(options);
|
|
127
|
+
const lossy = await loadLossyModule();
|
|
128
|
+
return lossy.createLossyEncoder(options, LOSSY_FORMATS[options.format]);
|
|
129
|
+
}
|
|
130
|
+
function createWavEncoder(options) {
|
|
131
|
+
const writer = createWavStreamWriter(options.sampleRate, options.channels);
|
|
132
|
+
const serial = createSerialQueue();
|
|
133
|
+
let state = "open";
|
|
134
|
+
return {
|
|
135
|
+
format: "wav",
|
|
136
|
+
mimeType: "audio/wav",
|
|
137
|
+
extension: "wav",
|
|
138
|
+
append: (block) => serial(async () => {
|
|
139
|
+
assertOpen(state);
|
|
140
|
+
blockFrames(block, options.channels);
|
|
141
|
+
writer.append(block);
|
|
142
|
+
}),
|
|
143
|
+
finish: () => serial(async () => {
|
|
144
|
+
assertOpen(state);
|
|
145
|
+
state = "finished";
|
|
146
|
+
return writer.finish();
|
|
147
|
+
}),
|
|
148
|
+
cancel: async () => {
|
|
149
|
+
if (state === "open") state = "cancelled";
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export {
|
|
155
|
+
supportedAudioFileFormats,
|
|
156
|
+
createAudioFileEncoder
|
|
157
|
+
};
|
|
@@ -450,6 +450,68 @@ async function muxAudioWithFfmpegWasm(videoMp4, wav, audioBitrate, loadConfig) {
|
|
|
450
450
|
}
|
|
451
451
|
}
|
|
452
452
|
|
|
453
|
+
// src/documentAudio.ts
|
|
454
|
+
import { computeAudioTimeline } from "@bendyline/squisq-video";
|
|
455
|
+
function documentAudioTimeline(doc, options = {}) {
|
|
456
|
+
const offset = options.offsetSec ?? 0;
|
|
457
|
+
return options.processedAudio ? computeAudioTimeline(doc, offset, { processedAudio: options.processedAudio }) : computeAudioTimeline(doc, offset);
|
|
458
|
+
}
|
|
459
|
+
async function loadAudioTimelineSources(timeline, readMedia, signal) {
|
|
460
|
+
const buffers = /* @__PURE__ */ new Map();
|
|
461
|
+
const missing = [];
|
|
462
|
+
for (const src of new Set(timeline.map((clip) => clip.src))) {
|
|
463
|
+
signal?.throwIfAborted();
|
|
464
|
+
const data = await readMedia(src);
|
|
465
|
+
if (data) buffers.set(src, data);
|
|
466
|
+
else missing.push(src);
|
|
467
|
+
}
|
|
468
|
+
signal?.throwIfAborted();
|
|
469
|
+
return { buffers, missing };
|
|
470
|
+
}
|
|
471
|
+
function audioTimelineEnd(timeline, minDurationSec = 0) {
|
|
472
|
+
const floor = Number.isFinite(minDurationSec) ? Math.max(0, minDurationSec) : 0;
|
|
473
|
+
return timeline.reduce((end, clip) => Math.max(end, clip.startSec + clip.durationSec), floor);
|
|
474
|
+
}
|
|
475
|
+
function mixAudioTimeline(timeline, buffers, minDurationSec, sampleRate = EXPORT_AUDIO_SAMPLE_RATE) {
|
|
476
|
+
return renderAudioTimeline(
|
|
477
|
+
timeline,
|
|
478
|
+
buffers,
|
|
479
|
+
audioTimelineEnd(timeline, minDurationSec),
|
|
480
|
+
sampleRate
|
|
481
|
+
);
|
|
482
|
+
}
|
|
483
|
+
async function renderDocumentAudio(doc, options) {
|
|
484
|
+
const { signal } = options;
|
|
485
|
+
signal?.throwIfAborted();
|
|
486
|
+
const timeline = documentAudioTimeline(
|
|
487
|
+
doc,
|
|
488
|
+
options.processedAudio ? { processedAudio: options.processedAudio } : {}
|
|
489
|
+
);
|
|
490
|
+
if (timeline.length === 0) return null;
|
|
491
|
+
const { buffers, missing } = await loadAudioTimelineSources(
|
|
492
|
+
timeline,
|
|
493
|
+
(src) => options.readMedia(src),
|
|
494
|
+
signal
|
|
495
|
+
);
|
|
496
|
+
try {
|
|
497
|
+
if (missing.length > 0 && (options.missingMedia ?? "error") === "error") {
|
|
498
|
+
throw new Error(`Audio files could not be loaded: ${missing.join(", ")}`);
|
|
499
|
+
}
|
|
500
|
+
const playable = timeline.filter((clip) => buffers.has(clip.src));
|
|
501
|
+
if (playable.length === 0) return null;
|
|
502
|
+
const mixed = await mixAudioTimeline(
|
|
503
|
+
playable,
|
|
504
|
+
buffers,
|
|
505
|
+
doc.duration,
|
|
506
|
+
options.sampleRate ?? EXPORT_AUDIO_SAMPLE_RATE
|
|
507
|
+
);
|
|
508
|
+
signal?.throwIfAborted();
|
|
509
|
+
return mixed;
|
|
510
|
+
} finally {
|
|
511
|
+
buffers.clear();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
453
515
|
export {
|
|
454
516
|
supportsWebCodecs,
|
|
455
517
|
supportsWebCodecsH264,
|
|
@@ -461,5 +523,9 @@ export {
|
|
|
461
523
|
renderAudioTimeline,
|
|
462
524
|
encodeAacTrack,
|
|
463
525
|
audioBufferToWav,
|
|
464
|
-
muxAudioWithFfmpegWasm
|
|
526
|
+
muxAudioWithFfmpegWasm,
|
|
527
|
+
documentAudioTimeline,
|
|
528
|
+
loadAudioTimelineSources,
|
|
529
|
+
mixAudioTimeline,
|
|
530
|
+
renderDocumentAudio
|
|
465
531
|
};
|
|
@@ -7,7 +7,7 @@ var inlineDenoiser = createRnnoiseDenoiserFactory(
|
|
|
7
7
|
() => import("@shiguredo/rnnoise-wasm").then(({ Rnnoise }) => Rnnoise.load())
|
|
8
8
|
);
|
|
9
9
|
async function inlineEngine() {
|
|
10
|
-
return import("./mediaEditEngine-
|
|
10
|
+
return import("./mediaEditEngine-JLZ4Z5BB.js");
|
|
11
11
|
}
|
|
12
12
|
function inlineOptions(options) {
|
|
13
13
|
return { ...options, denoiser: options.denoiser ?? inlineDenoiser };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useVideoExport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-47ODXCBA.js";
|
|
4
4
|
|
|
5
5
|
// src/VideoExportModal.tsx
|
|
6
6
|
import { useState, useCallback, useEffect, useId, useRef } from "react";
|
|
@@ -317,6 +317,7 @@ function VideoExportModal({
|
|
|
317
317
|
]);
|
|
318
318
|
const [saving, setSaving] = useState(false);
|
|
319
319
|
const [saveError, setSaveError] = useState(null);
|
|
320
|
+
const [savedOutput, setSavedOutput] = useState(null);
|
|
320
321
|
const handleSave = useCallback(async () => {
|
|
321
322
|
if (!downloadUrl || !outputBlob) return;
|
|
322
323
|
const ts = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -329,11 +330,12 @@ function VideoExportModal({
|
|
|
329
330
|
document.body.appendChild(a);
|
|
330
331
|
a.click();
|
|
331
332
|
document.body.removeChild(a);
|
|
333
|
+
setSavedOutput(outputBlob);
|
|
332
334
|
return;
|
|
333
335
|
}
|
|
334
336
|
setSaving(true);
|
|
335
337
|
try {
|
|
336
|
-
await saveOutput(outputBlob, filename);
|
|
338
|
+
if (await saveOutput(outputBlob, filename) !== false) setSavedOutput(outputBlob);
|
|
337
339
|
} catch (caught) {
|
|
338
340
|
setSaveError(caught instanceof Error ? caught.message : "The export could not be saved.");
|
|
339
341
|
} finally {
|
|
@@ -349,10 +351,11 @@ function VideoExportModal({
|
|
|
349
351
|
onClose();
|
|
350
352
|
}, [state, cancelExport, resetExport, onClose]);
|
|
351
353
|
const isExporting = state === "preparing" || state === "capturing" || state === "encoding";
|
|
354
|
+
const escapeCloses = !preparingEdits && (state === "idle" || state === "error" || state === "complete" && outputBlob !== null && savedOutput === outputBlob);
|
|
352
355
|
useModalDialog({
|
|
353
356
|
rootRef: overlayRef,
|
|
354
357
|
dialogRef,
|
|
355
|
-
closeOnEscape:
|
|
358
|
+
closeOnEscape: escapeCloses,
|
|
356
359
|
onClose: handleClose
|
|
357
360
|
});
|
|
358
361
|
return /* @__PURE__ */ jsx(
|
package/dist/components/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoExportButton,
|
|
3
3
|
VideoExportModal
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-TUZLVOSP.js";
|
|
5
5
|
import {
|
|
6
6
|
CoverImageExportModal,
|
|
7
7
|
coverImageFilename,
|
|
8
8
|
validateCoverImageDimensions
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-JDIMPME5.js";
|
|
10
10
|
import "../chunk-3NJDHK62.js";
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
13
|
-
import "../chunk-
|
|
14
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-47ODXCBA.js";
|
|
12
|
+
import "../chunk-A52VG7RA.js";
|
|
13
|
+
import "../chunk-C7NEHV5X.js";
|
|
14
|
+
import "../chunk-ISYJJJXN.js";
|
|
15
15
|
import "../chunk-5MFQMJ5Z.js";
|
|
16
16
|
export {
|
|
17
17
|
CoverImageExportModal,
|
|
@@ -2,10 +2,10 @@ import {
|
|
|
2
2
|
CoverImageExportModal,
|
|
3
3
|
coverImageFilename,
|
|
4
4
|
validateCoverImageDimensions
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-JDIMPME5.js";
|
|
6
6
|
import "../chunk-3NJDHK62.js";
|
|
7
|
-
import "../chunk-
|
|
8
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-A52VG7RA.js";
|
|
8
|
+
import "../chunk-C7NEHV5X.js";
|
|
9
9
|
export {
|
|
10
10
|
CoverImageExportModal,
|
|
11
11
|
coverImageFilename,
|
|
@@ -7,8 +7,8 @@ import {
|
|
|
7
7
|
} from "../chunk-3NJDHK62.js";
|
|
8
8
|
import {
|
|
9
9
|
useFrameCapture
|
|
10
|
-
} from "../chunk-
|
|
11
|
-
import "../chunk-
|
|
10
|
+
} from "../chunk-A52VG7RA.js";
|
|
11
|
+
import "../chunk-C7NEHV5X.js";
|
|
12
12
|
|
|
13
13
|
// src/DashboardImageExportModal.tsx
|
|
14
14
|
import { useCallback, useId, useMemo, useRef, useState } from "react";
|
package/dist/encoder/index.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { E as EncoderConfig, a as EncoderFrameSource, M as MainThreadEncoder, c as createEncoder, s as supportsWebCodecs, b as supportsWebCodecsH264 } from '../mainThreadEncoder-BVCLjlfj.js';
|
|
2
|
-
|
|
2
|
+
import { AudioTimelineClip } from '@bendyline/squisq-video';
|
|
3
|
+
export { AudioTimelineClip, FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
4
|
+
import { MediaScheduleOptions, Doc } from '@bendyline/squisq/schemas';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* audioTrack — In-browser audio rendering + AAC encoding for MP4 export.
|
|
@@ -26,5 +28,127 @@ export { FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
|
26
28
|
* older Chromium) or the config is unsupported.
|
|
27
29
|
*/
|
|
28
30
|
declare function supportsWebCodecsAac(sampleRate?: number, channels?: number): Promise<boolean>;
|
|
31
|
+
/**
|
|
32
|
+
* Mix a list of timeline clips into one `AudioBuffer`.
|
|
33
|
+
*
|
|
34
|
+
* Decodes each unique source once, then schedules an `AudioBufferSourceNode`
|
|
35
|
+
* per clip at its `startSec` with the trim window (`offset = sourceInSec`,
|
|
36
|
+
* `duration = durationSec`). Authored audio/narration sources must decode.
|
|
37
|
+
* Video sources are optional because a valid video can be silent: each is
|
|
38
|
+
* decoded independently, silent ones are skipped, and every audible video is
|
|
39
|
+
* connected to the same destination so overlapping tracks are mixed.
|
|
40
|
+
*/
|
|
41
|
+
declare function renderAudioTimeline(clips: AudioTimelineClip[], buffers: Map<string, ArrayBuffer>, totalDurationSec: number, sampleRate?: number): Promise<AudioBuffer | null>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Types and helpers shared by the audio-file encoder's WAV and lossy paths.
|
|
45
|
+
*/
|
|
46
|
+
type AudioFileFormat = 'm4a' | 'opus-webm' | 'wav';
|
|
47
|
+
interface AudioFileEncoderOptions {
|
|
48
|
+
readonly format: AudioFileFormat;
|
|
49
|
+
/** Sample rate of the PCM passed to `append` (e.g. 24000 or 48000). */
|
|
50
|
+
readonly sampleRate: number;
|
|
51
|
+
readonly channels: 1 | 2;
|
|
52
|
+
/** Bits per second for lossy formats. Defaults depend on format and channel count. */
|
|
53
|
+
readonly bitrate?: number;
|
|
54
|
+
}
|
|
55
|
+
interface AudioFileEncoder {
|
|
56
|
+
readonly format: AudioFileFormat;
|
|
57
|
+
/** `'audio/mp4'` | `'audio/webm'` | `'audio/wav'` */
|
|
58
|
+
readonly mimeType: string;
|
|
59
|
+
/** `'m4a'` | `'webm'` | `'wav'` */
|
|
60
|
+
readonly extension: string;
|
|
61
|
+
/**
|
|
62
|
+
* Append planar float32 PCM, one Float32Array per channel, at
|
|
63
|
+
* `options.sampleRate`. Streams; never buffers the whole file in float32.
|
|
64
|
+
* Calls are serialized, but awaiting each one respects encoder backpressure.
|
|
65
|
+
*/
|
|
66
|
+
append(channels: readonly Float32Array[]): Promise<void>;
|
|
67
|
+
/** Flush and return the finished file. */
|
|
68
|
+
finish(): Promise<Blob>;
|
|
69
|
+
/** Abandon the file and release encoder resources. */
|
|
70
|
+
cancel(): Promise<void>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Streaming audio-file encoder: planar float32 PCM in, an M4A (AAC), Opus/WebM
|
|
75
|
+
* or WAV `Blob` out.
|
|
76
|
+
*
|
|
77
|
+
* - `m4a` — AAC in an audio-only MP4 (mediabunny's MP4 writer; unlike the
|
|
78
|
+
* video export's muxer it declares no video track). Needs a WebCodecs AAC
|
|
79
|
+
* encoder, which Linux Chromium lacks.
|
|
80
|
+
* - `opus-webm` — Opus in WebM, the container recorded takes already use.
|
|
81
|
+
* - `wav` — 16-bit PCM, always available, written by a streaming writer.
|
|
82
|
+
*
|
|
83
|
+
* Callers append blocks at their own sample rate. When the codec cannot encode
|
|
84
|
+
* that rate (WebCodecs AAC takes only 44.1/48 kHz; TTS commonly emits 24 kHz)
|
|
85
|
+
* the encoder resamples in-stream with a band-limited converter. Input is
|
|
86
|
+
* consumed in short slices, so appending even a whole document's buffer never
|
|
87
|
+
* allocates another copy of it; lossy output accumulates as encoded bytes only.
|
|
88
|
+
*
|
|
89
|
+
* The lossy path (mediabunny) sits behind a dynamic import, so WAV-only
|
|
90
|
+
* callers and the rest of the `/encoder` entry never load it.
|
|
91
|
+
*/
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Formats this runtime can encode. `'wav'` is always included; `'m4a'` needs a
|
|
95
|
+
* WebCodecs AAC encoder (absent on Linux Chromium) and `'opus-webm'` an Opus
|
|
96
|
+
* one. Ordered m4a, opus-webm, wav.
|
|
97
|
+
*/
|
|
98
|
+
declare function supportedAudioFileFormats(): Promise<readonly AudioFileFormat[]>;
|
|
99
|
+
/**
|
|
100
|
+
* Create a streaming encoder for one audio file. Rejects when the runtime
|
|
101
|
+
* cannot encode the requested format (probe with
|
|
102
|
+
* {@link supportedAudioFileFormats} first); it never silently switches format.
|
|
103
|
+
*/
|
|
104
|
+
declare function createAudioFileEncoder(options: AudioFileEncoderOptions): Promise<AudioFileEncoder>;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* documentAudio — the doc → mixed `AudioBuffer` path shared by MP4 export and
|
|
108
|
+
* hosts that export a document's audio on its own.
|
|
109
|
+
*
|
|
110
|
+
* One pipeline, three steps, so the two consumers cannot drift:
|
|
111
|
+
*
|
|
112
|
+
* 1. {@link documentAudioTimeline} schedules every clip the doc plays —
|
|
113
|
+
* `doc.audio.segments`, `documentMedia` and `block.media` (including the
|
|
114
|
+
* audio of scheduled video) — via `computeAudioTimeline`, substituting
|
|
115
|
+
* media-edit renders through `processedAudio` exactly as video export does.
|
|
116
|
+
* 2. {@link loadAudioTimelineSources} reads each unique source once.
|
|
117
|
+
* 3. {@link mixAudioTimeline} renders the mix with `renderAudioTimeline`
|
|
118
|
+
* (OfflineAudioContext), at least as long as the doc's timeline.
|
|
119
|
+
*
|
|
120
|
+
* {@link renderDocumentAudio} composes the three for hosts.
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
interface RenderDocumentAudioOptions {
|
|
124
|
+
/** Read a media file referenced by the doc (relative src), or null if missing. */
|
|
125
|
+
readMedia(src: string): Promise<ArrayBuffer | null>;
|
|
126
|
+
/** Mix sample rate (default 48000). */
|
|
127
|
+
readonly sampleRate?: number;
|
|
128
|
+
readonly signal?: AbortSignal;
|
|
129
|
+
/**
|
|
130
|
+
* Media-edit render lookup (`fx` recipes), as `VideoExportConfig.processedAudio`:
|
|
131
|
+
* an edited clip mixes its processed render instead of its source. Hosts
|
|
132
|
+
* typically pass `createMediaEditRenderManager(...).processedAudio` after
|
|
133
|
+
* awaiting pending renders. Omitted → original audio everywhere.
|
|
134
|
+
*/
|
|
135
|
+
readonly processedAudio?: MediaScheduleOptions['processedAudio'];
|
|
136
|
+
/**
|
|
137
|
+
* What to do when `readMedia` returns null for a scheduled source.
|
|
138
|
+
* `'error'` (default) rejects, naming the missing files; `'skip'` mixes
|
|
139
|
+
* whatever could be read.
|
|
140
|
+
*/
|
|
141
|
+
readonly missingMedia?: 'error' | 'skip';
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Mix every audio clip the doc schedules (`doc.audio.segments`,
|
|
145
|
+
* `documentMedia`, `block.media`) on the doc's timeline, honoring media-edit
|
|
146
|
+
* renders the same way video export does. The result spans the doc's duration
|
|
147
|
+
* (or the last clip's end, if later). Null when the doc has no audio.
|
|
148
|
+
*
|
|
149
|
+
* Pass the same prepared doc video export would get — narration timing and
|
|
150
|
+
* audio mapping already resolved (`resolveAudioMapping`).
|
|
151
|
+
*/
|
|
152
|
+
declare function renderDocumentAudio(doc: Doc, options: RenderDocumentAudioOptions): Promise<AudioBuffer | null>;
|
|
29
153
|
|
|
30
|
-
export { supportsWebCodecsAac };
|
|
154
|
+
export { type AudioFileEncoder, type AudioFileEncoderOptions, type AudioFileFormat, type RenderDocumentAudioOptions, createAudioFileEncoder, renderAudioTimeline, renderDocumentAudio, supportedAudioFileFormats, supportsWebCodecsAac };
|
package/dist/encoder/index.js
CHANGED
|
@@ -1,12 +1,23 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createAudioFileEncoder,
|
|
3
|
+
supportedAudioFileFormats
|
|
4
|
+
} from "../chunk-FMQI6WK2.js";
|
|
5
|
+
import "../chunk-2W6JQSIY.js";
|
|
1
6
|
import {
|
|
2
7
|
createEncoder,
|
|
8
|
+
renderAudioTimeline,
|
|
9
|
+
renderDocumentAudio,
|
|
3
10
|
supportsWebCodecs,
|
|
4
11
|
supportsWebCodecsAac,
|
|
5
12
|
supportsWebCodecsH264
|
|
6
|
-
} from "../chunk-
|
|
13
|
+
} from "../chunk-ISYJJJXN.js";
|
|
7
14
|
import "../chunk-5MFQMJ5Z.js";
|
|
8
15
|
export {
|
|
16
|
+
createAudioFileEncoder,
|
|
9
17
|
createEncoder,
|
|
18
|
+
renderAudioTimeline,
|
|
19
|
+
renderDocumentAudio,
|
|
20
|
+
supportedAudioFileFormats,
|
|
10
21
|
supportsWebCodecs,
|
|
11
22
|
supportsWebCodecsAac,
|
|
12
23
|
supportsWebCodecsH264
|
package/dist/hooks/index.js
CHANGED
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
resolveVideoCoverFramePlan,
|
|
4
4
|
resolveVideoExportCover,
|
|
5
5
|
useVideoExport
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-47ODXCBA.js";
|
|
7
7
|
import {
|
|
8
8
|
useFrameCapture
|
|
9
|
-
} from "../chunk-
|
|
10
|
-
import "../chunk-
|
|
11
|
-
import "../chunk-
|
|
9
|
+
} from "../chunk-A52VG7RA.js";
|
|
10
|
+
import "../chunk-C7NEHV5X.js";
|
|
11
|
+
import "../chunk-ISYJJJXN.js";
|
|
12
12
|
import "../chunk-5MFQMJ5Z.js";
|
|
13
13
|
export {
|
|
14
14
|
DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
|
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,8 @@ export { CoverImageExportFormat, CoverImageExportModal, CoverImageExportModalPro
|
|
|
3
3
|
export { D as DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS, R as ResolvedVideoExportCover, U as UseVideoExportOptions, V as VideoAudioPolicy, a as VideoCoverFramePlan, b as VideoExportConfig, c as VideoExportFramePreview, d as VideoExportResult, e as VideoExportState, f as VideoOutputFormat, r as resolveVideoCoverFramePlan, g as resolveVideoExportCover, u as useVideoExport } from './useVideoExport-DsNDpjmO.js';
|
|
4
4
|
export { FrameCaptureHandle, FrameCaptureOptions, FrameCaptureRenderOptions, useFrameCapture } from './hooks/index.js';
|
|
5
5
|
export { E as EncoderConfig, a as EncoderFrameSource, M as MainThreadEncoder, c as createEncoder, s as supportsWebCodecs, b as supportsWebCodecsH264 } from './mainThreadEncoder-BVCLjlfj.js';
|
|
6
|
-
export { FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
7
|
-
export { supportsWebCodecsAac } from './encoder/index.js';
|
|
6
|
+
export { AudioTimelineClip, FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
7
|
+
export { AudioFileEncoder, AudioFileEncoderOptions, AudioFileFormat, RenderDocumentAudioOptions, createAudioFileEncoder, renderAudioTimeline, renderDocumentAudio, supportedAudioFileFormats, supportsWebCodecsAac } from './encoder/index.js';
|
|
8
8
|
export { M as MediaEditJobOptions, a as MediaEditPausesRequest, b as MediaEditPausesResult, c as MediaEditPreviewRequest, d as MediaEditPreviewResult, e as MediaEditRenderManager, f as MediaEditRenderManagerOptions, g as MediaEditRenderRequest, h as MediaEditRenderResult, i as MediaEditRenderState, j as MediaEditRenderStatus, k as MediaEditRenderer, l as MediaEditRendererOptions, m as createMediaEditRenderManager, n as createMediaEditRenderer } from './mediaEditRenderManager-W_o6RRhT.js';
|
|
9
9
|
export { MediaEditRendersView, useMediaEditRenders, useMediaEditStatus, useProcessedAudio } from './media-edit/index.js';
|
|
10
10
|
import 'react/jsx-runtime';
|
package/dist/index.js
CHANGED
|
@@ -1,36 +1,43 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoExportButton,
|
|
3
3
|
VideoExportModal
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-TUZLVOSP.js";
|
|
5
5
|
import {
|
|
6
6
|
CoverImageExportModal,
|
|
7
7
|
coverImageFilename,
|
|
8
8
|
validateCoverImageDimensions
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-JDIMPME5.js";
|
|
10
10
|
import "./chunk-3NJDHK62.js";
|
|
11
11
|
import {
|
|
12
12
|
DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
|
|
13
13
|
resolveVideoCoverFramePlan,
|
|
14
14
|
resolveVideoExportCover,
|
|
15
15
|
useVideoExport
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-47ODXCBA.js";
|
|
17
17
|
import {
|
|
18
18
|
useFrameCapture
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-A52VG7RA.js";
|
|
20
|
+
import "./chunk-C7NEHV5X.js";
|
|
21
|
+
import {
|
|
22
|
+
createAudioFileEncoder,
|
|
23
|
+
supportedAudioFileFormats
|
|
24
|
+
} from "./chunk-FMQI6WK2.js";
|
|
25
|
+
import "./chunk-2W6JQSIY.js";
|
|
21
26
|
import {
|
|
22
27
|
createEncoder,
|
|
28
|
+
renderAudioTimeline,
|
|
29
|
+
renderDocumentAudio,
|
|
23
30
|
supportsWebCodecs,
|
|
24
31
|
supportsWebCodecsAac,
|
|
25
32
|
supportsWebCodecsH264
|
|
26
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-ISYJJJXN.js";
|
|
27
34
|
import {
|
|
28
35
|
createMediaEditRenderManager,
|
|
29
36
|
createMediaEditRenderer,
|
|
30
37
|
useMediaEditRenders,
|
|
31
38
|
useMediaEditStatus,
|
|
32
39
|
useProcessedAudio
|
|
33
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-OLFWEKY5.js";
|
|
34
41
|
import "./chunk-TL2IMDIZ.js";
|
|
35
42
|
import "./chunk-5MFQMJ5Z.js";
|
|
36
43
|
export {
|
|
@@ -39,11 +46,15 @@ export {
|
|
|
39
46
|
VideoExportButton,
|
|
40
47
|
VideoExportModal,
|
|
41
48
|
coverImageFilename,
|
|
49
|
+
createAudioFileEncoder,
|
|
42
50
|
createEncoder,
|
|
43
51
|
createMediaEditRenderManager,
|
|
44
52
|
createMediaEditRenderer,
|
|
53
|
+
renderAudioTimeline,
|
|
54
|
+
renderDocumentAudio,
|
|
45
55
|
resolveVideoCoverFramePlan,
|
|
46
56
|
resolveVideoExportCover,
|
|
57
|
+
supportedAudioFileFormats,
|
|
47
58
|
supportsWebCodecs,
|
|
48
59
|
supportsWebCodecsAac,
|
|
49
60
|
supportsWebCodecsH264,
|