@remotion/webcodecs 4.0.228 → 4.0.230

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 (62) hide show
  1. package/dist/arraybuffer-to-uint8-array.d.ts +1 -0
  2. package/dist/arraybuffer-to-uint8-array.js +7 -0
  3. package/dist/audio-decoder-config.js +3 -0
  4. package/dist/audio-decoder.d.ts +2 -2
  5. package/dist/audio-encoder-config.js +15 -2
  6. package/dist/audio-encoder.d.ts +2 -1
  7. package/dist/audio-encoder.js +16 -4
  8. package/dist/calculate-progress.d.ts +2 -2
  9. package/dist/calculate-progress.js +3 -3
  10. package/dist/can-copy-audio-track.d.ts +6 -0
  11. package/dist/can-copy-audio-track.js +13 -0
  12. package/dist/can-copy-video-track.d.ts +6 -0
  13. package/dist/can-copy-video-track.js +13 -0
  14. package/dist/can-reencode-audio-track.d.ts +7 -0
  15. package/dist/can-reencode-audio-track.js +16 -0
  16. package/dist/can-reencode-video-track.d.ts +6 -0
  17. package/dist/can-reencode-video-track.js +16 -0
  18. package/dist/choose-correct-avc1-profile.d.ts +5 -0
  19. package/dist/choose-correct-avc1-profile.js +54 -0
  20. package/dist/codec-id.d.ts +10 -2
  21. package/dist/codec-id.js +30 -0
  22. package/dist/convert-encoded-chunk.d.ts +3 -0
  23. package/dist/convert-encoded-chunk.js +38 -0
  24. package/dist/convert-media.d.ts +22 -14
  25. package/dist/convert-media.js +25 -20
  26. package/dist/default-on-audio-track-handler.d.ts +2 -0
  27. package/dist/default-on-audio-track-handler.js +36 -0
  28. package/dist/default-on-video-track-handler.d.ts +2 -0
  29. package/dist/default-on-video-track-handler.js +29 -0
  30. package/dist/esm/index.mjs +497 -210
  31. package/dist/get-default-audio-codec.d.ts +4 -0
  32. package/dist/get-default-audio-codec.js +13 -0
  33. package/dist/get-default-video-codec.d.ts +4 -0
  34. package/dist/get-default-video-codec.js +10 -0
  35. package/dist/index.d.ts +12 -1
  36. package/dist/index.js +21 -1
  37. package/dist/io-manager/io-synchronizer.js +2 -2
  38. package/dist/on-audio-track-handler.d.ts +19 -0
  39. package/dist/on-audio-track-handler.js +2 -0
  40. package/dist/on-audio-track.d.ts +6 -6
  41. package/dist/on-audio-track.js +54 -27
  42. package/dist/on-frame.d.ts +11 -0
  43. package/dist/on-frame.js +32 -0
  44. package/dist/on-video-track-handler.d.ts +18 -0
  45. package/dist/on-video-track-handler.js +2 -0
  46. package/dist/on-video-track.d.ts +10 -9
  47. package/dist/on-video-track.js +55 -25
  48. package/dist/video-decoder.d.ts +2 -2
  49. package/dist/video-decoder.js +5 -0
  50. package/dist/video-encoder-config.d.ts +7 -1
  51. package/dist/video-encoder-config.js +11 -1
  52. package/dist/video-encoder.d.ts +2 -2
  53. package/dist/video-encoder.js +4 -6
  54. package/package.json +4 -4
  55. package/dist/event-emitter.d.ts +0 -25
  56. package/dist/event-emitter.js +0 -23
  57. package/dist/resolve-audio-action.d.ts +0 -15
  58. package/dist/resolve-audio-action.js +0 -30
  59. package/dist/resolve-video-action.d.ts +0 -15
  60. package/dist/resolve-video-action.js +0 -33
  61. package/dist/wait-until-return.d.ts +0 -4
  62. package/dist/wait-until-return.js +0 -14
@@ -59,11 +59,11 @@ var makeIoSynchronizer = (logLevel, label) => {
59
59
  let unprocessed = 0;
60
60
  const getUnprocessed = () => unprocessed;
61
61
  const getUnemittedItems = () => {
62
- inputs = inputs.filter((input) => input > lastOutput);
62
+ inputs = inputs.filter((input) => Math.floor(input) > Math.floor(lastOutput));
63
63
  return inputs.length;
64
64
  };
65
65
  const getUnemittedKeyframes = () => {
66
- keyframes = keyframes.filter((keyframe) => keyframe > lastOutput);
66
+ keyframes = keyframes.filter((keyframe) => Math.floor(keyframe) > Math.floor(lastOutput));
67
67
  return keyframes.length;
68
68
  };
69
69
  const printState = (prefix) => {
@@ -221,7 +221,8 @@ var createAudioEncoder = ({
221
221
  codec,
222
222
  signal,
223
223
  config: audioEncoderConfig,
224
- logLevel
224
+ logLevel,
225
+ onNewAudioSampleRate
225
226
  }) => {
226
227
  if (signal.aborted) {
227
228
  throw new Error("Not creating audio encoder, already aborted");
@@ -258,10 +259,10 @@ var createAudioEncoder = ({
258
259
  close();
259
260
  };
260
261
  signal.addEventListener("abort", onAbort);
261
- if (codec !== "opus") {
262
- throw new Error('Only `codec: "opus"` is supported currently');
262
+ if (codec !== "opus" && codec !== "aac") {
263
+ throw new Error('Only `codec: "opus"` and `codec: "aac"` is supported currently');
263
264
  }
264
- encoder.configure(audioEncoderConfig);
265
+ const wantedSampleRate = audioEncoderConfig.sampleRate;
265
266
  const encodeFrame = async (audioData) => {
266
267
  if (encoder.state === "closed") {
267
268
  return;
@@ -270,6 +271,17 @@ var createAudioEncoder = ({
270
271
  if (encoder.state === "closed") {
271
272
  return;
272
273
  }
274
+ if (encoder.state === "unconfigured") {
275
+ if (audioData.sampleRate === wantedSampleRate) {
276
+ encoder.configure(audioEncoderConfig);
277
+ } else {
278
+ encoder.configure({
279
+ ...audioEncoderConfig,
280
+ sampleRate: audioData.sampleRate
281
+ });
282
+ onNewAudioSampleRate(audioData.sampleRate);
283
+ }
284
+ }
273
285
  encoder.encode(audioData);
274
286
  ioSynchronizer.inputItem(audioData.timestamp, true);
275
287
  };
@@ -290,9 +302,214 @@ var createAudioEncoder = ({
290
302
  }
291
303
  };
292
304
  };
305
+ // src/can-copy-audio-track.ts
306
+ var canCopyAudioTrack = ({
307
+ inputCodec,
308
+ container
309
+ }) => {
310
+ if (container === "webm") {
311
+ return inputCodec === "opus";
312
+ }
313
+ if (container === "mp4") {
314
+ return inputCodec === "aac";
315
+ }
316
+ throw new Error(`Unhandled codec: ${container}`);
317
+ };
318
+ // src/can-copy-video-track.ts
319
+ var canCopyVideoTrack = ({
320
+ inputCodec,
321
+ container
322
+ }) => {
323
+ if (container === "webm") {
324
+ return inputCodec === "vp8" || inputCodec === "vp9";
325
+ }
326
+ if (container === "mp4") {
327
+ return inputCodec === "h264" || inputCodec === "h265";
328
+ }
329
+ throw new Error(`Unhandled codec: ${container}`);
330
+ };
331
+ // src/audio-decoder-config.ts
332
+ var getAudioDecoderConfig = async (config) => {
333
+ if (typeof AudioDecoder === "undefined") {
334
+ return null;
335
+ }
336
+ if (typeof EncodedAudioChunk === "undefined") {
337
+ return null;
338
+ }
339
+ if ((await AudioDecoder.isConfigSupported(config)).supported) {
340
+ return config;
341
+ }
342
+ return null;
343
+ };
344
+
345
+ // src/audio-encoder-config.ts
346
+ var getCodecString = (audioCodec) => {
347
+ if (audioCodec === "opus") {
348
+ return "opus";
349
+ }
350
+ if (audioCodec === "aac") {
351
+ return "mp4a.40.02";
352
+ }
353
+ throw new Error(`Unsupported audio codec: ${audioCodec}`);
354
+ };
355
+ var getAudioEncoderConfig = async (config) => {
356
+ const actualConfig = {
357
+ ...config,
358
+ codec: getCodecString(config.codec)
359
+ };
360
+ if (typeof AudioEncoder === "undefined") {
361
+ return null;
362
+ }
363
+ if ((await AudioEncoder.isConfigSupported(actualConfig)).supported) {
364
+ return actualConfig;
365
+ }
366
+ return null;
367
+ };
368
+
369
+ // src/can-reencode-audio-track.ts
370
+ var canReencodeAudioTrack = async ({
371
+ track,
372
+ audioCodec,
373
+ bitrate
374
+ }) => {
375
+ const audioDecoderConfig = await getAudioDecoderConfig(track);
376
+ const audioEncoderConfig = await getAudioEncoderConfig({
377
+ codec: audioCodec,
378
+ numberOfChannels: track.numberOfChannels,
379
+ sampleRate: track.sampleRate,
380
+ bitrate
381
+ });
382
+ return Boolean(audioDecoderConfig && audioEncoderConfig);
383
+ };
384
+ // src/video-decoder-config.ts
385
+ var getVideoDecoderConfigWithHardwareAcceleration = async (config) => {
386
+ if (typeof VideoDecoder === "undefined") {
387
+ return null;
388
+ }
389
+ const hardware = {
390
+ ...config,
391
+ hardwareAcceleration: "prefer-hardware"
392
+ };
393
+ if ((await VideoDecoder.isConfigSupported(hardware)).supported) {
394
+ return hardware;
395
+ }
396
+ const software = {
397
+ ...config,
398
+ hardwareAcceleration: "prefer-software"
399
+ };
400
+ if ((await VideoDecoder.isConfigSupported(software)).supported) {
401
+ return software;
402
+ }
403
+ return null;
404
+ };
405
+
406
+ // src/choose-correct-avc1-profile.ts
407
+ var chooseCorrectAvc1Profile = ({
408
+ width,
409
+ height,
410
+ fps
411
+ }) => {
412
+ const profiles = [
413
+ { level: "3.1", hex: "1F", width: 1280, height: 720, fps: 30 },
414
+ { level: "3.2", hex: "20", width: 1280, height: 1024, fps: 42.2 },
415
+ { level: "4.0", hex: "28", width: 2048, height: 1024, fps: 30 },
416
+ { level: "4.1", hex: "29", width: 2048, height: 1024, fps: 30 },
417
+ { level: "4.2", hex: "2A", width: 2048, height: 1080, fps: 60 },
418
+ { level: "5.0", hex: "32", width: 3672, height: 1536, fps: 26.7 },
419
+ { level: "5.1", hex: "33", width: 4096, height: 2304, fps: 26.7 },
420
+ { level: "5.2", hex: "34", width: 4096, height: 2304, fps: 56.3 },
421
+ { level: "6.0", hex: "3C", width: 8192, height: 4320, fps: 30.2 },
422
+ { level: "6.1", hex: "3D", width: 8192, height: 4320, fps: 60.4 },
423
+ { level: "6.2", hex: "3E", width: 8192, height: 4320, fps: 120.8 }
424
+ ];
425
+ const profile = profiles.find((p) => {
426
+ if (width > p.width) {
427
+ return false;
428
+ }
429
+ if (height > p.height) {
430
+ return false;
431
+ }
432
+ const fallbackFps = fps ?? 60;
433
+ return fallbackFps <= p.fps;
434
+ });
435
+ if (!profile) {
436
+ throw new Error(`No suitable AVC1 profile found for ${width}x${height}@${fps}fps`);
437
+ }
438
+ return `avc1.6400${profile.hex}`;
439
+ };
440
+
441
+ // src/video-encoder-config.ts
442
+ var getVideoEncoderConfig = async ({
443
+ codec,
444
+ height,
445
+ width,
446
+ fps
447
+ }) => {
448
+ if (typeof VideoEncoder === "undefined") {
449
+ return null;
450
+ }
451
+ const config = {
452
+ codec: codec === "h264" ? chooseCorrectAvc1Profile({ fps, height, width }) : codec === "vp9" ? "vp09.00.10.08" : codec,
453
+ height,
454
+ width
455
+ };
456
+ const hardware = {
457
+ ...config,
458
+ hardwareAcceleration: "prefer-hardware"
459
+ };
460
+ if ((await VideoEncoder.isConfigSupported(hardware)).supported) {
461
+ return hardware;
462
+ }
463
+ const software = {
464
+ ...config,
465
+ hardwareAcceleration: "prefer-software"
466
+ };
467
+ if ((await VideoEncoder.isConfigSupported(software)).supported) {
468
+ return software;
469
+ }
470
+ return null;
471
+ };
472
+
473
+ // src/can-reencode-video-track.ts
474
+ var canReencodeVideoTrack = async ({
475
+ videoCodec,
476
+ track
477
+ }) => {
478
+ const videoEncoderConfig = await getVideoEncoderConfig({
479
+ codec: videoCodec,
480
+ height: track.displayAspectHeight,
481
+ width: track.displayAspectWidth,
482
+ fps: track.fps
483
+ });
484
+ const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
485
+ return Boolean(videoDecoderConfig && videoEncoderConfig);
486
+ };
487
+ // src/codec-id.ts
488
+ var availableContainers = ["webm", "mp4"];
489
+ var getAvailableContainers = () => {
490
+ return availableContainers;
491
+ };
492
+ var getAvailableVideoCodecs = (container) => {
493
+ if (container === "mp4") {
494
+ return ["h264"];
495
+ }
496
+ if (container === "webm") {
497
+ return ["vp8", "vp9"];
498
+ }
499
+ throw new Error(`Unsupported container: ${container}`);
500
+ };
501
+ var getAvailableAudioCodecs = (container) => {
502
+ if (container === "mp4") {
503
+ return ["aac"];
504
+ }
505
+ if (container === "webm") {
506
+ return ["opus"];
507
+ }
508
+ throw new Error(`Unsupported container: ${container}`);
509
+ };
293
510
  // src/convert-media.ts
294
511
  import {
295
- MediaParserInternals as MediaParserInternals2,
512
+ MediaParserInternals as MediaParserInternals4,
296
513
  parseMedia
297
514
  } from "@remotion/media-parser";
298
515
 
@@ -316,122 +533,141 @@ var autoSelectWriter = async (writer, logLevel) => {
316
533
  // src/calculate-progress.ts
317
534
  var calculateProgress = ({
318
535
  millisecondsWritten,
319
- expectedOutputMilliseconds
536
+ expectedOutputDurationInMs
320
537
  }) => {
321
- if (expectedOutputMilliseconds === null) {
538
+ if (expectedOutputDurationInMs === null) {
322
539
  return null;
323
540
  }
324
- return millisecondsWritten / expectedOutputMilliseconds;
541
+ return millisecondsWritten / expectedOutputDurationInMs;
325
542
  };
326
543
 
327
544
  // src/error-cause.ts
328
545
  var error_cause_default = Error;
329
546
 
330
- // src/audio-decoder-config.ts
331
- var getAudioDecoderConfig = async (config) => {
332
- if (typeof AudioDecoder === "undefined") {
333
- return null;
334
- }
335
- if ((await AudioDecoder.isConfigSupported(config)).supported) {
336
- return config;
337
- }
338
- return null;
547
+ // src/convert-encoded-chunk.ts
548
+ var convertEncodedChunk = (chunk, trackId) => {
549
+ const arr = new Uint8Array(chunk.byteLength);
550
+ chunk.copyTo(arr);
551
+ return {
552
+ data: arr,
553
+ duration: chunk.duration ?? undefined,
554
+ timestamp: chunk.timestamp,
555
+ type: chunk.type,
556
+ cts: chunk.timestamp,
557
+ dts: chunk.timestamp,
558
+ trackId
559
+ };
339
560
  };
340
561
 
341
- // src/audio-encoder-config.ts
342
- var getAudioEncoderConfig = async (config) => {
343
- if (typeof AudioEncoder === "undefined") {
344
- return null;
562
+ // src/default-on-audio-track-handler.ts
563
+ import { MediaParserInternals as MediaParserInternals2 } from "@remotion/media-parser";
564
+
565
+ // src/get-default-audio-codec.ts
566
+ var getDefaultAudioCodec = ({
567
+ container
568
+ }) => {
569
+ if (container === "webm") {
570
+ return "opus";
345
571
  }
346
- if ((await AudioEncoder.isConfigSupported(config)).supported) {
347
- return config;
572
+ if (container === "mp4") {
573
+ return "aac";
348
574
  }
349
- return null;
575
+ throw new Error(`Unhandled container: ${container}`);
350
576
  };
351
577
 
352
- // src/resolve-audio-action.ts
353
- var canCopyAudioTrack = (inputCodec, outputCodec) => {
354
- if (outputCodec === "opus") {
355
- return inputCodec === "opus";
356
- }
357
- throw new Error(`Unhandled codec: ${outputCodec}`);
358
- };
359
- var defaultResolveAudioAction = ({
360
- canReencode,
361
- canCopy
578
+ // src/default-on-audio-track-handler.ts
579
+ var DEFAULT_BITRATE = 128000;
580
+ var defaultOnAudioTrackHandler = async ({
581
+ track,
582
+ defaultAudioCodec,
583
+ logLevel,
584
+ container
362
585
  }) => {
586
+ const bitrate = DEFAULT_BITRATE;
587
+ const canCopy = canCopyAudioTrack({
588
+ inputCodec: track.codecWithoutConfig,
589
+ container
590
+ });
363
591
  if (canCopy) {
364
- return "copy";
592
+ MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can copy track, therefore copying`);
593
+ return Promise.resolve({ type: "copy" });
365
594
  }
595
+ const audioCodec = defaultAudioCodec ?? getDefaultAudioCodec({ container });
596
+ const canReencode = await canReencodeAudioTrack({
597
+ audioCodec,
598
+ track,
599
+ bitrate
600
+ });
366
601
  if (canReencode) {
367
- return "reencode";
602
+ MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
603
+ return Promise.resolve({
604
+ type: "reencode",
605
+ bitrate,
606
+ audioCodec
607
+ });
368
608
  }
369
- return "drop";
370
- };
371
- var resolveAudioAction = async ({
372
- audioDecoderConfig,
373
- audioEncoderConfig,
374
- track,
375
- audioCodec,
376
- resolverFunction
377
- }) => {
378
- const canReencode = Boolean(audioDecoderConfig && audioEncoderConfig);
379
- const canCopy = canCopyAudioTrack(track.codecWithoutConfig, audioCodec);
380
- const resolved = await resolverFunction({
381
- canReencode,
382
- canCopy
383
- });
384
- return resolved;
609
+ MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
610
+ return Promise.resolve({ type: "fail" });
385
611
  };
386
612
 
387
613
  // src/on-audio-track.ts
388
614
  var makeAudioTrackHandler = ({
389
615
  state,
390
- audioCodec,
616
+ defaultAudioCodec: audioCodec,
391
617
  convertMediaState,
392
618
  controller,
393
619
  abortConversion,
394
620
  onMediaStateUpdate,
395
621
  onAudioTrack,
396
- bitrate,
397
- logLevel
622
+ logLevel,
623
+ container
398
624
  }) => async (track) => {
399
- const audioEncoderConfig = await getAudioEncoderConfig({
400
- codec: audioCodec,
401
- numberOfChannels: track.numberOfChannels,
402
- sampleRate: track.sampleRate,
403
- bitrate
404
- });
405
- const audioDecoderConfig = await getAudioDecoderConfig({
406
- codec: track.codec,
407
- numberOfChannels: track.numberOfChannels,
408
- sampleRate: track.sampleRate,
409
- description: track.description
410
- });
411
- const audioOperation = await resolveAudioAction({
412
- audioDecoderConfig,
413
- audioEncoderConfig,
414
- audioCodec,
625
+ const audioOperation = await (onAudioTrack ?? defaultOnAudioTrackHandler)({
626
+ defaultAudioCodec: audioCodec,
415
627
  track,
416
- resolverFunction: onAudioTrack
628
+ logLevel,
629
+ container
417
630
  });
418
- if (audioOperation === "drop") {
631
+ if (audioOperation.type === "drop") {
419
632
  return null;
420
633
  }
421
- if (audioOperation === "copy") {
634
+ if (audioOperation.type === "fail") {
635
+ throw new error_cause_default(`Audio track with ID ${track.trackId} could resolved with {"type": "fail"}. This could mean that this audio track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
636
+ }
637
+ if (audioOperation.type === "copy") {
422
638
  const addedTrack = await state.addTrack({
423
639
  type: "audio",
424
- codec: audioCodec,
640
+ codec: track.codecWithoutConfig,
425
641
  numberOfChannels: track.numberOfChannels,
426
642
  sampleRate: track.sampleRate,
427
- codecPrivate: track.codecPrivate
643
+ codecPrivate: track.codecPrivate,
644
+ timescale: track.timescale
428
645
  });
646
+ Log.verbose(logLevel, `Copying audio track ${track.trackId} as track ${addedTrack.trackNumber}. Timescale = ${track.timescale}, codec = ${track.codecWithoutConfig} (${track.codec}) `);
429
647
  return async (audioSample) => {
430
- await state.addSample(new EncodedAudioChunk(audioSample), addedTrack.trackNumber, false);
648
+ await state.addSample({
649
+ chunk: audioSample,
650
+ trackNumber: addedTrack.trackNumber,
651
+ isVideo: false,
652
+ timescale: track.timescale,
653
+ codecPrivate: track.codecPrivate
654
+ });
431
655
  convertMediaState.encodedAudioFrames++;
432
656
  onMediaStateUpdate?.({ ...convertMediaState });
433
657
  };
434
658
  }
659
+ const audioEncoderConfig = await getAudioEncoderConfig({
660
+ numberOfChannels: track.numberOfChannels,
661
+ sampleRate: track.sampleRate,
662
+ codec: audioOperation.audioCodec,
663
+ bitrate: audioOperation.bitrate
664
+ });
665
+ const audioDecoderConfig = await getAudioDecoderConfig({
666
+ codec: track.codec,
667
+ numberOfChannels: track.numberOfChannels,
668
+ sampleRate: track.sampleRate,
669
+ description: track.description
670
+ });
435
671
  if (!audioEncoderConfig) {
436
672
  abortConversion(new error_cause_default(`Could not configure audio encoder of track ${track.trackId}`));
437
673
  return null;
@@ -440,16 +676,27 @@ var makeAudioTrackHandler = ({
440
676
  abortConversion(new error_cause_default(`Could not configure audio decoder of track ${track.trackId}`));
441
677
  return null;
442
678
  }
679
+ const codecPrivate = audioOperation.audioCodec === "aac" ? new Uint8Array([17, 144]) : null;
443
680
  const { trackNumber } = await state.addTrack({
444
681
  type: "audio",
445
- codec: audioCodec,
682
+ codec: audioOperation.audioCodec,
446
683
  numberOfChannels: track.numberOfChannels,
447
684
  sampleRate: track.sampleRate,
448
- codecPrivate: null
685
+ codecPrivate,
686
+ timescale: track.timescale
449
687
  });
450
688
  const audioEncoder = createAudioEncoder({
689
+ onNewAudioSampleRate: (sampleRate) => {
690
+ state.updateTrackSampleRate({ sampleRate, trackNumber });
691
+ },
451
692
  onChunk: async (chunk) => {
452
- await state.addSample(chunk, trackNumber, false);
693
+ await state.addSample({
694
+ chunk: convertEncodedChunk(chunk, trackNumber),
695
+ trackNumber,
696
+ isVideo: false,
697
+ timescale: track.timescale,
698
+ codecPrivate
699
+ });
453
700
  convertMediaState.encodedAudioFrames++;
454
701
  onMediaStateUpdate?.({ ...convertMediaState });
455
702
  },
@@ -458,7 +705,7 @@ var makeAudioTrackHandler = ({
458
705
  cause: err
459
706
  }));
460
707
  },
461
- codec: audioCodec,
708
+ codec: audioOperation.audioCodec,
462
709
  signal: controller.signal,
463
710
  config: audioEncoderConfig,
464
711
  logLevel
@@ -490,47 +737,92 @@ var makeAudioTrackHandler = ({
490
737
  };
491
738
  };
492
739
 
493
- // src/resolve-video-action.ts
494
- var canCopyVideoTrack = (inputCodec, outputCodec) => {
495
- if (outputCodec === "vp8") {
496
- return inputCodec === "vp8";
497
- }
498
- if (outputCodec === "vp9") {
499
- return inputCodec === "vp9";
740
+ // src/arraybuffer-to-uint8-array.ts
741
+ var arrayBufferToUint8Array = (buffer) => {
742
+ return buffer ? new Uint8Array(buffer) : null;
743
+ };
744
+
745
+ // src/default-on-video-track-handler.ts
746
+ import { MediaParserInternals as MediaParserInternals3 } from "@remotion/media-parser";
747
+
748
+ // src/get-default-video-codec.ts
749
+ var getDefaultVideoCodec = ({
750
+ container
751
+ }) => {
752
+ if (container === "webm") {
753
+ return "vp8";
500
754
  }
501
- throw new Error(`Unhandled codec: ${outputCodec}`);
755
+ throw new Error(`Unhandled container: ${container} satisfies never`);
502
756
  };
503
- var defaultResolveVideoAction = ({
504
- canReencode,
505
- canCopy
757
+
758
+ // src/default-on-video-track-handler.ts
759
+ var defaultOnVideoTrackHandler = async ({
760
+ track,
761
+ defaultVideoCodec,
762
+ logLevel,
763
+ container
506
764
  }) => {
765
+ const canCopy = canCopyVideoTrack({
766
+ inputCodec: track.codecWithoutConfig,
767
+ container
768
+ });
507
769
  if (canCopy) {
508
- return "copy";
770
+ MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Can copy, therefore copying`);
771
+ return Promise.resolve({ type: "copy" });
509
772
  }
773
+ const videoCodec = defaultVideoCodec ?? getDefaultVideoCodec({ container });
774
+ const canReencode = await canReencodeVideoTrack({
775
+ videoCodec,
776
+ track
777
+ });
510
778
  if (canReencode) {
511
- return "reencode";
779
+ MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Cannot copy, but re-enconde, therefore re-encoding`);
780
+ return Promise.resolve({ type: "reencode", videoCodec });
512
781
  }
513
- return "drop";
782
+ MediaParserInternals3.Log.verbose(logLevel, `Track ${track.trackId} (video): Can neither copy nor re-encode, therefore failing`);
783
+ return Promise.resolve({ type: "fail" });
514
784
  };
515
- var resolveVideoAction = async ({
516
- videoDecoderConfig,
517
- videoEncoderConfig,
785
+
786
+ // src/on-frame.ts
787
+ var onFrame = async ({
788
+ frame,
789
+ onVideoFrame,
790
+ videoEncoder,
791
+ onMediaStateUpdate,
518
792
  track,
519
- videoCodec,
520
- resolverFunction
793
+ convertMediaState
521
794
  }) => {
522
- const canReencode = Boolean(videoDecoderConfig && videoEncoderConfig);
523
- const canCopy = canCopyVideoTrack(track.codecWithoutConfig, videoCodec);
524
- const resolved = await resolverFunction({
525
- canReencode,
526
- canCopy
527
- });
528
- return resolved;
795
+ const newFrame = onVideoFrame ? await onVideoFrame({ frame, track }) : frame;
796
+ if (newFrame.codedHeight !== frame.codedHeight) {
797
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different codedHeight (${newFrame.codedHeight}) than the input frame (${frame.codedHeight})`);
798
+ }
799
+ if (newFrame.codedWidth !== frame.codedWidth) {
800
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different codedWidth (${newFrame.codedWidth}) than the input frame (${frame.codedWidth})`);
801
+ }
802
+ if (newFrame.displayWidth !== frame.displayWidth) {
803
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayWidth (${newFrame.displayWidth}) than the input frame (${newFrame.displayHeight})`);
804
+ }
805
+ if (newFrame.displayHeight !== frame.displayHeight) {
806
+ throw new Error(`Returned VideoFrame of track ${track.trackId} has different displayHeight (${newFrame.displayHeight}) than the input frame (${newFrame.displayHeight})`);
807
+ }
808
+ if (newFrame.timestamp !== frame.timestamp) {
809
+ 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`);
810
+ }
811
+ if (newFrame.duration !== frame.duration) {
812
+ 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`);
813
+ }
814
+ await videoEncoder.encodeFrame(newFrame, newFrame.timestamp);
815
+ convertMediaState.decodedVideoFrames++;
816
+ onMediaStateUpdate?.({ ...convertMediaState });
817
+ newFrame.close();
818
+ if (frame !== newFrame) {
819
+ frame.close();
820
+ }
529
821
  };
530
822
 
531
823
  // src/video-decoder.ts
532
824
  var createVideoDecoder = ({
533
- onFrame,
825
+ onFrame: onFrame2,
534
826
  onError,
535
827
  signal,
536
828
  config,
@@ -549,7 +841,7 @@ var createVideoDecoder = ({
549
841
  if (signal.aborted) {
550
842
  return;
551
843
  }
552
- return onFrame(inputFrame);
844
+ return onFrame2(inputFrame);
553
845
  }).then(() => {
554
846
  ioSynchronizer.onProcessed();
555
847
  signal.removeEventListener("abort", abortHandler);
@@ -597,9 +889,13 @@ var createVideoDecoder = ({
597
889
  },
598
890
  waitForFinish: async () => {
599
891
  await videoDecoder.flush();
892
+ Log.verbose(logLevel, "Flushed video decoder");
600
893
  await ioSynchronizer.waitForFinish();
894
+ Log.verbose(logLevel, "IO synchro finished");
601
895
  await outputQueue;
896
+ Log.verbose(logLevel, "Output queue finished");
602
897
  await inputQueue;
898
+ Log.verbose(logLevel, "Input queue finished");
603
899
  },
604
900
  close,
605
901
  flush: async () => {
@@ -608,28 +904,6 @@ var createVideoDecoder = ({
608
904
  };
609
905
  };
610
906
 
611
- // src/video-decoder-config.ts
612
- var getVideoDecoderConfigWithHardwareAcceleration = async (config) => {
613
- if (typeof VideoDecoder === "undefined") {
614
- return null;
615
- }
616
- const hardware = {
617
- ...config,
618
- hardwareAcceleration: "prefer-hardware"
619
- };
620
- if ((await VideoDecoder.isConfigSupported(hardware)).supported) {
621
- return hardware;
622
- }
623
- const software = {
624
- ...config,
625
- hardwareAcceleration: "prefer-software"
626
- };
627
- if ((await VideoDecoder.isConfigSupported(software)).supported) {
628
- return software;
629
- }
630
- return null;
631
- };
632
-
633
907
  // src/video-encoder.ts
634
908
  var createVideoEncoder = ({
635
909
  onChunk,
@@ -647,17 +921,14 @@ var createVideoEncoder = ({
647
921
  error(error) {
648
922
  onError(error);
649
923
  },
650
- output(chunk) {
651
- if (chunk.duration === null) {
652
- throw new Error("Duration is null");
653
- }
654
- const timestamp = chunk.timestamp + chunk.duration;
924
+ output(chunk, metadata) {
925
+ const timestamp = chunk.timestamp + (chunk.duration ?? 0);
655
926
  ioSynchronizer.onOutput(timestamp);
656
927
  outputQueue = outputQueue.then(() => {
657
928
  if (signal.aborted) {
658
929
  return;
659
930
  }
660
- return onChunk(chunk);
931
+ return onChunk(chunk, metadata ?? null);
661
932
  }).then(() => {
662
933
  ioSynchronizer.onProcessed();
663
934
  return Promise.resolve();
@@ -715,28 +986,6 @@ var createVideoEncoder = ({
715
986
  };
716
987
  };
717
988
 
718
- // src/video-encoder-config.ts
719
- var getVideoEncoderConfig = async (config) => {
720
- if (typeof VideoEncoder === "undefined") {
721
- return null;
722
- }
723
- const hardware = {
724
- ...config,
725
- hardwareAcceleration: "prefer-hardware"
726
- };
727
- if ((await VideoEncoder.isConfigSupported(hardware)).supported) {
728
- return hardware;
729
- }
730
- const software = {
731
- ...config,
732
- hardwareAcceleration: "prefer-software"
733
- };
734
- if ((await VideoEncoder.isConfigSupported(software)).supported) {
735
- return software;
736
- }
737
- return null;
738
- };
739
-
740
989
  // src/on-video-track.ts
741
990
  var makeVideoTrackHandler = ({
742
991
  state,
@@ -745,44 +994,56 @@ var makeVideoTrackHandler = ({
745
994
  abortConversion,
746
995
  convertMediaState,
747
996
  controller,
748
- videoCodec,
997
+ defaultVideoCodec,
749
998
  onVideoTrack,
750
- logLevel
999
+ logLevel,
1000
+ container
751
1001
  }) => async (track) => {
752
1002
  if (controller.signal.aborted) {
753
1003
  throw new error_cause_default("Aborted");
754
1004
  }
755
- const videoEncoderConfig = await getVideoEncoderConfig({
756
- codec: videoCodec === "vp9" ? "vp09.00.10.08" : videoCodec,
757
- height: track.displayAspectHeight,
758
- width: track.displayAspectWidth
759
- });
760
- const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
761
- const videoOperation = await resolveVideoAction({
762
- videoDecoderConfig,
763
- videoEncoderConfig,
1005
+ const videoOperation = await (onVideoTrack ?? defaultOnVideoTrackHandler)({
764
1006
  track,
765
- videoCodec,
766
- resolverFunction: onVideoTrack
1007
+ defaultVideoCodec,
1008
+ logLevel,
1009
+ container
767
1010
  });
768
- if (videoOperation === "drop") {
1011
+ if (videoOperation.type === "drop") {
769
1012
  return null;
770
1013
  }
771
- if (videoOperation === "copy") {
1014
+ if (videoOperation.type === "fail") {
1015
+ throw new error_cause_default(`Video track with ID ${track.trackId} could resolved with {"type": "fail"}. This could mean that this video track could neither be copied to the output container or re-encoded. You have the option to drop the track instead of failing it: https://remotion.dev/docs/webcodecs/track-transformation`);
1016
+ }
1017
+ if (videoOperation.type === "copy") {
1018
+ Log.verbose(logLevel, `Copying video track with codec ${track.codec} and timescale ${track.timescale}`);
772
1019
  const videoTrack = await state.addTrack({
773
1020
  type: "video",
774
1021
  color: track.color,
775
1022
  width: track.codedWidth,
776
1023
  height: track.codedHeight,
777
1024
  codec: track.codecWithoutConfig,
778
- codecPrivate: track.codecPrivate
1025
+ codecPrivate: track.codecPrivate,
1026
+ timescale: track.timescale
779
1027
  });
780
1028
  return async (sample) => {
781
- await state.addSample(new EncodedVideoChunk(sample), videoTrack.trackNumber, true);
1029
+ await state.addSample({
1030
+ chunk: sample,
1031
+ trackNumber: videoTrack.trackNumber,
1032
+ isVideo: true,
1033
+ timescale: track.timescale,
1034
+ codecPrivate: track.codecPrivate
1035
+ });
782
1036
  convertMediaState.decodedVideoFrames++;
783
1037
  onMediaStateUpdate?.({ ...convertMediaState });
784
1038
  };
785
1039
  }
1040
+ const videoEncoderConfig = await getVideoEncoderConfig({
1041
+ codec: videoOperation.videoCodec,
1042
+ height: track.displayAspectHeight,
1043
+ width: track.displayAspectWidth,
1044
+ fps: track.fps
1045
+ });
1046
+ const videoDecoderConfig = await getVideoDecoderConfigWithHardwareAcceleration(track);
786
1047
  if (videoEncoderConfig === null) {
787
1048
  abortConversion(new error_cause_default(`Could not configure video encoder of track ${track.trackId}`));
788
1049
  return null;
@@ -796,12 +1057,20 @@ var makeVideoTrackHandler = ({
796
1057
  color: track.color,
797
1058
  width: track.codedWidth,
798
1059
  height: track.codedHeight,
799
- codec: videoCodec,
800
- codecPrivate: null
1060
+ codec: videoOperation.videoCodec,
1061
+ codecPrivate: null,
1062
+ timescale: track.timescale
801
1063
  });
1064
+ Log.verbose(logLevel, `Created new video track with ID ${trackNumber}, codec ${videoOperation.videoCodec} and timescale ${track.timescale}`);
802
1065
  const videoEncoder = createVideoEncoder({
803
- onChunk: async (chunk) => {
804
- await state.addSample(chunk, trackNumber, true);
1066
+ onChunk: async (chunk, metadata) => {
1067
+ await state.addSample({
1068
+ chunk: convertEncodedChunk(chunk, trackNumber),
1069
+ trackNumber,
1070
+ isVideo: true,
1071
+ timescale: track.timescale,
1072
+ codecPrivate: arrayBufferToUint8Array(metadata?.decoderConfig?.description ?? null)
1073
+ });
805
1074
  convertMediaState.encodedVideoFrames++;
806
1075
  onMediaStateUpdate?.({ ...convertMediaState });
807
1076
  },
@@ -817,11 +1086,14 @@ var makeVideoTrackHandler = ({
817
1086
  const videoDecoder = createVideoDecoder({
818
1087
  config: videoDecoderConfig,
819
1088
  onFrame: async (frame) => {
820
- await onVideoFrame?.(frame, track);
821
- await videoEncoder.encodeFrame(frame);
822
- convertMediaState.decodedVideoFrames++;
823
- onMediaStateUpdate?.({ ...convertMediaState });
824
- frame.close();
1089
+ await onFrame({
1090
+ convertMediaState,
1091
+ frame,
1092
+ onMediaStateUpdate,
1093
+ track,
1094
+ videoEncoder,
1095
+ onVideoFrame
1096
+ });
825
1097
  },
826
1098
  onError: (err) => {
827
1099
  abortConversion(new error_cause_default(`Video decoder of track ${track.trackId} failed (see .cause of this error)`, {
@@ -832,10 +1104,13 @@ var makeVideoTrackHandler = ({
832
1104
  logLevel
833
1105
  });
834
1106
  state.addWaitForFinishPromise(async () => {
1107
+ Log.verbose(logLevel, "Waiting for video decoder to finish");
835
1108
  await videoDecoder.waitForFinish();
836
- await videoEncoder.waitForFinish();
837
1109
  videoDecoder.close();
1110
+ Log.verbose(logLevel, "Video decoder finished. Waiting for encoder to finish");
1111
+ await videoEncoder.waitForFinish();
838
1112
  videoEncoder.close();
1113
+ Log.verbose(logLevel, "Encoder finished");
839
1114
  });
840
1115
  return async (chunk) => {
841
1116
  await videoDecoder.processSample(chunk);
@@ -848,7 +1123,7 @@ var convertMedia = async function({
848
1123
  onVideoFrame,
849
1124
  onMediaStateUpdate: onMediaStateDoNoCallDirectly,
850
1125
  audioCodec,
851
- to,
1126
+ container,
852
1127
  videoCodec,
853
1128
  signal: userPassedAbortSignal,
854
1129
  onAudioTrack: userAudioResolver,
@@ -862,13 +1137,10 @@ var convertMedia = async function({
862
1137
  if (userPassedAbortSignal?.aborted) {
863
1138
  return Promise.reject(new error_cause_default("Aborted"));
864
1139
  }
865
- if (to !== "webm") {
866
- return Promise.reject(new TypeError('Only `to: "webm"` is supported currently'));
1140
+ if (container !== "webm" && container !== "mp4") {
1141
+ return Promise.reject(new TypeError('Only `to: "webm"` and `to: "mp4"` is supported currently'));
867
1142
  }
868
- if (audioCodec !== "opus") {
869
- return Promise.reject(new TypeError('Only `audioCodec: "opus"` is supported currently'));
870
- }
871
- if (videoCodec !== "vp8" && videoCodec !== "vp9") {
1143
+ if (videoCodec && videoCodec !== "vp8" && videoCodec !== "vp9") {
872
1144
  return Promise.reject(new TypeError('Only `videoCodec: "vp8"` and `videoCodec: "vp9"` are supported currently'));
873
1145
  }
874
1146
  const { resolve, reject, getPromiseToImmediatelyReturn } = withResolversAndWaitForReturn();
@@ -890,7 +1162,7 @@ var convertMedia = async function({
890
1162
  encodedAudioFrames: 0,
891
1163
  bytesWritten: 0,
892
1164
  millisecondsWritten: 0,
893
- expectedOutputMilliseconds: null,
1165
+ expectedOutputDurationInMs: null,
894
1166
  overallProgress: 0
895
1167
  };
896
1168
  const onMediaStateUpdate = (newState) => {
@@ -899,7 +1171,8 @@ var convertMedia = async function({
899
1171
  }
900
1172
  onMediaStateDoNoCallDirectly?.(newState);
901
1173
  };
902
- const state = await MediaParserInternals2.createMedia({
1174
+ const creator = container === "webm" ? MediaParserInternals4.createMatroskaMedia : MediaParserInternals4.createIsoBaseMedia;
1175
+ const state = await creator({
903
1176
  writer: await autoSelectWriter(writer, logLevel),
904
1177
  onBytesProgress: (bytesWritten) => {
905
1178
  convertMediaState.bytesWritten = bytesWritten;
@@ -910,11 +1183,12 @@ var convertMedia = async function({
910
1183
  convertMediaState.millisecondsWritten = millisecondsWritten;
911
1184
  convertMediaState.overallProgress = calculateProgress({
912
1185
  millisecondsWritten: convertMediaState.millisecondsWritten,
913
- expectedOutputMilliseconds: convertMediaState.expectedOutputMilliseconds
1186
+ expectedOutputDurationInMs: convertMediaState.expectedOutputDurationInMs
914
1187
  });
915
1188
  onMediaStateUpdate?.(convertMediaState);
916
1189
  }
917
- }
1190
+ },
1191
+ logLevel
918
1192
  });
919
1193
  const onVideoTrack = makeVideoTrackHandler({
920
1194
  state,
@@ -923,22 +1197,24 @@ var convertMedia = async function({
923
1197
  abortConversion,
924
1198
  convertMediaState,
925
1199
  controller,
926
- videoCodec,
927
- onVideoTrack: userVideoResolver ?? defaultResolveVideoAction,
928
- logLevel
1200
+ defaultVideoCodec: videoCodec ?? null,
1201
+ onVideoTrack: userVideoResolver ?? null,
1202
+ logLevel,
1203
+ container
929
1204
  });
930
1205
  const onAudioTrack = makeAudioTrackHandler({
931
1206
  abortConversion,
932
- audioCodec,
1207
+ defaultAudioCodec: audioCodec ?? null,
933
1208
  controller,
934
1209
  convertMediaState,
935
1210
  onMediaStateUpdate: onMediaStateUpdate ?? null,
936
1211
  state,
937
- onAudioTrack: userAudioResolver ?? defaultResolveAudioAction,
938
- bitrate: 128000,
939
- logLevel
1212
+ onAudioTrack: userAudioResolver ?? null,
1213
+ logLevel,
1214
+ container
940
1215
  });
941
1216
  parseMedia({
1217
+ logLevel,
942
1218
  src,
943
1219
  onVideoTrack,
944
1220
  onAudioTrack,
@@ -957,11 +1233,11 @@ var convertMedia = async function({
957
1233
  if (casted.onDurationInSeconds) {
958
1234
  casted.onDurationInSeconds(durationInSeconds);
959
1235
  }
960
- const expectedOutputMilliseconds = durationInSeconds * 1000;
961
- convertMediaState.expectedOutputMilliseconds = expectedOutputMilliseconds;
1236
+ const expectedOutputDurationInMs = durationInSeconds * 1000;
1237
+ convertMediaState.expectedOutputDurationInMs = expectedOutputDurationInMs;
962
1238
  convertMediaState.overallProgress = calculateProgress({
963
1239
  millisecondsWritten: convertMediaState.millisecondsWritten,
964
- expectedOutputMilliseconds
1240
+ expectedOutputDurationInMs
965
1241
  });
966
1242
  onMediaStateUpdate(convertMediaState);
967
1243
  }
@@ -977,9 +1253,20 @@ var convertMedia = async function({
977
1253
  });
978
1254
  };
979
1255
  export {
1256
+ getDefaultVideoCodec,
1257
+ getDefaultAudioCodec,
1258
+ getAvailableVideoCodecs,
1259
+ getAvailableContainers,
1260
+ getAvailableAudioCodecs,
1261
+ defaultOnVideoTrackHandler,
1262
+ defaultOnAudioTrackHandler,
980
1263
  createVideoEncoder,
981
1264
  createVideoDecoder,
982
1265
  createAudioEncoder,
983
1266
  createAudioDecoder,
984
- convertMedia
1267
+ convertMedia,
1268
+ canReencodeVideoTrack,
1269
+ canReencodeAudioTrack,
1270
+ canCopyVideoTrack,
1271
+ canCopyAudioTrack
985
1272
  };