@remotion/renderer 3.1.8 → 3.2.0

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,8 +1,5 @@
1
1
  import type { DownloadMap } from './assets/download-map';
2
2
  export declare const createFfmpegComplexFilter: (filters: number, downloadMap: DownloadMap) => Promise<{
3
- complexFilterFlag: [
4
- string,
5
- string
6
- ] | null;
3
+ complexFilterFlag: [string, string] | null;
7
4
  cleanup: () => void;
8
5
  }>;
@@ -1,2 +1,2 @@
1
1
  import type { Codec } from './codec';
2
- export declare const getFileExtensionFromCodec: (codec: Codec, type: 'chunk' | 'final') => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
2
+ export declare const getFileExtensionFromCodec: (codec: Codec, type: 'chunk' | 'final') => "mp3" | "aac" | "wav" | "gif" | "webm" | "mp4" | "mov" | "mkv";
@@ -0,0 +1 @@
1
+ export declare const getIdealVideoThreadsFlag: () => number;
@@ -0,0 +1,18 @@
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.getIdealVideoThreadsFlag = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const MEMORY_USAGE_PER_THREAD = 400000000; // 400MB
9
+ const RESERVED_MEMORY = 2000000000;
10
+ const getIdealVideoThreadsFlag = () => {
11
+ const freeMemory = os_1.default.freemem();
12
+ const cpus = os_1.default.cpus().length;
13
+ const maxRecommendedBasedOnCpus = (cpus * 2) / 3;
14
+ const maxRecommendedBasedOnMemory = (freeMemory - RESERVED_MEMORY) / MEMORY_USAGE_PER_THREAD;
15
+ const maxRecommended = Math.min(maxRecommendedBasedOnCpus, maxRecommendedBasedOnMemory);
16
+ return Math.max(1, Math.round(maxRecommended));
17
+ };
18
+ exports.getIdealVideoThreadsFlag = getIdealVideoThreadsFlag;
@@ -1 +1 @@
1
- export declare const guessExtensionForVideo: (src: string) => Promise<"mp3" | "wav" | "mp4" | "webm">;
1
+ export declare const guessExtensionForVideo: (src: string) => Promise<"mp3" | "wav" | "webm" | "mp4">;
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';
@@ -60,14 +61,14 @@ export declare const RenderInternals: {
60
61
  width: number;
61
62
  height: number;
62
63
  scale: number;
63
- codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
64
+ codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
64
65
  }) => void;
65
66
  normalizeServeUrl: (unnormalized: string) => string;
66
67
  spawnFfmpeg: (options: import("./stitch-frames-to-video").StitcherOptions) => Promise<{
67
68
  task: Promise<Buffer | null>;
68
69
  getLogs: () => string;
69
70
  }>;
70
- getFileExtensionFromCodec: (codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
71
+ getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "webm" | "mp4" | "mov" | "mkv";
71
72
  tmpDir: (str: string) => string;
72
73
  deleteDirectory: (directory: string) => Promise<void>;
73
74
  isServeUrl: (potentialUrl: string) => boolean;
@@ -115,17 +116,17 @@ export declare const RenderInternals: {
115
116
  };
116
117
  registerErrorSymbolicationLock: () => number;
117
118
  unlockErrorSymbolicationLock: (id: number) => void;
118
- canUseParallelEncoding: (codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => boolean;
119
+ canUseParallelEncoding: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => boolean;
119
120
  mimeContentType: typeof mimeContentType;
120
121
  mimeLookup: typeof mimeLookup;
121
122
  validateConcurrency: (value: unknown, setting: string) => void;
122
123
  validPixelFormats: readonly ["yuv420p", "yuva420p", "yuv422p", "yuv444p", "yuv420p10le", "yuv422p10le", "yuv444p10le", "yuva444p10le"];
123
124
  DEFAULT_BROWSER: import("./browser").Browser;
124
125
  validateFrameRange: (frameRange: import("./frame-range").FrameRange | null) => void;
125
- DEFAULT_OPENGL_RENDERER: "swangle" | "angle" | "egl" | "swiftshader" | null;
126
- validateOpenGlRenderer: (option: "swangle" | "angle" | "egl" | "swiftshader" | null) => "swangle" | "angle" | "egl" | "swiftshader" | null;
127
- getDefaultCrfForCodec: (codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => number;
128
- validateSelectedCrfAndCodecCombination: (crf: unknown, codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
126
+ DEFAULT_OPENGL_RENDERER: "angle" | "swangle" | "egl" | "swiftshader" | null;
127
+ validateOpenGlRenderer: (option: "angle" | "swangle" | "egl" | "swiftshader" | null) => "angle" | "swangle" | "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;
129
130
  validImageFormats: readonly ["png", "jpeg", "none"];
130
131
  validCodecs: readonly ["h264", "h265", "vp8", "vp9", "mp3", "aac", "wav", "prores", "h264-mkv", "gif"];
131
132
  DEFAULT_OVERWRITE: boolean;
@@ -133,14 +134,14 @@ export declare const RenderInternals: {
133
134
  validateQuality: (q: number | undefined) => void;
134
135
  validateFrame: (frame: number, durationInFrames: number) => void;
135
136
  DEFAULT_TIMEOUT: number;
136
- getValidCrfRanges: (codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => [number, number];
137
- validateSelectedPixelFormatAndCodecCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
138
- validateSelectedCodecAndProResCombination: (actualCodec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", actualProResProfile: "4444-xq" | "4444" | "hq" | "standard" | "light" | "proxy" | undefined) => void;
139
- validateSelectedPixelFormatAndImageFormatCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", imageFormat: "none" | "png" | "jpeg") => "none" | "valid";
140
- DEFAULT_CODEC: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
141
- isAudioCodec: (codec: "vp9" | "vp8" | "h264" | "h265" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
137
+ getValidCrfRanges: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => [number, number];
138
+ validateSelectedPixelFormatAndCodecCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif") => void;
139
+ validateSelectedCodecAndProResCombination: (actualCodec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", actualProResProfile: "proxy" | "4444-xq" | "4444" | "hq" | "standard" | "light" | undefined) => void;
140
+ validateSelectedPixelFormatAndImageFormatCombination: (pixelFormat: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le", imageFormat: "jpeg" | "png" | "none") => "none" | "valid";
141
+ DEFAULT_CODEC: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
142
+ isAudioCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
142
143
  logLevels: readonly ["verbose", "info", "warn", "error"];
143
- isEqualOrBelowLogLevel: (currentLevel: "verbose" | "info" | "warn" | "error", level: "verbose" | "info" | "warn" | "error") => boolean;
144
+ isEqualOrBelowLogLevel: (currentLevel: "error" | "verbose" | "info" | "warn", level: "error" | "verbose" | "info" | "warn") => boolean;
144
145
  isValidLogLevel: (level: string) => boolean;
145
146
  validateEveryNthFrame: (everyNthFrame: unknown) => void;
146
147
  perf: typeof perf;
@@ -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';
@@ -45,6 +45,17 @@ const startOffthreadVideoServer = ({ ffmpegExecutable, ffprobeExecutable, onDown
45
45
  const { src, time, imageFormat } = (0, exports.extractUrlAndSourceFromUrl)(req.url);
46
46
  res.setHeader('access-control-allow-origin', '*');
47
47
  res.setHeader('content-type', `image/${imageFormat === 'jpeg' ? 'jpg' : 'png'}`);
48
+ // Handling this case on Lambda:
49
+ // https://support.google.com/chrome/a/answer/7679408?hl=en
50
+ // Chrome sends Private Network Access preflights for subresources
51
+ if (req.method === 'OPTIONS') {
52
+ res.statusCode = 200;
53
+ if (req.headers['access-control-request-private-network']) {
54
+ res.setHeader('Access-Control-Allow-Private-Network', 'true');
55
+ }
56
+ res.end();
57
+ return;
58
+ }
48
59
  (0, download_and_map_assets_to_file_1.downloadAsset)({ src, onDownload, downloadMap })
49
60
  .then((to) => {
50
61
  return (0, extract_frame_from_video_1.extractFrameFromVideo)({
@@ -9,6 +9,7 @@ const os_1 = __importDefault(require("os"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const node_1 = require("./browser/node");
11
11
  const get_local_browser_executable_1 = require("./get-local-browser-executable");
12
+ const get_video_threads_flag_1 = require("./get-video-threads-flag");
12
13
  const validate_opengl_renderer_1 = require("./validate-opengl-renderer");
13
14
  const validRenderers = ['swangle', 'angle', 'egl', 'swiftshader'];
14
15
  const getOpenGlRenderer = (option) => {
@@ -69,7 +70,7 @@ const openBrowser = async (browser, options) => {
69
70
  '--force-color-profile=srgb',
70
71
  '--metrics-recording-only',
71
72
  '--no-first-run',
72
- '--video-threads=16',
73
+ '--video-threads=' + (0, get_video_threads_flag_1.getIdealVideoThreadsFlag)(),
73
74
  '--enable-automation',
74
75
  '--password-store=basic',
75
76
  '--use-mock-keychain',
@@ -0,0 +1,12 @@
1
+ export declare const estimateMemoryUsageForPrestitcher: ({ width, height, }: {
2
+ width: number;
3
+ height: number;
4
+ }) => number;
5
+ export declare const shouldUseParallelEncoding: ({ width, height, }: {
6
+ width: number;
7
+ height: number;
8
+ }) => {
9
+ hasEnoughMemory: boolean;
10
+ freeMemory: number;
11
+ estimatedUsage: number;
12
+ };
@@ -0,0 +1,30 @@
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.shouldUseParallelEncoding = exports.estimateMemoryUsageForPrestitcher = void 0;
7
+ const os_1 = __importDefault(require("os"));
8
+ const estimateMemoryUsageForPrestitcher = ({ width, height, }) => {
9
+ // Empirically we detected that per 1 million pixels, FFMPEG uses around 1GB of memory, relatively independent of
10
+ // the duration of the video.
11
+ const memoryUsageFor4K = 1000000000;
12
+ const memoryUsageOfPixel = memoryUsageFor4K / 1000000;
13
+ return memoryUsageOfPixel * width * height;
14
+ };
15
+ exports.estimateMemoryUsageForPrestitcher = estimateMemoryUsageForPrestitcher;
16
+ const shouldUseParallelEncoding = ({ width, height, }) => {
17
+ const freeMemory = os_1.default.freemem();
18
+ const estimatedUsage = (0, exports.estimateMemoryUsageForPrestitcher)({
19
+ height,
20
+ width,
21
+ });
22
+ const hasEnoughMemory = freeMemory - estimatedUsage > 2000000000 &&
23
+ estimatedUsage / freeMemory < 0.5;
24
+ return {
25
+ hasEnoughMemory,
26
+ freeMemory,
27
+ estimatedUsage,
28
+ };
29
+ };
30
+ exports.shouldUseParallelEncoding = shouldUseParallelEncoding;
@@ -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';
@@ -23,6 +23,7 @@ const make_cancel_signal_1 = require("./make-cancel-signal");
23
23
  const overwrite_1 = require("./overwrite");
24
24
  const perf_1 = require("./perf");
25
25
  const prespawn_ffmpeg_1 = require("./prespawn-ffmpeg");
26
+ const prestitcher_memory_usage_1 = require("./prestitcher-memory-usage");
26
27
  const quality_1 = require("./quality");
27
28
  const render_frames_1 = require("./render-frames");
28
29
  const stitch_frames_to_video_1 = require("./stitch-frames-to-video");
@@ -57,7 +58,21 @@ const renderMedia = ({ parallelism, proResProfile, crf, composition, imageFormat
57
58
  let cancelled = false;
58
59
  const renderStart = Date.now();
59
60
  const downloadMap = (_c = options.downloadMap) !== null && _c !== void 0 ? _c : (0, download_map_1.makeDownloadMap)();
60
- const parallelEncoding = (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec);
61
+ const { estimatedUsage, freeMemory, hasEnoughMemory } = (0, prestitcher_memory_usage_1.shouldUseParallelEncoding)({
62
+ height: composition.height,
63
+ width: composition.width,
64
+ });
65
+ const parallelEncoding = hasEnoughMemory && (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec);
66
+ if (options.verbose) {
67
+ console.log('[PRESTITCHER] Free memory:', freeMemory, 'Estimated usage parallel encoding', estimatedUsage);
68
+ console.log('[PRESTICHER]: Codec supports parallel rendering:', (0, can_use_parallel_encoding_1.canUseParallelEncoding)(codec));
69
+ if (parallelEncoding) {
70
+ console.log('[PRESTICHER] Parallel encoding is enabled.');
71
+ }
72
+ else {
73
+ console.log('[PRESTITCHER] Parallel encoding is disabled.');
74
+ }
75
+ }
61
76
  const actualImageFormat = imageFormat !== null && imageFormat !== void 0 ? imageFormat : 'jpeg';
62
77
  const preEncodedFileLocation = parallelEncoding
63
78
  ? path_1.default.join(downloadMap.preEncode, 'pre-encode.' + (0, get_extension_from_codec_1.getFileExtensionFromCodec)(codec, 'chunk'))
@@ -7,6 +7,7 @@ exports._screenshotTask = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const perf_1 = require("./perf");
9
9
  const _screenshotTask = async (page, format, options) => {
10
+ var _a;
10
11
  const client = page._client();
11
12
  const target = page.target();
12
13
  const perfTarget = (0, perf_1.startPerfMeasure)('activate-target');
@@ -20,20 +21,34 @@ const _screenshotTask = async (page, format, options) => {
20
21
  color: { r: 0, g: 0, b: 0, a: 0 },
21
22
  });
22
23
  const cap = (0, perf_1.startPerfMeasure)('capture');
23
- const result = await client.send('Page.captureScreenshot', {
24
- format,
25
- quality: options.quality,
26
- clip: undefined,
27
- captureBeyondViewport: true,
28
- });
29
- (0, perf_1.stopPerfMeasure)(cap);
30
- if (shouldSetDefaultBackground)
31
- await client.send('Emulation.setDefaultBackgroundColorOverride');
32
- const saveMarker = (0, perf_1.startPerfMeasure)('save');
33
- const buffer = Buffer.from(result.data, 'base64');
34
- if (options.path)
35
- await fs_1.default.promises.writeFile(options.path, buffer);
36
- (0, perf_1.stopPerfMeasure)(saveMarker);
37
- return buffer;
24
+ try {
25
+ const result = await client.send('Page.captureScreenshot', {
26
+ format,
27
+ quality: options.quality,
28
+ clip: undefined,
29
+ captureBeyondViewport: true,
30
+ });
31
+ (0, perf_1.stopPerfMeasure)(cap);
32
+ if (shouldSetDefaultBackground)
33
+ await client.send('Emulation.setDefaultBackgroundColorOverride');
34
+ const saveMarker = (0, perf_1.startPerfMeasure)('save');
35
+ const buffer = Buffer.from(result.data, 'base64');
36
+ if (options.path)
37
+ await fs_1.default.promises.writeFile(options.path, buffer);
38
+ (0, perf_1.stopPerfMeasure)(saveMarker);
39
+ return buffer;
40
+ }
41
+ catch (err) {
42
+ if (err.message.includes('Unable to capture screenshot')) {
43
+ const errMessage = [
44
+ 'Could not take a screenshot because Google Chrome ran out of memory or disk space.',
45
+ ((_a = process === null || process === void 0 ? void 0 : process.env) === null || _a === void 0 ? void 0 : _a.REMOTION_LAMBDA)
46
+ ? 'Deploy a new Lambda function with more memory or disk space.'
47
+ : 'Decrease the concurrency to use less RAM.',
48
+ ].join(' ');
49
+ throw new Error(errMessage);
50
+ }
51
+ throw err;
52
+ }
38
53
  };
39
54
  exports._screenshotTask = _screenshotTask;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.1.8",
3
+ "version": "3.2.0",
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.1.8",
25
+ "remotion": "3.2.0",
26
26
  "source-map": "^0.8.0-beta.0",
27
27
  "ws": "8.7.0"
28
28
  },
@@ -57,5 +57,5 @@
57
57
  "publishConfig": {
58
58
  "access": "public"
59
59
  },
60
- "gitHead": "57f34a02b0f830053f92af426a6b70b6824f887e"
60
+ "gitHead": "47b188161a9e922a247eaa998e72d37af9137707"
61
61
  }
@@ -1 +0,0 @@
1
- export declare const makeAssetsDownloadTmpDir: () => string;
@@ -1,13 +0,0 @@
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;