@remotion/studio-server 4.0.471 → 4.0.473
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/add-effect.d.ts +11 -0
- package/dist/codemods/add-effect.js +14 -52
- package/dist/codemods/effect-param-expression.d.ts +15 -0
- package/dist/codemods/effect-param-expression.js +131 -0
- package/dist/codemods/format-file-content.js +1 -1
- package/dist/codemods/parse-ast.js +4 -1
- package/dist/codemods/paste-effects.d.ts +15 -0
- package/dist/codemods/paste-effects.js +147 -0
- package/dist/codemods/recast-mods.js +178 -31
- package/dist/codemods/reorder-sequence.d.ts +14 -0
- package/dist/codemods/reorder-sequence.js +109 -0
- package/dist/codemods/update-effect-props/update-effect-props.d.ts +7 -0
- package/dist/codemods/update-effect-props/update-effect-props.js +64 -16
- package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.d.ts +1 -1
- package/dist/codemods/update-keyframes/ensure-imports-and-frame-hook.js +7 -55
- package/dist/codemods/update-keyframes/update-keyframes.d.ts +24 -6
- package/dist/codemods/update-keyframes/update-keyframes.js +279 -16
- package/dist/helpers/get-ast-node-path.js +6 -1
- package/dist/helpers/import-agnostic-node-path.d.ts +10 -0
- package/dist/helpers/import-agnostic-node-path.js +154 -0
- package/dist/helpers/imports.d.ts +16 -0
- package/dist/helpers/imports.js +145 -0
- package/dist/helpers/resolve-composition-component.js +133 -51
- package/dist/preview-server/api-routes.js +16 -0
- package/dist/preview-server/routes/add-effect-keyframe.js +2 -0
- package/dist/preview-server/routes/add-sequence-keyframe.js +1 -0
- package/dist/preview-server/routes/apply-codemod.js +18 -0
- package/dist/preview-server/routes/can-update-effect-props.d.ts +3 -2
- package/dist/preview-server/routes/can-update-effect-props.js +78 -8
- package/dist/preview-server/routes/can-update-sequence-props.d.ts +1 -1
- package/dist/preview-server/routes/can-update-sequence-props.js +82 -43
- package/dist/preview-server/routes/delete-keyframes.js +1 -0
- package/dist/preview-server/routes/download-remote-asset.d.ts +7 -0
- package/dist/preview-server/routes/download-remote-asset.js +267 -0
- package/dist/preview-server/routes/insert-jsx-element.js +26 -0
- package/dist/preview-server/routes/log-studio-error.d.ts +3 -0
- package/dist/preview-server/routes/log-studio-error.js +58 -0
- package/dist/preview-server/routes/move-keyframes.d.ts +7 -0
- package/dist/preview-server/routes/move-keyframes.js +242 -0
- package/dist/preview-server/routes/paste-effects.d.ts +3 -0
- package/dist/preview-server/routes/paste-effects.js +78 -0
- package/dist/preview-server/routes/rename-static-file.d.ts +3 -0
- package/dist/preview-server/routes/rename-static-file.js +53 -0
- package/dist/preview-server/routes/reorder-sequence.d.ts +3 -0
- package/dist/preview-server/routes/reorder-sequence.js +67 -0
- package/dist/preview-server/routes/save-effect-props.js +25 -9
- package/dist/preview-server/routes/save-sequence-props.js +2 -34
- package/dist/preview-server/routes/update-effect-keyframe-settings.d.ts +3 -0
- package/dist/preview-server/routes/update-effect-keyframe-settings.js +90 -0
- package/dist/preview-server/routes/update-sequence-keyframe-settings.d.ts +3 -0
- package/dist/preview-server/routes/update-sequence-keyframe-settings.js +85 -0
- package/dist/preview-server/undo-stack.d.ts +9 -1
- package/package.json +6 -6
|
@@ -0,0 +1,267 @@
|
|
|
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.downloadRemoteAssetHandler = exports.getRemoteAssetFilename = void 0;
|
|
7
|
+
const promises_1 = require("node:dns/promises");
|
|
8
|
+
const node_fs_1 = require("node:fs");
|
|
9
|
+
const node_net_1 = require("node:net");
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const studio_shared_1 = require("@remotion/studio-shared");
|
|
12
|
+
const validate_same_origin_1 = require("../validate-same-origin");
|
|
13
|
+
const maxRemoteAssetSize = 50 * 1024 * 1024;
|
|
14
|
+
const remoteAssetDownloadTimeout = 15000;
|
|
15
|
+
const maxRemoteAssetRedirects = 5;
|
|
16
|
+
const remoteAssetAcceptHeader = 'image/png,image/jpeg,image/webp,image/bmp,image/gif';
|
|
17
|
+
const extensionsForFileType = {
|
|
18
|
+
png: ['png'],
|
|
19
|
+
jpeg: ['jpg', 'jpeg'],
|
|
20
|
+
webp: ['webp'],
|
|
21
|
+
bmp: ['bmp'],
|
|
22
|
+
gif: ['gif'],
|
|
23
|
+
};
|
|
24
|
+
const safeDecodeURIComponent = (value) => {
|
|
25
|
+
try {
|
|
26
|
+
return decodeURIComponent(value);
|
|
27
|
+
}
|
|
28
|
+
catch (_a) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const sanitizeAssetFilename = (filename) => {
|
|
33
|
+
return Array.from(filename)
|
|
34
|
+
.map((character) => {
|
|
35
|
+
const charCode = character.charCodeAt(0);
|
|
36
|
+
return charCode <= 31 || '<>:"/\\|?*'.includes(character)
|
|
37
|
+
? '-'
|
|
38
|
+
: character;
|
|
39
|
+
})
|
|
40
|
+
.join('')
|
|
41
|
+
.trim()
|
|
42
|
+
.replace(/^[. ]+|[. ]+$/g, '');
|
|
43
|
+
};
|
|
44
|
+
const getRemoteAssetFilename = ({ fileType, url, }) => {
|
|
45
|
+
const basename = safeDecodeURIComponent(node_path_1.default.posix.basename(url.pathname));
|
|
46
|
+
const sanitized = sanitizeAssetFilename(basename);
|
|
47
|
+
const filenameWithoutFallback = sanitized === '' ? 'image' : sanitized;
|
|
48
|
+
const extensions = extensionsForFileType[fileType.type];
|
|
49
|
+
const extension = node_path_1.default
|
|
50
|
+
.extname(filenameWithoutFallback)
|
|
51
|
+
.slice(1)
|
|
52
|
+
.toLowerCase();
|
|
53
|
+
if (extensions.includes(extension)) {
|
|
54
|
+
return filenameWithoutFallback;
|
|
55
|
+
}
|
|
56
|
+
const withoutExtension = extension
|
|
57
|
+
? filenameWithoutFallback.slice(0, -(extension.length + 1))
|
|
58
|
+
: filenameWithoutFallback;
|
|
59
|
+
const safeName = withoutExtension === '' ? 'image' : withoutExtension;
|
|
60
|
+
return `${safeName}.${extensions[0]}`;
|
|
61
|
+
};
|
|
62
|
+
exports.getRemoteAssetFilename = getRemoteAssetFilename;
|
|
63
|
+
const isForbiddenIpv4Address = (address) => {
|
|
64
|
+
const parts = address.split('.').map((part) => Number(part));
|
|
65
|
+
if (parts.length !== 4 ||
|
|
66
|
+
parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
const [first, second] = parts;
|
|
70
|
+
return (first === 0 ||
|
|
71
|
+
first === 10 ||
|
|
72
|
+
first === 127 ||
|
|
73
|
+
(first === 100 && second >= 64 && second <= 127) ||
|
|
74
|
+
(first === 169 && second === 254) ||
|
|
75
|
+
(first === 172 && second >= 16 && second <= 31) ||
|
|
76
|
+
(first === 192 && second === 168) ||
|
|
77
|
+
(first === 198 && (second === 18 || second === 19)) ||
|
|
78
|
+
first >= 224);
|
|
79
|
+
};
|
|
80
|
+
const isForbiddenIpv6Address = (address) => {
|
|
81
|
+
var _a;
|
|
82
|
+
const normalized = address.toLowerCase();
|
|
83
|
+
const mappedIpv4 = (_a = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/)) === null || _a === void 0 ? void 0 : _a[1];
|
|
84
|
+
if (mappedIpv4) {
|
|
85
|
+
return isForbiddenIpv4Address(mappedIpv4);
|
|
86
|
+
}
|
|
87
|
+
return (normalized === '::' ||
|
|
88
|
+
normalized === '::1' ||
|
|
89
|
+
normalized.startsWith('fc') ||
|
|
90
|
+
normalized.startsWith('fd') ||
|
|
91
|
+
normalized.startsWith('fe8') ||
|
|
92
|
+
normalized.startsWith('fe9') ||
|
|
93
|
+
normalized.startsWith('fea') ||
|
|
94
|
+
normalized.startsWith('feb'));
|
|
95
|
+
};
|
|
96
|
+
const ensureRemoteUrlIsAllowed = async (url) => {
|
|
97
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
98
|
+
throw new Error('Only HTTP(S) URLs can be imported');
|
|
99
|
+
}
|
|
100
|
+
if (url.username !== '' || url.password !== '') {
|
|
101
|
+
throw new Error('Remote asset URLs cannot include credentials');
|
|
102
|
+
}
|
|
103
|
+
if (url.hostname === 'localhost' || url.hostname.endsWith('.localhost')) {
|
|
104
|
+
throw new Error('Localhost URLs cannot be imported');
|
|
105
|
+
}
|
|
106
|
+
const hostnameAsIp = (0, node_net_1.isIP)(url.hostname);
|
|
107
|
+
if (hostnameAsIp === 4 && isForbiddenIpv4Address(url.hostname)) {
|
|
108
|
+
throw new Error('Private IP addresses cannot be imported');
|
|
109
|
+
}
|
|
110
|
+
if (hostnameAsIp === 6 && isForbiddenIpv6Address(url.hostname)) {
|
|
111
|
+
throw new Error('Private IP addresses cannot be imported');
|
|
112
|
+
}
|
|
113
|
+
const addresses = hostnameAsIp
|
|
114
|
+
? [{ address: url.hostname, family: hostnameAsIp }]
|
|
115
|
+
: await (0, promises_1.lookup)(url.hostname, { all: true });
|
|
116
|
+
if (addresses.length === 0) {
|
|
117
|
+
throw new Error(`Could not resolve ${url.hostname}`);
|
|
118
|
+
}
|
|
119
|
+
for (const address of addresses) {
|
|
120
|
+
if ((address.family === 4 && isForbiddenIpv4Address(address.address)) ||
|
|
121
|
+
(address.family === 6 && isForbiddenIpv6Address(address.address))) {
|
|
122
|
+
throw new Error('Private IP addresses cannot be imported');
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const fetchRemoteAsset = async ({ signal, url, }) => {
|
|
127
|
+
var _a;
|
|
128
|
+
let currentUrl = url;
|
|
129
|
+
for (let redirects = 0; redirects <= maxRemoteAssetRedirects; redirects++) {
|
|
130
|
+
await ensureRemoteUrlIsAllowed(currentUrl);
|
|
131
|
+
const response = await fetch(currentUrl, {
|
|
132
|
+
headers: {
|
|
133
|
+
accept: remoteAssetAcceptHeader,
|
|
134
|
+
},
|
|
135
|
+
redirect: 'manual',
|
|
136
|
+
signal,
|
|
137
|
+
});
|
|
138
|
+
if (response.status < 300 || response.status >= 400) {
|
|
139
|
+
return response;
|
|
140
|
+
}
|
|
141
|
+
const location = response.headers.get('location');
|
|
142
|
+
if (location === null) {
|
|
143
|
+
throw new Error('Remote asset redirect is missing a Location header');
|
|
144
|
+
}
|
|
145
|
+
if (redirects === maxRemoteAssetRedirects) {
|
|
146
|
+
throw new Error('Remote asset redirected too many times');
|
|
147
|
+
}
|
|
148
|
+
await ((_a = response.body) === null || _a === void 0 ? void 0 : _a.cancel());
|
|
149
|
+
currentUrl = new URL(location, currentUrl);
|
|
150
|
+
}
|
|
151
|
+
throw new Error('Remote asset redirected too many times');
|
|
152
|
+
};
|
|
153
|
+
const readRemoteAsset = async ({ response, abort, }) => {
|
|
154
|
+
const contentLength = response.headers.get('content-length');
|
|
155
|
+
if (contentLength !== null && Number(contentLength) > maxRemoteAssetSize) {
|
|
156
|
+
abort();
|
|
157
|
+
throw new Error('Remote asset exceeds the 50MB size limit');
|
|
158
|
+
}
|
|
159
|
+
if (!response.body) {
|
|
160
|
+
const buffer = await response.arrayBuffer();
|
|
161
|
+
if (buffer.byteLength > maxRemoteAssetSize) {
|
|
162
|
+
throw new Error('Remote asset exceeds the 50MB size limit');
|
|
163
|
+
}
|
|
164
|
+
return new Uint8Array(buffer);
|
|
165
|
+
}
|
|
166
|
+
const reader = response.body.getReader();
|
|
167
|
+
const chunks = [];
|
|
168
|
+
let size = 0;
|
|
169
|
+
while (true) {
|
|
170
|
+
const { done, value } = await reader.read();
|
|
171
|
+
if (done) {
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
if (!value) {
|
|
175
|
+
continue;
|
|
176
|
+
}
|
|
177
|
+
size += value.byteLength;
|
|
178
|
+
if (size > maxRemoteAssetSize) {
|
|
179
|
+
abort();
|
|
180
|
+
await reader.cancel();
|
|
181
|
+
throw new Error('Remote asset exceeds the 50MB size limit');
|
|
182
|
+
}
|
|
183
|
+
chunks.push(value);
|
|
184
|
+
}
|
|
185
|
+
const result = new Uint8Array(size);
|
|
186
|
+
let offset = 0;
|
|
187
|
+
for (const chunk of chunks) {
|
|
188
|
+
result.set(chunk, offset);
|
|
189
|
+
offset += chunk.byteLength;
|
|
190
|
+
}
|
|
191
|
+
return result;
|
|
192
|
+
};
|
|
193
|
+
const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
|
|
194
|
+
(0, validate_same_origin_1.validateSameOrigin)(request);
|
|
195
|
+
if (typeof input.url !== 'string') {
|
|
196
|
+
throw new Error('No `url` provided');
|
|
197
|
+
}
|
|
198
|
+
if (typeof fetch !== 'function') {
|
|
199
|
+
throw new Error('Downloading remote assets requires Node.js 18 or newer. Drag a local file instead.');
|
|
200
|
+
}
|
|
201
|
+
const url = new URL(input.url);
|
|
202
|
+
const controller = new AbortController();
|
|
203
|
+
const timeout = setTimeout(() => {
|
|
204
|
+
controller.abort();
|
|
205
|
+
}, remoteAssetDownloadTimeout);
|
|
206
|
+
let contents;
|
|
207
|
+
try {
|
|
208
|
+
const response = await fetchRemoteAsset({
|
|
209
|
+
signal: controller.signal,
|
|
210
|
+
url,
|
|
211
|
+
});
|
|
212
|
+
if (!response.ok) {
|
|
213
|
+
throw new Error(`Could not download remote asset: ${response.status}`);
|
|
214
|
+
}
|
|
215
|
+
contents = await readRemoteAsset({
|
|
216
|
+
response,
|
|
217
|
+
abort: () => controller.abort(),
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
catch (err) {
|
|
221
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
222
|
+
throw new Error('Timed out downloading remote asset');
|
|
223
|
+
}
|
|
224
|
+
throw err;
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
clearTimeout(timeout);
|
|
228
|
+
}
|
|
229
|
+
const fileType = (0, studio_shared_1.detectFileType)(contents);
|
|
230
|
+
if (!(0, studio_shared_1.isImageFileType)(fileType)) {
|
|
231
|
+
throw new Error('Remote asset is not a supported image');
|
|
232
|
+
}
|
|
233
|
+
const assetPath = (0, exports.getRemoteAssetFilename)({ fileType, url });
|
|
234
|
+
const absolutePath = node_path_1.default.join(publicDir, assetPath);
|
|
235
|
+
const relativeToPublicDir = node_path_1.default.relative(publicDir, absolutePath);
|
|
236
|
+
if (relativeToPublicDir.startsWith('..') ||
|
|
237
|
+
node_path_1.default.isAbsolute(relativeToPublicDir)) {
|
|
238
|
+
throw new Error(`Not allowed to write to ${relativeToPublicDir}`);
|
|
239
|
+
}
|
|
240
|
+
const exists = (0, node_fs_1.existsSync)(absolutePath);
|
|
241
|
+
if (exists) {
|
|
242
|
+
const stat = (0, node_fs_1.statSync)(absolutePath);
|
|
243
|
+
if (!stat.isFile()) {
|
|
244
|
+
throw new Error(`${assetPath} already exists and is not a file`);
|
|
245
|
+
}
|
|
246
|
+
if (stat.size !== contents.byteLength) {
|
|
247
|
+
throw new Error(`File with name ${assetPath} already exists and is different`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
(0, node_fs_1.mkdirSync)(node_path_1.default.dirname(absolutePath), { recursive: true });
|
|
252
|
+
(0, node_fs_1.writeFileSync)(absolutePath, contents);
|
|
253
|
+
}
|
|
254
|
+
const element = {
|
|
255
|
+
type: 'asset',
|
|
256
|
+
assetType: fileType.type === 'gif' ? 'gif' : 'image',
|
|
257
|
+
src: assetPath,
|
|
258
|
+
dimensions: fileType.dimensions,
|
|
259
|
+
};
|
|
260
|
+
return {
|
|
261
|
+
assetPath,
|
|
262
|
+
sizeInBytes: contents.byteLength,
|
|
263
|
+
created: !exists,
|
|
264
|
+
element,
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
exports.downloadRemoteAssetHandler = downloadRemoteAssetHandler;
|
|
@@ -17,12 +17,38 @@ const validateElement = (element) => {
|
|
|
17
17
|
if (element.type === 'solid') {
|
|
18
18
|
validateDimension('width', element.width);
|
|
19
19
|
validateDimension('height', element.height);
|
|
20
|
+
return;
|
|
20
21
|
}
|
|
22
|
+
if (element.type === 'asset') {
|
|
23
|
+
if (!element.src || element.src.includes('\\')) {
|
|
24
|
+
throw new Error('Asset path must be a static file path');
|
|
25
|
+
}
|
|
26
|
+
if (element.dimensions) {
|
|
27
|
+
validateDimension('width', element.dimensions.width);
|
|
28
|
+
validateDimension('height', element.dimensions.height);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
throw new Error('Unsupported element type');
|
|
21
33
|
};
|
|
22
34
|
const getElementLabel = (element) => {
|
|
23
35
|
if (element.type === 'solid') {
|
|
24
36
|
return '<Solid>';
|
|
25
37
|
}
|
|
38
|
+
if (element.type === 'asset') {
|
|
39
|
+
if (element.assetType === 'image') {
|
|
40
|
+
return '<Img>';
|
|
41
|
+
}
|
|
42
|
+
if (element.assetType === 'video') {
|
|
43
|
+
return '<Video>';
|
|
44
|
+
}
|
|
45
|
+
if (element.assetType === 'gif') {
|
|
46
|
+
return '<Gif>';
|
|
47
|
+
}
|
|
48
|
+
if (element.assetType === 'audio') {
|
|
49
|
+
return '<Audio>';
|
|
50
|
+
}
|
|
51
|
+
}
|
|
26
52
|
throw new Error('Unsupported element type');
|
|
27
53
|
};
|
|
28
54
|
const insertJsxElementHandler = ({ input: { compositionFile, compositionId, element }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.logStudioErrorHandler = void 0;
|
|
4
|
+
const renderer_1 = require("@remotion/renderer");
|
|
5
|
+
const { chalk, isColorSupported } = renderer_1.RenderInternals;
|
|
6
|
+
const coerceString = (value) => {
|
|
7
|
+
return typeof value === 'string' && value.length > 0 ? value : null;
|
|
8
|
+
};
|
|
9
|
+
const formatFrame = (frame) => {
|
|
10
|
+
const codeFrame = renderer_1.RenderInternals.formatStackFrameCodeFrame(frame);
|
|
11
|
+
if (codeFrame !== null) {
|
|
12
|
+
const fileName = renderer_1.RenderInternals.makeStackFrameFileName(frame);
|
|
13
|
+
const header = fileName ? `at ${chalk.underline(fileName)}` : 'at';
|
|
14
|
+
return `${header}\n${codeFrame}`;
|
|
15
|
+
}
|
|
16
|
+
return renderer_1.RenderInternals.formatStackFrameLocationLine(frame);
|
|
17
|
+
};
|
|
18
|
+
const formatSymbolicatedStack = (name, message, frames) => {
|
|
19
|
+
const label = name !== null && name !== void 0 ? name : 'Error';
|
|
20
|
+
const firstLine = isColorSupported()
|
|
21
|
+
? `${chalk.bgRed(chalk.white(` ${label} `))} ${message}`
|
|
22
|
+
: `${label}: ${message}`;
|
|
23
|
+
const lines = [firstLine];
|
|
24
|
+
for (const frame of frames) {
|
|
25
|
+
const formatted = formatFrame(frame);
|
|
26
|
+
if (formatted === null) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
lines.push('');
|
|
30
|
+
lines.push(formatted);
|
|
31
|
+
}
|
|
32
|
+
return lines.join('\n');
|
|
33
|
+
};
|
|
34
|
+
const logStudioErrorHandler = ({ input, logLevel }) => {
|
|
35
|
+
var _a;
|
|
36
|
+
const name = coerceString(input.name);
|
|
37
|
+
const message = (_a = coerceString(input.message)) !== null && _a !== void 0 ? _a : 'Unknown Studio error';
|
|
38
|
+
const symbolicatedStackFrames = input.symbolicatedStackFrames && input.symbolicatedStackFrames.length > 0
|
|
39
|
+
? input.symbolicatedStackFrames
|
|
40
|
+
: null;
|
|
41
|
+
let output;
|
|
42
|
+
if (symbolicatedStackFrames) {
|
|
43
|
+
output = formatSymbolicatedStack(name, message, symbolicatedStackFrames);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const stack = coerceString(input.stack);
|
|
47
|
+
const headline = name ? `${name}: ${message}` : message;
|
|
48
|
+
output = stack
|
|
49
|
+
? stack.startsWith(headline)
|
|
50
|
+
? stack
|
|
51
|
+
: `${headline}\n${stack}`
|
|
52
|
+
: headline;
|
|
53
|
+
}
|
|
54
|
+
renderer_1.RenderInternals.Log.error({ indent: false, logLevel }, chalk.red('An error occurred in the Studio:'));
|
|
55
|
+
renderer_1.RenderInternals.Log.error({ indent: false, logLevel }, output);
|
|
56
|
+
return Promise.resolve({});
|
|
57
|
+
};
|
|
58
|
+
exports.logStudioErrorHandler = logStudioErrorHandler;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MoveKeyframesRequest, MoveKeyframesResponse } from '@remotion/studio-shared';
|
|
2
|
+
import type { ApiHandler } from '../api-types';
|
|
3
|
+
export declare const moveKeyframes: ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }: MoveKeyframesRequest & {
|
|
4
|
+
remotionRoot: string;
|
|
5
|
+
logLevel: "error" | "info" | "trace" | "verbose" | "warn";
|
|
6
|
+
}) => Promise<void>;
|
|
7
|
+
export declare const moveKeyframesHandler: ApiHandler<MoveKeyframesRequest, MoveKeyframesResponse>;
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.moveKeyframesHandler = exports.moveKeyframes = void 0;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const renderer_1 = require("@remotion/renderer");
|
|
6
|
+
const update_keyframes_1 = require("../../codemods/update-keyframes/update-keyframes");
|
|
7
|
+
const file_watcher_1 = require("../../file-watcher");
|
|
8
|
+
const resolve_file_inside_project_1 = require("../../helpers/resolve-file-inside-project");
|
|
9
|
+
const undo_stack_1 = require("../undo-stack");
|
|
10
|
+
const watch_ignore_next_change_1 = require("../watch-ignore-next-change");
|
|
11
|
+
const log_effect_update_1 = require("./log-updates/log-effect-update");
|
|
12
|
+
const log_update_1 = require("./log-updates/log-update");
|
|
13
|
+
const save_props_mutex_1 = require("./save-props-mutex");
|
|
14
|
+
const groupBy = (items, getKey) => {
|
|
15
|
+
var _a;
|
|
16
|
+
const groups = new Map();
|
|
17
|
+
for (const item of items) {
|
|
18
|
+
const key = getKey(item);
|
|
19
|
+
const group = (_a = groups.get(key)) !== null && _a !== void 0 ? _a : [];
|
|
20
|
+
group.push(item);
|
|
21
|
+
groups.set(key, group);
|
|
22
|
+
}
|
|
23
|
+
return [...groups.values()];
|
|
24
|
+
};
|
|
25
|
+
const getBatchDescription = ({ totalKeyframes, firstKeyframe, }) => {
|
|
26
|
+
if (totalKeyframes === 1) {
|
|
27
|
+
return {
|
|
28
|
+
undoMessage: `↩️ ${firstKeyframe.key} keyframe moved back to frame ${firstKeyframe.fromFrame}`,
|
|
29
|
+
redoMessage: `↪️ ${firstKeyframe.key} keyframe moved to frame ${firstKeyframe.toFrame}`,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
undoMessage: `↩️ ${totalKeyframes} keyframes moved back`,
|
|
34
|
+
redoMessage: `↪️ ${totalKeyframes} keyframes moved`,
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
const moveKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, remotionRoot, logLevel, }) => {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
const totalKeyframes = sequenceKeyframes.length + effectKeyframes.length;
|
|
40
|
+
if (totalKeyframes === 0) {
|
|
41
|
+
throw new Error('No keyframes were specified for moving');
|
|
42
|
+
}
|
|
43
|
+
const fileGroups = new Map();
|
|
44
|
+
for (const [index, keyframe] of sequenceKeyframes.entries()) {
|
|
45
|
+
const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
|
|
46
|
+
remotionRoot,
|
|
47
|
+
fileName: keyframe.fileName,
|
|
48
|
+
action: 'modify',
|
|
49
|
+
});
|
|
50
|
+
const group = (_a = fileGroups.get(absolutePath)) !== null && _a !== void 0 ? _a : {
|
|
51
|
+
fileRelativeToRoot,
|
|
52
|
+
sequenceKeyframes: [],
|
|
53
|
+
effectKeyframes: [],
|
|
54
|
+
};
|
|
55
|
+
group.sequenceKeyframes.push({
|
|
56
|
+
...keyframe,
|
|
57
|
+
index,
|
|
58
|
+
absolutePath,
|
|
59
|
+
fileRelativeToRoot,
|
|
60
|
+
});
|
|
61
|
+
fileGroups.set(absolutePath, group);
|
|
62
|
+
}
|
|
63
|
+
for (const [index, keyframe] of effectKeyframes.entries()) {
|
|
64
|
+
const { absolutePath, fileRelativeToRoot } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
|
|
65
|
+
remotionRoot,
|
|
66
|
+
fileName: keyframe.fileName,
|
|
67
|
+
action: 'modify',
|
|
68
|
+
});
|
|
69
|
+
const group = (_b = fileGroups.get(absolutePath)) !== null && _b !== void 0 ? _b : {
|
|
70
|
+
fileRelativeToRoot,
|
|
71
|
+
sequenceKeyframes: [],
|
|
72
|
+
effectKeyframes: [],
|
|
73
|
+
};
|
|
74
|
+
group.effectKeyframes.push({
|
|
75
|
+
...keyframe,
|
|
76
|
+
index,
|
|
77
|
+
absolutePath,
|
|
78
|
+
fileRelativeToRoot,
|
|
79
|
+
});
|
|
80
|
+
fileGroups.set(absolutePath, group);
|
|
81
|
+
}
|
|
82
|
+
const snapshots = [];
|
|
83
|
+
const sequenceLogs = [];
|
|
84
|
+
const effectLogs = [];
|
|
85
|
+
for (const [absolutePath, group] of fileGroups) {
|
|
86
|
+
const fileContents = (0, node_fs_1.readFileSync)(absolutePath, 'utf-8');
|
|
87
|
+
let output = fileContents;
|
|
88
|
+
let firstLogLine = Number.POSITIVE_INFINITY;
|
|
89
|
+
for (const keyframeGroup of groupBy(group.sequenceKeyframes, (keyframe) => JSON.stringify(keyframe.nodePath.nodePath))) {
|
|
90
|
+
const [firstSequenceKeyframe] = keyframeGroup;
|
|
91
|
+
if (!firstSequenceKeyframe) {
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
const updates = groupBy(keyframeGroup, (keyframe) => keyframe.key).map((keyframes) => {
|
|
95
|
+
const [firstMove] = keyframes;
|
|
96
|
+
if (!firstMove) {
|
|
97
|
+
throw new Error('Expected keyframe');
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
key: firstMove.key,
|
|
101
|
+
operation: {
|
|
102
|
+
type: 'move',
|
|
103
|
+
moves: keyframes.map((keyframe) => ({
|
|
104
|
+
fromFrame: keyframe.fromFrame,
|
|
105
|
+
toFrame: keyframe.toFrame,
|
|
106
|
+
})),
|
|
107
|
+
},
|
|
108
|
+
};
|
|
109
|
+
});
|
|
110
|
+
const result = await (0, update_keyframes_1.updateSequenceKeyframes)({
|
|
111
|
+
input: output,
|
|
112
|
+
nodePath: firstSequenceKeyframe.nodePath.nodePath,
|
|
113
|
+
schema: firstSequenceKeyframe.schema,
|
|
114
|
+
updates,
|
|
115
|
+
});
|
|
116
|
+
output = result.output;
|
|
117
|
+
firstLogLine = Math.min(firstLogLine, result.logLine);
|
|
118
|
+
for (const [updateIndex, update] of updates.entries()) {
|
|
119
|
+
sequenceLogs.push({
|
|
120
|
+
fileRelativeToRoot: firstSequenceKeyframe.fileRelativeToRoot,
|
|
121
|
+
line: result.logLine,
|
|
122
|
+
key: update.key,
|
|
123
|
+
oldValueString: result.oldValueStrings[updateIndex],
|
|
124
|
+
newValueString: result.newValueStrings[updateIndex],
|
|
125
|
+
formatted: result.formatted,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
for (const keyframeGroup of groupBy(group.effectKeyframes, (keyframe) => `${JSON.stringify(keyframe.sequenceNodePath.nodePath)}:${keyframe.effectIndex}`)) {
|
|
130
|
+
const [firstEffectKeyframe] = keyframeGroup;
|
|
131
|
+
if (!firstEffectKeyframe) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
const updates = groupBy(keyframeGroup, (keyframe) => keyframe.key).map((keyframes) => {
|
|
135
|
+
const [firstMove] = keyframes;
|
|
136
|
+
if (!firstMove) {
|
|
137
|
+
throw new Error('Expected keyframe');
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
key: firstMove.key,
|
|
141
|
+
operation: {
|
|
142
|
+
type: 'move',
|
|
143
|
+
moves: keyframes.map((keyframe) => ({
|
|
144
|
+
fromFrame: keyframe.fromFrame,
|
|
145
|
+
toFrame: keyframe.toFrame,
|
|
146
|
+
})),
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
});
|
|
150
|
+
const result = await (0, update_keyframes_1.updateEffectKeyframes)({
|
|
151
|
+
input: output,
|
|
152
|
+
sequenceNodePath: firstEffectKeyframe.sequenceNodePath.nodePath,
|
|
153
|
+
effectIndex: firstEffectKeyframe.effectIndex,
|
|
154
|
+
schema: firstEffectKeyframe.schema,
|
|
155
|
+
updates,
|
|
156
|
+
});
|
|
157
|
+
output = result.output;
|
|
158
|
+
firstLogLine = Math.min(firstLogLine, result.logLine);
|
|
159
|
+
for (const [updateIndex, update] of updates.entries()) {
|
|
160
|
+
effectLogs.push({
|
|
161
|
+
fileRelativeToRoot: firstEffectKeyframe.fileRelativeToRoot,
|
|
162
|
+
line: result.logLine,
|
|
163
|
+
effectName: result.effectCallee,
|
|
164
|
+
propKey: update.key,
|
|
165
|
+
oldValueString: result.oldValueStrings[updateIndex],
|
|
166
|
+
newValueString: result.newValueStrings[updateIndex],
|
|
167
|
+
formatted: result.formatted,
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
snapshots.push({
|
|
172
|
+
filePath: absolutePath,
|
|
173
|
+
oldContents: fileContents,
|
|
174
|
+
newContents: output,
|
|
175
|
+
logLine: Number.isFinite(firstLogLine) ? firstLogLine : 1,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
const [firstKeyframe] = sequenceKeyframes.length > 0 ? sequenceKeyframes : effectKeyframes;
|
|
179
|
+
if (!firstKeyframe) {
|
|
180
|
+
throw new Error('No keyframes were specified for moving');
|
|
181
|
+
}
|
|
182
|
+
(0, undo_stack_1.pushTransactionToUndoStack)({
|
|
183
|
+
snapshots,
|
|
184
|
+
logLevel,
|
|
185
|
+
remotionRoot,
|
|
186
|
+
description: getBatchDescription({ totalKeyframes, firstKeyframe }),
|
|
187
|
+
entryType: sequenceKeyframes.length > 0 && effectKeyframes.length > 0
|
|
188
|
+
? 'keyframe-delete'
|
|
189
|
+
: sequenceKeyframes.length > 0
|
|
190
|
+
? 'sequence-props'
|
|
191
|
+
: 'effect-props',
|
|
192
|
+
suppressHmrOnFileRestore: true,
|
|
193
|
+
});
|
|
194
|
+
for (const snapshot of snapshots) {
|
|
195
|
+
(0, undo_stack_1.suppressUndoStackInvalidation)(snapshot.filePath);
|
|
196
|
+
(0, watch_ignore_next_change_1.suppressBundlerUpdateForFile)(snapshot.filePath);
|
|
197
|
+
(0, file_watcher_1.writeFileAndNotifyFileWatchers)(snapshot.filePath, snapshot.newContents, clientId);
|
|
198
|
+
}
|
|
199
|
+
for (const log of sequenceLogs) {
|
|
200
|
+
(0, log_update_1.logUpdate)({
|
|
201
|
+
fileRelativeToRoot: log.fileRelativeToRoot,
|
|
202
|
+
line: log.line,
|
|
203
|
+
key: log.key,
|
|
204
|
+
oldValueString: log.oldValueString,
|
|
205
|
+
newValueString: log.newValueString,
|
|
206
|
+
defaultValueString: null,
|
|
207
|
+
formatted: log.formatted,
|
|
208
|
+
logLevel,
|
|
209
|
+
removedProps: [],
|
|
210
|
+
addedProps: [],
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
for (const log of effectLogs) {
|
|
214
|
+
(0, log_effect_update_1.logEffectUpdate)({
|
|
215
|
+
fileRelativeToRoot: log.fileRelativeToRoot,
|
|
216
|
+
line: log.line,
|
|
217
|
+
effectName: log.effectName,
|
|
218
|
+
propKey: log.propKey,
|
|
219
|
+
oldValueString: log.oldValueString,
|
|
220
|
+
newValueString: log.newValueString,
|
|
221
|
+
defaultValueString: null,
|
|
222
|
+
formatted: log.formatted,
|
|
223
|
+
logLevel,
|
|
224
|
+
removedProps: [],
|
|
225
|
+
addedProps: [],
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
(0, undo_stack_1.printUndoHint)(logLevel);
|
|
229
|
+
};
|
|
230
|
+
exports.moveKeyframes = moveKeyframes;
|
|
231
|
+
const moveKeyframesHandler = ({ input: { sequenceKeyframes, effectKeyframes, clientId }, remotionRoot, logLevel, }) => (0, save_props_mutex_1.withSavePropsLock)(async () => {
|
|
232
|
+
renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[move-keyframes] Received request to move ${sequenceKeyframes.length + effectKeyframes.length} keyframe(s)`);
|
|
233
|
+
await (0, exports.moveKeyframes)({
|
|
234
|
+
sequenceKeyframes,
|
|
235
|
+
effectKeyframes,
|
|
236
|
+
clientId,
|
|
237
|
+
remotionRoot,
|
|
238
|
+
logLevel,
|
|
239
|
+
});
|
|
240
|
+
return { success: true };
|
|
241
|
+
});
|
|
242
|
+
exports.moveKeyframesHandler = moveKeyframesHandler;
|