@remotion/studio-server 4.0.492 → 4.0.494

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.
@@ -434,6 +434,8 @@ const transformLoneJsxElement = (expression, transformation, changesMade, parent
434
434
  compId === transformation.idToDelete) ||
435
435
  (transformation.type === 'rename-composition' &&
436
436
  compId === transformation.idToRename) ||
437
+ (transformation.type === 'update-composition-metadata' &&
438
+ compId === transformation.idToUpdate) ||
437
439
  (transformation.type === 'duplicate-composition' &&
438
440
  compId === transformation.idToDuplicate) ||
439
441
  (transformation.type === 'delete-folder' &&
@@ -512,6 +514,21 @@ const mapJsxChild = (c, transformation, changesMade, parentFolderName) => {
512
514
  }),
513
515
  ];
514
516
  }
517
+ if (transformation.type === 'update-composition-metadata' &&
518
+ compId === transformation.idToUpdate) {
519
+ return [
520
+ changeComposition({
521
+ jsxElement: c,
522
+ newCompositionId: null,
523
+ newCompositionFps: transformation.newFps,
524
+ newCompositionDurationInFrames: transformation.newDurationInFrames,
525
+ newCompositionHeight: transformation.newHeight,
526
+ newCompositionWidth: transformation.newWidth,
527
+ changesMade,
528
+ newTagToUse: null,
529
+ }),
530
+ ];
531
+ }
515
532
  if (transformation.type === 'delete-composition' &&
516
533
  compId === transformation.idToDelete) {
517
534
  changesMade.push({
@@ -783,7 +800,8 @@ const changeComposition = ({ jsxElement, newCompositionId, newCompositionFps, ne
783
800
  // id="one"
784
801
  if (attribute.name.name === 'id' &&
785
802
  attribute.value &&
786
- attribute.value.type === 'StringLiteral') {
803
+ attribute.value.type === 'StringLiteral' &&
804
+ newCompositionId !== null) {
787
805
  changesMade.push({
788
806
  description: 'Replaced composition id',
789
807
  });
@@ -796,7 +814,8 @@ const changeComposition = ({ jsxElement, newCompositionId, newCompositionFps, ne
796
814
  if (attribute.name.name === 'id' &&
797
815
  attribute.value &&
798
816
  attribute.value.type === 'JSXExpressionContainer' &&
799
- attribute.value.expression.type === 'StringLiteral') {
817
+ attribute.value.expression.type === 'StringLiteral' &&
818
+ newCompositionId !== null) {
800
819
  changesMade.push({
801
820
  description: 'Replaced composition id',
802
821
  });
@@ -811,85 +830,74 @@ const changeComposition = ({ jsxElement, newCompositionId, newCompositionFps, ne
811
830
  },
812
831
  };
813
832
  }
814
- if (attribute.name.name === 'fps' &&
815
- attribute.value &&
816
- attribute.value.type === 'JSXExpressionContainer' &&
817
- attribute.value.expression.type === 'NumericLiteral' &&
818
- newCompositionFps !== null) {
833
+ if (attribute.name.name === 'fps' && newCompositionFps !== null) {
819
834
  changesMade.push({
820
835
  description: 'Replaced FPS',
821
836
  });
822
837
  return {
823
838
  ...attribute,
824
- value: {
825
- ...attribute.value,
826
- expression: {
827
- ...attribute.value.expression,
828
- value: newCompositionFps,
829
- },
830
- },
839
+ value: b.jsxExpressionContainer(b.numericLiteral(newCompositionFps)),
831
840
  };
832
841
  }
833
842
  if (attribute.name.name === 'durationInFrames' &&
834
- attribute.value &&
835
- attribute.value.type === 'JSXExpressionContainer' &&
836
- attribute.value.expression.type === 'NumericLiteral' &&
837
843
  newCompositionDurationInFrames !== null) {
838
844
  changesMade.push({
839
845
  description: 'Replaced durationInFrames',
840
846
  });
841
847
  return {
842
848
  ...attribute,
843
- value: {
844
- ...attribute.value,
845
- expression: {
846
- ...attribute.value.expression,
847
- value: newCompositionDurationInFrames,
848
- },
849
- },
849
+ value: b.jsxExpressionContainer(b.numericLiteral(newCompositionDurationInFrames)),
850
850
  };
851
851
  }
852
- if (attribute.name.name === 'width' &&
853
- attribute.value &&
854
- attribute.value.type === 'JSXExpressionContainer' &&
855
- attribute.value.expression.type === 'NumericLiteral' &&
856
- newCompositionWidth !== null) {
852
+ if (attribute.name.name === 'width' && newCompositionWidth !== null) {
857
853
  changesMade.push({
858
854
  description: 'Replaced width',
859
855
  });
860
856
  return {
861
857
  ...attribute,
862
- value: {
863
- ...attribute.value,
864
- expression: {
865
- ...attribute.value.expression,
866
- value: newCompositionWidth,
867
- },
868
- },
858
+ value: b.jsxExpressionContainer(b.numericLiteral(newCompositionWidth)),
869
859
  };
870
860
  }
871
- if (attribute.name.name === 'height' &&
872
- attribute.value &&
873
- attribute.value.type === 'JSXExpressionContainer' &&
874
- attribute.value.expression.type === 'NumericLiteral' &&
875
- newCompositionHeight !== null) {
861
+ if (attribute.name.name === 'height' && newCompositionHeight !== null) {
876
862
  changesMade.push({
877
863
  description: 'Replaced height',
878
864
  });
879
865
  return {
880
866
  ...attribute,
881
- value: {
882
- ...attribute.value,
883
- expression: {
884
- ...attribute.value.expression,
885
- value: newCompositionHeight,
886
- },
887
- },
867
+ value: b.jsxExpressionContainer(b.numericLiteral(newCompositionHeight)),
888
868
  };
889
869
  }
890
870
  return attribute;
891
871
  })
892
872
  .filter(Boolean);
873
+ if (newCompositionFps !== null &&
874
+ !attributes.some((attribute) => attribute.type === 'JSXAttribute' &&
875
+ attribute.name.type === 'JSXIdentifier' &&
876
+ attribute.name.name === 'fps')) {
877
+ changesMade.push({ description: 'Added FPS' });
878
+ attributes.push(jsxAttributeWithExpression('fps', b.numericLiteral(newCompositionFps)));
879
+ }
880
+ if (newCompositionDurationInFrames !== null &&
881
+ !attributes.some((attribute) => attribute.type === 'JSXAttribute' &&
882
+ attribute.name.type === 'JSXIdentifier' &&
883
+ attribute.name.name === 'durationInFrames')) {
884
+ changesMade.push({ description: 'Added durationInFrames' });
885
+ attributes.push(jsxAttributeWithExpression('durationInFrames', b.numericLiteral(newCompositionDurationInFrames)));
886
+ }
887
+ if (newCompositionWidth !== null &&
888
+ !attributes.some((attribute) => attribute.type === 'JSXAttribute' &&
889
+ attribute.name.type === 'JSXIdentifier' &&
890
+ attribute.name.name === 'width')) {
891
+ changesMade.push({ description: 'Added width' });
892
+ attributes.push(jsxAttributeWithExpression('width', b.numericLiteral(newCompositionWidth)));
893
+ }
894
+ if (newCompositionHeight !== null &&
895
+ !attributes.some((attribute) => attribute.type === 'JSXAttribute' &&
896
+ attribute.name.type === 'JSXIdentifier' &&
897
+ attribute.name.name === 'height')) {
898
+ changesMade.push({ description: 'Added height' });
899
+ attributes.push(jsxAttributeWithExpression('height', b.numericLiteral(newCompositionHeight)));
900
+ }
893
901
  const newName = newTagToUse !== null && newTagToUse !== void 0 ? newTagToUse : name.name;
894
902
  if (newName !== name.name) {
895
903
  changesMade.push({
@@ -10,6 +10,7 @@ export type KeyframeOperation = {
10
10
  } | {
11
11
  type: 'remove';
12
12
  frame: number;
13
+ valueWhenLastKeyframeDeleted: unknown | null;
13
14
  } | {
14
15
  type: 'settings';
15
16
  clamping: {
@@ -682,7 +682,7 @@ const addKeyframe = ({ expression, key, frame, value, schema, videoConfigValues,
682
682
  },
683
683
  };
684
684
  };
685
- const removeKeyframe = ({ expression, frame, videoConfigValues, }) => {
685
+ const removeKeyframe = ({ expression, frame, videoConfigValues, valueWhenLastKeyframeDeleted, }) => {
686
686
  const existing = getInterpolationExpression(expression, videoConfigValues);
687
687
  if (!existing) {
688
688
  throw new Error('Cannot remove keyframe from non-interpolated expression');
@@ -694,7 +694,9 @@ const removeKeyframe = ({ expression, frame, videoConfigValues, }) => {
694
694
  const nextKeyframes = existing.keyframes.filter((_keyframe, index) => index !== keyframeIndex);
695
695
  if (nextKeyframes.length === 0) {
696
696
  return {
697
- expression: existing.keyframes[keyframeIndex].output,
697
+ expression: valueWhenLastKeyframeDeleted === null
698
+ ? existing.keyframes[keyframeIndex].output
699
+ : (0, update_nested_prop_1.parseValueExpression)(valueWhenLastKeyframeDeleted),
698
700
  introduced: noIntroducedIdentifiers,
699
701
  };
700
702
  }
@@ -828,6 +830,7 @@ const applyKeyframeOperation = ({ expression, key, operation, schema, videoConfi
828
830
  expression,
829
831
  frame: operation.frame,
830
832
  videoConfigValues,
833
+ valueWhenLastKeyframeDeleted: operation.valueWhenLastKeyframeDeleted,
831
834
  });
832
835
  };
833
836
  const getExpressionFromJsxAttribute = (attr) => {
@@ -508,6 +508,9 @@ const getComponentRootNode = (declaration) => {
508
508
  const createSequenceElement = () => {
509
509
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier('Sequence'), []), recast.types.builders.jsxClosingElement(recast.types.builders.jsxIdentifier('Sequence')), []);
510
510
  };
511
+ const createSequenceWithChild = ({ child, sequenceLocalName, }) => {
512
+ return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(sequenceLocalName), []), recast.types.builders.jsxClosingElement(recast.types.builders.jsxIdentifier(sequenceLocalName)), [child]);
513
+ };
511
514
  const createNumberAttribute = (name, value) => {
512
515
  return recast.types.builders.jsxAttribute(recast.types.builders.jsxIdentifier(name), recast.types.builders.jsxExpressionContainer(recast.types.builders.numericLiteral(value)));
513
516
  };
@@ -1019,8 +1022,8 @@ const createCompositionComponentElement = ({ localName, props, }) => {
1019
1022
  return recast.types.builders.jsxElement(recast.types.builders.jsxOpeningElement(recast.types.builders.jsxIdentifier(localName), attributes, true), null, []);
1020
1023
  };
1021
1024
  const addElementToComponentRoot = ({ ast, exportName, element, }) => {
1022
- var _a;
1023
- var _b;
1025
+ var _a, _b;
1026
+ var _c, _d;
1024
1027
  const declaration = getDeclarationByExportName({ ast, exportName });
1025
1028
  if (!declaration) {
1026
1029
  throw new Error('Could not find composition component declaration');
@@ -1033,9 +1036,6 @@ const addElementToComponentRoot = ({ ast, exportName, element, }) => {
1033
1036
  }
1034
1037
  throw new Error('Composition component does not return JSX');
1035
1038
  }
1036
- if (rootNode.type === 'JSXElement' && rootNode.openingElement.selfClosing) {
1037
- throw new Error('Cannot insert into a self-closing root JSX element');
1038
- }
1039
1039
  const CANVAS_ROOT_ELEMENTS = [
1040
1040
  'ThreeCanvas',
1041
1041
  'RiveCanvas',
@@ -1047,11 +1047,35 @@ const addElementToComponentRoot = ({ ast, exportName, element, }) => {
1047
1047
  CANVAS_ROOT_ELEMENTS.includes(rootNode.openingElement.name.name)) {
1048
1048
  throw new Error(`Cannot insert a JSX element into a composition whose root element is <${rootNode.openingElement.name.name}>`);
1049
1049
  }
1050
+ if (rootNode.type === 'JSXElement' && rootNode.openingElement.selfClosing) {
1051
+ const fragment = recast.types.builders.jsxFragment(recast.types.builders.jsxOpeningFragment(), recast.types.builders.jsxClosingFragment(), [
1052
+ createSequenceWithChild({
1053
+ child: rootNode,
1054
+ sequenceLocalName: ensureSequenceImport(ast),
1055
+ }),
1056
+ element,
1057
+ ]);
1058
+ let replaced = false;
1059
+ recast.types.visit(ast, {
1060
+ visitJSXElement(astPath) {
1061
+ if (astPath.node === rootNode) {
1062
+ astPath.replace(fragment);
1063
+ replaced = true;
1064
+ return false;
1065
+ }
1066
+ this.traverse(astPath);
1067
+ },
1068
+ });
1069
+ if (!replaced) {
1070
+ throw new Error('Could not replace composition component root');
1071
+ }
1072
+ return (_c = (_a = rootNode.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _c !== void 0 ? _c : 1;
1073
+ }
1050
1074
  if (!rootNode.children) {
1051
1075
  throw new Error('Composition component root does not accept children');
1052
1076
  }
1053
1077
  rootNode.children.push(element);
1054
- return (_b = (_a = rootNode.loc) === null || _a === void 0 ? void 0 : _a.start.line) !== null && _b !== void 0 ? _b : 1;
1078
+ return (_d = (_b = rootNode.loc) === null || _b === void 0 ? void 0 : _b.start.line) !== null && _d !== void 0 ? _d : 1;
1055
1079
  };
1056
1080
  const getDefaultExportDeclaration = (ast) => {
1057
1081
  let declaration = null;
@@ -82,11 +82,35 @@ const parseVideoConfigNumericExpression = ({ node, videoConfigValues, }) => {
82
82
  if (videoConfigValue !== null) {
83
83
  return { type: 'video-config-value', ...videoConfigValue };
84
84
  }
85
- if (node.type !== 'BinaryExpression' || node.operator !== '*') {
85
+ if (node.type !== 'BinaryExpression') {
86
86
  return null;
87
87
  }
88
88
  const left = node.left;
89
89
  const right = node.right;
90
+ if (node.operator === '-') {
91
+ const minuend = getVideoConfigValue({
92
+ node: left,
93
+ videoConfigValues,
94
+ });
95
+ const subtrahend = getNumericLiteral(right);
96
+ if (minuend === null || subtrahend === null) {
97
+ return null;
98
+ }
99
+ const resolvedValue = minuend.value - subtrahend;
100
+ if (!Number.isFinite(resolvedValue)) {
101
+ return null;
102
+ }
103
+ return {
104
+ type: 'video-config-subtraction',
105
+ identifier: minuend.identifier,
106
+ minuend: minuend.value,
107
+ subtrahend,
108
+ value: resolvedValue,
109
+ };
110
+ }
111
+ if (node.operator !== '*') {
112
+ return null;
113
+ }
90
114
  const leftNumber = getNumericLiteral(left);
91
115
  const rightNumber = getNumericLiteral(right);
92
116
  const leftVideoConfig = getVideoConfigValue({
@@ -136,6 +160,13 @@ const updateVideoConfigNumericExpression = ({ expression, value, }) => {
136
160
  ? b.identifier(expression.identifier)
137
161
  : numericExpression(value);
138
162
  }
163
+ if (expression.type === 'video-config-subtraction') {
164
+ const subtrahend = expression.minuend - value;
165
+ if (!Number.isFinite(subtrahend)) {
166
+ return numericExpression(value);
167
+ }
168
+ return b.binaryExpression('-', b.identifier(expression.identifier), numericExpression(subtrahend));
169
+ }
139
170
  if (expression.type !== 'video-config-multiplication' || value === 0) {
140
171
  return numericExpression(value);
141
172
  }
@@ -40,6 +40,9 @@ const getCodemodLogMessage = (codemod) => {
40
40
  if (codemod.type === 'rename-composition') {
41
41
  return `Renamed composition "${codemod.idToRename}" to "${codemod.newId}"`;
42
42
  }
43
+ if (codemod.type === 'update-composition-metadata') {
44
+ return `Updated metadata of composition "${codemod.idToUpdate}"`;
45
+ }
43
46
  if (codemod.type === 'delete-composition') {
44
47
  return `Deleted composition "${codemod.idToDelete}"`;
45
48
  }
@@ -84,6 +87,14 @@ const getCodemodUndoDescription = (codemod) => {
84
87
  entryType: codemod.type,
85
88
  };
86
89
  }
90
+ if (codemod.type === 'update-composition-metadata') {
91
+ const label = `metadata of composition "${codemod.idToUpdate}"`;
92
+ return {
93
+ undoMessage: `↩️ Update of ${label}`,
94
+ redoMessage: `↪️ Update of ${label}`,
95
+ entryType: codemod.type,
96
+ };
97
+ }
87
98
  if (codemod.type === 'duplicate-composition') {
88
99
  const label = `composition "${codemod.idToDuplicate}" to "${codemod.newId}"`;
89
100
  return {
@@ -97,13 +97,17 @@ const deleteKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, r
97
97
  input: output,
98
98
  nodePath: firstSequenceKeyframe.nodePath.nodePath,
99
99
  videoConfigValues: firstSequenceKeyframe.nodePath.videoConfigValues,
100
- updates: keyframeGroup.map((keyframe) => ({
101
- key: keyframe.key,
102
- operation: {
103
- type: 'remove',
104
- frame: keyframe.frame,
105
- },
106
- })),
100
+ updates: keyframeGroup.map((keyframe) => {
101
+ var _a;
102
+ return ({
103
+ key: keyframe.key,
104
+ operation: {
105
+ type: 'remove',
106
+ frame: keyframe.frame,
107
+ valueWhenLastKeyframeDeleted: (_a = keyframe.valueWhenLastKeyframeDeleted) !== null && _a !== void 0 ? _a : null,
108
+ },
109
+ });
110
+ }),
107
111
  });
108
112
  output = result.output;
109
113
  firstLogLine = Math.min(firstLogLine, result.logLine);
@@ -128,13 +132,17 @@ const deleteKeyframes = async ({ sequenceKeyframes, effectKeyframes, clientId, r
128
132
  sequenceNodePath: firstEffectKeyframe.sequenceNodePath.nodePath,
129
133
  effectIndex: firstEffectKeyframe.effectIndex,
130
134
  videoConfigValues: firstEffectKeyframe.sequenceNodePath.videoConfigValues,
131
- updates: keyframeGroup.map((keyframe) => ({
132
- key: keyframe.key,
133
- operation: {
134
- type: 'remove',
135
- frame: keyframe.frame,
136
- },
137
- })),
135
+ updates: keyframeGroup.map((keyframe) => {
136
+ var _a;
137
+ return ({
138
+ key: keyframe.key,
139
+ operation: {
140
+ type: 'remove',
141
+ frame: keyframe.frame,
142
+ valueWhenLastKeyframeDeleted: (_a = keyframe.valueWhenLastKeyframeDeleted) !== null && _a !== void 0 ? _a : null,
143
+ },
144
+ });
145
+ }),
138
146
  });
139
147
  output = result.output;
140
148
  firstLogLine = Math.min(firstLogLine, result.logLine);
@@ -256,7 +256,8 @@ const downloadRemoteAssetHandler = async ({ input, publicDir, request }) => {
256
256
  type: 'asset',
257
257
  assetType: fileType.type === 'gif'
258
258
  ? 'gif'
259
- : fileType.type === 'apng'
259
+ : fileType.type === 'apng' ||
260
+ (fileType.type === 'webp' && fileType.animated)
260
261
  ? 'animated-image'
261
262
  : 'image',
262
263
  src: assetPath,
@@ -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' | 'duplicate-effect' | 'paste-effects' | 'reorder-effect' | 'reorder-sequence' | 'delete-jsx-node' | 'duplicate-jsx-node' | 'split-jsx-sequence' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'new-composition' | 'duplicate-composition' | 'move-composition-to-folder' | 'new-folder' | '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' | 'split-jsx-sequence' | 'insert-jsx-element' | 'delete-composition' | 'rename-composition' | 'update-composition-metadata' | 'new-composition' | 'duplicate-composition' | 'move-composition-to-folder' | 'new-folder' | 'delete-folder' | 'rename-folder';
7
7
  type UndoEntrySnapshot = {
8
8
  filePath: string;
9
9
  oldContents: string | null;
@@ -55,6 +55,8 @@ type UndoEntry = {
55
55
  entryType: 'delete-composition';
56
56
  } | {
57
57
  entryType: 'rename-composition';
58
+ } | {
59
+ entryType: 'update-composition-metadata';
58
60
  } | {
59
61
  entryType: 'new-composition';
60
62
  } | {
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.492",
6
+ "version": "4.0.494",
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.492",
30
+ "remotion": "4.0.494",
31
31
  "recast": "0.23.11",
32
- "@remotion/bundler": "4.0.492",
33
- "@remotion/renderer": "4.0.492",
34
- "@remotion/studio-shared": "4.0.492",
32
+ "@remotion/bundler": "4.0.494",
33
+ "@remotion/renderer": "4.0.494",
34
+ "@remotion/studio-shared": "4.0.494",
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.492",
42
+ "@remotion/eslint-config-internal": "4.0.494",
43
43
  "eslint": "9.19.0",
44
44
  "@types/node": "20.12.14",
45
45
  "@typescript/native-preview": "7.0.0-dev.20260217.1"