@remotion/renderer 3.3.19 → 3.3.25

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 (35) hide show
  1. package/README.md +47 -0
  2. package/dist/assets/download-map.d.ts +4 -0
  3. package/dist/assets/download-map.js +4 -0
  4. package/dist/compositor/compose.d.ts +13 -0
  5. package/dist/compositor/compose.js +47 -0
  6. package/dist/compositor/get-executable-path.d.ts +1 -0
  7. package/dist/compositor/get-executable-path.js +47 -0
  8. package/dist/compositor/payloads.d.ts +41 -0
  9. package/dist/compositor/payloads.js +2 -0
  10. package/dist/create-ffmpeg-complex-filter.d.ts +4 -1
  11. package/dist/extract-frame-from-video.d.ts +0 -1
  12. package/dist/get-extension-from-codec.d.ts +1 -1
  13. package/dist/get-frame-of-video-slow.d.ts +4 -2
  14. package/dist/get-frame-padded-index.d.ts +1 -1
  15. package/dist/get-frame-padded-index.js +3 -2
  16. package/dist/guess-extension-for-media.d.ts +1 -1
  17. package/dist/index.d.ts +4 -5
  18. package/dist/last-frame-from-video-cache.d.ts +0 -1
  19. package/dist/provide-screenshot.d.ts +3 -2
  20. package/dist/provide-screenshot.js +2 -1
  21. package/dist/puppeteer-screenshot.d.ts +2 -1
  22. package/dist/puppeteer-screenshot.js +1 -0
  23. package/dist/render-frames.js +32 -48
  24. package/dist/render-media.d.ts +0 -1
  25. package/dist/render-still.js +12 -10
  26. package/dist/screenshot-dom-element.d.ts +3 -2
  27. package/dist/screenshot-dom-element.js +2 -1
  28. package/dist/screenshot-task.d.ts +3 -2
  29. package/dist/screenshot-task.js +16 -8
  30. package/dist/take-frame-and-compose.d.ts +19 -0
  31. package/dist/take-frame-and-compose.js +96 -0
  32. package/dist/try-to-extract-frame-of-video-fast.d.ts +0 -1
  33. package/package.json +16 -6
  34. package/.prettierrc.js +0 -14
  35. package/tsconfig.json +0 -10
package/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # Rust development
2
+
3
+ To participate in the development of the Rust parts of Remotion, you need to do additional steps. If you don't want to change the Rust code, you don't have to set anything up.
4
+
5
+ ## Setup
6
+
7
+ These are instructions for macOS. Contributions for other platforms are appreciated.
8
+
9
+ First, install Cargo, if you don't have it, or upgrade to a version that supports `edition-2021`:
10
+
11
+ ```
12
+ curl https://sh.rustup.rs -sSf | sh
13
+ ```
14
+
15
+ Second, install components that allow for cross-compilation:
16
+
17
+ ```sh
18
+ sh install_platforms.sh
19
+ ```
20
+
21
+ Third, install linkers for cross compilation:
22
+
23
+ ```sh
24
+ brew install MaterializeInc/crosstools/x86_64-unknown-linux-gnu
25
+ brew install MaterializeInc/crosstools/aarch64-unknown-linux-gnu
26
+ brew install messense/macos-cross-toolchains/x86_64-unknown-linux-musl
27
+ brew install messense/macos-cross-toolchains/aarch64-unknown-linux-musl
28
+ brew install mingw-w64
29
+ ```
30
+
31
+ > This will take a few minutes.
32
+
33
+ ## Building
34
+
35
+ To build the Rust parts for your operating system, run:
36
+
37
+ ```
38
+ node build.mjs
39
+ ```
40
+
41
+ To build the Rust binaries for all supported platforms, run:
42
+
43
+ ```
44
+ node build.mjs --all
45
+ ```
46
+
47
+ The resulting artifacts should be checked into Git.
@@ -53,6 +53,10 @@ export declare type DownloadMap = {
53
53
  audioPreprocessing: string;
54
54
  stitchFrames: string;
55
55
  assetDir: string;
56
+ compositingDir: string;
57
+ compositorCache: {
58
+ [key: string]: string;
59
+ };
56
60
  };
57
61
  export declare type RenderAssetInfo = {
58
62
  assets: TAsset[][];
@@ -62,12 +62,16 @@ const makeDownloadMap = () => {
62
62
  audioMixing: makeAndReturn(dir, 'remotion-audio-mixing'),
63
63
  audioPreprocessing: makeAndReturn(dir, 'remotion-audio-preprocessing'),
64
64
  stitchFrames: makeAndReturn(dir, 'remotion-stitch-temp-dir'),
65
+ compositingDir: makeAndReturn(dir, 'remotion-compositing-temp-dir'),
66
+ compositorCache: {},
65
67
  };
66
68
  };
67
69
  exports.makeDownloadMap = makeDownloadMap;
68
70
  const cleanDownloadMap = async (downloadMap) => {
69
71
  await (0, delete_directory_1.deleteDirectory)(downloadMap.downloadDir);
70
72
  await (0, delete_directory_1.deleteDirectory)(downloadMap.complexFilter);
73
+ await (0, delete_directory_1.deleteDirectory)(downloadMap.compositingDir);
74
+ // Assets dir must be last since the others are contained
71
75
  await (0, delete_directory_1.deleteDirectory)(downloadMap.assetDir);
72
76
  };
73
77
  exports.cleanDownloadMap = cleanDownloadMap;
@@ -0,0 +1,13 @@
1
+ import type { DownloadMap } from '../assets/download-map';
2
+ import type { CompositorImageFormat, Layer } from './payloads';
3
+ declare type CompositorInput = {
4
+ height: number;
5
+ width: number;
6
+ layers: Layer[];
7
+ imageFormat: CompositorImageFormat;
8
+ };
9
+ export declare const compose: ({ height, width, layers, output, downloadMap, imageFormat, }: CompositorInput & {
10
+ downloadMap: DownloadMap;
11
+ output: string;
12
+ }) => Promise<void>;
13
+ export {};
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.compose = void 0;
4
+ const child_process_1 = require("child_process");
5
+ const crypto_1 = require("crypto");
6
+ const promises_1 = require("fs/promises");
7
+ const get_executable_path_1 = require("./get-executable-path");
8
+ const getCompositorHash = ({ ...input }) => {
9
+ return (0, crypto_1.createHash)('sha256').update(JSON.stringify(input)).digest('base64');
10
+ };
11
+ const compose = async ({ height, width, layers, output, downloadMap, imageFormat, }) => {
12
+ const bin = (0, get_executable_path_1.getExecutablePath)();
13
+ const hash = getCompositorHash({ height, width, layers, imageFormat });
14
+ if (downloadMap.compositorCache[hash]) {
15
+ await (0, promises_1.copyFile)(downloadMap.compositorCache[hash], output);
16
+ return;
17
+ }
18
+ const payload = {
19
+ v: 1,
20
+ height,
21
+ width,
22
+ layers,
23
+ output,
24
+ output_format: imageFormat,
25
+ };
26
+ await new Promise((resolve, reject) => {
27
+ const child = (0, child_process_1.spawn)(bin);
28
+ child.stdin.write(JSON.stringify(payload));
29
+ child.stdin.end();
30
+ const stderrChunks = [];
31
+ child.stderr.on('data', (d) => stderrChunks.push(d));
32
+ child.on('close', (code) => {
33
+ if (code === 0) {
34
+ resolve();
35
+ }
36
+ else {
37
+ const message = Buffer.concat(stderrChunks).toString('utf-8');
38
+ const parsed = JSON.parse(message);
39
+ const err = new Error(parsed.error);
40
+ err.stack = parsed.error + '\n' + parsed.backtrace;
41
+ reject(err);
42
+ }
43
+ });
44
+ });
45
+ downloadMap.compositorCache[hash] = output;
46
+ };
47
+ exports.compose = compose;
@@ -0,0 +1 @@
1
+ export declare const getExecutablePath: () => any;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ // Adapted from @swc/core package
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.getExecutablePath = void 0;
5
+ function isMusl() {
6
+ // @ts-expect-error no types
7
+ const { glibcVersionRuntime } = process.report.getReport().header;
8
+ return !glibcVersionRuntime;
9
+ }
10
+ const getExecutablePath = () => {
11
+ switch (process.platform) {
12
+ case 'win32':
13
+ switch (process.arch) {
14
+ case 'x64':
15
+ return require('@remotion/compositor-win32-x64-msvc').binaryPath;
16
+ default:
17
+ throw new Error(`Unsupported architecture on Windows: ${process.arch}`);
18
+ }
19
+ case 'darwin':
20
+ switch (process.arch) {
21
+ case 'x64':
22
+ return require('@remotion/compositor-darwin-x64').binaryPath;
23
+ case 'arm64':
24
+ return require('@remotion/compositor-darwin-arm64').binaryPath;
25
+ default:
26
+ throw new Error(`Unsupported architecture on macOS: ${process.arch}`);
27
+ }
28
+ case 'linux':
29
+ switch (process.arch) {
30
+ case 'x64':
31
+ if (isMusl()) {
32
+ return require('@remotion/compositor-linux-x64-musl').binaryPath;
33
+ }
34
+ return require('@remotion/compositor-linux-x64-gnu').binaryPath;
35
+ case 'arm64':
36
+ if (isMusl()) {
37
+ return require('@remotion/compositor-linux-arm64-musl').binaryPath;
38
+ }
39
+ return require('@remotion/compositor-linux-arm64-gnu').binaryPath;
40
+ default:
41
+ throw new Error(`Unsupported architecture on Linux: ${process.arch}`);
42
+ }
43
+ default:
44
+ throw new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`);
45
+ }
46
+ };
47
+ exports.getExecutablePath = getExecutablePath;
@@ -0,0 +1,41 @@
1
+ export declare type Layer = {
2
+ type: 'PngImage';
3
+ params: {
4
+ src: string;
5
+ x: number;
6
+ y: number;
7
+ width: number;
8
+ height: number;
9
+ };
10
+ } | {
11
+ type: 'JpgImage';
12
+ params: {
13
+ src: string;
14
+ x: number;
15
+ y: number;
16
+ width: number;
17
+ height: number;
18
+ };
19
+ } | {
20
+ type: 'Solid';
21
+ params: {
22
+ fill: [number, number, number, number];
23
+ x: number;
24
+ y: number;
25
+ width: number;
26
+ height: number;
27
+ };
28
+ };
29
+ export declare type CompositorImageFormat = 'Png' | 'Jpeg';
30
+ export declare type CliInput = {
31
+ v: number;
32
+ output: string;
33
+ width: number;
34
+ height: number;
35
+ layers: Layer[];
36
+ output_format: CompositorImageFormat;
37
+ };
38
+ export declare type ErrorPayload = {
39
+ error: string;
40
+ backtrace: string;
41
+ };
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,5 +1,8 @@
1
1
  import type { DownloadMap } from './assets/download-map';
2
2
  export declare const createFfmpegComplexFilter: (filters: number, downloadMap: DownloadMap) => Promise<{
3
- complexFilterFlag: [string, string] | null;
3
+ complexFilterFlag: [
4
+ string,
5
+ string
6
+ ] | null;
4
7
  cleanup: () => void;
5
8
  }>;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { OffthreadVideoImageFormat } from 'remotion';
3
2
  import type { DownloadMap } from './assets/download-map';
4
3
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -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" | "webm" | "mp4" | "mov" | "mkv";
2
+ export declare const getFileExtensionFromCodec: (codec: Codec, type: 'chunk' | 'final') => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { OffthreadVideoImageFormat } from 'remotion';
3
2
  import type { SpecialVCodecForTransparency } from './assets/download-map';
4
3
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -8,7 +7,10 @@ export declare const getFrameOfVideoSlow: ({ src, duration, ffmpegExecutable, im
8
7
  duration: number;
9
8
  imageFormat: OffthreadVideoImageFormat;
10
9
  specialVCodecForTransparency: SpecialVCodecForTransparency;
11
- needsResize: [number, number] | null;
10
+ needsResize: [
11
+ number,
12
+ number
13
+ ] | null;
12
14
  offset: number;
13
15
  fps: number | null;
14
16
  remotionRoot: string;
@@ -2,7 +2,7 @@ export declare type CountType = 'from-zero' | 'actual-frames';
2
2
  export declare const getFrameOutputFileName: ({ index, frame, imageFormat, countType, lastFrame, totalFrames, }: {
3
3
  index: number;
4
4
  frame: number;
5
- imageFormat: 'png' | 'jpeg';
5
+ imageFormat: 'png' | 'jpeg' | 'none';
6
6
  countType: CountType;
7
7
  lastFrame: number;
8
8
  totalFrames: number;
@@ -10,13 +10,14 @@ const padIndex = ({ num, filePadLength, }) => {
10
10
  };
11
11
  const getFrameOutputFileName = ({ index, frame, imageFormat, countType, lastFrame, totalFrames, }) => {
12
12
  const filePadLength = (0, exports.getFilePadLength)({ lastFrame, countType, totalFrames });
13
+ const prefix = 'element';
13
14
  if (countType === 'actual-frames') {
14
15
  const paddedIndex = padIndex({ filePadLength, num: frame });
15
- return `element-${paddedIndex}.${imageFormat}`;
16
+ return `${prefix}-${paddedIndex}.${imageFormat}`;
16
17
  }
17
18
  if (countType === 'from-zero') {
18
19
  const paddedIndex = padIndex({ filePadLength, num: index });
19
- return `element-${paddedIndex}.${imageFormat}`;
20
+ return `${prefix}-${paddedIndex}.${imageFormat}`;
20
21
  }
21
22
  throw new TypeError('Unknown count type');
22
23
  };
@@ -2,4 +2,4 @@ export declare const guessExtensionForVideo: ({ src, remotionRoot, ffprobeBinary
2
2
  src: string;
3
3
  remotionRoot: string;
4
4
  ffprobeBinary: string | null;
5
- }) => Promise<"mp3" | "wav" | "webm" | "mp4">;
5
+ }) => Promise<"mp3" | "wav" | "mp4" | "webm">;
package/dist/index.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import execa from 'execa';
3
2
  import { SymbolicateableError } from './error-handling/symbolicateable-error';
4
3
  import { mimeContentType, mimeLookup } from './mime-types';
@@ -61,7 +60,7 @@ export declare const RenderInternals: {
61
60
  scale: number;
62
61
  codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
63
62
  }) => void;
64
- getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "webm" | "mp4" | "mov" | "mkv";
63
+ getFileExtensionFromCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif", type: "chunk" | "final") => "mp3" | "aac" | "wav" | "gif" | "mp4" | "mkv" | "mov" | "webm";
65
64
  tmpDir: (str: string) => string;
66
65
  deleteDirectory: (directory: string) => Promise<void>;
67
66
  isServeUrl: (potentialUrl: string) => boolean;
@@ -115,8 +114,8 @@ export declare const RenderInternals: {
115
114
  validPixelFormats: readonly ["yuv420p", "yuva420p", "yuv422p", "yuv444p", "yuv420p10le", "yuv422p10le", "yuv444p10le", "yuva444p10le"];
116
115
  DEFAULT_BROWSER: import("./browser").Browser;
117
116
  validateFrameRange: (frameRange: import("./frame-range").FrameRange | null) => void;
118
- DEFAULT_OPENGL_RENDERER: "angle" | "swangle" | "egl" | "swiftshader" | null;
119
- validateOpenGlRenderer: (option: "angle" | "swangle" | "egl" | "swiftshader" | null) => "angle" | "swangle" | "egl" | "swiftshader" | null;
117
+ DEFAULT_OPENGL_RENDERER: "swangle" | "angle" | "egl" | "swiftshader" | null;
118
+ validateOpenGlRenderer: (option: "swangle" | "angle" | "egl" | "swiftshader" | null) => "swangle" | "angle" | "egl" | "swiftshader" | null;
120
119
  validImageFormats: readonly ["png", "jpeg", "none"];
121
120
  validCodecs: readonly ["h264", "h265", "vp8", "vp9", "mp3", "aac", "wav", "prores", "h264-mkv", "gif"];
122
121
  DEFAULT_PIXEL_FORMAT: "yuv420p" | "yuva420p" | "yuv422p" | "yuv444p" | "yuv420p10le" | "yuv422p10le" | "yuv444p10le" | "yuva444p10le";
@@ -126,7 +125,7 @@ export declare const RenderInternals: {
126
125
  DEFAULT_CODEC: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
127
126
  isAudioCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
128
127
  logLevels: readonly ["verbose", "info", "warn", "error"];
129
- isEqualOrBelowLogLevel: (currentLevel: "error" | "verbose" | "info" | "warn", level: "error" | "verbose" | "info" | "warn") => boolean;
128
+ isEqualOrBelowLogLevel: (currentLevel: "verbose" | "error" | "info" | "warn", level: "verbose" | "error" | "info" | "warn") => boolean;
130
129
  isValidLogLevel: (level: string) => boolean;
131
130
  perf: typeof perf;
132
131
  makeDownloadMap: () => import("./assets/download-map").DownloadMap;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { OffthreadVideoImageFormat } from 'remotion';
3
2
  import type { DownloadMap, SpecialVCodecForTransparency } from './assets/download-map';
4
3
  import type { FfmpegExecutable } from './ffmpeg-executable';
@@ -1,7 +1,7 @@
1
- /// <reference types="node" />
1
+ import type { ClipRegion } from 'remotion';
2
2
  import type { Page } from './browser/BrowserPage';
3
3
  import type { ImageFormat } from './image-format';
4
- export declare const provideScreenshot: ({ page, imageFormat, options, quality, height, width, }: {
4
+ export declare const provideScreenshot: ({ page, imageFormat, options, quality, height, width, clipRegion, }: {
5
5
  page: Page;
6
6
  imageFormat: ImageFormat;
7
7
  quality: number | undefined;
@@ -11,4 +11,5 @@ export declare const provideScreenshot: ({ page, imageFormat, options, quality,
11
11
  };
12
12
  height: number;
13
13
  width: number;
14
+ clipRegion: ClipRegion | null;
14
15
  }) => Promise<Buffer>;
@@ -2,7 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.provideScreenshot = void 0;
4
4
  const screenshot_dom_element_1 = require("./screenshot-dom-element");
5
- const provideScreenshot = ({ page, imageFormat, options, quality, height, width, }) => {
5
+ const provideScreenshot = ({ page, imageFormat, options, quality, height, width, clipRegion, }) => {
6
6
  return (0, screenshot_dom_element_1.screenshotDOMElement)({
7
7
  page,
8
8
  opts: {
@@ -12,6 +12,7 @@ const provideScreenshot = ({ page, imageFormat, options, quality, height, width,
12
12
  quality,
13
13
  height,
14
14
  width,
15
+ clipRegion,
15
16
  });
16
17
  };
17
18
  exports.provideScreenshot = provideScreenshot;
@@ -1,4 +1,4 @@
1
- /// <reference types="node" />
1
+ import type { ClipRegion } from 'remotion';
2
2
  import type { Page } from './browser/BrowserPage';
3
3
  export declare const screenshot: (options: {
4
4
  page: Page;
@@ -8,4 +8,5 @@ export declare const screenshot: (options: {
8
8
  omitBackground: boolean;
9
9
  width: number;
10
10
  height: number;
11
+ clipRegion: ClipRegion | null;
11
12
  }) => Promise<Buffer | string | void>;
@@ -66,6 +66,7 @@ const screenshot = (options) => {
66
66
  omitBackground: options.omitBackground,
67
67
  path: options.path,
68
68
  quality: options.quality,
69
+ clipRegion: options.clipRegion,
69
70
  }));
70
71
  };
71
72
  exports.screenshot = screenshot;
@@ -25,12 +25,12 @@ const open_browser_1 = require("./open-browser");
25
25
  const perf_1 = require("./perf");
26
26
  const pool_1 = require("./pool");
27
27
  const prepare_server_1 = require("./prepare-server");
28
- const provide_screenshot_1 = require("./provide-screenshot");
29
28
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
30
29
  const quality_1 = require("./quality");
31
30
  const replace_browser_1 = require("./replace-browser");
32
31
  const seek_to_frame_1 = require("./seek-to-frame");
33
32
  const set_props_and_env_1 = require("./set-props-and-env");
33
+ const take_frame_and_compose_1 = require("./take-frame-and-compose");
34
34
  const truthy_1 = require("./truthy");
35
35
  const validate_scale_1 = require("./validate-scale");
36
36
  const MAX_RETRIES_PER_FRAME = 1;
@@ -163,56 +163,40 @@ const innerRenderFrames = ({ onFrameUpdate, outputDir, onStart, inputProps, qual
163
163
  });
164
164
  freePage.on('error', errorCallbackOnFrame);
165
165
  await (0, seek_to_frame_1.seekToFrame)({ frame, page: freePage });
166
- if (imageFormat !== 'none') {
167
- if (onFrameBuffer) {
168
- const id = (0, perf_1.startPerfMeasure)('save');
169
- const buffer = await (0, provide_screenshot_1.provideScreenshot)({
170
- page: freePage,
171
- imageFormat,
172
- quality,
173
- options: {
174
- frame,
175
- output: null,
176
- },
177
- height: composition.height,
178
- width: composition.width,
179
- });
180
- (0, perf_1.stopPerfMeasure)(id);
181
- onFrameBuffer(buffer, frame);
182
- }
183
- else {
184
- if (!outputDir) {
185
- throw new Error('Called renderFrames() without specifying either `outputDir` or `onFrameBuffer`');
186
- }
187
- const output = path_1.default.join(outputDir, (0, get_frame_padded_index_1.getFrameOutputFileName)({
188
- frame,
189
- imageFormat,
190
- index,
191
- countType,
192
- lastFrame,
193
- totalFrames: framesToRender.length,
194
- }));
195
- await (0, provide_screenshot_1.provideScreenshot)({
196
- page: freePage,
197
- imageFormat,
198
- quality,
199
- options: {
200
- frame,
201
- output,
202
- },
203
- height,
204
- width,
205
- });
206
- }
166
+ if (!outputDir && !onFrameBuffer && imageFormat !== 'none') {
167
+ throw new Error('Called renderFrames() without specifying either `outputDir` or `onFrameBuffer`');
207
168
  }
208
- const collectedAssets = await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
209
- pageFunction: () => {
210
- return window.remotion_collectAssets();
211
- },
212
- args: [],
169
+ if (outputDir && onFrameBuffer && imageFormat !== 'none') {
170
+ throw new Error('Pass either `outputDir` or `onFrameBuffer` to renderFrames(), not both.');
171
+ }
172
+ const id = (0, perf_1.startPerfMeasure)('save');
173
+ const frameDir = outputDir !== null && outputDir !== void 0 ? outputDir : downloadMap.compositingDir;
174
+ const { buffer, collectedAssets } = await (0, take_frame_and_compose_1.takeFrameAndCompose)({
213
175
  frame,
214
- page: freePage,
176
+ freePage,
177
+ height,
178
+ imageFormat,
179
+ output: path_1.default.join(frameDir, (0, get_frame_padded_index_1.getFrameOutputFileName)({
180
+ frame,
181
+ imageFormat,
182
+ index,
183
+ countType,
184
+ lastFrame,
185
+ totalFrames: framesToRender.length,
186
+ })),
187
+ quality,
188
+ width,
189
+ scale,
190
+ downloadMap,
191
+ wantsBuffer: Boolean(onFrameBuffer),
215
192
  });
193
+ if (onFrameBuffer) {
194
+ if (!buffer) {
195
+ throw new Error('unexpected null buffer');
196
+ }
197
+ onFrameBuffer(buffer, frame);
198
+ }
199
+ (0, perf_1.stopPerfMeasure)(id);
216
200
  const compressedAssets = collectedAssets.map((asset) => (0, compress_assets_1.compressAsset)(assets.filter(truthy_1.truthy).flat(1), asset));
217
201
  assets[index] = compressedAssets;
218
202
  compressedAssets.forEach((asset) => {
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { SmallTCompMetadata } from 'remotion';
3
2
  import type { RenderMediaOnDownload } from './assets/download-and-map-assets-to-file';
4
3
  import type { DownloadMap } from './assets/download-map';
@@ -40,15 +40,15 @@ const image_format_1 = require("./image-format");
40
40
  const legacy_webpack_config_1 = require("./legacy-webpack-config");
41
41
  const open_browser_1 = require("./open-browser");
42
42
  const prepare_server_1 = require("./prepare-server");
43
- const provide_screenshot_1 = require("./provide-screenshot");
44
43
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
45
44
  const quality_1 = require("./quality");
46
45
  const seek_to_frame_1 = require("./seek-to-frame");
47
46
  const set_props_and_env_1 = require("./set-props-and-env");
47
+ const take_frame_and_compose_1 = require("./take-frame-and-compose");
48
48
  const validate_frame_1 = require("./validate-frame");
49
49
  const validate_puppeteer_timeout_1 = require("./validate-puppeteer-timeout");
50
50
  const validate_scale_1 = require("./validate-scale");
51
- const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale = 1, proxyPort, cancelSignal, }) => {
51
+ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', serveUrl, puppeteerInstance, dumpBrowserLogs = false, onError, inputProps, envVariables, output, frame = 0, overwrite = true, browserExecutable, timeoutInMilliseconds, chromiumOptions, scale = 1, proxyPort, cancelSignal, downloadMap, }) => {
52
52
  remotion_1.Internals.validateDimension(composition.height, 'height', 'in the `config` object passed to `renderStill()`');
53
53
  remotion_1.Internals.validateDimension(composition.width, 'width', 'in the `config` object passed to `renderStill()`');
54
54
  remotion_1.Internals.validateFps(composition.fps, 'in the `config` object of `renderStill()`', false);
@@ -151,16 +151,17 @@ const innerRenderStill = async ({ composition, quality, imageFormat = 'png', ser
151
151
  page,
152
152
  });
153
153
  await (0, seek_to_frame_1.seekToFrame)({ frame: stillFrame, page });
154
- const buffer = await (0, provide_screenshot_1.provideScreenshot)({
155
- page,
156
- imageFormat,
157
- quality,
158
- options: {
159
- frame: stillFrame,
160
- output,
161
- },
154
+ const { buffer } = await (0, take_frame_and_compose_1.takeFrameAndCompose)({
155
+ downloadMap,
156
+ frame: stillFrame,
157
+ freePage: page,
162
158
  height: composition.height,
163
159
  width: composition.width,
160
+ imageFormat,
161
+ scale,
162
+ output,
163
+ quality,
164
+ wantsBuffer: !output,
164
165
  });
165
166
  await cleanup();
166
167
  return { buffer: output ? null : buffer };
@@ -196,6 +197,7 @@ const renderStill = (options) => {
196
197
  serveUrl,
197
198
  onError: (err) => reject(err),
198
199
  proxyPort: offthreadPort,
200
+ downloadMap,
199
201
  });
200
202
  })
201
203
  .then((res) => resolve(res))
@@ -1,7 +1,7 @@
1
- /// <reference types="node" />
1
+ import type { ClipRegion } from 'remotion';
2
2
  import type { Page } from './browser/BrowserPage';
3
3
  import type { ImageFormat } from './image-format';
4
- export declare const screenshotDOMElement: ({ page, imageFormat, quality, opts, height, width, }: {
4
+ export declare const screenshotDOMElement: ({ page, imageFormat, quality, opts, height, width, clipRegion, }: {
5
5
  page: Page;
6
6
  imageFormat: ImageFormat;
7
7
  quality: number | undefined;
@@ -10,4 +10,5 @@ export declare const screenshotDOMElement: ({ page, imageFormat, quality, opts,
10
10
  };
11
11
  height: number;
12
12
  width: number;
13
+ clipRegion: ClipRegion | null;
13
14
  }) => Promise<Buffer>;
@@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.screenshotDOMElement = void 0;
4
4
  const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
5
5
  const puppeteer_screenshot_1 = require("./puppeteer-screenshot");
6
- const screenshotDOMElement = async ({ page, imageFormat, quality, opts, height, width, }) => {
6
+ const screenshotDOMElement = async ({ page, imageFormat, quality, opts, height, width, clipRegion, }) => {
7
7
  const { path } = opts;
8
8
  if (imageFormat === 'png') {
9
9
  await (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
@@ -36,6 +36,7 @@ const screenshotDOMElement = async ({ page, imageFormat, quality, opts, height,
36
36
  quality,
37
37
  width,
38
38
  height,
39
+ clipRegion,
39
40
  });
40
41
  };
41
42
  exports.screenshotDOMElement = screenshotDOMElement;
@@ -1,7 +1,7 @@
1
- /// <reference types="node" />
1
+ import type { ClipRegion } from 'remotion';
2
2
  import type { Page } from './browser/BrowserPage';
3
3
  import type { StillImageFormat } from './image-format';
4
- export declare const screenshotTask: ({ format, height, omitBackground, page, width, path, quality, }: {
4
+ export declare const screenshotTask: ({ format, height, omitBackground, page, width, path, quality, clipRegion, }: {
5
5
  page: Page;
6
6
  format: StillImageFormat;
7
7
  path?: string | undefined;
@@ -9,4 +9,5 @@ export declare const screenshotTask: ({ format, height, omitBackground, page, wi
9
9
  omitBackground: boolean;
10
10
  width: number;
11
11
  height: number;
12
+ clipRegion: ClipRegion | null;
12
13
  }) => Promise<Buffer | string>;
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.screenshotTask = void 0;
7
7
  const fs_1 = __importDefault(require("fs"));
8
8
  const perf_1 = require("./perf");
9
- const screenshotTask = async ({ format, height, omitBackground, page, width, path, quality, }) => {
9
+ const screenshotTask = async ({ format, height, omitBackground, page, width, path, quality, clipRegion, }) => {
10
10
  var _a;
11
11
  const client = page._client();
12
12
  const target = page.target();
@@ -25,13 +25,21 @@ const screenshotTask = async ({ format, height, omitBackground, page, width, pat
25
25
  const result = await client.send('Page.captureScreenshot', {
26
26
  format,
27
27
  quality,
28
- clip: {
29
- x: 0,
30
- y: 0,
31
- height,
32
- scale: 1,
33
- width,
34
- },
28
+ clip: clipRegion !== null && clipRegion !== 'hide'
29
+ ? {
30
+ x: clipRegion.x,
31
+ y: clipRegion.y,
32
+ height: clipRegion.height,
33
+ scale: 1,
34
+ width: clipRegion.width,
35
+ }
36
+ : {
37
+ x: 0,
38
+ y: 0,
39
+ height,
40
+ scale: 1,
41
+ width,
42
+ },
35
43
  captureBeyondViewport: true,
36
44
  optimizeForSpeed: true,
37
45
  });
@@ -0,0 +1,19 @@
1
+ import type { TAsset } from 'remotion';
2
+ import type { DownloadMap } from './assets/download-map';
3
+ import type { Page } from './browser/BrowserPage';
4
+ import type { ImageFormat } from './image-format';
5
+ export declare const takeFrameAndCompose: ({ freePage, imageFormat, quality, frame, width, height, output, scale, downloadMap, wantsBuffer, }: {
6
+ freePage: Page;
7
+ imageFormat: ImageFormat;
8
+ quality: number | undefined;
9
+ frame: number;
10
+ height: number;
11
+ width: number;
12
+ output: string | null;
13
+ scale: number;
14
+ downloadMap: DownloadMap;
15
+ wantsBuffer: boolean;
16
+ }) => Promise<{
17
+ buffer: Buffer | null;
18
+ collectedAssets: TAsset[];
19
+ }>;
@@ -0,0 +1,96 @@
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.takeFrameAndCompose = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const compose_1 = require("./compositor/compose");
10
+ const provide_screenshot_1 = require("./provide-screenshot");
11
+ const puppeteer_evaluate_1 = require("./puppeteer-evaluate");
12
+ const truthy_1 = require("./truthy");
13
+ const takeFrameAndCompose = async ({ freePage, imageFormat, quality, frame, width, height, output, scale, downloadMap, wantsBuffer, }) => {
14
+ var _a;
15
+ const [clipRegion, collectedAssets] = await Promise.all([
16
+ (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
17
+ pageFunction: () => {
18
+ if (typeof window.remotion_getClipRegion === 'undefined') {
19
+ return null;
20
+ }
21
+ return window.remotion_getClipRegion();
22
+ },
23
+ args: [],
24
+ frame,
25
+ page: freePage,
26
+ }),
27
+ (0, puppeteer_evaluate_1.puppeteerEvaluateWithCatch)({
28
+ pageFunction: () => {
29
+ return window.remotion_collectAssets();
30
+ },
31
+ args: [],
32
+ frame,
33
+ page: freePage,
34
+ }),
35
+ ]);
36
+ if (imageFormat === 'none') {
37
+ return { buffer: null, collectedAssets };
38
+ }
39
+ const needsComposing = clipRegion === null
40
+ ? null
41
+ : {
42
+ tmpFile: path_1.default.join(downloadMap.compositingDir, `${frame}.${imageFormat}`),
43
+ finalOutfie: output !== null && output !== void 0 ? output : path_1.default.join(downloadMap.compositingDir, `${frame}-final.${imageFormat}`),
44
+ clipRegion: clipRegion,
45
+ };
46
+ if (clipRegion !== 'hide') {
47
+ const shouldMakeBuffer = wantsBuffer && !needsComposing;
48
+ const buf = await (0, provide_screenshot_1.provideScreenshot)({
49
+ page: freePage,
50
+ imageFormat,
51
+ quality,
52
+ options: {
53
+ frame,
54
+ output: shouldMakeBuffer ? null : (_a = needsComposing === null || needsComposing === void 0 ? void 0 : needsComposing.tmpFile) !== null && _a !== void 0 ? _a : output,
55
+ },
56
+ height,
57
+ width,
58
+ clipRegion,
59
+ });
60
+ if (shouldMakeBuffer) {
61
+ return { buffer: buf, collectedAssets };
62
+ }
63
+ }
64
+ if (needsComposing) {
65
+ await (0, compose_1.compose)({
66
+ height: height * scale,
67
+ width: width * scale,
68
+ layers: [
69
+ needsComposing.clipRegion === 'hide'
70
+ ? null
71
+ : {
72
+ type: imageFormat === 'jpeg'
73
+ ? 'JpgImage'
74
+ : 'PngImage',
75
+ params: {
76
+ height: needsComposing.clipRegion.height * scale,
77
+ width: needsComposing.clipRegion.width * scale,
78
+ src: needsComposing.tmpFile,
79
+ x: needsComposing.clipRegion.x * scale,
80
+ y: needsComposing.clipRegion.y * scale,
81
+ },
82
+ },
83
+ ].filter(truthy_1.truthy),
84
+ output: needsComposing.finalOutfie,
85
+ downloadMap,
86
+ imageFormat: imageFormat === 'jpeg' ? 'Jpeg' : 'Png',
87
+ });
88
+ if (wantsBuffer) {
89
+ const buffer = await fs_1.default.promises.readFile(needsComposing.finalOutfie);
90
+ await fs_1.default.promises.unlink(needsComposing.finalOutfie);
91
+ return { buffer, collectedAssets };
92
+ }
93
+ }
94
+ return { buffer: null, collectedAssets };
95
+ };
96
+ exports.takeFrameAndCompose = takeFrameAndCompose;
@@ -1,4 +1,3 @@
1
- /// <reference types="node" />
2
1
  import type { OffthreadVideoImageFormat } from 'remotion';
3
2
  import type { NeedsResize, SpecialVCodecForTransparency } from './assets/download-map';
4
3
  import type { FfmpegExecutable } from './ffmpeg-executable';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/renderer",
3
- "version": "3.3.19",
3
+ "version": "3.3.25",
4
4
  "description": "Renderer for Remotion",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,9 +9,11 @@
9
9
  "lint": "eslint src --ext ts,tsx",
10
10
  "test": "vitest --run",
11
11
  "watch": "tsc -w",
12
- "build": "tsc -d"
12
+ "build": "node build.mjs && tsc -d",
13
+ "build-all": "node build.mjs --all",
14
+ "prepublishOnly": "node build.mjs --all"
13
15
  },
14
- "author": "",
16
+ "author": "Jonny Burger <jonny@remotion.dev>",
15
17
  "license": "SEE LICENSE IN LICENSE.md",
16
18
  "repository": {
17
19
  "url": "https://github.com/remotion-dev/remotion"
@@ -22,7 +24,7 @@
22
24
  "dependencies": {
23
25
  "execa": "5.1.1",
24
26
  "extract-zip": "2.0.1",
25
- "remotion": "3.3.19",
27
+ "remotion": "3.3.25",
26
28
  "source-map": "^0.8.0-beta.0",
27
29
  "ws": "8.7.0"
28
30
  },
@@ -46,16 +48,24 @@
46
48
  "typescript": "^4.7.0",
47
49
  "vitest": "0.24.3"
48
50
  },
51
+ "optionalDependencies": {
52
+ "@remotion/compositor-darwin-arm64": "3.3.25",
53
+ "@remotion/compositor-darwin-x64": "3.3.25",
54
+ "@remotion/compositor-linux-arm64-gnu": "3.3.25",
55
+ "@remotion/compositor-linux-arm64-musl": "3.3.25",
56
+ "@remotion/compositor-linux-x64-gnu": "3.3.25",
57
+ "@remotion/compositor-linux-x64-musl": "3.3.25",
58
+ "@remotion/compositor-win32-x64-msvc": "3.3.25"
59
+ },
49
60
  "keywords": [
50
61
  "remotion",
51
62
  "ffmpeg",
52
63
  "video",
53
64
  "react",
54
- "puppeteer",
55
65
  "player"
56
66
  ],
57
67
  "publishConfig": {
58
68
  "access": "public"
59
69
  },
60
- "gitHead": "097ff40cd34ecba3c7ebbbc08b442827b0f4ad7d"
70
+ "gitHead": "48e46dd2ede8fe903bf0139cebd6f546d7f2b5e0"
61
71
  }
package/.prettierrc.js DELETED
@@ -1,14 +0,0 @@
1
- module.exports = {
2
- singleQuote: true,
3
- bracketSpacing: false,
4
- useTabs: true,
5
- overrides: [
6
- {
7
- files: ['*.yml'],
8
- options: {
9
- singleQuote: false,
10
- },
11
- },
12
- ],
13
- plugins: [require.resolve('prettier-plugin-organize-imports')],
14
- };
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "extends": "../tsconfig.settings.json",
3
- "compilerOptions": {
4
- "composite": true,
5
- "rootDir": "src",
6
- "outDir": "dist"
7
- },
8
- "references": [{"path": "../core"}, {"path": "../bundler"}],
9
- "exclude": ["vitest.config.ts", "dist"]
10
- }