@remotion/studio 4.0.431 → 4.0.432

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.
Files changed (37) hide show
  1. package/dist/Studio.d.ts +1 -0
  2. package/dist/Studio.js +4 -4
  3. package/dist/components/AssetSelectorItem.js +6 -8
  4. package/dist/components/NewComposition/DuplicateComposition.js +1 -4
  5. package/dist/components/RenderModal/SchemaEditor/SchemaLabel.js +1 -3
  6. package/dist/components/RenderModal/SchemaEditor/ZodArrayEditor.js +1 -3
  7. package/dist/components/RenderModal/SchemaEditor/ZodFieldValidation.js +1 -3
  8. package/dist/components/RenderModal/SchemaEditor/ZodTupleEditor.js +1 -3
  9. package/dist/components/RenderModal/WebRenderModal.d.ts +1 -1
  10. package/dist/components/RenderModal/WebRenderModal.js +55 -26
  11. package/dist/components/RenderModal/WebRenderModalAudio.d.ts +2 -0
  12. package/dist/components/RenderModal/WebRenderModalAudio.js +11 -5
  13. package/dist/components/RenderModal/WebRenderModalBasic.js +35 -35
  14. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +3 -3
  15. package/dist/components/RenderQueue/client-side-render-types.d.ts +1 -1
  16. package/dist/components/Timeline/TimelineExpandedSection.js +11 -147
  17. package/dist/components/Timeline/TimelineFieldRow.d.ts +8 -0
  18. package/dist/components/Timeline/TimelineFieldRow.js +78 -0
  19. package/dist/components/Timeline/TimelineListItem.js +6 -17
  20. package/dist/components/Timeline/TimelineSchemaField.d.ts +2 -1
  21. package/dist/components/Timeline/TimelineSchemaField.js +4 -6
  22. package/dist/components/Timeline/use-resolved-stack.d.ts +2 -0
  23. package/dist/components/Timeline/use-resolved-stack.js +54 -0
  24. package/dist/components/Timeline/use-sequence-props-subscription.d.ts +3 -0
  25. package/dist/components/Timeline/use-sequence-props-subscription.js +130 -0
  26. package/dist/error-overlay/react-overlay/utils/get-source-map.d.ts +5 -0
  27. package/dist/esm/{chunk-3msfwcwh.js → chunk-t28xqw5n.js} +1932 -1795
  28. package/dist/esm/internals.mjs +1932 -1795
  29. package/dist/esm/previewEntry.mjs +1915 -1777
  30. package/dist/esm/renderEntry.mjs +5 -2
  31. package/dist/helpers/get-timeline-sequence-layout.js +3 -3
  32. package/dist/helpers/timeline-layout.d.ts +2 -2
  33. package/dist/helpers/timeline-layout.js +2 -2
  34. package/dist/internals.d.ts +1 -0
  35. package/dist/previewEntry.js +1 -1
  36. package/dist/renderEntry.js +3 -3
  37. package/package.json +10 -10
package/dist/Studio.d.ts CHANGED
@@ -2,4 +2,5 @@ import React from 'react';
2
2
  export declare const Studio: React.FC<{
3
3
  readonly rootComponent: React.FC;
4
4
  readonly readOnly: boolean;
5
+ readonly visualModeEnabled: boolean;
5
6
  }>;
package/dist/Studio.js CHANGED
@@ -16,18 +16,18 @@ const ResolveCompositionConfigInStudio_1 = require("./ResolveCompositionConfigIn
16
16
  const getServerDisconnectedDomElement = () => {
17
17
  return document.getElementById('server-disconnected-overlay');
18
18
  };
19
- const StudioInner = ({ rootComponent, readOnly }) => {
19
+ const StudioInner = ({ rootComponent, readOnly, visualModeEnabled }) => {
20
20
  var _a;
21
21
  const { fastRefreshes, manualRefreshes } = (0, react_1.useContext)(fast_refresh_context_1.FastRefreshContext);
22
- return (jsx_runtime_1.jsx(remotion_1.Internals.CompositionManagerProvider, { onlyRenderComposition: null, currentCompositionMetadata: null, initialCompositions: [], initialCanvasContent: null, children: jsx_runtime_1.jsx(remotion_1.Internals.RemotionRootContexts, { frameState: null, audioEnabled: window.remotion_audioEnabled, videoEnabled: window.remotion_videoEnabled, logLevel: window.remotion_logLevel, numberOfAudioTags: window.remotion_numberOfAudioTags, audioLatencyHint: (_a = window.remotion_audioLatencyHint) !== null && _a !== void 0 ? _a : 'interactive', nonceContextSeed: fastRefreshes + manualRefreshes, children: jsx_runtime_1.jsx(use_static_files_1.StaticFilesProvider, { children: jsx_runtime_1.jsx(ResolveCompositionConfigInStudio_1.ResolveCompositionConfigInStudio, { children: jsx_runtime_1.jsxs(EditorContexts_1.EditorContexts, { readOnlyStudio: readOnly, children: [
22
+ return (jsx_runtime_1.jsx(remotion_1.Internals.CompositionManagerProvider, { onlyRenderComposition: null, currentCompositionMetadata: null, initialCompositions: [], initialCanvasContent: null, children: jsx_runtime_1.jsx(remotion_1.Internals.RemotionRootContexts, { visualModeEnabled: visualModeEnabled, frameState: null, audioEnabled: window.remotion_audioEnabled, videoEnabled: window.remotion_videoEnabled, logLevel: window.remotion_logLevel, numberOfAudioTags: window.remotion_numberOfAudioTags, audioLatencyHint: (_a = window.remotion_audioLatencyHint) !== null && _a !== void 0 ? _a : 'interactive', nonceContextSeed: fastRefreshes + manualRefreshes, children: jsx_runtime_1.jsx(use_static_files_1.StaticFilesProvider, { children: jsx_runtime_1.jsx(ResolveCompositionConfigInStudio_1.ResolveCompositionConfigInStudio, { children: jsx_runtime_1.jsxs(EditorContexts_1.EditorContexts, { readOnlyStudio: readOnly, children: [
23
23
  jsx_runtime_1.jsx(Editor_1.Editor, { readOnlyStudio: readOnly, Root: rootComponent }), readOnly
24
24
  ? null
25
25
  : (0, react_dom_1.createPortal)(jsx_runtime_1.jsx(ServerDisconnected_1.ServerDisconnected, {}), getServerDisconnectedDomElement())] }) }) }) }) }));
26
26
  };
27
- const Studio = ({ rootComponent, readOnly }) => {
27
+ const Studio = ({ rootComponent, readOnly, visualModeEnabled }) => {
28
28
  (0, react_1.useLayoutEffect)(() => {
29
29
  (0, inject_css_1.injectCSS)();
30
30
  }, []);
31
- return (jsx_runtime_1.jsx(FastRefreshProvider_1.FastRefreshProvider, { children: jsx_runtime_1.jsx(StudioInner, { rootComponent: rootComponent, readOnly: readOnly }) }));
31
+ return (jsx_runtime_1.jsx(FastRefreshProvider_1.FastRefreshProvider, { children: jsx_runtime_1.jsx(StudioInner, { rootComponent: rootComponent, readOnly: readOnly, visualModeEnabled: visualModeEnabled }) }));
32
32
  };
33
33
  exports.Studio = Studio;
@@ -161,20 +161,19 @@ const AssetSelectorItem = ({ item, tabIndex, level, parentFolder, readOnlyStudio
161
161
  }, []);
162
162
  const { setCanvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionSetters);
163
163
  const { canvasContent } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
164
+ const relativePath = (0, react_1.useMemo)(() => {
165
+ return parentFolder ? parentFolder + '/' + item.name : item.name;
166
+ }, [parentFolder, item.name]);
164
167
  const selected = (0, react_1.useMemo)(() => {
165
168
  if (canvasContent && canvasContent.type === 'asset') {
166
- const nameWOParent = canvasContent.asset.split('/').pop();
167
- return nameWOParent === item.name;
169
+ return canvasContent.asset === relativePath;
168
170
  }
169
171
  return false;
170
- }, [canvasContent, item.name]);
172
+ }, [canvasContent, relativePath]);
171
173
  const onPointerLeave = (0, react_1.useCallback)(() => {
172
174
  setHovered(false);
173
175
  }, []);
174
176
  const onClick = (0, react_1.useCallback)(() => {
175
- const relativePath = parentFolder
176
- ? parentFolder + '/' + item.name
177
- : item.name;
178
177
  setCanvasContent({ type: 'asset', asset: relativePath });
179
178
  (0, url_state_1.pushUrl)(`/assets/${relativePath}`);
180
179
  if (isMobileLayout) {
@@ -182,8 +181,7 @@ const AssetSelectorItem = ({ item, tabIndex, level, parentFolder, readOnlyStudio
182
181
  }
183
182
  }, [
184
183
  isMobileLayout,
185
- item.name,
186
- parentFolder,
184
+ relativePath,
187
185
  setCanvasContent,
188
186
  setSidebarCollapsedState,
189
187
  ]);
@@ -175,10 +175,7 @@ const DuplicateCompositionLoaded = ({ initialType }) => {
175
175
  }, []);
176
176
  return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
177
177
  jsx_runtime_1.jsx(ModalHeader_1.ModalHeader, { title: `Duplicate ${resolved.result.id}` }), jsx_runtime_1.jsxs("form", { onSubmit: onSubmit, children: [
178
- jsx_runtime_1.jsxs("div", { style: content, children: [initialCompType === 'composition' ? (
179
- // We allow converting from a composition to a still, but
180
- // not the other way around
181
- jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
178
+ jsx_runtime_1.jsxs("div", { style: content, children: [initialCompType === 'composition' ? (jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
182
179
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "Type" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { title: "Type of composition", style: comboBoxStyle, values: typeValues, selectedId: type }) })
183
180
  ] })) : null, jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
184
181
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "ID" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsxs("div", { children: [
@@ -41,8 +41,6 @@ const SchemaLabel = ({ jsonPath, isDefaultValue, onReset, onSave, showSaveButton
41
41
  setClickableButtonHovered(false);
42
42
  }, []);
43
43
  const labelContent = (jsx_runtime_1.jsxs("span", { style: labelStyle, children: [(0, get_schema_label_1.getSchemaLabel)(jsonPath), " ", suffix ? suffix : null] }));
44
- return (jsx_runtime_1.jsxs("div", { style: compactStyles, className: scroll_to_default_props_path_1.DEFAULT_PROPS_PATH_CLASSNAME, "data-json-path": jsonPath.join('.'), children: [handleClick ? (
45
- // Minus the padding that a button has (user agent padding-line-start)
46
- jsx_runtime_1.jsx("button", { onPointerEnter: onClickablePointerEnter, onPointerLeave: onClickablePointerLeave, type: "button", onClick: handleClick, style: { border: 'none', padding: 0 }, children: labelContent })) : (labelContent), jsx_runtime_1.jsx(layout_1.Flex, {}), isDefaultValue ? null : jsx_runtime_1.jsx(SchemaResetButton_1.SchemaResetButton, { onClick: onReset }), isDefaultValue ? null : showSaveButton ? (jsx_runtime_1.jsx(SchemaSaveButton_1.SchemaSaveButton, { onClick: onSave, disabled: disableSave })) : null, onRemove ? jsx_runtime_1.jsx(InlineRemoveButton_1.InlineRemoveButton, { onClick: onRemove }) : null] }));
44
+ return (jsx_runtime_1.jsxs("div", { style: compactStyles, className: scroll_to_default_props_path_1.DEFAULT_PROPS_PATH_CLASSNAME, "data-json-path": jsonPath.join('.'), children: [handleClick ? (jsx_runtime_1.jsx("button", { onPointerEnter: onClickablePointerEnter, onPointerLeave: onClickablePointerLeave, type: "button", onClick: handleClick, style: { border: 'none', padding: 0 }, children: labelContent })) : (labelContent), jsx_runtime_1.jsx(layout_1.Flex, {}), isDefaultValue ? null : jsx_runtime_1.jsx(SchemaResetButton_1.SchemaResetButton, { onClick: onReset }), isDefaultValue ? null : showSaveButton ? (jsx_runtime_1.jsx(SchemaSaveButton_1.SchemaSaveButton, { onClick: onSave, disabled: disableSave })) : null, onRemove ? jsx_runtime_1.jsx(InlineRemoveButton_1.InlineRemoveButton, { onClick: onRemove }) : null] }));
47
45
  };
48
46
  exports.SchemaLabel = SchemaLabel;
@@ -75,9 +75,7 @@ const ZodArrayEditor = ({ schema, jsonPath, setValue, defaultValue, value, onSav
75
75
  onSave(() => localValue.value, false, false);
76
76
  }, saveDisabledByParent: saveDisabledByParent, saving: saving, showSaveButton: showSaveButton, valid: localValue.zodValidation.success, handleClick: () => setExpanded(!expanded) }) }), expanded ? (jsx_runtime_1.jsx(RevisionContextProvider, { children: jsx_runtime_1.jsxs(SchemaVerticalGuide_1.SchemaVerticalGuide, { isRoot: false, children: [localValue.value.map((child, i) => {
77
77
  var _a;
78
- return (
79
- // eslint-disable-next-line react/no-array-index-key
80
- jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [
78
+ return (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [
81
79
  jsx_runtime_1.jsx(ZodArrayItemEditor_1.ZodArrayItemEditor, { onChange: onChange, value: child, elementSchema: arrayElement, index: i, jsonPath: jsonPath, defaultValue: (_a = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue[i]) !== null && _a !== void 0 ? _a : (0, create_zod_values_1.createZodValues)(arrayElement, z, zodTypes), onSave: onSave, showSaveButton: showSaveButton, saving: saving, saveDisabledByParent: saveDisabledByParent, mayPad: mayPad, mayRemove: true }), jsx_runtime_1.jsx(SchemaSeparationLine_1.SchemaArrayItemSeparationLine, { schema: schema, index: i, onChange: onChange, isLast: i === localValue.value.length - 1, showAddButton: true })
82
80
  ] }, `${i}${localValue.keyStabilityRevision}`));
83
81
  }), value.length === 0 ? (jsx_runtime_1.jsx(SchemaSeparationLine_1.SchemaArrayItemSeparationLine, { schema: schema, index: 0, onChange: onChange, isLast: true, showAddButton: true })) : null] }) })) : null, jsx_runtime_1.jsx(ZodFieldValidation_1.ZodFieldValidation, { path: jsonPath, localValue: localValue })
@@ -23,9 +23,7 @@ const ZodFieldValidation = ({ localValue, path }) => {
23
23
  }
24
24
  return (jsx_runtime_1.jsxs("div", { style: legend, children: [
25
25
  jsx_runtime_1.jsx(ValidationMessage_1.ValidationMessage, { align: "flex-start", message: zodValidation.error.format()._errors[0], type: "error" }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(InfoBubble_1.InfoBubble, { title: "Zod validation failure", children: jsx_runtime_1.jsxs("div", { style: stackTrace, children: [
26
- jsx_runtime_1.jsx("div", { style: stackTraceLabel, children: "Zod Validation has failed:" }), zodValidation.error.issues.map((error, index) => (
27
- // eslint-disable-next-line react/no-array-index-key
28
- jsx_runtime_1.jsxs("div", { style: stackTraceLabel, children: ["Type: ", error.code, " ",
26
+ jsx_runtime_1.jsx("div", { style: stackTraceLabel, children: "Zod Validation has failed:" }), zodValidation.error.issues.map((error, index) => (jsx_runtime_1.jsxs("div", { style: stackTraceLabel, children: ["Type: ", error.code, " ",
29
27
  jsx_runtime_1.jsx("br", {}),
30
28
  "Message: ", error.message, jsx_runtime_1.jsx("br", {}),
31
29
  "Path: ", path.join('.')] }, index)))] }) }), jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 })
@@ -75,9 +75,7 @@ const ZodTupleEditor = ({ schema, jsonPath, setValue, defaultValue, value, onSav
75
75
  onSave(() => localValue.value, false, false);
76
76
  }, saveDisabledByParent: saveDisabledByParent, saving: saving, showSaveButton: showSaveButton, valid: localValue.zodValidation.success, handleClick: () => setExpanded(!expanded) }) }), expanded ? (jsx_runtime_1.jsx(RevisionContextProvider, { children: jsx_runtime_1.jsxs(SchemaVerticalGuide_1.SchemaVerticalGuide, { isRoot: false, children: [localValue.value.map((child, i) => {
77
77
  var _a;
78
- return (
79
- // eslint-disable-next-line react/no-array-index-key
80
- jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [
78
+ return (jsx_runtime_1.jsxs(react_1.default.Fragment, { children: [
81
79
  jsx_runtime_1.jsx(ZodTupleItemEditor_1.ZodTupleItemEditor, { onChange: onChange, value: child, tupleItems: tupleItems, index: i, jsonPath: jsonPath, defaultValue: (_a = defaultValue === null || defaultValue === void 0 ? void 0 : defaultValue[i]) !== null && _a !== void 0 ? _a : (0, create_zod_values_1.createZodValues)(tupleItems[i], z, zodTypes), onSave: onSave, showSaveButton: showSaveButton, saving: saving, saveDisabledByParent: saveDisabledByParent, mayPad: mayPad }), jsx_runtime_1.jsx(SchemaSeparationLine_1.SchemaArrayItemSeparationLine, { schema: schema, index: i, onChange: onChange, isLast: i === localValue.value.length - 1, showAddButton: false })
82
80
  ] }, `${i}${localValue.keyStabilityRevision}`));
83
81
  }), value.length === 0 ? (jsx_runtime_1.jsx(SchemaSeparationLine_1.SchemaArrayItemSeparationLine, { schema: schema, index: 0, onChange: onChange, isLast: true, showAddButton: false })) : null] }) })) : null, jsx_runtime_1.jsx(ZodFieldValidation_1.ZodFieldValidation, { path: jsonPath, localValue: localValue })
@@ -1,3 +1,3 @@
1
1
  import type { WebRenderModalState } from '../../state/modals';
2
- export type RenderType = 'still' | 'video';
2
+ export type RenderType = 'still' | 'video' | 'audio';
3
3
  export declare const WebRenderModalWithLoader: React.FC<WebRenderModalState>;
@@ -77,7 +77,6 @@ const validateOutnameForStill = ({ outName, stillImageFormat, }) => {
77
77
  };
78
78
  // TODO: Switch to server-side rendering
79
79
  // TODO: Filter out codecs that are not supported for the container
80
- // TODO: Add more containers
81
80
  // TODO: Shortcut: Shift + R
82
81
  // TODO: Apply defaultCodec
83
82
  // TODO: Apply defaultOutName
@@ -96,7 +95,11 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
96
95
  ? true
97
96
  : resolvedComposition.durationInFrames > 1;
98
97
  });
99
- const [renderMode, setRenderMode] = (0, react_1.useState)(isVideo ? 'video' : 'still');
98
+ const [renderMode, setRenderMode] = (0, react_1.useState)(initialContainer && (0, web_renderer_1.isAudioOnlyContainer)(initialContainer)
99
+ ? 'audio'
100
+ : isVideo
101
+ ? 'video'
102
+ : 'still');
100
103
  const [tab, setTab] = (0, react_1.useState)('general');
101
104
  const [imageFormat, setImageFormat] = (0, react_1.useState)(() => initialStillImageFormat !== null && initialStillImageFormat !== void 0 ? initialStillImageFormat : 'png');
102
105
  const [frame, setFrame] = (0, react_1.useState)(() => initialFrame);
@@ -170,36 +173,52 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
170
173
  return defaultOut;
171
174
  });
172
175
  const [outName, setOutName] = (0, react_1.useState)(() => initialOutNameState);
176
+ const updateOutNameExtension = (0, react_1.useCallback)((extension) => {
177
+ setOutName((prev) => (0, get_string_before_suffix_1.getStringBeforeSuffix)(prev) + '.' + extension);
178
+ }, []);
173
179
  const setStillFormat = (0, react_1.useCallback)((format) => {
174
180
  setImageFormat(format);
175
- setOutName((prev) => {
176
- const newFileName = (0, get_string_before_suffix_1.getStringBeforeSuffix)(prev) + '.' + format;
177
- return newFileName;
178
- });
179
- }, []);
181
+ updateOutNameExtension(format);
182
+ }, [updateOutNameExtension]);
180
183
  const setContainerFormat = (0, react_1.useCallback)((newContainer) => {
184
+ setContainer(newContainer);
185
+ const defaultVideoCodec = (0, web_renderer_1.getDefaultVideoCodecForContainer)(newContainer);
186
+ if (defaultVideoCodec) {
187
+ setCodec(defaultVideoCodec);
188
+ }
189
+ setAudioCodec((0, web_renderer_1.getDefaultAudioCodecForContainer)(newContainer));
190
+ updateOutNameExtension(newContainer);
191
+ }, [updateOutNameExtension]);
192
+ const setCodecWithContainer = (0, react_1.useCallback)((newCodec) => {
193
+ setCodec(newCodec);
194
+ const newContainer = (0, web_renderer_1.getDefaultContainerForCodec)(newCodec);
181
195
  setContainer(newContainer);
182
196
  setAudioCodec((0, web_renderer_1.getDefaultAudioCodecForContainer)(newContainer));
183
- setOutName((prev) => {
184
- const newFileName = (0, get_string_before_suffix_1.getStringBeforeSuffix)(prev) + '.' + newContainer;
185
- return newFileName;
186
- });
187
- }, []);
197
+ updateOutNameExtension(newContainer);
198
+ }, [updateOutNameExtension]);
188
199
  const onRenderModeChange = (0, react_1.useCallback)((newMode) => {
189
200
  setRenderMode(newMode);
190
201
  if (newMode === 'video') {
191
- setOutName((prev) => {
192
- const newFileName = (0, get_string_before_suffix_1.getStringBeforeSuffix)(prev) + '.' + container;
193
- return newFileName;
194
- });
202
+ const newContainer = (0, web_renderer_1.isAudioOnlyContainer)(container)
203
+ ? 'mp4'
204
+ : container;
205
+ setContainer(newContainer);
206
+ setAudioCodec((0, web_renderer_1.getDefaultAudioCodecForContainer)(newContainer));
207
+ updateOutNameExtension(newContainer);
208
+ }
209
+ else if (newMode === 'audio') {
210
+ const newContainer = 'wav';
211
+ setContainer(newContainer);
212
+ setMuted(false);
213
+ setAudioCodec((0, web_renderer_1.getDefaultAudioCodecForContainer)(newContainer));
214
+ updateOutNameExtension(newContainer);
215
+ setTab((prev) => (prev === 'picture' ? 'general' : prev));
195
216
  }
196
217
  else if (newMode === 'still') {
197
- setOutName((prev) => {
198
- const newFileName = (0, get_string_before_suffix_1.getStringBeforeSuffix)(prev) + '.' + imageFormat;
199
- return newFileName;
200
- });
218
+ updateOutNameExtension(imageFormat);
219
+ setTab((prev) => (prev === 'audio' ? 'general' : prev));
201
220
  }
202
- }, [container, imageFormat]);
221
+ }, [container, imageFormat, updateOutNameExtension]);
203
222
  const renderTabOptions = (0, react_1.useMemo)(() => {
204
223
  const options = [
205
224
  {
@@ -211,7 +230,7 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
211
230
  selected: renderMode === 'still',
212
231
  },
213
232
  ];
214
- // Only show video option if composition has more than 1 frame
233
+ // Only show video/audio options if composition has more than 1 frame
215
234
  if (resolvedComposition.durationInFrames > 1) {
216
235
  options.push({
217
236
  label: 'Video',
@@ -221,6 +240,14 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
221
240
  key: 'video',
222
241
  selected: renderMode === 'video',
223
242
  });
243
+ options.push({
244
+ label: 'Audio',
245
+ onClick: () => {
246
+ onRenderModeChange('audio');
247
+ },
248
+ key: 'audio',
249
+ selected: renderMode === 'audio',
250
+ });
224
251
  }
225
252
  return options;
226
253
  }, [renderMode, resolvedComposition.durationInFrames, onRenderModeChange]);
@@ -315,7 +342,9 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
315
342
  compositionId: resolvedComposition.id,
316
343
  outName,
317
344
  container,
318
- videoCodec: effectiveVideoCodec,
345
+ videoCodec: (0, web_renderer_1.isAudioOnlyContainer)(container)
346
+ ? null
347
+ : effectiveVideoCodec,
319
348
  audioCodec: effectiveAudioCodec,
320
349
  startFrame: finalStartFrame,
321
350
  endFrame: finalEndFrame,
@@ -382,16 +411,16 @@ const WebRenderModal = ({ initialFrame, defaultProps, inFrameMark, outFrameMark,
382
411
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(file_1.FileIcon, { style: render_modals_1.icon }) }),
383
412
  "General"] }), jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'data', onClick: () => setTab('data'), children: [
384
413
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(data_1.DataIcon, { style: render_modals_1.icon }) }),
385
- "Input Props"] }), jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'picture', onClick: () => setTab('picture'), children: [
414
+ "Input Props"] }), renderMode !== 'audio' ? (jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'picture', onClick: () => setTab('picture'), children: [
386
415
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(frame_1.PicIcon, { style: render_modals_1.icon }) }),
387
- "Picture"] }), renderMode === 'video' ? (jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'audio', onClick: () => setTab('audio'), children: [
416
+ "Picture"] })) : null, renderMode === 'video' || renderMode === 'audio' ? (jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'audio', onClick: () => setTab('audio'), children: [
388
417
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(audio_1.AudioIcon, { style: render_modals_1.icon }) }),
389
418
  "Audio"] })) : null, jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'advanced', onClick: () => setTab('advanced'), children: [
390
419
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(gear_1.GearIcon, { style: render_modals_1.icon }) }),
391
420
  "Other"] }), jsx_runtime_1.jsxs(vertical_1.VerticalTab, { style: render_modals_1.horizontalTab, selected: tab === 'license', onClick: () => setTab('license'), children: [
392
421
  jsx_runtime_1.jsx("div", { style: render_modals_1.iconContainer, children: jsx_runtime_1.jsx(certificate_1.CertificateIcon, { style: render_modals_1.icon }) }),
393
422
  "License"] })
394
- ] }), jsx_runtime_1.jsx("div", { style: render_modals_1.optionsPanel, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: tab === 'general' ? (jsx_runtime_1.jsx(WebRenderModalBasic_1.WebRenderModalBasic, { renderMode: renderMode, resolvedComposition: resolvedComposition, imageFormat: imageFormat, setStillFormat: setStillFormat, frame: frame, onFrameChanged: onFrameChanged, onFrameSetDirectly: onFrameSetDirectly, container: container, setContainerFormat: setContainerFormat, setCodec: setCodec, encodableVideoCodecs: encodableVideoCodecs, effectiveVideoCodec: effectiveVideoCodec, startFrame: finalStartFrame, setStartFrame: setStartFrame, endFrame: finalEndFrame, setEndFrame: setEndFrame, outName: outName, onOutNameChange: onOutNameChange, validationMessage: outnameValidation.valid ? null : outnameValidation.error.message, logLevel: logLevel, setLogLevel: setLogLevel })) : tab === 'data' ? (jsx_runtime_1.jsx(DataEditor_1.DataEditor, { defaultProps: inputProps, setDefaultProps: setInputProps, unresolvedComposition: unresolvedComposition, mayShowSaveButton: false, propsEditType: "input-props", saving: saving, setSaving: setSaving, readOnlyStudio: false })) : tab === 'picture' ? (jsx_runtime_1.jsx(WebRenderModalPicture_1.WebRenderModalPicture, { renderMode: renderMode, videoBitrate: videoBitrate, setVideoBitrate: setVideoBitrate, keyframeIntervalInSeconds: keyframeIntervalInSeconds, setKeyframeIntervalInSeconds: setKeyframeIntervalInSeconds, transparent: transparent, setTransparent: setTransparent, scale: scale, setScale: setScale, compositionWidth: resolvedComposition.width, compositionHeight: resolvedComposition.height })) : tab === 'audio' ? (jsx_runtime_1.jsx(WebRenderModalAudio_1.WebRenderModalAudio, { muted: muted, setMuted: setMuted, audioCodec: audioCodec, setAudioCodec: setAudioCodec, audioBitrate: audioBitrate, setAudioBitrate: setAudioBitrate, container: container, encodableCodecs: encodableAudioCodecs, effectiveAudioCodec: effectiveAudioCodec })) : tab === 'advanced' ? (jsx_runtime_1.jsx(WebRenderModalAdvanced_1.WebRenderModalAdvanced, { renderMode: renderMode, delayRenderTimeout: delayRenderTimeout, setDelayRenderTimeout: setDelayRenderTimeout, mediaCacheSizeInBytes: mediaCacheSizeInBytes, setMediaCacheSizeInBytes: setMediaCacheSizeInBytes, hardwareAcceleration: hardwareAcceleration, setHardwareAcceleration: setHardwareAcceleration })) : (jsx_runtime_1.jsx(WebRenderModalLicense_1.WebRenderModalLicense, { licenseKey: licenseKey, setLicenseKey: setLicenseKey, initialPublicLicenseKey: initialLicenseKey })) })
423
+ ] }), jsx_runtime_1.jsx("div", { style: render_modals_1.optionsPanel, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: tab === 'general' ? (jsx_runtime_1.jsx(WebRenderModalBasic_1.WebRenderModalBasic, { renderMode: renderMode, resolvedComposition: resolvedComposition, imageFormat: imageFormat, setStillFormat: setStillFormat, frame: frame, onFrameChanged: onFrameChanged, onFrameSetDirectly: onFrameSetDirectly, container: container, setContainerFormat: setContainerFormat, setCodec: setCodecWithContainer, encodableVideoCodecs: encodableVideoCodecs, effectiveVideoCodec: effectiveVideoCodec, startFrame: finalStartFrame, setStartFrame: setStartFrame, endFrame: finalEndFrame, setEndFrame: setEndFrame, outName: outName, onOutNameChange: onOutNameChange, validationMessage: outnameValidation.valid ? null : outnameValidation.error.message, logLevel: logLevel, setLogLevel: setLogLevel })) : tab === 'data' ? (jsx_runtime_1.jsx(DataEditor_1.DataEditor, { defaultProps: inputProps, setDefaultProps: setInputProps, unresolvedComposition: unresolvedComposition, mayShowSaveButton: false, propsEditType: "input-props", saving: saving, setSaving: setSaving, readOnlyStudio: false })) : tab === 'picture' ? (jsx_runtime_1.jsx(WebRenderModalPicture_1.WebRenderModalPicture, { renderMode: renderMode, videoBitrate: videoBitrate, setVideoBitrate: setVideoBitrate, keyframeIntervalInSeconds: keyframeIntervalInSeconds, setKeyframeIntervalInSeconds: setKeyframeIntervalInSeconds, transparent: transparent, setTransparent: setTransparent, scale: scale, setScale: setScale, compositionWidth: resolvedComposition.width, compositionHeight: resolvedComposition.height })) : tab === 'audio' ? (jsx_runtime_1.jsx(WebRenderModalAudio_1.WebRenderModalAudio, { renderMode: renderMode, muted: muted, setMuted: setMuted, audioCodec: audioCodec, setAudioCodec: setAudioCodec, audioBitrate: audioBitrate, setAudioBitrate: setAudioBitrate, container: container, encodableCodecs: encodableAudioCodecs, effectiveAudioCodec: effectiveAudioCodec })) : tab === 'advanced' ? (jsx_runtime_1.jsx(WebRenderModalAdvanced_1.WebRenderModalAdvanced, { renderMode: renderMode, delayRenderTimeout: delayRenderTimeout, setDelayRenderTimeout: setDelayRenderTimeout, mediaCacheSizeInBytes: mediaCacheSizeInBytes, setMediaCacheSizeInBytes: setMediaCacheSizeInBytes, hardwareAcceleration: hardwareAcceleration, setHardwareAcceleration: setHardwareAcceleration })) : (jsx_runtime_1.jsx(WebRenderModalLicense_1.WebRenderModalLicense, { licenseKey: licenseKey, setLicenseKey: setLicenseKey, initialPublicLicenseKey: initialLicenseKey })) })
395
424
  ] })
396
425
  ] }));
397
426
  };
@@ -1,6 +1,8 @@
1
1
  import type { WebRendererAudioCodec, WebRendererContainer, WebRendererQuality } from '@remotion/web-renderer';
2
2
  import React from 'react';
3
+ import type { RenderType } from './WebRenderModal';
3
4
  export declare const WebRenderModalAudio: React.FC<{
5
+ readonly renderMode: RenderType;
4
6
  readonly muted: boolean;
5
7
  readonly setMuted: React.Dispatch<React.SetStateAction<boolean>>;
6
8
  readonly audioCodec: WebRendererAudioCodec;
@@ -34,11 +34,17 @@ const humanReadableWebAudioCodec = (audioCodec) => {
34
34
  return 'AAC';
35
35
  case 'opus':
36
36
  return 'Opus';
37
+ case 'mp3':
38
+ return 'MP3';
39
+ case 'vorbis':
40
+ return 'Vorbis';
41
+ case 'pcm-s16':
42
+ return 'Lossless (PCM)';
37
43
  default:
38
- return audioCodec;
44
+ throw new Error(`Unsupported audio codec: ${audioCodec}`);
39
45
  }
40
46
  };
41
- const WebRenderModalAudio = ({ muted, setMuted, audioCodec, setAudioCodec, audioBitrate, setAudioBitrate, container: videoContainer, encodableCodecs, effectiveAudioCodec, }) => {
47
+ const WebRenderModalAudio = ({ renderMode, muted, setMuted, audioCodec, setAudioCodec, audioBitrate, setAudioBitrate, container: videoContainer, encodableCodecs, effectiveAudioCodec, }) => {
42
48
  const containerSupported = (0, react_1.useMemo)(() => (0, web_renderer_1.getSupportedAudioCodecsForContainer)(videoContainer), [videoContainer]);
43
49
  const audioCodecOptions = (0, react_1.useMemo)(() => {
44
50
  return containerSupported.map((codec) => {
@@ -58,9 +64,9 @@ const WebRenderModalAudio = ({ muted, setMuted, audioCodec, setAudioCodec, audio
58
64
  });
59
65
  }, [containerSupported, encodableCodecs, audioCodec, setAudioCodec]);
60
66
  const audioBitrateOptions = (0, react_1.useMemo)(() => (0, quality_options_1.getQualityOptions)(audioBitrate, setAudioBitrate), [audioBitrate, setAudioBitrate]);
61
- return (jsx_runtime_1.jsxs("div", { style: container, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: [
62
- jsx_runtime_1.jsx(MutedSetting_1.MutedSetting, { enforceAudioTrack: false, muted: muted, setMuted: setMuted }), !muted ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
63
- jsx_runtime_1.jsx(RenderModalHr_1.RenderModalHr, {}), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
67
+ const isAudioOnly = renderMode === 'audio';
68
+ const showAudioSettings = isAudioOnly || !muted;
69
+ return (jsx_runtime_1.jsxs("div", { style: container, className: is_menu_item_1.VERTICAL_SCROLLBAR_CLASSNAME, children: [isAudioOnly ? null : (jsx_runtime_1.jsx(MutedSetting_1.MutedSetting, { enforceAudioTrack: false, muted: muted, setMuted: setMuted })), showAudioSettings ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [isAudioOnly ? null : jsx_runtime_1.jsx(RenderModalHr_1.RenderModalHr, {}), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
64
70
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Audio Quality",
65
71
  jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 })
66
72
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: audioBitrateOptions, selectedId: audioBitrate, title: "Audio Quality" }) })
@@ -17,6 +17,25 @@ const RenderModalOutputName_1 = require("./RenderModalOutputName");
17
17
  const tabContainer = {
18
18
  flex: 1,
19
19
  };
20
+ const containerLabels = {
21
+ mp4: 'MP4',
22
+ webm: 'WebM',
23
+ mkv: 'MKV',
24
+ mov: 'MOV',
25
+ wav: 'WAV',
26
+ mp3: 'MP3',
27
+ aac: 'AAC',
28
+ ogg: 'OGG',
29
+ };
30
+ const videoContainers = ['mp4', 'webm', 'mkv', 'mov'];
31
+ const audioContainers = ['wav', 'mp3', 'aac', 'ogg'];
32
+ const codecLabels = {
33
+ h264: 'H.264',
34
+ h265: 'H.265',
35
+ vp8: 'VP8',
36
+ vp9: 'VP9',
37
+ av1: 'AV1',
38
+ };
20
39
  const WebRenderModalBasic = ({ renderMode, resolvedComposition, imageFormat, setStillFormat, frame, onFrameChanged, onFrameSetDirectly, container, setContainerFormat, setCodec, encodableVideoCodecs, effectiveVideoCodec, startFrame, setStartFrame, endFrame, setEndFrame, outName, onOutNameChange, validationMessage, logLevel, setLogLevel, }) => {
21
40
  const imageFormatOptions = (0, react_1.useMemo)(() => {
22
41
  return [
@@ -56,38 +75,19 @@ const WebRenderModalBasic = ({ renderMode, resolvedComposition, imageFormat, set
56
75
  });
57
76
  }, [logLevel, setLogLevel]);
58
77
  const containerOptions = (0, react_1.useMemo)(() => {
59
- return [
60
- {
61
- label: 'MP4',
62
- onClick: () => setContainerFormat('mp4'),
63
- leftItem: container === 'mp4' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
64
- id: 'mp4',
65
- keyHint: null,
66
- quickSwitcherLabel: null,
67
- subMenu: null,
68
- type: 'item',
69
- value: 'mp4',
70
- },
71
- {
72
- label: 'WebM',
73
- onClick: () => setContainerFormat('webm'),
74
- leftItem: container === 'webm' ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
75
- id: 'webm',
76
- keyHint: null,
77
- quickSwitcherLabel: null,
78
- subMenu: null,
79
- type: 'item',
80
- value: 'webm',
81
- },
82
- ];
83
- }, [container, setContainerFormat]);
84
- const codecLabels = (0, react_1.useMemo)(() => ({
85
- h264: 'H.264',
86
- h265: 'H.265',
87
- vp8: 'VP8',
88
- vp9: 'VP9',
89
- av1: 'AV1',
90
- }), []);
78
+ const containers = renderMode === 'audio' ? audioContainers : videoContainers;
79
+ return containers.map((c) => ({
80
+ label: containerLabels[c],
81
+ onClick: () => setContainerFormat(c),
82
+ leftItem: container === c ? jsx_runtime_1.jsx(Checkmark_1.Checkmark, {}) : null,
83
+ id: c,
84
+ keyHint: null,
85
+ quickSwitcherLabel: null,
86
+ subMenu: null,
87
+ type: 'item',
88
+ value: c,
89
+ }));
90
+ }, [container, setContainerFormat, renderMode]);
91
91
  const codecOptions = (0, react_1.useMemo)(() => {
92
92
  return encodableVideoCodecs.map((c) => ({
93
93
  label: codecLabels[c],
@@ -100,7 +100,7 @@ const WebRenderModalBasic = ({ renderMode, resolvedComposition, imageFormat, set
100
100
  type: 'item',
101
101
  value: c,
102
102
  }));
103
- }, [encodableVideoCodecs, effectiveVideoCodec, setCodec, codecLabels]);
103
+ }, [encodableVideoCodecs, effectiveVideoCodec, setCodec]);
104
104
  return (jsx_runtime_1.jsxs("div", { style: tabContainer, children: [renderMode === 'still' ? (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
105
105
  jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
106
106
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "Format" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(SegmentedControl_1.SegmentedControl, { items: imageFormatOptions, needsWrapping: true }) })
@@ -109,11 +109,11 @@ const WebRenderModalBasic = ({ renderMode, resolvedComposition, imageFormat, set
109
109
  ] })) : null] })) : (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
110
110
  jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
111
111
  jsx_runtime_1.jsx("div", { style: layout_2.label, children: "Container" }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: containerOptions, selectedId: container, title: "Container" }) })
112
- ] }), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
112
+ ] }), renderMode === 'video' ? (jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
113
113
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Codec",
114
114
  jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "videoCodecOption" })
115
115
  ] }), jsx_runtime_1.jsx("div", { style: layout_2.rightRow, children: jsx_runtime_1.jsx(ComboBox_1.Combobox, { values: codecOptions, selectedId: effectiveVideoCodec, title: "Codec" }) })
116
- ] }), jsx_runtime_1.jsx(FrameRangeSetting_1.FrameRangeSetting, { durationInFrames: resolvedComposition.durationInFrames, startFrame: startFrame !== null && startFrame !== void 0 ? startFrame : 0, endFrame: endFrame !== null && endFrame !== void 0 ? endFrame : resolvedComposition.durationInFrames - 1, setStartFrame: setStartFrame, setEndFrame: setEndFrame })
116
+ ] })) : null, jsx_runtime_1.jsx(FrameRangeSetting_1.FrameRangeSetting, { durationInFrames: resolvedComposition.durationInFrames, startFrame: startFrame !== null && startFrame !== void 0 ? startFrame : 0, endFrame: endFrame !== null && endFrame !== void 0 ? endFrame : resolvedComposition.durationInFrames - 1, setStartFrame: setStartFrame, setEndFrame: setEndFrame })
117
117
  ] })), jsx_runtime_1.jsx(RenderModalOutputName_1.RenderModalOutputName, { existence: false, inputStyle: layout_2.input, outName: outName, onValueChange: onOutNameChange, validationMessage: validationMessage, label: window.remotion_isReadOnlyStudio ? 'Download name' : 'Output name' }), jsx_runtime_1.jsxs("div", { style: layout_2.optionRow, children: [
118
118
  jsx_runtime_1.jsxs("div", { style: layout_2.label, children: ["Log Level ",
119
119
  jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }), jsx_runtime_1.jsx(OptionExplainerBubble_1.OptionExplainerBubble, { id: "logLevelOption" })
@@ -53,7 +53,7 @@ const ClientRenderQueueProcessor = () => {
53
53
  };
54
54
  }, [getCompositionForJob]);
55
55
  const processVideoJob = (0, react_1.useCallback)(async (job, signal, onProgress) => {
56
- var _a, _b;
56
+ var _a, _b, _c;
57
57
  const compositionRef = getCompositionForJob(job.id);
58
58
  if (!compositionRef) {
59
59
  throw new Error(`Composition not found for job ${job.id}`);
@@ -74,7 +74,7 @@ const ClientRenderQueueProcessor = () => {
74
74
  delayRenderTimeoutInMilliseconds: job.delayRenderTimeout,
75
75
  mediaCacheSizeInBytes: job.mediaCacheSizeInBytes,
76
76
  logLevel: job.logLevel,
77
- videoCodec: job.videoCodec,
77
+ videoCodec: (_b = job.videoCodec) !== null && _b !== void 0 ? _b : undefined,
78
78
  audioCodec: job.audioCodec,
79
79
  audioBitrate: job.audioBitrate,
80
80
  container: job.container,
@@ -93,7 +93,7 @@ const ClientRenderQueueProcessor = () => {
93
93
  });
94
94
  },
95
95
  outputTarget: 'web-fs',
96
- licenseKey: (_b = job.licenseKey) !== null && _b !== void 0 ? _b : undefined,
96
+ licenseKey: (_c = job.licenseKey) !== null && _c !== void 0 ? _c : undefined,
97
97
  });
98
98
  return {
99
99
  getBlob,
@@ -46,7 +46,7 @@ export type ClientStillRenderJob = ClientRenderJobBase & {
46
46
  export type ClientVideoRenderJob = ClientRenderJobBase & {
47
47
  type: 'client-video';
48
48
  container: WebRendererContainer;
49
- videoCodec: WebRendererVideoCodec;
49
+ videoCodec: WebRendererVideoCodec | null;
50
50
  audioCodec: WebRendererAudioCodec;
51
51
  startFrame: number;
52
52
  endFrame: number;