@remotion/renderer 4.0.12 → 4.0.14
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/calculate-asset-positions.d.ts +2 -2
- package/dist/assets/calculate-asset-positions.js +6 -6
- package/dist/assets/convert-assets-to-file-urls.d.ts +3 -3
- package/dist/assets/convert-assets-to-file-urls.js +1 -1
- package/dist/assets/download-and-map-assets-to-file.d.ts +4 -4
- package/dist/assets/download-and-map-assets-to-file.js +3 -3
- package/dist/assets/download-map.d.ts +2 -2
- package/dist/assets/types.d.ts +3 -3
- package/dist/assets/types.js +3 -3
- package/dist/compress-assets.d.ts +2 -2
- package/dist/compress-assets.js +6 -6
- package/dist/index.d.ts +14 -14
- package/dist/logger.d.ts +1 -1
- package/dist/render-frames.js +2 -2
- package/dist/take-frame-and-compose.d.ts +2 -2
- package/package.json +9 -9
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TRenderAsset } from 'remotion';
|
|
2
2
|
import type { Assets } from './types';
|
|
3
|
-
export declare const calculateAssetPositions: (frames:
|
|
3
|
+
export declare const calculateAssetPositions: (frames: TRenderAsset[][]) => Assets;
|
|
@@ -7,19 +7,19 @@ const types_1 = require("./types");
|
|
|
7
7
|
const areEqual = (a, b) => {
|
|
8
8
|
return a.id === b.id;
|
|
9
9
|
};
|
|
10
|
-
const findFrom = (target,
|
|
11
|
-
const index = target.findIndex((a) => areEqual(a,
|
|
10
|
+
const findFrom = (target, renderAsset) => {
|
|
11
|
+
const index = target.findIndex((a) => areEqual(a, renderAsset));
|
|
12
12
|
if (index === -1) {
|
|
13
13
|
return false;
|
|
14
14
|
}
|
|
15
15
|
target.splice(index, 1);
|
|
16
16
|
return true;
|
|
17
17
|
};
|
|
18
|
-
const copyAndDeduplicateAssets = (
|
|
18
|
+
const copyAndDeduplicateAssets = (renderAssets) => {
|
|
19
19
|
const deduplicated = [];
|
|
20
|
-
for (const
|
|
21
|
-
if (!deduplicated.find((d) => d.id ===
|
|
22
|
-
deduplicated.push(
|
|
20
|
+
for (const renderAsset of renderAssets) {
|
|
21
|
+
if (!deduplicated.find((d) => d.id === renderAsset.id)) {
|
|
22
|
+
deduplicated.push(renderAsset);
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
25
|
return deduplicated;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TRenderAsset } from 'remotion';
|
|
2
2
|
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
|
|
3
3
|
import type { DownloadMap } from './download-map';
|
|
4
4
|
export declare const convertAssetsToFileUrls: ({ assets, onDownload, downloadMap, }: {
|
|
5
|
-
assets:
|
|
5
|
+
assets: TRenderAsset[][];
|
|
6
6
|
onDownload: RenderMediaOnDownload;
|
|
7
7
|
downloadMap: DownloadMap;
|
|
8
|
-
}) => Promise<
|
|
8
|
+
}) => Promise<TRenderAsset[][]>;
|
|
@@ -16,7 +16,7 @@ const convertAssetsToFileUrls = async ({ assets, onDownload, downloadMap, }) =>
|
|
|
16
16
|
const result = await Promise.all(ch.map((assetsForFrame) => {
|
|
17
17
|
return Promise.all(assetsForFrame.map((a) => {
|
|
18
18
|
return (0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
|
|
19
|
-
|
|
19
|
+
renderAsset: a,
|
|
20
20
|
onDownload,
|
|
21
21
|
downloadMap,
|
|
22
22
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TRenderAsset } from 'remotion';
|
|
2
2
|
import type { DownloadMap } from './download-map';
|
|
3
3
|
export type RenderMediaOnDownload = (src: string) => ((progress: {
|
|
4
4
|
percent: number | null;
|
|
@@ -16,9 +16,9 @@ export declare const getSanitizedFilenameForAssetUrl: ({ src, downloadDir, conte
|
|
|
16
16
|
contentDisposition: string | null;
|
|
17
17
|
contentType: string | null;
|
|
18
18
|
}) => string;
|
|
19
|
-
export declare const downloadAndMapAssetsToFileUrl: ({
|
|
20
|
-
|
|
19
|
+
export declare const downloadAndMapAssetsToFileUrl: ({ renderAsset, onDownload, downloadMap, }: {
|
|
20
|
+
renderAsset: TRenderAsset;
|
|
21
21
|
onDownload: RenderMediaOnDownload | null;
|
|
22
22
|
downloadMap: DownloadMap;
|
|
23
|
-
}) => Promise<
|
|
23
|
+
}) => Promise<TRenderAsset>;
|
|
24
24
|
export declare const attachDownloadListenerToEmitter: (downloadMap: DownloadMap, onDownload: RenderMediaOnDownload | null) => () => void;
|
|
@@ -241,15 +241,15 @@ const getSanitizedFilenameForAssetUrl = ({ src, downloadDir, contentDisposition,
|
|
|
241
241
|
return node_path_1.default.join(downloadDir, (0, sanitize_filepath_1.sanitizeFilePath)(filename));
|
|
242
242
|
};
|
|
243
243
|
exports.getSanitizedFilenameForAssetUrl = getSanitizedFilenameForAssetUrl;
|
|
244
|
-
const downloadAndMapAssetsToFileUrl = async ({
|
|
244
|
+
const downloadAndMapAssetsToFileUrl = async ({ renderAsset, onDownload, downloadMap, }) => {
|
|
245
245
|
const cleanup = (0, exports.attachDownloadListenerToEmitter)(downloadMap, onDownload);
|
|
246
246
|
const newSrc = await (0, exports.downloadAsset)({
|
|
247
|
-
src:
|
|
247
|
+
src: renderAsset.src,
|
|
248
248
|
downloadMap,
|
|
249
249
|
});
|
|
250
250
|
cleanup();
|
|
251
251
|
return {
|
|
252
|
-
...
|
|
252
|
+
...renderAsset,
|
|
253
253
|
src: newSrc,
|
|
254
254
|
};
|
|
255
255
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { TRenderAsset } from 'remotion';
|
|
2
2
|
import { OffthreadVideoServerEmitter } from '../offthread-video-server';
|
|
3
3
|
import type { RenderMediaOnDownload } from './download-and-map-assets-to-file';
|
|
4
4
|
export type AudioChannelsAndDurationResultCache = {
|
|
@@ -38,7 +38,7 @@ export type DownloadMap = {
|
|
|
38
38
|
};
|
|
39
39
|
};
|
|
40
40
|
export type RenderAssetInfo = {
|
|
41
|
-
assets:
|
|
41
|
+
assets: TRenderAsset[][];
|
|
42
42
|
imageSequenceName: string;
|
|
43
43
|
firstFrameIndex: number;
|
|
44
44
|
downloadMap: DownloadMap;
|
package/dist/assets/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export type UnsafeAsset = Omit<
|
|
1
|
+
import type { TRenderAsset } from 'remotion';
|
|
2
|
+
export type UnsafeAsset = Omit<TRenderAsset, 'frame' | 'id' | 'volume' | 'mediaFrame'> & {
|
|
3
3
|
startInVideo: number;
|
|
4
4
|
duration: number | null;
|
|
5
5
|
trimLeft: number;
|
|
@@ -13,5 +13,5 @@ export type MediaAsset = Omit<UnsafeAsset, 'duration' | 'volume'> & {
|
|
|
13
13
|
duration: number;
|
|
14
14
|
volume: AssetVolume;
|
|
15
15
|
};
|
|
16
|
-
export declare const uncompressMediaAsset: (
|
|
16
|
+
export declare const uncompressMediaAsset: (allRenderAssets: TRenderAsset[], assetToUncompress: TRenderAsset) => TRenderAsset;
|
|
17
17
|
export type Assets = MediaAsset[];
|
package/dist/assets/types.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.uncompressMediaAsset = void 0;
|
|
4
|
-
const uncompressMediaAsset = (
|
|
4
|
+
const uncompressMediaAsset = (allRenderAssets, assetToUncompress) => {
|
|
5
5
|
const isCompressed = assetToUncompress.src.match(/same-as-(.*)-([0-9]+)$/);
|
|
6
6
|
if (!isCompressed) {
|
|
7
7
|
return assetToUncompress;
|
|
8
8
|
}
|
|
9
9
|
const [, id, frame] = isCompressed;
|
|
10
|
-
const assetToFill =
|
|
10
|
+
const assetToFill = allRenderAssets.find((a) => a.id === id && String(a.frame) === frame);
|
|
11
11
|
if (!assetToFill) {
|
|
12
12
|
console.log('List of assets:');
|
|
13
|
-
console.log(
|
|
13
|
+
console.log(allRenderAssets);
|
|
14
14
|
throw new TypeError(`Cannot uncompress asset, asset list seems corrupt. Please file a bug in the Remotion repo with the debug information above.`);
|
|
15
15
|
}
|
|
16
16
|
return {
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* Since audio or video can be base64-encoded, those can be really long strings.
|
|
3
3
|
* Since we track the `src` property for every frame, Node.JS can run out of memory easily. Instead of duplicating the src for every frame, we save memory by replacing the full base 64 encoded data with a string `same-as-[asset-id]-[frame]` referencing a previous asset with the same src.
|
|
4
4
|
*/
|
|
5
|
-
import type {
|
|
6
|
-
export declare const compressAsset: (
|
|
5
|
+
import type { TRenderAsset } from 'remotion';
|
|
6
|
+
export declare const compressAsset: (previousRenderAssets: TRenderAsset[], newRenderAsset: TRenderAsset) => TRenderAsset;
|
|
7
7
|
export declare const isAssetCompressed: (src: string) => boolean;
|
package/dist/compress-assets.js
CHANGED
|
@@ -5,16 +5,16 @@
|
|
|
5
5
|
*/
|
|
6
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
7
|
exports.isAssetCompressed = exports.compressAsset = void 0;
|
|
8
|
-
const compressAsset = (
|
|
9
|
-
if (
|
|
10
|
-
return
|
|
8
|
+
const compressAsset = (previousRenderAssets, newRenderAsset) => {
|
|
9
|
+
if (newRenderAsset.src.length < 400) {
|
|
10
|
+
return newRenderAsset;
|
|
11
11
|
}
|
|
12
|
-
const assetWithSameSrc =
|
|
12
|
+
const assetWithSameSrc = previousRenderAssets.find((a) => a.src === newRenderAsset.src);
|
|
13
13
|
if (!assetWithSameSrc) {
|
|
14
|
-
return
|
|
14
|
+
return newRenderAsset;
|
|
15
15
|
}
|
|
16
16
|
return {
|
|
17
|
-
...
|
|
17
|
+
...newRenderAsset,
|
|
18
18
|
src: `same-as-${assetWithSameSrc.id}-${assetWithSameSrc.frame}`,
|
|
19
19
|
};
|
|
20
20
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,7 @@ export declare const RenderInternals: {
|
|
|
44
44
|
downloadMap: import("./assets/download-map").DownloadMap;
|
|
45
45
|
remotionRoot: string;
|
|
46
46
|
concurrency: number;
|
|
47
|
-
logLevel: "
|
|
47
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
48
48
|
indent: boolean;
|
|
49
49
|
}) => Promise<{
|
|
50
50
|
port: number;
|
|
@@ -123,7 +123,7 @@ export declare const RenderInternals: {
|
|
|
123
123
|
DEFAULT_CODEC: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif";
|
|
124
124
|
isAudioCodec: (codec: "h264" | "h265" | "vp8" | "vp9" | "mp3" | "aac" | "wav" | "prores" | "h264-mkv" | "gif" | undefined) => boolean;
|
|
125
125
|
logLevels: readonly ["verbose", "info", "warn", "error"];
|
|
126
|
-
isEqualOrBelowLogLevel: (currentLevel: "
|
|
126
|
+
isEqualOrBelowLogLevel: (currentLevel: "verbose" | "info" | "warn" | "error", level: "verbose" | "info" | "warn" | "error") => boolean;
|
|
127
127
|
isValidLogLevel: (level: string) => boolean;
|
|
128
128
|
perf: typeof perf;
|
|
129
129
|
convertToPositiveFrameIndex: ({ frame, durationInFrames, }: {
|
|
@@ -352,30 +352,30 @@ export declare const RenderInternals: {
|
|
|
352
352
|
verbose: (message?: any, ...optionalParams: any[]) => void;
|
|
353
353
|
verboseAdvanced: (options: {
|
|
354
354
|
indent: boolean;
|
|
355
|
-
logLevel: "
|
|
355
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
356
356
|
} & {
|
|
357
357
|
tag?: string | undefined;
|
|
358
358
|
}, message?: any, ...optionalParams: any[]) => void;
|
|
359
359
|
info: (message?: any, ...optionalParams: any[]) => void;
|
|
360
360
|
infoAdvanced: (options: {
|
|
361
361
|
indent: boolean;
|
|
362
|
-
logLevel: "
|
|
362
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
363
363
|
}, message?: any, ...optionalParams: any[]) => void;
|
|
364
364
|
warn: (message?: any, ...optionalParams: any[]) => void;
|
|
365
365
|
warnAdvanced: (options: {
|
|
366
366
|
indent: boolean;
|
|
367
|
-
logLevel: "
|
|
367
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
368
368
|
}, message?: any, ...optionalParams: any[]) => void;
|
|
369
369
|
error: (message?: any, ...optionalParams: any[]) => void;
|
|
370
370
|
errorAdvanced: (options: {
|
|
371
371
|
indent: boolean;
|
|
372
|
-
logLevel: "
|
|
372
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
373
373
|
} & {
|
|
374
374
|
tag?: string | undefined;
|
|
375
375
|
}, message?: any, ...optionalParams: any[]) => void;
|
|
376
376
|
};
|
|
377
|
-
getLogLevel: () => "
|
|
378
|
-
setLogLevel: (newLogLevel: "
|
|
377
|
+
getLogLevel: () => "verbose" | "info" | "warn" | "error";
|
|
378
|
+
setLogLevel: (newLogLevel: "verbose" | "info" | "warn" | "error") => void;
|
|
379
379
|
INDENT_TOKEN: string;
|
|
380
380
|
isColorSupported: () => boolean;
|
|
381
381
|
HeadlessBrowser: typeof HeadlessBrowser;
|
|
@@ -384,7 +384,7 @@ export declare const RenderInternals: {
|
|
|
384
384
|
port: number | null;
|
|
385
385
|
remotionRoot: string;
|
|
386
386
|
concurrency: number;
|
|
387
|
-
logLevel: "
|
|
387
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
388
388
|
indent: boolean;
|
|
389
389
|
}) => Promise<import("./prepare-server").RemotionServer>;
|
|
390
390
|
makeOrReuseServer: (server: import("./prepare-server").RemotionServer | undefined, config: {
|
|
@@ -392,7 +392,7 @@ export declare const RenderInternals: {
|
|
|
392
392
|
port: number | null;
|
|
393
393
|
remotionRoot: string;
|
|
394
394
|
concurrency: number;
|
|
395
|
-
logLevel: "
|
|
395
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
396
396
|
indent: boolean;
|
|
397
397
|
}, { onDownload, onError, }: {
|
|
398
398
|
onError: (err: Error) => void;
|
|
@@ -421,7 +421,7 @@ export declare const RenderInternals: {
|
|
|
421
421
|
cancelSignal: import("./make-cancel-signal").CancelSignal | null;
|
|
422
422
|
indent: boolean;
|
|
423
423
|
server: import("./prepare-server").RemotionServer | undefined;
|
|
424
|
-
logLevel: "
|
|
424
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
425
425
|
serveUrl: string;
|
|
426
426
|
port: number | null;
|
|
427
427
|
}) => Promise<{
|
|
@@ -434,7 +434,7 @@ export declare const RenderInternals: {
|
|
|
434
434
|
viewport: import("./browser/PuppeteerViewport").Viewport | null;
|
|
435
435
|
indent: boolean;
|
|
436
436
|
browser: import("./browser").Browser;
|
|
437
|
-
logLevel: "
|
|
437
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
438
438
|
}) => Promise<HeadlessBrowser>;
|
|
439
439
|
internalSelectComposition: (options: {
|
|
440
440
|
serializedInputPropsWithCustomSchema: string;
|
|
@@ -447,7 +447,7 @@ export declare const RenderInternals: {
|
|
|
447
447
|
port: number | null;
|
|
448
448
|
indent: boolean;
|
|
449
449
|
server: import("./prepare-server").RemotionServer | undefined;
|
|
450
|
-
logLevel: "
|
|
450
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
451
451
|
serveUrl: string;
|
|
452
452
|
id: string;
|
|
453
453
|
}) => Promise<{
|
|
@@ -465,7 +465,7 @@ export declare const RenderInternals: {
|
|
|
465
465
|
port: number | null;
|
|
466
466
|
server: import("./prepare-server").RemotionServer | undefined;
|
|
467
467
|
indent: boolean;
|
|
468
|
-
logLevel: "
|
|
468
|
+
logLevel: "verbose" | "info" | "warn" | "error";
|
|
469
469
|
serveUrlOrWebpackUrl: string;
|
|
470
470
|
}) => Promise<import("remotion").VideoConfig[]>;
|
|
471
471
|
internalRenderFrames: ({ browserExecutable, cancelSignal, chromiumOptions, composition, concurrency, envVariables, everyNthFrame, frameRange, imageFormat, indent, jpegQuality, muted, onBrowserLog, onDownload, onFrameBuffer, onFrameUpdate, onStart, outputDir, port, puppeteerInstance, scale, server, timeoutInMilliseconds, logLevel, webpackBundleOrServeUrl, serializedInputPropsWithCustomSchema, serializedResolvedPropsWithCustomSchema, }: import("./render-frames").InternalRenderFramesOptions) => Promise<import("./types").RenderFramesOutput>;
|
package/dist/logger.d.ts
CHANGED
|
@@ -19,6 +19,6 @@ export declare const Log: {
|
|
|
19
19
|
error: (message?: any, ...optionalParams: any[]) => void;
|
|
20
20
|
errorAdvanced: (options: VerboseLogOptions, message?: any, ...optionalParams: any[]) => void;
|
|
21
21
|
};
|
|
22
|
-
export declare const getLogLevel: () => "
|
|
22
|
+
export declare const getLogLevel: () => "verbose" | "info" | "warn" | "error";
|
|
23
23
|
export declare const setLogLevel: (newLogLevel: LogLevel) => void;
|
|
24
24
|
export {};
|
package/dist/render-frames.js
CHANGED
|
@@ -199,9 +199,9 @@ const innerRenderFrames = async ({ onFrameUpdate, outputDir, onStart, serialized
|
|
|
199
199
|
(0, perf_1.stopPerfMeasure)(id);
|
|
200
200
|
const compressedAssets = collectedAssets.map((asset) => (0, compress_assets_1.compressAsset)(assets.filter(truthy_1.truthy).flat(1), asset));
|
|
201
201
|
assets[index] = compressedAssets;
|
|
202
|
-
compressedAssets.forEach((
|
|
202
|
+
compressedAssets.forEach((renderAsset) => {
|
|
203
203
|
(0, download_and_map_assets_to_file_1.downloadAndMapAssetsToFileUrl)({
|
|
204
|
-
|
|
204
|
+
renderAsset,
|
|
205
205
|
onDownload,
|
|
206
206
|
downloadMap,
|
|
207
207
|
}).catch((err) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import type {
|
|
2
|
+
import type { TRenderAsset } from 'remotion';
|
|
3
3
|
import type { DownloadMap } from './assets/download-map';
|
|
4
4
|
import type { Page } from './browser/BrowserPage';
|
|
5
5
|
import type { Compositor } from './compositor/compositor';
|
|
@@ -18,5 +18,5 @@ export declare const takeFrameAndCompose: ({ freePage, imageFormat, jpegQuality,
|
|
|
18
18
|
compositor: Compositor;
|
|
19
19
|
}) => Promise<{
|
|
20
20
|
buffer: Buffer | null;
|
|
21
|
-
collectedAssets:
|
|
21
|
+
collectedAssets: TRenderAsset[];
|
|
22
22
|
}>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remotion/renderer",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.14",
|
|
4
4
|
"description": "Renderer for Remotion",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"extract-zip": "2.0.1",
|
|
19
19
|
"source-map": "^0.8.0-beta.0",
|
|
20
20
|
"ws": "8.7.0",
|
|
21
|
-
"remotion": "4.0.
|
|
21
|
+
"remotion": "4.0.14"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"react": ">=16.8.0",
|
|
@@ -42,13 +42,13 @@
|
|
|
42
42
|
"zod": "^3.21.4"
|
|
43
43
|
},
|
|
44
44
|
"optionalDependencies": {
|
|
45
|
-
"@remotion/compositor-
|
|
46
|
-
"@remotion/compositor-darwin-x64": "4.0.
|
|
47
|
-
"@remotion/compositor-
|
|
48
|
-
"@remotion/compositor-linux-
|
|
49
|
-
"@remotion/compositor-linux-
|
|
50
|
-
"@remotion/compositor-win32-x64-msvc": "4.0.
|
|
51
|
-
"@remotion/compositor-linux-x64-
|
|
45
|
+
"@remotion/compositor-darwin-arm64": "4.0.14",
|
|
46
|
+
"@remotion/compositor-darwin-x64": "4.0.14",
|
|
47
|
+
"@remotion/compositor-linux-arm64-musl": "4.0.14",
|
|
48
|
+
"@remotion/compositor-linux-x64-gnu": "4.0.14",
|
|
49
|
+
"@remotion/compositor-linux-arm64-gnu": "4.0.14",
|
|
50
|
+
"@remotion/compositor-win32-x64-msvc": "4.0.14",
|
|
51
|
+
"@remotion/compositor-linux-x64-musl": "4.0.14"
|
|
52
52
|
},
|
|
53
53
|
"keywords": [
|
|
54
54
|
"remotion",
|