@remotion/timeline-utils 4.0.529 → 4.0.530
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/draw-peaks.d.ts +2 -1
- package/dist/audio-waveform/draw-peaks.js +6 -6
- package/dist/device-pixel-alignment.d.ts +14 -0
- package/dist/device-pixel-alignment.js +19 -0
- package/dist/esm/index.mjs +65 -23
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/render-frame-strip-to-canvas.d.ts +2 -1
- package/dist/render-frame-strip-to-canvas.js +13 -8
- package/dist/render-frame-strip.d.ts +6 -3
- package/dist/render-frame-strip.js +19 -5
- package/package.json +3 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
export type WaveformVolume = number | readonly number[];
|
|
2
|
-
export declare const drawBars: ({ canvas, color, peaks, volume, width, }: {
|
|
2
|
+
export declare const drawBars: ({ canvas, color, peaks, volume, width, horizontalOffset, }: {
|
|
3
3
|
readonly canvas: HTMLCanvasElement | OffscreenCanvas;
|
|
4
4
|
readonly peaks: Float32Array<ArrayBufferLike>;
|
|
5
5
|
readonly color: string;
|
|
6
6
|
readonly volume: WaveformVolume;
|
|
7
7
|
readonly width: number;
|
|
8
|
+
readonly horizontalOffset: number;
|
|
8
9
|
}) => void;
|
|
@@ -18,7 +18,7 @@ const getVolumeAtBar = ({ barIndex, numBars, volume, }) => {
|
|
|
18
18
|
const volumeIndex = Math.round((barIndex / (numBars - 1)) * (volume.length - 1));
|
|
19
19
|
return (_a = volume[volumeIndex]) !== null && _a !== void 0 ? _a : 1;
|
|
20
20
|
};
|
|
21
|
-
const drawBars = ({ canvas, color, peaks, volume, width, }) => {
|
|
21
|
+
const drawBars = ({ canvas, color, peaks, volume, width, horizontalOffset, }) => {
|
|
22
22
|
const ctx = canvas.getContext('2d');
|
|
23
23
|
if (!ctx) {
|
|
24
24
|
throw new Error('Failed to get canvas context');
|
|
@@ -29,7 +29,7 @@ const drawBars = ({ canvas, color, peaks, volume, width, }) => {
|
|
|
29
29
|
// `createImageData(0, h)` / `(w, 0)` throws a DOMException, which
|
|
30
30
|
// surfaces in Studio's console for compositions with many audio
|
|
31
31
|
// sequences — some segments are 0 px wide at certain zoom levels.
|
|
32
|
-
if (w === 0 || height === 0) {
|
|
32
|
+
if (w === 0 || height === 0 || width <= 0) {
|
|
33
33
|
return;
|
|
34
34
|
}
|
|
35
35
|
ctx.clearRect(0, 0, w, height);
|
|
@@ -39,10 +39,10 @@ const drawBars = ({ canvas, color, peaks, volume, width, }) => {
|
|
|
39
39
|
const { data } = imageData;
|
|
40
40
|
const numBars = width;
|
|
41
41
|
const fullScaleHalfBar = (height * WAVEFORM_HEIGHT_SCALE) / 2;
|
|
42
|
-
for (let
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
for (let x = 0; x < w; x++) {
|
|
43
|
+
// The canvas may start before its fractionally positioned container so its
|
|
44
|
+
// bitmap stays device-pixel aligned. Sample from the exact container origin.
|
|
45
|
+
const barIndex = Math.max(0, Math.min(numBars - Number.EPSILON * Math.max(1, numBars), x - horizontalOffset));
|
|
46
46
|
const peakIndex = Math.floor((barIndex / numBars) * peaks.length);
|
|
47
47
|
const peak = peaks[peakIndex] || 0;
|
|
48
48
|
const barVolume = getVolumeAtBar({ barIndex, numBars, volume });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare const getDevicePixelAlignedCanvasLayout: ({ cssWidth, cssHeight, containerLeft, devicePixelRatio, }: {
|
|
2
|
+
readonly cssWidth: number;
|
|
3
|
+
readonly cssHeight: number;
|
|
4
|
+
readonly containerLeft: number;
|
|
5
|
+
readonly devicePixelRatio: number;
|
|
6
|
+
}) => {
|
|
7
|
+
height: number;
|
|
8
|
+
horizontalOffset: number;
|
|
9
|
+
width: number;
|
|
10
|
+
};
|
|
11
|
+
export declare const snapCanvasPositionToDevicePixel: ({ position, horizontalOffset, }: {
|
|
12
|
+
readonly position: number;
|
|
13
|
+
readonly horizontalOffset: number;
|
|
14
|
+
}) => number;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.snapCanvasPositionToDevicePixel = exports.getDevicePixelAlignedCanvasLayout = void 0;
|
|
4
|
+
const getDevicePixelAlignedCanvasLayout = ({ cssWidth, cssHeight, containerLeft, devicePixelRatio, }) => {
|
|
5
|
+
const containerLeftInPixels = containerLeft * devicePixelRatio;
|
|
6
|
+
const horizontalOffset = cssWidth === 0
|
|
7
|
+
? 0
|
|
8
|
+
: containerLeftInPixels - Math.floor(containerLeftInPixels);
|
|
9
|
+
return {
|
|
10
|
+
height: Math.ceil(cssHeight * devicePixelRatio),
|
|
11
|
+
horizontalOffset,
|
|
12
|
+
width: Math.ceil(cssWidth * devicePixelRatio + horizontalOffset),
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
exports.getDevicePixelAlignedCanvasLayout = getDevicePixelAlignedCanvasLayout;
|
|
16
|
+
const snapCanvasPositionToDevicePixel = ({ position, horizontalOffset, }) => {
|
|
17
|
+
return Math.floor(position + horizontalOffset) - horizontalOffset;
|
|
18
|
+
};
|
|
19
|
+
exports.snapCanvasPositionToDevicePixel = snapCanvasPositionToDevicePixel;
|
package/dist/esm/index.mjs
CHANGED
|
@@ -40,7 +40,8 @@ var drawBars = ({
|
|
|
40
40
|
color,
|
|
41
41
|
peaks,
|
|
42
42
|
volume,
|
|
43
|
-
width
|
|
43
|
+
width,
|
|
44
|
+
horizontalOffset
|
|
44
45
|
}) => {
|
|
45
46
|
const ctx = canvas.getContext("2d");
|
|
46
47
|
if (!ctx) {
|
|
@@ -48,7 +49,7 @@ var drawBars = ({
|
|
|
48
49
|
}
|
|
49
50
|
const { height } = canvas;
|
|
50
51
|
const w = canvas.width;
|
|
51
|
-
if (w === 0 || height === 0) {
|
|
52
|
+
if (w === 0 || height === 0 || width <= 0) {
|
|
52
53
|
return;
|
|
53
54
|
}
|
|
54
55
|
ctx.clearRect(0, 0, w, height);
|
|
@@ -58,10 +59,8 @@ var drawBars = ({
|
|
|
58
59
|
const { data } = imageData;
|
|
59
60
|
const numBars = width;
|
|
60
61
|
const fullScaleHalfBar = height * WAVEFORM_HEIGHT_SCALE / 2;
|
|
61
|
-
for (let
|
|
62
|
-
const
|
|
63
|
-
if (x >= w)
|
|
64
|
-
break;
|
|
62
|
+
for (let x = 0;x < w; x++) {
|
|
63
|
+
const barIndex = Math.max(0, Math.min(numBars - Number.EPSILON * Math.max(1, numBars), x - horizontalOffset));
|
|
65
64
|
const peakIndex = Math.floor(barIndex / numBars * peaks.length);
|
|
66
65
|
const peak = peaks[peakIndex] || 0;
|
|
67
66
|
const barVolume = getVolumeAtBar({ barIndex, numBars, volume });
|
|
@@ -618,6 +617,27 @@ var clampTimestampsToDuration = ({
|
|
|
618
617
|
const lastTimestamp = Math.max(0, durationInSeconds - LAST_FRAME_EPSILON_IN_SECONDS);
|
|
619
618
|
return timestamps.map((timestamp) => Math.min(timestamp, lastTimestamp));
|
|
620
619
|
};
|
|
620
|
+
// src/device-pixel-alignment.ts
|
|
621
|
+
var getDevicePixelAlignedCanvasLayout = ({
|
|
622
|
+
cssWidth,
|
|
623
|
+
cssHeight,
|
|
624
|
+
containerLeft,
|
|
625
|
+
devicePixelRatio
|
|
626
|
+
}) => {
|
|
627
|
+
const containerLeftInPixels = containerLeft * devicePixelRatio;
|
|
628
|
+
const horizontalOffset = cssWidth === 0 ? 0 : containerLeftInPixels - Math.floor(containerLeftInPixels);
|
|
629
|
+
return {
|
|
630
|
+
height: Math.ceil(cssHeight * devicePixelRatio),
|
|
631
|
+
horizontalOffset,
|
|
632
|
+
width: Math.ceil(cssWidth * devicePixelRatio + horizontalOffset)
|
|
633
|
+
};
|
|
634
|
+
};
|
|
635
|
+
var snapCanvasPositionToDevicePixel = ({
|
|
636
|
+
position,
|
|
637
|
+
horizontalOffset
|
|
638
|
+
}) => {
|
|
639
|
+
return Math.floor(position + horizontalOffset) - horizontalOffset;
|
|
640
|
+
};
|
|
621
641
|
// src/extract-frames.ts
|
|
622
642
|
import {
|
|
623
643
|
ALL_FORMATS as ALL_FORMATS2,
|
|
@@ -855,7 +875,8 @@ var drawSlot = ({
|
|
|
855
875
|
segmentDuration,
|
|
856
876
|
fromSeconds,
|
|
857
877
|
devicePixelRatio,
|
|
858
|
-
frameHeight
|
|
878
|
+
frameHeight,
|
|
879
|
+
horizontalOffset
|
|
859
880
|
}) => {
|
|
860
881
|
const durationOfOneFrame = getDurationOfOneFrame({
|
|
861
882
|
visualizationWidth,
|
|
@@ -866,8 +887,17 @@ var drawSlot = ({
|
|
|
866
887
|
const relativeTimestamp = timestamp - fromSeconds * WEBCODECS_TIMESCALE;
|
|
867
888
|
const frameIndex = relativeTimestamp / durationOfOneFrame;
|
|
868
889
|
const thumbnailWidth = frame.displayWidth / devicePixelRatio;
|
|
869
|
-
const
|
|
870
|
-
|
|
890
|
+
const idealLeft = frameIndex * thumbnailWidth;
|
|
891
|
+
const idealRight = idealLeft + thumbnailWidth;
|
|
892
|
+
const left = snapCanvasPositionToDevicePixel({
|
|
893
|
+
horizontalOffset,
|
|
894
|
+
position: idealLeft
|
|
895
|
+
});
|
|
896
|
+
const right = snapCanvasPositionToDevicePixel({
|
|
897
|
+
horizontalOffset,
|
|
898
|
+
position: idealRight
|
|
899
|
+
});
|
|
900
|
+
ctx.drawImage(frame, left, 0, right - left, frame.displayHeight / devicePixelRatio);
|
|
871
901
|
filledSlots.set(timestamp, frame.timestamp);
|
|
872
902
|
};
|
|
873
903
|
var fillWithCachedFrames = ({
|
|
@@ -878,7 +908,8 @@ var fillWithCachedFrames = ({
|
|
|
878
908
|
segmentDuration,
|
|
879
909
|
fromSeconds,
|
|
880
910
|
devicePixelRatio,
|
|
881
|
-
frameHeight
|
|
911
|
+
frameHeight,
|
|
912
|
+
horizontalOffset
|
|
882
913
|
}) => {
|
|
883
914
|
const prefix = getFrameDatabaseKeyPrefix(src);
|
|
884
915
|
const keys = Array.from(frameDatabase.keys()).filter((k) => k.startsWith(prefix));
|
|
@@ -914,7 +945,8 @@ var fillWithCachedFrames = ({
|
|
|
914
945
|
segmentDuration,
|
|
915
946
|
fromSeconds,
|
|
916
947
|
devicePixelRatio,
|
|
917
|
-
frameHeight
|
|
948
|
+
frameHeight,
|
|
949
|
+
horizontalOffset
|
|
918
950
|
});
|
|
919
951
|
}
|
|
920
952
|
};
|
|
@@ -926,7 +958,8 @@ var fillFrameWhereItFits = ({
|
|
|
926
958
|
segmentDuration,
|
|
927
959
|
fromSeconds,
|
|
928
960
|
devicePixelRatio,
|
|
929
|
-
frameHeight
|
|
961
|
+
frameHeight,
|
|
962
|
+
horizontalOffset
|
|
930
963
|
}) => {
|
|
931
964
|
const slots = Array.from(filledSlots.keys());
|
|
932
965
|
for (let i = 0;i < slots.length; i++) {
|
|
@@ -947,7 +980,8 @@ var fillFrameWhereItFits = ({
|
|
|
947
980
|
segmentDuration,
|
|
948
981
|
fromSeconds,
|
|
949
982
|
devicePixelRatio,
|
|
950
|
-
frameHeight
|
|
983
|
+
frameHeight,
|
|
984
|
+
horizontalOffset
|
|
951
985
|
});
|
|
952
986
|
}
|
|
953
987
|
};
|
|
@@ -1009,9 +1043,11 @@ var renderFrameStripToCanvas = async ({
|
|
|
1009
1043
|
width,
|
|
1010
1044
|
frameHeight,
|
|
1011
1045
|
devicePixelRatio,
|
|
1046
|
+
horizontalOffset,
|
|
1012
1047
|
signal
|
|
1013
1048
|
}) => {
|
|
1014
|
-
const
|
|
1049
|
+
const drawingWidth = Math.max(1, width);
|
|
1050
|
+
const naturalWidth = Math.max(1, Math.ceil(width + horizontalOffset));
|
|
1015
1051
|
const naturalHeight = Math.max(1, Math.ceil(frameHeight));
|
|
1016
1052
|
const segmentDuration = toSeconds - fromSeconds;
|
|
1017
1053
|
canvas.width = naturalWidth;
|
|
@@ -1021,12 +1057,13 @@ var renderFrameStripToCanvas = async ({
|
|
|
1021
1057
|
}
|
|
1022
1058
|
const ctx = getContext(canvas);
|
|
1023
1059
|
ctx.clearRect(0, 0, naturalWidth, naturalHeight);
|
|
1060
|
+
ctx.setTransform(1, 0, 0, 1, horizontalOffset, 0);
|
|
1024
1061
|
const filledSlots = new Map;
|
|
1025
1062
|
let aspectRatio = getAspectRatioFromCache(src);
|
|
1026
1063
|
if (aspectRatio !== null) {
|
|
1027
1064
|
ensureSlots({
|
|
1028
1065
|
filledSlots,
|
|
1029
|
-
naturalWidth,
|
|
1066
|
+
naturalWidth: drawingWidth,
|
|
1030
1067
|
fromSeconds,
|
|
1031
1068
|
toSeconds,
|
|
1032
1069
|
aspectRatio,
|
|
@@ -1034,13 +1071,14 @@ var renderFrameStripToCanvas = async ({
|
|
|
1034
1071
|
});
|
|
1035
1072
|
fillWithCachedFrames({
|
|
1036
1073
|
ctx,
|
|
1037
|
-
naturalWidth,
|
|
1074
|
+
naturalWidth: drawingWidth,
|
|
1038
1075
|
filledSlots,
|
|
1039
1076
|
src,
|
|
1040
1077
|
segmentDuration,
|
|
1041
1078
|
fromSeconds,
|
|
1042
1079
|
devicePixelRatio,
|
|
1043
|
-
frameHeight
|
|
1080
|
+
frameHeight,
|
|
1081
|
+
horizontalOffset
|
|
1044
1082
|
});
|
|
1045
1083
|
const hasUnfilledSlots = Array.from(filledSlots.values()).some((value) => value === undefined);
|
|
1046
1084
|
if (!hasUnfilledSlots) {
|
|
@@ -1055,7 +1093,7 @@ var renderFrameStripToCanvas = async ({
|
|
|
1055
1093
|
aspectRatioCache.set(src, aspectRatio);
|
|
1056
1094
|
ensureSlots({
|
|
1057
1095
|
filledSlots,
|
|
1058
|
-
naturalWidth,
|
|
1096
|
+
naturalWidth: drawingWidth,
|
|
1059
1097
|
fromSeconds,
|
|
1060
1098
|
toSeconds,
|
|
1061
1099
|
aspectRatio,
|
|
@@ -1079,7 +1117,7 @@ var renderFrameStripToCanvas = async ({
|
|
|
1079
1117
|
}
|
|
1080
1118
|
ensureSlots({
|
|
1081
1119
|
filledSlots,
|
|
1082
|
-
naturalWidth,
|
|
1120
|
+
naturalWidth: drawingWidth,
|
|
1083
1121
|
fromSeconds,
|
|
1084
1122
|
toSeconds,
|
|
1085
1123
|
aspectRatio,
|
|
@@ -1088,12 +1126,13 @@ var renderFrameStripToCanvas = async ({
|
|
|
1088
1126
|
fillFrameWhereItFits({
|
|
1089
1127
|
ctx,
|
|
1090
1128
|
filledSlots,
|
|
1091
|
-
visualizationWidth:
|
|
1129
|
+
visualizationWidth: drawingWidth,
|
|
1092
1130
|
frame: transformed,
|
|
1093
1131
|
segmentDuration,
|
|
1094
1132
|
fromSeconds,
|
|
1095
1133
|
devicePixelRatio,
|
|
1096
|
-
frameHeight
|
|
1134
|
+
frameHeight,
|
|
1135
|
+
horizontalOffset
|
|
1097
1136
|
});
|
|
1098
1137
|
} catch (e) {
|
|
1099
1138
|
if (frame) {
|
|
@@ -1110,13 +1149,14 @@ var renderFrameStripToCanvas = async ({
|
|
|
1110
1149
|
}
|
|
1111
1150
|
fillWithCachedFrames({
|
|
1112
1151
|
ctx,
|
|
1113
|
-
naturalWidth,
|
|
1152
|
+
naturalWidth: drawingWidth,
|
|
1114
1153
|
filledSlots,
|
|
1115
1154
|
src,
|
|
1116
1155
|
segmentDuration,
|
|
1117
1156
|
fromSeconds,
|
|
1118
1157
|
devicePixelRatio,
|
|
1119
|
-
frameHeight
|
|
1158
|
+
frameHeight,
|
|
1159
|
+
horizontalOffset
|
|
1120
1160
|
});
|
|
1121
1161
|
};
|
|
1122
1162
|
// src/format-average-audio-volume.ts
|
|
@@ -1128,6 +1168,7 @@ var formatAverageAudioVolume = (volume) => {
|
|
|
1128
1168
|
};
|
|
1129
1169
|
export {
|
|
1130
1170
|
subscribeToWaveformPeaks,
|
|
1171
|
+
snapCanvasPositionToDevicePixel,
|
|
1131
1172
|
sliceWaveformPeaks,
|
|
1132
1173
|
sliceVisibleWaveformPeaks,
|
|
1133
1174
|
shouldTileLoopDisplay,
|
|
@@ -1141,6 +1182,7 @@ export {
|
|
|
1141
1182
|
getLoopDisplaySegments,
|
|
1142
1183
|
getFrameDatabaseKeyPrefix,
|
|
1143
1184
|
getDurationOfOneFrame,
|
|
1185
|
+
getDevicePixelAlignedCanvasLayout,
|
|
1144
1186
|
getAspectRatioFromCache,
|
|
1145
1187
|
frameDatabase,
|
|
1146
1188
|
formatAverageAudioVolume,
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { subscribeToWaveformPeaks } from './audio-waveform/subscribe-to-waveform
|
|
|
8
8
|
export { sliceWaveformPeaks } from './audio-waveform/slice-waveform-peaks';
|
|
9
9
|
export { createWaveformPeakProcessor, emitWaveformProgress, } from './audio-waveform/waveform-peak-processor';
|
|
10
10
|
export { clampTimestampsToDuration } from './clamp-timestamps-to-duration';
|
|
11
|
+
export { getDevicePixelAlignedCanvasLayout, snapCanvasPositionToDevicePixel, } from './device-pixel-alignment';
|
|
11
12
|
export { extractFrames } from './extract-frames';
|
|
12
13
|
export type { ExtractFramesProps, ExtractFramesTimestampsInSecondsFn, } from './extract-frames';
|
|
13
14
|
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.formatAverageAudioVolume = 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.makeFrameDatabaseKey = exports.getTimestampFromFrameDatabaseKey = exports.getFrameDatabaseKeyPrefix = exports.getAspectRatioFromCache = exports.frameDatabase = exports.aspectRatioCache = exports.addFrameToCache = exports.extractFrames = exports.clampTimestampsToDuration = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.subscribeToWaveformPeaks = exports.sliceVisibleWaveformPeaks = exports.loadWaveformPeaks = exports.getVisibleWaveformVolume = exports.drawBars = exports.TARGET_SAMPLE_RATE = void 0;
|
|
3
|
+
exports.formatAverageAudioVolume = 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.makeFrameDatabaseKey = exports.getTimestampFromFrameDatabaseKey = exports.getFrameDatabaseKeyPrefix = exports.getAspectRatioFromCache = exports.frameDatabase = exports.aspectRatioCache = exports.addFrameToCache = exports.extractFrames = exports.snapCanvasPositionToDevicePixel = exports.getDevicePixelAlignedCanvasLayout = exports.clampTimestampsToDuration = exports.emitWaveformProgress = exports.createWaveformPeakProcessor = exports.sliceWaveformPeaks = exports.subscribeToWaveformPeaks = exports.sliceVisibleWaveformPeaks = 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");
|
|
@@ -20,6 +20,9 @@ Object.defineProperty(exports, "createWaveformPeakProcessor", { enumerable: true
|
|
|
20
20
|
Object.defineProperty(exports, "emitWaveformProgress", { enumerable: true, get: function () { return waveform_peak_processor_1.emitWaveformProgress; } });
|
|
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
|
+
const device_pixel_alignment_1 = require("./device-pixel-alignment");
|
|
24
|
+
Object.defineProperty(exports, "getDevicePixelAlignedCanvasLayout", { enumerable: true, get: function () { return device_pixel_alignment_1.getDevicePixelAlignedCanvasLayout; } });
|
|
25
|
+
Object.defineProperty(exports, "snapCanvasPositionToDevicePixel", { enumerable: true, get: function () { return device_pixel_alignment_1.snapCanvasPositionToDevicePixel; } });
|
|
23
26
|
const extract_frames_1 = require("./extract-frames");
|
|
24
27
|
Object.defineProperty(exports, "extractFrames", { enumerable: true, get: function () { return extract_frames_1.extractFrames; } });
|
|
25
28
|
const frame_database_1 = require("./frame-database");
|
|
@@ -7,7 +7,8 @@ export type RenderFrameStripToCanvasOptions = {
|
|
|
7
7
|
readonly width: number;
|
|
8
8
|
readonly frameHeight: number;
|
|
9
9
|
readonly devicePixelRatio: number;
|
|
10
|
+
readonly horizontalOffset: number;
|
|
10
11
|
readonly signal?: AbortSignal;
|
|
11
12
|
};
|
|
12
|
-
export declare const renderFrameStripToCanvas: ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, signal, }: RenderFrameStripToCanvasOptions) => Promise<void>;
|
|
13
|
+
export declare const renderFrameStripToCanvas: ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, horizontalOffset, signal, }: RenderFrameStripToCanvasOptions) => Promise<void>;
|
|
13
14
|
export {};
|
|
@@ -12,8 +12,9 @@ const getContext = (canvas) => {
|
|
|
12
12
|
}
|
|
13
13
|
return ctx;
|
|
14
14
|
};
|
|
15
|
-
const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, signal, }) => {
|
|
16
|
-
const
|
|
15
|
+
const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, width, frameHeight, devicePixelRatio, horizontalOffset, signal, }) => {
|
|
16
|
+
const drawingWidth = Math.max(1, width);
|
|
17
|
+
const naturalWidth = Math.max(1, Math.ceil(width + horizontalOffset));
|
|
17
18
|
const naturalHeight = Math.max(1, Math.ceil(frameHeight));
|
|
18
19
|
const segmentDuration = toSeconds - fromSeconds;
|
|
19
20
|
canvas.width = naturalWidth;
|
|
@@ -23,12 +24,13 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
23
24
|
}
|
|
24
25
|
const ctx = getContext(canvas);
|
|
25
26
|
ctx.clearRect(0, 0, naturalWidth, naturalHeight);
|
|
27
|
+
ctx.setTransform(1, 0, 0, 1, horizontalOffset, 0);
|
|
26
28
|
const filledSlots = new Map();
|
|
27
29
|
let aspectRatio = (0, frame_database_1.getAspectRatioFromCache)(src);
|
|
28
30
|
if (aspectRatio !== null) {
|
|
29
31
|
(0, render_frame_strip_1.ensureSlots)({
|
|
30
32
|
filledSlots,
|
|
31
|
-
naturalWidth,
|
|
33
|
+
naturalWidth: drawingWidth,
|
|
32
34
|
fromSeconds,
|
|
33
35
|
toSeconds,
|
|
34
36
|
aspectRatio,
|
|
@@ -36,13 +38,14 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
36
38
|
});
|
|
37
39
|
(0, render_frame_strip_1.fillWithCachedFrames)({
|
|
38
40
|
ctx,
|
|
39
|
-
naturalWidth,
|
|
41
|
+
naturalWidth: drawingWidth,
|
|
40
42
|
filledSlots,
|
|
41
43
|
src,
|
|
42
44
|
segmentDuration,
|
|
43
45
|
fromSeconds,
|
|
44
46
|
devicePixelRatio,
|
|
45
47
|
frameHeight,
|
|
48
|
+
horizontalOffset,
|
|
46
49
|
});
|
|
47
50
|
const hasUnfilledSlots = Array.from(filledSlots.values()).some((value) => value === undefined);
|
|
48
51
|
if (!hasUnfilledSlots) {
|
|
@@ -57,7 +60,7 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
57
60
|
frame_database_1.aspectRatioCache.set(src, aspectRatio);
|
|
58
61
|
(0, render_frame_strip_1.ensureSlots)({
|
|
59
62
|
filledSlots,
|
|
60
|
-
naturalWidth,
|
|
63
|
+
naturalWidth: drawingWidth,
|
|
61
64
|
fromSeconds,
|
|
62
65
|
toSeconds,
|
|
63
66
|
aspectRatio,
|
|
@@ -81,7 +84,7 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
81
84
|
}
|
|
82
85
|
(0, render_frame_strip_1.ensureSlots)({
|
|
83
86
|
filledSlots,
|
|
84
|
-
naturalWidth,
|
|
87
|
+
naturalWidth: drawingWidth,
|
|
85
88
|
fromSeconds,
|
|
86
89
|
toSeconds,
|
|
87
90
|
aspectRatio,
|
|
@@ -90,12 +93,13 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
90
93
|
(0, render_frame_strip_1.fillFrameWhereItFits)({
|
|
91
94
|
ctx,
|
|
92
95
|
filledSlots,
|
|
93
|
-
visualizationWidth:
|
|
96
|
+
visualizationWidth: drawingWidth,
|
|
94
97
|
frame: transformed,
|
|
95
98
|
segmentDuration,
|
|
96
99
|
fromSeconds,
|
|
97
100
|
devicePixelRatio,
|
|
98
101
|
frameHeight,
|
|
102
|
+
horizontalOffset,
|
|
99
103
|
});
|
|
100
104
|
}
|
|
101
105
|
catch (e) {
|
|
@@ -114,13 +118,14 @@ const renderFrameStripToCanvas = async ({ canvas, src, fromSeconds, toSeconds, w
|
|
|
114
118
|
}
|
|
115
119
|
(0, render_frame_strip_1.fillWithCachedFrames)({
|
|
116
120
|
ctx,
|
|
117
|
-
naturalWidth,
|
|
121
|
+
naturalWidth: drawingWidth,
|
|
118
122
|
filledSlots,
|
|
119
123
|
src,
|
|
120
124
|
segmentDuration,
|
|
121
125
|
fromSeconds,
|
|
122
126
|
devicePixelRatio,
|
|
123
127
|
frameHeight,
|
|
128
|
+
horizontalOffset,
|
|
124
129
|
});
|
|
125
130
|
};
|
|
126
131
|
exports.renderFrameStripToCanvas = renderFrameStripToCanvas;
|
|
@@ -20,7 +20,7 @@ export declare const ensureSlots: ({ filledSlots, naturalWidth, fromSeconds, toS
|
|
|
20
20
|
aspectRatio: number;
|
|
21
21
|
frameHeight: number;
|
|
22
22
|
}) => void;
|
|
23
|
-
export declare const drawSlot: ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }: {
|
|
23
|
+
export declare const drawSlot: ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }: {
|
|
24
24
|
frame: VideoFrame;
|
|
25
25
|
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
26
26
|
filledSlots: Map<number, number | undefined>;
|
|
@@ -30,8 +30,9 @@ export declare const drawSlot: ({ frame, ctx, filledSlots, visualizationWidth, t
|
|
|
30
30
|
fromSeconds: number;
|
|
31
31
|
devicePixelRatio: number;
|
|
32
32
|
frameHeight: number;
|
|
33
|
+
horizontalOffset: number;
|
|
33
34
|
}) => void;
|
|
34
|
-
export declare const fillWithCachedFrames: ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }: {
|
|
35
|
+
export declare const fillWithCachedFrames: ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }: {
|
|
35
36
|
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
36
37
|
naturalWidth: number;
|
|
37
38
|
filledSlots: Map<number, number | undefined>;
|
|
@@ -40,8 +41,9 @@ export declare const fillWithCachedFrames: ({ ctx, naturalWidth, filledSlots, sr
|
|
|
40
41
|
fromSeconds: number;
|
|
41
42
|
devicePixelRatio: number;
|
|
42
43
|
frameHeight: number;
|
|
44
|
+
horizontalOffset: number;
|
|
43
45
|
}) => void;
|
|
44
|
-
export declare const fillFrameWhereItFits: ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }: {
|
|
46
|
+
export declare const fillFrameWhereItFits: ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }: {
|
|
45
47
|
frame: VideoFrame;
|
|
46
48
|
filledSlots: Map<number, number | undefined>;
|
|
47
49
|
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
|
|
@@ -50,4 +52,5 @@ export declare const fillFrameWhereItFits: ({ frame, filledSlots, ctx, visualiza
|
|
|
50
52
|
fromSeconds: number;
|
|
51
53
|
devicePixelRatio: number;
|
|
52
54
|
frameHeight: number;
|
|
55
|
+
horizontalOffset: number;
|
|
53
56
|
}) => void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.fillFrameWhereItFits = exports.fillWithCachedFrames = exports.drawSlot = exports.ensureSlots = exports.calculateTimestampSlots = exports.getDurationOfOneFrame = exports.WEBCODECS_TIMESCALE = void 0;
|
|
4
|
+
const device_pixel_alignment_1 = require("./device-pixel-alignment");
|
|
4
5
|
const frame_database_1 = require("./frame-database");
|
|
5
6
|
exports.WEBCODECS_TIMESCALE = 1000000;
|
|
6
7
|
const MAX_TIME_DEVIATION = exports.WEBCODECS_TIMESCALE * 0.05;
|
|
@@ -50,7 +51,7 @@ const ensureSlots = ({ filledSlots, naturalWidth, fromSeconds, toSeconds, aspect
|
|
|
50
51
|
}
|
|
51
52
|
};
|
|
52
53
|
exports.ensureSlots = ensureSlots;
|
|
53
|
-
const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }) => {
|
|
54
|
+
const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }) => {
|
|
54
55
|
const durationOfOneFrame = (0, exports.getDurationOfOneFrame)({
|
|
55
56
|
visualizationWidth,
|
|
56
57
|
aspectRatio: frame.displayWidth / frame.displayHeight,
|
|
@@ -60,12 +61,23 @@ const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segm
|
|
|
60
61
|
const relativeTimestamp = timestamp - fromSeconds * exports.WEBCODECS_TIMESCALE;
|
|
61
62
|
const frameIndex = relativeTimestamp / durationOfOneFrame;
|
|
62
63
|
const thumbnailWidth = frame.displayWidth / devicePixelRatio;
|
|
63
|
-
const
|
|
64
|
-
|
|
64
|
+
const idealLeft = frameIndex * thumbnailWidth;
|
|
65
|
+
const idealRight = idealLeft + thumbnailWidth;
|
|
66
|
+
// When the canvas is aligned to the device-pixel grid, use the same absolute
|
|
67
|
+
// grid for both edges so adjacent thumbnails neither shimmer nor leave seams.
|
|
68
|
+
const left = (0, device_pixel_alignment_1.snapCanvasPositionToDevicePixel)({
|
|
69
|
+
horizontalOffset,
|
|
70
|
+
position: idealLeft,
|
|
71
|
+
});
|
|
72
|
+
const right = (0, device_pixel_alignment_1.snapCanvasPositionToDevicePixel)({
|
|
73
|
+
horizontalOffset,
|
|
74
|
+
position: idealRight,
|
|
75
|
+
});
|
|
76
|
+
ctx.drawImage(frame, left, 0, right - left, frame.displayHeight / devicePixelRatio);
|
|
65
77
|
filledSlots.set(timestamp, frame.timestamp);
|
|
66
78
|
};
|
|
67
79
|
exports.drawSlot = drawSlot;
|
|
68
|
-
const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }) => {
|
|
80
|
+
const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }) => {
|
|
69
81
|
const prefix = (0, frame_database_1.getFrameDatabaseKeyPrefix)(src);
|
|
70
82
|
const keys = Array.from(frame_database_1.frameDatabase.keys()).filter((k) => k.startsWith(prefix));
|
|
71
83
|
const targets = Array.from(filledSlots.keys());
|
|
@@ -103,11 +115,12 @@ const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDura
|
|
|
103
115
|
fromSeconds,
|
|
104
116
|
devicePixelRatio,
|
|
105
117
|
frameHeight,
|
|
118
|
+
horizontalOffset,
|
|
106
119
|
});
|
|
107
120
|
}
|
|
108
121
|
};
|
|
109
122
|
exports.fillWithCachedFrames = fillWithCachedFrames;
|
|
110
|
-
const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, }) => {
|
|
123
|
+
const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, segmentDuration, fromSeconds, devicePixelRatio, frameHeight, horizontalOffset, }) => {
|
|
111
124
|
const slots = Array.from(filledSlots.keys());
|
|
112
125
|
for (let i = 0; i < slots.length; i++) {
|
|
113
126
|
const slot = slots[i];
|
|
@@ -129,6 +142,7 @@ const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, seg
|
|
|
129
142
|
fromSeconds,
|
|
130
143
|
devicePixelRatio,
|
|
131
144
|
frameHeight,
|
|
145
|
+
horizontalOffset,
|
|
132
146
|
});
|
|
133
147
|
}
|
|
134
148
|
};
|
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.530",
|
|
7
7
|
"description": "Internal utilities for rendering Remotion timelines",
|
|
8
8
|
"main": "dist/index.js",
|
|
9
9
|
"scripts": {
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"mediabunny": "1.56.1",
|
|
25
|
-
"remotion": "4.0.
|
|
25
|
+
"remotion": "4.0.530"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@mediabunny/server": "1.56.1",
|
|
29
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
29
|
+
"@remotion/eslint-config-internal": "4.0.530",
|
|
30
30
|
"eslint": "9.19.0",
|
|
31
31
|
"@typescript/native-preview": "7.0.0-dev.20260217.1"
|
|
32
32
|
},
|