@remotion/webcodecs 4.0.232 → 4.0.233

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.
Files changed (48) hide show
  1. package/README.md +2 -2
  2. package/dist/audio-decoder-config.d.ts +2 -1
  3. package/dist/audio-decoder-config.js +44 -3
  4. package/dist/audio-decoder.d.ts +6 -3
  5. package/dist/audio-decoder.js +20 -4
  6. package/dist/audio-encoder.d.ts +3 -2
  7. package/dist/audio-encoder.js +11 -3
  8. package/dist/browser-quirks.d.ts +1 -0
  9. package/dist/browser-quirks.js +5 -1
  10. package/dist/can-copy-video-track.d.ts +3 -1
  11. package/dist/can-copy-video-track.js +6 -1
  12. package/dist/can-reencode-audio-track.d.ts +3 -2
  13. package/dist/can-reencode-audio-track.js +2 -2
  14. package/dist/convert-media.d.ts +2 -1
  15. package/dist/convert-media.js +7 -3
  16. package/dist/convert-to-correct-videoframe.js +4 -0
  17. package/dist/default-on-video-track-handler.js +8 -2
  18. package/dist/esm/index.mjs +351 -137
  19. package/dist/get-wave-audio-decoder.d.ts +2 -0
  20. package/dist/get-wave-audio-decoder.js +29 -0
  21. package/dist/index.d.ts +7 -0
  22. package/dist/index.js +6 -1
  23. package/dist/io-manager/event-emitter.d.ts +4 -0
  24. package/dist/io-manager/event-emitter.js +1 -0
  25. package/dist/io-manager/io-synchronizer.d.ts +8 -2
  26. package/dist/io-manager/io-synchronizer.js +31 -20
  27. package/dist/io-manager/make-timeout-promise.d.ts +4 -0
  28. package/dist/io-manager/make-timeout-promise.js +18 -0
  29. package/dist/on-audio-track.d.ts +3 -2
  30. package/dist/on-audio-track.js +6 -3
  31. package/dist/on-frame.d.ts +2 -1
  32. package/dist/on-frame.js +25 -14
  33. package/dist/on-video-track-handler.d.ts +2 -0
  34. package/dist/on-video-track.d.ts +4 -2
  35. package/dist/on-video-track.js +20 -5
  36. package/dist/rotate-video-frame.d.ts +5 -0
  37. package/dist/rotate-video-frame.js +48 -0
  38. package/dist/rotate-video.d.ts +4 -0
  39. package/dist/rotate-video.js +43 -0
  40. package/dist/rotation.d.ts +8 -0
  41. package/dist/rotation.js +10 -0
  42. package/dist/select-container-creator.d.ts +1 -1
  43. package/dist/test/avi-to-mp4.test.js +15 -0
  44. package/dist/video-decoder.d.ts +3 -2
  45. package/dist/video-decoder.js +11 -3
  46. package/dist/video-encoder.d.ts +3 -2
  47. package/dist/video-encoder.js +7 -2
  48. package/package.json +5 -3
@@ -1,3 +1,59 @@
1
+ // src/rotation.ts
2
+ var calculateNewDimensionsFromDimensions = ({
3
+ width,
4
+ height,
5
+ rotation
6
+ }) => {
7
+ const switchDimensions = rotation % 90 === 0 && rotation % 180 !== 0;
8
+ const newHeight = switchDimensions ? width : height;
9
+ const newWidth = switchDimensions ? height : width;
10
+ return { height: newHeight, width: newWidth };
11
+ };
12
+
13
+ // src/rotate-video-frame.ts
14
+ var normalizeVideoRotation = (rotation) => {
15
+ return (rotation % 360 + 360) % 360;
16
+ };
17
+ var rotateVideoFrame = ({
18
+ frame,
19
+ rotation
20
+ }) => {
21
+ const normalized = (rotation % 360 + 360) % 360;
22
+ if (normalized % 360 === 0) {
23
+ return frame;
24
+ }
25
+ if (normalized % 90 !== 0) {
26
+ throw new Error("Only 90 degree rotations are supported");
27
+ }
28
+ const { height, width } = calculateNewDimensionsFromDimensions({
29
+ height: frame.displayHeight,
30
+ width: frame.displayWidth,
31
+ rotation
32
+ });
33
+ const canvas = new OffscreenCanvas(width, height);
34
+ const ctx = canvas.getContext("2d");
35
+ if (!ctx) {
36
+ throw new Error("Could not get 2d context");
37
+ }
38
+ canvas.width = width;
39
+ canvas.height = height;
40
+ if (normalized === 90) {
41
+ ctx.translate(width, 0);
42
+ } else if (normalized === 180) {
43
+ ctx.translate(width, height);
44
+ } else if (normalized === 270) {
45
+ ctx.translate(0, height);
46
+ }
47
+ ctx.rotate(normalized * (Math.PI / 180));
48
+ ctx.drawImage(frame, 0, 0);
49
+ return new VideoFrame(canvas, {
50
+ displayHeight: height,
51
+ displayWidth: width,
52
+ duration: frame.duration ?? undefined,
53
+ timestamp: frame.timestamp
54
+ });
55
+ };
56
+
1
57
  // src/set-remotion-imported.ts
2
58
  import { VERSION } from "@remotion/media-parser";
3
59
  var setRemotionImported = () => {
@@ -13,58 +69,65 @@ var setRemotionImported = () => {
13
69
  }
14
70
  };
15
71
 
16
- // src/log.ts
17
- import { MediaParserInternals } from "@remotion/media-parser";
18
- var { Log } = MediaParserInternals;
19
-
20
- // src/with-resolvers.ts
21
- var withResolvers = function() {
22
- let resolve;
23
- let reject;
24
- const promise = new Promise((res, rej) => {
25
- resolve = res;
26
- reject = rej;
27
- });
28
- return { promise, resolve, reject };
29
- };
30
- var withResolversAndWaitForReturn = () => {
31
- const { promise, reject, resolve } = withResolvers();
32
- const { promise: returnPromise, resolve: resolveReturn } = withResolvers();
72
+ // src/get-wave-audio-decoder.ts
73
+ var getWaveAudioDecoder = ({
74
+ onFrame,
75
+ track
76
+ }) => {
77
+ let queue = Promise.resolve();
78
+ const processSample = async (audioSample) => {
79
+ await onFrame(new AudioData({
80
+ data: audioSample.data,
81
+ format: "s16",
82
+ numberOfChannels: track.numberOfChannels,
83
+ numberOfFrames: audioSample.data.byteLength / 2,
84
+ sampleRate: track.sampleRate,
85
+ timestamp: audioSample.timestamp
86
+ }));
87
+ };
33
88
  return {
34
- getPromiseToImmediatelyReturn: () => {
35
- resolveReturn(undefined);
36
- return promise;
89
+ close() {
90
+ return Promise.resolve();
37
91
  },
38
- reject: (reason) => {
39
- returnPromise.then(() => reject(reason));
92
+ processSample(audioSample) {
93
+ queue = queue.then(() => processSample(audioSample));
94
+ return queue;
40
95
  },
41
- resolve
96
+ flush: () => Promise.resolve(),
97
+ waitForFinish: () => Promise.resolve()
42
98
  };
43
99
  };
44
100
 
45
- // src/io-manager/event-emitter.ts
46
- class IoEventEmitter {
47
- listeners = {
48
- input: [],
49
- output: [],
50
- processed: []
101
+ // src/io-manager/io-synchronizer.ts
102
+ import { MediaParserInternals as MediaParserInternals3 } from "@remotion/media-parser";
103
+
104
+ // src/log.ts
105
+ import { MediaParserInternals } from "@remotion/media-parser";
106
+ var { Log } = MediaParserInternals;
107
+
108
+ // src/io-manager/make-timeout-promise.ts
109
+ import { MediaParserInternals as MediaParserInternals2 } from "@remotion/media-parser";
110
+ var makeTimeoutPromise = (label, ms) => {
111
+ const { promise, reject, resolve } = MediaParserInternals2.withResolvers();
112
+ const timeout = setTimeout(() => {
113
+ reject(new Error(`${label} (timed out after ${ms}ms)`));
114
+ }, ms);
115
+ return {
116
+ timeoutPromise: promise,
117
+ clear: () => {
118
+ clearTimeout(timeout);
119
+ resolve();
120
+ }
51
121
  };
52
- addEventListener(name, callback) {
53
- this.listeners[name].push(callback);
54
- }
55
- removeEventListener(name, callback) {
56
- this.listeners[name] = this.listeners[name].filter((l) => l !== callback);
57
- }
58
- dispatchEvent(dispatchName, context) {
59
- this.listeners[dispatchName].forEach((callback) => {
60
- callback({ detail: context });
61
- });
62
- }
63
- }
122
+ };
64
123
 
65
124
  // src/io-manager/io-synchronizer.ts
66
- var makeIoSynchronizer = (logLevel, label) => {
67
- const eventEmitter = new IoEventEmitter;
125
+ var makeIoSynchronizer = ({
126
+ logLevel,
127
+ label,
128
+ progress
129
+ }) => {
130
+ const eventEmitter = new MediaParserInternals3.IoEventEmitter;
68
131
  let lastInput = 0;
69
132
  let lastInputKeyframe = 0;
70
133
  let lastOutput = 0;
@@ -108,7 +171,7 @@ var makeIoSynchronizer = (logLevel, label) => {
108
171
  printState("Got output");
109
172
  };
110
173
  const waitForOutput = () => {
111
- const { promise, resolve } = withResolvers();
174
+ const { promise, resolve } = MediaParserInternals3.withResolvers();
112
175
  const on = () => {
113
176
  eventEmitter.removeEventListener("output", on);
114
177
  resolve();
@@ -117,7 +180,7 @@ var makeIoSynchronizer = (logLevel, label) => {
117
180
  return promise;
118
181
  };
119
182
  const waitForProcessed = () => {
120
- const { promise, resolve } = withResolvers();
183
+ const { promise, resolve } = MediaParserInternals3.withResolvers();
121
184
  const on = () => {
122
185
  eventEmitter.removeEventListener("processed", on);
123
186
  resolve();
@@ -127,23 +190,33 @@ var makeIoSynchronizer = (logLevel, label) => {
127
190
  };
128
191
  const waitFor = async ({
129
192
  _unprocessed,
130
- unemitted
193
+ unemitted,
194
+ minimumProgress
131
195
  }) => {
132
- await Promise.all([
133
- async () => {
134
- while (getUnemittedItems() > unemitted) {
135
- await waitForOutput();
136
- }
137
- },
138
- async () => {
139
- while (getUnprocessed() > _unprocessed) {
140
- await waitForProcessed();
141
- }
142
- }
143
- ]);
196
+ const { timeoutPromise, clear } = makeTimeoutPromise(`Waited too long for ${label}`, 1e4);
197
+ await Promise.race([
198
+ timeoutPromise,
199
+ Promise.all([
200
+ (async () => {
201
+ while (getUnemittedItems() > unemitted) {
202
+ await waitForOutput();
203
+ }
204
+ })(),
205
+ (async () => {
206
+ while (getUnprocessed() > _unprocessed) {
207
+ await waitForProcessed();
208
+ }
209
+ })(),
210
+ minimumProgress === null ? Promise.resolve() : (async () => {
211
+ while (progress.getSmallestProgress() < minimumProgress) {
212
+ await progress.waitForProgress();
213
+ }
214
+ })()
215
+ ])
216
+ ]).finally(() => clear());
144
217
  };
145
218
  const waitForFinish = async () => {
146
- await waitFor({ _unprocessed: 0, unemitted: 0 });
219
+ await waitFor({ _unprocessed: 0, unemitted: 0, minimumProgress: null });
147
220
  };
148
221
  const onProcessed = () => {
149
222
  eventEmitter.dispatchEvent("processed", {});
@@ -165,12 +238,21 @@ var createAudioDecoder = ({
165
238
  onError,
166
239
  signal,
167
240
  config,
168
- logLevel
241
+ logLevel,
242
+ track,
243
+ progressTracker
169
244
  }) => {
170
245
  if (signal.aborted) {
171
246
  throw new Error("Not creating audio decoder, already aborted");
172
247
  }
173
- const ioSynchronizer = makeIoSynchronizer(logLevel, "Audio decoder");
248
+ if (config.codec === "pcm-s16") {
249
+ return getWaveAudioDecoder({ onFrame, track });
250
+ }
251
+ const ioSynchronizer = makeIoSynchronizer({
252
+ logLevel,
253
+ label: "Audio decoder",
254
+ progress: progressTracker
255
+ });
174
256
  let outputQueue = Promise.resolve();
175
257
  const audioDecoder = new AudioDecoder({
176
258
  output(inputFrame) {
@@ -213,7 +295,11 @@ var createAudioDecoder = ({
213
295
  if (audioDecoder.state === "closed") {
214
296
  return;
215
297
  }
216
- await ioSynchronizer.waitFor({ unemitted: 100, _unprocessed: 2 });
298
+ await ioSynchronizer.waitFor({
299
+ unemitted: 20,
300
+ _unprocessed: 20,
301
+ minimumProgress: audioSample.timestamp - 1e7
302
+ });
217
303
  const chunk = new EncodedAudioChunk(audioSample);
218
304
  audioDecoder.decode(chunk);
219
305
  ioSynchronizer.inputItem(chunk.timestamp, audioSample.type === "key");
@@ -228,7 +314,10 @@ var createAudioDecoder = ({
228
314
  return queue;
229
315
  },
230
316
  waitForFinish: async () => {
231
- await audioDecoder.flush();
317
+ try {
318
+ await audioDecoder.flush();
319
+ } catch {
320
+ }
232
321
  await queue;
233
322
  await ioSynchronizer.waitForFinish();
234
323
  await outputQueue;
@@ -274,7 +363,8 @@ var createAudioEncoder = ({
274
363
  signal,
275
364
  config: audioEncoderConfig,
276
365
  logLevel,
277
- onNewAudioSampleRate
366
+ onNewAudioSampleRate,
367
+ progressTracker
278
368
  }) => {
279
369
  if (signal.aborted) {
280
370
  throw new Error("Not creating audio encoder, already aborted");
@@ -282,7 +372,11 @@ var createAudioEncoder = ({
282
372
  if (codec === "wav") {
283
373
  return getWaveAudioEncoder({ onChunk, signal });
284
374
  }
285
- const ioSynchronizer = makeIoSynchronizer(logLevel, "Audio encoder");
375
+ const ioSynchronizer = makeIoSynchronizer({
376
+ logLevel,
377
+ label: "Audio encoder",
378
+ progress: progressTracker
379
+ });
286
380
  let prom = Promise.resolve();
287
381
  const encoder = new AudioEncoder({
288
382
  output: (chunk) => {
@@ -322,7 +416,11 @@ var createAudioEncoder = ({
322
416
  if (encoder.state === "closed") {
323
417
  return;
324
418
  }
325
- await ioSynchronizer.waitFor({ unemitted: 20, _unprocessed: 20 });
419
+ await ioSynchronizer.waitFor({
420
+ unemitted: 20,
421
+ _unprocessed: 20,
422
+ minimumProgress: audioData.timestamp - 1e7
423
+ });
326
424
  if (encoder.state === "closed") {
327
425
  return;
328
426
  }
@@ -376,8 +474,13 @@ var canCopyAudioTrack = ({
376
474
  // src/can-copy-video-track.ts
377
475
  var canCopyVideoTrack = ({
378
476
  inputCodec,
379
- container
477
+ container,
478
+ inputRotation,
479
+ rotationToApply
380
480
  }) => {
481
+ if (normalizeVideoRotation(inputRotation) !== normalizeVideoRotation(rotationToApply)) {
482
+ return false;
483
+ }
381
484
  if (container === "webm") {
382
485
  return inputCodec === "vp8" || inputCodec === "vp9";
383
486
  }
@@ -389,16 +492,68 @@ var canCopyVideoTrack = ({
389
492
  }
390
493
  throw new Error(`Unhandled codec: ${container}`);
391
494
  };
495
+ // src/browser-quirks.ts
496
+ var isFirefox = () => {
497
+ return navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
498
+ };
499
+ var isSafari = () => {
500
+ return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
501
+ };
502
+ var isChrome = () => {
503
+ return navigator.userAgent.toLowerCase().indexOf("chrome") > -1;
504
+ };
505
+
392
506
  // src/audio-decoder-config.ts
393
- var getAudioDecoderConfig = async (config) => {
507
+ var overrideBrowserQuirks = ({
508
+ config,
509
+ logLevel
510
+ }) => {
511
+ const bytes = config.description;
512
+ if (!bytes) {
513
+ return config;
514
+ }
515
+ if (bytes[0] === 18 && bytes[1] === 8) {
516
+ if (isFirefox()) {
517
+ return {
518
+ ...config,
519
+ codec: "mp4a.40.2",
520
+ description: bytes
521
+ };
522
+ }
523
+ if (!isChrome()) {
524
+ return config;
525
+ }
526
+ Log.warn(logLevel, "Chrome has a bug and might not be able to decode this audio. It will be fixed, see: https://issues.chromium.org/issues/360083330");
527
+ return {
528
+ ...config,
529
+ description: new Uint8Array([18, 16])
530
+ };
531
+ }
532
+ if (bytes.byteLength === 2 && bytes[0] === 17 && bytes[1] === 136) {
533
+ Log.warn(logLevel, "Chrome has a bug and might not be able to decode this audio. It will be fixed, see: https://issues.chromium.org/issues/360083330");
534
+ return {
535
+ ...config,
536
+ description: new Uint8Array([18, 144])
537
+ };
538
+ }
539
+ return config;
540
+ };
541
+ var getAudioDecoderConfig = async (config, logLevel) => {
542
+ if (config.codec === "pcm-s16") {
543
+ return config;
544
+ }
394
545
  if (typeof AudioDecoder === "undefined") {
395
546
  return null;
396
547
  }
397
548
  if (typeof EncodedAudioChunk === "undefined") {
398
549
  return null;
399
550
  }
400
- if ((await AudioDecoder.isConfigSupported(config)).supported) {
401
- return config;
551
+ const realConfig = overrideBrowserQuirks({
552
+ config,
553
+ logLevel
554
+ });
555
+ if ((await AudioDecoder.isConfigSupported(realConfig)).supported) {
556
+ return realConfig;
402
557
  }
403
558
  return null;
404
559
  };
@@ -437,9 +592,10 @@ var getAudioEncoderConfig = async (config) => {
437
592
  var canReencodeAudioTrack = async ({
438
593
  track,
439
594
  audioCodec,
440
- bitrate
595
+ bitrate,
596
+ logLevel = "info"
441
597
  }) => {
442
- const audioDecoderConfig = await getAudioDecoderConfig(track);
598
+ const audioDecoderConfig = await getAudioDecoderConfig(track, logLevel);
443
599
  if (audioCodec === "wav" && audioDecoderConfig) {
444
600
  return true;
445
601
  }
@@ -473,14 +629,6 @@ var getVideoDecoderConfigWithHardwareAcceleration = async (config) => {
473
629
  return null;
474
630
  };
475
631
 
476
- // src/browser-quirks.ts
477
- var isFirefox = () => {
478
- return navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
479
- };
480
- var isSafari = () => {
481
- return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
482
- };
483
-
484
632
  // src/choose-correct-avc1-profile.ts
485
633
  var chooseCorrectAvc1Profile = ({
486
634
  width,
@@ -564,7 +712,10 @@ var canReencodeVideoTrack = async ({
564
712
  return Boolean(videoDecoderConfig && videoEncoderConfig);
565
713
  };
566
714
  // src/convert-media.ts
567
- import { parseMedia } from "@remotion/media-parser";
715
+ import {
716
+ MediaParserInternals as MediaParserInternals7,
717
+ parseMedia
718
+ } from "@remotion/media-parser";
568
719
 
569
720
  // src/auto-select-writer.ts
570
721
  import { bufferWriter } from "@remotion/media-parser/buffer";
@@ -621,7 +772,7 @@ var convertEncodedChunk = (chunk, trackId) => {
621
772
  };
622
773
 
623
774
  // src/default-on-audio-track-handler.ts
624
- import { MediaParserInternals as MediaParserInternals2 } from "@remotion/media-parser";
775
+ import { MediaParserInternals as MediaParserInternals4 } from "@remotion/media-parser";
625
776
 
626
777
  // src/get-default-audio-codec.ts
627
778
  var getDefaultAudioCodec = ({
@@ -653,7 +804,7 @@ var defaultOnAudioTrackHandler = async ({
653
804
  container
654
805
  });
655
806
  if (canCopy) {
656
- MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
807
+ MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
657
808
  return Promise.resolve({ type: "copy" });
658
809
  }
659
810
  const audioCodec = defaultAudioCodec ?? getDefaultAudioCodec({ container });
@@ -663,14 +814,14 @@ var defaultOnAudioTrackHandler = async ({
663
814
  bitrate
664
815
  });
665
816
  if (canReencode) {
666
- MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
817
+ MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
667
818
  return Promise.resolve({
668
819
  type: "reencode",
669
820
  bitrate,
670
821
  audioCodec
671
822
  });
672
823
  }
673
- MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
824
+ MediaParserInternals4.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
674
825
  return Promise.resolve({ type: "fail" });
675
826
  };
676
827
 
@@ -683,7 +834,8 @@ var makeAudioTrackHandler = ({
683
834
  onMediaStateUpdate,
684
835
  onAudioTrack,
685
836
  logLevel,
686
- container
837
+ container,
838
+ progressTracker
687
839
  }) => async (track) => {
688
840
  const audioOperation = await (onAudioTrack ?? defaultOnAudioTrackHandler)({
689
841
  defaultAudioCodec: audioCodec,
@@ -734,7 +886,7 @@ var makeAudioTrackHandler = ({
734
886
  numberOfChannels: track.numberOfChannels,
735
887
  sampleRate: track.sampleRate,
736
888
  description: track.description
737
- });
889
+ }, logLevel);
738
890
  if (!audioEncoderConfig) {
739
891
  abortConversion(new error_cause_default(`Could not configure audio encoder of track ${track.trackId}`));
740
892
  return null;
@@ -779,7 +931,8 @@ var makeAudioTrackHandler = ({
779
931
  codec: audioOperation.audioCodec,
780
932
  signal: controller.signal,
781
933
  config: audioEncoderConfig,
782
- logLevel
934
+ logLevel,
935
+ progressTracker
783
936
  });
784
937
  const audioDecoder = createAudioDecoder({
785
938
  onFrame: async (frame) => {
@@ -793,13 +946,15 @@ var makeAudioTrackHandler = ({
793
946
  frame.close();
794
947
  },
795
948
  onError(error) {
796
- abortConversion(new error_cause_default(`Audio decoder of track ${track.trackId} failed (see .cause of this error)`, {
949
+ abortConversion(new error_cause_default(`Audio decoder of track ${track.trackId} failed. Config: ${JSON.stringify(audioDecoderConfig)} (see .cause of this error)`, {
797
950
  cause: error
798
951
  }));
799
952
  },
800
953
  signal: controller.signal,
801
954
  config: audioDecoderConfig,
802
- logLevel
955
+ logLevel,
956
+ track,
957
+ progressTracker
803
958
  });
804
959
  state.addWaitForFinishPromise(async () => {
805
960
  await audioDecoder.waitForFinish();
@@ -818,7 +973,7 @@ var arrayBufferToUint8Array = (buffer) => {
818
973
  };
819
974
 
820
975
  // src/default-on-video-track-handler.ts
821
- import { MediaParserInternals as MediaParserInternals3 } from "@remotion/media-parser";
976
+ import { MediaParserInternals as MediaParserInternals5 } from "@remotion/media-parser";
822
977
 
823
978
  // src/get-default-video-codec.ts
824
979
  var getDefaultVideoCodec = ({
@@ -841,19 +996,22 @@ var defaultOnVideoTrackHandler = async ({
841
996
  track,
842
997
  defaultVideoCodec,
843
998
  logLevel,
844
- container
999
+ container,
1000
+ rotate
845
1001
  }) => {
846
1002
  const canCopy = canCopyVideoTrack({
847
1003
  inputCodec: track.codecWithoutConfig,
848
- container
1004
+ container,
1005
+ inputRotation: track.rotation,
1006
+ rotationToApply: rotate
849
1007
  });
850
1008
  if (canCopy) {
851
- MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
1009
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
852
1010
  return Promise.resolve({ type: "copy" });
853
1011
  }
854
1012
  const videoCodec = defaultVideoCodec ?? getDefaultVideoCodec({ container });
855
1013
  if (videoCodec === null) {
856
- MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): No default video codec, therefore dropping`);
1014
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (video): No default video codec, therefore dropping`);
857
1015
  return Promise.resolve({ type: "drop" });
858
1016
  }
859
1017
  const canReencode = await canReencodeVideoTrack({
@@ -861,10 +1019,14 @@ var defaultOnVideoTrackHandler = async ({
861
1019
  track
862
1020
  });
863
1021
  if (canReencode) {
864
- MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
865
- return Promise.resolve({ type: "reencode", videoCodec });
1022
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
1023
+ return Promise.resolve({
1024
+ type: "reencode",
1025
+ videoCodec,
1026
+ rotation: rotate - track.rotation
1027
+ });
866
1028
  }
867
- MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
1029
+ MediaParserInternals5.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
868
1030
  return Promise.resolve({ type: "fail" });
869
1031
  };
870
1032
 
@@ -876,6 +1038,9 @@ var needsToCorrectVideoFrame = ({
876
1038
  if (videoFrame.format === null) {
877
1039
  return true;
878
1040
  }
1041
+ if (videoFrame.format === "I420P10") {
1042
+ return true;
1043
+ }
879
1044
  return isFirefox() && videoFrame.format === "BGRX" && outputCodec === "h264";
880
1045
  };
881
1046
  var convertToCorrectVideoFrame = ({
@@ -903,35 +1068,43 @@ var convertToCorrectVideoFrame = ({
903
1068
 
904
1069
  // src/on-frame.ts
905
1070
  var onFrame = async ({
906
- frame,
1071
+ frame: unrotatedFrame,
907
1072
  onVideoFrame,
908
1073
  videoEncoder,
909
1074
  track,
910
- outputCodec
1075
+ outputCodec,
1076
+ rotation
911
1077
  }) => {
912
- const newFrame = onVideoFrame ? await onVideoFrame({ frame, track }) : frame;
913
- if (newFrame.displayWidth !== frame.displayWidth) {
914
- throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayWidth (${newFrame.displayWidth}) than the input frame (${newFrame.displayHeight})`);
1078
+ const rotated = rotateVideoFrame({
1079
+ rotation,
1080
+ frame: unrotatedFrame
1081
+ });
1082
+ if (unrotatedFrame !== rotated) {
1083
+ unrotatedFrame.close();
1084
+ }
1085
+ const userProcessedFrame = onVideoFrame ? await onVideoFrame({ frame: rotated, track }) : rotated;
1086
+ if (userProcessedFrame.displayWidth !== rotated.displayWidth) {
1087
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayWidth (${userProcessedFrame.displayWidth}) than the input frame (${userProcessedFrame.displayHeight})`);
915
1088
  }
916
- if (newFrame.displayHeight !== frame.displayHeight) {
917
- throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${newFrame.displayHeight}) than the input frame (${newFrame.displayHeight})`);
1089
+ if (userProcessedFrame.displayHeight !== rotated.displayHeight) {
1090
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${userProcessedFrame.displayHeight}) than the input frame (${userProcessedFrame.displayHeight})`);
918
1091
  }
919
- if (newFrame.timestamp !== frame.timestamp) {
920
- throw new Error(`Returned VideoFrame of track ${track.trackId} has different timestamp (${newFrame.timestamp}) than the input frame (${newFrame.timestamp}). When calling new VideoFrame(), pass {timestamp: frame.timestamp} as second argument`);
1092
+ if (userProcessedFrame.timestamp !== rotated.timestamp) {
1093
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different timestamp (${userProcessedFrame.timestamp}) than the input frame (${rotated.timestamp}). When calling new VideoFrame(), pass {timestamp: frame.timestamp} as second argument`);
921
1094
  }
922
- if (newFrame.duration !== frame.duration) {
923
- throw new Error(`Returned VideoFrame of track ${track.trackId} has different duration (${newFrame.duration}) than the input frame (${newFrame.duration}). When calling new VideoFrame(), pass {duration: frame.duration} as second argument`);
1095
+ if ((userProcessedFrame.duration ?? 0) !== (rotated.duration ?? 0)) {
1096
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different duration (${userProcessedFrame.duration}) than the input frame (${rotated.duration}). When calling new VideoFrame(), pass {duration: frame.duration} as second argument`);
924
1097
  }
925
1098
  const fixedFrame = convertToCorrectVideoFrame({
926
- videoFrame: newFrame,
1099
+ videoFrame: userProcessedFrame,
927
1100
  outputCodec
928
1101
  });
929
1102
  await videoEncoder.encodeFrame(fixedFrame, fixedFrame.timestamp);
930
1103
  fixedFrame.close();
931
- if (frame !== newFrame) {
932
- frame.close();
1104
+ if (rotated !== userProcessedFrame) {
1105
+ rotated.close();
933
1106
  }
934
- if (fixedFrame !== newFrame) {
1107
+ if (fixedFrame !== userProcessedFrame) {
935
1108
  fixedFrame.close();
936
1109
  }
937
1110
  };
@@ -942,9 +1115,14 @@ var createVideoDecoder = ({
942
1115
  onError,
943
1116
  signal,
944
1117
  config,
945
- logLevel
1118
+ logLevel,
1119
+ progress
946
1120
  }) => {
947
- const ioSynchronizer = makeIoSynchronizer(logLevel, "Video decoder");
1121
+ const ioSynchronizer = makeIoSynchronizer({
1122
+ logLevel,
1123
+ label: "Video decoder",
1124
+ progress
1125
+ });
948
1126
  let outputQueue = Promise.resolve();
949
1127
  const videoDecoder = new VideoDecoder({
950
1128
  output(inputFrame) {
@@ -990,7 +1168,11 @@ var createVideoDecoder = ({
990
1168
  if (videoDecoder.state === "closed") {
991
1169
  return;
992
1170
  }
993
- await ioSynchronizer.waitFor({ unemitted: 20, _unprocessed: 2 });
1171
+ await ioSynchronizer.waitFor({
1172
+ unemitted: 20,
1173
+ _unprocessed: 2,
1174
+ minimumProgress: sample.timestamp - 5000000
1175
+ });
994
1176
  if (sample.type === "key") {
995
1177
  await videoDecoder.flush();
996
1178
  }
@@ -1027,12 +1209,17 @@ var createVideoEncoder = ({
1027
1209
  signal,
1028
1210
  config,
1029
1211
  logLevel,
1030
- outputCodec
1212
+ outputCodec,
1213
+ progress
1031
1214
  }) => {
1032
1215
  if (signal.aborted) {
1033
1216
  throw new Error("Not creating video encoder, already aborted");
1034
1217
  }
1035
- const ioSynchronizer = makeIoSynchronizer(logLevel, "Video encoder");
1218
+ const ioSynchronizer = makeIoSynchronizer({
1219
+ logLevel,
1220
+ label: "Video encoder",
1221
+ progress
1222
+ });
1036
1223
  let outputQueue = Promise.resolve();
1037
1224
  const encoder = new VideoEncoder({
1038
1225
  error(error) {
@@ -1074,7 +1261,8 @@ var createVideoEncoder = ({
1074
1261
  }
1075
1262
  await ioSynchronizer.waitFor({
1076
1263
  unemitted: 10,
1077
- _unprocessed: 10
1264
+ _unprocessed: 10,
1265
+ minimumProgress: frame.timestamp - 5000000
1078
1266
  });
1079
1267
  if (encoder.state === "closed") {
1080
1268
  return;
@@ -1114,7 +1302,9 @@ var makeVideoTrackHandler = ({
1114
1302
  defaultVideoCodec,
1115
1303
  onVideoTrack,
1116
1304
  logLevel,
1117
- container
1305
+ container,
1306
+ rotate,
1307
+ progress
1118
1308
  }) => async (track) => {
1119
1309
  if (controller.signal.aborted) {
1120
1310
  throw new error_cause_default("Aborted");
@@ -1123,7 +1313,8 @@ var makeVideoTrackHandler = ({
1123
1313
  track,
1124
1314
  defaultVideoCodec,
1125
1315
  logLevel,
1126
- container
1316
+ container,
1317
+ rotate
1127
1318
  });
1128
1319
  if (videoOperation.type === "drop") {
1129
1320
  return null;
@@ -1158,10 +1349,19 @@ var makeVideoTrackHandler = ({
1158
1349
  });
1159
1350
  };
1160
1351
  }
1352
+ if (videoOperation.type !== "reencode") {
1353
+ 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"`);
1354
+ }
1355
+ const rotation = videoOperation.rotate ?? -track.rotation;
1356
+ const { height: newHeight, width: newWidth } = calculateNewDimensionsFromDimensions({
1357
+ width: track.codedWidth,
1358
+ height: track.codedHeight,
1359
+ rotation
1360
+ });
1161
1361
  const videoEncoderConfig = await getVideoEncoderConfig({
1162
1362
  codec: videoOperation.videoCodec,
1163
- height: track.displayAspectHeight,
1164
- width: track.displayAspectWidth,
1363
+ height: newHeight,
1364
+ width: newWidth,
1165
1365
  fps: track.fps
1166
1366
  });
1167
1367
  const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
@@ -1176,8 +1376,8 @@ var makeVideoTrackHandler = ({
1176
1376
  const { trackNumber } = await state.addTrack({
1177
1377
  type: "video",
1178
1378
  color: track.color,
1179
- width: track.codedWidth,
1180
- height: track.codedHeight,
1379
+ width: newWidth,
1380
+ height: newHeight,
1181
1381
  codec: videoOperation.videoCodec,
1182
1382
  codecPrivate: null,
1183
1383
  timescale: track.timescale
@@ -1207,7 +1407,8 @@ var makeVideoTrackHandler = ({
1207
1407
  signal: controller.signal,
1208
1408
  config: videoEncoderConfig,
1209
1409
  logLevel,
1210
- outputCodec: videoOperation.videoCodec
1410
+ outputCodec: videoOperation.videoCodec,
1411
+ progress
1211
1412
  });
1212
1413
  const videoDecoder = createVideoDecoder({
1213
1414
  config: videoDecoderConfig,
@@ -1217,7 +1418,8 @@ var makeVideoTrackHandler = ({
1217
1418
  track,
1218
1419
  videoEncoder,
1219
1420
  onVideoFrame,
1220
- outputCodec: videoOperation.videoCodec
1421
+ outputCodec: videoOperation.videoCodec,
1422
+ rotation
1221
1423
  });
1222
1424
  },
1223
1425
  onError: (err) => {
@@ -1226,7 +1428,8 @@ var makeVideoTrackHandler = ({
1226
1428
  }));
1227
1429
  },
1228
1430
  signal: controller.signal,
1229
- logLevel
1431
+ logLevel,
1432
+ progress
1230
1433
  });
1231
1434
  state.addWaitForFinishPromise(async () => {
1232
1435
  Log.verbose(logLevel, "Waiting for video decoder to finish");
@@ -1243,16 +1446,16 @@ var makeVideoTrackHandler = ({
1243
1446
  };
1244
1447
 
1245
1448
  // src/select-container-creator.ts
1246
- import { MediaParserInternals as MediaParserInternals4 } from "@remotion/media-parser";
1449
+ import { MediaParserInternals as MediaParserInternals6 } from "@remotion/media-parser";
1247
1450
  var selectContainerCreator = (container) => {
1248
1451
  if (container === "mp4") {
1249
- return MediaParserInternals4.createIsoBaseMedia;
1452
+ return MediaParserInternals6.createIsoBaseMedia;
1250
1453
  }
1251
1454
  if (container === "wav") {
1252
- return MediaParserInternals4.createWav;
1455
+ return MediaParserInternals6.createWav;
1253
1456
  }
1254
1457
  if (container === "webm") {
1255
- return MediaParserInternals4.createMatroskaMedia;
1458
+ return MediaParserInternals6.createMatroskaMedia;
1256
1459
  }
1257
1460
  throw new Error(`Unsupported container: ${container}`);
1258
1461
  };
@@ -1325,6 +1528,7 @@ var convertMedia = async function({
1325
1528
  logLevel = "info",
1326
1529
  writer,
1327
1530
  progressIntervalInMs,
1531
+ rotate,
1328
1532
  ...more
1329
1533
  }) {
1330
1534
  if (userPassedAbortSignal?.aborted) {
@@ -1336,7 +1540,7 @@ var convertMedia = async function({
1336
1540
  if (videoCodec && videoCodec !== "vp8" && videoCodec !== "vp9") {
1337
1541
  return Promise.reject(new TypeError('Only `videoCodec: "vp8"` and `videoCodec: "vp9"` are supported currently'));
1338
1542
  }
1339
- const { resolve, reject, getPromiseToImmediatelyReturn } = withResolversAndWaitForReturn();
1543
+ const { resolve, reject, getPromiseToImmediatelyReturn } = MediaParserInternals7.withResolversAndWaitForReturn();
1340
1544
  const controller = new AbortController;
1341
1545
  const abortConversion = (errCause) => {
1342
1546
  reject(errCause);
@@ -1354,6 +1558,7 @@ var convertMedia = async function({
1354
1558
  everyMilliseconds: progressIntervalInMs ?? 100,
1355
1559
  signal: controller.signal
1356
1560
  });
1561
+ const progressTracker = MediaParserInternals7.makeProgressTracker();
1357
1562
  const state = await creator({
1358
1563
  filename: generateOutputFilename(src, container),
1359
1564
  writer: await autoSelectWriter(writer, logLevel),
@@ -1380,7 +1585,8 @@ var convertMedia = async function({
1380
1585
  return prevState;
1381
1586
  });
1382
1587
  },
1383
- logLevel
1588
+ logLevel,
1589
+ progressTracker
1384
1590
  });
1385
1591
  const onVideoTrack = makeVideoTrackHandler({
1386
1592
  state,
@@ -1391,7 +1597,9 @@ var convertMedia = async function({
1391
1597
  defaultVideoCodec: videoCodec ?? null,
1392
1598
  onVideoTrack: userVideoResolver ?? null,
1393
1599
  logLevel,
1394
- container
1600
+ container,
1601
+ rotate: rotate ?? 0,
1602
+ progress: progressTracker
1395
1603
  });
1396
1604
  const onAudioTrack = makeAudioTrackHandler({
1397
1605
  abortConversion,
@@ -1401,7 +1609,8 @@ var convertMedia = async function({
1401
1609
  state,
1402
1610
  onAudioTrack: userAudioResolver ?? null,
1403
1611
  logLevel,
1404
- container
1612
+ container,
1613
+ progressTracker
1405
1614
  });
1406
1615
  parseMedia({
1407
1616
  logLevel,
@@ -1488,6 +1697,10 @@ var getAvailableVideoCodecs = ({
1488
1697
  throw new Error(`Unsupported container: ${container}`);
1489
1698
  };
1490
1699
  // src/index.ts
1700
+ var WebCodecsInternals = {
1701
+ rotateVideoFrame,
1702
+ normalizeVideoRotation
1703
+ };
1491
1704
  setRemotionImported();
1492
1705
  export {
1493
1706
  getDefaultVideoCodec,
@@ -1505,5 +1718,6 @@ export {
1505
1718
  canReencodeVideoTrack,
1506
1719
  canReencodeAudioTrack,
1507
1720
  canCopyVideoTrack,
1508
- canCopyAudioTrack
1721
+ canCopyAudioTrack,
1722
+ WebCodecsInternals
1509
1723
  };