@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
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
// src/audio-waveform/parse-color.ts
|
|
2
|
-
var colorCache = new Map;
|
|
3
|
-
var parseColor = (color) => {
|
|
4
|
-
const cached = colorCache.get(color);
|
|
5
|
-
if (cached)
|
|
6
|
-
return cached;
|
|
7
|
-
const ctx = new OffscreenCanvas(1, 1).getContext("2d");
|
|
8
|
-
ctx.fillStyle = color;
|
|
9
|
-
ctx.fillRect(0, 0, 1, 1);
|
|
10
|
-
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
11
|
-
const result = [r, g, b, a];
|
|
12
|
-
colorCache.set(color, result);
|
|
13
|
-
return result;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// src/audio-waveform/draw-peaks.ts
|
|
17
|
-
var CLIPPING_COLOR = "#FF7F50";
|
|
18
|
-
var getVolumeAtBar = ({
|
|
19
|
-
barIndex,
|
|
20
|
-
numBars,
|
|
21
|
-
volume
|
|
22
|
-
}) => {
|
|
23
|
-
if (typeof volume === "number") {
|
|
24
|
-
return volume;
|
|
25
|
-
}
|
|
26
|
-
if (volume.length === 0) {
|
|
27
|
-
return 1;
|
|
28
|
-
}
|
|
29
|
-
if (volume.length === 1 || numBars <= 1) {
|
|
30
|
-
return volume[0];
|
|
31
|
-
}
|
|
32
|
-
const volumeIndex = Math.round(barIndex / (numBars - 1) * (volume.length - 1));
|
|
33
|
-
return volume[volumeIndex] ?? 1;
|
|
34
|
-
};
|
|
35
|
-
var drawBars = ({
|
|
36
|
-
canvas,
|
|
37
|
-
color,
|
|
38
|
-
peaks,
|
|
39
|
-
volume,
|
|
40
|
-
width
|
|
41
|
-
}) => {
|
|
42
|
-
const ctx = canvas.getContext("2d");
|
|
43
|
-
if (!ctx) {
|
|
44
|
-
throw new Error("Failed to get canvas context");
|
|
45
|
-
}
|
|
46
|
-
const { height } = canvas;
|
|
47
|
-
const w = canvas.width;
|
|
48
|
-
if (w === 0 || height === 0) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
ctx.clearRect(0, 0, w, height);
|
|
52
|
-
const [r, g, b, a] = parseColor(color);
|
|
53
|
-
const [cr, cg, cb, ca] = parseColor(CLIPPING_COLOR);
|
|
54
|
-
const imageData = ctx.createImageData(w, height);
|
|
55
|
-
const { data } = imageData;
|
|
56
|
-
const numBars = width;
|
|
57
|
-
for (let barIndex = 0;barIndex < numBars; barIndex++) {
|
|
58
|
-
const x = barIndex;
|
|
59
|
-
if (x >= w)
|
|
60
|
-
break;
|
|
61
|
-
const peakIndex = Math.floor(barIndex / numBars * peaks.length);
|
|
62
|
-
const peak = peaks[peakIndex] || 0;
|
|
63
|
-
const barVolume = getVolumeAtBar({ barIndex, numBars, volume });
|
|
64
|
-
const scaledPeak = peak * barVolume;
|
|
65
|
-
const halfBar = Math.max(0, Math.min(height / 2, scaledPeak * height / 2));
|
|
66
|
-
if (halfBar === 0)
|
|
67
|
-
continue;
|
|
68
|
-
const mid = height / 2;
|
|
69
|
-
const barY = Math.round(mid - halfBar);
|
|
70
|
-
const barEnd = Math.round(mid + halfBar);
|
|
71
|
-
const isClipping = scaledPeak > 1;
|
|
72
|
-
const clipTopEnd = isClipping ? Math.min(barY + 2, barEnd) : barY;
|
|
73
|
-
const clipBotStart = isClipping ? Math.max(barEnd - 2, barY) : barEnd;
|
|
74
|
-
for (let y = barY;y < clipTopEnd; y++) {
|
|
75
|
-
const idx = (y * w + x) * 4;
|
|
76
|
-
data[idx] = cr;
|
|
77
|
-
data[idx + 1] = cg;
|
|
78
|
-
data[idx + 2] = cb;
|
|
79
|
-
data[idx + 3] = ca;
|
|
80
|
-
}
|
|
81
|
-
for (let y = clipTopEnd;y < clipBotStart; y++) {
|
|
82
|
-
const idx = (y * w + x) * 4;
|
|
83
|
-
data[idx] = r;
|
|
84
|
-
data[idx + 1] = g;
|
|
85
|
-
data[idx + 2] = b;
|
|
86
|
-
data[idx + 3] = a;
|
|
87
|
-
}
|
|
88
|
-
for (let y = clipBotStart;y < barEnd; y++) {
|
|
89
|
-
const idx = (y * w + x) * 4;
|
|
90
|
-
data[idx] = cr;
|
|
91
|
-
data[idx + 1] = cg;
|
|
92
|
-
data[idx + 2] = cb;
|
|
93
|
-
data[idx + 3] = ca;
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
ctx.putImageData(imageData, 0, 0);
|
|
97
|
-
};
|
|
98
|
-
|
|
99
1
|
// src/audio-waveform/load-waveform-peaks.ts
|
|
100
2
|
import { ALL_FORMATS, AudioSampleSink, Input, UrlSource } from "mediabunny";
|
|
101
3
|
|
|
@@ -280,56 +182,18 @@ async function loadWaveformPeaks(url, signal, options) {
|
|
|
280
182
|
}
|
|
281
183
|
}
|
|
282
184
|
|
|
283
|
-
// src/audio-waveform/slice-waveform-peaks.ts
|
|
284
|
-
var sliceWaveformPeaks = ({
|
|
285
|
-
durationInFrames,
|
|
286
|
-
fps,
|
|
287
|
-
peaks,
|
|
288
|
-
playbackRate,
|
|
289
|
-
startFrom
|
|
290
|
-
}) => {
|
|
291
|
-
if (peaks.length === 0) {
|
|
292
|
-
return peaks;
|
|
293
|
-
}
|
|
294
|
-
const startTimeInSeconds = startFrom / fps;
|
|
295
|
-
const durationInSeconds = durationInFrames / fps * playbackRate;
|
|
296
|
-
const startPeakIndex = Math.floor(startTimeInSeconds * TARGET_SAMPLE_RATE);
|
|
297
|
-
const endPeakIndex = Math.ceil((startTimeInSeconds + durationInSeconds) * TARGET_SAMPLE_RATE);
|
|
298
|
-
if (!Number.isFinite(startPeakIndex) || !Number.isFinite(endPeakIndex)) {
|
|
299
|
-
return peaks.subarray(Math.max(0, startPeakIndex), Math.min(peaks.length, endPeakIndex));
|
|
300
|
-
}
|
|
301
|
-
if (startPeakIndex >= 0 && endPeakIndex <= peaks.length) {
|
|
302
|
-
return peaks.subarray(startPeakIndex, endPeakIndex);
|
|
303
|
-
}
|
|
304
|
-
const portion = new Float32Array(Math.max(0, endPeakIndex - startPeakIndex));
|
|
305
|
-
const sourceStart = Math.max(0, startPeakIndex);
|
|
306
|
-
const sourceEnd = Math.min(peaks.length, endPeakIndex);
|
|
307
|
-
if (sourceStart < sourceEnd) {
|
|
308
|
-
portion.set(peaks.subarray(sourceStart, sourceEnd), sourceStart - startPeakIndex);
|
|
309
|
-
}
|
|
310
|
-
return portion;
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
// src/loop-display.ts
|
|
314
|
-
var shouldTileLoopDisplay = (loopDisplay) => {
|
|
315
|
-
return loopDisplay !== undefined && loopDisplay.numberOfTimes > 1;
|
|
316
|
-
};
|
|
317
|
-
var getLoopDisplayWidth = ({
|
|
318
|
-
visualizationWidth,
|
|
319
|
-
loopDisplay
|
|
320
|
-
}) => {
|
|
321
|
-
if (!shouldTileLoopDisplay(loopDisplay)) {
|
|
322
|
-
return visualizationWidth;
|
|
323
|
-
}
|
|
324
|
-
return visualizationWidth / loopDisplay.numberOfTimes;
|
|
325
|
-
};
|
|
326
|
-
|
|
327
185
|
// src/audio-waveform-worker.ts
|
|
328
|
-
var
|
|
329
|
-
|
|
330
|
-
|
|
186
|
+
var postPeaks = (requestId, peaks, final) => {
|
|
187
|
+
const payload = {
|
|
188
|
+
type: "peaks",
|
|
189
|
+
requestId,
|
|
190
|
+
peaks,
|
|
191
|
+
final
|
|
192
|
+
};
|
|
193
|
+
self.postMessage(payload);
|
|
194
|
+
};
|
|
331
195
|
var postError = (requestId, error) => {
|
|
332
|
-
const message = error instanceof Error ? error.message : "Failed to
|
|
196
|
+
const message = error instanceof Error ? error.message : "Failed to load waveform";
|
|
333
197
|
const payload = {
|
|
334
198
|
type: "error",
|
|
335
199
|
requestId,
|
|
@@ -337,96 +201,18 @@ var postError = (requestId, error) => {
|
|
|
337
201
|
};
|
|
338
202
|
self.postMessage(payload);
|
|
339
203
|
};
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
return;
|
|
343
|
-
}
|
|
344
|
-
const portionPeaks = sliceWaveformPeaks({
|
|
345
|
-
durationInFrames: shouldTileLoopDisplay(message.loopDisplay) ? message.loopDisplay.durationInFrames : message.durationInFrames,
|
|
346
|
-
fps: message.fps,
|
|
347
|
-
peaks,
|
|
348
|
-
playbackRate: message.playbackRate,
|
|
349
|
-
startFrom: message.startFrom
|
|
350
|
-
});
|
|
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
|
-
drawBars({
|
|
367
|
-
canvas: targetCanvas,
|
|
368
|
-
peaks: portionPeaks,
|
|
369
|
-
color: "rgba(255, 255, 255, 0.6)",
|
|
370
|
-
volume: message.volume,
|
|
371
|
-
width: targetCanvas.width
|
|
372
|
-
});
|
|
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
|
-
};
|
|
386
|
-
var renderWaveform = async (message) => {
|
|
387
|
-
if (!canvas) {
|
|
388
|
-
postError(message.requestId, new Error("Waveform canvas not initialized"));
|
|
389
|
-
return;
|
|
390
|
-
}
|
|
204
|
+
self.addEventListener("message", (event) => {
|
|
205
|
+
const message = event.data;
|
|
391
206
|
const controller = new AbortController;
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
canvas.width = message.width;
|
|
397
|
-
canvas.height = message.height;
|
|
398
|
-
const peaks = await loadWaveformPeaks(message.src, controller.signal, {
|
|
399
|
-
onProgress: ({ peaks: nextPeaks }) => {
|
|
400
|
-
if (controller.signal.aborted || latestRequestId !== message.requestId) {
|
|
401
|
-
return;
|
|
402
|
-
}
|
|
403
|
-
drawPartialWaveform(message, nextPeaks);
|
|
207
|
+
loadWaveformPeaks(message.src, controller.signal, {
|
|
208
|
+
onProgress: ({ peaks, final }) => {
|
|
209
|
+
if (!final) {
|
|
210
|
+
postPeaks(message.requestId, peaks, false);
|
|
404
211
|
}
|
|
405
|
-
});
|
|
406
|
-
if (controller.signal.aborted || latestRequestId !== message.requestId) {
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
drawPartialWaveform(message, peaks);
|
|
410
|
-
} catch (error) {
|
|
411
|
-
if (controller.signal.aborted || latestRequestId !== message.requestId) {
|
|
412
|
-
return;
|
|
413
212
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
}
|
|
417
|
-
self.addEventListener("message", (event) => {
|
|
418
|
-
const message = event.data;
|
|
419
|
-
if (message.type === "init") {
|
|
420
|
-
canvas = message.canvas;
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
if (message.type === "dispose") {
|
|
424
|
-
currentController?.abort();
|
|
425
|
-
currentController = null;
|
|
426
|
-
canvas = null;
|
|
427
|
-
return;
|
|
428
|
-
}
|
|
429
|
-
renderWaveform(message).catch((error) => {
|
|
213
|
+
}).then((peaks) => {
|
|
214
|
+
postPeaks(message.requestId, peaks, true);
|
|
215
|
+
}).catch((error) => {
|
|
430
216
|
postError(message.requestId, error);
|
|
431
217
|
});
|
|
432
218
|
});
|