@remotion/renderer 3.3.82 → 3.3.84
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/assets/download-and-map-assets-to-file.js +6 -0
- package/dist/call-ffmpeg.d.ts +4 -1
- package/dist/call-ffmpeg.js +5 -4
- package/dist/compositor/compositor.d.ts +15 -0
- package/dist/compositor/compositor.js +193 -0
- package/dist/compositor/make-nonce.d.ts +1 -0
- package/dist/compositor/make-nonce.js +8 -0
- package/dist/create-ffmpeg-complex-filter.d.ts +1 -4
- package/dist/determine-resize-params.d.ts +1 -4
- package/dist/extract-frame-from-video.d.ts +1 -0
- package/dist/get-frame-of-video-slow.d.ts +2 -4
- package/dist/index.d.ts +1 -0
- package/dist/last-frame-from-video-cache.d.ts +1 -0
- package/dist/open-browser.d.ts +1 -0
- package/dist/open-browser.js +5 -2
- package/dist/provide-screenshot.d.ts +1 -0
- package/dist/puppeteer-screenshot.d.ts +1 -0
- package/dist/render-media.d.ts +1 -0
- package/dist/screenshot-dom-element.d.ts +1 -0
- package/dist/screenshot-task.d.ts +1 -0
- package/dist/take-frame-and-compose.d.ts +1 -0
- package/dist/try-to-extract-frame-of-video-fast.d.ts +1 -0
- package/package.json +9 -9
|
@@ -37,6 +37,9 @@ const download_file_1 = require("./download-file");
|
|
|
37
37
|
const sanitize_filepath_1 = require("./sanitize-filepath");
|
|
38
38
|
const waitForAssetToBeDownloaded = ({ src, downloadDir, downloadMap, }) => {
|
|
39
39
|
var _a, _b;
|
|
40
|
+
if (process.env.NODE_ENV === 'test') {
|
|
41
|
+
console.log('waiting for asset to be downloaded', src);
|
|
42
|
+
}
|
|
40
43
|
if ((_a = downloadMap.hasBeenDownloadedMap[src]) === null || _a === void 0 ? void 0 : _a[downloadDir]) {
|
|
41
44
|
return Promise.resolve((_b = downloadMap.hasBeenDownloadedMap[src]) === null || _b === void 0 ? void 0 : _b[downloadDir]);
|
|
42
45
|
}
|
|
@@ -139,6 +142,9 @@ const downloadAsset = async ({ src, onDownload, downloadMap, }) => {
|
|
|
139
142
|
downloadMap.isDownloadingMap[src] = {};
|
|
140
143
|
}
|
|
141
144
|
downloadMap.isDownloadingMap[src][downloadDir] = true;
|
|
145
|
+
if (process.env.NODE_ENV === 'test') {
|
|
146
|
+
console.log('Actually downloading asset', src);
|
|
147
|
+
}
|
|
142
148
|
const onProgress = onDownload(src);
|
|
143
149
|
if (src.startsWith('data:')) {
|
|
144
150
|
const [assetDetails, assetData] = src.substring('data:'.length).split(',');
|
package/dist/call-ffmpeg.d.ts
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
import execa from 'execa';
|
|
2
|
-
export declare const
|
|
2
|
+
export declare const dynamicLibraryPathOptions: () => {
|
|
3
3
|
env: {
|
|
4
4
|
DYLD_LIBRARY_PATH: string;
|
|
5
|
+
RUST_BACKTRACE: string;
|
|
5
6
|
PATH?: undefined;
|
|
6
7
|
LD_LIBRARY_PATH?: undefined;
|
|
7
8
|
} | {
|
|
8
9
|
PATH: string;
|
|
9
10
|
DYLD_LIBRARY_PATH?: undefined;
|
|
11
|
+
RUST_BACKTRACE?: undefined;
|
|
10
12
|
LD_LIBRARY_PATH?: undefined;
|
|
11
13
|
} | {
|
|
12
14
|
LD_LIBRARY_PATH: string;
|
|
13
15
|
DYLD_LIBRARY_PATH?: undefined;
|
|
16
|
+
RUST_BACKTRACE?: undefined;
|
|
14
17
|
PATH?: undefined;
|
|
15
18
|
};
|
|
16
19
|
};
|
package/dist/call-ffmpeg.js
CHANGED
|
@@ -3,17 +3,18 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.callFf = exports.
|
|
6
|
+
exports.callFf = exports.dynamicLibraryPathOptions = void 0;
|
|
7
7
|
const execa_1 = __importDefault(require("execa"));
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const get_executable_path_1 = require("./compositor/get-executable-path");
|
|
10
10
|
const truthy_1 = require("./truthy");
|
|
11
|
-
const
|
|
11
|
+
const dynamicLibraryPathOptions = () => {
|
|
12
12
|
const lib = path_1.default.join((0, get_executable_path_1.getExecutablePath)('ffmpeg-cwd'), 'remotion', 'lib');
|
|
13
13
|
return {
|
|
14
14
|
env: process.platform === 'darwin'
|
|
15
15
|
? {
|
|
16
16
|
DYLD_LIBRARY_PATH: lib,
|
|
17
|
+
RUST_BACKTRACE: '1',
|
|
17
18
|
}
|
|
18
19
|
: process.platform === 'win32'
|
|
19
20
|
? {
|
|
@@ -24,10 +25,10 @@ const callFfExtraOptions = () => {
|
|
|
24
25
|
},
|
|
25
26
|
};
|
|
26
27
|
};
|
|
27
|
-
exports.
|
|
28
|
+
exports.dynamicLibraryPathOptions = dynamicLibraryPathOptions;
|
|
28
29
|
const callFf = (bin, args, options) => {
|
|
29
30
|
return (0, execa_1.default)((0, get_executable_path_1.getExecutablePath)(bin), args.filter(truthy_1.truthy), {
|
|
30
|
-
...(0, exports.
|
|
31
|
+
...(0, exports.dynamicLibraryPathOptions)(),
|
|
31
32
|
...options,
|
|
32
33
|
});
|
|
33
34
|
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import type { CompositorCommand } from './payloads';
|
|
3
|
+
export declare type Compositor = {
|
|
4
|
+
finishCommands: () => void;
|
|
5
|
+
executeCommand: <T extends keyof CompositorCommand>(type: T, payload: CompositorCommand[T]) => Promise<Buffer>;
|
|
6
|
+
waitForDone: () => Promise<void>;
|
|
7
|
+
};
|
|
8
|
+
export declare const spawnCompositorOrReuse: <T extends keyof CompositorCommand>({ initiatePayload, renderId, type, }: {
|
|
9
|
+
type: T;
|
|
10
|
+
initiatePayload: CompositorCommand[T];
|
|
11
|
+
renderId: string;
|
|
12
|
+
}) => Compositor;
|
|
13
|
+
export declare const releaseCompositorWithId: (renderId: string) => void;
|
|
14
|
+
export declare const waitForCompositorWithIdToQuit: (renderId: string) => Promise<void>;
|
|
15
|
+
export declare const startCompositor: <T extends keyof CompositorCommand>(type: T, payload: CompositorCommand[T]) => Compositor;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.startCompositor = exports.waitForCompositorWithIdToQuit = exports.releaseCompositorWithId = exports.spawnCompositorOrReuse = void 0;
|
|
4
|
+
const child_process_1 = require("child_process");
|
|
5
|
+
const call_ffmpeg_1 = require("../call-ffmpeg");
|
|
6
|
+
const get_executable_path_1 = require("./get-executable-path");
|
|
7
|
+
const make_nonce_1 = require("./make-nonce");
|
|
8
|
+
const compositorMap = {};
|
|
9
|
+
const spawnCompositorOrReuse = ({ initiatePayload, renderId, type, }) => {
|
|
10
|
+
if (!compositorMap[renderId]) {
|
|
11
|
+
compositorMap[renderId] = (0, exports.startCompositor)(type, initiatePayload);
|
|
12
|
+
}
|
|
13
|
+
return compositorMap[renderId];
|
|
14
|
+
};
|
|
15
|
+
exports.spawnCompositorOrReuse = spawnCompositorOrReuse;
|
|
16
|
+
const releaseCompositorWithId = (renderId) => {
|
|
17
|
+
if (compositorMap[renderId]) {
|
|
18
|
+
compositorMap[renderId].finishCommands();
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
exports.releaseCompositorWithId = releaseCompositorWithId;
|
|
22
|
+
const waitForCompositorWithIdToQuit = (renderId) => {
|
|
23
|
+
if (!compositorMap[renderId]) {
|
|
24
|
+
throw new TypeError('No compositor with that id');
|
|
25
|
+
}
|
|
26
|
+
return compositorMap[renderId].waitForDone();
|
|
27
|
+
};
|
|
28
|
+
exports.waitForCompositorWithIdToQuit = waitForCompositorWithIdToQuit;
|
|
29
|
+
const startCompositor = (type, payload) => {
|
|
30
|
+
const bin = (0, get_executable_path_1.getExecutablePath)('compositor');
|
|
31
|
+
const fullCommand = {
|
|
32
|
+
nonce: (0, make_nonce_1.makeNonce)(),
|
|
33
|
+
payload: {
|
|
34
|
+
type,
|
|
35
|
+
params: payload,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
const child = (0, child_process_1.spawn)(bin, [JSON.stringify(fullCommand)], (0, call_ffmpeg_1.dynamicLibraryPathOptions)());
|
|
39
|
+
const stderrChunks = [];
|
|
40
|
+
let outputBuffer = Buffer.from('');
|
|
41
|
+
const separator = Buffer.from('remotion_buffer:');
|
|
42
|
+
const waiters = new Map();
|
|
43
|
+
const onMessage = (statusType, nonce, data) => {
|
|
44
|
+
if (nonce === '0') {
|
|
45
|
+
console.log(data.toString('utf8'));
|
|
46
|
+
}
|
|
47
|
+
if (waiters.has(nonce)) {
|
|
48
|
+
if (statusType === 'error') {
|
|
49
|
+
try {
|
|
50
|
+
const parsed = JSON.parse(data.toString('utf8'));
|
|
51
|
+
waiters.get(nonce).reject(new Error(`Compositor error: ${parsed.error}`));
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
waiters.get(nonce).reject(new Error(data.toString('utf8')));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
waiters.get(nonce).resolve(data);
|
|
59
|
+
}
|
|
60
|
+
waiters.delete(nonce);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
let quit = false;
|
|
64
|
+
let missingData = null;
|
|
65
|
+
const processInput = () => {
|
|
66
|
+
let separatorIndex = outputBuffer.indexOf(separator);
|
|
67
|
+
if (separatorIndex === -1) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
separatorIndex += separator.length;
|
|
71
|
+
let nonceString = '';
|
|
72
|
+
let lengthString = '';
|
|
73
|
+
let statusString = '';
|
|
74
|
+
// Each message from Rust is prefixed with `remotion_buffer;{[nonce]}:{[length]}`
|
|
75
|
+
// Let's read the buffer to extract the nonce, and if the full length is available,
|
|
76
|
+
// we'll extract the data and pass it to the callback.
|
|
77
|
+
// eslint-disable-next-line no-constant-condition
|
|
78
|
+
while (true) {
|
|
79
|
+
const nextDigit = outputBuffer[separatorIndex];
|
|
80
|
+
// 0x3a is the character ":"
|
|
81
|
+
if (nextDigit === 0x3a) {
|
|
82
|
+
separatorIndex++;
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
separatorIndex++;
|
|
86
|
+
nonceString += String.fromCharCode(nextDigit);
|
|
87
|
+
}
|
|
88
|
+
// eslint-disable-next-line no-constant-condition
|
|
89
|
+
while (true) {
|
|
90
|
+
const nextDigit = outputBuffer[separatorIndex];
|
|
91
|
+
if (nextDigit === 0x3a) {
|
|
92
|
+
separatorIndex++;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
separatorIndex++;
|
|
96
|
+
lengthString += String.fromCharCode(nextDigit);
|
|
97
|
+
}
|
|
98
|
+
// eslint-disable-next-line no-constant-condition
|
|
99
|
+
while (true) {
|
|
100
|
+
const nextDigit = outputBuffer[separatorIndex];
|
|
101
|
+
if (nextDigit === 0x3a) {
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
separatorIndex++;
|
|
105
|
+
statusString += String.fromCharCode(nextDigit);
|
|
106
|
+
}
|
|
107
|
+
const length = Number(lengthString);
|
|
108
|
+
const status = Number(statusString);
|
|
109
|
+
const dataLength = outputBuffer.length - separatorIndex - 1;
|
|
110
|
+
if (dataLength < length) {
|
|
111
|
+
missingData = {
|
|
112
|
+
dataMissing: length - dataLength,
|
|
113
|
+
};
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
const data = outputBuffer.subarray(separatorIndex + 1, separatorIndex + 1 + Number(lengthString));
|
|
117
|
+
onMessage(status === 1 ? 'error' : 'success', nonceString, data);
|
|
118
|
+
missingData = null;
|
|
119
|
+
outputBuffer = outputBuffer.subarray(separatorIndex + Number(lengthString) + 1);
|
|
120
|
+
processInput();
|
|
121
|
+
};
|
|
122
|
+
let unprocessedBuffers = [];
|
|
123
|
+
child.stdout.on('data', (data) => {
|
|
124
|
+
unprocessedBuffers.push(data);
|
|
125
|
+
const separatorIndex = data.indexOf(separator);
|
|
126
|
+
if (separatorIndex === -1) {
|
|
127
|
+
if (missingData) {
|
|
128
|
+
missingData.dataMissing -= data.length;
|
|
129
|
+
}
|
|
130
|
+
if (!missingData || missingData.dataMissing > 0) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
unprocessedBuffers.unshift(outputBuffer);
|
|
135
|
+
outputBuffer = Buffer.concat(unprocessedBuffers);
|
|
136
|
+
unprocessedBuffers = [];
|
|
137
|
+
processInput();
|
|
138
|
+
});
|
|
139
|
+
child.stderr.on('data', (data) => {
|
|
140
|
+
if (data.toString('utf-8').includes('No accelerated colorspace conversion')) {
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
console.log(data.toString('utf-8'));
|
|
144
|
+
});
|
|
145
|
+
return {
|
|
146
|
+
waitForDone: () => {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
child.on('close', (code) => {
|
|
149
|
+
quit = true;
|
|
150
|
+
const waitersToKill = Array.from(waiters.values());
|
|
151
|
+
for (const waiter of waitersToKill) {
|
|
152
|
+
waiter.reject(new Error(`Compositor quit with code ${code}`));
|
|
153
|
+
}
|
|
154
|
+
waiters.clear();
|
|
155
|
+
if (code === 0) {
|
|
156
|
+
resolve();
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
reject(Buffer.concat(stderrChunks).toString('utf-8'));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
finishCommands: () => {
|
|
165
|
+
if (quit) {
|
|
166
|
+
throw new Error('Compositor already quit');
|
|
167
|
+
}
|
|
168
|
+
child.stdin.write('EOF\n');
|
|
169
|
+
},
|
|
170
|
+
executeCommand: (command, params) => {
|
|
171
|
+
if (quit) {
|
|
172
|
+
throw new Error('Compositor already quit');
|
|
173
|
+
}
|
|
174
|
+
return new Promise((resolve, reject) => {
|
|
175
|
+
const nonce = (0, make_nonce_1.makeNonce)();
|
|
176
|
+
const composed = {
|
|
177
|
+
nonce,
|
|
178
|
+
payload: {
|
|
179
|
+
type: command,
|
|
180
|
+
params,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
// TODO: Should have a way to error out a single task
|
|
184
|
+
child.stdin.write(JSON.stringify(composed) + '\n');
|
|
185
|
+
waiters.set(nonce, {
|
|
186
|
+
resolve,
|
|
187
|
+
reject,
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
};
|
|
193
|
+
exports.startCompositor = startCompositor;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const makeNonce: () => string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.makeNonce = void 0;
|
|
4
|
+
const makeNonce = () => {
|
|
5
|
+
return (Math.random().toString(36).substring(2, 15) +
|
|
6
|
+
Math.random().toString(36).substring(2, 15));
|
|
7
|
+
};
|
|
8
|
+
exports.makeNonce = makeNonce;
|
|
@@ -7,9 +7,6 @@ export declare const createFfmpegComplexFilter: ({ filters, downloadMap, ffmpegE
|
|
|
7
7
|
ffmpegExecutable: FfmpegExecutable;
|
|
8
8
|
remotionRoot: string;
|
|
9
9
|
}) => Promise<{
|
|
10
|
-
complexFilterFlag: [
|
|
11
|
-
string,
|
|
12
|
-
string
|
|
13
|
-
] | null;
|
|
10
|
+
complexFilterFlag: [string, string] | null;
|
|
14
11
|
cleanup: () => void;
|
|
15
12
|
}>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
1
2
|
import type { OffthreadVideoImageFormat } from 'remotion';
|
|
2
3
|
import type { SpecialVCodecForTransparency } from './assets/download-map';
|
|
3
4
|
import type { FfmpegExecutable } from './ffmpeg-executable';
|
|
@@ -7,10 +8,7 @@ export declare const getFrameOfVideoSlow: ({ src, duration, ffmpegExecutable, im
|
|
|
7
8
|
duration: number;
|
|
8
9
|
imageFormat: OffthreadVideoImageFormat;
|
|
9
10
|
specialVCodecForTransparency: SpecialVCodecForTransparency;
|
|
10
|
-
needsResize: [
|
|
11
|
-
number,
|
|
12
|
-
number
|
|
13
|
-
] | null;
|
|
11
|
+
needsResize: [number, number] | null;
|
|
14
12
|
offset: number;
|
|
15
13
|
fps: number | null;
|
|
16
14
|
remotionRoot: string;
|
package/dist/index.d.ts
CHANGED
package/dist/open-browser.d.ts
CHANGED
package/dist/open-browser.js
CHANGED
|
@@ -32,7 +32,7 @@ exports.killAllBrowsers = killAllBrowsers;
|
|
|
32
32
|
* @see [Documentation](https://www.remotion.dev/docs/renderer/open-browser)
|
|
33
33
|
*/
|
|
34
34
|
const openBrowser = async (browser, options) => {
|
|
35
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
35
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
|
|
36
36
|
if (browser === 'firefox') {
|
|
37
37
|
throw new TypeError('Firefox supported is not yet turned on. Stay tuned for the future.');
|
|
38
38
|
}
|
|
@@ -102,8 +102,11 @@ const openBrowser = async (browser, options) => {
|
|
|
102
102
|
...(((_j = options === null || options === void 0 ? void 0 : options.chromiumOptions) === null || _j === void 0 ? void 0 : _j.disableWebSecurity)
|
|
103
103
|
? ['--disable-web-security']
|
|
104
104
|
: []),
|
|
105
|
+
((_k = options === null || options === void 0 ? void 0 : options.chromiumOptions) === null || _k === void 0 ? void 0 : _k.userAgent)
|
|
106
|
+
? `--user-agent="${options.chromiumOptions.userAgent}"`
|
|
107
|
+
: null,
|
|
105
108
|
].filter(Boolean),
|
|
106
|
-
defaultViewport: (
|
|
109
|
+
defaultViewport: (_l = options === null || options === void 0 ? void 0 : options.viewport) !== null && _l !== void 0 ? _l : {
|
|
107
110
|
height: 720,
|
|
108
111
|
width: 1280,
|
|
109
112
|
deviceScaleFactor: 1,
|
package/dist/render-media.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/renderer",
|
|
3
|
-
"version": "3.3.
|
|
3
|
+
"version": "3.3.84",
|
|
4
4
|
"description": "Renderer for Remotion",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"execa": "5.1.1",
|
|
18
18
|
"extract-zip": "2.0.1",
|
|
19
|
-
"remotion": "3.3.
|
|
19
|
+
"remotion": "3.3.84",
|
|
20
20
|
"source-map": "^0.8.0-beta.0",
|
|
21
21
|
"ws": "8.7.0"
|
|
22
22
|
},
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"vitest": "0.24.3"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@remotion/compositor-darwin-arm64": "3.3.
|
|
45
|
-
"@remotion/compositor-darwin-x64": "3.3.
|
|
46
|
-
"@remotion/compositor-linux-arm64-gnu": "3.3.
|
|
47
|
-
"@remotion/compositor-linux-arm64-musl": "3.3.
|
|
48
|
-
"@remotion/compositor-linux-x64-gnu": "3.3.
|
|
49
|
-
"@remotion/compositor-linux-x64-musl": "3.3.
|
|
50
|
-
"@remotion/compositor-win32-x64-msvc": "3.3.
|
|
44
|
+
"@remotion/compositor-darwin-arm64": "3.3.84",
|
|
45
|
+
"@remotion/compositor-darwin-x64": "3.3.84",
|
|
46
|
+
"@remotion/compositor-linux-arm64-gnu": "3.3.84",
|
|
47
|
+
"@remotion/compositor-linux-arm64-musl": "3.3.84",
|
|
48
|
+
"@remotion/compositor-linux-x64-gnu": "3.3.84",
|
|
49
|
+
"@remotion/compositor-linux-x64-musl": "3.3.84",
|
|
50
|
+
"@remotion/compositor-win32-x64-msvc": "3.3.84"
|
|
51
51
|
},
|
|
52
52
|
"keywords": [
|
|
53
53
|
"remotion",
|