@remotion/renderer 4.0.126 → 4.0.127

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 (59) hide show
  1. package/dist/assets/convert-assets-to-file-urls.d.ts +2 -1
  2. package/dist/assets/convert-assets-to-file-urls.js +6 -4
  3. package/dist/assets/download-map.d.ts +6 -2
  4. package/dist/assets/ffmpeg-volume-expression.js +7 -3
  5. package/dist/calculate-ffmpeg-filters.d.ts +4 -2
  6. package/dist/calculate-ffmpeg-filters.js +10 -17
  7. package/dist/check-apple-silicon.d.ts +1 -3
  8. package/dist/check-apple-silicon.js +2 -32
  9. package/dist/client.d.ts +57 -21
  10. package/dist/combine-audio.js +6 -6
  11. package/dist/combine-videos.js +1 -0
  12. package/dist/compress-audio.d.ts +2 -2
  13. package/dist/compress-audio.js +4 -7
  14. package/dist/create-audio.d.ts +11 -4
  15. package/dist/create-audio.js +7 -5
  16. package/dist/create-combined-video.d.ts +3 -2
  17. package/dist/create-combined-video.js +7 -1
  18. package/dist/create-silent-audio.d.ts +2 -2
  19. package/dist/create-silent-audio.js +2 -2
  20. package/dist/does-have-m2-bug.d.ts +3 -0
  21. package/dist/does-have-m2-bug.js +12 -0
  22. package/dist/get-extension-from-audio-codec.d.ts +2 -2
  23. package/dist/get-extra-frames-to-capture.js +27 -19
  24. package/dist/index.d.ts +5 -7
  25. package/dist/logger.js +3 -1
  26. package/dist/merge-audio-track.d.ts +1 -1
  27. package/dist/merge-audio-track.js +7 -11
  28. package/dist/options/gl.d.ts +3 -3
  29. package/dist/options/index.d.ts +39 -3
  30. package/dist/options/index.js +4 -0
  31. package/dist/options/options-map.d.ts +18 -18
  32. package/dist/options/options-map.js +1 -1
  33. package/dist/options/prores-profile.d.ts +0 -0
  34. package/dist/options/prores-profile.js +1 -0
  35. package/dist/options/{separate-audio-to.d.ts → public-dir.d.ts} +5 -5
  36. package/dist/options/public-dir.js +37 -0
  37. package/dist/options/public-path.d.ts +18 -0
  38. package/dist/options/public-path.js +37 -0
  39. package/dist/port-config.d.ts +0 -4
  40. package/dist/preprocess-audio-track.d.ts +3 -1
  41. package/dist/preprocess-audio-track.js +7 -7
  42. package/dist/render-frames.d.ts +6 -0
  43. package/dist/render-frames.js +70 -29
  44. package/dist/render-media.d.ts +1 -0
  45. package/dist/render-media.js +5 -2
  46. package/dist/stitch-frames-to-video.d.ts +1 -1
  47. package/dist/stitch-frames-to-video.js +9 -9
  48. package/dist/stringify-ffmpeg-filter.d.ts +3 -2
  49. package/dist/stringify-ffmpeg-filter.js +56 -14
  50. package/dist/x264-preset.d.ts +15 -0
  51. package/dist/x264-preset.js +26 -1
  52. package/package.json +9 -9
  53. package/dist/can-concatenate-seamlessly.d.ts +0 -3
  54. package/dist/can-concatenate-seamlessly.js +0 -7
  55. package/dist/options/separate-audio-to.js +0 -31
  56. package/dist/should-seamless.d.ts +0 -3
  57. package/dist/should-seamless.js +0 -7
  58. package/dist/supported-audio-codecs.d.ts +0 -13
  59. package/dist/supported-audio-codecs.js +0 -16
@@ -18,6 +18,7 @@ const handle_javascript_exception_1 = require("./error-handling/handle-javascrip
18
18
  const find_closest_package_json_1 = require("./find-closest-package-json");
19
19
  const get_concurrency_1 = require("./get-concurrency");
20
20
  const get_duration_from_frame_range_1 = require("./get-duration-from-frame-range");
21
+ const get_extra_frames_to_capture_1 = require("./get-extra-frames-to-capture");
21
22
  const get_frame_padded_index_1 = require("./get-frame-padded-index");
22
23
  const get_frame_to_render_1 = require("./get-frame-to-render");
23
24
  const jpeg_quality_1 = require("./jpeg-quality");
@@ -37,7 +38,7 @@ const validate_1 = require("./validate");
37
38
  const validate_scale_1 = require("./validate-scale");
38
39
  const wrap_with_error_handling_1 = require("./wrap-with-error-handling");
39
40
  const MAX_RETRIES_PER_FRAME = 1;
40
- const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, jpegQuality, imageFormat, frameRange, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, actualConcurrency, everyNthFrame, proxyPort, cancelSignal, downloadMap, muted, makeBrowser, browserReplacer, compositor, sourceMapGetter, logLevel, indent, parallelEncodingEnabled, }) => {
41
+ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, jpegQuality, imageFormat, frameRange, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, actualConcurrency, everyNthFrame, proxyPort, cancelSignal, downloadMap, muted, makeBrowser, browserReplacer, compositor, sourceMapGetter, logLevel, indent, parallelEncodingEnabled, compositionStart, forSeamlessAacConcatenation, }) => {
41
42
  if (outputDir) {
42
43
  if (!node_fs_1.default.existsSync(outputDir)) {
43
44
  node_fs_1.default.mkdirSync(outputDir, {
@@ -47,6 +48,12 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
47
48
  }
48
49
  const downloadPromises = [];
49
50
  const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange);
51
+ const { extraFramesToCaptureAssets, chunkLengthInSeconds, trimLeftOffset, trimRightOffset, } = (0, get_extra_frames_to_capture_1.getExtraFramesToCapture)({
52
+ fps: composition.fps,
53
+ compositionStart,
54
+ realFrameRange,
55
+ forSeamlessAacConcatenation,
56
+ });
50
57
  const framesToRender = (0, get_duration_from_frame_range_1.getFramesToRender)(realFrameRange, everyNthFrame);
51
58
  const lastFrame = framesToRender[framesToRender.length - 1];
52
59
  const makePage = async (context) => {
@@ -136,13 +143,13 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
136
143
  frameCount: framesToRender.length,
137
144
  parallelEncoding: parallelEncodingEnabled,
138
145
  });
139
- const assets = new Array(framesToRender.length).fill(undefined);
146
+ const assets = [];
140
147
  let stopped = false;
141
148
  cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
142
149
  stopped = true;
143
150
  });
144
151
  const frameDir = outputDir !== null && outputDir !== void 0 ? outputDir : downloadMap.compositingDir;
145
- const renderFrameWithOptionToReject = async ({ frame, index, reject, width, height, compId, }) => {
152
+ const renderFrameWithOptionToReject = async ({ frame, index, reject, width, height, compId, assetsOnly, }) => {
146
153
  const pool = await poolPromise;
147
154
  const freePage = await pool.acquire();
148
155
  if (stopped) {
@@ -182,15 +189,17 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
182
189
  frame,
183
190
  freePage,
184
191
  height,
185
- imageFormat,
186
- output: node_path_1.default.join(frameDir, (0, get_frame_padded_index_1.getFrameOutputFileName)({
187
- frame,
188
- imageFormat,
189
- index,
190
- countType,
191
- lastFrame,
192
- totalFrames: framesToRender.length,
193
- })),
192
+ imageFormat: assetsOnly ? 'none' : imageFormat,
193
+ output: index === null
194
+ ? null
195
+ : node_path_1.default.join(frameDir, (0, get_frame_padded_index_1.getFrameOutputFileName)({
196
+ frame,
197
+ imageFormat,
198
+ index,
199
+ countType,
200
+ lastFrame,
201
+ totalFrames: framesToRender.length,
202
+ })),
194
203
  jpegQuality,
195
204
  width,
196
205
  scale,
@@ -199,15 +208,21 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
199
208
  compositor,
200
209
  timeoutInMilliseconds,
201
210
  });
202
- if (onFrameBuffer) {
211
+ if (onFrameBuffer && !assetsOnly) {
203
212
  if (!buffer) {
204
213
  throw new Error('unexpected null buffer');
205
214
  }
206
215
  onFrameBuffer(buffer, frame);
207
216
  }
208
217
  (0, perf_1.stopPerfMeasure)(id);
209
- const compressedAssets = collectedAssets.map((asset) => (0, compress_assets_1.compressAsset)(assets.filter(truthy_1.truthy).flat(1), asset));
210
- assets[index] = compressedAssets;
218
+ const compressedAssets = collectedAssets.map((asset) => (0, compress_assets_1.compressAsset)(assets
219
+ .filter(truthy_1.truthy)
220
+ .map((a) => a.assets)
221
+ .flat(2), asset));
222
+ assets.push({
223
+ assets: compressedAssets,
224
+ frame,
225
+ });
211
226
  compressedAssets.forEach((renderAsset) => {
212
227
  (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
213
228
  renderAsset,
@@ -219,13 +234,15 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
219
234
  onError(new Error(`Error while downloading asset: ${err.stack}`));
220
235
  });
221
236
  });
222
- framesRendered++;
223
- onFrameUpdate === null || onFrameUpdate === void 0 ? void 0 : onFrameUpdate(framesRendered, frame, perf_hooks_1.performance.now() - startTime);
237
+ if (!assetsOnly) {
238
+ framesRendered++;
239
+ onFrameUpdate === null || onFrameUpdate === void 0 ? void 0 : onFrameUpdate(framesRendered, frame, perf_hooks_1.performance.now() - startTime);
240
+ }
224
241
  cleanupPageError();
225
242
  freePage.off('error', errorCallbackOnFrame);
226
243
  pool.release(freePage);
227
244
  };
228
- const renderFrame = (frame, index) => {
245
+ const renderFrame = (frame, index, assetsOnly) => {
229
246
  return new Promise((resolve, reject) => {
230
247
  renderFrameWithOptionToReject({
231
248
  frame,
@@ -234,6 +251,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
234
251
  width: composition.width,
235
252
  height: composition.height,
236
253
  compId: composition.id,
254
+ assetsOnly,
237
255
  })
238
256
  .then(() => {
239
257
  resolve();
@@ -243,10 +261,10 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
243
261
  });
244
262
  });
245
263
  };
246
- const renderFrameAndRetryTargetClose = async ({ frame, index, retriesLeft, attempt, }) => {
264
+ const renderFrameAndRetryTargetClose = async ({ frame, index, retriesLeft, attempt, assetsOnly, }) => {
247
265
  try {
248
266
  await Promise.race([
249
- renderFrame(frame, index),
267
+ renderFrame(frame, index, assetsOnly),
250
268
  new Promise((_, reject) => {
251
269
  cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
252
270
  reject(new Error(make_cancel_signal_1.cancelErrorMessages.renderFrames));
@@ -284,23 +302,42 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
284
302
  index,
285
303
  retriesLeft: retriesLeft - 1,
286
304
  attempt: attempt + 1,
305
+ assetsOnly,
287
306
  });
288
307
  }
289
308
  };
290
- const progress = Promise.all(framesToRender.map((frame, index) => renderFrameAndRetryTargetClose({
291
- frame,
292
- index,
293
- retriesLeft: MAX_RETRIES_PER_FRAME,
294
- attempt: 1,
295
- })));
296
- const happyPath = progress.then(() => {
309
+ const extraFrames = Promise.all(extraFramesToCaptureAssets.map((frame) => {
310
+ return renderFrameAndRetryTargetClose({
311
+ frame,
312
+ index: null,
313
+ retriesLeft: MAX_RETRIES_PER_FRAME,
314
+ attempt: 1,
315
+ assetsOnly: true,
316
+ });
317
+ }));
318
+ const mainFrames = Promise.all(framesToRender.map((frame, index) => {
319
+ return renderFrameAndRetryTargetClose({
320
+ frame,
321
+ index,
322
+ retriesLeft: MAX_RETRIES_PER_FRAME,
323
+ attempt: 1,
324
+ assetsOnly: false,
325
+ });
326
+ }));
327
+ const happyPath = Promise.all([extraFrames, mainFrames]).then(() => {
297
328
  const firstFrameIndex = countType === 'from-zero' ? 0 : framesToRender[0];
298
329
  const returnValue = {
299
330
  assetsInfo: {
300
- assets,
331
+ assets: assets.sort((a, b) => {
332
+ return a.frame - b.frame;
333
+ }),
301
334
  imageSequenceName: node_path_1.default.join(frameDir, `element-%0${filePadLength}d.${imageFormat}`),
302
335
  firstFrameIndex,
303
336
  downloadMap,
337
+ trimLeftOffset,
338
+ trimRightOffset,
339
+ chunkLengthInSeconds,
340
+ forSeamlessAacConcatenation,
304
341
  },
305
342
  frameCount: framesToRender.length,
306
343
  };
@@ -310,7 +347,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
310
347
  await Promise.all(downloadPromises);
311
348
  return result;
312
349
  };
313
- const internalRenderFramesRaw = ({ browserExecutable, cancelSignal, chromiumOptions, composition, concurrency, envVariables, everyNthFrame, frameRange, imageFormat, indent, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, onFrameUpdate, onStart, outputDir, port, puppeteerInstance, scale, server, timeoutInMilliseconds, logLevel, webpackBundleOrServeUrl, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, offthreadVideoCacheSizeInBytes, parallelEncodingEnabled, binariesDirectory, }) => {
350
+ const internalRenderFramesRaw = ({ browserExecutable, cancelSignal, chromiumOptions, composition, concurrency, envVariables, everyNthFrame, frameRange, imageFormat, indent, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, onFrameUpdate, onStart, outputDir, port, puppeteerInstance, scale, server, timeoutInMilliseconds, logLevel, webpackBundleOrServeUrl, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, offthreadVideoCacheSizeInBytes, parallelEncodingEnabled, binariesDirectory, forSeamlessAacConcatenation, compositionStart, }) => {
314
351
  (0, validate_1.validateDimension)(composition.height, 'height', 'in the `config` object passed to `renderFrames()`');
315
352
  (0, validate_1.validateDimension)(composition.width, 'width', 'in the `config` object passed to `renderFrames()`');
316
353
  (0, validate_1.validateFps)(composition.fps, 'in the `config` object of `renderFrames()`', false);
@@ -401,6 +438,8 @@ const internalRenderFramesRaw = ({ browserExecutable, cancelSignal, chromiumOpti
401
438
  serializedResolvedPropsWithCustomSchema,
402
439
  parallelEncodingEnabled,
403
440
  binariesDirectory,
441
+ forSeamlessAacConcatenation,
442
+ compositionStart,
404
443
  });
405
444
  }),
406
445
  ])
@@ -493,6 +532,8 @@ const renderFrames = (options) => {
493
532
  offthreadVideoCacheSizeInBytes: offthreadVideoCacheSizeInBytes !== null && offthreadVideoCacheSizeInBytes !== void 0 ? offthreadVideoCacheSizeInBytes : null,
494
533
  parallelEncodingEnabled: false,
495
534
  binariesDirectory: binariesDirectory !== null && binariesDirectory !== void 0 ? binariesDirectory : null,
535
+ compositionStart: 0,
536
+ forSeamlessAacConcatenation: false,
496
537
  });
497
538
  };
498
539
  exports.renderFrames = renderFrames;
@@ -65,6 +65,7 @@ export type InternalRenderMediaOptions = {
65
65
  concurrency: number | string | null;
66
66
  finishRenderProgress: () => void;
67
67
  binariesDirectory: string | null;
68
+ compositionStart: number;
68
69
  } & MoreRenderMediaOptions;
69
70
  type Prettify<T> = {
70
71
  [K in keyof T]: T[K];
@@ -47,7 +47,7 @@ const validate_scale_1 = require("./validate-scale");
47
47
  const validate_videobitrate_1 = require("./validate-videobitrate");
48
48
  const wrap_with_error_handling_1 = require("./wrap-with-error-handling");
49
49
  const SLOWEST_FRAME_COUNT = 10;
50
- const internalRenderMediaRaw = ({ proResProfile, x264Preset, crf, composition, serializedInputPropsWithCustomSchema, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, audioBitrate, videoBitrate, encodingMaxRate, encodingBufferSize, audioCodec, concurrency, disallowParallelEncoding, everyNthFrame, imageFormat: provisionalImageFormat, indent, jpegQuality, numberOfGifLoops, onCtrlCExit, preferLossless, serveUrl, server: reusedServer, logLevel, serializedResolvedPropsWithCustomSchema, offthreadVideoCacheSizeInBytes, colorSpace, repro, finishRenderProgress, binariesDirectory, separateAudioTo, forSeamlessAacConcatenation, }) => {
50
+ const internalRenderMediaRaw = ({ proResProfile, x264Preset, crf, composition, serializedInputPropsWithCustomSchema, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, audioBitrate, videoBitrate, encodingMaxRate, encodingBufferSize, audioCodec, concurrency, disallowParallelEncoding, everyNthFrame, imageFormat: provisionalImageFormat, indent, jpegQuality, numberOfGifLoops, onCtrlCExit, preferLossless, serveUrl, server: reusedServer, logLevel, serializedResolvedPropsWithCustomSchema, offthreadVideoCacheSizeInBytes, colorSpace, repro, finishRenderProgress, binariesDirectory, separateAudioTo, forSeamlessAacConcatenation, compositionStart, }) => {
51
51
  if (repro) {
52
52
  (0, repro_1.enableRepro)({
53
53
  serveUrl,
@@ -340,6 +340,8 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, crf, composition, s
340
340
  offthreadVideoCacheSizeInBytes,
341
341
  parallelEncodingEnabled: parallelEncoding,
342
342
  binariesDirectory,
343
+ compositionStart,
344
+ forSeamlessAacConcatenation,
343
345
  });
344
346
  return renderFramesProc;
345
347
  })
@@ -399,7 +401,6 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, crf, composition, s
399
401
  colorSpace,
400
402
  binariesDirectory,
401
403
  separateAudioTo,
402
- forSeamlessAacConcatenation,
403
404
  }),
404
405
  stitchStart,
405
406
  ]);
@@ -547,6 +548,8 @@ const renderMedia = ({ proResProfile, x264Preset, crf, composition, inputProps,
547
548
  binariesDirectory: binariesDirectory !== null && binariesDirectory !== void 0 ? binariesDirectory : null,
548
549
  separateAudioTo: separateAudioTo !== null && separateAudioTo !== void 0 ? separateAudioTo : null,
549
550
  forSeamlessAacConcatenation: forSeamlessAacConcatenation !== null && forSeamlessAacConcatenation !== void 0 ? forSeamlessAacConcatenation : false,
551
+ // TODO: In the future, introduce this as a public API when launching the distributed rendering API
552
+ compositionStart: 0,
550
553
  });
551
554
  };
552
555
  exports.renderMedia = renderMedia;
@@ -75,5 +75,5 @@ export declare const internalStitchFramesToVideo: (options: InternalStitchFrames
75
75
  * @description Takes a series of images and audio information generated by renderFrames() and encodes it to a video.
76
76
  * @see [Documentation](https://www.remotion.dev/docs/renderer/stitch-frames-to-video)
77
77
  */
78
- export declare const stitchFramesToVideo: ({ assetsInfo, force, fps, height, width, audioBitrate, audioCodec, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, muted, numberOfGifLoops, onDownload, onProgress, outputLocation, pixelFormat, proResProfile, verbose, videoBitrate, encodingMaxRate, encodingBufferSize, x264Preset, colorSpace, binariesDirectory, separateAudioTo, forSeamlessAacConcatenation, }: StitchFramesToVideoOptions) => Promise<Buffer | null>;
78
+ export declare const stitchFramesToVideo: ({ assetsInfo, force, fps, height, width, audioBitrate, audioCodec, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, muted, numberOfGifLoops, onDownload, onProgress, outputLocation, pixelFormat, proResProfile, verbose, videoBitrate, encodingMaxRate, encodingBufferSize, x264Preset, colorSpace, binariesDirectory, separateAudioTo, }: StitchFramesToVideoOptions) => Promise<Buffer | null>;
79
79
  export {};
@@ -28,7 +28,7 @@ const prores_profile_1 = require("./prores-profile");
28
28
  const validate_1 = require("./validate");
29
29
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
30
30
  const validate_videobitrate_1 = require("./validate-videobitrate");
31
- const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec: audioCodecSetting, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, force, fps, height, indent, muted, onDownload, outputLocation, pixelFormat, preEncodedFileLocation, preferLossless, proResProfile, logLevel, videoBitrate, encodingMaxRate, encodingBufferSize, width, numberOfGifLoops, onProgress, x264Preset, colorSpace, binariesDirectory, separateAudioTo, forSeamlessAacConcatenation, }, remotionRoot) => {
31
+ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec: audioCodecSetting, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, force, fps, height, indent, muted, onDownload, outputLocation, pixelFormat, preEncodedFileLocation, preferLossless, proResProfile, logLevel, videoBitrate, encodingMaxRate, encodingBufferSize, width, numberOfGifLoops, onProgress, x264Preset, colorSpace, binariesDirectory, separateAudioTo, }, remotionRoot) => {
32
32
  var _a;
33
33
  (0, validate_1.validateDimension)(height, 'height', 'passed to `stitchFramesToVideo()`');
34
34
  (0, validate_1.validateDimension)(width, 'width', 'passed to `stitchFramesToVideo()`');
@@ -101,7 +101,6 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
101
101
  encodingBufferSize,
102
102
  });
103
103
  (0, pixel_format_1.validateSelectedPixelFormatAndCodecCombination)(pixelFormat, codec);
104
- const expectedFrames = assetsInfo.assets.length;
105
104
  const updateProgress = (muxProgress) => {
106
105
  onProgress === null || onProgress === void 0 ? void 0 : onProgress(muxProgress);
107
106
  };
@@ -110,7 +109,7 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
110
109
  assets: assetsInfo.assets,
111
110
  onDownload,
112
111
  fps,
113
- expectedFrames,
112
+ chunkLengthInSeconds: assetsInfo.chunkLengthInSeconds,
114
113
  logLevel,
115
114
  onProgress: () => updateProgress(0),
116
115
  downloadMap: assetsInfo.downloadMap,
@@ -120,7 +119,9 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
120
119
  audioBitrate,
121
120
  audioCodec: resolvedAudioCodec,
122
121
  cancelSignal: cancelSignal !== null && cancelSignal !== void 0 ? cancelSignal : undefined,
123
- forSeamlessAacConcatenation,
122
+ trimLeftOffset: assetsInfo.trimLeftOffset,
123
+ trimRightOffset: assetsInfo.trimRightOffset,
124
+ forSeamlessAacConcatenation: assetsInfo.forSeamlessAacConcatenation,
124
125
  })
125
126
  : null;
126
127
  if (mediaSupport.audio && !mediaSupport.video) {
@@ -134,7 +135,7 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
134
135
  throw new Error('`separateAudioTo` was set, but this render was audio-only. This option is meant to be used for video renders.');
135
136
  }
136
137
  (0, node_fs_1.cpSync)(audio, outputLocation !== null && outputLocation !== void 0 ? outputLocation : tempFile);
137
- onProgress === null || onProgress === void 0 ? void 0 : onProgress(expectedFrames);
138
+ onProgress === null || onProgress === void 0 ? void 0 : onProgress(Math.round(assetsInfo.chunkLengthInSeconds * fps));
138
139
  (0, delete_directory_1.deleteDirectory)(node_path_1.default.dirname(audio));
139
140
  const file = await new Promise((resolve, reject) => {
140
141
  if (tempFile) {
@@ -196,7 +197,7 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
196
197
  indent: indent !== null && indent !== void 0 ? indent : false,
197
198
  logLevel,
198
199
  tag: 'stitchFramesToVideo()',
199
- }, 'Generated final FFMPEG command:');
200
+ }, 'Generated final FFmpeg command:');
200
201
  logger_1.Log.verbose({
201
202
  indent,
202
203
  logLevel,
@@ -222,7 +223,7 @@ const innerStitchFramesToVideo = async ({ assetsInfo, audioBitrate, audioCodec:
222
223
  // Example repo: https://github.com/JonnyBurger/ffmpeg-repro (access can be given upon request)
223
224
  if (parsed !== undefined) {
224
225
  // If two times in a row the finishing frame is logged, we quit the render
225
- if (parsed === expectedFrames) {
226
+ if (parsed === assetsInfo.assets.length) {
226
227
  if (isFinished) {
227
228
  (_a = task.stdin) === null || _a === void 0 ? void 0 : _a.write('q');
228
229
  }
@@ -286,7 +287,7 @@ exports.internalStitchFramesToVideo = internalStitchFramesToVideo;
286
287
  * @description Takes a series of images and audio information generated by renderFrames() and encodes it to a video.
287
288
  * @see [Documentation](https://www.remotion.dev/docs/renderer/stitch-frames-to-video)
288
289
  */
289
- const stitchFramesToVideo = ({ assetsInfo, force, fps, height, width, audioBitrate, audioCodec, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, muted, numberOfGifLoops, onDownload, onProgress, outputLocation, pixelFormat, proResProfile, verbose, videoBitrate, encodingMaxRate, encodingBufferSize, x264Preset, colorSpace, binariesDirectory, separateAudioTo, forSeamlessAacConcatenation, }) => {
290
+ const stitchFramesToVideo = ({ assetsInfo, force, fps, height, width, audioBitrate, audioCodec, cancelSignal, codec, crf, enforceAudioTrack, ffmpegOverride, muted, numberOfGifLoops, onDownload, onProgress, outputLocation, pixelFormat, proResProfile, verbose, videoBitrate, encodingMaxRate, encodingBufferSize, x264Preset, colorSpace, binariesDirectory, separateAudioTo, }) => {
290
291
  return (0, exports.internalStitchFramesToVideo)({
291
292
  assetsInfo,
292
293
  audioBitrate: audioBitrate !== null && audioBitrate !== void 0 ? audioBitrate : null,
@@ -318,7 +319,6 @@ const stitchFramesToVideo = ({ assetsInfo, force, fps, height, width, audioBitra
318
319
  colorSpace: colorSpace !== null && colorSpace !== void 0 ? colorSpace : 'default',
319
320
  binariesDirectory: binariesDirectory !== null && binariesDirectory !== void 0 ? binariesDirectory : null,
320
321
  separateAudioTo: separateAudioTo !== null && separateAudioTo !== void 0 ? separateAudioTo : null,
321
- forSeamlessAacConcatenation: forSeamlessAacConcatenation !== null && forSeamlessAacConcatenation !== void 0 ? forSeamlessAacConcatenation : false,
322
322
  });
323
323
  };
324
324
  exports.stitchFramesToVideo = stitchFramesToVideo;
@@ -6,16 +6,17 @@ export type ProcessedTrack = {
6
6
  pad_start: string | null;
7
7
  pad_end: string | null;
8
8
  };
9
- export declare const stringifyFfmpegFilter: ({ trimLeft, trimRight, channels, startInVideo, volume, fps, playbackRate, durationInFrames, assetDuration, allowAmplificationDuringRender, toneFrequency, }: {
9
+ export declare const stringifyFfmpegFilter: ({ trimLeft, trimRight, channels, startInVideo, volume, fps, playbackRate, assetDuration, allowAmplificationDuringRender, toneFrequency, chunkLengthInSeconds, forSeamlessAacConcatenation, }: {
10
10
  trimLeft: number;
11
11
  trimRight: number;
12
12
  channels: number;
13
13
  startInVideo: number;
14
14
  volume: AssetVolume;
15
15
  fps: number;
16
- durationInFrames: number;
17
16
  playbackRate: number;
18
17
  assetDuration: number | null;
19
18
  allowAmplificationDuringRender: boolean;
20
19
  toneFrequency: number | null;
20
+ chunkLengthInSeconds: number;
21
+ forSeamlessAacConcatenation: boolean;
21
22
  }) => FilterWithoutPaddingApplied | null;
@@ -5,7 +5,50 @@ const calculate_atempo_1 = require("./assets/calculate-atempo");
5
5
  const ffmpeg_volume_expression_1 = require("./assets/ffmpeg-volume-expression");
6
6
  const sample_rate_1 = require("./sample-rate");
7
7
  const truthy_1 = require("./truthy");
8
- const stringifyFfmpegFilter = ({ trimLeft, trimRight, channels, startInVideo, volume, fps, playbackRate, durationInFrames, assetDuration, allowAmplificationDuringRender, toneFrequency, }) => {
8
+ const stringifyTrim = (trim) => {
9
+ const value = trim * 1000000;
10
+ const asString = `${value}us`;
11
+ // Handle very small values such as `"6e-7us"`, those are essentially rounding errors to 0
12
+ if (asString.includes('e-')) {
13
+ return '0us';
14
+ }
15
+ return asString;
16
+ };
17
+ const trimAndSetTempo = ({ playbackRate, trimLeft, trimRight, forSeamlessAacConcatenation, assetDuration, }) => {
18
+ const trimRightOrAssetDuration = assetDuration
19
+ ? Math.min(trimRight, assetDuration)
20
+ : trimRight;
21
+ // If we need seamless AAC stitching, we need to apply the tempo filter first
22
+ // because the atempo filter is not frame-perfect. It creates a small offset
23
+ // and the offset needs to be the same for all audio tracks, before processing it further.
24
+ // This also affects the trimLeft and trimRight values, as they need to be adjusted.
25
+ if (forSeamlessAacConcatenation) {
26
+ const actualTrimLeft = trimLeft / playbackRate;
27
+ const actualTrimRight = trimRightOrAssetDuration / playbackRate;
28
+ return {
29
+ filter: [
30
+ (0, calculate_atempo_1.calculateATempo)(playbackRate),
31
+ `atrim=${stringifyTrim(actualTrimLeft)}:${stringifyTrim(actualTrimRight)}`,
32
+ ],
33
+ actualTrimLeft,
34
+ audibleDuration: actualTrimRight - actualTrimLeft,
35
+ };
36
+ }
37
+ // Otherwise, we first trim and then apply playback rate, as then the atempo
38
+ // filter needs to do less work.
39
+ if (!forSeamlessAacConcatenation) {
40
+ return {
41
+ filter: [
42
+ `atrim=${stringifyTrim(trimLeft)}:${stringifyTrim(trimRightOrAssetDuration)}`,
43
+ (0, calculate_atempo_1.calculateATempo)(playbackRate),
44
+ ],
45
+ actualTrimLeft: trimLeft,
46
+ audibleDuration: (trimRightOrAssetDuration - trimLeft) / playbackRate,
47
+ };
48
+ }
49
+ throw new Error('This should never happen');
50
+ };
51
+ const stringifyFfmpegFilter = ({ trimLeft, trimRight, channels, startInVideo, volume, fps, playbackRate, assetDuration, allowAmplificationDuringRender, toneFrequency, chunkLengthInSeconds, forSeamlessAacConcatenation, }) => {
9
52
  const startInVideoSeconds = startInVideo / fps;
10
53
  if (assetDuration && trimLeft >= assetDuration) {
11
54
  return null;
@@ -13,28 +56,27 @@ const stringifyFfmpegFilter = ({ trimLeft, trimRight, channels, startInVideo, vo
13
56
  if (toneFrequency !== null && (toneFrequency <= 0 || toneFrequency > 2)) {
14
57
  throw new Error('toneFrequency must be a positive number between 0.01 and 2');
15
58
  }
59
+ const { actualTrimLeft, audibleDuration, filter: trimAndTempoFilter, } = trimAndSetTempo({
60
+ playbackRate,
61
+ forSeamlessAacConcatenation,
62
+ assetDuration,
63
+ trimLeft,
64
+ trimRight,
65
+ });
16
66
  const volumeFilter = (0, ffmpeg_volume_expression_1.ffmpegVolumeExpression)({
17
67
  volume,
18
68
  fps,
19
- trimLeft,
69
+ trimLeft: actualTrimLeft,
20
70
  allowAmplificationDuringRender,
21
71
  });
22
- // Avoid setting filters if possible, as combining them can create noise
23
- const chunkLength = durationInFrames / fps;
24
- const actualTrimRight = assetDuration
25
- ? Math.min(trimRight, assetDuration)
26
- : trimRight;
27
- const audibleDuration = (actualTrimRight - trimLeft) / playbackRate;
28
- const padAtEnd = chunkLength - audibleDuration - startInVideoSeconds;
72
+ const padAtEnd = chunkLengthInSeconds - audibleDuration - startInVideoSeconds;
73
+ // Set as few filters as possible, as combining them can create noise
29
74
  return {
30
75
  filter: `[0:a]` +
31
76
  [
32
77
  `aformat=sample_fmts=s32:sample_rates=${sample_rate_1.DEFAULT_SAMPLE_RATE}`,
33
- // Order matters! First trim the audio
34
- `atrim=${trimLeft * 1000000}us:${actualTrimRight * 1000000}us`,
35
- // then set the tempo
36
- (0, calculate_atempo_1.calculateATempo)(playbackRate),
37
- // set the volume if needed
78
+ // The order matters here! For speed and correctness, we first trim the audio
79
+ ...trimAndTempoFilter,
38
80
  // The timings for volume must include whatever is in atrim, unless the volume
39
81
  // filter gets applied before atrim
40
82
  volumeFilter.value === '1'
@@ -5,3 +5,18 @@ export declare const validateSelectedCodecAndPresetCombination: ({ codec, x264Pr
5
5
  codec: Codec;
6
6
  x264Preset: X264Preset | undefined;
7
7
  }) => void;
8
+ export declare const x264Option: {
9
+ name: string;
10
+ cliFlag: "x264-preset";
11
+ description: () => import("react/jsx-runtime").JSX.Element;
12
+ ssrName: "x264Preset";
13
+ docLink: string;
14
+ type: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow" | "placebo";
15
+ getValue: ({ commandLine }: {
16
+ commandLine: Record<string, unknown>;
17
+ }) => {
18
+ value: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow" | "placebo";
19
+ source: string;
20
+ };
21
+ setConfig: (profile: X264Preset | undefined) => void;
22
+ };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateSelectedCodecAndPresetCombination = exports.x264PresetOptions = void 0;
3
+ exports.x264Option = exports.validateSelectedCodecAndPresetCombination = exports.x264PresetOptions = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
5
  exports.x264PresetOptions = [
5
6
  'ultrafast',
6
7
  'superfast',
@@ -13,6 +14,7 @@ exports.x264PresetOptions = [
13
14
  'veryslow',
14
15
  'placebo',
15
16
  ];
17
+ let preset;
16
18
  const validateSelectedCodecAndPresetCombination = ({ codec, x264Preset, }) => {
17
19
  if (typeof x264Preset !== 'undefined' &&
18
20
  codec !== 'h264' &&
@@ -27,3 +29,26 @@ const validateSelectedCodecAndPresetCombination = ({ codec, x264Preset, }) => {
27
29
  }
28
30
  };
29
31
  exports.validateSelectedCodecAndPresetCombination = validateSelectedCodecAndPresetCombination;
32
+ const cliFlag = 'x264-preset';
33
+ const DEFAULT_PRESET = 'medium';
34
+ exports.x264Option = {
35
+ name: 'x264 Preset',
36
+ cliFlag,
37
+ description: () => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: ["Sets a x264 preset profile. Only applies to videos rendered with", ' ', (0, jsx_runtime_1.jsx)("code", { children: "h264" }), " codec.", (0, jsx_runtime_1.jsx)("br", {}), "Possible values: ", (0, jsx_runtime_1.jsx)("code", { children: "superfast" }), ", ", (0, jsx_runtime_1.jsx)("code", { children: "veryfast" }), ",", ' ', (0, jsx_runtime_1.jsx)("code", { children: "faster" }), ", ", (0, jsx_runtime_1.jsx)("code", { children: "fast" }), ", ", (0, jsx_runtime_1.jsx)("code", { children: "medium" }), ",", ' ', (0, jsx_runtime_1.jsx)("code", { children: "slow" }), ", ", (0, jsx_runtime_1.jsx)("code", { children: "slower" }), ", ", (0, jsx_runtime_1.jsx)("code", { children: "veryslow" }), ",", ' ', (0, jsx_runtime_1.jsx)("code", { children: "placebo" }), ".", (0, jsx_runtime_1.jsx)("br", {}), "Default: ", (0, jsx_runtime_1.jsx)("code", { children: DEFAULT_PRESET })] })),
38
+ ssrName: 'x264Preset',
39
+ docLink: 'https://www.remotion.dev/docs/renderer/render-media',
40
+ type: 'fast',
41
+ getValue: ({ commandLine }) => {
42
+ const value = commandLine[cliFlag];
43
+ if (typeof value !== 'undefined') {
44
+ return { value: value, source: 'cli' };
45
+ }
46
+ if (typeof preset !== 'undefined') {
47
+ return { value: preset, source: 'config' };
48
+ }
49
+ return { value: DEFAULT_PRESET, source: 'default' };
50
+ },
51
+ setConfig: (profile) => {
52
+ preset = profile;
53
+ },
54
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "4.0.126",
3
+ "version": "4.0.127",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -18,7 +18,7 @@
18
18
  "extract-zip": "2.0.1",
19
19
  "source-map": "^0.8.0-beta.0",
20
20
  "ws": "8.7.0",
21
- "remotion": "4.0.126"
21
+ "remotion": "4.0.127"
22
22
  },
23
23
  "peerDependencies": {
24
24
  "react": ">=16.8.0",
@@ -40,13 +40,13 @@
40
40
  "vitest": "0.31.1"
41
41
  },
42
42
  "optionalDependencies": {
43
- "@remotion/compositor-darwin-arm64": "4.0.126",
44
- "@remotion/compositor-linux-arm64-gnu": "4.0.126",
45
- "@remotion/compositor-darwin-x64": "4.0.126",
46
- "@remotion/compositor-linux-arm64-musl": "4.0.126",
47
- "@remotion/compositor-linux-x64-musl": "4.0.126",
48
- "@remotion/compositor-win32-x64-msvc": "4.0.126",
49
- "@remotion/compositor-linux-x64-gnu": "4.0.126"
43
+ "@remotion/compositor-darwin-arm64": "4.0.127",
44
+ "@remotion/compositor-darwin-x64": "4.0.127",
45
+ "@remotion/compositor-linux-arm64-musl": "4.0.127",
46
+ "@remotion/compositor-linux-arm64-gnu": "4.0.127",
47
+ "@remotion/compositor-linux-x64-gnu": "4.0.127",
48
+ "@remotion/compositor-linux-x64-musl": "4.0.127",
49
+ "@remotion/compositor-win32-x64-msvc": "4.0.127"
50
50
  },
51
51
  "keywords": [
52
52
  "remotion",
@@ -1,3 +0,0 @@
1
- import type { AudioCodec } from './audio-codec';
2
- import type { Codec } from './codec';
3
- export declare const canConcatSeamlessly: (audioCodec: AudioCodec | null, codec: Codec) => boolean;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canConcatSeamlessly = void 0;
4
- const canConcatSeamlessly = (audioCodec, codec) => {
5
- return audioCodec === 'aac' && codec === 'h264';
6
- };
7
- exports.canConcatSeamlessly = canConcatSeamlessly;
@@ -1,31 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.separateAudioOption = void 0;
4
- const jsx_runtime_1 = require("react/jsx-runtime");
5
- const DEFAULT = null;
6
- const cliFlag = 'separate-audio-to';
7
- exports.separateAudioOption = {
8
- cliFlag,
9
- description: () => {
10
- return ((0, jsx_runtime_1.jsx)(jsx_runtime_1.Fragment, { children: "If set, the audio will not be included in the main output but rendered as a separate file at the location you pass. It is recommended to use an absolute path. If a relative path is passed, it is relative to the Remotion Root." }));
11
- },
12
- docLink: 'https://remotion.dev/docs/renderer/render-media',
13
- getValue: ({ commandLine }) => {
14
- if (commandLine[cliFlag]) {
15
- return {
16
- source: 'cli',
17
- value: commandLine[cliFlag],
18
- };
19
- }
20
- return {
21
- source: 'default',
22
- value: DEFAULT,
23
- };
24
- },
25
- name: 'Separate audio to',
26
- setConfig: () => {
27
- throw new Error('Not implemented');
28
- },
29
- ssrName: 'separateAudioTo',
30
- type: 'string',
31
- };
@@ -1,3 +0,0 @@
1
- import type { AudioCodec } from './audio-codec';
2
- import type { Codec } from './codec';
3
- export declare const canConcatSeamlessly: (audioCodec: AudioCodec | null, codec: Codec) => boolean;
@@ -1,7 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.canConcatSeamlessly = void 0;
4
- const canConcatSeamlessly = (audioCodec, codec) => {
5
- return audioCodec === 'aac' && codec === 'h264';
6
- };
7
- exports.canConcatSeamlessly = canConcatSeamlessly;
@@ -1,13 +0,0 @@
1
- export declare const supportedAudioCodecs: {
2
- readonly h264: readonly ["aac", "pcm-16", "mp3"];
3
- readonly 'h264-mkv': readonly ["pcm-16", "mp3"];
4
- readonly 'h264-ts': readonly ["pcm-16", "aac"];
5
- readonly aac: readonly ["aac", "pcm-16"];
6
- readonly gif: readonly [];
7
- readonly h265: readonly ["aac", "pcm-16"];
8
- readonly mp3: readonly ["mp3", "pcm-16"];
9
- readonly prores: readonly ["aac", "pcm-16"];
10
- readonly vp8: readonly ["opus", "pcm-16"];
11
- readonly vp9: readonly ["opus", "pcm-16"];
12
- readonly wav: readonly ["pcm-16"];
13
- };