@remotion/studio-server 4.0.508 → 4.0.509

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.
@@ -0,0 +1,14 @@
1
+ import type { CanvasCaptureData } from '@remotion/studio-shared';
2
+ export declare const generateCanvasCaptureComposition: ({ componentName, compositionId, data, durationInFrames, fps, height, keyframeFps, videoFileName, videoHeight, videoWidth, width, }: {
3
+ readonly componentName: string;
4
+ readonly compositionId: string;
5
+ readonly data: CanvasCaptureData;
6
+ readonly durationInFrames: number;
7
+ readonly fps: number;
8
+ readonly height: number;
9
+ readonly keyframeFps: number;
10
+ readonly videoFileName: string;
11
+ readonly videoHeight: number;
12
+ readonly videoWidth: number;
13
+ readonly width: number;
14
+ }) => Promise<string>;
@@ -0,0 +1,174 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateCanvasCaptureComposition = void 0;
4
+ const duplicate_composition_1 = require("../codemods/duplicate-composition");
5
+ const customCursorRegex = /^\s*url\(/;
6
+ const collapseMouseMovementsByFrame = ({ data, keyframeFps, }) => {
7
+ const movements = [];
8
+ for (const movement of data.mouseMovements) {
9
+ if (movement.canvasX === null || movement.canvasY === null) {
10
+ continue;
11
+ }
12
+ const framedMovement = {
13
+ ...movement,
14
+ frame: Math.ceil(movement.timeInSeconds * keyframeFps),
15
+ };
16
+ const previous = movements.at(-1);
17
+ if ((previous === null || previous === void 0 ? void 0 : previous.frame) === framedMovement.frame) {
18
+ movements[movements.length - 1] = framedMovement;
19
+ }
20
+ else {
21
+ movements.push(framedMovement);
22
+ }
23
+ }
24
+ return movements;
25
+ };
26
+ const getCursorName = (cursor) => {
27
+ var _a;
28
+ if (customCursorRegex.test(cursor)) {
29
+ return 'custom';
30
+ }
31
+ return ((_a = cursor.split(',').at(-1)) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()) || 'default';
32
+ };
33
+ const serialize = (value) => JSON.stringify(value);
34
+ const generateCanvasCaptureComposition = ({ componentName, compositionId, data, durationInFrames, fps, height, keyframeFps, videoFileName, videoHeight, videoWidth, width, }) => {
35
+ var _a;
36
+ const movements = collapseMouseMovementsByFrame({ data, keyframeFps });
37
+ if (movements.length === 0) {
38
+ throw new Error('The Canvas Capture does not contain cursor movements');
39
+ }
40
+ const cursorKeyframes = movements.reduce((keyframes, movement) => {
41
+ var _a;
42
+ const value = getCursorName(movement.cursor);
43
+ if (((_a = keyframes.at(-1)) === null || _a === void 0 ? void 0 : _a.value) !== value) {
44
+ keyframes.push({ frame: movement.frame, value });
45
+ }
46
+ return keyframes;
47
+ }, []);
48
+ const positionKeyframes = [];
49
+ for (const movement of movements) {
50
+ const previous = positionKeyframes.at(-1);
51
+ if (previous !== undefined && movement.frame - previous.frame > 1) {
52
+ positionKeyframes.push({
53
+ frame: movement.frame - 1,
54
+ value: previous.value,
55
+ });
56
+ }
57
+ positionKeyframes.push({
58
+ frame: movement.frame,
59
+ value: `${movement.canvasX}px ${movement.canvasY}px`,
60
+ });
61
+ }
62
+ const scaleKeyframes = [
63
+ { frame: 0, value: data.captureMetadata.density },
64
+ ...data.pointerClicks.map((click) => ({
65
+ frame: Math.ceil(click.timeInSeconds * keyframeFps),
66
+ value: click.type === 'pointer-down'
67
+ ? data.captureMetadata.density * 0.9
68
+ : data.captureMetadata.density,
69
+ })),
70
+ ].reduce((keyframes, keyframe) => {
71
+ var _a, _b;
72
+ if (((_a = keyframes.at(-1)) === null || _a === void 0 ? void 0 : _a.frame) === keyframe.frame) {
73
+ keyframes[keyframes.length - 1] = keyframe;
74
+ }
75
+ else if (((_b = keyframes.at(-1)) === null || _b === void 0 ? void 0 : _b.value) !== keyframe.value) {
76
+ keyframes.push(keyframe);
77
+ }
78
+ return keyframes;
79
+ }, []);
80
+ const customCursor = (_a = movements.find((movement) => customCursorRegex.test(movement.cursor))) === null || _a === void 0 ? void 0 : _a.cursor;
81
+ const cursorProp = cursorKeyframes.length === 1
82
+ ? `cursor=${serialize(cursorKeyframes[0].value)}`
83
+ : `cursor={interpolate(
84
+ frame,
85
+ ${serialize(cursorKeyframes.map((keyframe) => keyframe.frame))},
86
+ ${serialize(cursorKeyframes.map((keyframe) => keyframe.value))},
87
+ {
88
+ easing: Easing.step1,
89
+ extrapolateLeft: 'clamp',
90
+ extrapolateRight: 'clamp',
91
+ },
92
+ )}`;
93
+ const scale = scaleKeyframes.length === 1
94
+ ? serialize(scaleKeyframes[0].value)
95
+ : `interpolate(
96
+ frame,
97
+ ${serialize(scaleKeyframes.map((keyframe) => keyframe.frame))},
98
+ ${serialize(scaleKeyframes.map((keyframe) => keyframe.value))},
99
+ {
100
+ easing: Easing.step1,
101
+ extrapolateLeft: 'clamp',
102
+ extrapolateRight: 'clamp',
103
+ },
104
+ )`;
105
+ const translate = positionKeyframes.length === 1
106
+ ? serialize(positionKeyframes[0].value)
107
+ : `interpolate(
108
+ frame,
109
+ ${serialize(positionKeyframes.map((keyframe) => keyframe.frame))},
110
+ ${serialize(positionKeyframes.map((keyframe) => keyframe.value))},
111
+ {
112
+ extrapolateLeft: 'clamp',
113
+ extrapolateRight: 'clamp',
114
+ },
115
+ )`;
116
+ const previewComponentName = componentName.endsWith('Composition')
117
+ ? `${componentName.slice(0, -'Composition'.length)}Preview`
118
+ : `${componentName}Preview`;
119
+ return (0, duplicate_composition_1.formatOutput)(`import {MacOSCursor} from '@remotion/mac-cursors';
120
+ import {Video} from '@remotion/media';
121
+ import {
122
+ AbsoluteFill,
123
+ Composition,
124
+ Easing,
125
+ interpolate,
126
+ staticFile,
127
+ useCurrentFrame,
128
+ } from 'remotion';
129
+
130
+ export const ${previewComponentName} = () => {
131
+ const frame = useCurrentFrame();
132
+
133
+ return (
134
+ <AbsoluteFill
135
+ style={{
136
+ width: ${videoWidth},
137
+ height: ${videoHeight},
138
+ }}
139
+ >
140
+ <Video
141
+ src={staticFile(${serialize(videoFileName)})}
142
+ style={{
143
+ position: 'absolute',
144
+ }}
145
+ />
146
+ <MacOSCursor
147
+ ${cursorProp}
148
+ ${customCursor === undefined ? '' : `\t\t\t\tcustomCursor=${serialize(customCursor)}\n`} style={{
149
+ position: 'absolute',
150
+ left: 0,
151
+ top: 0,
152
+ scale: ${scale},
153
+ translate: ${translate},
154
+ }}
155
+ />
156
+ </AbsoluteFill>
157
+ );
158
+ };
159
+
160
+ export const ${componentName} = () => {
161
+ return (
162
+ <Composition
163
+ id=${serialize(compositionId)}
164
+ component={${previewComponentName}}
165
+ width={${width}}
166
+ height={${height}}
167
+ fps={${fps}}
168
+ durationInFrames={${durationInFrames}}
169
+ />
170
+ );
171
+ };
172
+ `);
173
+ };
174
+ exports.generateCanvasCaptureComposition = generateCanvasCaptureComposition;
@@ -316,7 +316,7 @@ const deleteJsxNodes = async ({ input, nodePaths, prettierConfigOverride, }) =>
316
316
  input: finalFile,
317
317
  prettierConfigOverride,
318
318
  });
319
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
319
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
320
320
  ast,
321
321
  captured: capturedNodePaths,
322
322
  output,
@@ -81,12 +81,14 @@ const parseAndApplyCodemod = ({ input, codeMod, }) => {
81
81
  });
82
82
  }
83
83
  if (codeMod.type === 'new-composition') {
84
- (0, imports_1.ensureNamedImport)({
85
- ast: newAst,
86
- importedName: 'Composition',
87
- sourcePath: 'remotion',
88
- localName: 'Composition',
89
- });
84
+ if (codeMod.canvasCapture === null) {
85
+ (0, imports_1.ensureNamedImport)({
86
+ ast: newAst,
87
+ importedName: 'Composition',
88
+ sourcePath: 'remotion',
89
+ localName: 'Composition',
90
+ });
91
+ }
90
92
  (0, imports_1.ensureNamedImport)({
91
93
  ast: newAst,
92
94
  importedName: codeMod.componentName,
@@ -303,7 +303,7 @@ const duplicateJsxNode = async ({ input, nodePath, prettierConfigOverride, }) =>
303
303
  input: finalFile,
304
304
  prettierConfigOverride,
305
305
  });
306
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
306
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
307
307
  ast,
308
308
  captured: capturedNodePaths,
309
309
  output,
@@ -10,4 +10,7 @@ export declare const getNodePathRemappings: ({ ast, captured, output, }: {
10
10
  ast: File;
11
11
  captured: CapturedJsxNodePath[];
12
12
  output: string;
13
- }) => SequenceNodePathRemapping[];
13
+ }) => {
14
+ nodePathRemappings: SequenceNodePathRemapping[];
15
+ finalNodePathByNode: Map<JSXOpeningElement, SequenceNodePath>;
16
+ };
@@ -74,7 +74,7 @@ const getNodePathRemappings = ({ ast, captured, output, }) => {
74
74
  for (let i = 0; i < nodesAfterMutation.length; i++) {
75
75
  finalNodePathByNode.set(nodesAfterMutation[i], finalNodePaths[i]);
76
76
  }
77
- return captured.flatMap(({ node, nodePath }) => {
77
+ const nodePathRemappings = captured.flatMap(({ node, nodePath }) => {
78
78
  var _a;
79
79
  const newNodePath = (_a = finalNodePathByNode.get(node)) !== null && _a !== void 0 ? _a : null;
80
80
  if (newNodePath !== null &&
@@ -83,5 +83,6 @@ const getNodePathRemappings = ({ ast, captured, output, }) => {
83
83
  }
84
84
  return [{ oldNodePath: nodePath, newNodePath }];
85
85
  });
86
+ return { finalNodePathByNode, nodePathRemappings };
86
87
  };
87
88
  exports.getNodePathRemappings = getNodePathRemappings;
@@ -164,6 +164,19 @@ const jsxAttributeWithExpression = (name, expression) => ({
164
164
  },
165
165
  });
166
166
  const newCompositionElement = (transformation) => {
167
+ if (transformation.canvasCapture !== null) {
168
+ return {
169
+ type: 'JSXElement',
170
+ openingElement: {
171
+ type: 'JSXOpeningElement',
172
+ name: jsxId(transformation.componentName),
173
+ attributes: [],
174
+ selfClosing: true,
175
+ },
176
+ closingElement: null,
177
+ children: [],
178
+ };
179
+ }
167
180
  return {
168
181
  type: 'JSXElement',
169
182
  openingElement: {
@@ -101,7 +101,7 @@ const reorderSequence = async ({ input, sourceNodePath, targetNodePath, position
101
101
  input: finalFile,
102
102
  prettierConfigOverride,
103
103
  });
104
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
104
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
105
105
  ast,
106
106
  captured: capturedNodePaths,
107
107
  output,
@@ -342,7 +342,7 @@ const splitJsxSequence = async ({ input, nodePath, splitFrame, prettierConfigOve
342
342
  input: finalFile,
343
343
  prettierConfigOverride,
344
344
  });
345
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
345
+ const { nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
346
346
  ast,
347
347
  captured: capturedNodePaths,
348
348
  output,
@@ -334,18 +334,16 @@ const getInlineOptionsFromExtraArgs = (extraArgs) => {
334
334
  }
335
335
  return existingOptions;
336
336
  };
337
- const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, nextSegmentCount, insertedKeyframeIndex, nextKeyframeCount, }) => {
338
- const options = getInlineOptionsFromExtraArgs(extraArgs);
337
+ const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, nextSegmentCount, insertedKeyframeIndex, nextKeyframeCount, defaultEasing, }) => {
338
+ var _a, _b;
339
+ const options = (_a = getInlineOptionsFromExtraArgs(extraArgs)) !== null && _a !== void 0 ? _a : (extraArgs.length === 0 ? createEmptyOptionsExpression() : null);
339
340
  if (!options) {
340
341
  return { extraArgs, needsEasingImport: false };
341
342
  }
342
- const easing = getExistingEasingArrayOrNull({
343
+ const easing = (_b = getExistingEasingArrayOrNull({
343
344
  options,
344
345
  segmentCount: previousSegmentCount,
345
- });
346
- if (easing === null) {
347
- return { extraArgs, needsEasingImport: false };
348
- }
346
+ })) !== null && _b !== void 0 ? _b : Array.from({ length: previousSegmentCount }, () => defaultEasing);
349
347
  if (easing.length < nextSegmentCount) {
350
348
  const isSplittingExistingSegment = insertedKeyframeIndex > 0 &&
351
349
  insertedKeyframeIndex < nextKeyframeCount - 1;
@@ -353,15 +351,16 @@ const normalizeEasingAfterAddingKeyframe = ({ extraArgs, previousSegmentCount, n
353
351
  ? Math.min(insertedKeyframeIndex - 1, easing.length - 1)
354
352
  : null;
355
353
  easing.splice(insertedKeyframeIndex, 0, easingIndexToDuplicate === null
356
- ? studio_shared_1.LINEAR_KEYFRAME_EASING
354
+ ? defaultEasing
357
355
  : easing[easingIndexToDuplicate]);
358
356
  }
359
357
  while (easing.length < nextSegmentCount) {
360
- easing.push(studio_shared_1.LINEAR_KEYFRAME_EASING);
358
+ easing.push(defaultEasing);
361
359
  }
360
+ const needsEasingImport = setEasingOption({ options, easing });
362
361
  return {
363
362
  extraArgs: getExtraArgsWithOptions({ extraArgs, options }),
364
- needsEasingImport: setEasingOption({ options, easing }),
363
+ needsEasingImport,
365
364
  };
366
365
  };
367
366
  const getEasingIndexToRemove = ({ removedKeyframeIndex, keyframeCountBeforeRemoval, }) => {
@@ -586,6 +585,9 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
586
585
  const existing = getInterpolationExpression(expression, videoConfigValues);
587
586
  const newOutput = (0, update_nested_prop_1.parseValueExpression)(value);
588
587
  if (existing) {
588
+ const defaultEasing = (0, studio_shared_1.isSchemaFieldHoldOnly)({ schema, key })
589
+ ? studio_shared_1.HOLD_KEYFRAME_EASING
590
+ : studio_shared_1.LINEAR_KEYFRAME_EASING;
589
591
  const existingCalleeName = existing.callee.type === 'Identifier'
590
592
  ? existing.callee.name
591
593
  : 'interpolate';
@@ -619,6 +621,7 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
619
621
  .sort((first, second) => first.frame - second.frame)
620
622
  .findIndex((keyframe) => keyframe.frame === frame),
621
623
  nextKeyframeCount: nextKeyframes.length,
624
+ defaultEasing,
622
625
  })
623
626
  : { extraArgs: existing.extraArgs, needsEasingImport: false };
624
627
  return {
@@ -804,6 +807,10 @@ const applyKeyframeOperation = ({ expression, key, operation, schema, videoConfi
804
807
  };
805
808
  }
806
809
  if (operation.type === 'easing') {
810
+ if ((0, studio_shared_1.isSchemaFieldHoldOnly)({ schema, key }) &&
811
+ operation.easing.type !== 'step1') {
812
+ throw new Error(`Cannot update easing: "${key}" only supports Easing.step1`);
813
+ }
807
814
  const updated = updateKeyframeEasing({
808
815
  expression,
809
816
  segmentIndex: operation.segmentIndex,
@@ -1,4 +1,5 @@
1
1
  import { type InsertableCompositionElement, type InsertableCompositionElementPosition, type SequenceNodePathRemapping } from '@remotion/studio-shared';
2
+ import type { SequenceNodePath } from 'remotion';
2
3
  export type ResolvedCompositionComponent = {
3
4
  source: string;
4
5
  line: number;
@@ -44,4 +45,5 @@ export declare const insertJsxElementIntoComposition: ({ remotionRoot, compositi
44
45
  formatted: boolean;
45
46
  logLine: number;
46
47
  nodePathRemappings: SequenceNodePathRemapping[];
48
+ insertedNodePath: SequenceNodePath | null;
47
49
  }>;
@@ -1427,7 +1427,7 @@ const createInsertableJsxElement = ({ addPositionStyleToComponent, ast, destinat
1427
1427
  throw new Error('Unsupported element type');
1428
1428
  };
1429
1429
  const insertJsxElementIntoComposition = async ({ remotionRoot, compositionFile, compositionId, element, from, prettierConfigOverride, wrapInSequence = null, }) => {
1430
- var _a;
1430
+ var _a, _b;
1431
1431
  const location = await (0, exports.resolveCompositionComponentWithFile)({
1432
1432
  remotionRoot,
1433
1433
  compositionFile,
@@ -1495,17 +1495,19 @@ const insertJsxElementIntoComposition = async ({ remotionRoot, compositionFile,
1495
1495
  input: finalFile,
1496
1496
  prettierConfigOverride,
1497
1497
  });
1498
- const nodePathRemappings = (0, get_node_path_remappings_1.getNodePathRemappings)({
1498
+ const { finalNodePathByNode, nodePathRemappings } = (0, get_node_path_remappings_1.getNodePathRemappings)({
1499
1499
  ast,
1500
1500
  captured: capturedNodePaths,
1501
1501
  output,
1502
1502
  });
1503
+ const insertedNodePath = (_b = finalNodePathByNode.get(finalElementToInsert.openingElement)) !== null && _b !== void 0 ? _b : null;
1503
1504
  return {
1504
1505
  fileName: location.fileName,
1505
1506
  source: location.source,
1506
1507
  oldContents: input,
1507
1508
  output,
1508
1509
  formatted,
1510
+ insertedNodePath,
1509
1511
  logLine,
1510
1512
  nodePathRemappings,
1511
1513
  };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,7 @@ export type { RemotionSkillsScope, RemotionSkillsStatus, } from './detect-outdat
7
7
  export type { RemotionSkillName } from './remotion-skill-names';
8
8
  export { detectOutdatedRemotionSkills, parseRemotionSkillVersion, remotionSkillNames, };
9
9
  export declare const StudioServerInternals: {
10
- startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
10
+ startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
11
11
  browserArgs: string;
12
12
  browserFlag: string;
13
13
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
@@ -26,6 +26,7 @@ export declare const StudioServerInternals: {
26
26
  getRenderQueue: () => import("@remotion/studio-shared").RenderJob[];
27
27
  getNumberOfAudioTags: () => number;
28
28
  getAudioLatencyHint: () => AudioContextLatencyCategory | null;
29
+ getExperimentalKeepAudioContextAlive: () => boolean;
29
30
  getPreviewSampleRate: () => number | null;
30
31
  enableCrossSiteIsolation: boolean;
31
32
  queueMethods: import("./preview-server/api-types").QueueMethods;
@@ -7,6 +7,7 @@ exports.applyCodemodHandler = exports.getCodemodLogMessage = void 0;
7
7
  const node_fs_1 = require("node:fs");
8
8
  const node_path_1 = __importDefault(require("node:path"));
9
9
  const renderer_1 = require("@remotion/renderer");
10
+ const generate_canvas_capture_composition_1 = require("../../canvas-capture/generate-canvas-capture-composition");
10
11
  const apply_codemod_to_file_1 = require("../../codemods/apply-codemod-to-file");
11
12
  const duplicate_composition_1 = require("../../codemods/duplicate-composition");
12
13
  const simple_diff_1 = require("../../codemods/simple-diff");
@@ -16,10 +17,25 @@ const project_info_1 = require("../project-info");
16
17
  const undo_stack_1 = require("../undo-stack");
17
18
  const can_update_default_props_1 = require("./can-update-default-props");
18
19
  const source_file_write_queue_1 = require("./source-file-write-queue");
19
- const formatNewCompositionFile = (componentName) => {
20
+ const formatNewCompositionFile = (codemod) => {
21
+ if (codemod.canvasCapture !== null) {
22
+ return (0, generate_canvas_capture_composition_1.generateCanvasCaptureComposition)({
23
+ componentName: codemod.componentName,
24
+ compositionId: codemod.newId,
25
+ data: codemod.canvasCapture.data,
26
+ durationInFrames: codemod.newDurationInFrames,
27
+ fps: codemod.newFps,
28
+ height: codemod.newHeight,
29
+ keyframeFps: codemod.canvasCapture.keyframeFps,
30
+ videoFileName: codemod.canvasCapture.videoFileName,
31
+ videoHeight: codemod.canvasCapture.videoHeight,
32
+ videoWidth: codemod.canvasCapture.videoWidth,
33
+ width: codemod.newWidth,
34
+ });
35
+ }
20
36
  return (0, duplicate_composition_1.formatOutput)(`import React from 'react';
21
37
 
22
- export const ${componentName}: React.FC = () => {
38
+ export const ${codemod.componentName}: React.FC = () => {
23
39
  return null;
24
40
  };
25
41
  `);
@@ -200,7 +216,7 @@ const applyCodemodHandler = ({ input: { codemod, dryRun, symbolicatedStack }, lo
200
216
  if (componentFilePath === null) {
201
217
  throw new Error('Could not determine the new component file path');
202
218
  }
203
- componentFileContents = await formatNewCompositionFile(codemod.componentName);
219
+ componentFileContents = await formatNewCompositionFile(codemod);
204
220
  snapshots.push({
205
221
  filePath: componentFilePath,
206
222
  oldContents: null,
@@ -159,7 +159,7 @@ const insertJsxElementHandler = ({ input: { compositionFile, compositionId, elem
159
159
  }
160
160
  const elementLabel = getElementLabel(element);
161
161
  renderer_1.RenderInternals.Log.trace({ indent: false, logLevel }, `[insert-jsx-element] Received request for compositionFile="${compositionFile}" compositionId="${compositionId}" element="${element.type}"`);
162
- const { fileName, source, oldContents, output, formatted, logLine, nodePathRemappings, } = await (0, resolve_composition_component_1.insertJsxElementIntoComposition)({
162
+ const { fileName, source, oldContents, output, formatted, insertedNodePath, logLine, nodePathRemappings, } = await (0, resolve_composition_component_1.insertJsxElementIntoComposition)({
163
163
  remotionRoot,
164
164
  compositionFile,
165
165
  compositionId,
@@ -174,6 +174,9 @@ const insertJsxElementHandler = ({ input: { compositionFile, compositionId, elem
174
174
  restoredNodePaths: [],
175
175
  },
176
176
  ]);
177
+ if (insertedNodePath === null) {
178
+ renderer_1.RenderInternals.Log.warn({ indent: false, logLevel }, 'Could not determine the inserted JSX element node path. Skipping automatic selection.');
179
+ }
177
180
  (0, undo_stack_1.pushToUndoStack)({
178
181
  filePath: fileName,
179
182
  oldContents,
@@ -209,6 +212,9 @@ const insertJsxElementHandler = ({ input: { compositionFile, compositionId, elem
209
212
  (0, undo_stack_1.printUndoHint)(logLevel);
210
213
  return {
211
214
  success: true,
215
+ insertedNodePath: insertedNodePath === null
216
+ ? null
217
+ : { absolutePath: fileName, nodePath: insertedNodePath },
212
218
  nodePathMutation,
213
219
  };
214
220
  }
@@ -27,6 +27,15 @@ const stringifySequencePropEditValue = (value) => {
27
27
  }
28
28
  return JSON.stringify(value);
29
29
  };
30
+ const stringifySequencePropSourceEdit = (sourceEdit, fallbackValue) => {
31
+ if ((sourceEdit === null || sourceEdit === void 0 ? void 0 : sourceEdit.type) !== 'clipboard-param') {
32
+ return stringifySequencePropEditValue(fallbackValue);
33
+ }
34
+ if (sourceEdit.param.type === 'static') {
35
+ return stringifySequencePropEditValue(sourceEdit.param.value);
36
+ }
37
+ return `${sourceEdit.param.interpolationFunction}(frame, ${JSON.stringify(sourceEdit.param.keyframes.map((keyframe) => keyframe.frame))}, ${JSON.stringify(sourceEdit.param.keyframes.map((keyframe) => keyframe.value))})`;
38
+ };
30
39
  const groupBy = (items, getKey) => {
31
40
  var _a;
32
41
  const groups = new Map();
@@ -39,7 +48,7 @@ const groupBy = (items, getKey) => {
39
48
  return [...groups.values()];
40
49
  };
41
50
  const convertSequencePropEditToCodemodChange = (edit) => {
42
- var _a;
51
+ var _a, _b;
43
52
  return {
44
53
  nodePath: edit.nodePath.nodePath,
45
54
  updates: [
@@ -48,6 +57,9 @@ const convertSequencePropEditToCodemodChange = (edit) => {
48
57
  value: edit.value,
49
58
  defaultValue: edit.defaultValue,
50
59
  googleFont: ((_a = edit.sourceEdit) === null || _a === void 0 ? void 0 : _a.type) === 'google-font' ? edit.sourceEdit.font : null,
60
+ clipboardParam: ((_b = edit.sourceEdit) === null || _b === void 0 ? void 0 : _b.type) === 'clipboard-param'
61
+ ? edit.sourceEdit.param
62
+ : null,
51
63
  },
52
64
  ],
53
65
  schema: edit.schema,
@@ -98,7 +110,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
98
110
  nodePath: edit.nodePath,
99
111
  key: edit.key,
100
112
  value: parsedValue,
101
- valueString: stringifySequencePropEditValue(parsedValue),
113
+ valueString: stringifySequencePropSourceEdit(edit.sourceEdit, parsedValue),
102
114
  defaultValue: parsedDefaultValue,
103
115
  defaultValueString: parsedDefaultValue !== null
104
116
  ? JSON.stringify(parsedDefaultValue)
@@ -179,6 +191,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
179
191
  const snapshots = [];
180
192
  const outputByPath = new Map();
181
193
  const resultByIndex = new Map();
194
+ const updatedNodePaths = new Map();
182
195
  const sequenceKeyframeLogs = [];
183
196
  const captionPatchLogs = [];
184
197
  const effectKeyframeLogs = [];
@@ -204,7 +217,9 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
204
217
  logLine: result.logLine,
205
218
  removedProps: result.removedProps,
206
219
  formatted,
220
+ newNodePath: result.newNodePath,
207
221
  });
222
+ updatedNodePaths.set(`${absolutePath}:${JSON.stringify(edit.nodePath.nodePath)}`, result.newNodePath);
208
223
  }
209
224
  }
210
225
  for (const captionPatchRequest of group.captionPatches) {
@@ -456,6 +471,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
456
471
  ])).values(),
457
472
  ];
458
473
  const results = statusTargets.map((target) => {
474
+ var _a;
459
475
  const { absolutePath } = (0, resolve_file_inside_project_1.resolveFileInsideProject)({
460
476
  remotionRoot,
461
477
  fileName: target.fileName,
@@ -469,7 +485,7 @@ const saveSequencePropsHandler = ({ input: { edits, addedKeyframes, movedKeyfram
469
485
  fileContents: output,
470
486
  keys: (0, studio_shared_1.getAllSchemaKeys)(target.schema),
471
487
  assetKeys: (0, studio_shared_1.getAssetSchemaKeys)(target.schema),
472
- nodePath: target.nodePath.nodePath,
488
+ nodePath: (_a = updatedNodePaths.get(`${absolutePath}:${JSON.stringify(target.nodePath.nodePath)}`)) !== null && _a !== void 0 ? _a : target.nodePath.nodePath,
473
489
  componentIdentity: null,
474
490
  effects: [],
475
491
  videoConfigValues: target.nodePath.videoConfigValues,
@@ -37,6 +37,7 @@ export declare const startServer: (options: {
37
37
  binariesDirectory: string | null;
38
38
  forceIPv4: boolean;
39
39
  getAudioLatencyHint: () => AudioContextLatencyCategory | null;
40
+ getExperimentalKeepAudioContextAlive: () => boolean;
40
41
  getPreviewSampleRate: () => number | null;
41
42
  enableCrossSiteIsolation: boolean;
42
43
  forceNew: boolean;
@@ -99,6 +99,7 @@ const startServer = async (options) => {
99
99
  gitSource: options.gitSource,
100
100
  binariesDirectory: options.binariesDirectory,
101
101
  getAudioLatencyHint: options.getAudioLatencyHint,
102
+ getExperimentalKeepAudioContextAlive: options.getExperimentalKeepAudioContextAlive,
102
103
  getPreviewSampleRate: options.getPreviewSampleRate,
103
104
  enableCrossSiteIsolation: options.enableCrossSiteIsolation,
104
105
  getStudioRuntimeConfig: options.getStudioRuntimeConfig,
package/dist/routes.d.ts CHANGED
@@ -3,7 +3,7 @@ import type { DefaultEditor } from '@remotion/renderer';
3
3
  import type { GitSource, RenderDefaults, RenderJob, StudioRuntimeConfig } from '@remotion/studio-shared';
4
4
  import type { QueueMethods } from './preview-server/api-types';
5
5
  import type { LiveEventsServer } from './preview-server/live-events';
6
- export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, getAudioLatencyHint, getPreviewSampleRate, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
6
+ export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
7
7
  staticHash: string;
8
8
  staticHashPrefix: string;
9
9
  outputHash: string;
@@ -24,6 +24,7 @@ export declare const handleRoutes: ({ staticHash, staticHashPrefix, outputHash,
24
24
  gitSource: GitSource | null;
25
25
  binariesDirectory: string | null;
26
26
  getAudioLatencyHint: () => AudioContextLatencyCategory | null;
27
+ getExperimentalKeepAudioContextAlive: () => boolean;
27
28
  getPreviewSampleRate: () => number | null;
28
29
  enableCrossSiteIsolation: boolean;
29
30
  getStudioRuntimeConfig: () => StudioRuntimeConfig;
package/dist/routes.js CHANGED
@@ -84,7 +84,7 @@ const handleRemotionConfig = (response, remotionRoot) => {
84
84
  response.end(JSON.stringify(body));
85
85
  return Promise.resolve();
86
86
  };
87
- const handleFallback = async ({ remotionRoot, hash, response, request, getCurrentInputProps, getEnvVariables, publicDir, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, getAudioLatencyHint, getPreviewSampleRate, gitSource, logLevel, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultEditor, }) => {
87
+ const handleFallback = async ({ remotionRoot, hash, response, request, getCurrentInputProps, getEnvVariables, publicDir, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, gitSource, logLevel, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultEditor, }) => {
88
88
  var _a, _b;
89
89
  const acceptsHtml = ((_a = request.headers.accept) !== null && _a !== void 0 ? _a : '').includes('text/html');
90
90
  if (request.method === 'GET' && acceptsHtml) {
@@ -149,6 +149,7 @@ const handleFallback = async ({ remotionRoot, hash, response, request, getCurren
149
149
  logLevel,
150
150
  mode: 'dev',
151
151
  audioLatencyHint: (_b = getAudioLatencyHint()) !== null && _b !== void 0 ? _b : 'playback',
152
+ experimentalKeepAudioContextAlive: getExperimentalKeepAudioContextAlive(),
152
153
  sampleRate: getPreviewSampleRate(),
153
154
  studioRuntimeConfig: getStudioRuntimeConfig(),
154
155
  }));
@@ -253,7 +254,7 @@ const handleBeep = (_, response) => {
253
254
  readStream.pipe(response);
254
255
  return Promise.resolve();
255
256
  };
256
- const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, getAudioLatencyHint, getPreviewSampleRate, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }) => {
257
+ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPrefix, request, response, liveEventsServer, getCurrentInputProps, getEnvVariables, remotionRoot, entryPoint, publicDir, logLevel, getRenderQueue, getRenderDefaults, getNumberOfAudioTags, queueMethods: methods, gitSource, binariesDirectory, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, enableCrossSiteIsolation, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }) => {
257
258
  const url = new URL(request.url, 'http://localhost');
258
259
  if (url.pathname === '/api/file-source') {
259
260
  return handleFileSource({
@@ -397,6 +398,7 @@ const handleRoutes = ({ staticHash, staticHashPrefix, outputHash, outputHashPref
397
398
  gitSource,
398
399
  logLevel,
399
400
  getAudioLatencyHint,
401
+ getExperimentalKeepAudioContextAlive,
400
402
  getPreviewSampleRate,
401
403
  enableCrossSiteIsolation,
402
404
  getStudioRuntimeConfig,
@@ -7,7 +7,7 @@ export type StartStudioResult = {
7
7
  } | {
8
8
  type: 'already-running';
9
9
  };
10
- export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
10
+ export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }: {
11
11
  browserArgs: string;
12
12
  browserFlag: string;
13
13
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
@@ -26,6 +26,7 @@ export declare const startStudio: ({ browserArgs, browserFlag, shouldOpenBrowser
26
26
  getRenderQueue: () => RenderJob[];
27
27
  getNumberOfAudioTags: () => number;
28
28
  getAudioLatencyHint: () => AudioContextLatencyCategory | null;
29
+ getExperimentalKeepAudioContextAlive: () => boolean;
29
30
  getPreviewSampleRate: () => number | null;
30
31
  enableCrossSiteIsolation: boolean;
31
32
  queueMethods: QueueMethods;
@@ -19,7 +19,7 @@ const public_folder_1 = require("./preview-server/public-folder");
19
19
  const start_server_1 = require("./preview-server/start-server");
20
20
  const server_ready_1 = require("./server-ready");
21
21
  const watch_root_file_1 = require("./watch-root-file");
22
- const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }) => {
22
+ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEntryPath, logLevel, getCurrentInputProps, getEnvVariables, desiredPort, remotionRoot, relativePublicDir, bundlerOverride, rspackOverride, webpackOverride, poll, getRenderDefaults, getRenderQueue, getNumberOfAudioTags, queueMethods, previewEntry, gitSource, binariesDirectory, forceIPv4, getAudioLatencyHint, getExperimentalKeepAudioContextAlive, getPreviewSampleRate, enableCrossSiteIsolation, forceNew, rspack, getStudioRuntimeConfig, getDefaultCodingAgent, getDefaultEditor, configFile, }) => {
23
23
  try {
24
24
  if (typeof Bun === 'undefined') {
25
25
  process.title = 'node (npx remotion studio)';
@@ -88,6 +88,7 @@ const startStudio = async ({ browserArgs, browserFlag, shouldOpenBrowser, fullEn
88
88
  binariesDirectory,
89
89
  forceIPv4,
90
90
  getAudioLatencyHint,
91
+ getExperimentalKeepAudioContextAlive,
91
92
  getPreviewSampleRate,
92
93
  enableCrossSiteIsolation,
93
94
  forceNew,
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.508",
6
+ "version": "4.0.509",
7
7
  "description": "Run a Remotion Studio with a server backend",
8
8
  "main": "dist",
9
9
  "scripts": {
@@ -23,7 +23,7 @@
23
23
  "access": "public"
24
24
  },
25
25
  "dependencies": {
26
- "@remotion/studio-protocol": "4.0.508",
26
+ "@remotion/studio-protocol": "4.0.509",
27
27
  "@babel/types": "7.24.0",
28
28
  "@babel/parser": "7.24.1",
29
29
  "@svgr/core": "8.1.0",
@@ -32,12 +32,12 @@
32
32
  "semver": "7.5.3",
33
33
  "zod": "4.4.3",
34
34
  "prettier": "3.8.1",
35
- "remotion": "4.0.508",
35
+ "remotion": "4.0.509",
36
36
  "recast": "0.23.11",
37
- "@remotion/bundler": "4.0.508",
38
- "@remotion/renderer": "4.0.508",
39
- "@remotion/studio-codemods": "4.0.508",
40
- "@remotion/studio-shared": "4.0.508",
37
+ "@remotion/bundler": "4.0.509",
38
+ "@remotion/renderer": "4.0.509",
39
+ "@remotion/studio-codemods": "4.0.509",
40
+ "@remotion/studio-shared": "4.0.509",
41
41
  "memfs": "3.4.3",
42
42
  "open": "8.4.2"
43
43
  },
@@ -45,7 +45,7 @@
45
45
  "ast-types": "0.16.1",
46
46
  "react": "19.2.3",
47
47
  "@types/semver": "7.5.3",
48
- "@remotion/eslint-config-internal": "4.0.508",
48
+ "@remotion/eslint-config-internal": "4.0.509",
49
49
  "eslint": "9.19.0",
50
50
  "@types/node": "20.12.14",
51
51
  "@typescript/native-preview": "7.0.0-dev.20260217.1"