@remotion/webcodecs 4.0.241 → 4.0.242

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.
@@ -78,11 +78,6 @@ const createAudioDecoder = ({ onFrame, onError, signal, config, logLevel, track,
78
78
  let queue = Promise.resolve();
79
79
  return {
80
80
  processSample: (sample) => {
81
- // In example.avi, we have samples with 0 data
82
- // Chrome fails on these
83
- if (sample.data.length === 0) {
84
- return queue;
85
- }
86
81
  queue = queue.then(() => processSample(sample));
87
82
  return queue;
88
83
  },
@@ -1,9 +1,10 @@
1
- import type { MediaParserVideoCodec, ParseMediaContainer } from '@remotion/media-parser';
1
+ import type { ParseMediaContainer, VideoTrack } from '@remotion/media-parser';
2
2
  import type { ConvertMediaContainer } from './get-available-containers';
3
- export declare const canCopyVideoTrack: ({ inputCodec, outputContainer, inputRotation, rotationToApply, inputContainer, }: {
3
+ import type { ResizeOperation } from './resizing/mode';
4
+ export declare const canCopyVideoTrack: ({ outputContainer, rotationToApply, inputContainer, resizeOperation, inputTrack, }: {
4
5
  inputContainer: ParseMediaContainer;
5
- inputCodec: MediaParserVideoCodec;
6
- inputRotation: number;
6
+ inputTrack: VideoTrack;
7
7
  rotationToApply: number;
8
8
  outputContainer: ConvertMediaContainer;
9
+ resizeOperation: ResizeOperation | null;
9
10
  }) => boolean;
@@ -1,17 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.canCopyVideoTrack = void 0;
4
- const rotate_video_frame_1 = require("./rotate-video-frame");
5
- const canCopyVideoTrack = ({ inputCodec, outputContainer, inputRotation, rotationToApply, inputContainer, }) => {
6
- if ((0, rotate_video_frame_1.normalizeVideoRotation)(inputRotation) !==
7
- (0, rotate_video_frame_1.normalizeVideoRotation)(rotationToApply)) {
4
+ const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
5
+ const rotation_1 = require("./rotation");
6
+ const canCopyVideoTrack = ({ outputContainer, rotationToApply, inputContainer, resizeOperation, inputTrack, }) => {
7
+ if ((0, rotate_and_resize_video_frame_1.normalizeVideoRotation)(inputTrack.rotation) !==
8
+ (0, rotate_and_resize_video_frame_1.normalizeVideoRotation)(rotationToApply)) {
9
+ return false;
10
+ }
11
+ const newDimensions = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
12
+ height: inputTrack.height,
13
+ resizeOperation,
14
+ rotation: rotationToApply,
15
+ videoCodec: inputTrack.codecWithoutConfig,
16
+ width: inputTrack.width,
17
+ });
18
+ if (newDimensions.height !== inputTrack.height ||
19
+ newDimensions.width !== inputTrack.width) {
8
20
  return false;
9
21
  }
10
22
  if (outputContainer === 'webm') {
11
- return inputCodec === 'vp8' || inputCodec === 'vp9';
23
+ return (inputTrack.codecWithoutConfig === 'vp8' ||
24
+ inputTrack.codecWithoutConfig === 'vp9');
12
25
  }
13
26
  if (outputContainer === 'mp4') {
14
- return (inputCodec === 'h264' &&
27
+ return (inputTrack.codecWithoutConfig === 'h264' &&
15
28
  (inputContainer === 'mp4' || inputContainer === 'avi'));
16
29
  }
17
30
  if (outputContainer === 'wav') {
@@ -33,6 +33,8 @@ const convertEncodedChunk = (chunk, trackId) => {
33
33
  cts: chunk.timestamp,
34
34
  dts: chunk.timestamp,
35
35
  trackId,
36
+ offset: 0,
37
+ timescale: 1000000,
36
38
  };
37
39
  };
38
40
  exports.convertEncodedChunk = convertEncodedChunk;
@@ -8,6 +8,7 @@ import type { ConvertMediaContainer } from './get-available-containers';
8
8
  import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
9
9
  import { type ConvertMediaOnAudioTrackHandler } from './on-audio-track-handler';
10
10
  import { type ConvertMediaOnVideoTrackHandler } from './on-video-track-handler';
11
+ import type { ResizeOperation } from './resizing/mode';
11
12
  export type ConvertMediaProgress = {
12
13
  decodedVideoFrames: number;
13
14
  decodedAudioFrames: number;
@@ -28,7 +29,7 @@ export type ConvertMediaOnVideoFrame = (options: {
28
29
  frame: VideoFrame;
29
30
  track: VideoTrack;
30
31
  }) => Promise<VideoFrame> | VideoFrame;
31
- export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src, onVideoFrame, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, signal: userPassedAbortSignal, onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel, writer, progressIntervalInMs, rotate, apiKey, ...more }: {
32
+ export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src, onVideoFrame, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, signal: userPassedAbortSignal, onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel, writer, progressIntervalInMs, rotate, apiKey, resize, ...more }: {
32
33
  src: ParseMediaOptions<F>["src"];
33
34
  container: ConvertMediaContainer;
34
35
  onVideoFrame?: ConvertMediaOnVideoFrame;
@@ -43,5 +44,6 @@ export declare const convertMedia: <F extends Options<ParseMediaFields>>({ src,
43
44
  writer?: WriterInterface;
44
45
  progressIntervalInMs?: number;
45
46
  rotate?: number;
47
+ resize?: ResizeOperation;
46
48
  apiKey?: string | null;
47
49
  } & ParseMediaDynamicOptions<F>) => Promise<ConvertMediaResult>;
@@ -19,7 +19,7 @@ const on_video_track_1 = require("./on-video-track");
19
19
  const select_container_creator_1 = require("./select-container-creator");
20
20
  const send_telemetry_event_1 = require("./send-telemetry-event");
21
21
  const throttled_state_update_1 = require("./throttled-state-update");
22
- const convertMedia = async function ({ src, onVideoFrame, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, signal: userPassedAbortSignal, onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel = 'info', writer, progressIntervalInMs, rotate, apiKey, ...more }) {
22
+ const convertMedia = async function ({ src, onVideoFrame, onProgress: onProgressDoNotCallDirectly, audioCodec, container, videoCodec, signal: userPassedAbortSignal, onAudioTrack: userAudioResolver, onVideoTrack: userVideoResolver, reader, fields, logLevel = 'info', writer, progressIntervalInMs, rotate, apiKey, resize, ...more }) {
23
23
  var _a, _b;
24
24
  if (userPassedAbortSignal === null || userPassedAbortSignal === void 0 ? void 0 : userPassedAbortSignal.aborted) {
25
25
  return Promise.reject(new error_cause_1.default('Aborted'));
@@ -92,6 +92,7 @@ const convertMedia = async function ({ src, onVideoFrame, onProgress: onProgress
92
92
  outputContainer: container,
93
93
  rotate: rotate !== null && rotate !== void 0 ? rotate : 0,
94
94
  progress: progressTracker,
95
+ resizeOperation: resize !== null && resize !== void 0 ? resize : null,
95
96
  });
96
97
  const onAudioTrack = (0, on_audio_track_1.makeAudioTrackHandler)({
97
98
  abortConversion,
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.defaultOnVideoTrackHandler = void 0;
4
4
  const media_parser_1 = require("@remotion/media-parser");
5
5
  const can_reencode_video_track_1 = require("./can-reencode-video-track");
6
- const defaultOnVideoTrackHandler = async ({ track, defaultVideoCodec, logLevel, rotate, canCopyTrack, }) => {
6
+ const defaultOnVideoTrackHandler = async ({ track, defaultVideoCodec, logLevel, rotate, canCopyTrack, resizeOperation, }) => {
7
7
  if (canCopyTrack) {
8
8
  media_parser_1.MediaParserInternals.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
9
9
  return Promise.resolve({ type: 'copy' });
@@ -21,7 +21,8 @@ const defaultOnVideoTrackHandler = async ({ track, defaultVideoCodec, logLevel,
21
21
  return Promise.resolve({
22
22
  type: 'reencode',
23
23
  videoCodec: defaultVideoCodec,
24
- rotation: rotate - track.rotation,
24
+ rotate: rotate - track.rotation,
25
+ resize: resizeOperation,
25
26
  });
26
27
  }
27
28
  media_parser_1.MediaParserInternals.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
@@ -92,38 +92,161 @@ var require_dist = __commonJS((exports) => {
92
92
  } });
93
93
  });
94
94
 
95
+ // src/resizing/calculate-new-size.ts
96
+ var ensureMultipleOfTwo = ({
97
+ dimensions,
98
+ needsToBeMultipleOfTwo
99
+ }) => {
100
+ if (!needsToBeMultipleOfTwo) {
101
+ return dimensions;
102
+ }
103
+ return {
104
+ width: Math.floor(dimensions.width / 2) * 2,
105
+ height: Math.floor(dimensions.height / 2) * 2
106
+ };
107
+ };
108
+ var calculateNewSizeAfterResizing = ({
109
+ dimensions,
110
+ resizeOperation,
111
+ videoCodec
112
+ }) => {
113
+ const needsToBeMultipleOfTwo = videoCodec === "h264";
114
+ if (resizeOperation === null) {
115
+ return ensureMultipleOfTwo({
116
+ dimensions,
117
+ needsToBeMultipleOfTwo
118
+ });
119
+ }
120
+ if (resizeOperation.mode === "width") {
121
+ return ensureMultipleOfTwo({
122
+ dimensions: {
123
+ width: resizeOperation.width,
124
+ height: Math.round(resizeOperation.width / dimensions.width * dimensions.height)
125
+ },
126
+ needsToBeMultipleOfTwo
127
+ });
128
+ }
129
+ if (resizeOperation.mode === "height") {
130
+ return ensureMultipleOfTwo({
131
+ dimensions: {
132
+ width: Math.round(resizeOperation.height / dimensions.height * dimensions.width),
133
+ height: resizeOperation.height
134
+ },
135
+ needsToBeMultipleOfTwo
136
+ });
137
+ }
138
+ if (resizeOperation.mode === "max-height") {
139
+ const height = Math.min(dimensions.height, resizeOperation.maxHeight);
140
+ return ensureMultipleOfTwo({
141
+ dimensions: {
142
+ width: Math.round(height / dimensions.height * dimensions.width),
143
+ height
144
+ },
145
+ needsToBeMultipleOfTwo
146
+ });
147
+ }
148
+ if (resizeOperation.mode === "max-width") {
149
+ const width = Math.min(dimensions.width, resizeOperation.maxWidth);
150
+ return ensureMultipleOfTwo({
151
+ dimensions: {
152
+ width,
153
+ height: Math.round(width / dimensions.width * dimensions.height)
154
+ },
155
+ needsToBeMultipleOfTwo
156
+ });
157
+ }
158
+ if (resizeOperation.mode === "max-height-width") {
159
+ const height = Math.min(dimensions.height, resizeOperation.maxHeight);
160
+ const width = Math.min(dimensions.width, resizeOperation.maxWidth);
161
+ const scale = Math.min(width / dimensions.width, height / dimensions.height);
162
+ const actualWidth = Math.round(dimensions.width * scale);
163
+ const actualHeight = Math.round(dimensions.height * scale);
164
+ return ensureMultipleOfTwo({
165
+ dimensions: {
166
+ height: actualHeight,
167
+ width: actualWidth
168
+ },
169
+ needsToBeMultipleOfTwo
170
+ });
171
+ }
172
+ if (resizeOperation.mode === "scale") {
173
+ if (resizeOperation.scale <= 0) {
174
+ throw new Error("Scale must be greater than 0");
175
+ }
176
+ const width = Math.round(dimensions.width * resizeOperation.scale);
177
+ const height = Math.round(dimensions.height * resizeOperation.scale);
178
+ return ensureMultipleOfTwo({
179
+ dimensions: {
180
+ width,
181
+ height
182
+ },
183
+ needsToBeMultipleOfTwo
184
+ });
185
+ }
186
+ throw new Error("Invalid resizing mode " + resizeOperation);
187
+ };
188
+
95
189
  // src/rotation.ts
96
- var calculateNewDimensionsFromDimensions = ({
97
- width,
190
+ var calculateNewDimensionsFromRotate = ({
98
191
  height,
192
+ width,
99
193
  rotation
100
194
  }) => {
101
- const switchDimensions = rotation % 90 === 0 && rotation % 180 !== 0;
195
+ const normalized = normalizeVideoRotation(rotation);
196
+ const switchDimensions = normalized % 90 === 0 && normalized % 180 !== 0;
102
197
  const newHeight = switchDimensions ? width : height;
103
198
  const newWidth = switchDimensions ? height : width;
104
- return { height: newHeight, width: newWidth };
199
+ return {
200
+ height: newHeight,
201
+ width: newWidth
202
+ };
203
+ };
204
+ var calculateNewDimensionsFromRotateAndScale = ({
205
+ width,
206
+ height,
207
+ rotation,
208
+ resizeOperation,
209
+ videoCodec
210
+ }) => {
211
+ const { height: newHeight, width: newWidth } = calculateNewDimensionsFromRotate({
212
+ height,
213
+ rotation,
214
+ width
215
+ });
216
+ return calculateNewSizeAfterResizing({
217
+ dimensions: { height: newHeight, width: newWidth },
218
+ resizeOperation,
219
+ videoCodec
220
+ });
105
221
  };
106
222
 
107
- // src/rotate-video-frame.ts
223
+ // src/rotate-and-resize-video-frame.ts
108
224
  var normalizeVideoRotation = (rotation) => {
109
225
  return (rotation % 360 + 360) % 360;
110
226
  };
111
- var rotateVideoFrame = ({
227
+ var rotateAndResizeVideoFrame = ({
112
228
  frame,
113
- rotation
229
+ rotation,
230
+ videoCodec,
231
+ resizeOperation
114
232
  }) => {
115
233
  const normalized = (rotation % 360 + 360) % 360;
116
- if (normalized % 360 === 0) {
234
+ if (normalized === 0 && resizeOperation === null) {
117
235
  return frame;
118
236
  }
119
237
  if (normalized % 90 !== 0) {
120
238
  throw new Error("Only 90 degree rotations are supported");
121
239
  }
122
- const { height, width } = calculateNewDimensionsFromDimensions({
240
+ const { height, width } = calculateNewDimensionsFromRotateAndScale({
123
241
  height: frame.displayHeight,
124
242
  width: frame.displayWidth,
125
- rotation
243
+ rotation,
244
+ videoCodec,
245
+ resizeOperation
126
246
  });
247
+ if (normalized === 0 && height === frame.displayHeight && width === frame.displayWidth) {
248
+ return frame;
249
+ }
127
250
  const canvas = new OffscreenCanvas(width, height);
128
251
  const ctx = canvas.getContext("2d");
129
252
  if (!ctx) {
@@ -138,7 +261,17 @@ var rotateVideoFrame = ({
138
261
  } else if (normalized === 270) {
139
262
  ctx.translate(0, height);
140
263
  }
141
- ctx.rotate(normalized * (Math.PI / 180));
264
+ if (normalized !== 0) {
265
+ ctx.rotate(normalized * (Math.PI / 180));
266
+ }
267
+ if (frame.displayHeight !== height || frame.displayWidth !== width) {
268
+ const dimensionsAfterRotate = calculateNewDimensionsFromRotate({
269
+ height: frame.displayHeight,
270
+ rotation,
271
+ width: frame.displayWidth
272
+ });
273
+ ctx.scale(width / dimensionsAfterRotate.width, height / dimensionsAfterRotate.height);
274
+ }
142
275
  ctx.drawImage(frame, 0, 0);
143
276
  return new VideoFrame(canvas, {
144
277
  displayHeight: height,
@@ -204,7 +337,7 @@ import { MediaParserInternals as MediaParserInternals2 } from "@remotion/media-p
204
337
  var makeTimeoutPromise = (label, ms) => {
205
338
  const { promise, reject, resolve } = MediaParserInternals2.withResolvers();
206
339
  const timeout = setTimeout(() => {
207
- reject(new Error(`${label} (timed out after ${ms}ms)`));
340
+ reject(new Error(`${label()} (timed out after ${ms}ms)`));
208
341
  }, ms);
209
342
  return {
210
343
  timeoutPromise: promise,
@@ -288,14 +421,14 @@ var makeIoSynchronizer = ({
288
421
  minimumProgress,
289
422
  signal
290
423
  }) => {
291
- const { timeoutPromise, clear } = makeTimeoutPromise([
292
- `Waited too long for ${label}:`,
424
+ const { timeoutPromise, clear } = makeTimeoutPromise(() => [
425
+ `Waited too long for ${label} to finish:`,
293
426
  `${getUnemittedItems()} unemitted items`,
294
- `${getUnprocessed()} unprocessed items`,
295
- `minimum progress ${minimumProgress}`,
427
+ `${getUnprocessed()} unprocessed items: ${JSON.stringify(_unprocessed)}`,
296
428
  `smallest progress: ${progress.getSmallestProgress()}`,
297
429
  `inputs: ${JSON.stringify(inputs)}`,
298
- `last output: ${lastOutput}`
430
+ `last output: ${lastOutput}`,
431
+ `wanted: ${unemitted} unemitted items, ${unprocessed} unprocessed items, minimum progress ${minimumProgress}`
299
432
  ].join(`
300
433
  `), 1e4);
301
434
  signal.addEventListener("abort", clear);
@@ -420,9 +553,6 @@ var createAudioDecoder = ({
420
553
  let queue = Promise.resolve();
421
554
  return {
422
555
  processSample: (sample) => {
423
- if (sample.data.length === 0) {
424
- return queue;
425
- }
426
556
  queue = queue.then(() => processSample(sample));
427
557
  return queue;
428
558
  },
@@ -589,20 +719,30 @@ var canCopyAudioTrack = ({
589
719
  };
590
720
  // src/can-copy-video-track.ts
591
721
  var canCopyVideoTrack = ({
592
- inputCodec,
593
722
  outputContainer,
594
- inputRotation,
595
723
  rotationToApply,
596
- inputContainer
724
+ inputContainer,
725
+ resizeOperation,
726
+ inputTrack
597
727
  }) => {
598
- if (normalizeVideoRotation(inputRotation) !== normalizeVideoRotation(rotationToApply)) {
728
+ if (normalizeVideoRotation(inputTrack.rotation) !== normalizeVideoRotation(rotationToApply)) {
729
+ return false;
730
+ }
731
+ const newDimensions = calculateNewDimensionsFromRotateAndScale({
732
+ height: inputTrack.height,
733
+ resizeOperation,
734
+ rotation: rotationToApply,
735
+ videoCodec: inputTrack.codecWithoutConfig,
736
+ width: inputTrack.width
737
+ });
738
+ if (newDimensions.height !== inputTrack.height || newDimensions.width !== inputTrack.width) {
599
739
  return false;
600
740
  }
601
741
  if (outputContainer === "webm") {
602
- return inputCodec === "vp8" || inputCodec === "vp9";
742
+ return inputTrack.codecWithoutConfig === "vp8" || inputTrack.codecWithoutConfig === "vp9";
603
743
  }
604
744
  if (outputContainer === "mp4") {
605
- return inputCodec === "h264" && (inputContainer === "mp4" || inputContainer === "avi");
745
+ return inputTrack.codecWithoutConfig === "h264" && (inputContainer === "mp4" || inputContainer === "avi");
606
746
  }
607
747
  if (outputContainer === "wav") {
608
748
  return false;
@@ -868,7 +1008,9 @@ var convertEncodedChunk = (chunk, trackId) => {
868
1008
  type: chunk.type,
869
1009
  cts: chunk.timestamp,
870
1010
  dts: chunk.timestamp,
871
- trackId
1011
+ trackId,
1012
+ offset: 0,
1013
+ timescale: 1e6
872
1014
  };
873
1015
  };
874
1016
 
@@ -1086,7 +1228,8 @@ var defaultOnVideoTrackHandler = async ({
1086
1228
  defaultVideoCodec,
1087
1229
  logLevel,
1088
1230
  rotate,
1089
- canCopyTrack
1231
+ canCopyTrack,
1232
+ resizeOperation
1090
1233
  }) => {
1091
1234
  if (canCopyTrack) {
1092
1235
  MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
@@ -1105,7 +1248,8 @@ var defaultOnVideoTrackHandler = async ({
1105
1248
  return Promise.resolve({
1106
1249
  type: "reencode",
1107
1250
  videoCodec: defaultVideoCodec,
1108
- rotation: rotate - track.rotation
1251
+ rotate: rotate - track.rotation,
1252
+ resize: resizeOperation
1109
1253
  });
1110
1254
  }
1111
1255
  MediaParserInternals7.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
@@ -1171,11 +1315,14 @@ var onFrame = async ({
1171
1315
  videoEncoder,
1172
1316
  track,
1173
1317
  outputCodec,
1174
- rotation
1318
+ rotation,
1319
+ resizeOperation
1175
1320
  }) => {
1176
- const rotated = rotateVideoFrame({
1321
+ const rotated = rotateAndResizeVideoFrame({
1177
1322
  rotation,
1178
- frame: unrotatedFrame
1323
+ frame: unrotatedFrame,
1324
+ resizeOperation,
1325
+ videoCodec: outputCodec
1179
1326
  });
1180
1327
  if (unrotatedFrame !== rotated) {
1181
1328
  unrotatedFrame.close();
@@ -1406,17 +1553,18 @@ var makeVideoTrackHandler = ({
1406
1553
  logLevel,
1407
1554
  outputContainer,
1408
1555
  rotate,
1409
- progress
1556
+ progress,
1557
+ resizeOperation
1410
1558
  }) => async ({ track, container: inputContainer }) => {
1411
1559
  if (controller.signal.aborted) {
1412
1560
  throw new error_cause_default("Aborted");
1413
1561
  }
1414
1562
  const canCopyTrack = canCopyVideoTrack({
1415
- inputCodec: track.codecWithoutConfig,
1416
1563
  inputContainer,
1417
- inputRotation: track.rotation,
1418
1564
  outputContainer,
1419
- rotationToApply: rotate
1565
+ rotationToApply: rotate,
1566
+ inputTrack: track,
1567
+ resizeOperation
1420
1568
  });
1421
1569
  const videoOperation = await (onVideoTrack ?? defaultOnVideoTrackHandler)({
1422
1570
  track,
@@ -1425,7 +1573,8 @@ var makeVideoTrackHandler = ({
1425
1573
  outputContainer,
1426
1574
  rotate,
1427
1575
  inputContainer,
1428
- canCopyTrack
1576
+ canCopyTrack,
1577
+ resizeOperation
1429
1578
  });
1430
1579
  if (videoOperation.type === "drop") {
1431
1580
  return null;
@@ -1463,10 +1612,12 @@ var makeVideoTrackHandler = ({
1463
1612
  throw new error_cause_default(`Video track with ID ${track.trackId} could not be resolved with a valid operation. Received ${JSON.stringify(videoOperation)}, but must be either "copy", "reencode", "drop" or "fail"`);
1464
1613
  }
1465
1614
  const rotation = (videoOperation.rotate ?? rotate) - track.rotation;
1466
- const { height: newHeight, width: newWidth } = calculateNewDimensionsFromDimensions({
1615
+ const { height: newHeight, width: newWidth } = calculateNewDimensionsFromRotateAndScale({
1467
1616
  width: track.codedWidth,
1468
1617
  height: track.codedHeight,
1469
- rotation
1618
+ rotation,
1619
+ videoCodec: videoOperation.videoCodec,
1620
+ resizeOperation: videoOperation.resize ?? null
1470
1621
  });
1471
1622
  const videoEncoderConfig = await getVideoEncoderConfig({
1472
1623
  codec: videoOperation.videoCodec,
@@ -1528,7 +1679,8 @@ var makeVideoTrackHandler = ({
1528
1679
  videoEncoder,
1529
1680
  onVideoFrame,
1530
1681
  outputCodec: videoOperation.videoCodec,
1531
- rotation
1682
+ rotation,
1683
+ resizeOperation: videoOperation.resize ?? null
1532
1684
  });
1533
1685
  },
1534
1686
  onError: (err) => {
@@ -1657,6 +1809,7 @@ var convertMedia = async function({
1657
1809
  progressIntervalInMs,
1658
1810
  rotate,
1659
1811
  apiKey,
1812
+ resize,
1660
1813
  ...more
1661
1814
  }) {
1662
1815
  if (userPassedAbortSignal?.aborted) {
@@ -1727,7 +1880,8 @@ var convertMedia = async function({
1727
1880
  logLevel,
1728
1881
  outputContainer: container,
1729
1882
  rotate: rotate ?? 0,
1730
- progress: progressTracker
1883
+ progress: progressTracker,
1884
+ resizeOperation: resize ?? null
1731
1885
  });
1732
1886
  const onAudioTrack = makeAudioTrackHandler({
1733
1887
  abortConversion,
@@ -1833,8 +1987,9 @@ var getAvailableVideoCodecs = ({
1833
1987
  };
1834
1988
  // src/index.ts
1835
1989
  var WebCodecsInternals = {
1836
- rotateVideoFrame,
1837
- normalizeVideoRotation
1990
+ rotateAndResizeVideoFrame,
1991
+ normalizeVideoRotation,
1992
+ calculateNewDimensionsFromDimensions: calculateNewDimensionsFromRotateAndScale
1838
1993
  };
1839
1994
  setRemotionImported();
1840
1995
  export {
package/dist/index.d.ts CHANGED
@@ -14,12 +14,22 @@ export { getDefaultAudioCodec } from './get-default-audio-codec';
14
14
  export { getDefaultVideoCodec } from './get-default-video-codec';
15
15
  export { AudioOperation, ConvertMediaOnAudioTrackHandler, } from './on-audio-track-handler';
16
16
  export { ConvertMediaOnVideoTrackHandler, VideoOperation, } from './on-video-track-handler';
17
+ export type { ResizeOperation } from './resizing/mode';
17
18
  export { createVideoDecoder, WebCodecsVideoDecoder } from './video-decoder';
18
19
  export { createVideoEncoder, WebCodecsVideoEncoder } from './video-encoder';
19
20
  export declare const WebCodecsInternals: {
20
- rotateVideoFrame: ({ frame, rotation, }: {
21
+ rotateAndResizeVideoFrame: ({ frame, rotation, videoCodec, resizeOperation, }: {
21
22
  frame: VideoFrame;
22
23
  rotation: number;
24
+ videoCodec: import("./get-available-video-codecs").ConvertMediaVideoCodec;
25
+ resizeOperation: import("./resizing/mode").ResizeOperation | null;
23
26
  }) => VideoFrame;
24
27
  normalizeVideoRotation: (rotation: number) => number;
28
+ calculateNewDimensionsFromDimensions: ({ width, height, rotation, resizeOperation, videoCodec, }: {
29
+ width: number;
30
+ height: number;
31
+ rotation: number;
32
+ resizeOperation: import("./resizing/mode").ResizeOperation | null;
33
+ videoCodec: import("./get-available-video-codecs").ConvertMediaVideoCodec | import("@remotion/media-parser").MediaParserVideoCodec;
34
+ }) => import("./resizing/mode").Dimensions;
25
35
  };
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.WebCodecsInternals = exports.createVideoEncoder = exports.createVideoDecoder = exports.getDefaultVideoCodec = exports.getDefaultAudioCodec = exports.getAvailableVideoCodecs = exports.getAvailableContainers = exports.getAvailableAudioCodecs = exports.defaultOnVideoTrackHandler = exports.defaultOnAudioTrackHandler = exports.convertMedia = exports.canReencodeVideoTrack = exports.canReencodeAudioTrack = exports.canCopyVideoTrack = exports.canCopyAudioTrack = exports.createAudioEncoder = exports.createAudioDecoder = void 0;
4
- const rotate_video_frame_1 = require("./rotate-video-frame");
4
+ const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
5
+ const rotation_1 = require("./rotation");
5
6
  const set_remotion_imported_1 = require("./set-remotion-imported");
6
7
  var audio_decoder_1 = require("./audio-decoder");
7
8
  Object.defineProperty(exports, "createAudioDecoder", { enumerable: true, get: function () { return audio_decoder_1.createAudioDecoder; } });
@@ -36,7 +37,8 @@ Object.defineProperty(exports, "createVideoDecoder", { enumerable: true, get: fu
36
37
  var video_encoder_1 = require("./video-encoder");
37
38
  Object.defineProperty(exports, "createVideoEncoder", { enumerable: true, get: function () { return video_encoder_1.createVideoEncoder; } });
38
39
  exports.WebCodecsInternals = {
39
- rotateVideoFrame: rotate_video_frame_1.rotateVideoFrame,
40
- normalizeVideoRotation: rotate_video_frame_1.normalizeVideoRotation,
40
+ rotateAndResizeVideoFrame: rotate_and_resize_video_frame_1.rotateAndResizeVideoFrame,
41
+ normalizeVideoRotation: rotate_and_resize_video_frame_1.normalizeVideoRotation,
42
+ calculateNewDimensionsFromDimensions: rotation_1.calculateNewDimensionsFromRotateAndScale,
41
43
  };
42
44
  (0, set_remotion_imported_1.setRemotionImported)();
@@ -69,14 +69,14 @@ const makeIoSynchronizer = ({ logLevel, label, progress, }) => {
69
69
  return promise;
70
70
  };
71
71
  const waitFor = async ({ unprocessed, unemitted, minimumProgress, signal, }) => {
72
- const { timeoutPromise, clear } = (0, make_timeout_promise_1.makeTimeoutPromise)([
73
- `Waited too long for ${label}:`,
72
+ const { timeoutPromise, clear } = (0, make_timeout_promise_1.makeTimeoutPromise)(() => [
73
+ `Waited too long for ${label} to finish:`,
74
74
  `${getUnemittedItems()} unemitted items`,
75
- `${getUnprocessed()} unprocessed items`,
76
- `minimum progress ${minimumProgress}`,
75
+ `${getUnprocessed()} unprocessed items: ${JSON.stringify(_unprocessed)}`,
77
76
  `smallest progress: ${progress.getSmallestProgress()}`,
78
77
  `inputs: ${JSON.stringify(inputs)}`,
79
78
  `last output: ${lastOutput}`,
79
+ `wanted: ${unemitted} unemitted items, ${unprocessed} unprocessed items, minimum progress ${minimumProgress}`,
80
80
  ].join('\n'), 10000);
81
81
  signal.addEventListener('abort', clear);
82
82
  await Promise.race([
@@ -1,4 +1,4 @@
1
- export declare const makeTimeoutPromise: (label: string, ms: number) => {
1
+ export declare const makeTimeoutPromise: (label: () => string, ms: number) => {
2
2
  timeoutPromise: Promise<void>;
3
3
  clear: () => void;
4
4
  };
@@ -5,7 +5,7 @@ const media_parser_1 = require("@remotion/media-parser");
5
5
  const makeTimeoutPromise = (label, ms) => {
6
6
  const { promise, reject, resolve } = media_parser_1.MediaParserInternals.withResolvers();
7
7
  const timeout = setTimeout(() => {
8
- reject(new Error(`${label} (timed out after ${ms}ms)`));
8
+ reject(new Error(`${label()} (timed out after ${ms}ms)`));
9
9
  }, ms);
10
10
  return {
11
11
  timeoutPromise: promise,
@@ -1,12 +1,14 @@
1
1
  import type { VideoTrack } from '@remotion/media-parser';
2
2
  import type { ConvertMediaOnVideoFrame } from './convert-media';
3
3
  import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
4
+ import type { ResizeOperation } from './resizing/mode';
4
5
  import type { WebCodecsVideoEncoder } from './video-encoder';
5
- export declare const onFrame: ({ frame: unrotatedFrame, onVideoFrame, videoEncoder, track, outputCodec, rotation, }: {
6
+ export declare const onFrame: ({ frame: unrotatedFrame, onVideoFrame, videoEncoder, track, outputCodec, rotation, resizeOperation, }: {
6
7
  frame: VideoFrame;
7
8
  onVideoFrame: ConvertMediaOnVideoFrame | null;
8
9
  videoEncoder: WebCodecsVideoEncoder;
9
10
  track: VideoTrack;
10
11
  outputCodec: ConvertMediaVideoCodec;
11
12
  rotation: number;
13
+ resizeOperation: ResizeOperation | null;
12
14
  }) => Promise<void>;
package/dist/on-frame.js CHANGED
@@ -3,12 +3,14 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.onFrame = void 0;
4
4
  const browser_quirks_1 = require("./browser-quirks");
5
5
  const convert_to_correct_videoframe_1 = require("./convert-to-correct-videoframe");
6
- const rotate_video_frame_1 = require("./rotate-video-frame");
7
- const onFrame = async ({ frame: unrotatedFrame, onVideoFrame, videoEncoder, track, outputCodec, rotation, }) => {
6
+ const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
7
+ const onFrame = async ({ frame: unrotatedFrame, onVideoFrame, videoEncoder, track, outputCodec, rotation, resizeOperation, }) => {
8
8
  var _a, _b;
9
- const rotated = (0, rotate_video_frame_1.rotateVideoFrame)({
9
+ const rotated = (0, rotate_and_resize_video_frame_1.rotateAndResizeVideoFrame)({
10
10
  rotation,
11
11
  frame: unrotatedFrame,
12
+ resizeOperation,
13
+ videoCodec: outputCodec,
12
14
  });
13
15
  if (unrotatedFrame !== rotated) {
14
16
  unrotatedFrame.close();
@@ -1,10 +1,12 @@
1
1
  import type { LogLevel, ParseMediaContainer, VideoTrack } from '@remotion/media-parser';
2
2
  import type { ConvertMediaContainer } from './get-available-containers';
3
3
  import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
4
+ import type { ResizeOperation } from './resizing/mode';
4
5
  export type VideoOperation = {
5
6
  type: 'reencode';
6
7
  videoCodec: ConvertMediaVideoCodec;
7
8
  rotate?: number;
9
+ resize?: ResizeOperation | null;
8
10
  } | {
9
11
  type: 'copy';
10
12
  } | {
@@ -18,6 +20,7 @@ export type ConvertMediaOnVideoTrackHandler = (options: {
18
20
  logLevel: LogLevel;
19
21
  outputContainer: ConvertMediaContainer;
20
22
  rotate: number;
23
+ resizeOperation: ResizeOperation | null;
21
24
  inputContainer: ParseMediaContainer;
22
25
  canCopyTrack: boolean;
23
26
  }) => VideoOperation | Promise<VideoOperation>;
@@ -4,8 +4,9 @@ import Error from './error-cause';
4
4
  import type { ConvertMediaContainer } from './get-available-containers';
5
5
  import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
6
6
  import type { ConvertMediaOnVideoTrackHandler } from './on-video-track-handler';
7
+ import type { ResizeOperation } from './resizing/mode';
7
8
  import type { ConvertMediaProgressFn } from './throttled-state-update';
8
- export declare const makeVideoTrackHandler: ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, progress, }: {
9
+ export declare const makeVideoTrackHandler: ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, progress, resizeOperation, }: {
9
10
  state: MediaFn;
10
11
  onVideoFrame: null | ConvertMediaOnVideoFrame;
11
12
  onMediaStateUpdate: null | ConvertMediaProgressFn;
@@ -17,4 +18,5 @@ export declare const makeVideoTrackHandler: ({ state, onVideoFrame, onMediaState
17
18
  outputContainer: ConvertMediaContainer;
18
19
  rotate: number;
19
20
  progress: ProgressTracker;
21
+ resizeOperation: ResizeOperation | null;
20
22
  }) => OnVideoTrack;
@@ -17,17 +17,17 @@ const video_decoder_1 = require("./video-decoder");
17
17
  const video_decoder_config_1 = require("./video-decoder-config");
18
18
  const video_encoder_1 = require("./video-encoder");
19
19
  const video_encoder_config_1 = require("./video-encoder-config");
20
- const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, progress, }) => async ({ track, container: inputContainer }) => {
21
- var _a;
20
+ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortConversion, controller, defaultVideoCodec, onVideoTrack, logLevel, outputContainer, rotate, progress, resizeOperation, }) => async ({ track, container: inputContainer }) => {
21
+ var _a, _b;
22
22
  if (controller.signal.aborted) {
23
23
  throw new error_cause_1.default('Aborted');
24
24
  }
25
25
  const canCopyTrack = (0, can_copy_video_track_1.canCopyVideoTrack)({
26
- inputCodec: track.codecWithoutConfig,
27
26
  inputContainer,
28
- inputRotation: track.rotation,
29
27
  outputContainer,
30
28
  rotationToApply: rotate,
29
+ inputTrack: track,
30
+ resizeOperation,
31
31
  });
32
32
  const videoOperation = await (onVideoTrack !== null && onVideoTrack !== void 0 ? onVideoTrack : default_on_video_track_handler_1.defaultOnVideoTrackHandler)({
33
33
  track,
@@ -37,6 +37,7 @@ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortC
37
37
  rotate,
38
38
  inputContainer,
39
39
  canCopyTrack,
40
+ resizeOperation,
40
41
  });
41
42
  if (videoOperation.type === 'drop') {
42
43
  return null;
@@ -74,10 +75,12 @@ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortC
74
75
  throw new error_cause_1.default(`Video track with ID ${track.trackId} could not be resolved with a valid operation. Received ${JSON.stringify(videoOperation)}, but must be either "copy", "reencode", "drop" or "fail"`);
75
76
  }
76
77
  const rotation = ((_a = videoOperation.rotate) !== null && _a !== void 0 ? _a : rotate) - track.rotation;
77
- const { height: newHeight, width: newWidth } = (0, rotation_1.calculateNewDimensionsFromDimensions)({
78
+ const { height: newHeight, width: newWidth } = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
78
79
  width: track.codedWidth,
79
80
  height: track.codedHeight,
80
81
  rotation,
82
+ videoCodec: videoOperation.videoCodec,
83
+ resizeOperation: (_b = videoOperation.resize) !== null && _b !== void 0 ? _b : null,
81
84
  });
82
85
  const videoEncoderConfig = await (0, video_encoder_config_1.getVideoEncoderConfig)({
83
86
  codec: videoOperation.videoCodec,
@@ -134,6 +137,7 @@ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortC
134
137
  const videoDecoder = (0, video_decoder_1.createVideoDecoder)({
135
138
  config: videoDecoderConfig,
136
139
  onFrame: async (frame) => {
140
+ var _a;
137
141
  await (0, on_frame_1.onFrame)({
138
142
  frame,
139
143
  track,
@@ -141,6 +145,7 @@ const makeVideoTrackHandler = ({ state, onVideoFrame, onMediaStateUpdate, abortC
141
145
  onVideoFrame,
142
146
  outputCodec: videoOperation.videoCodec,
143
147
  rotation,
148
+ resizeOperation: (_a = videoOperation.resize) !== null && _a !== void 0 ? _a : null,
144
149
  });
145
150
  },
146
151
  onError: (err) => {
@@ -0,0 +1,8 @@
1
+ import type { MediaParserVideoCodec } from '@remotion/media-parser';
2
+ import type { ConvertMediaVideoCodec } from '../get-available-video-codecs';
3
+ import type { Dimensions, ResizeOperation } from './mode';
4
+ export declare const calculateNewSizeAfterResizing: ({ dimensions, resizeOperation, videoCodec, }: {
5
+ dimensions: Dimensions;
6
+ resizeOperation: ResizeOperation | null;
7
+ videoCodec: ConvertMediaVideoCodec | MediaParserVideoCodec;
8
+ }) => Dimensions;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.calculateNewSizeAfterResizing = void 0;
4
+ const ensureMultipleOfTwo = ({ dimensions, needsToBeMultipleOfTwo, }) => {
5
+ if (!needsToBeMultipleOfTwo) {
6
+ return dimensions;
7
+ }
8
+ return {
9
+ width: Math.floor(dimensions.width / 2) * 2,
10
+ height: Math.floor(dimensions.height / 2) * 2,
11
+ };
12
+ };
13
+ const calculateNewSizeAfterResizing = ({ dimensions, resizeOperation, videoCodec, }) => {
14
+ const needsToBeMultipleOfTwo = videoCodec === 'h264';
15
+ if (resizeOperation === null) {
16
+ return ensureMultipleOfTwo({
17
+ dimensions,
18
+ needsToBeMultipleOfTwo,
19
+ });
20
+ }
21
+ if (resizeOperation.mode === 'width') {
22
+ return ensureMultipleOfTwo({
23
+ dimensions: {
24
+ width: resizeOperation.width,
25
+ height: Math.round((resizeOperation.width / dimensions.width) * dimensions.height),
26
+ },
27
+ needsToBeMultipleOfTwo,
28
+ });
29
+ }
30
+ if (resizeOperation.mode === 'height') {
31
+ return ensureMultipleOfTwo({
32
+ dimensions: {
33
+ width: Math.round((resizeOperation.height / dimensions.height) * dimensions.width),
34
+ height: resizeOperation.height,
35
+ },
36
+ needsToBeMultipleOfTwo,
37
+ });
38
+ }
39
+ if (resizeOperation.mode === 'max-height') {
40
+ const height = Math.min(dimensions.height, resizeOperation.maxHeight);
41
+ return ensureMultipleOfTwo({
42
+ dimensions: {
43
+ width: Math.round((height / dimensions.height) * dimensions.width),
44
+ height,
45
+ },
46
+ needsToBeMultipleOfTwo,
47
+ });
48
+ }
49
+ if (resizeOperation.mode === 'max-width') {
50
+ const width = Math.min(dimensions.width, resizeOperation.maxWidth);
51
+ return ensureMultipleOfTwo({
52
+ dimensions: {
53
+ width,
54
+ height: Math.round((width / dimensions.width) * dimensions.height),
55
+ },
56
+ needsToBeMultipleOfTwo,
57
+ });
58
+ }
59
+ if (resizeOperation.mode === 'max-height-width') {
60
+ const height = Math.min(dimensions.height, resizeOperation.maxHeight);
61
+ const width = Math.min(dimensions.width, resizeOperation.maxWidth);
62
+ const scale = Math.min(width / dimensions.width, height / dimensions.height);
63
+ const actualWidth = Math.round(dimensions.width * scale);
64
+ const actualHeight = Math.round(dimensions.height * scale);
65
+ return ensureMultipleOfTwo({
66
+ dimensions: {
67
+ height: actualHeight,
68
+ width: actualWidth,
69
+ },
70
+ needsToBeMultipleOfTwo,
71
+ });
72
+ }
73
+ if (resizeOperation.mode === 'scale') {
74
+ if (resizeOperation.scale <= 0) {
75
+ throw new Error('Scale must be greater than 0');
76
+ }
77
+ const width = Math.round(dimensions.width * resizeOperation.scale);
78
+ const height = Math.round(dimensions.height * resizeOperation.scale);
79
+ return ensureMultipleOfTwo({
80
+ dimensions: {
81
+ width,
82
+ height,
83
+ },
84
+ needsToBeMultipleOfTwo,
85
+ });
86
+ }
87
+ throw new Error('Invalid resizing mode ' + resizeOperation);
88
+ };
89
+ exports.calculateNewSizeAfterResizing = calculateNewSizeAfterResizing;
@@ -0,0 +1,24 @@
1
+ export type Dimensions = {
2
+ width: number;
3
+ height: number;
4
+ };
5
+ export type ResizeOperation = {
6
+ mode: 'width';
7
+ width: number;
8
+ } | {
9
+ mode: 'height';
10
+ height: number;
11
+ } | {
12
+ mode: 'max-height';
13
+ maxHeight: number;
14
+ } | {
15
+ mode: 'max-width';
16
+ maxWidth: number;
17
+ } | {
18
+ mode: 'max-height-width';
19
+ maxHeight: number;
20
+ maxWidth: number;
21
+ } | {
22
+ mode: 'scale';
23
+ scale: number;
24
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ export type ResizingMode = {
2
+ type: 'width';
3
+ width: number;
4
+ } | {
5
+ type: 'height';
6
+ height: number;
7
+ } | {
8
+ type: 'max-height';
9
+ height: number;
10
+ } | {
11
+ type: 'max-width';
12
+ width: number;
13
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ export type ResizingMode = {
2
+ type: 'width';
3
+ width: number;
4
+ } | {
5
+ type: 'height';
6
+ height: number;
7
+ } | {
8
+ type: 'max-height';
9
+ height: number;
10
+ } | {
11
+ type: 'max-width';
12
+ width: number;
13
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,9 @@
1
+ import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
2
+ import type { ResizeOperation } from './resizing/mode';
3
+ export declare const normalizeVideoRotation: (rotation: number) => number;
4
+ export declare const rotateAndResizeVideoFrame: ({ frame, rotation, videoCodec, resizeOperation, }: {
5
+ frame: VideoFrame;
6
+ rotation: number;
7
+ videoCodec: ConvertMediaVideoCodec;
8
+ resizeOperation: ResizeOperation | null;
9
+ }) => VideoFrame;
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.rotateAndResizeVideoFrame = exports.normalizeVideoRotation = void 0;
4
+ const rotation_1 = require("./rotation");
5
+ const normalizeVideoRotation = (rotation) => {
6
+ return ((rotation % 360) + 360) % 360;
7
+ };
8
+ exports.normalizeVideoRotation = normalizeVideoRotation;
9
+ const rotateAndResizeVideoFrame = ({ frame, rotation, videoCodec, resizeOperation, }) => {
10
+ var _a;
11
+ const normalized = ((rotation % 360) + 360) % 360;
12
+ // No resize, no rotation
13
+ if (normalized === 0 && resizeOperation === null) {
14
+ return frame;
15
+ }
16
+ if (normalized % 90 !== 0) {
17
+ throw new Error('Only 90 degree rotations are supported');
18
+ }
19
+ const { height, width } = (0, rotation_1.calculateNewDimensionsFromRotateAndScale)({
20
+ height: frame.displayHeight,
21
+ width: frame.displayWidth,
22
+ rotation,
23
+ videoCodec,
24
+ resizeOperation,
25
+ });
26
+ // No rotation, and resize turned out to be same dimensions
27
+ if (normalized === 0 &&
28
+ height === frame.displayHeight &&
29
+ width === frame.displayWidth) {
30
+ return frame;
31
+ }
32
+ const canvas = new OffscreenCanvas(width, height);
33
+ const ctx = canvas.getContext('2d');
34
+ if (!ctx) {
35
+ throw new Error('Could not get 2d context');
36
+ }
37
+ canvas.width = width;
38
+ canvas.height = height;
39
+ if (normalized === 90) {
40
+ ctx.translate(width, 0);
41
+ }
42
+ else if (normalized === 180) {
43
+ ctx.translate(width, height);
44
+ }
45
+ else if (normalized === 270) {
46
+ ctx.translate(0, height);
47
+ }
48
+ if (normalized !== 0) {
49
+ ctx.rotate(normalized * (Math.PI / 180));
50
+ }
51
+ if (frame.displayHeight !== height || frame.displayWidth !== width) {
52
+ const dimensionsAfterRotate = (0, rotation_1.calculateNewDimensionsFromRotate)({
53
+ height: frame.displayHeight,
54
+ rotation,
55
+ width: frame.displayWidth,
56
+ });
57
+ ctx.scale(width / dimensionsAfterRotate.width, height / dimensionsAfterRotate.height);
58
+ }
59
+ ctx.drawImage(frame, 0, 0);
60
+ return new VideoFrame(canvas, {
61
+ displayHeight: height,
62
+ displayWidth: width,
63
+ duration: (_a = frame.duration) !== null && _a !== void 0 ? _a : undefined,
64
+ timestamp: frame.timestamp,
65
+ });
66
+ };
67
+ exports.rotateAndResizeVideoFrame = rotateAndResizeVideoFrame;
@@ -1,5 +1,9 @@
1
+ import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
2
+ import type { ResizingMode } from './resizing/mode';
1
3
  export declare const normalizeVideoRotation: (rotation: number) => number;
2
- export declare const rotateVideoFrame: ({ frame, rotation, }: {
4
+ export declare const rotateAndResizeVideoFrame: ({ frame, rotation, videoCodec, resizingMode, }: {
3
5
  frame: VideoFrame;
4
6
  rotation: number;
7
+ videoCodec: ConvertMediaVideoCodec;
8
+ resizingMode: ResizingMode | null;
5
9
  }) => VideoFrame;
@@ -1,15 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.rotateVideoFrame = exports.normalizeVideoRotation = void 0;
3
+ exports.rotateAndResizeVideoFrame = exports.normalizeVideoRotation = void 0;
4
4
  const rotation_1 = require("./rotation");
5
5
  const normalizeVideoRotation = (rotation) => {
6
6
  return ((rotation % 360) + 360) % 360;
7
7
  };
8
8
  exports.normalizeVideoRotation = normalizeVideoRotation;
9
- const rotateVideoFrame = ({ frame, rotation, }) => {
9
+ const rotateAndResizeVideoFrame = ({ frame, rotation, videoCodec, resizingMode, }) => {
10
10
  var _a;
11
11
  const normalized = ((rotation % 360) + 360) % 360;
12
- if (normalized % 360 === 0) {
12
+ if (normalized % 360 === 0 && resizingMode === null) {
13
13
  return frame;
14
14
  }
15
15
  if (normalized % 90 !== 0) {
@@ -19,6 +19,8 @@ const rotateVideoFrame = ({ frame, rotation, }) => {
19
19
  height: frame.displayHeight,
20
20
  width: frame.displayWidth,
21
21
  rotation,
22
+ videoCodec,
23
+ resizingMode,
22
24
  });
23
25
  const canvas = new OffscreenCanvas(width, height);
24
26
  const ctx = canvas.getContext('2d');
@@ -37,6 +39,9 @@ const rotateVideoFrame = ({ frame, rotation, }) => {
37
39
  ctx.translate(0, height);
38
40
  }
39
41
  ctx.rotate(normalized * (Math.PI / 180));
42
+ if (frame.displayHeight !== height || frame.displayWidth !== width) {
43
+ ctx.scale(width / frame.displayWidth, height / frame.displayHeight);
44
+ }
40
45
  ctx.drawImage(frame, 0, 0);
41
46
  return new VideoFrame(canvas, {
42
47
  displayHeight: height,
@@ -45,4 +50,4 @@ const rotateVideoFrame = ({ frame, rotation, }) => {
45
50
  timestamp: frame.timestamp,
46
51
  });
47
52
  };
48
- exports.rotateVideoFrame = rotateVideoFrame;
53
+ exports.rotateAndResizeVideoFrame = rotateAndResizeVideoFrame;
@@ -1,8 +1,16 @@
1
- export declare const calculateNewDimensionsFromDimensions: ({ width, height, rotation, }: {
2
- width: number;
3
- height: number;
1
+ import type { Dimensions, MediaParserVideoCodec } from '@remotion/media-parser';
2
+ import type { ConvertMediaVideoCodec } from './get-available-video-codecs';
3
+ import type { ResizeOperation } from './resizing/mode';
4
+ export declare const calculateNewDimensionsFromRotate: ({ height, width, rotation, }: Dimensions & {
4
5
  rotation: number;
5
6
  }) => {
6
7
  height: number;
7
8
  width: number;
8
9
  };
10
+ export declare const calculateNewDimensionsFromRotateAndScale: ({ width, height, rotation, resizeOperation, videoCodec, }: {
11
+ width: number;
12
+ height: number;
13
+ rotation: number;
14
+ resizeOperation: ResizeOperation | null;
15
+ videoCodec: ConvertMediaVideoCodec | MediaParserVideoCodec;
16
+ }) => import("./resizing/mode").Dimensions;
package/dist/rotation.js CHANGED
@@ -1,10 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.calculateNewDimensionsFromDimensions = void 0;
4
- const calculateNewDimensionsFromDimensions = ({ width, height, rotation, }) => {
5
- const switchDimensions = rotation % 90 === 0 && rotation % 180 !== 0;
3
+ exports.calculateNewDimensionsFromRotateAndScale = exports.calculateNewDimensionsFromRotate = void 0;
4
+ const calculate_new_size_1 = require("./resizing/calculate-new-size");
5
+ const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
6
+ const calculateNewDimensionsFromRotate = ({ height, width, rotation, }) => {
7
+ const normalized = (0, rotate_and_resize_video_frame_1.normalizeVideoRotation)(rotation);
8
+ const switchDimensions = normalized % 90 === 0 && normalized % 180 !== 0;
6
9
  const newHeight = switchDimensions ? width : height;
7
10
  const newWidth = switchDimensions ? height : width;
8
- return { height: newHeight, width: newWidth };
11
+ return {
12
+ height: newHeight,
13
+ width: newWidth,
14
+ };
9
15
  };
10
- exports.calculateNewDimensionsFromDimensions = calculateNewDimensionsFromDimensions;
16
+ exports.calculateNewDimensionsFromRotate = calculateNewDimensionsFromRotate;
17
+ const calculateNewDimensionsFromRotateAndScale = ({ width, height, rotation, resizeOperation, videoCodec, }) => {
18
+ const { height: newHeight, width: newWidth } = (0, exports.calculateNewDimensionsFromRotate)({
19
+ height,
20
+ rotation,
21
+ width,
22
+ });
23
+ return (0, calculate_new_size_1.calculateNewSizeAfterResizing)({
24
+ dimensions: { height: newHeight, width: newWidth },
25
+ resizeOperation,
26
+ videoCodec,
27
+ });
28
+ };
29
+ exports.calculateNewDimensionsFromRotateAndScale = calculateNewDimensionsFromRotateAndScale;
@@ -0,0 +1,13 @@
1
+ export type ScalingMode = {
2
+ type: 'width';
3
+ width: number;
4
+ } | {
5
+ type: 'height';
6
+ height: number;
7
+ } | {
8
+ type: 'max-height';
9
+ height: number;
10
+ } | {
11
+ type: 'max-width';
12
+ width: number;
13
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/webcodecs",
3
- "version": "4.0.241",
3
+ "version": "4.0.242",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -17,15 +17,15 @@
17
17
  "author": "Jonny Burger <jonny@remotion.dev>",
18
18
  "license": "Remotion License (See https://remotion.dev/docs/webcodecs#license)",
19
19
  "dependencies": {
20
- "@remotion/media-parser": "4.0.241",
21
- "@remotion/licensing": "4.0.241"
20
+ "@remotion/licensing": "4.0.242",
21
+ "@remotion/media-parser": "4.0.242"
22
22
  },
23
23
  "peerDependencies": {},
24
24
  "devDependencies": {
25
25
  "@types/dom-webcodecs": "0.1.11",
26
26
  "eslint": "9.14.0",
27
- "@remotion/example-videos": "4.0.241",
28
- "@remotion/eslint-config-internal": "4.0.241"
27
+ "@remotion/example-videos": "4.0.242",
28
+ "@remotion/eslint-config-internal": "4.0.242"
29
29
  },
30
30
  "keywords": [],
31
31
  "publishConfig": {