@remotion/media 4.0.374 → 4.0.375

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.
@@ -1,6 +1,7 @@
1
1
  import { audioManager } from '../caches';
2
2
  import { combineAudioDataAndClosePrevious } from '../convert-audiodata/combine-audiodata';
3
- import { convertAudioData } from '../convert-audiodata/convert-audiodata';
3
+ import { convertAudioData, fixFloatingPoint, } from '../convert-audiodata/convert-audiodata';
4
+ import { TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from '../convert-audiodata/resample-audiodata';
4
5
  import { getSink } from '../get-sink';
5
6
  import { getTimeInSeconds } from '../get-time-in-seconds';
6
7
  const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds, durationInSeconds: durationNotYetApplyingPlaybackRate, logLevel, loop, playbackRate, audioStreamIndex, trimBefore, trimAfter, fps, }) => {
@@ -44,7 +45,6 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
44
45
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
45
46
  const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
46
47
  audioManager.logOpenFrames();
47
- const trimStartToleranceInSeconds = 0.002;
48
48
  const audioDataArray = [];
49
49
  for (let i = 0; i < samples.length; i++) {
50
50
  const sample = samples[i];
@@ -64,14 +64,18 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
64
64
  // amount of samples to shave from start and end
65
65
  let trimStartInSeconds = 0;
66
66
  let trimEndInSeconds = 0;
67
+ let leadingSilence = null;
67
68
  if (isFirstSample) {
68
- trimStartInSeconds = timeInSeconds - sample.timestamp;
69
- if (trimStartInSeconds < 0 &&
70
- trimStartInSeconds > -trimStartToleranceInSeconds) {
71
- trimStartInSeconds = 0;
72
- }
69
+ trimStartInSeconds = fixFloatingPoint(timeInSeconds - sample.timestamp);
73
70
  if (trimStartInSeconds < 0) {
74
- throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}. ${JSON.stringify({ timeInSeconds, ts: sample.timestamp, d: sample.duration, isFirstSample, isLastSample, durationInSeconds, i, st: samples.map((s) => s.timestamp) })}`);
71
+ const silenceFrames = Math.ceil(fixFloatingPoint(-trimStartInSeconds * TARGET_SAMPLE_RATE));
72
+ leadingSilence = {
73
+ data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
74
+ numberOfFrames: silenceFrames,
75
+ timestamp: timeInSeconds * 1000000,
76
+ durationInMicroSeconds: (silenceFrames / TARGET_SAMPLE_RATE) * 1000000,
77
+ };
78
+ trimStartInSeconds = 0;
75
79
  }
76
80
  }
77
81
  if (isLastSample) {
@@ -93,6 +97,9 @@ const extractAudioInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
93
97
  if (audioData.numberOfFrames === 0) {
94
98
  continue;
95
99
  }
100
+ if (leadingSilence) {
101
+ audioDataArray.push(leadingSilence);
102
+ }
96
103
  audioDataArray.push(audioData);
97
104
  }
98
105
  if (audioDataArray.length === 0) {
@@ -1,11 +1,12 @@
1
1
  import { resampleAudioData, TARGET_NUMBER_OF_CHANNELS, TARGET_SAMPLE_RATE, } from './resample-audiodata';
2
2
  const FORMAT = 's16';
3
3
  export const fixFloatingPoint = (value) => {
4
- if (value % 1 < 0.0000001) {
5
- return Math.floor(value);
4
+ const decimal = Math.abs(value % 1);
5
+ if (decimal < 0.0000001) {
6
+ return value < 0 ? Math.ceil(value) : Math.floor(value);
6
7
  }
7
- if (value % 1 > 0.9999999) {
8
- return Math.ceil(value);
8
+ if (decimal > 0.9999999) {
9
+ return value < 0 ? Math.floor(value) : Math.ceil(value);
9
10
  }
10
11
  return value;
11
12
  };
@@ -2733,11 +2733,12 @@ var getMaxVideoCacheSize = (logLevel) => {
2733
2733
  // src/convert-audiodata/convert-audiodata.ts
2734
2734
  var FORMAT = "s16";
2735
2735
  var fixFloatingPoint2 = (value) => {
2736
- if (value % 1 < 0.0000001) {
2737
- return Math.floor(value);
2736
+ const decimal = Math.abs(value % 1);
2737
+ if (decimal < 0.0000001) {
2738
+ return value < 0 ? Math.ceil(value) : Math.floor(value);
2738
2739
  }
2739
- if (value % 1 > 0.9999999) {
2740
- return Math.ceil(value);
2740
+ if (decimal > 0.9999999) {
2741
+ return value < 0 ? Math.floor(value) : Math.ceil(value);
2741
2742
  }
2742
2743
  return value;
2743
2744
  };
@@ -2892,7 +2893,6 @@ var extractAudioInternal = async ({
2892
2893
  const durationInSeconds = durationNotYetApplyingPlaybackRate * playbackRate;
2893
2894
  const samples = await sampleIterator.getSamples(timeInSeconds, durationInSeconds);
2894
2895
  audioManager.logOpenFrames();
2895
- const trimStartToleranceInSeconds = 0.002;
2896
2896
  const audioDataArray = [];
2897
2897
  for (let i = 0;i < samples.length; i++) {
2898
2898
  const sample = samples[i];
@@ -2907,13 +2907,18 @@ var extractAudioInternal = async ({
2907
2907
  const audioDataRaw = sample.toAudioData();
2908
2908
  let trimStartInSeconds = 0;
2909
2909
  let trimEndInSeconds = 0;
2910
+ let leadingSilence = null;
2910
2911
  if (isFirstSample) {
2911
- trimStartInSeconds = timeInSeconds - sample.timestamp;
2912
- if (trimStartInSeconds < 0 && trimStartInSeconds > -trimStartToleranceInSeconds) {
2913
- trimStartInSeconds = 0;
2914
- }
2912
+ trimStartInSeconds = fixFloatingPoint2(timeInSeconds - sample.timestamp);
2915
2913
  if (trimStartInSeconds < 0) {
2916
- throw new Error(`trimStartInSeconds is negative: ${trimStartInSeconds}. ${JSON.stringify({ timeInSeconds, ts: sample.timestamp, d: sample.duration, isFirstSample, isLastSample, durationInSeconds, i, st: samples.map((s) => s.timestamp) })}`);
2914
+ const silenceFrames = Math.ceil(fixFloatingPoint2(-trimStartInSeconds * TARGET_SAMPLE_RATE));
2915
+ leadingSilence = {
2916
+ data: new Int16Array(silenceFrames * TARGET_NUMBER_OF_CHANNELS),
2917
+ numberOfFrames: silenceFrames,
2918
+ timestamp: timeInSeconds * 1e6,
2919
+ durationInMicroSeconds: silenceFrames / TARGET_SAMPLE_RATE * 1e6
2920
+ };
2921
+ trimStartInSeconds = 0;
2917
2922
  }
2918
2923
  }
2919
2924
  if (isLastSample) {
@@ -2931,6 +2936,9 @@ var extractAudioInternal = async ({
2931
2936
  if (audioData.numberOfFrames === 0) {
2932
2937
  continue;
2933
2938
  }
2939
+ if (leadingSilence) {
2940
+ audioDataArray.push(leadingSilence);
2941
+ }
2934
2942
  audioDataArray.push(audioData);
2935
2943
  }
2936
2944
  if (audioDataArray.length === 0) {
@@ -3018,6 +3026,39 @@ var extractFrame = (params) => {
3018
3026
  return queue2;
3019
3027
  };
3020
3028
 
3029
+ // src/video-extraction/rotate-frame.ts
3030
+ var rotateFrame = async ({
3031
+ frame,
3032
+ rotation
3033
+ }) => {
3034
+ if (rotation === 0) {
3035
+ const directBitmap = await createImageBitmap(frame);
3036
+ frame.close();
3037
+ return directBitmap;
3038
+ }
3039
+ const width = rotation === 90 || rotation === 270 ? frame.displayHeight : frame.displayWidth;
3040
+ const height = rotation === 90 || rotation === 270 ? frame.displayWidth : frame.displayHeight;
3041
+ const canvas = new OffscreenCanvas(width, height);
3042
+ const ctx = canvas.getContext("2d");
3043
+ if (!ctx) {
3044
+ throw new Error("Could not get 2d context");
3045
+ }
3046
+ canvas.width = width;
3047
+ canvas.height = height;
3048
+ if (rotation === 90) {
3049
+ ctx.translate(width, 0);
3050
+ } else if (rotation === 180) {
3051
+ ctx.translate(width, height);
3052
+ } else if (rotation === 270) {
3053
+ ctx.translate(0, height);
3054
+ }
3055
+ ctx.rotate(rotation * (Math.PI / 180));
3056
+ ctx.drawImage(frame, 0, 0);
3057
+ const bitmap = await createImageBitmap(canvas);
3058
+ frame.close();
3059
+ return bitmap;
3060
+ };
3061
+
3021
3062
  // src/extract-frame-and-audio.ts
3022
3063
  var extractFrameAndAudio = async ({
3023
3064
  src,
@@ -3088,9 +3129,20 @@ var extractFrameAndAudio = async ({
3088
3129
  durationInSeconds: frame?.type === "success" ? frame.durationInSeconds : null
3089
3130
  };
3090
3131
  }
3132
+ if (!frame?.frame) {
3133
+ return {
3134
+ type: "success",
3135
+ frame: null,
3136
+ audio: audio?.data ?? null,
3137
+ durationInSeconds: audio?.durationInSeconds ?? null
3138
+ };
3139
+ }
3091
3140
  return {
3092
3141
  type: "success",
3093
- frame: frame?.frame?.toVideoFrame() ?? null,
3142
+ frame: await rotateFrame({
3143
+ frame: frame.frame.toVideoFrame(),
3144
+ rotation: frame.frame.rotation
3145
+ }),
3094
3146
  audio: audio?.data ?? null,
3095
3147
  durationInSeconds: audio?.durationInSeconds ?? null
3096
3148
  };
@@ -3158,10 +3210,9 @@ if (typeof window !== "undefined" && window.remotion_broadcastChannel && window.
3158
3210
  return;
3159
3211
  }
3160
3212
  const { frame, audio, durationInSeconds } = result;
3161
- const videoFrame = frame;
3162
- const imageBitmap = videoFrame ? await createImageBitmap(videoFrame) : null;
3163
- if (videoFrame) {
3164
- videoFrame.close();
3213
+ const imageBitmap = frame ? await createImageBitmap(frame) : null;
3214
+ if (frame) {
3215
+ frame.close();
3165
3216
  }
3166
3217
  const response = {
3167
3218
  type: "response-success",
@@ -3171,7 +3222,6 @@ if (typeof window !== "undefined" && window.remotion_broadcastChannel && window.
3171
3222
  durationInSeconds: durationInSeconds ?? null
3172
3223
  };
3173
3224
  window.remotion_broadcastChannel.postMessage(response);
3174
- videoFrame?.close();
3175
3225
  } catch (error) {
3176
3226
  const response = {
3177
3227
  type: "response-error",
@@ -4064,8 +4114,8 @@ var VideoForRendering = ({
4064
4114
  if (!context) {
4065
4115
  return;
4066
4116
  }
4067
- context.canvas.width = imageBitmap instanceof ImageBitmap ? imageBitmap.width : imageBitmap.displayWidth;
4068
- context.canvas.height = imageBitmap instanceof ImageBitmap ? imageBitmap.height : imageBitmap.displayHeight;
4117
+ context.canvas.width = imageBitmap.width;
4118
+ context.canvas.height = imageBitmap.height;
4069
4119
  context.canvas.style.aspectRatio = `${context.canvas.width} / ${context.canvas.height}`;
4070
4120
  context.drawImage(imageBitmap, 0, 0);
4071
4121
  imageBitmap.close();
@@ -1,6 +1,7 @@
1
1
  import { extractAudio } from './audio-extraction/extract-audio';
2
2
  import { isNetworkError } from './is-network-error';
3
3
  import { extractFrame } from './video-extraction/extract-frame';
4
+ import { rotateFrame } from './video-extraction/rotate-frame';
4
5
  export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, audioStreamIndex, trimAfter, trimBefore, fps, }) => {
5
6
  try {
6
7
  const [frame, audio] = await Promise.all([
@@ -61,9 +62,20 @@ export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durat
61
62
  durationInSeconds: frame?.type === 'success' ? frame.durationInSeconds : null,
62
63
  };
63
64
  }
65
+ if (!frame?.frame) {
66
+ return {
67
+ type: 'success',
68
+ frame: null,
69
+ audio: audio?.data ?? null,
70
+ durationInSeconds: audio?.durationInSeconds ?? null,
71
+ };
72
+ }
64
73
  return {
65
74
  type: 'success',
66
- frame: frame?.frame?.toVideoFrame() ?? null,
75
+ frame: await rotateFrame({
76
+ frame: frame.frame.toVideoFrame(),
77
+ rotation: frame.frame.rotation,
78
+ }),
67
79
  audio: audio?.data ?? null,
68
80
  durationInSeconds: audio?.durationInSeconds ?? null,
69
81
  };
@@ -120,14 +120,8 @@ export const VideoForRendering = ({ volume: volumeProp, playbackRate, src, muted
120
120
  if (!context) {
121
121
  return;
122
122
  }
123
- context.canvas.width =
124
- imageBitmap instanceof ImageBitmap
125
- ? imageBitmap.width
126
- : imageBitmap.displayWidth;
127
- context.canvas.height =
128
- imageBitmap instanceof ImageBitmap
129
- ? imageBitmap.height
130
- : imageBitmap.displayHeight;
123
+ context.canvas.width = imageBitmap.width;
124
+ context.canvas.height = imageBitmap.height;
131
125
  context.canvas.style.aspectRatio = `${context.canvas.width} / ${context.canvas.height}`;
132
126
  context.drawImage(imageBitmap, 0, 0);
133
127
  imageBitmap.close();
@@ -2,7 +2,7 @@ import { type LogLevel } from 'remotion';
2
2
  import type { PcmS16AudioData } from '../convert-audiodata/convert-audiodata';
3
3
  export type ExtractFrameViaBroadcastChannelResult = {
4
4
  type: 'success';
5
- frame: ImageBitmap | VideoFrame | null;
5
+ frame: ImageBitmap | null;
6
6
  audio: PcmS16AudioData | null;
7
7
  durationInSeconds: number | null;
8
8
  } | {
@@ -56,12 +56,9 @@ if (typeof window !== 'undefined' &&
56
56
  return;
57
57
  }
58
58
  const { frame, audio, durationInSeconds } = result;
59
- const videoFrame = frame;
60
- const imageBitmap = videoFrame
61
- ? await createImageBitmap(videoFrame)
62
- : null;
63
- if (videoFrame) {
64
- videoFrame.close();
59
+ const imageBitmap = frame ? await createImageBitmap(frame) : null;
60
+ if (frame) {
61
+ frame.close();
65
62
  }
66
63
  const response = {
67
64
  type: 'response-success',
@@ -71,7 +68,6 @@ if (typeof window !== 'undefined' &&
71
68
  durationInSeconds: durationInSeconds ?? null,
72
69
  };
73
70
  window.remotion_broadcastChannel.postMessage(response);
74
- videoFrame?.close();
75
71
  }
76
72
  catch (error) {
77
73
  const response = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.374",
3
+ "version": "4.0.375",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -21,15 +21,15 @@
21
21
  "make": "tsc -d && bun --env-file=../.env.bundle bundle.ts"
22
22
  },
23
23
  "dependencies": {
24
- "mediabunny": "1.24.3",
25
- "remotion": "4.0.374"
24
+ "mediabunny": "1.24.5",
25
+ "remotion": "4.0.375"
26
26
  },
27
27
  "peerDependencies": {
28
28
  "react": ">=16.8.0",
29
29
  "react-dom": ">=16.8.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@remotion/eslint-config-internal": "4.0.374",
32
+ "@remotion/eslint-config-internal": "4.0.375",
33
33
  "@vitest/browser-webdriverio": "4.0.7",
34
34
  "eslint": "9.19.0",
35
35
  "react": "19.0.0",