@remotion/timeline-utils 4.0.513 → 4.0.515
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/audio-waveform/audio-waveform-worker-types.d.ts +9 -20
- package/dist/audio-waveform/get-visible-waveform-volume.d.ts +8 -0
- package/dist/audio-waveform/get-visible-waveform-volume.js +36 -0
- package/dist/audio-waveform/slice-visible-waveform-peaks.d.ts +11 -0
- package/dist/audio-waveform/slice-visible-waveform-peaks.js +46 -0
- package/dist/audio-waveform/subscribe-to-waveform-peaks.d.ts +5 -0
- package/dist/audio-waveform/subscribe-to-waveform-peaks.js +148 -0
- package/dist/audio-waveform-worker.js +24 -99
- package/dist/esm/audio-waveform-worker.mjs +19 -233
- package/dist/esm/index.mjs +345 -81
- package/dist/extract-frames.js +24 -14
- package/dist/image-thumbnail/draw-repeating-image-thumbnail.d.ts +2 -1
- package/dist/image-thumbnail/draw-repeating-image-thumbnail.js +2 -1
- package/dist/index.d.ts +9 -7
- package/dist/index.js +13 -8
- package/dist/loop-display.d.ts +11 -0
- package/dist/loop-display.js +36 -1
- package/package.json +5 -4
package/dist/esm/index.mjs
CHANGED
|
@@ -97,6 +97,82 @@ var drawBars = ({
|
|
|
97
97
|
}
|
|
98
98
|
ctx.putImageData(imageData, 0, 0);
|
|
99
99
|
};
|
|
100
|
+
// src/loop-display.ts
|
|
101
|
+
var shouldTileLoopDisplay = (loopDisplay) => {
|
|
102
|
+
return loopDisplay !== undefined && loopDisplay.numberOfTimes > 1;
|
|
103
|
+
};
|
|
104
|
+
var getLoopDisplaySegments = ({
|
|
105
|
+
displayDurationInFrames,
|
|
106
|
+
displayOffsetInFrames,
|
|
107
|
+
loopDurationInFrames
|
|
108
|
+
}) => {
|
|
109
|
+
if (!Number.isFinite(displayDurationInFrames) || displayDurationInFrames <= 0 || !Number.isFinite(displayOffsetInFrames) || !Number.isFinite(loopDurationInFrames) || loopDurationInFrames <= 0) {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
const displayEndInFrames = displayOffsetInFrames + displayDurationInFrames;
|
|
113
|
+
const firstLoopIndex = Math.floor(displayOffsetInFrames / loopDurationInFrames);
|
|
114
|
+
const lastLoopIndex = Math.floor(displayEndInFrames / loopDurationInFrames);
|
|
115
|
+
const segments = [];
|
|
116
|
+
for (let loopIndex = firstLoopIndex;loopIndex <= lastLoopIndex; loopIndex++) {
|
|
117
|
+
const loopStartInFrames = loopIndex * loopDurationInFrames;
|
|
118
|
+
const segmentStart = Math.max(displayOffsetInFrames, loopStartInFrames);
|
|
119
|
+
const segmentEnd = Math.min(displayEndInFrames, loopStartInFrames + loopDurationInFrames);
|
|
120
|
+
const durationInFrames = segmentEnd - segmentStart;
|
|
121
|
+
if (durationInFrames <= 0) {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
segments.push({
|
|
125
|
+
loopIndex,
|
|
126
|
+
absoluteOffsetInFrames: segmentStart,
|
|
127
|
+
loopOffsetInFrames: segmentStart - loopStartInFrames,
|
|
128
|
+
durationInFrames
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
return segments;
|
|
132
|
+
};
|
|
133
|
+
var getLoopDisplayWidth = ({
|
|
134
|
+
visualizationWidth,
|
|
135
|
+
loopDisplay
|
|
136
|
+
}) => {
|
|
137
|
+
if (!shouldTileLoopDisplay(loopDisplay)) {
|
|
138
|
+
return visualizationWidth;
|
|
139
|
+
}
|
|
140
|
+
return visualizationWidth / loopDisplay.numberOfTimes;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// src/audio-waveform/get-visible-waveform-volume.ts
|
|
144
|
+
var getVisibleWaveformVolume = ({
|
|
145
|
+
displayDurationInFrames,
|
|
146
|
+
displayOffsetInFrames,
|
|
147
|
+
loopDisplay,
|
|
148
|
+
volume
|
|
149
|
+
}) => {
|
|
150
|
+
if (!Array.isArray(volume)) {
|
|
151
|
+
return volume;
|
|
152
|
+
}
|
|
153
|
+
if (!Number.isFinite(displayDurationInFrames) || displayDurationInFrames <= 0) {
|
|
154
|
+
return [];
|
|
155
|
+
}
|
|
156
|
+
if (!shouldTileLoopDisplay(loopDisplay) || loopDisplay.durationInFrames <= 0) {
|
|
157
|
+
const start = Math.max(0, Math.floor(displayOffsetInFrames));
|
|
158
|
+
const end = Math.min(volume.length, Math.ceil(displayOffsetInFrames + displayDurationInFrames));
|
|
159
|
+
return volume.slice(start, end);
|
|
160
|
+
}
|
|
161
|
+
const segments = getLoopDisplaySegments({
|
|
162
|
+
displayDurationInFrames,
|
|
163
|
+
displayOffsetInFrames,
|
|
164
|
+
loopDurationInFrames: loopDisplay.durationInFrames
|
|
165
|
+
});
|
|
166
|
+
const result = [];
|
|
167
|
+
for (const segment of segments) {
|
|
168
|
+
const start = Math.max(0, Math.floor(segment.loopOffsetInFrames));
|
|
169
|
+
const end = Math.min(volume.length, Math.ceil(segment.loopOffsetInFrames + segment.durationInFrames));
|
|
170
|
+
for (let index = start;index < end; index++) {
|
|
171
|
+
result.push(volume[index]);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
};
|
|
100
176
|
// src/audio-waveform/load-waveform-peaks.ts
|
|
101
177
|
import { ALL_FORMATS, AudioSampleSink, Input, UrlSource } from "mediabunny";
|
|
102
178
|
|
|
@@ -277,12 +353,6 @@ async function loadWaveformPeaks(url, signal, options) {
|
|
|
277
353
|
input.dispose();
|
|
278
354
|
}
|
|
279
355
|
}
|
|
280
|
-
// src/audio-waveform/make-audio-waveform-worker.ts
|
|
281
|
-
var makeAudioWaveformWorker = () => {
|
|
282
|
-
return new Worker(new URL("./audio-waveform-worker.mjs", import.meta.url), {
|
|
283
|
-
type: "module"
|
|
284
|
-
});
|
|
285
|
-
};
|
|
286
356
|
// src/audio-waveform/slice-waveform-peaks.ts
|
|
287
357
|
var sliceWaveformPeaks = ({
|
|
288
358
|
durationInFrames,
|
|
@@ -312,70 +382,197 @@ var sliceWaveformPeaks = ({
|
|
|
312
382
|
}
|
|
313
383
|
return portion;
|
|
314
384
|
};
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
385
|
+
|
|
386
|
+
// src/audio-waveform/slice-visible-waveform-peaks.ts
|
|
387
|
+
var sliceVisibleWaveformPeaks = ({
|
|
388
|
+
displayDurationInFrames,
|
|
389
|
+
displayOffsetInFrames,
|
|
390
|
+
durationInFrames,
|
|
391
|
+
fps,
|
|
392
|
+
loopDisplay,
|
|
393
|
+
peaks,
|
|
394
|
+
playbackRate,
|
|
395
|
+
startFrom
|
|
320
396
|
}) => {
|
|
321
|
-
if (
|
|
322
|
-
return {
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
397
|
+
if (!shouldTileLoopDisplay(loopDisplay) || loopDisplay.durationInFrames <= 0) {
|
|
398
|
+
return sliceWaveformPeaks({
|
|
399
|
+
durationInFrames: Math.min(displayDurationInFrames, Math.max(0, durationInFrames - displayOffsetInFrames)),
|
|
400
|
+
fps,
|
|
401
|
+
peaks,
|
|
402
|
+
playbackRate,
|
|
403
|
+
startFrom: startFrom + displayOffsetInFrames * playbackRate
|
|
404
|
+
});
|
|
326
405
|
}
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
};
|
|
406
|
+
const segments = getLoopDisplaySegments({
|
|
407
|
+
displayDurationInFrames,
|
|
408
|
+
displayOffsetInFrames,
|
|
409
|
+
loopDurationInFrames: loopDisplay.durationInFrames
|
|
410
|
+
});
|
|
411
|
+
const parts = [];
|
|
412
|
+
let totalLength = 0;
|
|
413
|
+
for (const segment of segments) {
|
|
414
|
+
const part = sliceWaveformPeaks({
|
|
415
|
+
durationInFrames: segment.durationInFrames,
|
|
416
|
+
fps,
|
|
417
|
+
peaks,
|
|
418
|
+
playbackRate,
|
|
419
|
+
startFrom: startFrom + segment.loopOffsetInFrames * playbackRate
|
|
420
|
+
});
|
|
421
|
+
parts.push(part);
|
|
422
|
+
totalLength += part.length;
|
|
423
|
+
}
|
|
424
|
+
if (parts.length === 1) {
|
|
425
|
+
return parts[0];
|
|
426
|
+
}
|
|
427
|
+
const result = new Float32Array(totalLength);
|
|
428
|
+
let writeOffset = 0;
|
|
429
|
+
for (const part of parts) {
|
|
430
|
+
result.set(part, writeOffset);
|
|
431
|
+
writeOffset += part.length;
|
|
432
|
+
}
|
|
433
|
+
return result;
|
|
434
|
+
};
|
|
435
|
+
// src/audio-waveform/make-audio-waveform-worker.ts
|
|
436
|
+
var makeAudioWaveformWorker = () => {
|
|
437
|
+
return new Worker(new URL("./audio-waveform-worker.mjs", import.meta.url), {
|
|
438
|
+
type: "module"
|
|
439
|
+
});
|
|
332
440
|
};
|
|
333
441
|
|
|
334
|
-
// src/
|
|
335
|
-
var
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
442
|
+
// src/audio-waveform/subscribe-to-waveform-peaks.ts
|
|
443
|
+
var peaksCache2 = new Map;
|
|
444
|
+
var inFlightBySrc = new Map;
|
|
445
|
+
var inFlightByRequestId = new Map;
|
|
446
|
+
var worker = null;
|
|
447
|
+
var workerFailed = false;
|
|
448
|
+
var nextRequestId = 0;
|
|
449
|
+
var emitPeaks = (load, peaks, final) => {
|
|
450
|
+
if (final) {
|
|
451
|
+
peaksCache2.set(load.src, peaks);
|
|
452
|
+
inFlightBySrc.delete(load.src);
|
|
453
|
+
if (load.requestId !== null) {
|
|
454
|
+
inFlightByRequestId.delete(load.requestId);
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
load.latestPeaks = peaks;
|
|
341
458
|
}
|
|
342
|
-
|
|
343
|
-
|
|
459
|
+
for (const listener of load.listeners) {
|
|
460
|
+
listener.onPeaks(peaks, final);
|
|
344
461
|
}
|
|
345
|
-
throw new Error("No canvas implementation available");
|
|
346
462
|
};
|
|
347
|
-
var
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
const ctx = canvas.getContext("2d");
|
|
352
|
-
if (!ctx) {
|
|
353
|
-
throw new Error("Failed to get canvas context");
|
|
463
|
+
var emitError = (load, error) => {
|
|
464
|
+
inFlightBySrc.delete(load.src);
|
|
465
|
+
if (load.requestId !== null) {
|
|
466
|
+
inFlightByRequestId.delete(load.requestId);
|
|
354
467
|
}
|
|
355
|
-
const
|
|
356
|
-
|
|
357
|
-
return;
|
|
468
|
+
for (const listener of load.listeners) {
|
|
469
|
+
listener.onError(error);
|
|
358
470
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
471
|
+
};
|
|
472
|
+
var startMainThreadLoad = (load) => {
|
|
473
|
+
load.requestId = null;
|
|
474
|
+
const controller = new AbortController;
|
|
475
|
+
loadWaveformPeaks(load.src, controller.signal, {
|
|
476
|
+
onProgress: ({ peaks, final }) => {
|
|
477
|
+
if (final) {
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
emitPeaks(load, peaks.slice(), false);
|
|
481
|
+
}
|
|
482
|
+
}).then((peaks) => {
|
|
483
|
+
emitPeaks(load, peaks, true);
|
|
484
|
+
}).catch((error) => {
|
|
485
|
+
emitError(load, error instanceof Error ? error : new Error("Failed to load waveform peaks"));
|
|
363
486
|
});
|
|
364
|
-
|
|
365
|
-
|
|
487
|
+
};
|
|
488
|
+
var handleWorkerFailure = () => {
|
|
489
|
+
workerFailed = true;
|
|
490
|
+
worker?.terminate();
|
|
491
|
+
worker = null;
|
|
492
|
+
const pending = [...inFlightByRequestId.values()];
|
|
493
|
+
inFlightByRequestId.clear();
|
|
494
|
+
for (const load of pending) {
|
|
495
|
+
startMainThreadLoad(load);
|
|
366
496
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (
|
|
370
|
-
|
|
497
|
+
};
|
|
498
|
+
var getOrCreateWorker = () => {
|
|
499
|
+
if (workerFailed || typeof Worker === "undefined") {
|
|
500
|
+
return null;
|
|
371
501
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
if (!pattern) {
|
|
375
|
-
return;
|
|
502
|
+
if (worker) {
|
|
503
|
+
return worker;
|
|
376
504
|
}
|
|
377
|
-
|
|
378
|
-
|
|
505
|
+
try {
|
|
506
|
+
worker = makeAudioWaveformWorker();
|
|
507
|
+
} catch {
|
|
508
|
+
workerFailed = true;
|
|
509
|
+
return null;
|
|
510
|
+
}
|
|
511
|
+
worker.addEventListener("message", (event) => {
|
|
512
|
+
const message = event.data;
|
|
513
|
+
const load = inFlightByRequestId.get(message.requestId);
|
|
514
|
+
if (!load) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (message.type === "error") {
|
|
518
|
+
emitError(load, new Error(message.message));
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
emitPeaks(load, message.peaks, message.final);
|
|
522
|
+
});
|
|
523
|
+
worker.addEventListener("error", (event) => {
|
|
524
|
+
event.preventDefault();
|
|
525
|
+
handleWorkerFailure();
|
|
526
|
+
});
|
|
527
|
+
return worker;
|
|
528
|
+
};
|
|
529
|
+
var subscribeToWaveformPeaks = ({
|
|
530
|
+
src,
|
|
531
|
+
onPeaks,
|
|
532
|
+
onError
|
|
533
|
+
}) => {
|
|
534
|
+
const cached = peaksCache2.get(src);
|
|
535
|
+
if (cached) {
|
|
536
|
+
onPeaks(cached, true);
|
|
537
|
+
return () => {
|
|
538
|
+
return;
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
const listener = { onPeaks, onError };
|
|
542
|
+
const existing = inFlightBySrc.get(src);
|
|
543
|
+
if (existing) {
|
|
544
|
+
existing.listeners.add(listener);
|
|
545
|
+
if (existing.latestPeaks) {
|
|
546
|
+
onPeaks(existing.latestPeaks, false);
|
|
547
|
+
}
|
|
548
|
+
return () => {
|
|
549
|
+
existing.listeners.delete(listener);
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
const load = {
|
|
553
|
+
src,
|
|
554
|
+
listeners: new Set([listener]),
|
|
555
|
+
latestPeaks: null,
|
|
556
|
+
requestId: null
|
|
557
|
+
};
|
|
558
|
+
inFlightBySrc.set(src, load);
|
|
559
|
+
const workerInstance = getOrCreateWorker();
|
|
560
|
+
if (workerInstance) {
|
|
561
|
+
const requestId = nextRequestId++;
|
|
562
|
+
load.requestId = requestId;
|
|
563
|
+
inFlightByRequestId.set(requestId, load);
|
|
564
|
+
const message = {
|
|
565
|
+
type: "load",
|
|
566
|
+
requestId,
|
|
567
|
+
src
|
|
568
|
+
};
|
|
569
|
+
workerInstance.postMessage(message);
|
|
570
|
+
} else {
|
|
571
|
+
startMainThreadLoad(load);
|
|
572
|
+
}
|
|
573
|
+
return () => {
|
|
574
|
+
load.listeners.delete(listener);
|
|
575
|
+
};
|
|
379
576
|
};
|
|
380
577
|
// src/clamp-timestamps-to-duration.ts
|
|
381
578
|
var LAST_FRAME_EPSILON_IN_SECONDS = 1 / 1e6;
|
|
@@ -397,6 +594,7 @@ import {
|
|
|
397
594
|
UrlSource as UrlSource2,
|
|
398
595
|
VideoSampleSink
|
|
399
596
|
} from "mediabunny";
|
|
597
|
+
import { Internals } from "remotion";
|
|
400
598
|
|
|
401
599
|
// src/get-duration-or-compute.ts
|
|
402
600
|
var getDurationOrCompute = async (input) => {
|
|
@@ -413,22 +611,34 @@ async function extractFrames({
|
|
|
413
611
|
signal,
|
|
414
612
|
repeatLastFrame = false
|
|
415
613
|
}) {
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
614
|
+
const lease = Internals.globalMediaResourceManager.acquire({
|
|
615
|
+
key: Internals.getMediabunnyInputResourceKey({
|
|
616
|
+
src,
|
|
617
|
+
credentials: null,
|
|
618
|
+
requestInitFingerprint: null,
|
|
619
|
+
revision: null
|
|
620
|
+
}),
|
|
621
|
+
create: () => {
|
|
622
|
+
const createdInput = new Input2({
|
|
623
|
+
formats: ALL_FORMATS2,
|
|
624
|
+
source: new UrlSource2(src)
|
|
625
|
+
});
|
|
626
|
+
return {
|
|
627
|
+
resource: createdInput,
|
|
628
|
+
dispose: () => createdInput.dispose()
|
|
629
|
+
};
|
|
630
|
+
}
|
|
419
631
|
});
|
|
420
|
-
const
|
|
421
|
-
input.dispose();
|
|
422
|
-
};
|
|
423
|
-
if (signal) {
|
|
424
|
-
signal.addEventListener("abort", dispose, { once: true });
|
|
425
|
-
}
|
|
632
|
+
const input = lease.resource;
|
|
426
633
|
try {
|
|
427
634
|
const [durationInSeconds, format, videoTrack] = await Promise.all([
|
|
428
|
-
getDurationOrCompute(input),
|
|
635
|
+
lease.getOrCreateValue(Internals.MEDIABUNNY_DURATION_VALUE_KEY, () => getDurationOrCompute(input)),
|
|
429
636
|
input.getFormat(),
|
|
430
637
|
input.getPrimaryVideoTrack()
|
|
431
638
|
]);
|
|
639
|
+
if (signal?.aborted) {
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
432
642
|
if (!videoTrack) {
|
|
433
643
|
throw new Error("No video track found in the input");
|
|
434
644
|
}
|
|
@@ -477,10 +687,7 @@ async function extractFrames({
|
|
|
477
687
|
}
|
|
478
688
|
throw error;
|
|
479
689
|
} finally {
|
|
480
|
-
|
|
481
|
-
if (signal) {
|
|
482
|
-
signal.removeEventListener("abort", dispose);
|
|
483
|
-
}
|
|
690
|
+
lease.release();
|
|
484
691
|
}
|
|
485
692
|
}
|
|
486
693
|
// src/frame-database.ts
|
|
@@ -540,18 +747,72 @@ var addFrameToCache = (key, frame) => {
|
|
|
540
747
|
});
|
|
541
748
|
evictLRU();
|
|
542
749
|
};
|
|
543
|
-
// src/
|
|
544
|
-
var
|
|
545
|
-
|
|
750
|
+
// src/image-thumbnail/get-scaled-image-thumbnail-dimensions.ts
|
|
751
|
+
var getScaledImageThumbnailDimensions = ({
|
|
752
|
+
naturalWidth,
|
|
753
|
+
naturalHeight,
|
|
754
|
+
canvasHeight
|
|
755
|
+
}) => {
|
|
756
|
+
if (naturalWidth === 0 || naturalHeight === 0 || canvasHeight === 0) {
|
|
757
|
+
return {
|
|
758
|
+
scaledWidth: 0,
|
|
759
|
+
scaledHeight: 0
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
const scale = canvasHeight / naturalHeight;
|
|
763
|
+
return {
|
|
764
|
+
scaledWidth: naturalWidth * scale,
|
|
765
|
+
scaledHeight: canvasHeight
|
|
766
|
+
};
|
|
546
767
|
};
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
768
|
+
|
|
769
|
+
// src/image-thumbnail/draw-repeating-image-thumbnail.ts
|
|
770
|
+
var createOffscreenCanvas = (width, height) => {
|
|
771
|
+
if (typeof document !== "undefined") {
|
|
772
|
+
const canvas = document.createElement("canvas");
|
|
773
|
+
canvas.width = width;
|
|
774
|
+
canvas.height = height;
|
|
775
|
+
return canvas;
|
|
776
|
+
}
|
|
777
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
778
|
+
return new OffscreenCanvas(width, height);
|
|
779
|
+
}
|
|
780
|
+
throw new Error("No canvas implementation available");
|
|
781
|
+
};
|
|
782
|
+
var drawRepeatingImageThumbnail = ({
|
|
783
|
+
canvas,
|
|
784
|
+
image,
|
|
785
|
+
offsetInPixels = 0
|
|
550
786
|
}) => {
|
|
551
|
-
|
|
552
|
-
|
|
787
|
+
const ctx = canvas.getContext("2d");
|
|
788
|
+
if (!ctx) {
|
|
789
|
+
throw new Error("Failed to get canvas context");
|
|
553
790
|
}
|
|
554
|
-
|
|
791
|
+
const { width, height } = canvas;
|
|
792
|
+
if (width === 0 || height === 0) {
|
|
793
|
+
return;
|
|
794
|
+
}
|
|
795
|
+
const { scaledWidth, scaledHeight } = getScaledImageThumbnailDimensions({
|
|
796
|
+
naturalWidth: image.naturalWidth,
|
|
797
|
+
naturalHeight: image.naturalHeight,
|
|
798
|
+
canvasHeight: height
|
|
799
|
+
});
|
|
800
|
+
if (scaledWidth === 0 || scaledHeight === 0) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
const offscreen = createOffscreenCanvas(scaledWidth, scaledHeight);
|
|
804
|
+
const offCtx = offscreen.getContext("2d");
|
|
805
|
+
if (!offCtx) {
|
|
806
|
+
throw new Error("Failed to get offscreen canvas context");
|
|
807
|
+
}
|
|
808
|
+
offCtx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
|
|
809
|
+
const pattern = ctx.createPattern(offscreen, "repeat-x");
|
|
810
|
+
if (!pattern) {
|
|
811
|
+
return;
|
|
812
|
+
}
|
|
813
|
+
pattern.setTransform(new DOMMatrix().translate(-offsetInPixels, 0));
|
|
814
|
+
ctx.fillStyle = pattern;
|
|
815
|
+
ctx.fillRect(0, 0, width, height);
|
|
555
816
|
};
|
|
556
817
|
// src/render-frame-strip.ts
|
|
557
818
|
var WEBCODECS_TIMESCALE = 1e6;
|
|
@@ -891,16 +1152,19 @@ var renderFrameStripToCanvas = async ({
|
|
|
891
1152
|
});
|
|
892
1153
|
};
|
|
893
1154
|
export {
|
|
1155
|
+
subscribeToWaveformPeaks,
|
|
894
1156
|
sliceWaveformPeaks,
|
|
1157
|
+
sliceVisibleWaveformPeaks,
|
|
895
1158
|
shouldTileLoopDisplay,
|
|
896
1159
|
resizeVideoFrame,
|
|
897
1160
|
renderFrameStripToCanvas,
|
|
898
1161
|
makeFrameDatabaseKey,
|
|
899
|
-
makeAudioWaveformWorker,
|
|
900
1162
|
loadWaveformPeaks,
|
|
1163
|
+
getVisibleWaveformVolume,
|
|
901
1164
|
getTimestampFromFrameDatabaseKey,
|
|
902
1165
|
getScaledImageThumbnailDimensions,
|
|
903
1166
|
getLoopDisplayWidth,
|
|
1167
|
+
getLoopDisplaySegments,
|
|
904
1168
|
getFrameDatabaseKeyPrefix,
|
|
905
1169
|
getDurationOfOneFrame,
|
|
906
1170
|
getAspectRatioFromCache,
|
package/dist/extract-frames.js
CHANGED
|
@@ -2,26 +2,39 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.extractFrames = extractFrames;
|
|
4
4
|
const mediabunny_1 = require("mediabunny");
|
|
5
|
+
const remotion_1 = require("remotion");
|
|
5
6
|
const clamp_timestamps_to_duration_1 = require("./clamp-timestamps-to-duration");
|
|
6
7
|
const get_duration_or_compute_1 = require("./get-duration-or-compute");
|
|
7
8
|
async function extractFrames({ src, timestampsInSeconds, onVideoSample, signal, repeatLastFrame = false, }) {
|
|
8
9
|
var _a;
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const lease = remotion_1.Internals.globalMediaResourceManager.acquire({
|
|
11
|
+
key: remotion_1.Internals.getMediabunnyInputResourceKey({
|
|
12
|
+
src,
|
|
13
|
+
credentials: null,
|
|
14
|
+
requestInitFingerprint: null,
|
|
15
|
+
revision: null,
|
|
16
|
+
}),
|
|
17
|
+
create: () => {
|
|
18
|
+
const createdInput = new mediabunny_1.Input({
|
|
19
|
+
formats: mediabunny_1.ALL_FORMATS,
|
|
20
|
+
source: new mediabunny_1.UrlSource(src),
|
|
21
|
+
});
|
|
22
|
+
return {
|
|
23
|
+
resource: createdInput,
|
|
24
|
+
dispose: () => createdInput.dispose(),
|
|
25
|
+
};
|
|
26
|
+
},
|
|
12
27
|
});
|
|
13
|
-
const
|
|
14
|
-
input.dispose();
|
|
15
|
-
};
|
|
16
|
-
if (signal) {
|
|
17
|
-
signal.addEventListener('abort', dispose, { once: true });
|
|
18
|
-
}
|
|
28
|
+
const input = lease.resource;
|
|
19
29
|
try {
|
|
20
30
|
const [durationInSeconds, format, videoTrack] = await Promise.all([
|
|
21
|
-
(0, get_duration_or_compute_1.getDurationOrCompute)(input),
|
|
31
|
+
lease.getOrCreateValue(remotion_1.Internals.MEDIABUNNY_DURATION_VALUE_KEY, () => (0, get_duration_or_compute_1.getDurationOrCompute)(input)),
|
|
22
32
|
input.getFormat(),
|
|
23
33
|
input.getPrimaryVideoTrack(),
|
|
24
34
|
]);
|
|
35
|
+
if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
25
38
|
if (!videoTrack) {
|
|
26
39
|
throw new Error('No video track found in the input');
|
|
27
40
|
}
|
|
@@ -84,9 +97,6 @@ async function extractFrames({ src, timestampsInSeconds, onVideoSample, signal,
|
|
|
84
97
|
throw error;
|
|
85
98
|
}
|
|
86
99
|
finally {
|
|
87
|
-
|
|
88
|
-
if (signal) {
|
|
89
|
-
signal.removeEventListener('abort', dispose);
|
|
90
|
-
}
|
|
100
|
+
lease.release();
|
|
91
101
|
}
|
|
92
102
|
}
|
|
@@ -2,8 +2,9 @@ type ImageWithNaturalDimensions = CanvasImageSource & {
|
|
|
2
2
|
readonly naturalWidth: number;
|
|
3
3
|
readonly naturalHeight: number;
|
|
4
4
|
};
|
|
5
|
-
export declare const drawRepeatingImageThumbnail: ({ canvas, image, }: {
|
|
5
|
+
export declare const drawRepeatingImageThumbnail: ({ canvas, image, offsetInPixels, }: {
|
|
6
6
|
readonly canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
7
7
|
readonly image: ImageWithNaturalDimensions;
|
|
8
|
+
readonly offsetInPixels?: number | undefined;
|
|
8
9
|
}) => void;
|
|
9
10
|
export {};
|
|
@@ -14,7 +14,7 @@ const createOffscreenCanvas = (width, height) => {
|
|
|
14
14
|
}
|
|
15
15
|
throw new Error('No canvas implementation available');
|
|
16
16
|
};
|
|
17
|
-
const drawRepeatingImageThumbnail = ({ canvas, image, }) => {
|
|
17
|
+
const drawRepeatingImageThumbnail = ({ canvas, image, offsetInPixels = 0, }) => {
|
|
18
18
|
const ctx = canvas.getContext('2d');
|
|
19
19
|
if (!ctx) {
|
|
20
20
|
throw new Error('Failed to get canvas context');
|
|
@@ -41,6 +41,7 @@ const drawRepeatingImageThumbnail = ({ canvas, image, }) => {
|
|
|
41
41
|
if (!pattern) {
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
+
pattern.setTransform(new DOMMatrix().translate(-offsetInPixels, 0));
|
|
44
45
|
ctx.fillStyle = pattern;
|
|
45
46
|
ctx.fillRect(0, 0, width, height);
|
|
46
47
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
|
-
export type { AudioWaveformWorkerIncomingMessage, AudioWaveformWorkerOutgoingMessage,
|
|
1
|
+
export type { AudioWaveformWorkerIncomingMessage, AudioWaveformWorkerOutgoingMessage, } from './audio-waveform/audio-waveform-worker-types';
|
|
2
2
|
export { TARGET_SAMPLE_RATE } from './audio-waveform/constants';
|
|
3
3
|
export { drawBars, type WaveformVolume } from './audio-waveform/draw-peaks';
|
|
4
|
+
export { getVisibleWaveformVolume } from './audio-waveform/get-visible-waveform-volume';
|
|
4
5
|
export { loadWaveformPeaks } from './audio-waveform/load-waveform-peaks';
|
|
5
|
-
export {
|
|
6
|
+
export { sliceVisibleWaveformPeaks } from './audio-waveform/slice-visible-waveform-peaks';
|
|
7
|
+
export { subscribeToWaveformPeaks } from './audio-waveform/subscribe-to-waveform-peaks';
|
|
6
8
|
export { sliceWaveformPeaks } from './audio-waveform/slice-waveform-peaks';
|
|
7
9
|
export { createWaveformPeakProcessor, emitWaveformProgress, } from './audio-waveform/waveform-peak-processor';
|
|
8
|
-
export { drawRepeatingImageThumbnail } from './image-thumbnail/draw-repeating-image-thumbnail';
|
|
9
|
-
export { getScaledImageThumbnailDimensions } from './image-thumbnail/get-scaled-image-thumbnail-dimensions';
|
|
10
10
|
export { clampTimestampsToDuration } from './clamp-timestamps-to-duration';
|
|
11
11
|
export { extractFrames } from './extract-frames';
|
|
12
12
|
export type { ExtractFramesProps, ExtractFramesTimestampsInSecondsFn, } from './extract-frames';
|
|
13
13
|
export { addFrameToCache, aspectRatioCache, frameDatabase, getAspectRatioFromCache, getFrameDatabaseKeyPrefix, getTimestampFromFrameDatabaseKey, makeFrameDatabaseKey, } from './frame-database';
|
|
14
14
|
export type { FrameDatabaseKey } from './frame-database';
|
|
15
|
-
export {
|
|
16
|
-
export
|
|
17
|
-
export {
|
|
15
|
+
export { drawRepeatingImageThumbnail } from './image-thumbnail/draw-repeating-image-thumbnail';
|
|
16
|
+
export { getScaledImageThumbnailDimensions } from './image-thumbnail/get-scaled-image-thumbnail-dimensions';
|
|
17
|
+
export { getLoopDisplaySegments, getLoopDisplayWidth, shouldTileLoopDisplay, } from './loop-display';
|
|
18
|
+
export type { LoopDisplaySegment, TimelineLoopDisplay } from './loop-display';
|
|
19
|
+
export { WEBCODECS_TIMESCALE, calculateTimestampSlots, drawSlot, ensureSlots, fillFrameWhereItFits, fillWithCachedFrames, getDurationOfOneFrame, } from './render-frame-strip';
|
|
18
20
|
export { renderFrameStripToCanvas } from './render-frame-strip-to-canvas';
|
|
19
21
|
export type { RenderFrameStripToCanvasOptions } from './render-frame-strip-to-canvas';
|
|
20
22
|
export { resizeVideoFrame } from './resize-video-frame';
|