@remotion/studio-server 4.0.477 → 4.0.478
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/codemods/duplicate-effect.d.ts +26 -0
- package/dist/codemods/duplicate-effect.js +92 -0
- package/dist/helpers/resolve-composition-component.js +11 -0
- package/dist/preview-server/api-routes.js +2 -0
- package/dist/preview-server/routes/download-remote-asset.js +7 -2
- package/dist/preview-server/routes/duplicate-effect.d.ts +3 -0
- package/dist/preview-server/routes/duplicate-effect.js +98 -0
- package/dist/preview-server/routes/insert-jsx-element.js +3 -0
- package/dist/preview-server/undo-stack.d.ts +3 -1
- package/package.json +6 -6
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { SequenceNodePath } from 'remotion';
|
|
2
|
+
export type EffectDuplicationTarget = {
|
|
3
|
+
sequenceNodePath: SequenceNodePath;
|
|
4
|
+
effectIndex: number;
|
|
5
|
+
};
|
|
6
|
+
export declare const duplicateEffects: ({ input, effects, prettierConfigOverride, }: {
|
|
7
|
+
input: string;
|
|
8
|
+
effects: EffectDuplicationTarget[];
|
|
9
|
+
prettierConfigOverride?: Record<string, unknown> | null | undefined;
|
|
10
|
+
}) => Promise<{
|
|
11
|
+
output: string;
|
|
12
|
+
formatted: boolean;
|
|
13
|
+
effectLabels: string[];
|
|
14
|
+
logLines: number[];
|
|
15
|
+
}>;
|
|
16
|
+
export declare const duplicateEffect: ({ input, sequenceNodePath, effectIndex, prettierConfigOverride, }: {
|
|
17
|
+
input: string;
|
|
18
|
+
sequenceNodePath: SequenceNodePath;
|
|
19
|
+
effectIndex: number;
|
|
20
|
+
prettierConfigOverride?: Record<string, unknown> | null | undefined;
|
|
21
|
+
}) => Promise<{
|
|
22
|
+
output: string;
|
|
23
|
+
formatted: boolean;
|
|
24
|
+
effectLabel: string;
|
|
25
|
+
logLine: number;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.duplicateEffect = exports.duplicateEffects = void 0;
|
|
4
|
+
const types_1 = require("@babel/types");
|
|
5
|
+
const can_update_sequence_props_1 = require("../preview-server/routes/can-update-sequence-props");
|
|
6
|
+
const format_file_content_1 = require("./format-file-content");
|
|
7
|
+
const parse_ast_1 = require("./parse-ast");
|
|
8
|
+
const update_effect_props_1 = require("./update-effect-props/update-effect-props");
|
|
9
|
+
const getEffectsArray = (attr) => {
|
|
10
|
+
if (!attr.value || attr.value.type !== 'JSXExpressionContainer') {
|
|
11
|
+
throw new Error('Cannot duplicate effect: effects prop is not an array');
|
|
12
|
+
}
|
|
13
|
+
const expr = attr.value.expression;
|
|
14
|
+
if (expr.type !== 'ArrayExpression') {
|
|
15
|
+
throw new Error('Cannot duplicate effect: effects prop is not an array');
|
|
16
|
+
}
|
|
17
|
+
return expr;
|
|
18
|
+
};
|
|
19
|
+
const duplicateEffects = async ({ input, effects, prettierConfigOverride, }) => {
|
|
20
|
+
var _a, _b;
|
|
21
|
+
var _c, _d, _e;
|
|
22
|
+
if (effects.length === 0) {
|
|
23
|
+
throw new Error('No effects were specified for duplication');
|
|
24
|
+
}
|
|
25
|
+
const ast = (0, parse_ast_1.parseAst)(input);
|
|
26
|
+
const duplicationsByAttr = new Map();
|
|
27
|
+
for (const effect of effects) {
|
|
28
|
+
const { sequenceNodePath, effectIndex } = effect;
|
|
29
|
+
const jsx = (0, can_update_sequence_props_1.findJsxElementAtNodePath)(ast, sequenceNodePath);
|
|
30
|
+
if (!jsx) {
|
|
31
|
+
throw new Error('Could not find a JSX element at the specified location to duplicate effect');
|
|
32
|
+
}
|
|
33
|
+
const attr = (0, update_effect_props_1.findEffectsAttr)((_c = jsx.attributes) !== null && _c !== void 0 ? _c : []);
|
|
34
|
+
if (!attr) {
|
|
35
|
+
throw new Error('Could not find effects on the target JSX element');
|
|
36
|
+
}
|
|
37
|
+
const effectsArray = getEffectsArray(attr);
|
|
38
|
+
const existingDuplication = duplicationsByAttr.get(attr);
|
|
39
|
+
const duplication = existingDuplication !== null && existingDuplication !== void 0 ? existingDuplication : {
|
|
40
|
+
effectsArray,
|
|
41
|
+
effectIndices: new Set(),
|
|
42
|
+
effectLabels: [],
|
|
43
|
+
logLines: [],
|
|
44
|
+
};
|
|
45
|
+
duplicationsByAttr.set(attr, duplication);
|
|
46
|
+
if (duplication.effectIndices.has(effectIndex)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const found = (0, update_effect_props_1.findEffectCallExpression)({ attr, effectIndex });
|
|
50
|
+
if (found.kind === 'error') {
|
|
51
|
+
throw new Error(`Cannot duplicate effect: ${found.reason}`);
|
|
52
|
+
}
|
|
53
|
+
duplication.effectIndices.add(effectIndex);
|
|
54
|
+
duplication.effectLabels.push(`${found.callee}()`);
|
|
55
|
+
duplication.logLines.push((_e = (_d = (_a = found.call.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _d !== void 0 ? _d : (_b = jsx.loc) === null || _b === void 0 ? void 0 : _b.start.line) !== null && _e !== void 0 ? _e : 1);
|
|
56
|
+
}
|
|
57
|
+
for (const duplication of duplicationsByAttr.values()) {
|
|
58
|
+
for (const effectIndex of [...duplication.effectIndices].sort((a, b) => b - a)) {
|
|
59
|
+
const effect = duplication.effectsArray.elements[effectIndex];
|
|
60
|
+
if (!effect || effect.type !== 'CallExpression') {
|
|
61
|
+
throw new Error('Cannot duplicate effect: not-call-expression');
|
|
62
|
+
}
|
|
63
|
+
duplication.effectsArray.elements.splice(effectIndex + 1, 0, (0, types_1.cloneNode)(effect, true));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const finalFile = (0, parse_ast_1.serializeAst)(ast);
|
|
67
|
+
const { output, formatted } = await (0, format_file_content_1.formatFileContent)({
|
|
68
|
+
input: finalFile,
|
|
69
|
+
prettierConfigOverride,
|
|
70
|
+
});
|
|
71
|
+
return {
|
|
72
|
+
output,
|
|
73
|
+
formatted,
|
|
74
|
+
effectLabels: [...duplicationsByAttr.values()].flatMap((duplication) => duplication.effectLabels),
|
|
75
|
+
logLines: [...duplicationsByAttr.values()].flatMap((duplication) => duplication.logLines),
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
exports.duplicateEffects = duplicateEffects;
|
|
79
|
+
const duplicateEffect = async ({ input, sequenceNodePath, effectIndex, prettierConfigOverride, }) => {
|
|
80
|
+
const { output, formatted, effectLabels, logLines } = await (0, exports.duplicateEffects)({
|
|
81
|
+
input,
|
|
82
|
+
effects: [{ sequenceNodePath, effectIndex }],
|
|
83
|
+
prettierConfigOverride,
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
output,
|
|
87
|
+
formatted,
|
|
88
|
+
effectLabel: effectLabels[0],
|
|
89
|
+
logLine: logLines[0],
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
exports.duplicateEffect = duplicateEffect;
|
|
@@ -735,6 +735,14 @@ const ensureImgImport = (ast) => {
|
|
|
735
735
|
label: '<Img>',
|
|
736
736
|
});
|
|
737
737
|
};
|
|
738
|
+
const ensureAnimatedImageImport = (ast) => {
|
|
739
|
+
return ensureOfficialNamedImport({
|
|
740
|
+
ast,
|
|
741
|
+
importedName: 'AnimatedImage',
|
|
742
|
+
sourcePath: 'remotion',
|
|
743
|
+
label: '<AnimatedImage>',
|
|
744
|
+
});
|
|
745
|
+
};
|
|
738
746
|
const ensureVideoImport = (ast) => {
|
|
739
747
|
return ensureOfficialNamedImport({
|
|
740
748
|
ast,
|
|
@@ -1045,6 +1053,9 @@ const createInsertableJsxElement = ({ ast, element, }) => {
|
|
|
1045
1053
|
else if (element.assetType === 'gif') {
|
|
1046
1054
|
localName = ensureGifImport(ast);
|
|
1047
1055
|
}
|
|
1056
|
+
else if (element.assetType === 'animated-image') {
|
|
1057
|
+
localName = ensureAnimatedImageImport(ast);
|
|
1058
|
+
}
|
|
1048
1059
|
else if (element.assetType === 'audio') {
|
|
1049
1060
|
localName = ensureAudioImport(ast);
|
|
1050
1061
|
}
|
|
@@ -15,6 +15,7 @@ const delete_jsx_node_1 = require("./routes/delete-jsx-node");
|
|
|
15
15
|
const delete_keyframes_1 = require("./routes/delete-keyframes");
|
|
16
16
|
const delete_static_file_1 = require("./routes/delete-static-file");
|
|
17
17
|
const download_remote_asset_1 = require("./routes/download-remote-asset");
|
|
18
|
+
const duplicate_effect_1 = require("./routes/duplicate-effect");
|
|
18
19
|
const duplicate_jsx_node_1 = require("./routes/duplicate-jsx-node");
|
|
19
20
|
const insert_jsx_element_1 = require("./routes/insert-jsx-element");
|
|
20
21
|
const install_dependency_1 = require("./routes/install-dependency");
|
|
@@ -67,6 +68,7 @@ exports.allApiRoutes = {
|
|
|
67
68
|
'/api/save-effect-props': save_effect_props_1.saveEffectPropsHandler,
|
|
68
69
|
'/api/add-effect': add_effect_1.addEffectHandler,
|
|
69
70
|
'/api/reorder-effect': reorder_effect_1.reorderEffectHandler,
|
|
71
|
+
'/api/duplicate-effect': duplicate_effect_1.duplicateEffectHandler,
|
|
70
72
|
'/api/reorder-sequence': reorder_sequence_1.reorderSequenceHandler,
|
|
71
73
|
'/api/delete-keyframes': delete_keyframes_1.deleteKeyframesHandler,
|
|
72
74
|
'/api/move-keyframes': move_keyframes_1.moveKeyframesHandler,
|
|
@@ -13,9 +13,10 @@ const validate_same_origin_1 = require("../validate-same-origin");
|
|
|
13
13
|
const maxRemoteAssetSize = 50 * 1024 * 1024;
|
|
14
14
|
const remoteAssetDownloadTimeout = 15000;
|
|
15
15
|
const maxRemoteAssetRedirects = 5;
|
|
16
|
-
const remoteAssetAcceptHeader = 'image/png,image/jpeg,image/webp,image/bmp,image/gif';
|
|
16
|
+
const remoteAssetAcceptHeader = 'image/png,image/apng,image/jpeg,image/webp,image/bmp,image/gif';
|
|
17
17
|
const extensionsForFileType = {
|
|
18
18
|
png: ['png'],
|
|
19
|
+
apng: ['png', 'apng'],
|
|
19
20
|
jpeg: ['jpg', 'jpeg'],
|
|
20
21
|
webp: ['webp'],
|
|
21
22
|
bmp: ['bmp'],
|
|
@@ -253,7 +254,11 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
|
|
|
253
254
|
}
|
|
254
255
|
const element = {
|
|
255
256
|
type: 'asset',
|
|
256
|
-
assetType: fileType.type === 'gif'
|
|
257
|
+
assetType: fileType.type === 'gif'
|
|
258
|
+
? 'gif'
|
|
259
|
+
: fileType.type === 'apng'
|
|
260
|
+
? 'animated-image'
|
|
261
|
+
: 'image',
|
|
257
262
|
src: assetPath,
|
|
258
263
|
srcType: 'static',
|
|
259
264
|
dimensions: fileType.dimensions,
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.duplicateEffectHandler = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const renderer_1 = require("@remotion/renderer");
|
|
6
|
+
const duplicate_effect_1 = require("../../codemods/duplicate-effect");
|
|
7
|
+
const file_watcher_1 = require("../../file-watcher");
|
|
8
|
+
const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
|
|
9
|
+
const format_log_file_location_1 = require("../format-log-file-location");
|
|
10
|
+
const undo_stack_1 = require("../undo-stack");
|
|
11
|
+
const formatting_1 = require("./log-updates/formatting");
|
|
12
|
+
const log_update_1 = require("./log-updates/log-update");
|
|
13
|
+
const getDuplicatedEffectDescription = (effectLabels) => {
|
|
14
|
+
if (effectLabels.length === 1) {
|
|
15
|
+
return effectLabels[0];
|
|
16
|
+
}
|
|
17
|
+
return `${effectLabels.length} effects`;
|
|
18
|
+
};
|
|
19
|
+
const duplicateEffectHandler = async ({ input: effects, remotionRoot, logLevel }) => {
|
|
20
|
+
var _a;
|
|
21
|
+
try {
|
|
22
|
+
if (effects.length === 0) {
|
|
23
|
+
throw new Error('No effects were specified for duplication');
|
|
24
|
+
}
|
|
25
|
+
renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[duplicate-effect] Received request to duplicate ${effects.length} effect target${effects.length === 1 ? '' : 's'}`);
|
|
26
|
+
const itemsByFileName = new Map();
|
|
27
|
+
for (const item of effects) {
|
|
28
|
+
const fileItems = (_a = itemsByFileName.get(item.fileName)) !== null && _a !== void 0 ? _a : [];
|
|
29
|
+
fileItems.push(item);
|
|
30
|
+
itemsByFileName.set(item.fileName, fileItems);
|
|
31
|
+
}
|
|
32
|
+
const updates = await Promise.all([...itemsByFileName.entries()].map(async ([fileName, fileItems]) => {
|
|
33
|
+
const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
|
|
34
|
+
remotionRoot,
|
|
35
|
+
fileName,
|
|
36
|
+
action: 'modify',
|
|
37
|
+
});
|
|
38
|
+
const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
|
|
39
|
+
const { output, formatted, effectLabels, logLines } = await (0, duplicate_effect_1.duplicateEffects)({
|
|
40
|
+
input: fileContents,
|
|
41
|
+
effects: fileItems.map((item) => ({
|
|
42
|
+
sequenceNodePath: item.sequenceNodePath.nodePath,
|
|
43
|
+
effectIndex: item.effectIndex,
|
|
44
|
+
})),
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
absolutePath,
|
|
48
|
+
fileRelativeToRoot,
|
|
49
|
+
fileContents,
|
|
50
|
+
output,
|
|
51
|
+
formatted,
|
|
52
|
+
effectLabels,
|
|
53
|
+
logLine: Math.min(...logLines),
|
|
54
|
+
};
|
|
55
|
+
}));
|
|
56
|
+
for (const update of updates) {
|
|
57
|
+
const duplicatedEffectDescription = getDuplicatedEffectDescription(update.effectLabels);
|
|
58
|
+
(0, undo_stack_1.pushToUndoStack)({
|
|
59
|
+
filePath: update.absolutePath,
|
|
60
|
+
oldContents: update.fileContents,
|
|
61
|
+
newContents: null,
|
|
62
|
+
logLevel,
|
|
63
|
+
remotionRoot,
|
|
64
|
+
logLine: update.logLine,
|
|
65
|
+
description: {
|
|
66
|
+
undoMessage: `↩️ Duplication of ${duplicatedEffectDescription}`,
|
|
67
|
+
redoMessage: `↪️ Duplication of ${duplicatedEffectDescription}`,
|
|
68
|
+
},
|
|
69
|
+
entryType: 'duplicate-effect',
|
|
70
|
+
suppressHmrOnFileRestore: false,
|
|
71
|
+
});
|
|
72
|
+
(0, undo_stack_1.suppressUndoStackInvalidation)(update.absolutePath);
|
|
73
|
+
(0, file_watcher_1.writeFileAndNotifyFileWatchers)(update.absolutePath, update.output, undefined);
|
|
74
|
+
const locationLabel = (0, format_log_file_location_1.formatLogFileLocation)({
|
|
75
|
+
remotionRoot,
|
|
76
|
+
absolutePath: update.absolutePath,
|
|
77
|
+
line: update.logLine,
|
|
78
|
+
});
|
|
79
|
+
renderer_1.RenderInternals.Log.info({ indent: false, logLevel }, `${renderer_1.RenderInternals.chalk.blueBright(`${locationLabel}`)} Duplicated ${(0, formatting_1.attrName)(duplicatedEffectDescription)}`);
|
|
80
|
+
if (!update.formatted) {
|
|
81
|
+
(0, log_update_1.warnAboutPrettierOnce)(logLevel);
|
|
82
|
+
}
|
|
83
|
+
renderer_1.RenderInternals.Log.verbose({ indent: false, logLevel }, `[duplicate-effect] Wrote ${update.fileRelativeToRoot}${update.formatted ? ' (formatted)' : ''}`);
|
|
84
|
+
}
|
|
85
|
+
(0, undo_stack_1.printUndoHint)(logLevel);
|
|
86
|
+
return {
|
|
87
|
+
success: true,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
reason: err.message,
|
|
94
|
+
stack: err.stack,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
exports.duplicateEffectHandler = duplicateEffectHandler;
|
|
@@ -3,7 +3,7 @@ export interface UndoEntryDescription {
|
|
|
3
3
|
undoMessage: string;
|
|
4
4
|
redoMessage: string;
|
|
5
5
|
}
|
|
6
|
-
type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'duplicate-composition' | 'delete-folder' | 'rename-folder';
|
|
6
|
+
type UndoEntryType = 'visual-control' | 'default-props' | 'sequence-props' | 'effect-props' | 'keyframe-add' | 'keyframe-delete' | 'add-effect' | 'delete-effect' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'duplicate-composition' | 'delete-folder' | 'rename-folder';
|
|
7
7
|
type UndoEntrySnapshot = {
|
|
8
8
|
filePath: string;
|
|
9
9
|
oldContents: string;
|
|
@@ -35,6 +35,8 @@ type UndoEntry = {
|
|
|
35
35
|
entryType: 'add-effect';
|
|
36
36
|
} | {
|
|
37
37
|
entryType: 'delete-effect';
|
|
38
|
+
} | {
|
|
39
|
+
entryType: 'duplicate-effect';
|
|
38
40
|
} | {
|
|
39
41
|
entryType: 'paste-effects';
|
|
40
42
|
} | {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"url": "https://github.com/remotion-dev/remotion/tree/main/packages/studio-server"
|
|
4
4
|
},
|
|
5
5
|
"name": "@remotion/studio-server",
|
|
6
|
-
"version": "4.0.
|
|
6
|
+
"version": "4.0.478",
|
|
7
7
|
"description": "Run a Remotion Studio with a server backend",
|
|
8
8
|
"main": "dist",
|
|
9
9
|
"scripts": {
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@babel/parser": "7.24.1",
|
|
28
28
|
"semver": "7.5.3",
|
|
29
29
|
"prettier": "3.8.1",
|
|
30
|
-
"remotion": "4.0.
|
|
30
|
+
"remotion": "4.0.478",
|
|
31
31
|
"recast": "0.23.11",
|
|
32
|
-
"@remotion/bundler": "4.0.
|
|
33
|
-
"@remotion/renderer": "4.0.
|
|
34
|
-
"@remotion/studio-shared": "4.0.
|
|
32
|
+
"@remotion/bundler": "4.0.478",
|
|
33
|
+
"@remotion/renderer": "4.0.478",
|
|
34
|
+
"@remotion/studio-shared": "4.0.478",
|
|
35
35
|
"memfs": "3.4.3",
|
|
36
36
|
"open": "8.4.2"
|
|
37
37
|
},
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ast-types": "0.16.1",
|
|
40
40
|
"react": "19.2.3",
|
|
41
41
|
"@types/semver": "7.5.3",
|
|
42
|
-
"@remotion/eslint-config-internal": "4.0.
|
|
42
|
+
"@remotion/eslint-config-internal": "4.0.478",
|
|
43
43
|
"eslint": "9.19.0",
|
|
44
44
|
"@types/node": "20.12.14",
|
|
45
45
|
"@typescript/native-preview": "7.0.0-dev.20260217.1"
|