@bendyline/squisq-video-react 2.5.27 → 2.5.29
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{captureVideoCodecs-HVCZBBNT.js → captureVideoCodecs-3QXX66ZI.js} +1 -1
- package/dist/chunk-AARDBHRJ.js +129 -0
- package/dist/{chunk-EJ3DVPFF.js → chunk-MOIVICAD.js} +1292 -0
- package/dist/{chunk-3IMR25SX.js → chunk-MVEZDB4G.js} +1 -1
- package/dist/{chunk-XPWYKETM.js → chunk-QKTQL3NK.js} +1 -1
- package/dist/{chunk-JKRF65OD.js → chunk-QZSP5DGO.js} +1 -1
- package/dist/{chunk-J6MJYH2I.js → chunk-SGLTT5CL.js} +62 -106
- package/dist/chunk-VKGKDTWK.js +292 -0
- package/dist/{chunk-VE3OUBRG.js → chunk-XDJ2FPQQ.js} +1 -1
- package/dist/{chunk-TRCTDIWS.js → chunk-XPPQWQEO.js} +189 -3
- package/dist/{chunk-ZEA6XJL7.js → chunk-YO7JVV3K.js} +1 -1
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +4 -4
- package/dist/cover-image/index.js +2 -2
- package/dist/dashboard-image/index.js +1 -1
- package/dist/encoder/index.js +1 -1
- package/dist/hooks/index.js +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -6
- package/dist/{lossyEncoder-27NI5K4Y.js → lossyEncoder-U4V3AER7.js} +1 -1
- package/dist/media-edit/index.d.ts +2 -2
- package/dist/media-edit/index.js +1 -1
- package/dist/mediaBakeEngine-IHLCD2GH.js +12 -0
- package/dist/{mediaEditEngine-3WUTBNJN.js → mediaEditEngine-22T4WV4X.js} +5 -2
- package/dist/{mediaEditRenderManager-W_o6RRhT.d.ts → mediaEditRenderManager-DhqIn8yo.d.ts} +97 -5
- package/dist/workers/mediaEdit.worker.js +15 -2
- package/package.json +4 -4
|
@@ -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-22T4WV4X.js");
|
|
11
11
|
}
|
|
12
12
|
function inlineOptions(options) {
|
|
13
13
|
return { ...options, denoiser: options.denoiser ?? inlineDenoiser };
|
|
@@ -108,6 +108,20 @@ function createMediaEditRenderer(options = {}) {
|
|
|
108
108
|
async () => (await inlineEngine()).analyzeMediaEditPauses(request, jobOptions)
|
|
109
109
|
);
|
|
110
110
|
},
|
|
111
|
+
waveform(request, jobOptions = {}) {
|
|
112
|
+
return dispatch(
|
|
113
|
+
{ ...request, type: "waveform", id: nextId++ },
|
|
114
|
+
jobOptions,
|
|
115
|
+
async () => (await inlineEngine()).analyzeMediaEditWaveform(request, jobOptions)
|
|
116
|
+
);
|
|
117
|
+
},
|
|
118
|
+
bake(request, jobOptions = {}) {
|
|
119
|
+
return dispatch(
|
|
120
|
+
{ ...request, type: "bake", id: nextId++ },
|
|
121
|
+
jobOptions,
|
|
122
|
+
async () => (await import("./mediaBakeEngine-IHLCD2GH.js")).bakeMediaClip(request, jobOptions)
|
|
123
|
+
);
|
|
124
|
+
},
|
|
111
125
|
dispose() {
|
|
112
126
|
failAll(abortError());
|
|
113
127
|
worker?.terminate();
|
|
@@ -118,20 +132,32 @@ function createMediaEditRenderer(options = {}) {
|
|
|
118
132
|
|
|
119
133
|
// src/mediaEdit/mediaEditRenderManager.ts
|
|
120
134
|
import {
|
|
135
|
+
MEDIA_BAKE_MANIFEST_VERSION,
|
|
121
136
|
MEDIA_FX_ORDER,
|
|
122
137
|
MEDIA_RENDER_MANIFEST_VERSION,
|
|
138
|
+
buildMediaBakeIndex,
|
|
123
139
|
buildMediaRenderIndex,
|
|
124
140
|
collectDocMediaClips,
|
|
141
|
+
isMediaBakeStale,
|
|
142
|
+
mediaBakeCuts,
|
|
143
|
+
mediaBakeFilePath,
|
|
144
|
+
mediaBakeKey,
|
|
145
|
+
mediaBakeManifestPath,
|
|
146
|
+
mediaBakeSpec,
|
|
125
147
|
mediaClipRenderKey,
|
|
126
148
|
mediaRenderFilePath,
|
|
127
149
|
mediaRenderManifestPath,
|
|
128
150
|
mediaRenderStaleness,
|
|
151
|
+
parseMediaBakeManifest,
|
|
129
152
|
parseMediaRenderManifest,
|
|
130
153
|
referencedMediaRenderKeys,
|
|
131
154
|
selectMediaRendersForGc,
|
|
155
|
+
selectSupersededMediaBakes,
|
|
156
|
+
serializeMediaBakeManifest,
|
|
132
157
|
serializeMediaFx,
|
|
133
158
|
serializeMediaRenderManifest
|
|
134
159
|
} from "@bendyline/squisq/mediaEdit";
|
|
160
|
+
var RENDER_SHARE_OF_BAKE = 0.6;
|
|
135
161
|
function sourceSizeOf(sizes, src) {
|
|
136
162
|
const direct = sizes.get(src);
|
|
137
163
|
if (direct != null) return direct;
|
|
@@ -158,6 +184,11 @@ function createMediaEditRenderManager(options) {
|
|
|
158
184
|
let disposed = false;
|
|
159
185
|
let latestDoc = null;
|
|
160
186
|
let lastExtras = [];
|
|
187
|
+
let bakeIndex = /* @__PURE__ */ new Map();
|
|
188
|
+
const bakeManifests = /* @__PURE__ */ new Map();
|
|
189
|
+
const staleBakes = /* @__PURE__ */ new Set();
|
|
190
|
+
const bakesInFlight = /* @__PURE__ */ new Map();
|
|
191
|
+
const pinnedRenders = /* @__PURE__ */ new Set();
|
|
161
192
|
const indexSignature = () => [...index].filter(([key]) => !staleKeys.has(key)).map(([key, entry]) => `${key}=${entry.path}`).sort().join("|");
|
|
162
193
|
const notify = () => {
|
|
163
194
|
version++;
|
|
@@ -179,11 +210,26 @@ function createMediaEditRenderManager(options) {
|
|
|
179
210
|
manifests.set(key, manifest);
|
|
180
211
|
return manifest;
|
|
181
212
|
};
|
|
213
|
+
const bakeManifestFor = async (key) => {
|
|
214
|
+
if (bakeManifests.has(key)) return bakeManifests.get(key) ?? null;
|
|
215
|
+
const entry = bakeIndex.get(key);
|
|
216
|
+
const manifest = entry?.manifestPath ? parseMediaBakeManifest(await loadText(entry.manifestPath) ?? "") : null;
|
|
217
|
+
bakeManifests.set(key, manifest);
|
|
218
|
+
return manifest;
|
|
219
|
+
};
|
|
182
220
|
const refreshIndex = async () => {
|
|
183
221
|
const entries = await provider.listMedia();
|
|
184
222
|
const before = indexSignature();
|
|
185
223
|
index = buildMediaRenderIndex(entries);
|
|
186
224
|
sourceSizes = new Map(entries.map((e) => [e.name, e.size]));
|
|
225
|
+
bakeIndex = buildMediaBakeIndex(entries);
|
|
226
|
+
staleBakes.clear();
|
|
227
|
+
for (const [key] of bakeIndex) {
|
|
228
|
+
const manifest = await bakeManifestFor(key);
|
|
229
|
+
if (manifest && isMediaBakeStale(manifest, { sourceSize: sourceSizeOf(sourceSizes, manifest.src) })) {
|
|
230
|
+
staleBakes.add(key);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
187
233
|
staleKeys.clear();
|
|
188
234
|
for (const [key] of index) {
|
|
189
235
|
const manifest = await manifestFor(key);
|
|
@@ -204,6 +250,7 @@ function createMediaEditRenderManager(options) {
|
|
|
204
250
|
return indexLoad;
|
|
205
251
|
};
|
|
206
252
|
const isCurrent = (key) => index.has(key) && !staleKeys.has(key);
|
|
253
|
+
const isBakeCurrent = (key) => bakeIndex.has(key) && !staleBakes.has(key);
|
|
207
254
|
const pump = async () => {
|
|
208
255
|
if (running || disposed) return;
|
|
209
256
|
const job = queue.shift();
|
|
@@ -296,6 +343,123 @@ function createMediaEditRenderManager(options) {
|
|
|
296
343
|
if (!response.ok) throw new Error(`Could not read ${src} (${response.status}).`);
|
|
297
344
|
return response.blob();
|
|
298
345
|
}
|
|
346
|
+
const cancelled = () => new DOMException("The bake was cancelled.", "AbortError");
|
|
347
|
+
const processedAudioFor = async (clip, renderKey, options2) => {
|
|
348
|
+
if (!isCurrent(renderKey)) {
|
|
349
|
+
pinnedRenders.add(renderKey);
|
|
350
|
+
try {
|
|
351
|
+
if (statuses.get(renderKey)?.state === "failed") statuses.delete(renderKey);
|
|
352
|
+
enqueue(clip, false);
|
|
353
|
+
notify();
|
|
354
|
+
void pump();
|
|
355
|
+
await new Promise((resolve, reject) => {
|
|
356
|
+
const check = () => {
|
|
357
|
+
const status = statuses.get(renderKey);
|
|
358
|
+
if (options2.signal?.aborted) settle(() => reject(cancelled()));
|
|
359
|
+
else if (isCurrent(renderKey)) settle(resolve);
|
|
360
|
+
else if (status?.state === "failed") {
|
|
361
|
+
settle(() => reject(new Error(`Audio cleanup failed: ${status.error}`)));
|
|
362
|
+
} else options2.onProgress?.((status?.progress ?? 0) * RENDER_SHARE_OF_BAKE);
|
|
363
|
+
};
|
|
364
|
+
const settle = (finish) => {
|
|
365
|
+
unsubscribe();
|
|
366
|
+
options2.signal?.removeEventListener("abort", check);
|
|
367
|
+
finish();
|
|
368
|
+
};
|
|
369
|
+
const unsubscribe = manager.subscribe(check);
|
|
370
|
+
options2.signal?.addEventListener("abort", check);
|
|
371
|
+
check();
|
|
372
|
+
});
|
|
373
|
+
} finally {
|
|
374
|
+
pinnedRenders.delete(renderKey);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const path = index.get(renderKey)?.path;
|
|
378
|
+
if (!path) throw new Error("The processed audio for this clip is missing.");
|
|
379
|
+
return path;
|
|
380
|
+
};
|
|
381
|
+
const referencedBakeKeys = () => {
|
|
382
|
+
const clips = [...latestDoc ? collectDocMediaClips(latestDoc) : [], ...lastExtras];
|
|
383
|
+
return /* @__PURE__ */ new Set([...clips.map((clip) => mediaBakeKey(clip)), ...bakesInFlight.keys()]);
|
|
384
|
+
};
|
|
385
|
+
const pruneBakes = async (sources, keep) => {
|
|
386
|
+
const loaded = /* @__PURE__ */ new Map();
|
|
387
|
+
for (const [key] of bakeIndex) {
|
|
388
|
+
const manifest = await bakeManifestFor(key);
|
|
389
|
+
if (manifest) loaded.set(key, manifest);
|
|
390
|
+
}
|
|
391
|
+
const referenced = referencedBakeKeys();
|
|
392
|
+
if (keep) referenced.add(keep);
|
|
393
|
+
const paths = selectSupersededMediaBakes(bakeIndex, loaded, sources, referenced);
|
|
394
|
+
for (const path of paths) await provider.removeMedia(path).catch(() => {
|
|
395
|
+
});
|
|
396
|
+
for (const [key, entry] of [...bakeIndex]) {
|
|
397
|
+
if (!paths.includes(entry.path)) continue;
|
|
398
|
+
bakeIndex.delete(key);
|
|
399
|
+
bakeManifests.delete(key);
|
|
400
|
+
staleBakes.delete(key);
|
|
401
|
+
}
|
|
402
|
+
return paths;
|
|
403
|
+
};
|
|
404
|
+
const runBake = async (clip, key, options2) => {
|
|
405
|
+
const renderKey = mediaClipRenderKey(clip);
|
|
406
|
+
const audioPath = renderKey ? await processedAudioFor(clip, renderKey, options2) : null;
|
|
407
|
+
const from = renderKey ? RENDER_SHARE_OF_BAKE : 0;
|
|
408
|
+
const source = await loadSource(clip.src);
|
|
409
|
+
const audio = audioPath ? await loadSource(audioPath) : void 0;
|
|
410
|
+
let lastReported = 0;
|
|
411
|
+
const result = await renderer.bake(
|
|
412
|
+
{
|
|
413
|
+
source,
|
|
414
|
+
kind: clip.kind,
|
|
415
|
+
...audio ? { audio } : {},
|
|
416
|
+
...clip.clipStart != null ? { clipStart: clip.clipStart } : {},
|
|
417
|
+
...clip.clipEnd != null ? { clipEnd: clip.clipEnd } : {},
|
|
418
|
+
cuts: mediaBakeCuts(clip),
|
|
419
|
+
...clip.edits?.gain ? { gain: clip.edits.gain } : {},
|
|
420
|
+
...clip.edits?.fadeIn ? { fadeIn: clip.edits.fadeIn } : {},
|
|
421
|
+
...clip.edits?.fadeOut ? { fadeOut: clip.edits.fadeOut } : {},
|
|
422
|
+
...clip.kind === "video" && clip.edits?.crop ? { crop: clip.edits.crop } : {}
|
|
423
|
+
},
|
|
424
|
+
{
|
|
425
|
+
signal: options2.signal,
|
|
426
|
+
onProgress: (fraction) => {
|
|
427
|
+
if (fraction - lastReported < 0.01 && fraction < 1) return;
|
|
428
|
+
lastReported = fraction;
|
|
429
|
+
options2.onProgress?.(from + fraction * (1 - from));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
);
|
|
433
|
+
const size = result.bytes.byteLength;
|
|
434
|
+
const bakePath = await provider.addMedia(
|
|
435
|
+
mediaBakeFilePath(clip.src, key, result.extension),
|
|
436
|
+
result.bytes,
|
|
437
|
+
result.mimeType
|
|
438
|
+
);
|
|
439
|
+
const manifest = {
|
|
440
|
+
version: MEDIA_BAKE_MANIFEST_VERSION,
|
|
441
|
+
src: clip.src,
|
|
442
|
+
key,
|
|
443
|
+
spec: mediaBakeSpec(clip),
|
|
444
|
+
file: bakePath,
|
|
445
|
+
mimeType: result.mimeType,
|
|
446
|
+
sourceSize: source.size,
|
|
447
|
+
duration: result.durationSec,
|
|
448
|
+
engine: result.engine,
|
|
449
|
+
createdAt: now().toISOString()
|
|
450
|
+
};
|
|
451
|
+
const manifestPath = await provider.addMedia(
|
|
452
|
+
mediaBakeManifestPath(clip.src, key),
|
|
453
|
+
new TextEncoder().encode(serializeMediaBakeManifest(manifest)),
|
|
454
|
+
"application/json"
|
|
455
|
+
);
|
|
456
|
+
bakeIndex.set(key, { key, path: bakePath, manifestPath, size });
|
|
457
|
+
bakeManifests.set(key, manifest);
|
|
458
|
+
staleBakes.delete(key);
|
|
459
|
+
await pruneBakes(/* @__PURE__ */ new Set([clip.src]), key);
|
|
460
|
+
notify();
|
|
461
|
+
return bakePath;
|
|
462
|
+
};
|
|
299
463
|
const requiredKeys = (doc) => {
|
|
300
464
|
const keys = referencedMediaRenderKeys(doc);
|
|
301
465
|
for (const clip of lastExtras) {
|
|
@@ -317,7 +481,7 @@ function createMediaEditRenderManager(options) {
|
|
|
317
481
|
clips.map((clip) => mediaClipRenderKey(clip)).filter((key) => key != null)
|
|
318
482
|
);
|
|
319
483
|
for (let i = queue.length - 1; i >= 0; i--) {
|
|
320
|
-
if (!wanted.has(queue[i].key)) {
|
|
484
|
+
if (!wanted.has(queue[i].key) && !pinnedRenders.has(queue[i].key)) {
|
|
321
485
|
statuses.delete(queue[i].key);
|
|
322
486
|
queue.splice(i, 1);
|
|
323
487
|
}
|
|
@@ -369,6 +533,26 @@ function createMediaEditRenderManager(options) {
|
|
|
369
533
|
void pump();
|
|
370
534
|
},
|
|
371
535
|
loadSource,
|
|
536
|
+
bakedMedia(clip) {
|
|
537
|
+
const key = mediaBakeKey(clip);
|
|
538
|
+
return isBakeCurrent(key) ? bakeIndex.get(key)?.path : void 0;
|
|
539
|
+
},
|
|
540
|
+
async bake(clip, bakeOptions = {}) {
|
|
541
|
+
if (bakeOptions.signal?.aborted) throw cancelled();
|
|
542
|
+
await ensureIndex();
|
|
543
|
+
const key = mediaBakeKey(clip);
|
|
544
|
+
const current = isBakeCurrent(key) ? bakeIndex.get(key)?.path : void 0;
|
|
545
|
+
if (current) {
|
|
546
|
+
bakeOptions.onProgress?.(1);
|
|
547
|
+
return current;
|
|
548
|
+
}
|
|
549
|
+
let job = bakesInFlight.get(key);
|
|
550
|
+
if (!job) {
|
|
551
|
+
job = runBake(clip, key, bakeOptions).finally(() => bakesInFlight.delete(key));
|
|
552
|
+
bakesInFlight.set(key, job);
|
|
553
|
+
}
|
|
554
|
+
return job;
|
|
555
|
+
},
|
|
372
556
|
async collectGarbage(doc, at = now()) {
|
|
373
557
|
await refreshIndex();
|
|
374
558
|
const docSources = new Set(
|
|
@@ -385,6 +569,8 @@ function createMediaEditRenderManager(options) {
|
|
|
385
569
|
}
|
|
386
570
|
const paths = selectMediaRendersForGc(candidates, requiredKeys(doc), loaded, at);
|
|
387
571
|
for (const path of paths) await provider.removeMedia(path);
|
|
572
|
+
const bakes = latestDoc === doc ? await pruneBakes(docSources) : [];
|
|
573
|
+
if (bakes.length > 0) notify();
|
|
388
574
|
if (paths.length > 0) {
|
|
389
575
|
for (const key of candidates.keys()) {
|
|
390
576
|
const entry = index.get(key);
|
|
@@ -396,7 +582,7 @@ function createMediaEditRenderManager(options) {
|
|
|
396
582
|
indexVersion++;
|
|
397
583
|
notify();
|
|
398
584
|
}
|
|
399
|
-
return paths;
|
|
585
|
+
return [...paths, ...bakes];
|
|
400
586
|
},
|
|
401
587
|
subscribe(listener) {
|
|
402
588
|
listeners.add(listener);
|
|
@@ -51,7 +51,7 @@ var DecodedVideoCursor = class _DecodedVideoCursor {
|
|
|
51
51
|
static async open(blob, ownerDocument) {
|
|
52
52
|
let codecs;
|
|
53
53
|
try {
|
|
54
|
-
codecs = await import("./captureVideoCodecs-
|
|
54
|
+
codecs = await import("./captureVideoCodecs-3QXX66ZI.js");
|
|
55
55
|
} catch {
|
|
56
56
|
return null;
|
|
57
57
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode } from 'react';
|
|
3
3
|
import { Doc, MediaProvider } from '@bendyline/squisq/schemas';
|
|
4
|
-
import {
|
|
4
|
+
import { g as MediaEditRenderManager } from '../mediaEditRenderManager-DhqIn8yo.js';
|
|
5
5
|
import { b as VideoExportConfig, f as VideoOutputFormat } from '../useVideoExport-DsNDpjmO.js';
|
|
6
6
|
export { CoverImageExportFormat, CoverImageExportModal, CoverImageExportModalProps, coverImageFilename, validateCoverImageDimensions } from '../cover-image/index.js';
|
|
7
7
|
import '@bendyline/squisq/mediaEdit';
|
package/dist/components/index.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoExportButton,
|
|
3
3
|
VideoExportModal
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-QZSP5DGO.js";
|
|
5
5
|
import {
|
|
6
6
|
CoverImageExportModal,
|
|
7
7
|
coverImageFilename,
|
|
8
8
|
validateCoverImageDimensions
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-QKTQL3NK.js";
|
|
10
10
|
import "../chunk-3NJDHK62.js";
|
|
11
|
-
import "../chunk-
|
|
12
|
-
import "../chunk-
|
|
11
|
+
import "../chunk-MVEZDB4G.js";
|
|
12
|
+
import "../chunk-YO7JVV3K.js";
|
|
13
13
|
import "../chunk-RIXBIX3H.js";
|
|
14
14
|
import "../chunk-5MFQMJ5Z.js";
|
|
15
15
|
export {
|
|
@@ -2,9 +2,9 @@ import {
|
|
|
2
2
|
CoverImageExportModal,
|
|
3
3
|
coverImageFilename,
|
|
4
4
|
validateCoverImageDimensions
|
|
5
|
-
} from "../chunk-
|
|
5
|
+
} from "../chunk-QKTQL3NK.js";
|
|
6
6
|
import "../chunk-3NJDHK62.js";
|
|
7
|
-
import "../chunk-
|
|
7
|
+
import "../chunk-YO7JVV3K.js";
|
|
8
8
|
export {
|
|
9
9
|
CoverImageExportModal,
|
|
10
10
|
coverImageFilename,
|
package/dist/encoder/index.js
CHANGED
package/dist/hooks/index.js
CHANGED
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
resolveVideoCoverFramePlan,
|
|
4
4
|
resolveVideoExportCover,
|
|
5
5
|
useVideoExport
|
|
6
|
-
} from "../chunk-
|
|
6
|
+
} from "../chunk-MVEZDB4G.js";
|
|
7
7
|
import {
|
|
8
8
|
useFrameCapture
|
|
9
|
-
} from "../chunk-
|
|
9
|
+
} from "../chunk-YO7JVV3K.js";
|
|
10
10
|
import "../chunk-RIXBIX3H.js";
|
|
11
11
|
import "../chunk-5MFQMJ5Z.js";
|
|
12
12
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { FrameCaptureHandle, FrameCaptureOptions, FrameCaptureRenderOptions, use
|
|
|
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
6
|
export { AudioTimelineClip, FfmpegWasmLoadConfig } from '@bendyline/squisq-video';
|
|
7
7
|
export { AudioFileEncoder, AudioFileEncoderOptions, AudioFileFormat, RenderDocumentAudioOptions, createAudioFileEncoder, renderAudioTimeline, renderDocumentAudio, supportedAudioFileFormats, supportsWebCodecsAac } from './encoder/index.js';
|
|
8
|
-
export { M as
|
|
8
|
+
export { M as MediaBakeRequest, a as MediaBakeResult, b as MediaEditJobOptions, c as MediaEditPausesRequest, d as MediaEditPausesResult, e as MediaEditPreviewRequest, f as MediaEditPreviewResult, g as MediaEditRenderManager, h as MediaEditRenderManagerOptions, i as MediaEditRenderRequest, j as MediaEditRenderResult, k as MediaEditRenderState, l as MediaEditRenderStatus, m as MediaEditRenderer, n as MediaEditRendererOptions, o as MediaEditWaveformRequest, p as MediaEditWaveformResult, q as createMediaEditRenderManager, r as createMediaEditRenderer } from './mediaEditRenderManager-DhqIn8yo.js';
|
|
9
9
|
export { MediaEditRendersView, useMediaEditRenders, useMediaEditStatus, useProcessedAudio } from './media-edit/index.js';
|
|
10
10
|
import 'react';
|
|
11
11
|
import '@bendyline/squisq/schemas';
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
import {
|
|
2
2
|
VideoExportButton,
|
|
3
3
|
VideoExportModal
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-QZSP5DGO.js";
|
|
5
5
|
import {
|
|
6
6
|
CoverImageExportModal,
|
|
7
7
|
coverImageFilename,
|
|
8
8
|
validateCoverImageDimensions
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QKTQL3NK.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-MVEZDB4G.js";
|
|
17
17
|
import {
|
|
18
18
|
useFrameCapture
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-YO7JVV3K.js";
|
|
20
20
|
import {
|
|
21
21
|
createAudioFileEncoder,
|
|
22
22
|
supportedAudioFileFormats
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-XDJ2FPQQ.js";
|
|
24
24
|
import "./chunk-2W6JQSIY.js";
|
|
25
25
|
import {
|
|
26
26
|
createEncoder,
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
useMediaEditRenders,
|
|
38
38
|
useMediaEditStatus,
|
|
39
39
|
useProcessedAudio
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-XPPQWQEO.js";
|
|
41
41
|
import "./chunk-TL2IMDIZ.js";
|
|
42
42
|
export {
|
|
43
43
|
CoverImageExportModal,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export { M as
|
|
1
|
+
import { l as MediaEditRenderStatus, g as MediaEditRenderManager } from '../mediaEditRenderManager-DhqIn8yo.js';
|
|
2
|
+
export { M as MediaBakeRequest, a as MediaBakeResult, b as MediaEditJobOptions, c as MediaEditPausesRequest, d as MediaEditPausesResult, e as MediaEditPreviewRequest, f as MediaEditPreviewResult, h as MediaEditRenderManagerOptions, i as MediaEditRenderRequest, j as MediaEditRenderResult, k as MediaEditRenderState, m as MediaEditRenderer, n as MediaEditRendererOptions, o as MediaEditWaveformRequest, p as MediaEditWaveformResult, q as createMediaEditRenderManager, r as createMediaEditRenderer } from '../mediaEditRenderManager-DhqIn8yo.js';
|
|
3
3
|
import { MediaClip, Doc } from '@bendyline/squisq/schemas';
|
|
4
4
|
import '@bendyline/squisq/mediaEdit';
|
|
5
5
|
|
package/dist/media-edit/index.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeMediaEditAudio,
|
|
3
3
|
analyzeMediaEditPauses,
|
|
4
|
+
analyzeMediaEditWaveform,
|
|
4
5
|
previewMediaEditAudio,
|
|
5
6
|
renderMediaEditAudio
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
7
|
+
} from "./chunk-SGLTT5CL.js";
|
|
8
|
+
import "./chunk-AARDBHRJ.js";
|
|
9
|
+
import "./chunk-MOIVICAD.js";
|
|
8
10
|
export {
|
|
9
11
|
analyzeMediaEditAudio,
|
|
10
12
|
analyzeMediaEditPauses,
|
|
13
|
+
analyzeMediaEditWaveform,
|
|
11
14
|
previewMediaEditAudio,
|
|
12
15
|
renderMediaEditAudio
|
|
13
16
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Doc, MediaClip, MediaProvider } from '@bendyline/squisq/schemas';
|
|
2
|
-
import { DenoiserFactory, PauseTightenOptions, MediaCut, MediaFxAnalysis } from '@bendyline/squisq/mediaEdit';
|
|
2
|
+
import { DenoiserFactory, PauseTightenOptions, MediaCut, MediaFxAnalysis, MediaCrop } from '@bendyline/squisq/mediaEdit';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Encode the chain's output into the render file: Opus in WebM when
|
|
@@ -62,6 +62,66 @@ interface MediaEditPreviewResult {
|
|
|
62
62
|
original: Float32Array[];
|
|
63
63
|
processed: Float32Array[];
|
|
64
64
|
}
|
|
65
|
+
interface MediaEditWaveformRequest {
|
|
66
|
+
source: Blob;
|
|
67
|
+
}
|
|
68
|
+
interface MediaEditWaveformResult {
|
|
69
|
+
/**
|
|
70
|
+
* The loudest sample (0…1) in each slice of the audio track, from media
|
|
71
|
+
* time 0. Empty when the media has no audio track.
|
|
72
|
+
*/
|
|
73
|
+
peaks: Float32Array;
|
|
74
|
+
/** Slices per second. */
|
|
75
|
+
peaksPerSec: number;
|
|
76
|
+
/** Length of the whole file — its longest track — in seconds. */
|
|
77
|
+
durationSec: number;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Media-bake engine (browser): a clip with its whole recipe applied, encoded
|
|
82
|
+
* as one ordinary media file — for players that cannot apply a recipe.
|
|
83
|
+
*
|
|
84
|
+
* Audio clips: decode the trim window once, drop the cut ranges, apply gain
|
|
85
|
+
* and fades, encode (M4A where the browser can encode AAC, else Opus in WebM,
|
|
86
|
+
* else WAV). Video clips: decode the kept ranges, crop, re-encode (H.264 in
|
|
87
|
+
* MP4 where available, else VP9/VP8 in WebM) and mux with the same baked
|
|
88
|
+
* audio. The sound comes from the clip's processed-audio render when the
|
|
89
|
+
* caller passes one, so the signal chain is never run twice.
|
|
90
|
+
*
|
|
91
|
+
* Realm-agnostic, like the render engine: runs in the media-edit worker, or
|
|
92
|
+
* on the main thread as a fallback.
|
|
93
|
+
*/
|
|
94
|
+
|
|
95
|
+
interface MediaBakeRequest {
|
|
96
|
+
/** The clip's source file. */
|
|
97
|
+
source: Blob;
|
|
98
|
+
kind: 'audio' | 'video';
|
|
99
|
+
/**
|
|
100
|
+
* Processed audio to use for the sound instead of the source's own: an `fx`
|
|
101
|
+
* render, which is sample-aligned with the source's audio track.
|
|
102
|
+
*/
|
|
103
|
+
audio?: Blob;
|
|
104
|
+
/** Source in-point in seconds. Default 0. */
|
|
105
|
+
clipStart?: number;
|
|
106
|
+
/** Source out-point in seconds. Default: the source's end. */
|
|
107
|
+
clipEnd?: number;
|
|
108
|
+
/** Removed source ranges. */
|
|
109
|
+
cuts?: MediaCut[];
|
|
110
|
+
/** Clip gain in dB. */
|
|
111
|
+
gain?: number;
|
|
112
|
+
fadeIn?: number;
|
|
113
|
+
fadeOut?: number;
|
|
114
|
+
/** Video only: the part of the frame to keep. */
|
|
115
|
+
crop?: MediaCrop;
|
|
116
|
+
}
|
|
117
|
+
interface MediaBakeResult {
|
|
118
|
+
bytes: ArrayBuffer;
|
|
119
|
+
mimeType: string;
|
|
120
|
+
extension: 'mp4' | 'webm' | 'm4a' | 'wav';
|
|
121
|
+
/** Length of the baked clip in seconds. */
|
|
122
|
+
durationSec: number;
|
|
123
|
+
engine: string;
|
|
124
|
+
}
|
|
65
125
|
|
|
66
126
|
/**
|
|
67
127
|
* Main-thread client for the media-edit engine.
|
|
@@ -78,6 +138,16 @@ interface MediaEditRenderer {
|
|
|
78
138
|
preview(request: MediaEditPreviewRequest, options?: MediaEditJobOptions): Promise<MediaEditPreviewResult>;
|
|
79
139
|
/** Propose cuts that shorten long pauses. */
|
|
80
140
|
pauses(request: MediaEditPausesRequest, options?: MediaEditJobOptions): Promise<MediaEditPausesResult>;
|
|
141
|
+
/**
|
|
142
|
+
* The take's waveform peaks and its length, for an editing timeline. Media
|
|
143
|
+
* with no audio track has no peaks, but still its length.
|
|
144
|
+
*/
|
|
145
|
+
waveform(request: MediaEditWaveformRequest, options?: MediaEditJobOptions): Promise<MediaEditWaveformResult>;
|
|
146
|
+
/**
|
|
147
|
+
* Bake a clip: its source with trim, cuts, gain, fades and crop applied,
|
|
148
|
+
* encoded as one ordinary media file.
|
|
149
|
+
*/
|
|
150
|
+
bake(request: MediaBakeRequest, options?: MediaEditJobOptions): Promise<MediaBakeResult>;
|
|
81
151
|
dispose(): void;
|
|
82
152
|
}
|
|
83
153
|
interface MediaEditRendererOptions {
|
|
@@ -104,6 +174,12 @@ declare function createMediaEditRenderer(options?: MediaEditRendererOptions): Me
|
|
|
104
174
|
* media, so they travel with it. A render is stale when its source changed
|
|
105
175
|
* size (a re-recorded take under the same name) or when it skipped ops this
|
|
106
176
|
* engine now knows; stale renders are replaced.
|
|
177
|
+
*
|
|
178
|
+
* The manager also makes BAKED clips — a clip's whole recipe (trim, cuts,
|
|
179
|
+
* processed audio, gain, fades, crop) in one ordinary file, for players that
|
|
180
|
+
* cannot apply a recipe. Those are made only when asked for (`bake`): an
|
|
181
|
+
* export that needs them, or "Save edited copy". They share the folder and
|
|
182
|
+
* are replaced as the recipe changes.
|
|
107
183
|
*/
|
|
108
184
|
|
|
109
185
|
type MediaEditRenderState = 'queued' | 'rendering' | 'ready' | 'failed';
|
|
@@ -114,6 +190,11 @@ interface MediaEditRenderStatus {
|
|
|
114
190
|
progress?: number;
|
|
115
191
|
error?: string;
|
|
116
192
|
}
|
|
193
|
+
interface MediaBakeOptions {
|
|
194
|
+
signal?: AbortSignal;
|
|
195
|
+
/** 0…1 across the whole job, including a processed-audio render it has to wait for. */
|
|
196
|
+
onProgress?: (fraction: number) => void;
|
|
197
|
+
}
|
|
117
198
|
interface MediaEditRenderManager {
|
|
118
199
|
/** The engine client, shared for previews and analysis. */
|
|
119
200
|
readonly renderer: MediaEditRenderer;
|
|
@@ -134,11 +215,22 @@ interface MediaEditRenderManager {
|
|
|
134
215
|
retry(clip: MediaClip): void;
|
|
135
216
|
/** Load a media reference as a Blob (sources for previews). */
|
|
136
217
|
loadSource(src: string): Promise<Blob>;
|
|
218
|
+
/** The baked copy of a clip — its whole recipe in one file — when a current one exists. */
|
|
219
|
+
bakedMedia(clip: MediaClip): string | undefined;
|
|
220
|
+
/**
|
|
221
|
+
* Make the baked copy of a clip, or reuse a current one, and resolve with
|
|
222
|
+
* its path. A recipe with a signal chain first gets its processed-audio
|
|
223
|
+
* render (queued here if it is not already), so the baked sound is the
|
|
224
|
+
* cleaned-up sound. Rejects when the clip cannot be baked (a browser that
|
|
225
|
+
* cannot encode video, a failed render) or the job is cancelled.
|
|
226
|
+
*/
|
|
227
|
+
bake(clip: MediaClip, options?: MediaBakeOptions): Promise<string>;
|
|
137
228
|
/**
|
|
138
229
|
* Delete renders of this document's sources that no current recipe
|
|
139
|
-
* references and that are older than the GC age
|
|
140
|
-
*
|
|
141
|
-
*
|
|
230
|
+
* references and that are older than the GC age, and its baked clips that
|
|
231
|
+
* no current recipe asks for (at once: they are made again on demand).
|
|
232
|
+
* Files of other sources are never touched — a media folder can be shared
|
|
233
|
+
* by several documents. Returns the removed paths.
|
|
142
234
|
*/
|
|
143
235
|
collectGarbage(doc: Doc, now?: Date): Promise<string[]>;
|
|
144
236
|
/** Change notifications (index, status, progress). Returns an unsubscribe. */
|
|
@@ -164,4 +256,4 @@ interface MediaEditRenderManagerOptions {
|
|
|
164
256
|
}
|
|
165
257
|
declare function createMediaEditRenderManager(options: MediaEditRenderManagerOptions): MediaEditRenderManager;
|
|
166
258
|
|
|
167
|
-
export { type
|
|
259
|
+
export { type MediaBakeRequest as M, type MediaBakeResult as a, type MediaEditJobOptions as b, type MediaEditPausesRequest as c, type MediaEditPausesResult as d, type MediaEditPreviewRequest as e, type MediaEditPreviewResult as f, type MediaEditRenderManager as g, type MediaEditRenderManagerOptions as h, type MediaEditRenderRequest as i, type MediaEditRenderResult as j, type MediaEditRenderState as k, type MediaEditRenderStatus as l, type MediaEditRenderer as m, type MediaEditRendererOptions as n, type MediaEditWaveformRequest as o, type MediaEditWaveformResult as p, createMediaEditRenderManager as q, createMediaEditRenderer as r };
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
analyzeMediaEditAudio,
|
|
3
3
|
analyzeMediaEditPauses,
|
|
4
|
+
analyzeMediaEditWaveform,
|
|
4
5
|
previewMediaEditAudio,
|
|
5
6
|
renderMediaEditAudio
|
|
6
|
-
} from "../chunk-
|
|
7
|
-
import
|
|
7
|
+
} from "../chunk-SGLTT5CL.js";
|
|
8
|
+
import {
|
|
9
|
+
bakeMediaClip
|
|
10
|
+
} from "../chunk-VKGKDTWK.js";
|
|
11
|
+
import "../chunk-AARDBHRJ.js";
|
|
12
|
+
import "../chunk-MOIVICAD.js";
|
|
8
13
|
import {
|
|
9
14
|
createRnnoiseDenoiserFactory
|
|
10
15
|
} from "../chunk-TL2IMDIZ.js";
|
|
@@ -32,6 +37,14 @@ async function run(request) {
|
|
|
32
37
|
} else if (request.type === "pauses") {
|
|
33
38
|
const result = await analyzeMediaEditPauses(request, options);
|
|
34
39
|
scope.postMessage({ type: "paused", id: request.id, result });
|
|
40
|
+
} else if (request.type === "bake") {
|
|
41
|
+
const result = await bakeMediaClip(request, options);
|
|
42
|
+
scope.postMessage({ type: "baked", id: request.id, result }, [result.bytes]);
|
|
43
|
+
} else if (request.type === "waveform") {
|
|
44
|
+
const result = await analyzeMediaEditWaveform(request, options);
|
|
45
|
+
scope.postMessage({ type: "waveformed", id: request.id, result }, [
|
|
46
|
+
result.peaks.buffer
|
|
47
|
+
]);
|
|
35
48
|
} else if (request.type === "analyze") {
|
|
36
49
|
const result = await analyzeMediaEditAudio(request, options);
|
|
37
50
|
scope.postMessage({ type: "analyzed", id: request.id, result });
|