@remotion/timeline-utils 4.0.512 → 4.0.514
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 +2 -0
- 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-worker.js +8 -35
- package/dist/esm/audio-waveform-worker.mjs +96 -41
- package/dist/esm/index.mjs +184 -66
- 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 +7 -5
- package/dist/index.js +11 -6
- package/dist/loop-display.d.ts +11 -0
- package/dist/loop-display.js +36 -1
- package/package.json +4 -4
|
@@ -13,6 +13,8 @@ export type AudioWaveformWorkerRenderMessage = {
|
|
|
13
13
|
readonly volume: WaveformVolume;
|
|
14
14
|
readonly startFrom: number;
|
|
15
15
|
readonly durationInFrames: number;
|
|
16
|
+
readonly displayOffsetInFrames: number;
|
|
17
|
+
readonly displayDurationInFrames: number;
|
|
16
18
|
readonly fps: number;
|
|
17
19
|
readonly playbackRate: number;
|
|
18
20
|
readonly loopDisplay: TimelineLoopDisplay | undefined;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type TimelineLoopDisplay } from '../loop-display';
|
|
2
|
+
import type { WaveformVolume } from './draw-peaks';
|
|
3
|
+
export declare const getVisibleWaveformVolume: ({ displayDurationInFrames, displayOffsetInFrames, loopDisplay, volume, }: {
|
|
4
|
+
readonly displayDurationInFrames: number;
|
|
5
|
+
readonly displayOffsetInFrames: number;
|
|
6
|
+
readonly loopDisplay: TimelineLoopDisplay | undefined;
|
|
7
|
+
readonly volume: WaveformVolume;
|
|
8
|
+
}) => WaveformVolume;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getVisibleWaveformVolume = void 0;
|
|
4
|
+
const loop_display_1 = require("../loop-display");
|
|
5
|
+
const getVisibleWaveformVolume = ({ displayDurationInFrames, displayOffsetInFrames, loopDisplay, volume, }) => {
|
|
6
|
+
if (!Array.isArray(volume)) {
|
|
7
|
+
return volume;
|
|
8
|
+
}
|
|
9
|
+
if (!Number.isFinite(displayDurationInFrames) ||
|
|
10
|
+
displayDurationInFrames <= 0) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
if (!(0, loop_display_1.shouldTileLoopDisplay)(loopDisplay) ||
|
|
14
|
+
loopDisplay.durationInFrames <= 0) {
|
|
15
|
+
const start = Math.max(0, Math.floor(displayOffsetInFrames));
|
|
16
|
+
const end = Math.min(volume.length, Math.ceil(displayOffsetInFrames + displayDurationInFrames));
|
|
17
|
+
return volume.slice(start, end);
|
|
18
|
+
}
|
|
19
|
+
const segments = (0, loop_display_1.getLoopDisplaySegments)({
|
|
20
|
+
displayDurationInFrames,
|
|
21
|
+
displayOffsetInFrames,
|
|
22
|
+
loopDurationInFrames: loopDisplay.durationInFrames,
|
|
23
|
+
});
|
|
24
|
+
const result = [];
|
|
25
|
+
for (const segment of segments) {
|
|
26
|
+
const start = Math.max(0, Math.floor(segment.loopOffsetInFrames));
|
|
27
|
+
const end = Math.min(volume.length, Math.ceil(segment.loopOffsetInFrames + segment.durationInFrames));
|
|
28
|
+
// Do not `push(...slice)`: Chrome throws RangeError: Invalid array length
|
|
29
|
+
// once a loop segment is longer than ~65k frames.
|
|
30
|
+
for (let index = start; index < end; index++) {
|
|
31
|
+
result.push(volume[index]);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
exports.getVisibleWaveformVolume = getVisibleWaveformVolume;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { TimelineLoopDisplay } from '../loop-display';
|
|
2
|
+
export declare const sliceVisibleWaveformPeaks: ({ displayDurationInFrames, displayOffsetInFrames, durationInFrames, fps, loopDisplay, peaks, playbackRate, startFrom, }: {
|
|
3
|
+
readonly displayDurationInFrames: number;
|
|
4
|
+
readonly displayOffsetInFrames: number;
|
|
5
|
+
readonly durationInFrames: number;
|
|
6
|
+
readonly fps: number;
|
|
7
|
+
readonly loopDisplay: TimelineLoopDisplay | undefined;
|
|
8
|
+
readonly peaks: Float32Array<ArrayBufferLike>;
|
|
9
|
+
readonly playbackRate: number;
|
|
10
|
+
readonly startFrom: number;
|
|
11
|
+
}) => Float32Array<ArrayBufferLike>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.sliceVisibleWaveformPeaks = void 0;
|
|
4
|
+
const loop_display_1 = require("../loop-display");
|
|
5
|
+
const slice_waveform_peaks_1 = require("./slice-waveform-peaks");
|
|
6
|
+
const sliceVisibleWaveformPeaks = ({ displayDurationInFrames, displayOffsetInFrames, durationInFrames, fps, loopDisplay, peaks, playbackRate, startFrom, }) => {
|
|
7
|
+
if (!(0, loop_display_1.shouldTileLoopDisplay)(loopDisplay) ||
|
|
8
|
+
loopDisplay.durationInFrames <= 0) {
|
|
9
|
+
return (0, slice_waveform_peaks_1.sliceWaveformPeaks)({
|
|
10
|
+
durationInFrames: Math.min(displayDurationInFrames, Math.max(0, durationInFrames - displayOffsetInFrames)),
|
|
11
|
+
fps,
|
|
12
|
+
peaks,
|
|
13
|
+
playbackRate,
|
|
14
|
+
startFrom: startFrom + displayOffsetInFrames * playbackRate,
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
const segments = (0, loop_display_1.getLoopDisplaySegments)({
|
|
18
|
+
displayDurationInFrames,
|
|
19
|
+
displayOffsetInFrames,
|
|
20
|
+
loopDurationInFrames: loopDisplay.durationInFrames,
|
|
21
|
+
});
|
|
22
|
+
const parts = [];
|
|
23
|
+
let totalLength = 0;
|
|
24
|
+
for (const segment of segments) {
|
|
25
|
+
const part = (0, slice_waveform_peaks_1.sliceWaveformPeaks)({
|
|
26
|
+
durationInFrames: segment.durationInFrames,
|
|
27
|
+
fps,
|
|
28
|
+
peaks,
|
|
29
|
+
playbackRate,
|
|
30
|
+
startFrom: startFrom + segment.loopOffsetInFrames * playbackRate,
|
|
31
|
+
});
|
|
32
|
+
parts.push(part);
|
|
33
|
+
totalLength += part.length;
|
|
34
|
+
}
|
|
35
|
+
if (parts.length === 1) {
|
|
36
|
+
return parts[0];
|
|
37
|
+
}
|
|
38
|
+
const result = new Float32Array(totalLength);
|
|
39
|
+
let writeOffset = 0;
|
|
40
|
+
for (const part of parts) {
|
|
41
|
+
result.set(part, writeOffset);
|
|
42
|
+
writeOffset += part.length;
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
};
|
|
46
|
+
exports.sliceVisibleWaveformPeaks = sliceVisibleWaveformPeaks;
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
4
|
const draw_peaks_1 = require("./audio-waveform/draw-peaks");
|
|
5
5
|
const load_waveform_peaks_1 = require("./audio-waveform/load-waveform-peaks");
|
|
6
|
-
const
|
|
7
|
-
const loop_display_1 = require("./loop-display");
|
|
6
|
+
const slice_visible_waveform_peaks_1 = require("./audio-waveform/slice-visible-waveform-peaks");
|
|
8
7
|
let canvas = null;
|
|
9
8
|
let currentController = null;
|
|
10
9
|
let latestRequestId = 0;
|
|
@@ -21,49 +20,23 @@ const drawPartialWaveform = (message, peaks) => {
|
|
|
21
20
|
if (!canvas) {
|
|
22
21
|
return;
|
|
23
22
|
}
|
|
24
|
-
const portionPeaks = (0,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
const portionPeaks = (0, slice_visible_waveform_peaks_1.sliceVisibleWaveformPeaks)({
|
|
24
|
+
displayDurationInFrames: message.displayDurationInFrames,
|
|
25
|
+
displayOffsetInFrames: message.displayOffsetInFrames,
|
|
26
|
+
durationInFrames: message.durationInFrames,
|
|
28
27
|
fps: message.fps,
|
|
28
|
+
loopDisplay: message.loopDisplay,
|
|
29
29
|
peaks,
|
|
30
30
|
playbackRate: message.playbackRate,
|
|
31
31
|
startFrom: message.startFrom,
|
|
32
32
|
});
|
|
33
|
-
if (!(0, loop_display_1.shouldTileLoopDisplay)(message.loopDisplay)) {
|
|
34
|
-
(0, draw_peaks_1.drawBars)({
|
|
35
|
-
canvas,
|
|
36
|
-
peaks: portionPeaks,
|
|
37
|
-
color: 'rgba(255, 255, 255, 0.6)',
|
|
38
|
-
volume: message.volume,
|
|
39
|
-
width: message.width,
|
|
40
|
-
});
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const loopWidth = (0, loop_display_1.getLoopDisplayWidth)({
|
|
44
|
-
visualizationWidth: message.width,
|
|
45
|
-
loopDisplay: message.loopDisplay,
|
|
46
|
-
});
|
|
47
|
-
const targetCanvas = new OffscreenCanvas(Math.max(1, Math.ceil(loopWidth)), message.height);
|
|
48
33
|
(0, draw_peaks_1.drawBars)({
|
|
49
|
-
canvas
|
|
34
|
+
canvas,
|
|
50
35
|
peaks: portionPeaks,
|
|
51
36
|
color: 'rgba(255, 255, 255, 0.6)',
|
|
52
37
|
volume: message.volume,
|
|
53
|
-
width:
|
|
38
|
+
width: message.width,
|
|
54
39
|
});
|
|
55
|
-
const ctx = canvas.getContext('2d');
|
|
56
|
-
if (!ctx) {
|
|
57
|
-
throw new Error('Failed to get canvas context');
|
|
58
|
-
}
|
|
59
|
-
const pattern = ctx.createPattern(targetCanvas, 'repeat-x');
|
|
60
|
-
if (!pattern) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
pattern.setTransform(new DOMMatrix().scaleSelf(loopWidth / targetCanvas.width, 1));
|
|
64
|
-
ctx.clearRect(0, 0, message.width, message.height);
|
|
65
|
-
ctx.fillStyle = pattern;
|
|
66
|
-
ctx.fillRect(0, 0, message.width, message.height);
|
|
67
40
|
};
|
|
68
41
|
const renderWaveform = async (message) => {
|
|
69
42
|
if (!canvas) {
|
|
@@ -280,6 +280,49 @@ async function loadWaveformPeaks(url, signal, options) {
|
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
// src/loop-display.ts
|
|
284
|
+
var shouldTileLoopDisplay = (loopDisplay) => {
|
|
285
|
+
return loopDisplay !== undefined && loopDisplay.numberOfTimes > 1;
|
|
286
|
+
};
|
|
287
|
+
var getLoopDisplaySegments = ({
|
|
288
|
+
displayDurationInFrames,
|
|
289
|
+
displayOffsetInFrames,
|
|
290
|
+
loopDurationInFrames
|
|
291
|
+
}) => {
|
|
292
|
+
if (!Number.isFinite(displayDurationInFrames) || displayDurationInFrames <= 0 || !Number.isFinite(displayOffsetInFrames) || !Number.isFinite(loopDurationInFrames) || loopDurationInFrames <= 0) {
|
|
293
|
+
return [];
|
|
294
|
+
}
|
|
295
|
+
const displayEndInFrames = displayOffsetInFrames + displayDurationInFrames;
|
|
296
|
+
const firstLoopIndex = Math.floor(displayOffsetInFrames / loopDurationInFrames);
|
|
297
|
+
const lastLoopIndex = Math.floor(displayEndInFrames / loopDurationInFrames);
|
|
298
|
+
const segments = [];
|
|
299
|
+
for (let loopIndex = firstLoopIndex;loopIndex <= lastLoopIndex; loopIndex++) {
|
|
300
|
+
const loopStartInFrames = loopIndex * loopDurationInFrames;
|
|
301
|
+
const segmentStart = Math.max(displayOffsetInFrames, loopStartInFrames);
|
|
302
|
+
const segmentEnd = Math.min(displayEndInFrames, loopStartInFrames + loopDurationInFrames);
|
|
303
|
+
const durationInFrames = segmentEnd - segmentStart;
|
|
304
|
+
if (durationInFrames <= 0) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
segments.push({
|
|
308
|
+
loopIndex,
|
|
309
|
+
absoluteOffsetInFrames: segmentStart,
|
|
310
|
+
loopOffsetInFrames: segmentStart - loopStartInFrames,
|
|
311
|
+
durationInFrames
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
return segments;
|
|
315
|
+
};
|
|
316
|
+
var getLoopDisplayWidth = ({
|
|
317
|
+
visualizationWidth,
|
|
318
|
+
loopDisplay
|
|
319
|
+
}) => {
|
|
320
|
+
if (!shouldTileLoopDisplay(loopDisplay)) {
|
|
321
|
+
return visualizationWidth;
|
|
322
|
+
}
|
|
323
|
+
return visualizationWidth / loopDisplay.numberOfTimes;
|
|
324
|
+
};
|
|
325
|
+
|
|
283
326
|
// src/audio-waveform/slice-waveform-peaks.ts
|
|
284
327
|
var sliceWaveformPeaks = ({
|
|
285
328
|
durationInFrames,
|
|
@@ -310,18 +353,54 @@ var sliceWaveformPeaks = ({
|
|
|
310
353
|
return portion;
|
|
311
354
|
};
|
|
312
355
|
|
|
313
|
-
// src/
|
|
314
|
-
var
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
loopDisplay
|
|
356
|
+
// src/audio-waveform/slice-visible-waveform-peaks.ts
|
|
357
|
+
var sliceVisibleWaveformPeaks = ({
|
|
358
|
+
displayDurationInFrames,
|
|
359
|
+
displayOffsetInFrames,
|
|
360
|
+
durationInFrames,
|
|
361
|
+
fps,
|
|
362
|
+
loopDisplay,
|
|
363
|
+
peaks,
|
|
364
|
+
playbackRate,
|
|
365
|
+
startFrom
|
|
320
366
|
}) => {
|
|
321
|
-
if (!shouldTileLoopDisplay(loopDisplay)) {
|
|
322
|
-
return
|
|
367
|
+
if (!shouldTileLoopDisplay(loopDisplay) || loopDisplay.durationInFrames <= 0) {
|
|
368
|
+
return sliceWaveformPeaks({
|
|
369
|
+
durationInFrames: Math.min(displayDurationInFrames, Math.max(0, durationInFrames - displayOffsetInFrames)),
|
|
370
|
+
fps,
|
|
371
|
+
peaks,
|
|
372
|
+
playbackRate,
|
|
373
|
+
startFrom: startFrom + displayOffsetInFrames * playbackRate
|
|
374
|
+
});
|
|
323
375
|
}
|
|
324
|
-
|
|
376
|
+
const segments = getLoopDisplaySegments({
|
|
377
|
+
displayDurationInFrames,
|
|
378
|
+
displayOffsetInFrames,
|
|
379
|
+
loopDurationInFrames: loopDisplay.durationInFrames
|
|
380
|
+
});
|
|
381
|
+
const parts = [];
|
|
382
|
+
let totalLength = 0;
|
|
383
|
+
for (const segment of segments) {
|
|
384
|
+
const part = sliceWaveformPeaks({
|
|
385
|
+
durationInFrames: segment.durationInFrames,
|
|
386
|
+
fps,
|
|
387
|
+
peaks,
|
|
388
|
+
playbackRate,
|
|
389
|
+
startFrom: startFrom + segment.loopOffsetInFrames * playbackRate
|
|
390
|
+
});
|
|
391
|
+
parts.push(part);
|
|
392
|
+
totalLength += part.length;
|
|
393
|
+
}
|
|
394
|
+
if (parts.length === 1) {
|
|
395
|
+
return parts[0];
|
|
396
|
+
}
|
|
397
|
+
const result = new Float32Array(totalLength);
|
|
398
|
+
let writeOffset = 0;
|
|
399
|
+
for (const part of parts) {
|
|
400
|
+
result.set(part, writeOffset);
|
|
401
|
+
writeOffset += part.length;
|
|
402
|
+
}
|
|
403
|
+
return result;
|
|
325
404
|
};
|
|
326
405
|
|
|
327
406
|
// src/audio-waveform-worker.ts
|
|
@@ -341,47 +420,23 @@ var drawPartialWaveform = (message, peaks) => {
|
|
|
341
420
|
if (!canvas) {
|
|
342
421
|
return;
|
|
343
422
|
}
|
|
344
|
-
const portionPeaks =
|
|
345
|
-
|
|
423
|
+
const portionPeaks = sliceVisibleWaveformPeaks({
|
|
424
|
+
displayDurationInFrames: message.displayDurationInFrames,
|
|
425
|
+
displayOffsetInFrames: message.displayOffsetInFrames,
|
|
426
|
+
durationInFrames: message.durationInFrames,
|
|
346
427
|
fps: message.fps,
|
|
428
|
+
loopDisplay: message.loopDisplay,
|
|
347
429
|
peaks,
|
|
348
430
|
playbackRate: message.playbackRate,
|
|
349
431
|
startFrom: message.startFrom
|
|
350
432
|
});
|
|
351
|
-
if (!shouldTileLoopDisplay(message.loopDisplay)) {
|
|
352
|
-
drawBars({
|
|
353
|
-
canvas,
|
|
354
|
-
peaks: portionPeaks,
|
|
355
|
-
color: "rgba(255, 255, 255, 0.6)",
|
|
356
|
-
volume: message.volume,
|
|
357
|
-
width: message.width
|
|
358
|
-
});
|
|
359
|
-
return;
|
|
360
|
-
}
|
|
361
|
-
const loopWidth = getLoopDisplayWidth({
|
|
362
|
-
visualizationWidth: message.width,
|
|
363
|
-
loopDisplay: message.loopDisplay
|
|
364
|
-
});
|
|
365
|
-
const targetCanvas = new OffscreenCanvas(Math.max(1, Math.ceil(loopWidth)), message.height);
|
|
366
433
|
drawBars({
|
|
367
|
-
canvas
|
|
434
|
+
canvas,
|
|
368
435
|
peaks: portionPeaks,
|
|
369
436
|
color: "rgba(255, 255, 255, 0.6)",
|
|
370
437
|
volume: message.volume,
|
|
371
|
-
width:
|
|
438
|
+
width: message.width
|
|
372
439
|
});
|
|
373
|
-
const ctx = canvas.getContext("2d");
|
|
374
|
-
if (!ctx) {
|
|
375
|
-
throw new Error("Failed to get canvas context");
|
|
376
|
-
}
|
|
377
|
-
const pattern = ctx.createPattern(targetCanvas, "repeat-x");
|
|
378
|
-
if (!pattern) {
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
pattern.setTransform(new DOMMatrix().scaleSelf(loopWidth / targetCanvas.width, 1));
|
|
382
|
-
ctx.clearRect(0, 0, message.width, message.height);
|
|
383
|
-
ctx.fillStyle = pattern;
|
|
384
|
-
ctx.fillRect(0, 0, message.width, message.height);
|
|
385
440
|
};
|
|
386
441
|
var renderWaveform = async (message) => {
|
|
387
442
|
if (!canvas) {
|
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
|
|
|
@@ -312,70 +388,55 @@ var sliceWaveformPeaks = ({
|
|
|
312
388
|
}
|
|
313
389
|
return portion;
|
|
314
390
|
};
|
|
315
|
-
// src/image-thumbnail/get-scaled-image-thumbnail-dimensions.ts
|
|
316
|
-
var getScaledImageThumbnailDimensions = ({
|
|
317
|
-
naturalWidth,
|
|
318
|
-
naturalHeight,
|
|
319
|
-
canvasHeight
|
|
320
|
-
}) => {
|
|
321
|
-
if (naturalWidth === 0 || naturalHeight === 0 || canvasHeight === 0) {
|
|
322
|
-
return {
|
|
323
|
-
scaledWidth: 0,
|
|
324
|
-
scaledHeight: 0
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
const scale = canvasHeight / naturalHeight;
|
|
328
|
-
return {
|
|
329
|
-
scaledWidth: naturalWidth * scale,
|
|
330
|
-
scaledHeight: canvasHeight
|
|
331
|
-
};
|
|
332
|
-
};
|
|
333
391
|
|
|
334
|
-
// src/
|
|
335
|
-
var
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
}
|
|
345
|
-
throw new Error("No canvas implementation available");
|
|
346
|
-
};
|
|
347
|
-
var drawRepeatingImageThumbnail = ({
|
|
348
|
-
canvas,
|
|
349
|
-
image
|
|
392
|
+
// src/audio-waveform/slice-visible-waveform-peaks.ts
|
|
393
|
+
var sliceVisibleWaveformPeaks = ({
|
|
394
|
+
displayDurationInFrames,
|
|
395
|
+
displayOffsetInFrames,
|
|
396
|
+
durationInFrames,
|
|
397
|
+
fps,
|
|
398
|
+
loopDisplay,
|
|
399
|
+
peaks,
|
|
400
|
+
playbackRate,
|
|
401
|
+
startFrom
|
|
350
402
|
}) => {
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
403
|
+
if (!shouldTileLoopDisplay(loopDisplay) || loopDisplay.durationInFrames <= 0) {
|
|
404
|
+
return sliceWaveformPeaks({
|
|
405
|
+
durationInFrames: Math.min(displayDurationInFrames, Math.max(0, durationInFrames - displayOffsetInFrames)),
|
|
406
|
+
fps,
|
|
407
|
+
peaks,
|
|
408
|
+
playbackRate,
|
|
409
|
+
startFrom: startFrom + displayOffsetInFrames * playbackRate
|
|
410
|
+
});
|
|
358
411
|
}
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
412
|
+
const segments = getLoopDisplaySegments({
|
|
413
|
+
displayDurationInFrames,
|
|
414
|
+
displayOffsetInFrames,
|
|
415
|
+
loopDurationInFrames: loopDisplay.durationInFrames
|
|
363
416
|
});
|
|
364
|
-
|
|
365
|
-
|
|
417
|
+
const parts = [];
|
|
418
|
+
let totalLength = 0;
|
|
419
|
+
for (const segment of segments) {
|
|
420
|
+
const part = sliceWaveformPeaks({
|
|
421
|
+
durationInFrames: segment.durationInFrames,
|
|
422
|
+
fps,
|
|
423
|
+
peaks,
|
|
424
|
+
playbackRate,
|
|
425
|
+
startFrom: startFrom + segment.loopOffsetInFrames * playbackRate
|
|
426
|
+
});
|
|
427
|
+
parts.push(part);
|
|
428
|
+
totalLength += part.length;
|
|
366
429
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
if (!offCtx) {
|
|
370
|
-
throw new Error("Failed to get offscreen canvas context");
|
|
430
|
+
if (parts.length === 1) {
|
|
431
|
+
return parts[0];
|
|
371
432
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
433
|
+
const result = new Float32Array(totalLength);
|
|
434
|
+
let writeOffset = 0;
|
|
435
|
+
for (const part of parts) {
|
|
436
|
+
result.set(part, writeOffset);
|
|
437
|
+
writeOffset += part.length;
|
|
376
438
|
}
|
|
377
|
-
|
|
378
|
-
ctx.fillRect(0, 0, width, height);
|
|
439
|
+
return result;
|
|
379
440
|
};
|
|
380
441
|
// src/clamp-timestamps-to-duration.ts
|
|
381
442
|
var LAST_FRAME_EPSILON_IN_SECONDS = 1 / 1e6;
|
|
@@ -540,18 +601,72 @@ var addFrameToCache = (key, frame) => {
|
|
|
540
601
|
});
|
|
541
602
|
evictLRU();
|
|
542
603
|
};
|
|
543
|
-
// src/
|
|
544
|
-
var
|
|
545
|
-
|
|
604
|
+
// src/image-thumbnail/get-scaled-image-thumbnail-dimensions.ts
|
|
605
|
+
var getScaledImageThumbnailDimensions = ({
|
|
606
|
+
naturalWidth,
|
|
607
|
+
naturalHeight,
|
|
608
|
+
canvasHeight
|
|
609
|
+
}) => {
|
|
610
|
+
if (naturalWidth === 0 || naturalHeight === 0 || canvasHeight === 0) {
|
|
611
|
+
return {
|
|
612
|
+
scaledWidth: 0,
|
|
613
|
+
scaledHeight: 0
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
const scale = canvasHeight / naturalHeight;
|
|
617
|
+
return {
|
|
618
|
+
scaledWidth: naturalWidth * scale,
|
|
619
|
+
scaledHeight: canvasHeight
|
|
620
|
+
};
|
|
546
621
|
};
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
622
|
+
|
|
623
|
+
// src/image-thumbnail/draw-repeating-image-thumbnail.ts
|
|
624
|
+
var createOffscreenCanvas = (width, height) => {
|
|
625
|
+
if (typeof document !== "undefined") {
|
|
626
|
+
const canvas = document.createElement("canvas");
|
|
627
|
+
canvas.width = width;
|
|
628
|
+
canvas.height = height;
|
|
629
|
+
return canvas;
|
|
630
|
+
}
|
|
631
|
+
if (typeof OffscreenCanvas !== "undefined") {
|
|
632
|
+
return new OffscreenCanvas(width, height);
|
|
633
|
+
}
|
|
634
|
+
throw new Error("No canvas implementation available");
|
|
635
|
+
};
|
|
636
|
+
var drawRepeatingImageThumbnail = ({
|
|
637
|
+
canvas,
|
|
638
|
+
image,
|
|
639
|
+
offsetInPixels = 0
|
|
550
640
|
}) => {
|
|
551
|
-
|
|
552
|
-
|
|
641
|
+
const ctx = canvas.getContext("2d");
|
|
642
|
+
if (!ctx) {
|
|
643
|
+
throw new Error("Failed to get canvas context");
|
|
553
644
|
}
|
|
554
|
-
|
|
645
|
+
const { width, height } = canvas;
|
|
646
|
+
if (width === 0 || height === 0) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
const { scaledWidth, scaledHeight } = getScaledImageThumbnailDimensions({
|
|
650
|
+
naturalWidth: image.naturalWidth,
|
|
651
|
+
naturalHeight: image.naturalHeight,
|
|
652
|
+
canvasHeight: height
|
|
653
|
+
});
|
|
654
|
+
if (scaledWidth === 0 || scaledHeight === 0) {
|
|
655
|
+
return;
|
|
656
|
+
}
|
|
657
|
+
const offscreen = createOffscreenCanvas(scaledWidth, scaledHeight);
|
|
658
|
+
const offCtx = offscreen.getContext("2d");
|
|
659
|
+
if (!offCtx) {
|
|
660
|
+
throw new Error("Failed to get offscreen canvas context");
|
|
661
|
+
}
|
|
662
|
+
offCtx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
|
|
663
|
+
const pattern = ctx.createPattern(offscreen, "repeat-x");
|
|
664
|
+
if (!pattern) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
pattern.setTransform(new DOMMatrix().translate(-offsetInPixels, 0));
|
|
668
|
+
ctx.fillStyle = pattern;
|
|
669
|
+
ctx.fillRect(0, 0, width, height);
|
|
555
670
|
};
|
|
556
671
|
// src/render-frame-strip.ts
|
|
557
672
|
var WEBCODECS_TIMESCALE = 1e6;
|
|
@@ -892,15 +1007,18 @@ var renderFrameStripToCanvas = async ({
|
|
|
892
1007
|
};
|
|
893
1008
|
export {
|
|
894
1009
|
sliceWaveformPeaks,
|
|
1010
|
+
sliceVisibleWaveformPeaks,
|
|
895
1011
|
shouldTileLoopDisplay,
|
|
896
1012
|
resizeVideoFrame,
|
|
897
1013
|
renderFrameStripToCanvas,
|
|
898
1014
|
makeFrameDatabaseKey,
|
|
899
1015
|
makeAudioWaveformWorker,
|
|
900
1016
|
loadWaveformPeaks,
|
|
1017
|
+
getVisibleWaveformVolume,
|
|
901
1018
|
getTimestampFromFrameDatabaseKey,
|
|
902
1019
|
getScaledImageThumbnailDimensions,
|
|
903
1020
|
getLoopDisplayWidth,
|
|
1021
|
+
getLoopDisplaySegments,
|
|
904
1022
|
getFrameDatabaseKeyPrefix,
|
|
905
1023
|
getDurationOfOneFrame,
|
|
906
1024
|
getAspectRatioFromCache,
|
|
@@ -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
1
|
export type { AudioWaveformWorkerIncomingMessage, AudioWaveformWorkerOutgoingMessage, AudioWaveformWorkerRenderMessage, } 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
6
|
export { makeAudioWaveformWorker } from './audio-waveform/make-audio-waveform-worker';
|
|
7
|
+
export { sliceVisibleWaveformPeaks } from './audio-waveform/slice-visible-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';
|
package/dist/index.js
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.resizeVideoFrame = exports.renderFrameStripToCanvas = exports.
|
|
3
|
+
exports.resizeVideoFrame = exports.renderFrameStripToCanvas = exports.getDurationOfOneFrame = exports.fillWithCachedFrames = exports.fillFrameWhereItFits = exports.ensureSlots = exports.drawSlot = exports.calculateTimestampSlots = exports.WEBCODECS_TIMESCALE = exports.shouldTileLoopDisplay = exports.getLoopDisplayWidth = exports.getLoopDisplaySegments = exports.getScaledImageThumbnailDimensions = exports.drawRepeatingImageThumbnail = exports.makeFrameDatabaseKey = exports.getTimestampFromFrameDatabaseKey = exports.getFrameDatabaseKeyPrefix = exports.getAspectRatioFromCache = exports.frameDatabase = exports.aspectRatioCache = exports.addFrameToCache = exports.extractFrames = exports.clampTimestampsToDuration = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.sliceVisibleWaveformPeaks = exports.makeAudioWaveformWorker = exports.loadWaveformPeaks = exports.getVisibleWaveformVolume = exports.drawBars = exports.TARGET_SAMPLE_RATE = void 0;
|
|
4
4
|
const constants_1 = require("./audio-waveform/constants");
|
|
5
5
|
Object.defineProperty(exports, "TARGET_SAMPLE_RATE", { enumerable: true, get: function () { return constants_1.TARGET_SAMPLE_RATE; } });
|
|
6
6
|
const draw_peaks_1 = require("./audio-waveform/draw-peaks");
|
|
7
7
|
Object.defineProperty(exports, "drawBars", { enumerable: true, get: function () { return draw_peaks_1.drawBars; } });
|
|
8
|
+
const get_visible_waveform_volume_1 = require("./audio-waveform/get-visible-waveform-volume");
|
|
9
|
+
Object.defineProperty(exports, "getVisibleWaveformVolume", { enumerable: true, get: function () { return get_visible_waveform_volume_1.getVisibleWaveformVolume; } });
|
|
8
10
|
const load_waveform_peaks_1 = require("./audio-waveform/load-waveform-peaks");
|
|
9
11
|
Object.defineProperty(exports, "loadWaveformPeaks", { enumerable: true, get: function () { return load_waveform_peaks_1.loadWaveformPeaks; } });
|
|
10
12
|
const make_audio_waveform_worker_1 = require("./audio-waveform/make-audio-waveform-worker");
|
|
11
13
|
Object.defineProperty(exports, "makeAudioWaveformWorker", { enumerable: true, get: function () { return make_audio_waveform_worker_1.makeAudioWaveformWorker; } });
|
|
14
|
+
const slice_visible_waveform_peaks_1 = require("./audio-waveform/slice-visible-waveform-peaks");
|
|
15
|
+
Object.defineProperty(exports, "sliceVisibleWaveformPeaks", { enumerable: true, get: function () { return slice_visible_waveform_peaks_1.sliceVisibleWaveformPeaks; } });
|
|
12
16
|
const slice_waveform_peaks_1 = require("./audio-waveform/slice-waveform-peaks");
|
|
13
17
|
Object.defineProperty(exports, "sliceWaveformPeaks", { enumerable: true, get: function () { return slice_waveform_peaks_1.sliceWaveformPeaks; } });
|
|
14
18
|
const waveform_peak_processor_1 = require("./audio-waveform/waveform-peak-processor");
|
|
15
19
|
Object.defineProperty(exports, "createWaveformPeakProcessor", { enumerable: true, get: function () { return waveform_peak_processor_1.createWaveformPeakProcessor; } });
|
|
16
20
|
Object.defineProperty(exports, "emitWaveformProgress", { enumerable: true, get: function () { return waveform_peak_processor_1.emitWaveformProgress; } });
|
|
17
|
-
const draw_repeating_image_thumbnail_1 = require("./image-thumbnail/draw-repeating-image-thumbnail");
|
|
18
|
-
Object.defineProperty(exports, "drawRepeatingImageThumbnail", { enumerable: true, get: function () { return draw_repeating_image_thumbnail_1.drawRepeatingImageThumbnail; } });
|
|
19
|
-
const get_scaled_image_thumbnail_dimensions_1 = require("./image-thumbnail/get-scaled-image-thumbnail-dimensions");
|
|
20
|
-
Object.defineProperty(exports, "getScaledImageThumbnailDimensions", { enumerable: true, get: function () { return get_scaled_image_thumbnail_dimensions_1.getScaledImageThumbnailDimensions; } });
|
|
21
21
|
const clamp_timestamps_to_duration_1 = require("./clamp-timestamps-to-duration");
|
|
22
22
|
Object.defineProperty(exports, "clampTimestampsToDuration", { enumerable: true, get: function () { return clamp_timestamps_to_duration_1.clampTimestampsToDuration; } });
|
|
23
23
|
const extract_frames_1 = require("./extract-frames");
|
|
@@ -30,17 +30,22 @@ Object.defineProperty(exports, "getAspectRatioFromCache", { enumerable: true, ge
|
|
|
30
30
|
Object.defineProperty(exports, "getFrameDatabaseKeyPrefix", { enumerable: true, get: function () { return frame_database_1.getFrameDatabaseKeyPrefix; } });
|
|
31
31
|
Object.defineProperty(exports, "getTimestampFromFrameDatabaseKey", { enumerable: true, get: function () { return frame_database_1.getTimestampFromFrameDatabaseKey; } });
|
|
32
32
|
Object.defineProperty(exports, "makeFrameDatabaseKey", { enumerable: true, get: function () { return frame_database_1.makeFrameDatabaseKey; } });
|
|
33
|
+
const draw_repeating_image_thumbnail_1 = require("./image-thumbnail/draw-repeating-image-thumbnail");
|
|
34
|
+
Object.defineProperty(exports, "drawRepeatingImageThumbnail", { enumerable: true, get: function () { return draw_repeating_image_thumbnail_1.drawRepeatingImageThumbnail; } });
|
|
35
|
+
const get_scaled_image_thumbnail_dimensions_1 = require("./image-thumbnail/get-scaled-image-thumbnail-dimensions");
|
|
36
|
+
Object.defineProperty(exports, "getScaledImageThumbnailDimensions", { enumerable: true, get: function () { return get_scaled_image_thumbnail_dimensions_1.getScaledImageThumbnailDimensions; } });
|
|
33
37
|
const loop_display_1 = require("./loop-display");
|
|
38
|
+
Object.defineProperty(exports, "getLoopDisplaySegments", { enumerable: true, get: function () { return loop_display_1.getLoopDisplaySegments; } });
|
|
34
39
|
Object.defineProperty(exports, "getLoopDisplayWidth", { enumerable: true, get: function () { return loop_display_1.getLoopDisplayWidth; } });
|
|
35
40
|
Object.defineProperty(exports, "shouldTileLoopDisplay", { enumerable: true, get: function () { return loop_display_1.shouldTileLoopDisplay; } });
|
|
36
41
|
const render_frame_strip_1 = require("./render-frame-strip");
|
|
42
|
+
Object.defineProperty(exports, "WEBCODECS_TIMESCALE", { enumerable: true, get: function () { return render_frame_strip_1.WEBCODECS_TIMESCALE; } });
|
|
37
43
|
Object.defineProperty(exports, "calculateTimestampSlots", { enumerable: true, get: function () { return render_frame_strip_1.calculateTimestampSlots; } });
|
|
38
44
|
Object.defineProperty(exports, "drawSlot", { enumerable: true, get: function () { return render_frame_strip_1.drawSlot; } });
|
|
39
45
|
Object.defineProperty(exports, "ensureSlots", { enumerable: true, get: function () { return render_frame_strip_1.ensureSlots; } });
|
|
40
46
|
Object.defineProperty(exports, "fillFrameWhereItFits", { enumerable: true, get: function () { return render_frame_strip_1.fillFrameWhereItFits; } });
|
|
41
47
|
Object.defineProperty(exports, "fillWithCachedFrames", { enumerable: true, get: function () { return render_frame_strip_1.fillWithCachedFrames; } });
|
|
42
48
|
Object.defineProperty(exports, "getDurationOfOneFrame", { enumerable: true, get: function () { return render_frame_strip_1.getDurationOfOneFrame; } });
|
|
43
|
-
Object.defineProperty(exports, "WEBCODECS_TIMESCALE", { enumerable: true, get: function () { return render_frame_strip_1.WEBCODECS_TIMESCALE; } });
|
|
44
49
|
const render_frame_strip_to_canvas_1 = require("./render-frame-strip-to-canvas");
|
|
45
50
|
Object.defineProperty(exports, "renderFrameStripToCanvas", { enumerable: true, get: function () { return render_frame_strip_to_canvas_1.renderFrameStripToCanvas; } });
|
|
46
51
|
const resize_video_frame_1 = require("./resize-video-frame");
|
package/dist/loop-display.d.ts
CHANGED
|
@@ -4,6 +4,17 @@ export type TimelineLoopDisplay = {
|
|
|
4
4
|
startOffset: number;
|
|
5
5
|
};
|
|
6
6
|
export declare const shouldTileLoopDisplay: (loopDisplay: TimelineLoopDisplay | undefined) => loopDisplay is TimelineLoopDisplay;
|
|
7
|
+
export type LoopDisplaySegment = {
|
|
8
|
+
loopIndex: number;
|
|
9
|
+
absoluteOffsetInFrames: number;
|
|
10
|
+
loopOffsetInFrames: number;
|
|
11
|
+
durationInFrames: number;
|
|
12
|
+
};
|
|
13
|
+
export declare const getLoopDisplaySegments: ({ displayDurationInFrames, displayOffsetInFrames, loopDurationInFrames, }: {
|
|
14
|
+
readonly displayDurationInFrames: number;
|
|
15
|
+
readonly displayOffsetInFrames: number;
|
|
16
|
+
readonly loopDurationInFrames: number;
|
|
17
|
+
}) => LoopDisplaySegment[];
|
|
7
18
|
export declare const getLoopDisplayWidth: ({ visualizationWidth, loopDisplay, }: {
|
|
8
19
|
visualizationWidth: number;
|
|
9
20
|
loopDisplay: TimelineLoopDisplay | undefined;
|
package/dist/loop-display.js
CHANGED
|
@@ -1,10 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getLoopDisplayWidth = exports.shouldTileLoopDisplay = void 0;
|
|
3
|
+
exports.getLoopDisplayWidth = exports.getLoopDisplaySegments = exports.shouldTileLoopDisplay = void 0;
|
|
4
4
|
const shouldTileLoopDisplay = (loopDisplay) => {
|
|
5
5
|
return loopDisplay !== undefined && loopDisplay.numberOfTimes > 1;
|
|
6
6
|
};
|
|
7
7
|
exports.shouldTileLoopDisplay = shouldTileLoopDisplay;
|
|
8
|
+
// Computes the visible loop segments by loop index instead of accumulating
|
|
9
|
+
// floating-point segment durations. With fractional display offsets (e.g. from
|
|
10
|
+
// horizontal timeline virtualization), an accumulating `processed += segment`
|
|
11
|
+
// loop can stall once the residue is smaller than the ULP of `processed`,
|
|
12
|
+
// growing arrays unboundedly until the tab runs out of memory.
|
|
13
|
+
const getLoopDisplaySegments = ({ displayDurationInFrames, displayOffsetInFrames, loopDurationInFrames, }) => {
|
|
14
|
+
if (!Number.isFinite(displayDurationInFrames) ||
|
|
15
|
+
displayDurationInFrames <= 0 ||
|
|
16
|
+
!Number.isFinite(displayOffsetInFrames) ||
|
|
17
|
+
!Number.isFinite(loopDurationInFrames) ||
|
|
18
|
+
loopDurationInFrames <= 0) {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const displayEndInFrames = displayOffsetInFrames + displayDurationInFrames;
|
|
22
|
+
const firstLoopIndex = Math.floor(displayOffsetInFrames / loopDurationInFrames);
|
|
23
|
+
const lastLoopIndex = Math.floor(displayEndInFrames / loopDurationInFrames);
|
|
24
|
+
const segments = [];
|
|
25
|
+
for (let loopIndex = firstLoopIndex; loopIndex <= lastLoopIndex; loopIndex++) {
|
|
26
|
+
const loopStartInFrames = loopIndex * loopDurationInFrames;
|
|
27
|
+
const segmentStart = Math.max(displayOffsetInFrames, loopStartInFrames);
|
|
28
|
+
const segmentEnd = Math.min(displayEndInFrames, loopStartInFrames + loopDurationInFrames);
|
|
29
|
+
const durationInFrames = segmentEnd - segmentStart;
|
|
30
|
+
if (durationInFrames <= 0) {
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
segments.push({
|
|
34
|
+
loopIndex,
|
|
35
|
+
absoluteOffsetInFrames: segmentStart,
|
|
36
|
+
loopOffsetInFrames: segmentStart - loopStartInFrames,
|
|
37
|
+
durationInFrames,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
return segments;
|
|
41
|
+
};
|
|
42
|
+
exports.getLoopDisplaySegments = getLoopDisplaySegments;
|
|
8
43
|
const getLoopDisplayWidth = ({ visualizationWidth, loopDisplay, }) => {
|
|
9
44
|
if (!(0, exports.shouldTileLoopDisplay)(loopDisplay)) {
|
|
10
45
|
return visualizationWidth;
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/timeline-utils"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/timeline-utils",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.514",
|
|
7
7
|
"description": "Internal utilities for rendering Remotion timelines",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"scripts": {
|
|
@@ -21,11 +21,11 @@
|
|
|
21
21
|
"url": "https://github.com/remotion-dev/remotion/issues"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"mediabunny": "1.
|
|
24
|
+
"mediabunny": "1.55.1"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@mediabunny/server": "1.
|
|
28
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
27
|
+
"@mediabunny/server": "1.55.1",
|
|
28
|
+
"@remotion/eslint-config-internal": "4.0.514",
|
|
29
29
|
"eslint": "9.19.0",
|
|
30
30
|
"@typescript/native-preview": "7.0.0-dev.20260217.1"
|
|
31
31
|
},
|