@remotion/renderer 4.0.501 → 4.0.502

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.
@@ -15,6 +15,7 @@ const can_use_parallel_encoding_1 = require("./can-use-parallel-encoding");
15
15
  const cycle_browser_tabs_1 = require("./cycle-browser-tabs");
16
16
  const default_on_log_1 = require("./default-on-log");
17
17
  const find_closest_package_json_1 = require("./find-closest-package-json");
18
+ const frame_range_1 = require("./frame-range");
18
19
  const get_concurrency_1 = require("./get-concurrency");
19
20
  const get_duration_from_frame_range_1 = require("./get-duration-from-frame-range");
20
21
  const get_extra_frames_to_capture_1 = require("./get-extra-frames-to-capture");
@@ -33,9 +34,10 @@ const render_frame_and_retry_target_close_1 = require("./render-frame-and-retry-
33
34
  const replace_browser_1 = require("./replace-browser");
34
35
  const validate_1 = require("./validate");
35
36
  const validate_scale_1 = require("./validate-scale");
37
+ const validate_selected_frames_1 = require("./validate-selected-frames");
36
38
  const wrap_with_error_handling_1 = require("./wrap-with-error-handling");
37
39
  const MAX_RETRIES_PER_FRAME = 1;
38
- const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, jpegQuality, imageFormat, frameRange, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, resolvedConcurrency, everyNthFrame, proxyPort, cancelSignal, downloadMap, muted, makeBrowser, browserReplacer, sourceMapGetter, logLevel, indent, parallelEncodingEnabled, compositionStart, forSeamlessAacConcatenation, onArtifact, binariesDirectory, imageSequencePattern, mediaCacheSizeInBytes, onLog, darkMode, sampleRate, }) => {
40
+ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, jpegQuality, imageFormat, frameRange, frames, outputFramesInSequence, onError, envVariables, onBrowserLog, onFrameBuffer, onDownload, pagesArray, serveUrl, composition, timeoutInMilliseconds, scale, resolvedConcurrency, everyNthFrame, proxyPort, cancelSignal, downloadMap, muted, makeBrowser, browserReplacer, sourceMapGetter, logLevel, indent, parallelEncodingEnabled, compositionStart, forSeamlessAacConcatenation, onArtifact, binariesDirectory, imageSequencePattern, mediaCacheSizeInBytes, onLog, darkMode, sampleRate, }) => {
39
41
  if (outputDir) {
40
42
  if (!node_fs_1.default.existsSync(outputDir)) {
41
43
  node_fs_1.default.mkdirSync(outputDir, {
@@ -44,15 +46,26 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
44
46
  }
45
47
  }
46
48
  const downloadPromises = [];
47
- const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange);
48
- const { extraFramesToCaptureAssetsBackend, extraFramesToCaptureAssetsFrontend, chunkLengthInSeconds, trimLeftOffset, trimRightOffset, } = (0, get_extra_frames_to_capture_1.getExtraFramesToCapture)({
49
+ const framesToRender = frames
50
+ ? (0, validate_selected_frames_1.validateSelectedFrames)({
51
+ frames,
52
+ durationInFrames: composition.durationInFrames,
53
+ })
54
+ : (0, get_duration_from_frame_range_1.getFramesToRender)((0, get_frame_to_render_1.getRealFrameRanges)(composition.durationInFrames, frameRange), everyNthFrame);
55
+ const realFrameRange = [
56
+ framesToRender[0],
57
+ framesToRender[framesToRender.length - 1],
58
+ ];
59
+ const { extraFramesToCaptureAssetsBackend, extraFramesToCaptureAssetsFrontend, chunkLengthInSeconds: seamlessChunkLengthInSeconds, trimLeftOffset, trimRightOffset, } = (0, get_extra_frames_to_capture_1.getExtraFramesToCapture)({
49
60
  fps: composition.fps,
50
61
  compositionStart,
51
62
  realFrameRange,
52
63
  forSeamlessAacConcatenation,
53
64
  sampleRate,
54
65
  });
55
- const framesToRender = (0, get_duration_from_frame_range_1.getFramesToRender)(realFrameRange, everyNthFrame);
66
+ const chunkLengthInSeconds = forSeamlessAacConcatenation
67
+ ? seamlessChunkLengthInSeconds
68
+ : framesToRender.length / composition.fps;
56
69
  const lastFrame = framesToRender[framesToRender.length - 1];
57
70
  const concurrencyOrFramesToRender = Math.min(framesToRender.length, resolvedConcurrency);
58
71
  const makeNewPage = (frame, pageIndex) => {
@@ -93,7 +106,11 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
93
106
  };
94
107
  // If rendering a GIF and skipping frames, we must ensure it starts from 0
95
108
  // and then is consecutive so FFMPEG recognizes the sequence
96
- const countType = everyNthFrame === 1 ? 'actual-frames' : 'from-zero';
109
+ const countType = outputFramesInSequence
110
+ ? 'from-zero'
111
+ : everyNthFrame === 1
112
+ ? 'actual-frames'
113
+ : 'from-zero';
97
114
  const filePadLength = (0, get_frame_padded_index_1.getFilePadLength)({
98
115
  lastFrame,
99
116
  totalFrames: framesToRender.length,
@@ -198,7 +215,7 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
198
215
  frameCount: framesToRender.length,
199
216
  };
200
217
  };
201
- 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, onBrowserDownload, onArtifact, chromeMode, offthreadVideoThreads, imageSequencePattern, mediaCacheSizeInBytes, onLog, sampleRate, }) => {
218
+ const internalRenderFramesRaw = ({ browserExecutable, cancelSignal, chromiumOptions, composition, concurrency, envVariables, everyNthFrame, frameRange, frames, outputFramesInSequence, imageFormat, indent, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, onFrameUpdate, onStart, outputDir, port, puppeteerInstance, scale, server, timeoutInMilliseconds, logLevel, webpackBundleOrServeUrl, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, offthreadVideoCacheSizeInBytes, parallelEncodingEnabled, binariesDirectory, forSeamlessAacConcatenation, compositionStart, onBrowserDownload, onArtifact, chromeMode, offthreadVideoThreads, imageSequencePattern, mediaCacheSizeInBytes, onLog, sampleRate, }) => {
202
219
  (0, validate_1.validateDimension)(composition.height, 'height', 'in the `config` object passed to `renderFrames()`');
203
220
  (0, validate_1.validateDimension)(composition.width, 'width', 'in the `config` object passed to `renderFrames()`');
204
221
  (0, validate_1.validateFps)(composition.fps, 'in the `config` object of `renderFrames()`', false);
@@ -280,6 +297,8 @@ const internalRenderFramesRaw = ({ browserExecutable, cancelSignal, chromiumOpti
280
297
  envVariables,
281
298
  everyNthFrame,
282
299
  frameRange,
300
+ frames,
301
+ outputFramesInSequence,
283
302
  imageFormat,
284
303
  jpegQuality,
285
304
  muted,
@@ -356,10 +375,22 @@ exports.internalRenderFrames = (0, wrap_with_error_handling_1.wrapWithErrorHandl
356
375
  */
357
376
  const renderFrames = (options) => {
358
377
  var _a;
359
- const { composition, inputProps, onFrameUpdate, onStart, outputDir, serveUrl, browserExecutable, cancelSignal, chromiumOptions, concurrency, dumpBrowserLogs, envVariables, everyNthFrame, frameRange, imageFormat, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, port, puppeteerInstance, scale, timeoutInMilliseconds, verbose, quality, logLevel: passedLogLevel, offthreadVideoCacheSizeInBytes, binariesDirectory, onBrowserDownload, onArtifact, chromeMode, offthreadVideoThreads, imageSequencePattern, mediaCacheSizeInBytes, sampleRate, } = options;
378
+ const { composition, inputProps, onFrameUpdate, onStart, outputDir, serveUrl, browserExecutable, cancelSignal, chromiumOptions, concurrency, dumpBrowserLogs, envVariables, everyNthFrame, frameRange, frames, imageFormat, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, port, puppeteerInstance, scale, timeoutInMilliseconds, verbose, quality, logLevel: passedLogLevel, offthreadVideoCacheSizeInBytes, binariesDirectory, onBrowserDownload, onArtifact, chromeMode, offthreadVideoThreads, imageSequencePattern, mediaCacheSizeInBytes, sampleRate, } = options;
360
379
  if (!composition) {
361
380
  throw new Error('No `composition` option has been specified for renderFrames()');
362
381
  }
382
+ if (frames !== undefined && frameRange !== undefined && frameRange !== null) {
383
+ throw new Error('The `frames` and `frameRange` options are mutually exclusive.');
384
+ }
385
+ if (frames !== undefined && everyNthFrame !== undefined) {
386
+ throw new Error('The `frames` and `everyNthFrame` options are mutually exclusive.');
387
+ }
388
+ const validatedFrames = frames === undefined
389
+ ? null
390
+ : (0, validate_selected_frames_1.validateSelectedFrames)({
391
+ frames,
392
+ durationInFrames: composition.durationInFrames,
393
+ });
363
394
  if (typeof jpegQuality !== 'undefined' && imageFormat !== 'jpeg') {
364
395
  throw new Error("You can only pass the `quality` option if `imageFormat` is 'jpeg'.");
365
396
  }
@@ -377,6 +408,10 @@ const renderFrames = (options) => {
377
408
  envVariables: envVariables !== null && envVariables !== void 0 ? envVariables : {},
378
409
  everyNthFrame: everyNthFrame !== null && everyNthFrame !== void 0 ? everyNthFrame : 1,
379
410
  frameRange: frameRange !== null && frameRange !== void 0 ? frameRange : null,
411
+ frames: validatedFrames,
412
+ outputFramesInSequence: frameRange !== undefined &&
413
+ frameRange !== null &&
414
+ (0, frame_range_1.isMultipleFrameRanges)(frameRange),
380
415
  imageFormat: imageFormat !== null && imageFormat !== void 0 ? imageFormat : 'jpeg',
381
416
  indent,
382
417
  jpegQuality: jpegQuality !== null && jpegQuality !== void 0 ? jpegQuality : jpeg_quality_1.DEFAULT_JPEG_QUALITY,
@@ -199,9 +199,11 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
199
199
  height: heightEvenDimensions,
200
200
  width: widthEvenDimensions,
201
201
  };
202
- const realFrameRange = (0, get_frame_to_render_1.getRealFrameRange)(composition.durationInFrames, frameRange);
203
- const totalFramesToRender = (0, get_duration_from_frame_range_1.getFramesToRender)(realFrameRange, everyNthFrame).length;
204
- logger_1.Log.verbose({ indent, logLevel, tag: 'renderMedia()' }, `Rendering frames ${realFrameRange.join('-')}`);
202
+ const realFrameRanges = (0, get_frame_to_render_1.getRealFrameRanges)(composition.durationInFrames, frameRange);
203
+ const framesToRender = (0, get_duration_from_frame_range_1.getFramesToRender)(realFrameRanges, everyNthFrame);
204
+ const totalFramesToRender = framesToRender.length;
205
+ const lastFrameToRender = framesToRender[framesToRender.length - 1];
206
+ logger_1.Log.verbose({ indent, logLevel, tag: 'renderMedia()' }, `Rendering frames ${realFrameRanges.map((range) => range.join('-')).join(', ')}`);
205
207
  const callUpdate = () => {
206
208
  const encoded = Math.round(0.8 * encodedFrames + 0.2 * muxedFrames);
207
209
  onProgress === null || onProgress === void 0 ? void 0 : onProgress({
@@ -221,7 +223,7 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
221
223
  cancelSignal === null || cancelSignal === void 0 ? void 0 : cancelSignal(() => {
222
224
  cancelRenderFrames.cancel();
223
225
  });
224
- const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(realFrameRange);
226
+ const { waitForRightTimeOfFrameToBeInserted, setFrameToStitch, waitForFinish } = (0, ensure_frames_in_order_1.ensureFramesInOrder)(framesToRender);
225
227
  const fps = composition.fps / everyNthFrame;
226
228
  (0, validate_1.validateFps)(fps, 'in "renderMedia()"', codec === 'gif');
227
229
  const createPrestitcherIfNecessary = () => {
@@ -352,11 +354,14 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
352
354
  imageFormat,
353
355
  jpegQuality,
354
356
  frameRange,
357
+ frames: null,
358
+ outputFramesInSequence: true,
355
359
  puppeteerInstance,
356
360
  everyNthFrame,
357
361
  onFrameBuffer: parallelEncoding
358
362
  ? async (buffer, frame) => {
359
363
  var _a;
364
+ var _b;
360
365
  await waitForRightTimeOfFrameToBeInserted(frame);
361
366
  if (cancelled) {
362
367
  return;
@@ -371,7 +376,8 @@ const internalRenderMediaRaw = ({ proResProfile, x264Preset, gopSize, crf, compo
371
376
  }
372
377
  (_a = stitcherFfmpeg === null || stitcherFfmpeg === void 0 ? void 0 : stitcherFfmpeg.stdin) === null || _a === void 0 ? void 0 : _a.write(buffer);
373
378
  (0, perf_1.stopPerfMeasure)(id);
374
- setFrameToStitch(Math.min(realFrameRange[1] + 1, frame + everyNthFrame));
379
+ const frameIndex = framesToRender.indexOf(frame);
380
+ setFrameToStitch((_b = framesToRender[frameIndex + 1]) !== null && _b !== void 0 ? _b : lastFrameToRender + 1);
375
381
  }
376
382
  : null,
377
383
  webpackBundleOrServeUrl: serveUrl,
@@ -0,0 +1,4 @@
1
+ export declare const validateSelectedFrames: ({ frames, durationInFrames, }: {
2
+ frames: unknown;
3
+ durationInFrames: number | null;
4
+ }) => number[];
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateSelectedFrames = void 0;
4
+ const validateSelectedFrames = ({ frames, durationInFrames, }) => {
5
+ if (!Array.isArray(frames)) {
6
+ throw new TypeError('The `frames` option must be an array of frame numbers.');
7
+ }
8
+ if (frames.length === 0) {
9
+ throw new TypeError('The `frames` option must contain at least one frame.');
10
+ }
11
+ for (const frame of frames) {
12
+ if (typeof frame !== 'number') {
13
+ throw new TypeError(`The \`frames\` option must contain only numbers, but got ${typeof frame}.`);
14
+ }
15
+ if (!Number.isFinite(frame)) {
16
+ throw new TypeError(`The \`frames\` option must contain only finite numbers, but got ${frame}.`);
17
+ }
18
+ if (!Number.isInteger(frame)) {
19
+ throw new TypeError(`The \`frames\` option must contain only integers, but got ${frame}.`);
20
+ }
21
+ if (frame < 0) {
22
+ throw new TypeError(`The \`frames\` option must contain only non-negative numbers, but got ${frame}.`);
23
+ }
24
+ if (durationInFrames !== null && frame >= durationInFrames) {
25
+ throw new RangeError(`Frame ${frame} is out of range, must be between 0 and ${durationInFrames - 1}.`);
26
+ }
27
+ }
28
+ const uniqueFrames = new Set(frames);
29
+ if (uniqueFrames.size !== frames.length) {
30
+ throw new TypeError('The `frames` option must not contain duplicate frames.');
31
+ }
32
+ return [...frames].sort((a, b) => a - b);
33
+ };
34
+ exports.validateSelectedFrames = validateSelectedFrames;
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/renderer"
4
4
  },
5
5
  "name": "@remotion/renderer",
6
- "version": "4.0.501",
6
+ "version": "4.0.502",
7
7
  "description": "Render Remotion videos using Node.js or Bun",
8
8
  "main": "dist/index.js",
9
9
  "types": "dist/index.d.ts",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "execa": "5.1.1",
25
- "remotion": "4.0.501",
26
- "@remotion/streaming": "4.0.501",
25
+ "remotion": "4.0.502",
26
+ "@remotion/streaming": "4.0.502",
27
27
  "source-map": "0.8.0",
28
28
  "ws": "8.21.0",
29
- "@remotion/licensing": "4.0.501"
29
+ "@remotion/licensing": "4.0.502"
30
30
  },
31
31
  "peerDependencies": {
32
32
  "react": ">=16.8.0",
@@ -40,19 +40,19 @@
40
40
  "react-dom": "19.2.3",
41
41
  "@typescript/native-preview": "7.0.0-dev.20260217.1",
42
42
  "@types/ws": "8.5.10",
43
- "@remotion/example-videos": "4.0.501",
44
- "@remotion/eslint-config-internal": "4.0.501",
43
+ "@remotion/example-videos": "4.0.502",
44
+ "@remotion/eslint-config-internal": "4.0.502",
45
45
  "eslint": "9.19.0",
46
46
  "@types/node": "20.12.14"
47
47
  },
48
48
  "optionalDependencies": {
49
- "@remotion/compositor-darwin-arm64": "4.0.501",
50
- "@remotion/compositor-darwin-x64": "4.0.501",
51
- "@remotion/compositor-linux-arm64-gnu": "4.0.501",
52
- "@remotion/compositor-linux-arm64-musl": "4.0.501",
53
- "@remotion/compositor-linux-x64-gnu": "4.0.501",
54
- "@remotion/compositor-linux-x64-musl": "4.0.501",
55
- "@remotion/compositor-win32-x64-msvc": "4.0.501"
49
+ "@remotion/compositor-darwin-arm64": "4.0.502",
50
+ "@remotion/compositor-darwin-x64": "4.0.502",
51
+ "@remotion/compositor-linux-arm64-gnu": "4.0.502",
52
+ "@remotion/compositor-linux-arm64-musl": "4.0.502",
53
+ "@remotion/compositor-linux-x64-gnu": "4.0.502",
54
+ "@remotion/compositor-linux-x64-musl": "4.0.502",
55
+ "@remotion/compositor-win32-x64-msvc": "4.0.502"
56
56
  },
57
57
  "keywords": [
58
58
  "remotion",