@remotion/webcodecs 4.0.286 → 4.0.288

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 (81) hide show
  1. package/dist/audio-data/data-types.d.ts +1 -0
  2. package/dist/audio-data/data-types.js +26 -0
  3. package/dist/audio-data/is-planar-format.d.ts +1 -0
  4. package/dist/audio-data/is-planar-format.js +7 -0
  5. package/dist/audio-encoder.js +5 -1
  6. package/dist/can-reencode-audio-track.d.ts +2 -1
  7. package/dist/can-reencode-audio-track.js +2 -2
  8. package/dist/convert-audiodata.d.ts +10 -0
  9. package/dist/convert-audiodata.js +85 -0
  10. package/dist/convert-media.d.ts +2 -1
  11. package/dist/convert-media.js +2 -1
  12. package/dist/create/iso-base-media/codec-specific/create-codec-specific-data.js +8 -0
  13. package/dist/create/iso-base-media/create-iso-base-media.js +2 -0
  14. package/dist/create/iso-base-media/example-stts.js +620 -0
  15. package/dist/create/matroska/create-matroska-media.js +1 -1
  16. package/dist/create/matroska/matroska-utils.d.ts +1 -1
  17. package/dist/default-on-audio-track-handler.js +2 -0
  18. package/dist/esm/index.mjs +183 -24
  19. package/dist/esm/web-fs.mjs +2 -4
  20. package/dist/get-wave-audio-decoder.js +29 -1
  21. package/dist/index.d.ts +1 -0
  22. package/dist/index.js +3 -1
  23. package/dist/on-audio-track-handler.d.ts +1 -0
  24. package/dist/on-audio-track.js +3 -3
  25. package/dist/resample-audiodata.d.ts +4 -0
  26. package/dist/resample-audiodata.js +35 -0
  27. package/dist/wav-audio-encoder.d.ts +1 -1
  28. package/dist/wav-audio-encoder.js +11 -4
  29. package/package.json +12 -7
  30. package/dist/test/avc1.test.d.ts +0 -1
  31. package/dist/test/avc1.test.js +0 -39
  32. package/dist/test/avcc.test.d.ts +0 -1
  33. package/dist/test/avcc.test.js +0 -15
  34. package/dist/test/avi-to-mp4.test.d.ts +0 -1
  35. package/dist/test/avi-to-mp4.test.js +0 -15
  36. package/dist/test/cmt.test.d.ts +0 -1
  37. package/dist/test/cmt.test.js +0 -13
  38. package/dist/test/colr.test.d.ts +0 -1
  39. package/dist/test/colr.test.js +0 -16
  40. package/dist/test/correct-byte-length.test.d.ts +0 -1
  41. package/dist/test/correct-byte-length.test.js +0 -23
  42. package/dist/test/create-ftyp.test.d.ts +0 -1
  43. package/dist/test/create-ftyp.test.js +0 -47
  44. package/dist/test/create-mvhd.test.d.ts +0 -1
  45. package/dist/test/create-mvhd.test.js +0 -108
  46. package/dist/test/ctts.test.d.ts +0 -1
  47. package/dist/test/ctts.test.js +0 -49
  48. package/dist/test/dinf.test.d.ts +0 -1
  49. package/dist/test/dinf.test.js +0 -12
  50. package/dist/test/ilst.test.d.ts +0 -1
  51. package/dist/test/ilst.test.js +0 -22
  52. package/dist/test/mdhd.test.d.ts +0 -1
  53. package/dist/test/mdhd.test.js +0 -17
  54. package/dist/test/meta.test.d.ts +0 -1
  55. package/dist/test/meta.test.js +0 -26
  56. package/dist/test/mp4-header-length.test.d.ts +0 -1
  57. package/dist/test/mp4-header-length.test.js +0 -12
  58. package/dist/test/mp4a.test.d.ts +0 -1
  59. package/dist/test/mp4a.test.js +0 -24
  60. package/dist/test/pasp.test.d.ts +0 -1
  61. package/dist/test/pasp.test.js +0 -12
  62. package/dist/test/remux-serverside.test.d.ts +0 -1
  63. package/dist/test/remux-serverside.test.js +0 -23
  64. package/dist/test/stbl.test.d.ts +0 -0
  65. package/dist/test/stbl.test.js +0 -176
  66. package/dist/test/stco.test.d.ts +0 -1
  67. package/dist/test/stco.test.js +0 -34
  68. package/dist/test/stsc.test.d.ts +0 -1
  69. package/dist/test/stsc.test.js +0 -63
  70. package/dist/test/stss.test.d.ts +0 -1
  71. package/dist/test/stss.test.js +0 -14
  72. package/dist/test/stsz.test.d.ts +0 -1
  73. package/dist/test/stsz.test.js +0 -43
  74. package/dist/test/stts.test.d.ts +0 -1
  75. package/dist/test/stts.test.js +0 -12
  76. package/dist/test/tkhd.test.d.ts +0 -1
  77. package/dist/test/tkhd.test.js +0 -175
  78. package/dist/test/too.test.d.ts +0 -1
  79. package/dist/test/too.test.js +0 -12
  80. package/dist/test/url.test.d.ts +0 -1
  81. package/dist/test/url.test.js +0 -11
@@ -152,7 +152,7 @@ const createMatroskaMedia = async ({ writer, onBytesProgress, onMillisecondsProg
152
152
  }
153
153
  });
154
154
  },
155
- getBlob: async () => {
155
+ getBlob: () => {
156
156
  return w.getBlob();
157
157
  },
158
158
  remove: async () => {
@@ -19,7 +19,7 @@ export type EbmlParsedOrUint8Array<T extends Ebml> = {
19
19
  value: EbmlValueOrUint8Array<T>;
20
20
  minVintWidth: number | null;
21
21
  };
22
- export declare const measureEBMLVarInt: (value: number) => 2 | 1 | 5 | 4 | 3 | 6;
22
+ export declare const measureEBMLVarInt: (value: number) => 2 | 4 | 1 | 5 | 3 | 6;
23
23
  export declare const getVariableInt: (value: number, minWidth: number | null) => Uint8Array<ArrayBuffer>;
24
24
  export declare const makeMatroskaBytes: (fields: PossibleEbmlOrUint8Array) => BytesAndOffset;
25
25
  export type PossibleEbmlOrUint8Array = Prettify<{
@@ -19,6 +19,7 @@ const defaultOnAudioTrackHandler = async ({ track, defaultAudioCodec, logLevel,
19
19
  audioCodec: defaultAudioCodec,
20
20
  track,
21
21
  bitrate,
22
+ sampleRate: null,
22
23
  });
23
24
  if (canReencode) {
24
25
  media_parser_1.MediaParserInternals.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
@@ -26,6 +27,7 @@ const defaultOnAudioTrackHandler = async ({ track, defaultAudioCodec, logLevel,
26
27
  type: 'reencode',
27
28
  bitrate,
28
29
  audioCodec: defaultAudioCodec,
30
+ sampleRate: null,
29
31
  });
30
32
  }
31
33
  media_parser_1.MediaParserInternals.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
@@ -101,8 +101,7 @@ var createContent = async ({ filename }) => {
101
101
  await directoryHandle.removeEntry(actualFilename, {
102
102
  recursive: true
103
103
  });
104
- } catch {
105
- }
104
+ } catch {}
106
105
  };
107
106
  await remove();
108
107
  const fileHandle = await directoryHandle.getFileHandle(actualFilename, {
@@ -129,8 +128,7 @@ var createContent = async ({ filename }) => {
129
128
  await writPromise;
130
129
  try {
131
130
  await writable.close();
132
- } catch {
133
- }
131
+ } catch {}
134
132
  },
135
133
  async getBlob() {
136
134
  const newHandle = await directoryHandle.getFileHandle(actualFilename, {
@@ -430,6 +428,33 @@ var setRemotionImported = () => {
430
428
  };
431
429
 
432
430
  // src/get-wave-audio-decoder.ts
431
+ var getBytesPerSample = (sampleFormat) => {
432
+ if (sampleFormat === "s16") {
433
+ return 2;
434
+ }
435
+ if (sampleFormat === "s32") {
436
+ return 4;
437
+ }
438
+ if (sampleFormat === "f32") {
439
+ return 4;
440
+ }
441
+ if (sampleFormat === "u8") {
442
+ return 1;
443
+ }
444
+ if (sampleFormat === "f32-planar") {
445
+ return 4;
446
+ }
447
+ if (sampleFormat === "s16-planar") {
448
+ return 2;
449
+ }
450
+ if (sampleFormat === "s32-planar") {
451
+ return 4;
452
+ }
453
+ if (sampleFormat === "u8-planar") {
454
+ return 1;
455
+ }
456
+ throw new Error(`Unsupported sample format: ${sampleFormat}`);
457
+ };
433
458
  var getWaveAudioDecoder = ({
434
459
  onFrame,
435
460
  track,
@@ -437,11 +462,12 @@ var getWaveAudioDecoder = ({
437
462
  }) => {
438
463
  let queue = Promise.resolve();
439
464
  const processSample = async (audioSample) => {
465
+ const bytesPerSample = getBytesPerSample(sampleFormat);
440
466
  await onFrame(new AudioData({
441
467
  data: audioSample.data,
442
468
  format: sampleFormat,
443
469
  numberOfChannels: track.numberOfChannels,
444
- numberOfFrames: audioSample.data.byteLength / 2,
470
+ numberOfFrames: audioSample.data.byteLength / bytesPerSample / track.numberOfChannels,
445
471
  sampleRate: track.sampleRate,
446
472
  timestamp: audioSample.timestamp
447
473
  }));
@@ -766,8 +792,7 @@ var createAudioDecoder = ({
766
792
  waitForFinish: async () => {
767
793
  try {
768
794
  await audioDecoder.flush();
769
- } catch {
770
- }
795
+ } catch {}
771
796
  await queue;
772
797
  await ioSynchronizer.waitForFinish(controller);
773
798
  await outputQueue;
@@ -781,25 +806,142 @@ var createAudioDecoder = ({
781
806
  // src/audio-encoder.ts
782
807
  import { MediaParserAbortError } from "@remotion/media-parser";
783
808
 
809
+ // src/audio-data/data-types.ts
810
+ var getDataTypeForAudioFormat = (format) => {
811
+ switch (format) {
812
+ case "f32":
813
+ return Float32Array;
814
+ case "f32-planar":
815
+ return Float32Array;
816
+ case "s16":
817
+ return Int16Array;
818
+ case "s16-planar":
819
+ return Int16Array;
820
+ case "u8":
821
+ return Uint8Array;
822
+ case "u8-planar":
823
+ return Uint8Array;
824
+ case "s32":
825
+ return Int32Array;
826
+ case "s32-planar":
827
+ return Int32Array;
828
+ default:
829
+ throw new Error(`Unsupported audio format: ${format}`);
830
+ }
831
+ };
832
+
833
+ // src/audio-data/is-planar-format.ts
834
+ var isPlanarFormat = (format) => {
835
+ return format.includes("-planar");
836
+ };
837
+
838
+ // src/convert-audiodata.ts
839
+ var validateRange = (format, value) => {
840
+ if (format === "f32" || format === "f32-planar") {
841
+ if (value < -1 || value > 1) {
842
+ throw new Error("All values in a Float32 array must be between -1 and 1");
843
+ }
844
+ }
845
+ };
846
+ var convertAudioData = ({
847
+ audioData,
848
+ newSampleRate = audioData.sampleRate,
849
+ format = audioData.format
850
+ }) => {
851
+ const {
852
+ numberOfChannels,
853
+ sampleRate: currentSampleRate,
854
+ numberOfFrames: currentNumberOfFrames
855
+ } = audioData;
856
+ const ratio = currentSampleRate / newSampleRate;
857
+ const newNumberOfFrames = Math.floor(currentNumberOfFrames / ratio);
858
+ if (newNumberOfFrames === 0) {
859
+ throw new Error("Cannot resample - the given sample rate would result in less than 1 sample");
860
+ }
861
+ if (newSampleRate < 3000 || newSampleRate > 768000) {
862
+ throw new Error("newSampleRate must be between 3000 and 768000");
863
+ }
864
+ if (!format) {
865
+ throw new Error("AudioData format is not set");
866
+ }
867
+ if (format === audioData.format && newNumberOfFrames === currentNumberOfFrames) {
868
+ return audioData.clone();
869
+ }
870
+ const DataType = getDataTypeForAudioFormat(format);
871
+ const isPlanar = isPlanarFormat(format);
872
+ const planes = isPlanar ? numberOfChannels : 1;
873
+ const srcChannels = new Array(planes).fill(true).map(() => new DataType((isPlanar ? 1 : numberOfChannels) * currentNumberOfFrames));
874
+ for (let i = 0;i < planes; i++) {
875
+ audioData.clone().copyTo(srcChannels[i], {
876
+ planeIndex: i,
877
+ format
878
+ });
879
+ }
880
+ const data = new DataType(newNumberOfFrames * numberOfChannels);
881
+ const chunkSize = currentNumberOfFrames / newNumberOfFrames;
882
+ for (let newFrameIndex = 0;newFrameIndex < newNumberOfFrames; newFrameIndex++) {
883
+ const start = Math.floor(newFrameIndex * chunkSize);
884
+ const end = Math.max(Math.floor(start + chunkSize), start + 1);
885
+ if (isPlanar) {
886
+ for (let channelIndex = 0;channelIndex < numberOfChannels; channelIndex++) {
887
+ const chunk = srcChannels[channelIndex].slice(start, end);
888
+ const average = chunk.reduce((a, b) => {
889
+ return a + b;
890
+ }, 0) / chunk.length;
891
+ validateRange(format, average);
892
+ data[newFrameIndex + channelIndex * newNumberOfFrames] = average;
893
+ }
894
+ } else {
895
+ const sampleCountAvg = end - start;
896
+ for (let channelIndex = 0;channelIndex < numberOfChannels; channelIndex++) {
897
+ const items = [];
898
+ for (let k = 0;k < sampleCountAvg; k++) {
899
+ const num = srcChannels[0][(start + k) * numberOfChannels + channelIndex];
900
+ items.push(num);
901
+ }
902
+ const average = items.reduce((a, b) => a + b, 0) / items.length;
903
+ validateRange(format, average);
904
+ data[newFrameIndex * numberOfChannels + channelIndex] = average;
905
+ }
906
+ }
907
+ }
908
+ const newAudioData = new AudioData({
909
+ data,
910
+ format,
911
+ numberOfChannels,
912
+ numberOfFrames: newNumberOfFrames,
913
+ sampleRate: newSampleRate,
914
+ timestamp: audioData.timestamp
915
+ });
916
+ return newAudioData;
917
+ };
918
+
784
919
  // src/wav-audio-encoder.ts
785
920
  var getWaveAudioEncoder = ({
786
921
  onChunk,
787
- controller
922
+ controller,
923
+ config
788
924
  }) => {
789
925
  return {
790
926
  close: () => {
791
927
  return Promise.resolve();
792
928
  },
793
- encodeFrame: (audioData) => {
929
+ encodeFrame: (unconvertedAudioData) => {
794
930
  if (controller._internals.signal.aborted) {
795
931
  return Promise.resolve();
796
932
  }
933
+ const audioData = convertAudioData({
934
+ audioData: unconvertedAudioData,
935
+ newSampleRate: config.sampleRate,
936
+ format: "s16"
937
+ });
938
+ unconvertedAudioData.close();
797
939
  const chunk = {
798
940
  timestamp: audioData.timestamp,
799
941
  duration: audioData.duration,
800
942
  type: "key",
801
- copyTo: (destination) => audioData.copyTo(destination, { planeIndex: 0, format: "s16" }),
802
- byteLength: audioData.allocationSize({ planeIndex: 0, format: "s16" })
943
+ copyTo: (destination) => audioData.copyTo(destination, { planeIndex: 0 }),
944
+ byteLength: audioData.allocationSize({ planeIndex: 0 })
803
945
  };
804
946
  return onChunk(chunk);
805
947
  },
@@ -823,7 +965,11 @@ var createAudioEncoder = ({
823
965
  throw new MediaParserAbortError("Not creating audio encoder, already aborted");
824
966
  }
825
967
  if (codec === "wav") {
826
- return getWaveAudioEncoder({ onChunk, controller });
968
+ return getWaveAudioEncoder({
969
+ onChunk,
970
+ controller,
971
+ config: audioEncoderConfig
972
+ });
827
973
  }
828
974
  const ioSynchronizer = makeIoSynchronizer({
829
975
  logLevel,
@@ -1020,7 +1166,8 @@ var getAudioEncoderConfig = async (config) => {
1020
1166
  var canReencodeAudioTrack = async ({
1021
1167
  track,
1022
1168
  audioCodec,
1023
- bitrate
1169
+ bitrate,
1170
+ sampleRate
1024
1171
  }) => {
1025
1172
  const audioDecoderConfig = await getAudioDecoderConfig(track);
1026
1173
  if (audioCodec === "wav" && audioDecoderConfig) {
@@ -1029,7 +1176,7 @@ var canReencodeAudioTrack = async ({
1029
1176
  const audioEncoderConfig = await getAudioEncoderConfig({
1030
1177
  codec: audioCodec,
1031
1178
  numberOfChannels: track.numberOfChannels,
1032
- sampleRate: track.sampleRate,
1179
+ sampleRate: sampleRate ?? track.sampleRate,
1033
1180
  bitrate
1034
1181
  });
1035
1182
  return Boolean(audioDecoderConfig && audioEncoderConfig);
@@ -1621,14 +1768,16 @@ var defaultOnAudioTrackHandler = async ({
1621
1768
  const canReencode = await canReencodeAudioTrack({
1622
1769
  audioCodec: defaultAudioCodec,
1623
1770
  track,
1624
- bitrate
1771
+ bitrate,
1772
+ sampleRate: null
1625
1773
  });
1626
1774
  if (canReencode) {
1627
1775
  MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Cannot copy, but re-encode, therefore re-encoding`);
1628
1776
  return Promise.resolve({
1629
1777
  type: "reencode",
1630
1778
  bitrate,
1631
- audioCodec: defaultAudioCodec
1779
+ audioCodec: defaultAudioCodec,
1780
+ sampleRate: null
1632
1781
  });
1633
1782
  }
1634
1783
  MediaParserInternals2.Log.verbose(logLevel, `Track ${track.trackId} (audio): Can neither re-encode nor copy, failing render`);
@@ -1710,7 +1859,7 @@ var makeAudioTrackHandler = ({
1710
1859
  }
1711
1860
  const audioEncoderConfig = await getAudioEncoderConfig({
1712
1861
  numberOfChannels: track.numberOfChannels,
1713
- sampleRate: track.sampleRate,
1862
+ sampleRate: audioOperation.sampleRate ?? track.sampleRate,
1714
1863
  codec: audioOperation.audioCodec,
1715
1864
  bitrate: audioOperation.bitrate
1716
1865
  });
@@ -1730,7 +1879,7 @@ var makeAudioTrackHandler = ({
1730
1879
  }
1731
1880
  const codecPrivate = audioOperation.audioCodec === "aac" ? MediaParserInternals3.createAacCodecPrivate({
1732
1881
  audioObjectType: 2,
1733
- sampleRate: audioEncoderConfig.sampleRate,
1882
+ sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
1734
1883
  channelConfiguration: audioEncoderConfig.numberOfChannels,
1735
1884
  codecPrivate: null
1736
1885
  }) : null;
@@ -1738,7 +1887,7 @@ var makeAudioTrackHandler = ({
1738
1887
  type: "audio",
1739
1888
  codec: audioOperation.audioCodec === "wav" ? "pcm-s16" : audioOperation.audioCodec,
1740
1889
  numberOfChannels: audioEncoderConfig.numberOfChannels,
1741
- sampleRate: audioEncoderConfig.sampleRate,
1890
+ sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
1742
1891
  codecPrivate,
1743
1892
  timescale: track.timescale
1744
1893
  });
@@ -3069,6 +3218,9 @@ var createMp4a = ({
3069
3218
  var createCodecSpecificData = (track) => {
3070
3219
  if (track.type === "video") {
3071
3220
  if (track.codec === "h264") {
3221
+ if (!track.codecPrivate) {
3222
+ return new Uint8Array([]);
3223
+ }
3072
3224
  return createAvc1Data({
3073
3225
  avccBox: createAvccBox(track.codecPrivate),
3074
3226
  compressorName: "WebCodecs",
@@ -3082,6 +3234,9 @@ var createCodecSpecificData = (track) => {
3082
3234
  });
3083
3235
  }
3084
3236
  if (track.codec === "h265") {
3237
+ if (!track.codecPrivate) {
3238
+ return new Uint8Array([]);
3239
+ }
3085
3240
  return createHvc1Data({
3086
3241
  hvccBox: createHvccBox(track.codecPrivate),
3087
3242
  compressorName: "WebCodecs",
@@ -3784,7 +3939,9 @@ var createIsoBaseMedia = async ({
3784
3939
  cts: Math.round(chunk.cts / 1e6 * currentTrack.timescale),
3785
3940
  dts: Math.round(chunk.dts / 1e6 * currentTrack.timescale),
3786
3941
  duration: Math.round((chunk.duration ?? 0) / 1e6 * currentTrack.timescale),
3787
- size: chunk.data.length
3942
+ size: chunk.data.length,
3943
+ bigEndian: false,
3944
+ chunkSize: null
3788
3945
  };
3789
3946
  lastChunkWasVideo = isVideo;
3790
3947
  samplePositions[trackNumber].push(samplePositionToAdd);
@@ -4591,7 +4748,7 @@ var createMatroskaMedia = async ({
4591
4748
  }
4592
4749
  });
4593
4750
  },
4594
- getBlob: async () => {
4751
+ getBlob: () => {
4595
4752
  return w.getBlob();
4596
4753
  },
4597
4754
  remove: async () => {
@@ -4801,8 +4958,7 @@ var throttledStateUpdate = ({
4801
4958
  return {
4802
4959
  get: () => currentState,
4803
4960
  update: null,
4804
- stopAndGetLastProgress: () => {
4805
- }
4961
+ stopAndGetLastProgress: () => {}
4806
4962
  };
4807
4963
  }
4808
4964
  let lastUpdated = null;
@@ -4899,6 +5055,7 @@ var convertMedia = async function({
4899
5055
  selectM3uStream,
4900
5056
  selectM3uAssociatedPlaylists,
4901
5057
  expectedDurationInSeconds,
5058
+ seekingHints,
4902
5059
  ...more
4903
5060
  }) {
4904
5061
  if (controller._internals.signal.aborted) {
@@ -5053,7 +5210,8 @@ var convertMedia = async function({
5053
5210
  selectM3uStream: selectM3uStream ?? defaultSelectM3uStreamFn,
5054
5211
  selectM3uAssociatedPlaylists: selectM3uAssociatedPlaylists ?? defaultSelectM3uAssociatedPlaylists,
5055
5212
  makeSamplesStartAtZero: false,
5056
- mp4HeaderSegment: null
5213
+ mp4HeaderSegment: null,
5214
+ seekingHints: seekingHints ?? null
5057
5215
  }).then(() => {
5058
5216
  return state.waitForFinish();
5059
5217
  }).then(() => {
@@ -5115,6 +5273,7 @@ export {
5115
5273
  createAudioEncoder,
5116
5274
  createAudioDecoder,
5117
5275
  convertMedia,
5276
+ convertAudioData,
5118
5277
  canReencodeVideoTrack,
5119
5278
  canReencodeAudioTrack,
5120
5279
  canCopyVideoTrack,
@@ -7,8 +7,7 @@ var createContent = async ({ filename }) => {
7
7
  await directoryHandle.removeEntry(actualFilename, {
8
8
  recursive: true
9
9
  });
10
- } catch {
11
- }
10
+ } catch {}
12
11
  };
13
12
  await remove();
14
13
  const fileHandle = await directoryHandle.getFileHandle(actualFilename, {
@@ -35,8 +34,7 @@ var createContent = async ({ filename }) => {
35
34
  await writPromise;
36
35
  try {
37
36
  await writable.close();
38
- } catch {
39
- }
37
+ } catch {}
40
38
  },
41
39
  async getBlob() {
42
40
  const newHandle = await directoryHandle.getFileHandle(actualFilename, {
@@ -1,15 +1,43 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getWaveAudioDecoder = void 0;
4
+ const getBytesPerSample = (sampleFormat) => {
5
+ if (sampleFormat === 's16') {
6
+ return 2;
7
+ }
8
+ if (sampleFormat === 's32') {
9
+ return 4;
10
+ }
11
+ if (sampleFormat === 'f32') {
12
+ return 4;
13
+ }
14
+ if (sampleFormat === 'u8') {
15
+ return 1;
16
+ }
17
+ if (sampleFormat === 'f32-planar') {
18
+ return 4;
19
+ }
20
+ if (sampleFormat === 's16-planar') {
21
+ return 2;
22
+ }
23
+ if (sampleFormat === 's32-planar') {
24
+ return 4;
25
+ }
26
+ if (sampleFormat === 'u8-planar') {
27
+ return 1;
28
+ }
29
+ throw new Error(`Unsupported sample format: ${sampleFormat}`);
30
+ };
4
31
  // TODO: Should also be subject to throttling
5
32
  const getWaveAudioDecoder = ({ onFrame, track, sampleFormat, }) => {
6
33
  let queue = Promise.resolve();
7
34
  const processSample = async (audioSample) => {
35
+ const bytesPerSample = getBytesPerSample(sampleFormat);
8
36
  await onFrame(new AudioData({
9
37
  data: audioSample.data,
10
38
  format: sampleFormat,
11
39
  numberOfChannels: track.numberOfChannels,
12
- numberOfFrames: audioSample.data.byteLength / 2,
40
+ numberOfFrames: audioSample.data.byteLength / bytesPerSample / track.numberOfChannels,
13
41
  sampleRate: track.sampleRate,
14
42
  timestamp: audioSample.timestamp,
15
43
  }));
package/dist/index.d.ts CHANGED
@@ -6,6 +6,7 @@ export { canCopyAudioTrack } from './can-copy-audio-track';
6
6
  export { canCopyVideoTrack } from './can-copy-video-track';
7
7
  export { canReencodeAudioTrack } from './can-reencode-audio-track';
8
8
  export { canReencodeVideoTrack } from './can-reencode-video-track';
9
+ export { convertAudioData, ConvertAudioDataOptions } from './convert-audiodata';
9
10
  export { convertMedia } from './convert-media';
10
11
  export type { ConvertMediaOnAudioData, ConvertMediaOnProgress, ConvertMediaOnVideoFrame, ConvertMediaProgress, ConvertMediaResult, } from './convert-media';
11
12
  export { defaultOnAudioTrackHandler } from './default-on-audio-track-handler';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WebCodecsInternals = exports.webcodecsController = 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;
3
+ exports.WebCodecsInternals = exports.webcodecsController = exports.createVideoEncoder = exports.createVideoDecoder = exports.getDefaultVideoCodec = exports.getDefaultAudioCodec = exports.getAvailableVideoCodecs = exports.getAvailableContainers = exports.getAvailableAudioCodecs = exports.defaultOnVideoTrackHandler = exports.defaultOnAudioTrackHandler = exports.convertMedia = exports.convertAudioData = exports.canReencodeVideoTrack = exports.canReencodeAudioTrack = exports.canCopyVideoTrack = exports.canCopyAudioTrack = exports.createAudioEncoder = exports.createAudioDecoder = void 0;
4
4
  const rotate_and_resize_video_frame_1 = require("./rotate-and-resize-video-frame");
5
5
  const rotation_1 = require("./rotation");
6
6
  const set_remotion_imported_1 = require("./set-remotion-imported");
@@ -16,6 +16,8 @@ var can_reencode_audio_track_1 = require("./can-reencode-audio-track");
16
16
  Object.defineProperty(exports, "canReencodeAudioTrack", { enumerable: true, get: function () { return can_reencode_audio_track_1.canReencodeAudioTrack; } });
17
17
  var can_reencode_video_track_1 = require("./can-reencode-video-track");
18
18
  Object.defineProperty(exports, "canReencodeVideoTrack", { enumerable: true, get: function () { return can_reencode_video_track_1.canReencodeVideoTrack; } });
19
+ var convert_audiodata_1 = require("./convert-audiodata");
20
+ Object.defineProperty(exports, "convertAudioData", { enumerable: true, get: function () { return convert_audiodata_1.convertAudioData; } });
19
21
  var convert_media_1 = require("./convert-media");
20
22
  Object.defineProperty(exports, "convertMedia", { enumerable: true, get: function () { return convert_media_1.convertMedia; } });
21
23
  var default_on_audio_track_handler_1 = require("./default-on-audio-track-handler");
@@ -5,6 +5,7 @@ export type AudioOperation = {
5
5
  type: 'reencode';
6
6
  bitrate: number;
7
7
  audioCodec: ConvertMediaAudioCodec;
8
+ sampleRate: number | null;
8
9
  } | {
9
10
  type: 'copy';
10
11
  } | {
@@ -58,7 +58,7 @@ const makeAudioTrackHandler = ({ state, defaultAudioCodec: audioCodec, controlle
58
58
  }
59
59
  const audioEncoderConfig = await (0, audio_encoder_config_1.getAudioEncoderConfig)({
60
60
  numberOfChannels: track.numberOfChannels,
61
- sampleRate: track.sampleRate,
61
+ sampleRate: audioOperation.sampleRate ?? track.sampleRate,
62
62
  codec: audioOperation.audioCodec,
63
63
  bitrate: audioOperation.bitrate,
64
64
  });
@@ -79,7 +79,7 @@ const makeAudioTrackHandler = ({ state, defaultAudioCodec: audioCodec, controlle
79
79
  const codecPrivate = audioOperation.audioCodec === 'aac'
80
80
  ? media_parser_1.MediaParserInternals.createAacCodecPrivate({
81
81
  audioObjectType: 2,
82
- sampleRate: audioEncoderConfig.sampleRate,
82
+ sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
83
83
  channelConfiguration: audioEncoderConfig.numberOfChannels,
84
84
  codecPrivate: null,
85
85
  })
@@ -90,7 +90,7 @@ const makeAudioTrackHandler = ({ state, defaultAudioCodec: audioCodec, controlle
90
90
  ? 'pcm-s16'
91
91
  : audioOperation.audioCodec,
92
92
  numberOfChannels: audioEncoderConfig.numberOfChannels,
93
- sampleRate: audioEncoderConfig.sampleRate,
93
+ sampleRate: audioOperation.sampleRate ?? audioEncoderConfig.sampleRate,
94
94
  codecPrivate,
95
95
  timescale: track.timescale,
96
96
  });
@@ -0,0 +1,4 @@
1
+ export declare const resampleAudioData: ({ audioData, newSampleRate, }: {
2
+ audioData: AudioData;
3
+ newSampleRate: number;
4
+ }) => AudioData;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resampleAudioData = void 0;
4
+ const resampleAudioData = ({ audioData, newSampleRate, }) => {
5
+ const { numberOfChannels, sampleRate: currentSampleRate, numberOfFrames, } = audioData;
6
+ const ratio = currentSampleRate / newSampleRate;
7
+ const newNumberOfFrames = Math.floor(numberOfFrames / ratio);
8
+ // TODO: Float32Array hardcoded
9
+ const src = new Float32Array(numberOfChannels * numberOfFrames);
10
+ audioData.clone().copyTo(src, {
11
+ // TODO: Plane index hardcoded
12
+ planeIndex: 0,
13
+ });
14
+ const data = new Float32Array(newNumberOfFrames * numberOfChannels);
15
+ const chunkSize = numberOfFrames / newNumberOfFrames;
16
+ for (let i = 0; i < newNumberOfFrames; i++) {
17
+ const start = Math.floor(i * chunkSize);
18
+ const end = Math.max(Math.floor(start + chunkSize), start + 1);
19
+ const chunk = src.slice(start, end);
20
+ const average = chunk.reduce((a, b) => a + b, 0) / chunk.length;
21
+ for (let j = 0; j < numberOfChannels; j++) {
22
+ data[i * numberOfChannels + j] = average;
23
+ }
24
+ }
25
+ const newAudioData = new AudioData({
26
+ data,
27
+ format: audioData.format,
28
+ numberOfChannels,
29
+ numberOfFrames: newNumberOfFrames,
30
+ sampleRate: newSampleRate,
31
+ timestamp: audioData.timestamp,
32
+ });
33
+ return newAudioData;
34
+ };
35
+ exports.resampleAudioData = resampleAudioData;
@@ -1,2 +1,2 @@
1
1
  import type { AudioEncoderInit, WebCodecsAudioEncoder } from './audio-encoder';
2
- export declare const getWaveAudioEncoder: ({ onChunk, controller, }: Pick<AudioEncoderInit, "onChunk" | "controller">) => WebCodecsAudioEncoder;
2
+ export declare const getWaveAudioEncoder: ({ onChunk, controller, config, }: Pick<AudioEncoderInit, "onChunk" | "controller" | "config">) => WebCodecsAudioEncoder;
@@ -1,21 +1,28 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getWaveAudioEncoder = void 0;
4
- const getWaveAudioEncoder = ({ onChunk, controller, }) => {
4
+ const convert_audiodata_1 = require("./convert-audiodata");
5
+ const getWaveAudioEncoder = ({ onChunk, controller, config, }) => {
5
6
  return {
6
7
  close: () => {
7
8
  return Promise.resolve();
8
9
  },
9
- encodeFrame: (audioData) => {
10
+ encodeFrame: (unconvertedAudioData) => {
10
11
  if (controller._internals.signal.aborted) {
11
12
  return Promise.resolve();
12
13
  }
14
+ const audioData = (0, convert_audiodata_1.convertAudioData)({
15
+ audioData: unconvertedAudioData,
16
+ newSampleRate: config.sampleRate,
17
+ format: 's16',
18
+ });
19
+ unconvertedAudioData.close();
13
20
  const chunk = {
14
21
  timestamp: audioData.timestamp,
15
22
  duration: audioData.duration,
16
23
  type: 'key',
17
- copyTo: (destination) => audioData.copyTo(destination, { planeIndex: 0, format: 's16' }),
18
- byteLength: audioData.allocationSize({ planeIndex: 0, format: 's16' }),
24
+ copyTo: (destination) => audioData.copyTo(destination, { planeIndex: 0 }),
25
+ byteLength: audioData.allocationSize({ planeIndex: 0 }),
19
26
  };
20
27
  return onChunk(chunk);
21
28
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/webcodecs",
3
- "version": "4.0.286",
3
+ "version": "4.0.288",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -12,20 +12,24 @@
12
12
  "url": "https://github.com/remotion-dev/remotion/issues"
13
13
  },
14
14
  "files": [
15
- "dist"
15
+ "dist",
16
+ "!dist/test",
17
+ "!dist/it-tests"
16
18
  ],
17
19
  "author": "Jonny Burger <jonny@remotion.dev>",
18
20
  "license": "Remotion License (See https://remotion.dev/docs/webcodecs#license)",
19
21
  "dependencies": {
20
- "@remotion/media-parser": "4.0.286",
21
- "@remotion/licensing": "4.0.286"
22
+ "@remotion/media-parser": "4.0.288",
23
+ "@remotion/licensing": "4.0.288"
22
24
  },
23
25
  "peerDependencies": {},
24
26
  "devDependencies": {
25
27
  "@types/dom-webcodecs": "0.1.11",
28
+ "playwright": "1.51.1",
29
+ "@playwright/test": "1.51.1",
26
30
  "eslint": "9.19.0",
27
- "@remotion/example-videos": "4.0.286",
28
- "@remotion/eslint-config-internal": "4.0.286"
31
+ "@remotion/eslint-config-internal": "4.0.288",
32
+ "@remotion/example-videos": "4.0.288"
29
33
  },
30
34
  "keywords": [],
31
35
  "publishConfig": {
@@ -76,7 +80,8 @@
76
80
  "scripts": {
77
81
  "formatting": "prettier src --check",
78
82
  "lint": "eslint src",
79
- "test": "bun test src",
83
+ "test": "bun test src/test",
84
+ "testwebcodecs": "playwright test src/it-tests",
80
85
  "watch": "tsc -w",
81
86
  "make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
82
87
  }
@@ -1 +0,0 @@
1
- export {};