@bendyline/squisq-video-react 2.4.11 → 2.4.13
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/NOTICE.md +20 -9
- package/THIRD_PARTY_LICENSES.txt +378 -0
- package/dist/{chunk-4NI2QIFE.js → chunk-2C4BEAWT.js} +369 -31
- package/dist/chunk-4GFZ3GUE.js +24645 -0
- package/dist/{chunk-32QCPFXE.js → chunk-GBIYYDMY.js} +31 -1
- package/dist/chunk-IFQJAW4V.js +321 -0
- package/dist/{chunk-BRE3IRIM.js → chunk-M5AK4MMV.js} +1 -1
- package/dist/{chunk-JGSJQZGU.js → chunk-RO52S6GO.js} +39 -3
- package/dist/chunk-SLLH2RTX.js +452 -0
- package/dist/chunk-TL2IMDIZ.js +27 -0
- package/dist/{chunk-BGS2NEYC.js → chunk-TUIVBMAK.js} +5 -3
- package/dist/components/index.d.ts +9 -2
- package/dist/components/index.js +6 -5
- package/dist/cover-image/index.js +3 -2
- package/dist/dashboard-image/index.js +2 -1
- package/dist/encoder/index.js +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +4 -3
- package/dist/index.d.ts +4 -1
- package/dist/index.js +19 -5
- package/dist/media-edit/index.d.ts +29 -0
- package/dist/media-edit/index.js +15 -0
- package/dist/mediaEditEngine-ZLG7D3TB.js +13 -0
- package/dist/mediaEditRenderManager-W_o6RRhT.d.ts +167 -0
- package/dist/{useVideoExport-B2eVCvTX.d.ts → useVideoExport-DsNDpjmO.d.ts} +7 -1
- package/dist/workers/mediaEdit.worker.d.ts +2 -0
- package/dist/workers/mediaEdit.worker.js +63 -0
- package/package.json +11 -5
|
@@ -247,6 +247,25 @@ function resolveOfflineAudioContext() {
|
|
|
247
247
|
}
|
|
248
248
|
return ctor;
|
|
249
249
|
}
|
|
250
|
+
function clipGainEnvelope(clip, when, duration) {
|
|
251
|
+
const level = clip.gainDb ? Math.pow(10, clip.gainDb / 20) : 1;
|
|
252
|
+
let fadeIn = Math.max(0, clip.fadeInSec ?? 0);
|
|
253
|
+
let fadeOut = Math.max(0, clip.fadeOutSec ?? 0);
|
|
254
|
+
if (level === 1 && fadeIn === 0 && fadeOut === 0) return null;
|
|
255
|
+
if (duration <= 0) return [{ time: when, value: level }];
|
|
256
|
+
if (fadeIn + fadeOut > duration) {
|
|
257
|
+
const scale = duration / (fadeIn + fadeOut);
|
|
258
|
+
fadeIn *= scale;
|
|
259
|
+
fadeOut *= scale;
|
|
260
|
+
}
|
|
261
|
+
const points = [{ time: when, value: fadeIn > 0 ? 0 : level }];
|
|
262
|
+
if (fadeIn > 0) points.push({ time: when + fadeIn, value: level });
|
|
263
|
+
if (fadeOut > 0) {
|
|
264
|
+
points.push({ time: when + duration - fadeOut, value: level });
|
|
265
|
+
points.push({ time: when + duration, value: 0 });
|
|
266
|
+
}
|
|
267
|
+
return points;
|
|
268
|
+
}
|
|
250
269
|
async function renderAudioTimeline(clips, buffers, totalDurationSec, sampleRate = EXPORT_AUDIO_SAMPLE_RATE) {
|
|
251
270
|
const Ctor = resolveOfflineAudioContext();
|
|
252
271
|
const channels = EXPORT_AUDIO_CHANNELS;
|
|
@@ -277,10 +296,21 @@ async function renderAudioTimeline(clips, buffers, totalDurationSec, sampleRate
|
|
|
277
296
|
}
|
|
278
297
|
const node = ctx.createBufferSource();
|
|
279
298
|
node.buffer = buffer;
|
|
280
|
-
node.connect(ctx.destination);
|
|
281
299
|
const when = Math.max(0, clip.startSec);
|
|
282
300
|
const offset = Math.max(0, clip.sourceInSec);
|
|
283
301
|
const duration = Math.max(0, clip.durationSec);
|
|
302
|
+
const envelope = clipGainEnvelope(clip, when, duration);
|
|
303
|
+
if (envelope) {
|
|
304
|
+
const gain = ctx.createGain();
|
|
305
|
+
gain.gain.setValueAtTime(envelope[0].value, envelope[0].time);
|
|
306
|
+
for (const point of envelope.slice(1)) {
|
|
307
|
+
gain.gain.linearRampToValueAtTime(point.value, point.time);
|
|
308
|
+
}
|
|
309
|
+
node.connect(gain);
|
|
310
|
+
gain.connect(ctx.destination);
|
|
311
|
+
} else {
|
|
312
|
+
node.connect(ctx.destination);
|
|
313
|
+
}
|
|
284
314
|
node.start(when, offset, duration);
|
|
285
315
|
scheduledNodes.push(node);
|
|
286
316
|
}
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ADTS,
|
|
3
|
+
AudioSample,
|
|
4
|
+
AudioSampleSink,
|
|
5
|
+
AudioSampleSource,
|
|
6
|
+
BlobSource,
|
|
7
|
+
BufferTarget,
|
|
8
|
+
FLAC,
|
|
9
|
+
Input,
|
|
10
|
+
MATROSKA,
|
|
11
|
+
MP3,
|
|
12
|
+
MP4,
|
|
13
|
+
OGG,
|
|
14
|
+
Output,
|
|
15
|
+
QTFF,
|
|
16
|
+
WAVE,
|
|
17
|
+
WEBM,
|
|
18
|
+
WavOutputFormat,
|
|
19
|
+
WebMOutputFormat,
|
|
20
|
+
canEncodeAudio
|
|
21
|
+
} from "./chunk-4GFZ3GUE.js";
|
|
22
|
+
|
|
23
|
+
// src/mediaEdit/mediaEditEngine.ts
|
|
24
|
+
import {
|
|
25
|
+
MEDIA_FX_ENGINE,
|
|
26
|
+
analyzeMediaFx,
|
|
27
|
+
correctedLoudnessAnalysis,
|
|
28
|
+
createBreathAnalyzer,
|
|
29
|
+
detectPauseCuts,
|
|
30
|
+
parseMediaFx,
|
|
31
|
+
renderMediaFx
|
|
32
|
+
} from "@bendyline/squisq/mediaEdit";
|
|
33
|
+
|
|
34
|
+
// src/mediaEdit/audioRenderSink.ts
|
|
35
|
+
var OPUS_BITRATE_PER_CHANNEL = 96e3;
|
|
36
|
+
async function createAudioRenderSink(channels, sampleRate) {
|
|
37
|
+
const bitrate = OPUS_BITRATE_PER_CHANNEL * channels;
|
|
38
|
+
const opus = await canEncodeAudio("opus", { numberOfChannels: channels, sampleRate, bitrate });
|
|
39
|
+
const target = new BufferTarget();
|
|
40
|
+
const output = new Output({
|
|
41
|
+
format: opus ? new WebMOutputFormat() : new WavOutputFormat(),
|
|
42
|
+
target
|
|
43
|
+
});
|
|
44
|
+
const source = new AudioSampleSource(opus ? { codec: "opus", bitrate } : { codec: "pcm-s16" });
|
|
45
|
+
output.addAudioTrack(source);
|
|
46
|
+
await output.start();
|
|
47
|
+
let written = 0;
|
|
48
|
+
return {
|
|
49
|
+
async write(block) {
|
|
50
|
+
const frames = block[0]?.length ?? 0;
|
|
51
|
+
if (frames === 0) return;
|
|
52
|
+
const data = new Float32Array(frames * channels);
|
|
53
|
+
for (let c = 0; c < channels; c++) data.set(block[c], c * frames);
|
|
54
|
+
const sample = new AudioSample({
|
|
55
|
+
data,
|
|
56
|
+
format: "f32-planar",
|
|
57
|
+
numberOfChannels: channels,
|
|
58
|
+
sampleRate,
|
|
59
|
+
timestamp: written / sampleRate
|
|
60
|
+
});
|
|
61
|
+
try {
|
|
62
|
+
await source.add(sample);
|
|
63
|
+
} finally {
|
|
64
|
+
sample.close();
|
|
65
|
+
}
|
|
66
|
+
written += frames;
|
|
67
|
+
},
|
|
68
|
+
async finish() {
|
|
69
|
+
source.close();
|
|
70
|
+
await output.finalize();
|
|
71
|
+
if (!target.buffer) throw new Error("The audio render produced no output.");
|
|
72
|
+
return opus ? { bytes: target.buffer, mimeType: "audio/webm", extension: "webm" } : { bytes: target.buffer, mimeType: "audio/wav", extension: "wav" };
|
|
73
|
+
},
|
|
74
|
+
cancel: () => output.cancel()
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// src/mediaEdit/mediaAudioSource.ts
|
|
79
|
+
import {
|
|
80
|
+
MEDIA_FX_SAMPLE_RATE,
|
|
81
|
+
createResampler
|
|
82
|
+
} from "@bendyline/squisq/mediaEdit";
|
|
83
|
+
var INPUT_FORMATS = [MP4, QTFF, WEBM, MATROSKA, WAVE, MP3, OGG, FLAC, ADTS];
|
|
84
|
+
async function openMediaAudio(blob, options = {}) {
|
|
85
|
+
const input = new Input({ source: new BlobSource(blob), formats: INPUT_FORMATS });
|
|
86
|
+
const track = await input.getPrimaryAudioTrack();
|
|
87
|
+
if (!track) {
|
|
88
|
+
input.dispose();
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
const trackDurationSec = await track.computeDuration();
|
|
92
|
+
const sourceRate = track.sampleRate;
|
|
93
|
+
const channels = Math.min(2, Math.max(1, track.numberOfChannels));
|
|
94
|
+
const startSec = Math.max(0, options.startSec ?? 0);
|
|
95
|
+
const endSec = Math.max(startSec, Math.min(options.endSec ?? trackDurationSec, trackDurationSec));
|
|
96
|
+
const durationSec = endSec - startSec;
|
|
97
|
+
const frames = Math.round(durationSec * MEDIA_FX_SAMPLE_RATE);
|
|
98
|
+
const sourceFrames = Math.round(durationSec * sourceRate);
|
|
99
|
+
async function* open() {
|
|
100
|
+
const sink = new AudioSampleSink(track);
|
|
101
|
+
const resamplers = Array.from(
|
|
102
|
+
{ length: channels },
|
|
103
|
+
() => createResampler(sourceRate, MEDIA_FX_SAMPLE_RATE)
|
|
104
|
+
);
|
|
105
|
+
let placed = 0;
|
|
106
|
+
let emitted = 0;
|
|
107
|
+
const feed = (planes) => {
|
|
108
|
+
const out = resamplers.map((r, c) => r.process(planes[c]));
|
|
109
|
+
const room2 = frames - emitted;
|
|
110
|
+
const take = Math.min(out[0].length, room2);
|
|
111
|
+
if (take <= 0) return null;
|
|
112
|
+
emitted += take;
|
|
113
|
+
return out.map((ch) => ch.length === take ? ch : ch.subarray(0, take));
|
|
114
|
+
};
|
|
115
|
+
for await (const sample of sink.samples(startSec, endSec)) {
|
|
116
|
+
try {
|
|
117
|
+
const at = Math.round((sample.timestamp - startSec) * sourceRate);
|
|
118
|
+
let planes = Array.from({ length: channels }, (_, c) => {
|
|
119
|
+
const plane = new Float32Array(sample.numberOfFrames);
|
|
120
|
+
sample.copyTo(plane, {
|
|
121
|
+
planeIndex: Math.min(c, sample.numberOfChannels - 1),
|
|
122
|
+
format: "f32-planar"
|
|
123
|
+
});
|
|
124
|
+
return plane;
|
|
125
|
+
});
|
|
126
|
+
if (at > placed) {
|
|
127
|
+
const gap = Math.min(at - placed, sourceFrames - placed);
|
|
128
|
+
if (gap > 0) {
|
|
129
|
+
const out2 = feed(Array.from({ length: channels }, () => new Float32Array(gap)));
|
|
130
|
+
placed += gap;
|
|
131
|
+
if (out2) yield out2;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const overlap = placed - at;
|
|
135
|
+
if (overlap > 0) planes = planes.map((p) => p.subarray(Math.min(overlap, p.length)));
|
|
136
|
+
const room2 = sourceFrames - placed;
|
|
137
|
+
if (room2 <= 0) break;
|
|
138
|
+
if (planes[0].length > room2) planes = planes.map((p) => p.subarray(0, room2));
|
|
139
|
+
if (planes[0].length === 0) continue;
|
|
140
|
+
placed += planes[0].length;
|
|
141
|
+
const out = feed(planes);
|
|
142
|
+
if (out) yield out;
|
|
143
|
+
} finally {
|
|
144
|
+
sample.close();
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (placed < sourceFrames) {
|
|
148
|
+
const out = feed(
|
|
149
|
+
Array.from({ length: channels }, () => new Float32Array(sourceFrames - placed))
|
|
150
|
+
);
|
|
151
|
+
if (out) yield out;
|
|
152
|
+
}
|
|
153
|
+
const tail = resamplers.map((r) => r.flush());
|
|
154
|
+
const room = frames - emitted;
|
|
155
|
+
if (room > 0) {
|
|
156
|
+
const padded = tail.map((ch) => {
|
|
157
|
+
const full = new Float32Array(room);
|
|
158
|
+
full.set(ch.subarray(0, room));
|
|
159
|
+
return full;
|
|
160
|
+
});
|
|
161
|
+
emitted += room;
|
|
162
|
+
yield padded;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return {
|
|
166
|
+
sampleRate: MEDIA_FX_SAMPLE_RATE,
|
|
167
|
+
channels,
|
|
168
|
+
frames,
|
|
169
|
+
durationSec,
|
|
170
|
+
trackDurationSec,
|
|
171
|
+
sourceSampleRate: sourceRate,
|
|
172
|
+
open,
|
|
173
|
+
dispose: () => input.dispose()
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// src/mediaEdit/mediaEditEngine.ts
|
|
178
|
+
var PREVIEW_PREROLL_SEC = 1;
|
|
179
|
+
var FIRST_PASS_SHARE = 0.67;
|
|
180
|
+
function chainOf(fx) {
|
|
181
|
+
const chain = parseMediaFx(fx);
|
|
182
|
+
if (!chain || chain.ops.length === 0) throw new Error(`Nothing to render in fx "${fx}".`);
|
|
183
|
+
return chain;
|
|
184
|
+
}
|
|
185
|
+
function concatBlocks(blocks, channels) {
|
|
186
|
+
return Array.from({ length: channels }, (_, c) => {
|
|
187
|
+
const total = blocks.reduce((n, b) => n + b[c].length, 0);
|
|
188
|
+
const out = new Float32Array(total);
|
|
189
|
+
let at = 0;
|
|
190
|
+
for (const b of blocks) {
|
|
191
|
+
out.set(b[c], at);
|
|
192
|
+
at += b[c].length;
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
async function renderMediaEditAudio(request, options = {}) {
|
|
198
|
+
const fx = chainOf(request.fx);
|
|
199
|
+
const source = await openMediaAudio(request.source);
|
|
200
|
+
if (!source) throw new Error("This media has no audio track to process.");
|
|
201
|
+
try {
|
|
202
|
+
let analysis;
|
|
203
|
+
for (let attempt = 0; ; attempt++) {
|
|
204
|
+
const from = attempt === 0 ? 0 : FIRST_PASS_SHARE;
|
|
205
|
+
const span = attempt === 0 ? FIRST_PASS_SHARE : 1 - FIRST_PASS_SHARE;
|
|
206
|
+
const sink = await createAudioRenderSink(source.channels, source.sampleRate);
|
|
207
|
+
try {
|
|
208
|
+
const result = await renderMediaFx({
|
|
209
|
+
fx,
|
|
210
|
+
source,
|
|
211
|
+
denoiser: options.denoiser,
|
|
212
|
+
write: (block) => sink.write(block),
|
|
213
|
+
onProgress: (f) => options.onProgress?.(from + f * span),
|
|
214
|
+
signal: options.signal,
|
|
215
|
+
...analysis ? { analysis } : {}
|
|
216
|
+
});
|
|
217
|
+
const corrected = attempt === 0 ? correctedLoudnessAnalysis(fx, result) : null;
|
|
218
|
+
if (corrected) {
|
|
219
|
+
await sink.cancel();
|
|
220
|
+
analysis = corrected;
|
|
221
|
+
continue;
|
|
222
|
+
}
|
|
223
|
+
const encoded = await sink.finish();
|
|
224
|
+
options.onProgress?.(1);
|
|
225
|
+
return {
|
|
226
|
+
...encoded,
|
|
227
|
+
analysis: result.analysis,
|
|
228
|
+
ignoredOps: result.ignoredOps,
|
|
229
|
+
sourceDuration: source.trackDurationSec,
|
|
230
|
+
engine: MEDIA_FX_ENGINE
|
|
231
|
+
};
|
|
232
|
+
} catch (err) {
|
|
233
|
+
await sink.cancel().catch(() => {
|
|
234
|
+
});
|
|
235
|
+
throw err;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
} finally {
|
|
239
|
+
source.dispose();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
async function analyzeMediaEditAudio(request, options = {}) {
|
|
243
|
+
const fx = chainOf(request.fx);
|
|
244
|
+
const source = await openMediaAudio(request.source);
|
|
245
|
+
if (!source) return null;
|
|
246
|
+
try {
|
|
247
|
+
return await analyzeMediaFx({
|
|
248
|
+
fx,
|
|
249
|
+
source,
|
|
250
|
+
denoiser: options.denoiser,
|
|
251
|
+
onProgress: options.onProgress,
|
|
252
|
+
signal: options.signal
|
|
253
|
+
});
|
|
254
|
+
} finally {
|
|
255
|
+
source.dispose();
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async function previewMediaEditAudio(request, options = {}) {
|
|
259
|
+
const fx = chainOf(request.fx);
|
|
260
|
+
const decodeFrom = Math.max(0, request.startSec - PREVIEW_PREROLL_SEC);
|
|
261
|
+
const source = await openMediaAudio(request.source, {
|
|
262
|
+
startSec: decodeFrom,
|
|
263
|
+
endSec: request.endSec
|
|
264
|
+
});
|
|
265
|
+
if (!source) throw new Error("This media has no audio track to process.");
|
|
266
|
+
try {
|
|
267
|
+
const originalBlocks = [];
|
|
268
|
+
for await (const block of source.open()) originalBlocks.push(block.map((ch) => ch.slice()));
|
|
269
|
+
const processedBlocks = [];
|
|
270
|
+
await renderMediaFx({
|
|
271
|
+
fx,
|
|
272
|
+
source,
|
|
273
|
+
startSec: decodeFrom,
|
|
274
|
+
denoiser: options.denoiser,
|
|
275
|
+
write: (block) => {
|
|
276
|
+
processedBlocks.push(block.map((ch) => ch.slice()));
|
|
277
|
+
},
|
|
278
|
+
onProgress: options.onProgress,
|
|
279
|
+
signal: options.signal,
|
|
280
|
+
...request.analysis ? { analysis: request.analysis } : {}
|
|
281
|
+
});
|
|
282
|
+
const skip = Math.round((request.startSec - decodeFrom) * source.sampleRate);
|
|
283
|
+
const trim = (channels) => channels.map((ch) => ch.slice(skip));
|
|
284
|
+
return {
|
|
285
|
+
sampleRate: source.sampleRate,
|
|
286
|
+
startSec: request.startSec,
|
|
287
|
+
original: trim(concatBlocks(originalBlocks, source.channels)),
|
|
288
|
+
processed: trim(concatBlocks(processedBlocks, source.channels))
|
|
289
|
+
};
|
|
290
|
+
} finally {
|
|
291
|
+
source.dispose();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
async function analyzeMediaEditPauses(request, options = {}) {
|
|
295
|
+
const source = await openMediaAudio(request.source);
|
|
296
|
+
if (!source) throw new Error("This media has no audio track to analyze.");
|
|
297
|
+
try {
|
|
298
|
+
const analyzer = createBreathAnalyzer(source.sampleRate);
|
|
299
|
+
let seen = 0;
|
|
300
|
+
for await (const block of source.open()) {
|
|
301
|
+
options.signal?.throwIfAborted();
|
|
302
|
+
const mono = block.length === 1 ? block[0] : block[0].map((_, i) => block.reduce((sum, ch) => sum + ch[i], 0) / block.length);
|
|
303
|
+
analyzer.push(mono);
|
|
304
|
+
seen += block[0].length;
|
|
305
|
+
options.onProgress?.(Math.min(1, seen / Math.max(1, source.frames)));
|
|
306
|
+
}
|
|
307
|
+
return {
|
|
308
|
+
cuts: detectPauseCuts(analyzer.frames(), request.options),
|
|
309
|
+
durationSec: source.trackDurationSec
|
|
310
|
+
};
|
|
311
|
+
} finally {
|
|
312
|
+
source.dispose();
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export {
|
|
317
|
+
renderMediaEditAudio,
|
|
318
|
+
analyzeMediaEditAudio,
|
|
319
|
+
previewMediaEditAudio,
|
|
320
|
+
analyzeMediaEditPauses
|
|
321
|
+
};
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
useVideoExport
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TUIVBMAK.js";
|
|
4
4
|
|
|
5
5
|
// src/VideoExportModal.tsx
|
|
6
|
-
import { useState, useCallback, useId, useRef } from "react";
|
|
6
|
+
import { useState, useCallback, useEffect, useId, useRef } from "react";
|
|
7
7
|
import { useModalDialog } from "@bendyline/squisq-react";
|
|
8
8
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
9
9
|
function formatDuration(seconds) {
|
|
@@ -160,6 +160,7 @@ function VideoExportModal({
|
|
|
160
160
|
mediaProvider,
|
|
161
161
|
images,
|
|
162
162
|
audio,
|
|
163
|
+
mediaEditRenders,
|
|
163
164
|
defaultConfig,
|
|
164
165
|
colorScheme = "light",
|
|
165
166
|
uiPalette,
|
|
@@ -256,7 +257,23 @@ function VideoExportModal({
|
|
|
256
257
|
setCaptionMode("off");
|
|
257
258
|
}
|
|
258
259
|
}, []);
|
|
260
|
+
const [preparingEdits, setPreparingEdits] = useState(false);
|
|
261
|
+
const editsWaitRef = useRef(null);
|
|
262
|
+
useEffect(() => () => editsWaitRef.current?.abort(), []);
|
|
259
263
|
const handleExport = useCallback(async () => {
|
|
264
|
+
if (includeAudio && mediaEditRenders) {
|
|
265
|
+
const controller = new AbortController();
|
|
266
|
+
editsWaitRef.current = controller;
|
|
267
|
+
setPreparingEdits(true);
|
|
268
|
+
try {
|
|
269
|
+
await mediaEditRenders.whenReady(doc, controller.signal);
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
} finally {
|
|
273
|
+
editsWaitRef.current = null;
|
|
274
|
+
setPreparingEdits(false);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
260
277
|
const previewCanvas = previewCanvasRef.current;
|
|
261
278
|
const previewContext = previewCanvas?.getContext("2d");
|
|
262
279
|
if (previewCanvas && previewContext) {
|
|
@@ -276,6 +293,7 @@ function VideoExportModal({
|
|
|
276
293
|
images,
|
|
277
294
|
audio,
|
|
278
295
|
mediaProvider,
|
|
296
|
+
...mediaEditRenders ? { processedAudio: mediaEditRenders.processedAudio } : {},
|
|
279
297
|
// Only thread the bundle through when the host actually supplied one.
|
|
280
298
|
...playerScript !== void 0 ? { playerScript } : {}
|
|
281
299
|
};
|
|
@@ -292,6 +310,7 @@ function VideoExportModal({
|
|
|
292
310
|
images,
|
|
293
311
|
audio,
|
|
294
312
|
mediaProvider,
|
|
313
|
+
mediaEditRenders,
|
|
295
314
|
playerScript,
|
|
296
315
|
defaultConfig,
|
|
297
316
|
startExport
|
|
@@ -505,9 +524,26 @@ function VideoExportModal({
|
|
|
505
524
|
),
|
|
506
525
|
outputFormat === "gif" && animationsEnabled && /* @__PURE__ */ jsx("p", { style: { fontSize: 12, color: palette.muted, margin: "-6px 0 12px" }, children: "Disabling motion is recommended for much smaller GIFs." })
|
|
507
526
|
] }),
|
|
527
|
+
preparingEdits && /* @__PURE__ */ jsx(
|
|
528
|
+
"p",
|
|
529
|
+
{
|
|
530
|
+
role: "status",
|
|
531
|
+
"data-squisq-video-export-preparing-edits": true,
|
|
532
|
+
style: { fontSize: 12, color: palette.muted, margin: "0 0 12px" },
|
|
533
|
+
children: "Processing audio edits\u2026"
|
|
534
|
+
}
|
|
535
|
+
),
|
|
508
536
|
/* @__PURE__ */ jsxs("div", { style: footerStyle, children: [
|
|
509
537
|
/* @__PURE__ */ jsx("button", { style: themedSecondaryButtonStyle, onClick: handleClose, children: "Cancel" }),
|
|
510
|
-
/* @__PURE__ */ jsx(
|
|
538
|
+
/* @__PURE__ */ jsx(
|
|
539
|
+
"button",
|
|
540
|
+
{
|
|
541
|
+
style: themedPrimaryButtonStyle,
|
|
542
|
+
onClick: handleExport,
|
|
543
|
+
disabled: preparingEdits,
|
|
544
|
+
children: outputFormat === "gif" ? "Export GIF" : "Export Video"
|
|
545
|
+
}
|
|
546
|
+
)
|
|
511
547
|
] })
|
|
512
548
|
] }),
|
|
513
549
|
isExporting && /* @__PURE__ */ jsxs(Fragment, { children: [
|