@remotion/timeline-utils 4.0.462 → 4.0.464

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.
@@ -5,6 +5,7 @@ exports.loadWaveformPeaks = loadWaveformPeaks;
5
5
  const mediabunny_1 = require("mediabunny");
6
6
  const constants_1 = require("./constants");
7
7
  Object.defineProperty(exports, "TARGET_SAMPLE_RATE", { enumerable: true, get: function () { return constants_1.TARGET_SAMPLE_RATE; } });
8
+ const trim_audio_sample_before_zero_1 = require("./trim-audio-sample-before-zero");
8
9
  const waveform_peak_processor_1 = require("./waveform-peak-processor");
9
10
  const DEFAULT_PROGRESS_INTERVAL_IN_MS = 50;
10
11
  const peaksCache = new Map();
@@ -55,12 +56,29 @@ async function loadWaveformPeaks(url, signal, options) {
55
56
  sample.close();
56
57
  return new Float32Array(0);
57
58
  }
59
+ const startFrame = (0, trim_audio_sample_before_zero_1.getAudioSampleStartFrameAtTimelineZero)(sample);
60
+ if (startFrame === null) {
61
+ sample.close();
62
+ continue;
63
+ }
64
+ const frameCount = sample.numberOfFrames - startFrame;
65
+ if (frameCount <= 0) {
66
+ sample.close();
67
+ continue;
68
+ }
58
69
  const bytesNeeded = sample.allocationSize({
59
70
  format: 'f32',
60
71
  planeIndex: 0,
72
+ frameOffset: startFrame,
73
+ frameCount,
61
74
  });
62
75
  const floats = new Float32Array(bytesNeeded / 4);
63
- sample.copyTo(floats, { format: 'f32', planeIndex: 0 });
76
+ sample.copyTo(floats, {
77
+ format: 'f32',
78
+ planeIndex: 0,
79
+ frameOffset: startFrame,
80
+ frameCount,
81
+ });
64
82
  const channels = Math.max(1, sample.numberOfChannels);
65
83
  sample.close();
66
84
  processor.processSampleChunk(floats, channels);
@@ -0,0 +1,11 @@
1
+ export type AudioSampleTiming = {
2
+ readonly timestamp: number;
3
+ readonly numberOfFrames: number;
4
+ readonly duration: number;
5
+ readonly sampleRate: number;
6
+ };
7
+ /**
8
+ * Returns the first frame index to include when building a timeline-aligned waveform.
9
+ * Returns null when the entire sample ends before timeline t=0.
10
+ */
11
+ export declare const getAudioSampleStartFrameAtTimelineZero: (sample: AudioSampleTiming) => number | null;
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAudioSampleStartFrameAtTimelineZero = void 0;
4
+ /**
5
+ * Returns the first frame index to include when building a timeline-aligned waveform.
6
+ * Returns null when the entire sample ends before timeline t=0.
7
+ */
8
+ const getAudioSampleStartFrameAtTimelineZero = (sample) => {
9
+ if (sample.timestamp + sample.duration <= 0) {
10
+ return null;
11
+ }
12
+ if (sample.timestamp >= 0) {
13
+ return 0;
14
+ }
15
+ return Math.min(sample.numberOfFrames, Math.ceil(-sample.timestamp * sample.sampleRate));
16
+ };
17
+ exports.getAudioSampleStartFrameAtTimelineZero = getAudioSampleStartFrameAtTimelineZero;
@@ -86,6 +86,17 @@ import { ALL_FORMATS, AudioSampleSink, Input, UrlSource } from "mediabunny";
86
86
  // src/audio-waveform/constants.ts
87
87
  var TARGET_SAMPLE_RATE = 100;
88
88
 
89
+ // src/audio-waveform/trim-audio-sample-before-zero.ts
90
+ var getAudioSampleStartFrameAtTimelineZero = (sample) => {
91
+ if (sample.timestamp + sample.duration <= 0) {
92
+ return null;
93
+ }
94
+ if (sample.timestamp >= 0) {
95
+ return 0;
96
+ }
97
+ return Math.min(sample.numberOfFrames, Math.ceil(-sample.timestamp * sample.sampleRate));
98
+ };
99
+
89
100
  // src/audio-waveform/waveform-peak-processor.ts
90
101
  var emitWaveformProgress = ({
91
102
  completedPeaks,
@@ -217,12 +228,29 @@ async function loadWaveformPeaks(url, signal, options) {
217
228
  sample.close();
218
229
  return new Float32Array(0);
219
230
  }
231
+ const startFrame = getAudioSampleStartFrameAtTimelineZero(sample);
232
+ if (startFrame === null) {
233
+ sample.close();
234
+ continue;
235
+ }
236
+ const frameCount = sample.numberOfFrames - startFrame;
237
+ if (frameCount <= 0) {
238
+ sample.close();
239
+ continue;
240
+ }
220
241
  const bytesNeeded = sample.allocationSize({
221
242
  format: "f32",
222
- planeIndex: 0
243
+ planeIndex: 0,
244
+ frameOffset: startFrame,
245
+ frameCount
223
246
  });
224
247
  const floats = new Float32Array(bytesNeeded / 4);
225
- sample.copyTo(floats, { format: "f32", planeIndex: 0 });
248
+ sample.copyTo(floats, {
249
+ format: "f32",
250
+ planeIndex: 0,
251
+ frameOffset: startFrame,
252
+ frameCount
253
+ });
226
254
  const channels = Math.max(1, sample.numberOfChannels);
227
255
  sample.close();
228
256
  processor.processSampleChunk(floats, channels);
@@ -84,6 +84,17 @@ var drawBars = ({
84
84
  // src/audio-waveform/load-waveform-peaks.ts
85
85
  import { ALL_FORMATS, AudioSampleSink, Input, UrlSource } from "mediabunny";
86
86
 
87
+ // src/audio-waveform/trim-audio-sample-before-zero.ts
88
+ var getAudioSampleStartFrameAtTimelineZero = (sample) => {
89
+ if (sample.timestamp + sample.duration <= 0) {
90
+ return null;
91
+ }
92
+ if (sample.timestamp >= 0) {
93
+ return 0;
94
+ }
95
+ return Math.min(sample.numberOfFrames, Math.ceil(-sample.timestamp * sample.sampleRate));
96
+ };
97
+
87
98
  // src/audio-waveform/waveform-peak-processor.ts
88
99
  var emitWaveformProgress = ({
89
100
  completedPeaks,
@@ -215,12 +226,29 @@ async function loadWaveformPeaks(url, signal, options) {
215
226
  sample.close();
216
227
  return new Float32Array(0);
217
228
  }
229
+ const startFrame = getAudioSampleStartFrameAtTimelineZero(sample);
230
+ if (startFrame === null) {
231
+ sample.close();
232
+ continue;
233
+ }
234
+ const frameCount = sample.numberOfFrames - startFrame;
235
+ if (frameCount <= 0) {
236
+ sample.close();
237
+ continue;
238
+ }
218
239
  const bytesNeeded = sample.allocationSize({
219
240
  format: "f32",
220
- planeIndex: 0
241
+ planeIndex: 0,
242
+ frameOffset: startFrame,
243
+ frameCount
221
244
  });
222
245
  const floats = new Float32Array(bytesNeeded / 4);
223
- sample.copyTo(floats, { format: "f32", planeIndex: 0 });
246
+ sample.copyTo(floats, {
247
+ format: "f32",
248
+ planeIndex: 0,
249
+ frameOffset: startFrame,
250
+ frameCount
251
+ });
224
252
  const channels = Math.max(1, sample.numberOfChannels);
225
253
  sample.close();
226
254
  processor.processSampleChunk(floats, channels);
@@ -256,6 +284,71 @@ var sliceWaveformPeaks = ({
256
284
  const endPeakIndex = Math.ceil((startTimeInSeconds + durationInSeconds) * TARGET_SAMPLE_RATE);
257
285
  return peaks.subarray(Math.max(0, startPeakIndex), Math.min(peaks.length, endPeakIndex));
258
286
  };
287
+ // src/image-thumbnail/get-scaled-image-thumbnail-dimensions.ts
288
+ var getScaledImageThumbnailDimensions = ({
289
+ naturalWidth,
290
+ naturalHeight,
291
+ canvasHeight
292
+ }) => {
293
+ if (naturalWidth === 0 || naturalHeight === 0 || canvasHeight === 0) {
294
+ return {
295
+ scaledWidth: 0,
296
+ scaledHeight: 0
297
+ };
298
+ }
299
+ const scale = canvasHeight / naturalHeight;
300
+ return {
301
+ scaledWidth: naturalWidth * scale,
302
+ scaledHeight: canvasHeight
303
+ };
304
+ };
305
+
306
+ // src/image-thumbnail/draw-repeating-image-thumbnail.ts
307
+ var createOffscreenCanvas = (width, height) => {
308
+ if (typeof document !== "undefined") {
309
+ const canvas = document.createElement("canvas");
310
+ canvas.width = width;
311
+ canvas.height = height;
312
+ return canvas;
313
+ }
314
+ if (typeof OffscreenCanvas !== "undefined") {
315
+ return new OffscreenCanvas(width, height);
316
+ }
317
+ throw new Error("No canvas implementation available");
318
+ };
319
+ var drawRepeatingImageThumbnail = ({
320
+ canvas,
321
+ image
322
+ }) => {
323
+ const ctx = canvas.getContext("2d");
324
+ if (!ctx) {
325
+ throw new Error("Failed to get canvas context");
326
+ }
327
+ const { width, height } = canvas;
328
+ if (width === 0 || height === 0) {
329
+ return;
330
+ }
331
+ const { scaledWidth, scaledHeight } = getScaledImageThumbnailDimensions({
332
+ naturalWidth: image.naturalWidth,
333
+ naturalHeight: image.naturalHeight,
334
+ canvasHeight: height
335
+ });
336
+ if (scaledWidth === 0 || scaledHeight === 0) {
337
+ return;
338
+ }
339
+ const offscreen = createOffscreenCanvas(scaledWidth, scaledHeight);
340
+ const offCtx = offscreen.getContext("2d");
341
+ if (!offCtx) {
342
+ throw new Error("Failed to get offscreen canvas context");
343
+ }
344
+ offCtx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
345
+ const pattern = ctx.createPattern(offscreen, "repeat-x");
346
+ if (!pattern) {
347
+ return;
348
+ }
349
+ ctx.fillStyle = pattern;
350
+ ctx.fillRect(0, 0, width, height);
351
+ };
259
352
  // src/extract-frames.ts
260
353
  import {
261
354
  ALL_FORMATS as ALL_FORMATS2,
@@ -633,6 +726,7 @@ export {
633
726
  makeAudioWaveformWorker,
634
727
  loadWaveformPeaks,
635
728
  getTimestampFromFrameDatabaseKey,
729
+ getScaledImageThumbnailDimensions,
636
730
  getLoopDisplayWidth,
637
731
  getFrameDatabaseKeyPrefix,
638
732
  getDurationOfOneFrame,
@@ -644,6 +738,7 @@ export {
644
738
  ensureSlots,
645
739
  emitWaveformProgress,
646
740
  drawSlot,
741
+ drawRepeatingImageThumbnail,
647
742
  drawBars,
648
743
  createWaveformPeakProcessor,
649
744
  calculateTimestampSlots,
@@ -0,0 +1,9 @@
1
+ type ImageWithNaturalDimensions = CanvasImageSource & {
2
+ readonly naturalWidth: number;
3
+ readonly naturalHeight: number;
4
+ };
5
+ export declare const drawRepeatingImageThumbnail: ({ canvas, image, }: {
6
+ readonly canvas: HTMLCanvasElement | OffscreenCanvas;
7
+ readonly image: ImageWithNaturalDimensions;
8
+ }) => void;
9
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.drawRepeatingImageThumbnail = void 0;
4
+ const get_scaled_image_thumbnail_dimensions_1 = require("./get-scaled-image-thumbnail-dimensions");
5
+ const createOffscreenCanvas = (width, height) => {
6
+ if (typeof document !== 'undefined') {
7
+ const canvas = document.createElement('canvas');
8
+ canvas.width = width;
9
+ canvas.height = height;
10
+ return canvas;
11
+ }
12
+ if (typeof OffscreenCanvas !== 'undefined') {
13
+ return new OffscreenCanvas(width, height);
14
+ }
15
+ throw new Error('No canvas implementation available');
16
+ };
17
+ const drawRepeatingImageThumbnail = ({ canvas, image, }) => {
18
+ const ctx = canvas.getContext('2d');
19
+ if (!ctx) {
20
+ throw new Error('Failed to get canvas context');
21
+ }
22
+ const { width, height } = canvas;
23
+ if (width === 0 || height === 0) {
24
+ return;
25
+ }
26
+ const { scaledWidth, scaledHeight } = (0, get_scaled_image_thumbnail_dimensions_1.getScaledImageThumbnailDimensions)({
27
+ naturalWidth: image.naturalWidth,
28
+ naturalHeight: image.naturalHeight,
29
+ canvasHeight: height,
30
+ });
31
+ if (scaledWidth === 0 || scaledHeight === 0) {
32
+ return;
33
+ }
34
+ const offscreen = createOffscreenCanvas(scaledWidth, scaledHeight);
35
+ const offCtx = offscreen.getContext('2d');
36
+ if (!offCtx) {
37
+ throw new Error('Failed to get offscreen canvas context');
38
+ }
39
+ offCtx.drawImage(image, 0, 0, scaledWidth, scaledHeight);
40
+ const pattern = ctx.createPattern(offscreen, 'repeat-x');
41
+ if (!pattern) {
42
+ return;
43
+ }
44
+ ctx.fillStyle = pattern;
45
+ ctx.fillRect(0, 0, width, height);
46
+ };
47
+ exports.drawRepeatingImageThumbnail = drawRepeatingImageThumbnail;
@@ -0,0 +1,8 @@
1
+ export declare const getScaledImageThumbnailDimensions: ({ naturalWidth, naturalHeight, canvasHeight, }: {
2
+ readonly naturalWidth: number;
3
+ readonly naturalHeight: number;
4
+ readonly canvasHeight: number;
5
+ }) => {
6
+ scaledWidth: number;
7
+ scaledHeight: number;
8
+ };
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getScaledImageThumbnailDimensions = void 0;
4
+ const getScaledImageThumbnailDimensions = ({ naturalWidth, naturalHeight, canvasHeight, }) => {
5
+ if (naturalWidth === 0 || naturalHeight === 0 || canvasHeight === 0) {
6
+ return {
7
+ scaledWidth: 0,
8
+ scaledHeight: 0,
9
+ };
10
+ }
11
+ const scale = canvasHeight / naturalHeight;
12
+ return {
13
+ scaledWidth: naturalWidth * scale,
14
+ scaledHeight: canvasHeight,
15
+ };
16
+ };
17
+ exports.getScaledImageThumbnailDimensions = getScaledImageThumbnailDimensions;
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export { loadWaveformPeaks } from './audio-waveform/load-waveform-peaks';
5
5
  export { makeAudioWaveformWorker } from './audio-waveform/make-audio-waveform-worker';
6
6
  export { sliceWaveformPeaks } from './audio-waveform/slice-waveform-peaks';
7
7
  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';
8
10
  export { extractFrames } from './extract-frames';
9
11
  export type { ExtractFramesProps, ExtractFramesTimestampsInSecondsFn, } from './extract-frames';
10
12
  export { addFrameToCache, aspectRatioCache, frameDatabase, getAspectRatioFromCache, getFrameDatabaseKeyPrefix, getTimestampFromFrameDatabaseKey, makeFrameDatabaseKey, } from './frame-database';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.resizeVideoFrame = exports.WEBCODECS_TIMESCALE = exports.getDurationOfOneFrame = exports.fillWithCachedFrames = exports.fillFrameWhereItFits = exports.ensureSlots = exports.drawSlot = exports.calculateTimestampSlots = exports.shouldTileLoopDisplay = exports.getLoopDisplayWidth = exports.makeFrameDatabaseKey = exports.getTimestampFromFrameDatabaseKey = exports.getFrameDatabaseKeyPrefix = exports.getAspectRatioFromCache = exports.frameDatabase = exports.aspectRatioCache = exports.addFrameToCache = exports.extractFrames = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.makeAudioWaveformWorker = exports.loadWaveformPeaks = exports.drawBars = exports.TARGET_SAMPLE_RATE = void 0;
3
+ exports.resizeVideoFrame = exports.WEBCODECS_TIMESCALE = exports.getDurationOfOneFrame = exports.fillWithCachedFrames = exports.fillFrameWhereItFits = exports.ensureSlots = exports.drawSlot = exports.calculateTimestampSlots = exports.shouldTileLoopDisplay = exports.getLoopDisplayWidth = exports.makeFrameDatabaseKey = exports.getTimestampFromFrameDatabaseKey = exports.getFrameDatabaseKeyPrefix = exports.getAspectRatioFromCache = exports.frameDatabase = exports.aspectRatioCache = exports.addFrameToCache = exports.extractFrames = exports.getScaledImageThumbnailDimensions = exports.drawRepeatingImageThumbnail = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.makeAudioWaveformWorker = exports.loadWaveformPeaks = 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");
@@ -14,6 +14,10 @@ Object.defineProperty(exports, "sliceWaveformPeaks", { enumerable: true, get: fu
14
14
  const waveform_peak_processor_1 = require("./audio-waveform/waveform-peak-processor");
15
15
  Object.defineProperty(exports, "createWaveformPeakProcessor", { enumerable: true, get: function () { return waveform_peak_processor_1.createWaveformPeakProcessor; } });
16
16
  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; } });
17
21
  const extract_frames_1 = require("./extract-frames");
18
22
  Object.defineProperty(exports, "extractFrames", { enumerable: true, get: function () { return extract_frames_1.extractFrames; } });
19
23
  const frame_database_1 = require("./frame-database");
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.462",
6
+ "version": "4.0.464",
7
7
  "description": "Internal utilities for rendering Remotion timelines",
8
8
  "main": "dist/index.js",
9
9
  "scripts": {
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "@mediabunny/server": "1.45.0",
28
- "@remotion/eslint-config-internal": "4.0.462",
28
+ "@remotion/eslint-config-internal": "4.0.464",
29
29
  "eslint": "9.19.0",
30
30
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
31
31
  },