@remotion/timeline-utils 4.0.486 → 4.0.487

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.
@@ -734,10 +734,138 @@ var resizeVideoFrame = ({
734
734
  timestamp: frame.timestamp
735
735
  });
736
736
  };
737
+
738
+ // src/render-frame-strip-to-canvas.ts
739
+ var getContext = (canvas) => {
740
+ const ctx = canvas.getContext("2d");
741
+ if (!ctx) {
742
+ throw new Error("Could not get 2d context");
743
+ }
744
+ return ctx;
745
+ };
746
+ var renderFrameStripToCanvas = async ({
747
+ canvas,
748
+ src,
749
+ fromSeconds,
750
+ toSeconds,
751
+ width,
752
+ frameHeight,
753
+ devicePixelRatio,
754
+ signal
755
+ }) => {
756
+ const naturalWidth = Math.max(1, Math.ceil(width));
757
+ const naturalHeight = Math.max(1, Math.ceil(frameHeight));
758
+ const segmentDuration = toSeconds - fromSeconds;
759
+ canvas.width = naturalWidth;
760
+ canvas.height = naturalHeight;
761
+ if (segmentDuration <= 0 || signal?.aborted) {
762
+ return;
763
+ }
764
+ const ctx = getContext(canvas);
765
+ ctx.clearRect(0, 0, naturalWidth, naturalHeight);
766
+ const filledSlots = new Map;
767
+ let aspectRatio = getAspectRatioFromCache(src);
768
+ if (aspectRatio !== null) {
769
+ ensureSlots({
770
+ filledSlots,
771
+ naturalWidth,
772
+ fromSeconds,
773
+ toSeconds,
774
+ aspectRatio,
775
+ frameHeight
776
+ });
777
+ fillWithCachedFrames({
778
+ ctx,
779
+ naturalWidth,
780
+ filledSlots,
781
+ src,
782
+ segmentDuration,
783
+ fromSeconds,
784
+ devicePixelRatio,
785
+ frameHeight
786
+ });
787
+ const hasUnfilledSlots = Array.from(filledSlots.values()).some((value) => value === undefined);
788
+ if (!hasUnfilledSlots) {
789
+ return;
790
+ }
791
+ }
792
+ await extractFrames({
793
+ src,
794
+ signal,
795
+ timestampsInSeconds: ({ track }) => {
796
+ aspectRatio = track.width / track.height;
797
+ aspectRatioCache.set(src, aspectRatio);
798
+ ensureSlots({
799
+ filledSlots,
800
+ naturalWidth,
801
+ fromSeconds,
802
+ toSeconds,
803
+ aspectRatio,
804
+ frameHeight
805
+ });
806
+ return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
807
+ },
808
+ onVideoSample: (sample) => {
809
+ let frame;
810
+ try {
811
+ frame = sample.toVideoFrame();
812
+ const scale = frameHeight / frame.displayHeight * devicePixelRatio;
813
+ const transformed = resizeVideoFrame({ frame, scale });
814
+ if (transformed !== frame) {
815
+ frame.close();
816
+ }
817
+ frame = undefined;
818
+ addFrameToCache(makeFrameDatabaseKey(src, transformed.timestamp), transformed);
819
+ if (aspectRatio === null) {
820
+ throw new Error("Aspect ratio is not set");
821
+ }
822
+ ensureSlots({
823
+ filledSlots,
824
+ naturalWidth,
825
+ fromSeconds,
826
+ toSeconds,
827
+ aspectRatio,
828
+ frameHeight
829
+ });
830
+ fillFrameWhereItFits({
831
+ ctx,
832
+ filledSlots,
833
+ visualizationWidth: naturalWidth,
834
+ frame: transformed,
835
+ segmentDuration,
836
+ fromSeconds,
837
+ devicePixelRatio,
838
+ frameHeight
839
+ });
840
+ } catch (e) {
841
+ if (frame) {
842
+ frame.close();
843
+ }
844
+ throw e;
845
+ } finally {
846
+ sample.close();
847
+ }
848
+ }
849
+ });
850
+ if (signal?.aborted) {
851
+ return;
852
+ }
853
+ fillWithCachedFrames({
854
+ ctx,
855
+ naturalWidth,
856
+ filledSlots,
857
+ src,
858
+ segmentDuration,
859
+ fromSeconds,
860
+ devicePixelRatio,
861
+ frameHeight
862
+ });
863
+ };
737
864
  export {
738
865
  sliceWaveformPeaks,
739
866
  shouldTileLoopDisplay,
740
867
  resizeVideoFrame,
868
+ renderFrameStripToCanvas,
741
869
  makeFrameDatabaseKey,
742
870
  makeAudioWaveformWorker,
743
871
  loadWaveformPeaks,
package/dist/index.d.ts CHANGED
@@ -14,4 +14,6 @@ export type { FrameDatabaseKey } from './frame-database';
14
14
  export { getLoopDisplayWidth, shouldTileLoopDisplay } from './loop-display';
15
15
  export type { TimelineLoopDisplay } from './loop-display';
16
16
  export { calculateTimestampSlots, drawSlot, ensureSlots, fillFrameWhereItFits, fillWithCachedFrames, getDurationOfOneFrame, WEBCODECS_TIMESCALE, } from './render-frame-strip';
17
+ export { renderFrameStripToCanvas } from './render-frame-strip-to-canvas';
18
+ export type { RenderFrameStripToCanvasOptions } from './render-frame-strip-to-canvas';
17
19
  export { resizeVideoFrame } from './resize-video-frame';
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.getScaledImageThumbnailDimensions = exports.drawRepeatingImageThumbnail = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.makeAudioWaveformWorker = exports.loadWaveformPeaks = exports.drawBars = exports.TARGET_SAMPLE_RATE = void 0;
3
+ exports.resizeVideoFrame = exports.renderFrameStripToCanvas = 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");
@@ -39,5 +39,7 @@ Object.defineProperty(exports, "fillFrameWhereItFits", { enumerable: true, get:
39
39
  Object.defineProperty(exports, "fillWithCachedFrames", { enumerable: true, get: function () { return render_frame_strip_1.fillWithCachedFrames; } });
40
40
  Object.defineProperty(exports, "getDurationOfOneFrame", { enumerable: true, get: function () { return render_frame_strip_1.getDurationOfOneFrame; } });
41
41
  Object.defineProperty(exports, "WEBCODECS_TIMESCALE", { enumerable: true, get: function () { return render_frame_strip_1.WEBCODECS_TIMESCALE; } });
42
+ const render_frame_strip_to_canvas_1 = require("./render-frame-strip-to-canvas");
43
+ Object.defineProperty(exports, "renderFrameStripToCanvas", { enumerable: true, get: function () { return render_frame_strip_to_canvas_1.renderFrameStripToCanvas; } });
42
44
  const resize_video_frame_1 = require("./resize-video-frame");
43
45
  Object.defineProperty(exports, "resizeVideoFrame", { enumerable: true, get: function () { return resize_video_frame_1.resizeVideoFrame; } });
@@ -0,0 +1,13 @@
1
+ type Canvas = HTMLCanvasElement | OffscreenCanvas;
2
+ export type RenderFrameStripToCanvasOptions = {
3
+ readonly canvas: Canvas;
4
+ readonly src: string;
5
+ readonly fromSeconds: number;
6
+ readonly toSeconds: number;
7
+ readonly width: number;
8
+ readonly frameHeight: number;
9
+ readonly devicePixelRatio: number;
10
+ readonly signal?: AbortSignal;
11
+ };
12
+ export declare const renderFrameStripToCanvas: ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, signal, }: RenderFrameStripToCanvasOptions) => Promise<void>;
13
+ export {};
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderFrameStripToCanvas = void 0;
4
+ const extract_frames_1 = require("./extract-frames");
5
+ const frame_database_1 = require("./frame-database");
6
+ const render_frame_strip_1 = require("./render-frame-strip");
7
+ const resize_video_frame_1 = require("./resize-video-frame");
8
+ const getContext = (canvas) => {
9
+ const ctx = canvas.getContext('2d');
10
+ if (!ctx) {
11
+ throw new Error('Could not get 2d context');
12
+ }
13
+ return ctx;
14
+ };
15
+ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, signal, }) => {
16
+ const naturalWidth = Math.max(1, Math.ceil(width));
17
+ const naturalHeight = Math.max(1, Math.ceil(frameHeight));
18
+ const segmentDuration = toSeconds - fromSeconds;
19
+ canvas.width = naturalWidth;
20
+ canvas.height = naturalHeight;
21
+ if (segmentDuration <= 0 || (signal === null || signal === void 0 ? void 0 : signal.aborted)) {
22
+ return;
23
+ }
24
+ const ctx = getContext(canvas);
25
+ ctx.clearRect(0, 0, naturalWidth, naturalHeight);
26
+ const filledSlots = new Map();
27
+ let aspectRatio = (0, frame_database_1.getAspectRatioFromCache)(src);
28
+ if (aspectRatio !== null) {
29
+ (0, render_frame_strip_1.ensureSlots)({
30
+ filledSlots,
31
+ naturalWidth,
32
+ fromSeconds,
33
+ toSeconds,
34
+ aspectRatio,
35
+ frameHeight,
36
+ });
37
+ (0, render_frame_strip_1.fillWithCachedFrames)({
38
+ ctx,
39
+ naturalWidth,
40
+ filledSlots,
41
+ src,
42
+ segmentDuration,
43
+ fromSeconds,
44
+ devicePixelRatio,
45
+ frameHeight,
46
+ });
47
+ const hasUnfilledSlots = Array.from(filledSlots.values()).some((value) => value === undefined);
48
+ if (!hasUnfilledSlots) {
49
+ return;
50
+ }
51
+ }
52
+ await (0, extract_frames_1.extractFrames)({
53
+ src,
54
+ signal,
55
+ timestampsInSeconds: ({ track }) => {
56
+ aspectRatio = track.width / track.height;
57
+ frame_database_1.aspectRatioCache.set(src, aspectRatio);
58
+ (0, render_frame_strip_1.ensureSlots)({
59
+ filledSlots,
60
+ naturalWidth,
61
+ fromSeconds,
62
+ toSeconds,
63
+ aspectRatio,
64
+ frameHeight,
65
+ });
66
+ return Array.from(filledSlots.keys()).map((timestamp) => timestamp / render_frame_strip_1.WEBCODECS_TIMESCALE);
67
+ },
68
+ onVideoSample: (sample) => {
69
+ let frame;
70
+ try {
71
+ frame = sample.toVideoFrame();
72
+ const scale = (frameHeight / frame.displayHeight) * devicePixelRatio;
73
+ const transformed = (0, resize_video_frame_1.resizeVideoFrame)({ frame, scale });
74
+ if (transformed !== frame) {
75
+ frame.close();
76
+ }
77
+ frame = undefined;
78
+ (0, frame_database_1.addFrameToCache)((0, frame_database_1.makeFrameDatabaseKey)(src, transformed.timestamp), transformed);
79
+ if (aspectRatio === null) {
80
+ throw new Error('Aspect ratio is not set');
81
+ }
82
+ (0, render_frame_strip_1.ensureSlots)({
83
+ filledSlots,
84
+ naturalWidth,
85
+ fromSeconds,
86
+ toSeconds,
87
+ aspectRatio,
88
+ frameHeight,
89
+ });
90
+ (0, render_frame_strip_1.fillFrameWhereItFits)({
91
+ ctx,
92
+ filledSlots,
93
+ visualizationWidth: naturalWidth,
94
+ frame: transformed,
95
+ segmentDuration,
96
+ fromSeconds,
97
+ devicePixelRatio,
98
+ frameHeight,
99
+ });
100
+ }
101
+ catch (e) {
102
+ if (frame) {
103
+ frame.close();
104
+ }
105
+ throw e;
106
+ }
107
+ finally {
108
+ sample.close();
109
+ }
110
+ },
111
+ });
112
+ if (signal === null || signal === void 0 ? void 0 : signal.aborted) {
113
+ return;
114
+ }
115
+ (0, render_frame_strip_1.fillWithCachedFrames)({
116
+ ctx,
117
+ naturalWidth,
118
+ filledSlots,
119
+ src,
120
+ segmentDuration,
121
+ fromSeconds,
122
+ devicePixelRatio,
123
+ frameHeight,
124
+ });
125
+ };
126
+ exports.renderFrameStripToCanvas = renderFrameStripToCanvas;
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.486",
6
+ "version": "4.0.487",
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.50.3"
24
+ "mediabunny": "1.50.7"
25
25
  },
26
26
  "devDependencies": {
27
- "@mediabunny/server": "1.50.3",
28
- "@remotion/eslint-config-internal": "4.0.486",
27
+ "@mediabunny/server": "1.50.7",
28
+ "@remotion/eslint-config-internal": "4.0.487",
29
29
  "eslint": "9.19.0",
30
30
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
31
31
  },