@remotion/studio 4.0.420 → 4.0.422

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.
@@ -45,7 +45,7 @@ const Inner = ({ s, windowWidth }) => {
45
45
  const isPostmounting = relativeFrameWithPostmount >= 0 &&
46
46
  relativeFrameWithPostmount < ((_c = s.postmountDisplay) !== null && _c !== void 0 ? _c : 0) &&
47
47
  !isInRange;
48
- const { marginLeft, width, premountWidth, postmountWidth } = (0, react_1.useMemo)(() => {
48
+ const { marginLeft, width, naturalWidth, premountWidth, postmountWidth } = (0, react_1.useMemo)(() => {
49
49
  return (0, get_timeline_sequence_layout_1.getTimelineSequenceLayout)({
50
50
  durationInFrames: s.loopDisplay
51
51
  ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes
@@ -103,7 +103,7 @@ const Inner = ({ s, windowWidth }) => {
103
103
  )`,
104
104
  position: 'absolute',
105
105
  right: 0,
106
- } })) : null, s.type === 'audio' ? ((0, jsx_runtime_1.jsx)(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate })) : null, s.type === 'video' ? ((0, jsx_runtime_1.jsx)(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration })) : null, s.loopDisplay === undefined ? null : ((0, jsx_runtime_1.jsx)(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
106
+ } })) : null, s.type === 'audio' ? ((0, jsx_runtime_1.jsx)(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate })) : null, s.type === 'video' ? ((0, jsx_runtime_1.jsx)(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate })) : null, s.loopDisplay === undefined ? null : ((0, jsx_runtime_1.jsx)(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
107
107
  s.type !== 'video' &&
108
108
  s.loopDisplay === undefined &&
109
109
  (isInRange || isPremounting || isPostmounting) ? ((0, jsx_runtime_1.jsx)("div", { style: {
@@ -2,6 +2,8 @@ import React from 'react';
2
2
  export declare const TimelineVideoInfo: React.FC<{
3
3
  readonly src: string;
4
4
  readonly visualizationWidth: number;
5
- readonly startFrom: number;
5
+ readonly naturalWidth: number;
6
+ readonly trimBefore: number;
6
7
  readonly durationInFrames: number;
8
+ readonly playbackRate: number;
7
9
  }>;
@@ -48,10 +48,10 @@ const calculateTimestampSlots = ({ visualizationWidth, fromSeconds, segmentDurat
48
48
  }
49
49
  return timestampTargets;
50
50
  };
51
- const ensureSlots = ({ filledSlots, visualizationWidth, fromSeconds, toSeconds, aspectRatio, }) => {
51
+ const ensureSlots = ({ filledSlots, naturalWidth, fromSeconds, toSeconds, aspectRatio, }) => {
52
52
  const segmentDuration = toSeconds - fromSeconds;
53
53
  const timestampTargets = calculateTimestampSlots({
54
- visualizationWidth,
54
+ visualizationWidth: naturalWidth,
55
55
  fromSeconds,
56
56
  segmentDuration,
57
57
  aspectRatio,
@@ -74,7 +74,7 @@ const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segm
74
74
  ctx.drawImage(frame, left, 0, frame.displayWidth / window.devicePixelRatio, frame.displayHeight / window.devicePixelRatio);
75
75
  filledSlots.set(timestamp, frame.timestamp);
76
76
  };
77
- const fillWithCachedFrames = ({ ctx, visualizationWidth, filledSlots, src, segmentDuration, fromSeconds, }) => {
77
+ const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, }) => {
78
78
  const prefix = (0, frame_database_1.getFrameDatabaseKeyPrefix)(src);
79
79
  const keys = Array.from(frame_database_1.frameDatabase.keys()).filter((k) => k.startsWith(prefix));
80
80
  const targets = Array.from(filledSlots.keys());
@@ -107,7 +107,7 @@ const fillWithCachedFrames = ({ ctx, visualizationWidth, filledSlots, src, segme
107
107
  ctx,
108
108
  frame: frame.frame,
109
109
  filledSlots,
110
- visualizationWidth,
110
+ visualizationWidth: naturalWidth,
111
111
  timestamp,
112
112
  segmentDuration,
113
113
  fromSeconds,
@@ -138,7 +138,7 @@ const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, seg
138
138
  });
139
139
  }
140
140
  };
141
- const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames }) => {
141
+ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore, durationInFrames, playbackRate, }) => {
142
142
  const { fps } = (0, remotion_1.useVideoConfig)();
143
143
  const ref = (0, react_1.useRef)(null);
144
144
  const [error, setError] = (0, react_1.useState)(null);
@@ -168,19 +168,21 @@ const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrame
168
168
  current.appendChild(canvas);
169
169
  // desired-timestamp -> filled-timestamp
170
170
  const filledSlots = new Map();
171
- const fromSeconds = startFrom / fps;
172
- const toSeconds = (startFrom + durationInFrames) / fps;
171
+ const fromSeconds = trimBefore / fps;
172
+ // Trim is applied first, then playbackRate. Each composition frame
173
+ // advances the source video by `playbackRate` source frames.
174
+ const toSeconds = fromSeconds + (durationInFrames * playbackRate) / fps;
173
175
  if (aspectRatio.current !== null) {
174
176
  ensureSlots({
175
177
  filledSlots,
176
- visualizationWidth,
178
+ naturalWidth,
177
179
  fromSeconds,
178
180
  toSeconds,
179
181
  aspectRatio: aspectRatio.current,
180
182
  });
181
183
  fillWithCachedFrames({
182
184
  ctx,
183
- visualizationWidth,
185
+ naturalWidth,
184
186
  filledSlots,
185
187
  src,
186
188
  segmentDuration: toSeconds - fromSeconds,
@@ -204,7 +206,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrame
204
206
  filledSlots,
205
207
  fromSeconds,
206
208
  toSeconds,
207
- visualizationWidth,
209
+ naturalWidth,
208
210
  aspectRatio: aspectRatio.current,
209
211
  });
210
212
  return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
@@ -236,13 +238,13 @@ const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrame
236
238
  filledSlots,
237
239
  fromSeconds,
238
240
  toSeconds,
239
- visualizationWidth,
241
+ naturalWidth,
240
242
  aspectRatio: aspectRatio.current,
241
243
  });
242
244
  fillFrameWhereItFits({
243
245
  ctx,
244
246
  filledSlots,
245
- visualizationWidth,
247
+ visualizationWidth: naturalWidth,
246
248
  frame: transformed,
247
249
  segmentDuration: toSeconds - fromSeconds,
248
250
  fromSeconds,
@@ -254,7 +256,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrame
254
256
  .then(() => {
255
257
  fillWithCachedFrames({
256
258
  ctx,
257
- visualizationWidth,
259
+ naturalWidth,
258
260
  filledSlots,
259
261
  src,
260
262
  segmentDuration: toSeconds - fromSeconds,
@@ -271,7 +273,16 @@ const TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrame
271
273
  controller.abort();
272
274
  current.removeChild(canvas);
273
275
  };
274
- }, [durationInFrames, error, fps, src, startFrom, visualizationWidth]);
276
+ }, [
277
+ durationInFrames,
278
+ error,
279
+ fps,
280
+ naturalWidth,
281
+ playbackRate,
282
+ src,
283
+ trimBefore,
284
+ visualizationWidth,
285
+ ]);
275
286
  return (0, jsx_runtime_1.jsx)("div", { ref: ref, style: containerStyle });
276
287
  };
277
288
  exports.TimelineVideoInfo = TimelineVideoInfo;
@@ -21116,13 +21116,16 @@ var getTimelineSequenceLayout = ({
21116
21116
  const maxMediaSequenceDuration = (maxMediaDuration ?? Infinity) - startFromMedia;
21117
21117
  const lastFrame = (video.durationInFrames ?? 1) - 1;
21118
21118
  let spatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1, lastFrame - startFrom);
21119
+ let naturalSpatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1);
21119
21120
  const shouldAddHalfAFrameAtEnd = startFrom + durationInFrames < lastFrame;
21120
21121
  const shouldAddHalfAFrameAtStart = startFrom > 0;
21121
21122
  if (shouldAddHalfAFrameAtEnd) {
21122
21123
  spatialDuration += 0.5;
21124
+ naturalSpatialDuration += 0.5;
21123
21125
  }
21124
21126
  if (shouldAddHalfAFrameAtStart) {
21125
21127
  spatialDuration += 0.5;
21128
+ naturalSpatialDuration += 0.5;
21126
21129
  }
21127
21130
  const startFromWithOffset = shouldAddHalfAFrameAtStart ? startFrom - 0.5 : startFrom;
21128
21131
  const marginLeft = lastFrame === 0 ? 0 : startFromWithOffset / lastFrame * (windowWidth - TIMELINE_PADDING * 2);
@@ -21134,6 +21137,13 @@ var getTimelineSequenceLayout = ({
21134
21137
  spatialDuration,
21135
21138
  windowWidth
21136
21139
  });
21140
+ const naturalWidth = getWidthOfTrack({
21141
+ durationInFrames,
21142
+ lastFrame,
21143
+ nonNegativeMarginLeft,
21144
+ spatialDuration: naturalSpatialDuration,
21145
+ windowWidth
21146
+ });
21137
21147
  const premountWidth = premountDisplay ? getWidthOfTrack({
21138
21148
  durationInFrames: premountDisplay,
21139
21149
  lastFrame,
@@ -21151,6 +21161,7 @@ var getTimelineSequenceLayout = ({
21151
21161
  return {
21152
21162
  marginLeft: Math.max(marginLeft, 0) - (premountWidth ?? 0),
21153
21163
  width: width + (premountWidth ?? 0) + (postmountWidth ?? 0),
21164
+ naturalWidth: naturalWidth + (premountWidth ?? 0) + (postmountWidth ?? 0),
21154
21165
  premountWidth,
21155
21166
  postmountWidth
21156
21167
  };
@@ -36063,6 +36074,9 @@ var useMaxMediaDuration = (s, fps) => {
36063
36074
  input2.dispose();
36064
36075
  };
36065
36076
  }, [src, fps]);
36077
+ if (maxMediaDuration !== null && (s.type === "audio" || s.type === "video")) {
36078
+ return maxMediaDuration / s.playbackRate;
36079
+ }
36066
36080
  return maxMediaDuration;
36067
36081
  };
36068
36082
 
@@ -36544,14 +36558,14 @@ var calculateTimestampSlots = ({
36544
36558
  };
36545
36559
  var ensureSlots = ({
36546
36560
  filledSlots,
36547
- visualizationWidth,
36561
+ naturalWidth,
36548
36562
  fromSeconds,
36549
36563
  toSeconds,
36550
36564
  aspectRatio
36551
36565
  }) => {
36552
36566
  const segmentDuration = toSeconds - fromSeconds;
36553
36567
  const timestampTargets = calculateTimestampSlots({
36554
- visualizationWidth,
36568
+ visualizationWidth: naturalWidth,
36555
36569
  fromSeconds,
36556
36570
  segmentDuration,
36557
36571
  aspectRatio
@@ -36584,7 +36598,7 @@ var drawSlot = ({
36584
36598
  };
36585
36599
  var fillWithCachedFrames = ({
36586
36600
  ctx,
36587
- visualizationWidth,
36601
+ naturalWidth,
36588
36602
  filledSlots,
36589
36603
  src,
36590
36604
  segmentDuration,
@@ -36619,7 +36633,7 @@ var fillWithCachedFrames = ({
36619
36633
  ctx,
36620
36634
  frame: frame2.frame,
36621
36635
  filledSlots,
36622
- visualizationWidth,
36636
+ visualizationWidth: naturalWidth,
36623
36637
  timestamp,
36624
36638
  segmentDuration,
36625
36639
  fromSeconds
@@ -36655,7 +36669,14 @@ var fillFrameWhereItFits = ({
36655
36669
  });
36656
36670
  }
36657
36671
  };
36658
- var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames }) => {
36672
+ var TimelineVideoInfo = ({
36673
+ src,
36674
+ visualizationWidth,
36675
+ naturalWidth,
36676
+ trimBefore,
36677
+ durationInFrames,
36678
+ playbackRate
36679
+ }) => {
36659
36680
  const { fps } = useVideoConfig5();
36660
36681
  const ref = useRef40(null);
36661
36682
  const [error, setError] = useState69(null);
@@ -36683,19 +36704,19 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36683
36704
  }
36684
36705
  current.appendChild(canvas);
36685
36706
  const filledSlots = new Map;
36686
- const fromSeconds = startFrom / fps;
36687
- const toSeconds = (startFrom + durationInFrames) / fps;
36707
+ const fromSeconds = trimBefore / fps;
36708
+ const toSeconds = fromSeconds + durationInFrames * playbackRate / fps;
36688
36709
  if (aspectRatio.current !== null) {
36689
36710
  ensureSlots({
36690
36711
  filledSlots,
36691
- visualizationWidth,
36712
+ naturalWidth,
36692
36713
  fromSeconds,
36693
36714
  toSeconds,
36694
36715
  aspectRatio: aspectRatio.current
36695
36716
  });
36696
36717
  fillWithCachedFrames({
36697
36718
  ctx,
36698
- visualizationWidth,
36719
+ naturalWidth,
36699
36720
  filledSlots,
36700
36721
  src,
36701
36722
  segmentDuration: toSeconds - fromSeconds,
@@ -36720,7 +36741,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36720
36741
  filledSlots,
36721
36742
  fromSeconds,
36722
36743
  toSeconds,
36723
- visualizationWidth,
36744
+ naturalWidth,
36724
36745
  aspectRatio: aspectRatio.current
36725
36746
  });
36726
36747
  return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
@@ -36752,13 +36773,13 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36752
36773
  filledSlots,
36753
36774
  fromSeconds,
36754
36775
  toSeconds,
36755
- visualizationWidth,
36776
+ naturalWidth,
36756
36777
  aspectRatio: aspectRatio.current
36757
36778
  });
36758
36779
  fillFrameWhereItFits({
36759
36780
  ctx,
36760
36781
  filledSlots,
36761
- visualizationWidth,
36782
+ visualizationWidth: naturalWidth,
36762
36783
  frame: transformed,
36763
36784
  segmentDuration: toSeconds - fromSeconds,
36764
36785
  fromSeconds
@@ -36769,7 +36790,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36769
36790
  }).then(() => {
36770
36791
  fillWithCachedFrames({
36771
36792
  ctx,
36772
- visualizationWidth,
36793
+ naturalWidth,
36773
36794
  filledSlots,
36774
36795
  src,
36775
36796
  segmentDuration: toSeconds - fromSeconds,
@@ -36784,7 +36805,16 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36784
36805
  controller.abort();
36785
36806
  current.removeChild(canvas);
36786
36807
  };
36787
- }, [durationInFrames, error, fps, src, startFrom, visualizationWidth]);
36808
+ }, [
36809
+ durationInFrames,
36810
+ error,
36811
+ fps,
36812
+ naturalWidth,
36813
+ playbackRate,
36814
+ src,
36815
+ trimBefore,
36816
+ visualizationWidth
36817
+ ]);
36788
36818
  return /* @__PURE__ */ jsx200("div", {
36789
36819
  ref,
36790
36820
  style: containerStyle3
@@ -36819,7 +36849,7 @@ var Inner4 = ({ s, windowWidth }) => {
36819
36849
  const isInRange = relativeFrame >= 0 && relativeFrame < s.duration;
36820
36850
  const isPremounting = relativeFrameWithPremount >= 0 && relativeFrameWithPremount < s.duration && !isInRange;
36821
36851
  const isPostmounting = relativeFrameWithPostmount >= 0 && relativeFrameWithPostmount < (s.postmountDisplay ?? 0) && !isInRange;
36822
- const { marginLeft, width: width2, premountWidth, postmountWidth } = useMemo107(() => {
36852
+ const { marginLeft, width: width2, naturalWidth, premountWidth, postmountWidth } = useMemo107(() => {
36823
36853
  return getTimelineSequenceLayout({
36824
36854
  durationInFrames: s.loopDisplay ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes : s.duration,
36825
36855
  startFrom: s.loopDisplay ? s.from + s.loopDisplay.startOffset : s.from,
@@ -36893,8 +36923,10 @@ var Inner4 = ({ s, windowWidth }) => {
36893
36923
  s.type === "video" ? /* @__PURE__ */ jsx201(TimelineVideoInfo, {
36894
36924
  src: s.src,
36895
36925
  visualizationWidth: width2,
36896
- startFrom: s.startMediaFrom,
36897
- durationInFrames: s.duration
36926
+ naturalWidth,
36927
+ trimBefore: s.startMediaFrom,
36928
+ durationInFrames: s.duration,
36929
+ playbackRate: s.playbackRate
36898
36930
  }) : null,
36899
36931
  s.loopDisplay === undefined ? null : /* @__PURE__ */ jsx201(LoopedTimelineIndicator, {
36900
36932
  loops: s.loopDisplay.numberOfTimes
@@ -21135,13 +21135,16 @@ var getTimelineSequenceLayout = ({
21135
21135
  const maxMediaSequenceDuration = (maxMediaDuration ?? Infinity) - startFromMedia;
21136
21136
  const lastFrame = (video.durationInFrames ?? 1) - 1;
21137
21137
  let spatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1, lastFrame - startFrom);
21138
+ let naturalSpatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1);
21138
21139
  const shouldAddHalfAFrameAtEnd = startFrom + durationInFrames < lastFrame;
21139
21140
  const shouldAddHalfAFrameAtStart = startFrom > 0;
21140
21141
  if (shouldAddHalfAFrameAtEnd) {
21141
21142
  spatialDuration += 0.5;
21143
+ naturalSpatialDuration += 0.5;
21142
21144
  }
21143
21145
  if (shouldAddHalfAFrameAtStart) {
21144
21146
  spatialDuration += 0.5;
21147
+ naturalSpatialDuration += 0.5;
21145
21148
  }
21146
21149
  const startFromWithOffset = shouldAddHalfAFrameAtStart ? startFrom - 0.5 : startFrom;
21147
21150
  const marginLeft = lastFrame === 0 ? 0 : startFromWithOffset / lastFrame * (windowWidth - TIMELINE_PADDING * 2);
@@ -21153,6 +21156,13 @@ var getTimelineSequenceLayout = ({
21153
21156
  spatialDuration,
21154
21157
  windowWidth
21155
21158
  });
21159
+ const naturalWidth = getWidthOfTrack({
21160
+ durationInFrames,
21161
+ lastFrame,
21162
+ nonNegativeMarginLeft,
21163
+ spatialDuration: naturalSpatialDuration,
21164
+ windowWidth
21165
+ });
21156
21166
  const premountWidth = premountDisplay ? getWidthOfTrack({
21157
21167
  durationInFrames: premountDisplay,
21158
21168
  lastFrame,
@@ -21170,6 +21180,7 @@ var getTimelineSequenceLayout = ({
21170
21180
  return {
21171
21181
  marginLeft: Math.max(marginLeft, 0) - (premountWidth ?? 0),
21172
21182
  width: width + (premountWidth ?? 0) + (postmountWidth ?? 0),
21183
+ naturalWidth: naturalWidth + (premountWidth ?? 0) + (postmountWidth ?? 0),
21173
21184
  premountWidth,
21174
21185
  postmountWidth
21175
21186
  };
@@ -36082,6 +36093,9 @@ var useMaxMediaDuration = (s, fps) => {
36082
36093
  input2.dispose();
36083
36094
  };
36084
36095
  }, [src, fps]);
36096
+ if (maxMediaDuration !== null && (s.type === "audio" || s.type === "video")) {
36097
+ return maxMediaDuration / s.playbackRate;
36098
+ }
36085
36099
  return maxMediaDuration;
36086
36100
  };
36087
36101
 
@@ -36563,14 +36577,14 @@ var calculateTimestampSlots = ({
36563
36577
  };
36564
36578
  var ensureSlots = ({
36565
36579
  filledSlots,
36566
- visualizationWidth,
36580
+ naturalWidth,
36567
36581
  fromSeconds,
36568
36582
  toSeconds,
36569
36583
  aspectRatio
36570
36584
  }) => {
36571
36585
  const segmentDuration = toSeconds - fromSeconds;
36572
36586
  const timestampTargets = calculateTimestampSlots({
36573
- visualizationWidth,
36587
+ visualizationWidth: naturalWidth,
36574
36588
  fromSeconds,
36575
36589
  segmentDuration,
36576
36590
  aspectRatio
@@ -36603,7 +36617,7 @@ var drawSlot = ({
36603
36617
  };
36604
36618
  var fillWithCachedFrames = ({
36605
36619
  ctx,
36606
- visualizationWidth,
36620
+ naturalWidth,
36607
36621
  filledSlots,
36608
36622
  src,
36609
36623
  segmentDuration,
@@ -36638,7 +36652,7 @@ var fillWithCachedFrames = ({
36638
36652
  ctx,
36639
36653
  frame: frame2.frame,
36640
36654
  filledSlots,
36641
- visualizationWidth,
36655
+ visualizationWidth: naturalWidth,
36642
36656
  timestamp,
36643
36657
  segmentDuration,
36644
36658
  fromSeconds
@@ -36674,7 +36688,14 @@ var fillFrameWhereItFits = ({
36674
36688
  });
36675
36689
  }
36676
36690
  };
36677
- var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames }) => {
36691
+ var TimelineVideoInfo = ({
36692
+ src,
36693
+ visualizationWidth,
36694
+ naturalWidth,
36695
+ trimBefore,
36696
+ durationInFrames,
36697
+ playbackRate
36698
+ }) => {
36678
36699
  const { fps } = useVideoConfig5();
36679
36700
  const ref = useRef40(null);
36680
36701
  const [error, setError] = useState69(null);
@@ -36702,19 +36723,19 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36702
36723
  }
36703
36724
  current.appendChild(canvas);
36704
36725
  const filledSlots = new Map;
36705
- const fromSeconds = startFrom / fps;
36706
- const toSeconds = (startFrom + durationInFrames) / fps;
36726
+ const fromSeconds = trimBefore / fps;
36727
+ const toSeconds = fromSeconds + durationInFrames * playbackRate / fps;
36707
36728
  if (aspectRatio.current !== null) {
36708
36729
  ensureSlots({
36709
36730
  filledSlots,
36710
- visualizationWidth,
36731
+ naturalWidth,
36711
36732
  fromSeconds,
36712
36733
  toSeconds,
36713
36734
  aspectRatio: aspectRatio.current
36714
36735
  });
36715
36736
  fillWithCachedFrames({
36716
36737
  ctx,
36717
- visualizationWidth,
36738
+ naturalWidth,
36718
36739
  filledSlots,
36719
36740
  src,
36720
36741
  segmentDuration: toSeconds - fromSeconds,
@@ -36739,7 +36760,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36739
36760
  filledSlots,
36740
36761
  fromSeconds,
36741
36762
  toSeconds,
36742
- visualizationWidth,
36763
+ naturalWidth,
36743
36764
  aspectRatio: aspectRatio.current
36744
36765
  });
36745
36766
  return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
@@ -36771,13 +36792,13 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36771
36792
  filledSlots,
36772
36793
  fromSeconds,
36773
36794
  toSeconds,
36774
- visualizationWidth,
36795
+ naturalWidth,
36775
36796
  aspectRatio: aspectRatio.current
36776
36797
  });
36777
36798
  fillFrameWhereItFits({
36778
36799
  ctx,
36779
36800
  filledSlots,
36780
- visualizationWidth,
36801
+ visualizationWidth: naturalWidth,
36781
36802
  frame: transformed,
36782
36803
  segmentDuration: toSeconds - fromSeconds,
36783
36804
  fromSeconds
@@ -36788,7 +36809,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36788
36809
  }).then(() => {
36789
36810
  fillWithCachedFrames({
36790
36811
  ctx,
36791
- visualizationWidth,
36812
+ naturalWidth,
36792
36813
  filledSlots,
36793
36814
  src,
36794
36815
  segmentDuration: toSeconds - fromSeconds,
@@ -36803,7 +36824,16 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36803
36824
  controller.abort();
36804
36825
  current.removeChild(canvas);
36805
36826
  };
36806
- }, [durationInFrames, error, fps, src, startFrom, visualizationWidth]);
36827
+ }, [
36828
+ durationInFrames,
36829
+ error,
36830
+ fps,
36831
+ naturalWidth,
36832
+ playbackRate,
36833
+ src,
36834
+ trimBefore,
36835
+ visualizationWidth
36836
+ ]);
36807
36837
  return /* @__PURE__ */ jsx200("div", {
36808
36838
  ref,
36809
36839
  style: containerStyle3
@@ -36838,7 +36868,7 @@ var Inner4 = ({ s, windowWidth }) => {
36838
36868
  const isInRange = relativeFrame >= 0 && relativeFrame < s.duration;
36839
36869
  const isPremounting = relativeFrameWithPremount >= 0 && relativeFrameWithPremount < s.duration && !isInRange;
36840
36870
  const isPostmounting = relativeFrameWithPostmount >= 0 && relativeFrameWithPostmount < (s.postmountDisplay ?? 0) && !isInRange;
36841
- const { marginLeft, width: width2, premountWidth, postmountWidth } = useMemo107(() => {
36871
+ const { marginLeft, width: width2, naturalWidth, premountWidth, postmountWidth } = useMemo107(() => {
36842
36872
  return getTimelineSequenceLayout({
36843
36873
  durationInFrames: s.loopDisplay ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes : s.duration,
36844
36874
  startFrom: s.loopDisplay ? s.from + s.loopDisplay.startOffset : s.from,
@@ -36912,8 +36942,10 @@ var Inner4 = ({ s, windowWidth }) => {
36912
36942
  s.type === "video" ? /* @__PURE__ */ jsx201(TimelineVideoInfo, {
36913
36943
  src: s.src,
36914
36944
  visualizationWidth: width2,
36915
- startFrom: s.startMediaFrom,
36916
- durationInFrames: s.duration
36945
+ naturalWidth,
36946
+ trimBefore: s.startMediaFrom,
36947
+ durationInFrames: s.duration,
36948
+ playbackRate: s.playbackRate
36917
36949
  }) : null,
36918
36950
  s.loopDisplay === undefined ? null : /* @__PURE__ */ jsx201(LoopedTimelineIndicator, {
36919
36951
  loops: s.loopDisplay.numberOfTimes
@@ -21415,13 +21415,16 @@ var getTimelineSequenceLayout = ({
21415
21415
  const maxMediaSequenceDuration = (maxMediaDuration ?? Infinity) - startFromMedia;
21416
21416
  const lastFrame = (video.durationInFrames ?? 1) - 1;
21417
21417
  let spatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1, lastFrame - startFrom);
21418
+ let naturalSpatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1);
21418
21419
  const shouldAddHalfAFrameAtEnd = startFrom + durationInFrames < lastFrame;
21419
21420
  const shouldAddHalfAFrameAtStart = startFrom > 0;
21420
21421
  if (shouldAddHalfAFrameAtEnd) {
21421
21422
  spatialDuration += 0.5;
21423
+ naturalSpatialDuration += 0.5;
21422
21424
  }
21423
21425
  if (shouldAddHalfAFrameAtStart) {
21424
21426
  spatialDuration += 0.5;
21427
+ naturalSpatialDuration += 0.5;
21425
21428
  }
21426
21429
  const startFromWithOffset = shouldAddHalfAFrameAtStart ? startFrom - 0.5 : startFrom;
21427
21430
  const marginLeft = lastFrame === 0 ? 0 : startFromWithOffset / lastFrame * (windowWidth - TIMELINE_PADDING * 2);
@@ -21433,6 +21436,13 @@ var getTimelineSequenceLayout = ({
21433
21436
  spatialDuration,
21434
21437
  windowWidth
21435
21438
  });
21439
+ const naturalWidth = getWidthOfTrack({
21440
+ durationInFrames,
21441
+ lastFrame,
21442
+ nonNegativeMarginLeft,
21443
+ spatialDuration: naturalSpatialDuration,
21444
+ windowWidth
21445
+ });
21436
21446
  const premountWidth = premountDisplay ? getWidthOfTrack({
21437
21447
  durationInFrames: premountDisplay,
21438
21448
  lastFrame,
@@ -21450,6 +21460,7 @@ var getTimelineSequenceLayout = ({
21450
21460
  return {
21451
21461
  marginLeft: Math.max(marginLeft, 0) - (premountWidth ?? 0),
21452
21462
  width: width + (premountWidth ?? 0) + (postmountWidth ?? 0),
21463
+ naturalWidth: naturalWidth + (premountWidth ?? 0) + (postmountWidth ?? 0),
21453
21464
  premountWidth,
21454
21465
  postmountWidth
21455
21466
  };
@@ -36362,6 +36373,9 @@ var useMaxMediaDuration = (s, fps) => {
36362
36373
  input2.dispose();
36363
36374
  };
36364
36375
  }, [src, fps]);
36376
+ if (maxMediaDuration !== null && (s.type === "audio" || s.type === "video")) {
36377
+ return maxMediaDuration / s.playbackRate;
36378
+ }
36365
36379
  return maxMediaDuration;
36366
36380
  };
36367
36381
 
@@ -36843,14 +36857,14 @@ var calculateTimestampSlots = ({
36843
36857
  };
36844
36858
  var ensureSlots = ({
36845
36859
  filledSlots,
36846
- visualizationWidth,
36860
+ naturalWidth,
36847
36861
  fromSeconds,
36848
36862
  toSeconds,
36849
36863
  aspectRatio
36850
36864
  }) => {
36851
36865
  const segmentDuration = toSeconds - fromSeconds;
36852
36866
  const timestampTargets = calculateTimestampSlots({
36853
- visualizationWidth,
36867
+ visualizationWidth: naturalWidth,
36854
36868
  fromSeconds,
36855
36869
  segmentDuration,
36856
36870
  aspectRatio
@@ -36883,7 +36897,7 @@ var drawSlot = ({
36883
36897
  };
36884
36898
  var fillWithCachedFrames = ({
36885
36899
  ctx,
36886
- visualizationWidth,
36900
+ naturalWidth,
36887
36901
  filledSlots,
36888
36902
  src,
36889
36903
  segmentDuration,
@@ -36918,7 +36932,7 @@ var fillWithCachedFrames = ({
36918
36932
  ctx,
36919
36933
  frame: frame2.frame,
36920
36934
  filledSlots,
36921
- visualizationWidth,
36935
+ visualizationWidth: naturalWidth,
36922
36936
  timestamp,
36923
36937
  segmentDuration,
36924
36938
  fromSeconds
@@ -36954,7 +36968,14 @@ var fillFrameWhereItFits = ({
36954
36968
  });
36955
36969
  }
36956
36970
  };
36957
- var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames }) => {
36971
+ var TimelineVideoInfo = ({
36972
+ src,
36973
+ visualizationWidth,
36974
+ naturalWidth,
36975
+ trimBefore,
36976
+ durationInFrames,
36977
+ playbackRate
36978
+ }) => {
36958
36979
  const { fps } = useVideoConfig5();
36959
36980
  const ref = useRef40(null);
36960
36981
  const [error, setError] = useState70(null);
@@ -36982,19 +37003,19 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
36982
37003
  }
36983
37004
  current.appendChild(canvas);
36984
37005
  const filledSlots = new Map;
36985
- const fromSeconds = startFrom / fps;
36986
- const toSeconds = (startFrom + durationInFrames) / fps;
37006
+ const fromSeconds = trimBefore / fps;
37007
+ const toSeconds = fromSeconds + durationInFrames * playbackRate / fps;
36987
37008
  if (aspectRatio.current !== null) {
36988
37009
  ensureSlots({
36989
37010
  filledSlots,
36990
- visualizationWidth,
37011
+ naturalWidth,
36991
37012
  fromSeconds,
36992
37013
  toSeconds,
36993
37014
  aspectRatio: aspectRatio.current
36994
37015
  });
36995
37016
  fillWithCachedFrames({
36996
37017
  ctx,
36997
- visualizationWidth,
37018
+ naturalWidth,
36998
37019
  filledSlots,
36999
37020
  src,
37000
37021
  segmentDuration: toSeconds - fromSeconds,
@@ -37019,7 +37040,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
37019
37040
  filledSlots,
37020
37041
  fromSeconds,
37021
37042
  toSeconds,
37022
- visualizationWidth,
37043
+ naturalWidth,
37023
37044
  aspectRatio: aspectRatio.current
37024
37045
  });
37025
37046
  return Array.from(filledSlots.keys()).map((timestamp) => timestamp / WEBCODECS_TIMESCALE);
@@ -37051,13 +37072,13 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
37051
37072
  filledSlots,
37052
37073
  fromSeconds,
37053
37074
  toSeconds,
37054
- visualizationWidth,
37075
+ naturalWidth,
37055
37076
  aspectRatio: aspectRatio.current
37056
37077
  });
37057
37078
  fillFrameWhereItFits({
37058
37079
  ctx,
37059
37080
  filledSlots,
37060
- visualizationWidth,
37081
+ visualizationWidth: naturalWidth,
37061
37082
  frame: transformed,
37062
37083
  segmentDuration: toSeconds - fromSeconds,
37063
37084
  fromSeconds
@@ -37068,7 +37089,7 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
37068
37089
  }).then(() => {
37069
37090
  fillWithCachedFrames({
37070
37091
  ctx,
37071
- visualizationWidth,
37092
+ naturalWidth,
37072
37093
  filledSlots,
37073
37094
  src,
37074
37095
  segmentDuration: toSeconds - fromSeconds,
@@ -37083,7 +37104,16 @@ var TimelineVideoInfo = ({ src, visualizationWidth, startFrom, durationInFrames
37083
37104
  controller.abort();
37084
37105
  current.removeChild(canvas);
37085
37106
  };
37086
- }, [durationInFrames, error, fps, src, startFrom, visualizationWidth]);
37107
+ }, [
37108
+ durationInFrames,
37109
+ error,
37110
+ fps,
37111
+ naturalWidth,
37112
+ playbackRate,
37113
+ src,
37114
+ trimBefore,
37115
+ visualizationWidth
37116
+ ]);
37087
37117
  return /* @__PURE__ */ jsx201("div", {
37088
37118
  ref,
37089
37119
  style: containerStyle3
@@ -37118,7 +37148,7 @@ var Inner4 = ({ s, windowWidth }) => {
37118
37148
  const isInRange = relativeFrame >= 0 && relativeFrame < s.duration;
37119
37149
  const isPremounting = relativeFrameWithPremount >= 0 && relativeFrameWithPremount < s.duration && !isInRange;
37120
37150
  const isPostmounting = relativeFrameWithPostmount >= 0 && relativeFrameWithPostmount < (s.postmountDisplay ?? 0) && !isInRange;
37121
- const { marginLeft, width: width2, premountWidth, postmountWidth } = useMemo107(() => {
37151
+ const { marginLeft, width: width2, naturalWidth, premountWidth, postmountWidth } = useMemo107(() => {
37122
37152
  return getTimelineSequenceLayout({
37123
37153
  durationInFrames: s.loopDisplay ? s.loopDisplay.durationInFrames * s.loopDisplay.numberOfTimes : s.duration,
37124
37154
  startFrom: s.loopDisplay ? s.from + s.loopDisplay.startOffset : s.from,
@@ -37192,8 +37222,10 @@ var Inner4 = ({ s, windowWidth }) => {
37192
37222
  s.type === "video" ? /* @__PURE__ */ jsx202(TimelineVideoInfo, {
37193
37223
  src: s.src,
37194
37224
  visualizationWidth: width2,
37195
- startFrom: s.startMediaFrom,
37196
- durationInFrames: s.duration
37225
+ naturalWidth,
37226
+ trimBefore: s.startMediaFrom,
37227
+ durationInFrames: s.duration,
37228
+ playbackRate: s.playbackRate
37197
37229
  }) : null,
37198
37230
  s.loopDisplay === undefined ? null : /* @__PURE__ */ jsx202(LoopedTimelineIndicator, {
37199
37231
  loops: s.loopDisplay.numberOfTimes
@@ -208,7 +208,7 @@ var renderContent = (Root) => {
208
208
  renderToDOM(/* @__PURE__ */ jsx("div", {
209
209
  children: /* @__PURE__ */ jsx(DelayedSpinner, {})
210
210
  }));
211
- import("./chunk-wg48cezw.js").then(({ StudioInternals }) => {
211
+ import("./chunk-4153e552.js").then(({ StudioInternals }) => {
212
212
  window.remotion_isStudio = true;
213
213
  window.remotion_isReadOnlyStudio = true;
214
214
  window.remotion_inputProps = "{}";
@@ -12,6 +12,7 @@ export declare const getTimelineSequenceLayout: ({ durationInFrames, startFrom,
12
12
  }) => {
13
13
  marginLeft: number;
14
14
  width: number;
15
+ naturalWidth: number;
15
16
  premountWidth: number | null;
16
17
  postmountWidth: number | null;
17
18
  };
@@ -15,13 +15,17 @@ const getTimelineSequenceLayout = ({ durationInFrames, startFrom, maxMediaDurati
15
15
  const maxMediaSequenceDuration = (maxMediaDuration !== null && maxMediaDuration !== void 0 ? maxMediaDuration : Infinity) - startFromMedia;
16
16
  const lastFrame = ((_a = video.durationInFrames) !== null && _a !== void 0 ? _a : 1) - 1;
17
17
  let spatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1, lastFrame - startFrom);
18
+ // Unclipped spatial duration: without the lastFrame - startFrom constraint
19
+ let naturalSpatialDuration = Math.min(maxMediaSequenceDuration, durationInFrames - 1);
18
20
  const shouldAddHalfAFrameAtEnd = startFrom + durationInFrames < lastFrame;
19
21
  const shouldAddHalfAFrameAtStart = startFrom > 0;
20
22
  if (shouldAddHalfAFrameAtEnd) {
21
23
  spatialDuration += 0.5;
24
+ naturalSpatialDuration += 0.5;
22
25
  }
23
26
  if (shouldAddHalfAFrameAtStart) {
24
27
  spatialDuration += 0.5;
28
+ naturalSpatialDuration += 0.5;
25
29
  }
26
30
  const startFromWithOffset = shouldAddHalfAFrameAtStart
27
31
  ? startFrom - 0.5
@@ -38,6 +42,13 @@ const getTimelineSequenceLayout = ({ durationInFrames, startFrom, maxMediaDurati
38
42
  spatialDuration,
39
43
  windowWidth,
40
44
  });
45
+ const naturalWidth = getWidthOfTrack({
46
+ durationInFrames,
47
+ lastFrame,
48
+ nonNegativeMarginLeft,
49
+ spatialDuration: naturalSpatialDuration,
50
+ windowWidth,
51
+ });
41
52
  const premountWidth = premountDisplay
42
53
  ? getWidthOfTrack({
43
54
  durationInFrames: premountDisplay,
@@ -59,6 +70,7 @@ const getTimelineSequenceLayout = ({ durationInFrames, startFrom, maxMediaDurati
59
70
  return {
60
71
  marginLeft: Math.max(marginLeft, 0) - (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0),
61
72
  width: width + (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0) + (postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0),
73
+ naturalWidth: naturalWidth + (premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0) + (postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0),
62
74
  premountWidth,
63
75
  postmountWidth,
64
76
  };
@@ -52,6 +52,9 @@ const useMaxMediaDuration = (s, fps) => {
52
52
  input.dispose();
53
53
  };
54
54
  }, [src, fps]);
55
+ if (maxMediaDuration !== null && (s.type === 'audio' || s.type === 'video')) {
56
+ return maxMediaDuration / s.playbackRate;
57
+ }
55
58
  return maxMediaDuration;
56
59
  };
57
60
  exports.useMaxMediaDuration = useMaxMediaDuration;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio"
4
4
  },
5
5
  "name": "@remotion/studio",
6
- "version": "4.0.420",
6
+ "version": "4.0.422",
7
7
  "description": "APIs for interacting with the Remotion Studio",
8
8
  "main": "dist",
9
9
  "sideEffects": false,
@@ -25,13 +25,13 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "semver": "7.5.3",
28
- "remotion": "4.0.420",
29
- "@remotion/player": "4.0.420",
30
- "@remotion/media-utils": "4.0.420",
31
- "@remotion/renderer": "4.0.420",
32
- "@remotion/web-renderer": "4.0.420",
33
- "@remotion/studio-shared": "4.0.420",
34
- "@remotion/zod-types": "4.0.420",
28
+ "remotion": "4.0.422",
29
+ "@remotion/player": "4.0.422",
30
+ "@remotion/media-utils": "4.0.422",
31
+ "@remotion/renderer": "4.0.422",
32
+ "@remotion/web-renderer": "4.0.422",
33
+ "@remotion/studio-shared": "4.0.422",
34
+ "@remotion/zod-types": "4.0.422",
35
35
  "mediabunny": "1.29.0",
36
36
  "memfs": "3.4.3",
37
37
  "source-map": "0.7.3",
@@ -42,7 +42,7 @@
42
42
  "react": "19.2.3",
43
43
  "react-dom": "19.2.3",
44
44
  "@types/semver": "^7.3.4",
45
- "@remotion/eslint-config-internal": "4.0.420",
45
+ "@remotion/eslint-config-internal": "4.0.422",
46
46
  "eslint": "9.19.0"
47
47
  },
48
48
  "publishConfig": {