@remotion/renderer 3.2.31 → 3.2.32

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.
package/dist/crf.d.ts CHANGED
@@ -2,4 +2,8 @@ import type { Codec } from './codec';
2
2
  export declare type Crf = number | undefined;
3
3
  export declare const getDefaultCrfForCodec: (codec: Codec) => number;
4
4
  export declare const getValidCrfRanges: (codec: Codec) => [number, number];
5
- export declare const validateSelectedCrfAndCodecCombination: (crf: unknown, codec: Codec) => void;
5
+ export declare const validateQualitySettings: ({ codec, crf, videoBitrate, }: {
6
+ crf: unknown;
7
+ codec: Codec;
8
+ videoBitrate: string | null | undefined;
9
+ }) => string[];
package/dist/crf.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.validateSelectedCrfAndCodecCombination = exports.getValidCrfRanges = exports.getDefaultCrfForCodec = void 0;
3
+ exports.validateQualitySettings = exports.getValidCrfRanges = exports.getDefaultCrfForCodec = void 0;
4
4
  const is_audio_codec_1 = require("./is-audio-codec");
5
5
  const getDefaultCrfForCodec = (codec) => {
6
6
  if ((0, is_audio_codec_1.isAudioCodec)(codec)) {
@@ -46,19 +46,46 @@ const getValidCrfRanges = (codec) => {
46
46
  throw new TypeError(`Got unexpected codec "${codec}"`);
47
47
  };
48
48
  exports.getValidCrfRanges = getValidCrfRanges;
49
- const validateSelectedCrfAndCodecCombination = (crf, codec) => {
50
- if (crf === null) {
51
- return;
49
+ const validateQualitySettings = ({ codec, crf, videoBitrate, }) => {
50
+ if (crf && videoBitrate) {
51
+ throw new Error('"crf" and "videoBitrate" can not both be set. Choose one of either.');
52
+ }
53
+ if (videoBitrate) {
54
+ if (codec === 'prores') {
55
+ console.warn('ProRes does not support videoBitrate. Ignoring.');
56
+ return [];
57
+ }
58
+ if ((0, is_audio_codec_1.isAudioCodec)(codec)) {
59
+ console.warn(`${codec} does not support videoBitrate. Ignoring.`);
60
+ return [];
61
+ }
62
+ return ['-b:v', videoBitrate];
63
+ }
64
+ if (crf === null || typeof crf === 'undefined') {
65
+ const actualCrf = (0, exports.getDefaultCrfForCodec)(codec);
66
+ return ['-crf', String(actualCrf)];
52
67
  }
53
68
  if (typeof crf !== 'number') {
54
69
  throw new TypeError('Expected CRF to be a number, but is ' + JSON.stringify(crf));
55
70
  }
56
71
  const range = (0, exports.getValidCrfRanges)(codec);
57
72
  if (crf === 0 && (codec === 'h264' || codec === 'h264-mkv')) {
58
- throw new TypeError("Setting the CRF to 0 with a H264 codec is not supported anymore because of it's inconsistencies between platforms. Videos with CRF 0 cannot be played on iOS/macOS. 0 is a extreme value with inefficient settings which you probably want. Set CRF to a higher value to fix this error.");
73
+ throw new TypeError("Setting the CRF to 0 with a H264 codec is not supported anymore because of it's inconsistencies between platforms. Videos with CRF 0 cannot be played on iOS/macOS. 0 is a extreme value with inefficient settings which you probably do not want. Set CRF to a higher value to fix this error.");
59
74
  }
60
75
  if (crf < range[0] || crf > range[1]) {
76
+ if (range[0] === 0 && range[1] === 0) {
77
+ throw new TypeError(`The "${codec}" codec does not support the --crf option.`);
78
+ }
61
79
  throw new TypeError(`CRF must be between ${range[0]} and ${range[1]} for codec ${codec}. Passed: ${crf}`);
62
80
  }
81
+ if (codec === 'prores') {
82
+ console.warn('ProRes does not support the "crf" option. Ignoring.');
83
+ return [];
84
+ }
85
+ if ((0, is_audio_codec_1.isAudioCodec)(codec)) {
86
+ console.warn(`${codec} does not support the "crf" option. Ignoring.`);
87
+ return [];
88
+ }
89
+ return ['-crf', String(crf)];
63
90
  };
64
- exports.validateSelectedCrfAndCodecCombination = validateSelectedCrfAndCodecCombination;
91
+ exports.validateQualitySettings = validateQualitySettings;
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import type { OffthreadVideoImageFormat } from 'remotion';
2
3
  import type { DownloadMap } from './assets/download-map';
3
4
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -0,0 +1,4 @@
1
+ export declare type FfmpegArgsHook = (info: {
2
+ type: 'pre-stitcher' | 'stitcher';
3
+ args: string[];
4
+ }) => string[];
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import execa from 'execa';
2
3
  import { SymbolicateableError } from './error-handling/symbolicateable-error';
3
4
  import { mimeContentType, mimeLookup } from './mime-types';
@@ -68,7 +69,7 @@ export declare const RenderInternals: {
68
69
  task: Promise<Buffer | null>;
69
70
  getLogs: () => string;
70
71
  }>;
71
- getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
72
+ getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "webm" | "mp4" | "mov" | "mkv";
72
73
  tmpDir: (str: string) => string;
73
74
  deleteDirectory: (directory: string) => Promise<void>;
74
75
  isServeUrl: (potentialUrl: string) => boolean;
@@ -123,10 +124,13 @@ export declare const RenderInternals: {
123
124
  validPixelFormats: readonly ["yuv420p", "yuva420p", "yuv422p", "yuv444p", "yuv420p10le", "yuv422p10le", "yuv444p10le", "yuva444p10le"];
124
125
  DEFAULT_BROWSER: import("./browser").Browser;
125
126
  validateFrameRange: (frameRange: import("./frame-range").FrameRange | null) => void;
126
- DEFAULT_OPENGL_RENDERER: "swangle" | "angle" | "egl" | "swiftshader" | null;
127
- validateOpenGlRenderer: (option: "swangle" | "angle" | "egl" | "swiftshader" | null) => "swangle" | "angle" | "egl" | "swiftshader" | null;
128
- getDefaultCrfForCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => number;
129
- validateSelectedCrfAndCodecCombination: (crf: unknown, codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
127
+ DEFAULT_OPENGL_RENDERER: "angle" | "swangle" | "egl" | "swiftshader" | null;
128
+ validateOpenGlRenderer: (option: "angle" | "swangle" | "egl" | "swiftshader" | null) => "angle" | "swangle" | "egl" | "swiftshader" | null;
129
+ validateQualitySettings: ({ codec, crf, videoBitrate, }: {
130
+ crf: unknown;
131
+ codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
132
+ videoBitrate: string | null | undefined;
133
+ }) => string[];
130
134
  validImageFormats: readonly ["png", "jpeg", "none"];
131
135
  validCodecs: readonly ["h264", "h265", "vp8", "vp9", "mp3", "aac", "wav", "prores", "h264-mkv", "gif"];
132
136
  DEFAULT_PIXEL_FORMAT: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le";
@@ -137,9 +141,9 @@ export declare const RenderInternals: {
137
141
  validateSelectedPixelFormatAndCodecCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
138
142
  validateSelectedCodecAndProResCombination: ({ codec, proResProfile, }: {
139
143
  codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
140
- proResProfile: "4444-xq" | "4444" | "hq" | "standard" | "light" | "proxy" | undefined;
144
+ proResProfile: "proxy" | "4444-xq" | "4444" | "hq" | "standard" | "light" | undefined;
141
145
  }) => void;
142
- validateSelectedPixelFormatAndImageFormatCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", imageFormat: "png" | "jpeg" | "none") => "none" | "valid";
146
+ validateSelectedPixelFormatAndImageFormatCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", imageFormat: "jpeg" | "png" | "none") => "none" | "valid";
143
147
  DEFAULT_CODEC: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
144
148
  isAudioCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
145
149
  logLevels: readonly ["verbose", "info", "warn", "error"];
@@ -153,4 +157,5 @@ export declare const RenderInternals: {
153
157
  frame: number;
154
158
  durationInFrames: number;
155
159
  }) => number;
160
+ validateBitrate: (bitrate: unknown, name: string) => void;
156
161
  };
package/dist/index.js CHANGED
@@ -74,6 +74,7 @@ const validate_frame_1 = require("./validate-frame");
74
74
  const validate_opengl_renderer_1 = require("./validate-opengl-renderer");
75
75
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
76
76
  const validate_scale_1 = require("./validate-scale");
77
+ const validate_videobitrate_1 = require("./validate-videobitrate");
77
78
  const wait_for_symbolication_error_to_be_done_1 = require("./wait-for-symbolication-error-to-be-done");
78
79
  var combine_videos_1 = require("./combine-videos");
79
80
  Object.defineProperty(exports, "combineVideos", { enumerable: true, get: function () { return combine_videos_1.combineVideos; } });
@@ -139,8 +140,7 @@ exports.RenderInternals = {
139
140
  validateFrameRange: frame_range_1.validateFrameRange,
140
141
  DEFAULT_OPENGL_RENDERER: validate_opengl_renderer_1.DEFAULT_OPENGL_RENDERER,
141
142
  validateOpenGlRenderer: validate_opengl_renderer_1.validateOpenGlRenderer,
142
- getDefaultCrfForCodec: crf_1.getDefaultCrfForCodec,
143
- validateSelectedCrfAndCodecCombination: crf_1.validateSelectedCrfAndCodecCombination,
143
+ validateQualitySettings: crf_1.validateQualitySettings,
144
144
  validImageFormats: image_format_1.validImageFormats,
145
145
  validCodecs: codec_1.validCodecs,
146
146
  DEFAULT_PIXEL_FORMAT: pixel_format_1.DEFAULT_PIXEL_FORMAT,
@@ -161,6 +161,7 @@ exports.RenderInternals = {
161
161
  makeDownloadMap: download_map_1.makeDownloadMap,
162
162
  cleanDownloadMap: download_map_1.cleanDownloadMap,
163
163
  convertToPositiveFrameIndex: convert_to_positive_frame_index_1.convertToPositiveFrameIndex,
164
+ validateBitrate: validate_videobitrate_1.validateBitrate,
164
165
  };
165
166
  // Warn of potential performance issues with Apple Silicon (M1 chip under Rosetta)
166
167
  (0, check_apple_silicon_1.warnIfAppleSiliconIsNotUsingArm64Architecture)();
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import type { OffthreadVideoImageFormat } from 'remotion';
2
3
  import type { DownloadMap, SpecialVCodecForTransparency } from './assets/download-map';
3
4
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -0,0 +1 @@
1
+ export declare const makeAssetsDownloadTmpDir: () => string;
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.makeAssetsDownloadTmpDir = void 0;
4
+ const tmp_dir_1 = require("./tmp-dir");
5
+ let dir = null;
6
+ const makeAssetsDownloadTmpDir = () => {
7
+ if (dir) {
8
+ return dir;
9
+ }
10
+ dir = (0, tmp_dir_1.tmpDir)('remotion-assets-dir');
11
+ return dir;
12
+ };
13
+ exports.makeAssetsDownloadTmpDir = makeAssetsDownloadTmpDir;
@@ -19,8 +19,9 @@ declare type PreSticherOptions = {
19
19
  verbose: boolean;
20
20
  ffmpegExecutable: FfmpegExecutable | undefined;
21
21
  imageFormat: ImageFormat;
22
- ffmpegOverride?: FfmpegOverrideFn;
22
+ ffmpegOverride: FfmpegOverrideFn;
23
23
  signal: CancelSignal;
24
+ videoBitrate: string | null;
24
25
  };
25
26
  export declare const prespawnFfmpeg: (options: PreSticherOptions) => Promise<{
26
27
  task: execa.ExecaChildProcess<string>;
@@ -15,7 +15,7 @@ const pixel_format_1 = require("./pixel-format");
15
15
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
16
16
  const validate_ffmpeg_1 = require("./validate-ffmpeg");
17
17
  const prespawnFfmpeg = async (options) => {
18
- var _a, _b, _c, _d, _e, _f, _g;
18
+ var _a, _b, _c, _d, _e, _f;
19
19
  remotion_1.Internals.validateDimension(options.height, 'height', 'passed to `stitchFramesToVideo()`');
20
20
  remotion_1.Internals.validateDimension(options.width, 'width', 'passed to `stitchFramesToVideo()`');
21
21
  const codec = (_a = options.codec) !== null && _a !== void 0 ? _a : codec_1.DEFAULT_CODEC;
@@ -26,9 +26,8 @@ const prespawnFfmpeg = async (options) => {
26
26
  codec,
27
27
  scale: 1,
28
28
  });
29
- const crf = (_b = options.crf) !== null && _b !== void 0 ? _b : (0, crf_1.getDefaultCrfForCodec)(codec);
30
- const pixelFormat = (_c = options.pixelFormat) !== null && _c !== void 0 ? _c : pixel_format_1.DEFAULT_PIXEL_FORMAT;
31
- await (0, validate_ffmpeg_1.validateFfmpeg)((_d = options.ffmpegExecutable) !== null && _d !== void 0 ? _d : null);
29
+ const pixelFormat = (_b = options.pixelFormat) !== null && _b !== void 0 ? _b : pixel_format_1.DEFAULT_PIXEL_FORMAT;
30
+ await (0, validate_ffmpeg_1.validateFfmpeg)((_c = options.ffmpegExecutable) !== null && _c !== void 0 ? _c : null);
32
31
  const encoderName = (0, get_codec_name_1.getCodecName)(codec);
33
32
  const proResProfileName = (0, get_prores_profile_name_1.getProResProfileName)(codec, options.proResProfile);
34
33
  if (encoderName === null) {
@@ -36,16 +35,15 @@ const prespawnFfmpeg = async (options) => {
36
35
  }
37
36
  const supportsCrf = codec !== 'prores';
38
37
  if (options.verbose) {
39
- console.log('[verbose] ffmpeg', (_e = options.ffmpegExecutable) !== null && _e !== void 0 ? _e : 'ffmpeg in PATH');
38
+ console.log('[verbose] ffmpeg', (_d = options.ffmpegExecutable) !== null && _d !== void 0 ? _d : 'ffmpeg in PATH');
40
39
  console.log('[verbose] encoder', encoderName);
41
40
  console.log('[verbose] pixelFormat', pixelFormat);
42
41
  if (supportsCrf) {
43
- console.log('[verbose] crf', crf);
42
+ console.log('[verbose] crf', options.crf);
44
43
  }
45
44
  console.log('[verbose] codec', codec);
46
45
  console.log('[verbose] proResProfileName', proResProfileName);
47
46
  }
48
- (0, crf_1.validateSelectedCrfAndCodecCombination)(crf, codec);
49
47
  (0, pixel_format_1.validateSelectedPixelFormatAndCodecCombination)(pixelFormat, codec);
50
48
  const ffmpegArgs = [
51
49
  ['-r', options.fps.toFixed(2)],
@@ -61,12 +59,15 @@ const prespawnFfmpeg = async (options) => {
61
59
  // and specified the video codec.
62
60
  ['-c:v', encoderName],
63
61
  proResProfileName ? ['-profile:v', proResProfileName] : null,
64
- supportsCrf ? ['-crf', String(crf)] : null,
65
62
  ['-pix_fmt', pixelFormat],
66
63
  // Without explicitly disabling auto-alt-ref,
67
64
  // transparent WebM generation doesn't work
68
65
  pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
69
- ['-b:v', '1M'],
66
+ ...(0, crf_1.validateQualitySettings)({
67
+ crf: options.crf,
68
+ videoBitrate: options.videoBitrate,
69
+ codec,
70
+ }),
70
71
  '-y',
71
72
  options.outputLocation,
72
73
  ];
@@ -78,12 +79,12 @@ const prespawnFfmpeg = async (options) => {
78
79
  const finalFfmpegString = options.ffmpegOverride
79
80
  ? options.ffmpegOverride({ type: 'pre-stitcher', args: ffmpegString })
80
81
  : ffmpegString;
81
- const task = (0, execa_1.default)((_f = options.ffmpegExecutable) !== null && _f !== void 0 ? _f : 'ffmpeg', finalFfmpegString);
82
+ const task = (0, execa_1.default)((_e = options.ffmpegExecutable) !== null && _e !== void 0 ? _e : 'ffmpeg', finalFfmpegString);
82
83
  options.signal(() => {
83
84
  task.kill();
84
85
  });
85
86
  let ffmpegOutput = '';
86
- (_g = task.stderr) === null || _g === void 0 ? void 0 : _g.on('data', (data) => {
87
+ (_f = task.stderr) === null || _f === void 0 ? void 0 : _f.on('data', (data) => {
87
88
  const str = data.toString();
88
89
  ffmpegOutput += str;
89
90
  if (options.onProgress) {
@@ -1,3 +1,4 @@
1
+ /// <reference types="node" />
1
2
  import type { SmallTCompMetadata } from 'remotion';
2
3
  import type { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
3
4
  import type { DownloadMap } from './assets/download-map';
@@ -65,6 +66,8 @@ export declare type RenderMediaOptions = {
65
66
  muted?: boolean;
66
67
  enforceAudioTrack?: boolean;
67
68
  ffmpegOverride?: FfmpegOverrideFn;
69
+ audioBitrate?: string | null;
70
+ videoBitrate?: string | null;
68
71
  onSlowestFrames?: OnSlowestFrames;
69
72
  disallowParallelEncoding?: boolean;
70
73
  } & ServeUrlOrWebpackBundle & ConcurrencyOrParallelism;
@@ -81,5 +84,5 @@ declare type ConcurrencyOrParallelism = {
81
84
  * @description Render a video from a composition
82
85
  * @link https://www.remotion.dev/docs/renderer/render-media
83
86
  */
84
- export declare const renderMedia: ({ proResProfile, crf, composition, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, onSlowestFrames, ...options }: RenderMediaOptions) => Promise<Buffer | null>;
87
+ export declare const renderMedia: ({ proResProfile, crf, composition, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, audioBitrate, videoBitrate, onSlowestFrames, ...options }: RenderMediaOptions) => Promise<Buffer | null>;
85
88
  export {};
@@ -34,6 +34,7 @@ const validate_even_dimensions_with_codec_1 = require("./validate-even-dimension
34
34
  const validate_ffmpeg_override_1 = require("./validate-ffmpeg-override");
35
35
  const validate_output_filename_1 = require("./validate-output-filename");
36
36
  const validate_scale_1 = require("./validate-scale");
37
+ const validate_videobitrate_1 = require("./validate-videobitrate");
37
38
  const SLOWEST_FRAME_COUNT = 10;
38
39
  const getConcurrency = (others) => {
39
40
  if ('concurrency' in others) {
@@ -49,12 +50,12 @@ const getConcurrency = (others) => {
49
50
  * @description Render a video from a composition
50
51
  * @link https://www.remotion.dev/docs/renderer/render-media
51
52
  */
52
- const renderMedia = ({ proResProfile, crf, composition, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, onSlowestFrames, ...options }) => {
53
+ const renderMedia = ({ proResProfile, crf, composition, ffmpegExecutable, ffprobeExecutable, inputProps, pixelFormat, codec, envVariables, frameRange, puppeteerInstance, outputLocation, onProgress, overwrite, onDownload, dumpBrowserLogs, onBrowserLog, onStart, timeoutInMilliseconds, chromiumOptions, scale, browserExecutable, port, cancelSignal, muted, enforceAudioTrack, ffmpegOverride, audioBitrate, videoBitrate, onSlowestFrames, ...options }) => {
53
54
  var _a, _b, _c, _d;
54
55
  (0, quality_1.validateQuality)(options.quality);
55
- if (typeof crf !== 'undefined' && crf !== null) {
56
- (0, crf_1.validateSelectedCrfAndCodecCombination)(crf, codec);
57
- }
56
+ (0, crf_1.validateQualitySettings)({ crf, codec, videoBitrate });
57
+ (0, validate_videobitrate_1.validateBitrate)(audioBitrate, 'audioBitrate');
58
+ (0, validate_videobitrate_1.validateBitrate)(videoBitrate, 'videoBitrate');
58
59
  (0, prores_profile_1.validateSelectedCodecAndProResCombination)({
59
60
  codec,
60
61
  proResProfile,
@@ -157,7 +158,8 @@ const renderMedia = ({ proResProfile, crf, composition, ffmpegExecutable, ffprob
157
158
  ffmpegExecutable,
158
159
  imageFormat,
159
160
  signal: cancelPrestitcher.cancelSignal,
160
- ffmpegOverride,
161
+ ffmpegOverride: ffmpegOverride !== null && ffmpegOverride !== void 0 ? ffmpegOverride : (({ args }) => args),
162
+ videoBitrate: videoBitrate !== null && videoBitrate !== void 0 ? videoBitrate : null,
161
163
  });
162
164
  stitcherFfmpeg = preStitcher.task;
163
165
  }
@@ -295,6 +297,8 @@ const renderMedia = ({ proResProfile, crf, composition, ffmpegExecutable, ffprob
295
297
  muted: disableAudio,
296
298
  enforceAudioTrack,
297
299
  ffmpegOverride,
300
+ audioBitrate,
301
+ videoBitrate,
298
302
  }),
299
303
  stitchStart,
300
304
  ]);
@@ -9,6 +9,8 @@ import type { CancelSignal } from './make-cancel-signal';
9
9
  import type { PixelFormat } from './pixel-format';
10
10
  import type { ProResProfile } from './prores-profile';
11
11
  export declare type StitcherOptions = {
12
+ audioBitrate?: string | null;
13
+ videoBitrate?: string | null;
12
14
  fps: number;
13
15
  width: number;
14
16
  height: number;
@@ -29,6 +29,7 @@ const prores_profile_1 = require("./prores-profile");
29
29
  const truthy_1 = require("./truthy");
30
30
  const validate_even_dimensions_with_codec_1 = require("./validate-even-dimensions-with-codec");
31
31
  const validate_ffmpeg_1 = require("./validate-ffmpeg");
32
+ const validate_videobitrate_1 = require("./validate-videobitrate");
32
33
  const packageJsonPath = path_1.default.join(__dirname, '..', 'package.json');
33
34
  const packageJson = fs_1.default.existsSync(packageJsonPath)
34
35
  ? JSON.parse(fs_1.default.readFileSync(packageJsonPath, 'utf-8'))
@@ -93,15 +94,15 @@ const spawnFfmpeg = async (options) => {
93
94
  codec,
94
95
  proResProfile: options.proResProfile,
95
96
  });
97
+ (0, validate_videobitrate_1.validateBitrate)(options.audioBitrate, 'audioBitrate');
98
+ (0, validate_videobitrate_1.validateBitrate)(options.videoBitrate, 'videoBitrate');
96
99
  remotion_1.Internals.validateFps(options.fps, 'in `stitchFramesToVideo()`', false);
97
- const crf = (_b = options.crf) !== null && _b !== void 0 ? _b : (0, crf_1.getDefaultCrfForCodec)(codec);
98
- const pixelFormat = (_c = options.pixelFormat) !== null && _c !== void 0 ? _c : pixel_format_1.DEFAULT_PIXEL_FORMAT;
99
- await (0, validate_ffmpeg_1.validateFfmpeg)((_d = options.ffmpegExecutable) !== null && _d !== void 0 ? _d : null);
100
+ const pixelFormat = (_b = options.pixelFormat) !== null && _b !== void 0 ? _b : pixel_format_1.DEFAULT_PIXEL_FORMAT;
101
+ await (0, validate_ffmpeg_1.validateFfmpeg)((_c = options.ffmpegExecutable) !== null && _c !== void 0 ? _c : null);
100
102
  const encoderName = (0, get_codec_name_1.getCodecName)(codec);
101
103
  const audioCodecName = (0, get_audio_codec_name_1.getAudioCodecName)(codec);
102
104
  const proResProfileName = (0, get_prores_profile_name_1.getProResProfileName)(codec, options.proResProfile);
103
105
  const mediaSupport = (0, codec_supports_media_1.codecSupportsMedia)(codec);
104
- const supportsCrf = (0, codec_supports_media_1.codecSupportsCrf)(codec);
105
106
  const tempFile = options.outputLocation
106
107
  ? null
107
108
  : path_1.default.join(options.assetsInfo.downloadMap.stitchFrames, `out.${(0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, 'final')}`);
@@ -114,13 +115,10 @@ const spawnFfmpeg = async (options) => {
114
115
  throw new Error('The output format has neither audio nor video. This can happen if you are rendering an audio codec and the output file has no audio or the muted flag was passed.');
115
116
  }
116
117
  if (options.verbose) {
117
- console.log('[verbose] ffmpeg', (_e = options.ffmpegExecutable) !== null && _e !== void 0 ? _e : 'ffmpeg in PATH');
118
+ console.log('[verbose] ffmpeg', (_d = options.ffmpegExecutable) !== null && _d !== void 0 ? _d : 'ffmpeg in PATH');
118
119
  console.log('[verbose] encoder', encoderName);
119
120
  console.log('[verbose] audioCodec', audioCodecName);
120
121
  console.log('[verbose] pixelFormat', pixelFormat);
121
- if (supportsCrf) {
122
- console.log('[verbose] crf', crf);
123
- }
124
122
  if (options.ffmpegOverride) {
125
123
  console.log('[verbose] ffmpegOverride', options.ffmpegOverride);
126
124
  }
@@ -129,7 +127,11 @@ const spawnFfmpeg = async (options) => {
129
127
  console.log('[verbose] shouldRenderVideo', shouldRenderVideo);
130
128
  console.log('[verbose] proResProfileName', proResProfileName);
131
129
  }
132
- (0, crf_1.validateSelectedCrfAndCodecCombination)(crf, codec);
130
+ (0, crf_1.validateQualitySettings)({
131
+ crf: options.crf,
132
+ codec,
133
+ videoBitrate: options.videoBitrate,
134
+ });
133
135
  (0, pixel_format_1.validateSelectedPixelFormatAndCodecCombination)(pixelFormat, codec);
134
136
  const expectedFrames = options.assetsInfo.assets.length;
135
137
  const updateProgress = (preStitchProgress, muxProgress) => {
@@ -143,9 +145,9 @@ const spawnFfmpeg = async (options) => {
143
145
  onDownload: options.onDownload,
144
146
  fps: options.fps,
145
147
  expectedFrames,
146
- verbose: (_f = options.verbose) !== null && _f !== void 0 ? _f : false,
147
- ffmpegExecutable: (_g = options.ffmpegExecutable) !== null && _g !== void 0 ? _g : null,
148
- ffprobeExecutable: (_h = options.ffprobeExecutable) !== null && _h !== void 0 ? _h : null,
148
+ verbose: (_e = options.verbose) !== null && _e !== void 0 ? _e : false,
149
+ ffmpegExecutable: (_f = options.ffmpegExecutable) !== null && _f !== void 0 ? _f : null,
150
+ ffprobeExecutable: (_g = options.ffprobeExecutable) !== null && _g !== void 0 ? _g : null,
149
151
  onProgress: (prog) => updateProgress(prog, 0),
150
152
  downloadMap: options.assetsInfo.downloadMap,
151
153
  })
@@ -161,7 +163,7 @@ const spawnFfmpeg = async (options) => {
161
163
  audioCodecName,
162
164
  // Set bitrate up to 320k, for aac it might effectively be lower
163
165
  '-b:a',
164
- '320k',
166
+ (_h = options.audioBitrate) !== null && _h !== void 0 ? _h : '320k',
165
167
  options.force ? '-y' : null,
166
168
  (_j = options.outputLocation) !== null && _j !== void 0 ? _j : tempFile,
167
169
  ].filter(remotion_1.Internals.truthy));
@@ -215,17 +217,20 @@ const spawnFfmpeg = async (options) => {
215
217
  ? []
216
218
  : [
217
219
  proResProfileName ? ['-profile:v', proResProfileName] : null,
218
- supportsCrf ? ['-crf', String(crf)] : null,
219
220
  ['-pix_fmt', pixelFormat],
220
221
  // Without explicitly disabling auto-alt-ref,
221
222
  // transparent WebM generation doesn't work
222
223
  pixelFormat === 'yuva420p' ? ['-auto-alt-ref', '0'] : null,
223
- ['-b:v', '1M'],
224
+ ...(0, crf_1.validateQualitySettings)({
225
+ crf: options.crf,
226
+ videoBitrate: options.videoBitrate,
227
+ codec,
228
+ }),
224
229
  ]),
225
230
  codec === 'h264' ? ['-movflags', 'faststart'] : null,
226
231
  audioCodecName ? ['-c:a', audioCodecName] : null,
227
232
  // Set max bitrate up to 1024kbps, will choose lower if that's too much
228
- audioCodecName ? ['-b:a', '512K'] : null,
233
+ audioCodecName ? ['-b:a', options.audioBitrate || '512K'] : null,
229
234
  // Ignore metadata that may come from remote media
230
235
  ['-map_metadata', '-1'],
231
236
  [
@@ -0,0 +1,2 @@
1
+ import type { FfmpegOverrideFn } from './ffmpeg-override';
2
+ export declare const validateFfmpegArgsHook: (ffmpegArgsHook?: FfmpegOverrideFn) => void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateFfmpegArgsHook = void 0;
4
+ const validateFfmpegArgsHook = (ffmpegArgsHook) => {
5
+ if (typeof ffmpegArgsHook === 'undefined') {
6
+ return;
7
+ }
8
+ if (ffmpegArgsHook && typeof ffmpegArgsHook !== 'function') {
9
+ throw new TypeError(`Argument passed for "ffmpegArgsHook" is not a function: ${ffmpegArgsHook}`);
10
+ }
11
+ };
12
+ exports.validateFfmpegArgsHook = validateFfmpegArgsHook;
@@ -0,0 +1,2 @@
1
+ import type { FfmpegOverrideFn } from './ffmpeg-override';
2
+ export declare const validateFfmpegOverride: (ffmpegArgsHook?: FfmpegOverrideFn) => void;
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateFfmpegOverride = void 0;
4
+ const validateFfmpegOverride = (ffmpegArgsHook) => {
5
+ if (typeof ffmpegArgsHook === 'undefined') {
6
+ return;
7
+ }
8
+ if (ffmpegArgsHook && typeof ffmpegArgsHook !== 'function') {
9
+ throw new TypeError(`Argument passed for "ffmpegArgsHook" is not a function: ${ffmpegArgsHook}`);
10
+ }
11
+ };
12
+ exports.validateFfmpegOverride = validateFfmpegOverride;
@@ -0,0 +1 @@
1
+ export declare const validateBitrate: (bitrate: unknown, name: string) => void;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateBitrate = void 0;
4
+ const validateBitrate = (bitrate, name) => {
5
+ if (bitrate === null || typeof bitrate === 'undefined') {
6
+ return;
7
+ }
8
+ if (typeof bitrate === 'number') {
9
+ throw new TypeError(`"${name}" must be a string ending in "K" or "M". Got a number: ${bitrate}`);
10
+ }
11
+ if (typeof bitrate !== 'string') {
12
+ throw new TypeError(`"${name}" must be a string or null, but got ${JSON.stringify(bitrate)}`);
13
+ }
14
+ if (!bitrate.endsWith('K') &&
15
+ !bitrate.endsWith('k') &&
16
+ !bitrate.endsWith('M')) {
17
+ throw new TypeError(`"${name}" must end in "K", "k" or "M", but got ${JSON.stringify(bitrate)}`);
18
+ }
19
+ };
20
+ exports.validateBitrate = validateBitrate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.2.31",
3
+ "version": "3.2.32",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -22,7 +22,7 @@
22
22
  "dependencies": {
23
23
  "execa": "5.1.1",
24
24
  "extract-zip": "2.0.1",
25
- "remotion": "3.2.31",
25
+ "remotion": "3.2.32",
26
26
  "source-map": "^0.8.0-beta.0",
27
27
  "ws": "8.7.0"
28
28
  },
@@ -44,7 +44,7 @@
44
44
  "react": "18.0.0",
45
45
  "react-dom": "18.0.0",
46
46
  "typescript": "^4.7.0",
47
- "vitest": "^0.18.0"
47
+ "vitest": "0.18.0"
48
48
  },
49
49
  "keywords": [
50
50
  "remotion",
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "f011b454d78903e548c32f548d8fef642c5ff7a6"
60
+ "gitHead": "4f7ab3637405d140041f898f95f78c99943d1b40"
61
61
  }
@@ -1,2 +0,0 @@
1
- export declare const findClosestPackageJson: () => string | null;
2
- export declare const findRemotionRoot: () => string;
@@ -1,34 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.findRemotionRoot = exports.findClosestPackageJson = void 0;
7
- const fs_1 = __importDefault(require("fs"));
8
- const path_1 = __importDefault(require("path"));
9
- const recursionLimit = 5;
10
- const findClosestPackageJson = () => {
11
- let currentDir = process.cwd();
12
- let possiblePackageJson = '';
13
- for (let i = 0; i < recursionLimit; i++) {
14
- possiblePackageJson = path_1.default.join(currentDir, 'package.json');
15
- const exists = fs_1.default.existsSync(possiblePackageJson);
16
- if (exists) {
17
- return possiblePackageJson;
18
- }
19
- currentDir = path_1.default.dirname(currentDir);
20
- }
21
- return null;
22
- };
23
- exports.findClosestPackageJson = findClosestPackageJson;
24
- const findRemotionRoot = () => {
25
- const closestPackageJson = (0, exports.findClosestPackageJson)();
26
- if (closestPackageJson === null) {
27
- console.error('Could not find a package.json in the current directory or any of the ' +
28
- recursionLimit +
29
- ' parent directories. Is this a Remotion project?');
30
- process.exit(1);
31
- }
32
- return path_1.default.dirname(closestPackageJson);
33
- };
34
- exports.findRemotionRoot = findRemotionRoot;