@remotion/studio 4.0.433 → 4.0.435

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 (34) hide show
  1. package/dist/components/CompositionSelector.js +36 -2
  2. package/dist/components/CompositionSelectorItem.js +1 -0
  3. package/dist/components/CurrentComposition.d.ts +1 -1
  4. package/dist/components/CurrentComposition.js +2 -3
  5. package/dist/components/CurrentCompositionSideEffects.js +2 -4
  6. package/dist/components/Modals.js +3 -2
  7. package/dist/components/PreviewToolbar.js +1 -2
  8. package/dist/components/RenderButton.d.ts +1 -1
  9. package/dist/components/RenderButton.js +64 -17
  10. package/dist/components/RenderModal/ClientRenderProgress.js +13 -9
  11. package/dist/components/RenderModal/RenderModalBasic.d.ts +1 -0
  12. package/dist/components/RenderModal/RenderModalBasic.js +2 -2
  13. package/dist/components/RenderModal/ServerRenderModal.d.ts +1 -0
  14. package/dist/components/RenderModal/ServerRenderModal.js +170 -5
  15. package/dist/components/RenderModal/WebRenderModalAudio.js +3 -2
  16. package/dist/components/RenderQueue/ClientRenderQueueProcessor.js +3 -0
  17. package/dist/components/RenderQueue/RenderQueueItemStatus.js +1 -2
  18. package/dist/components/RenderQueue/RenderQueueProgressMessage.js +2 -3
  19. package/dist/components/RenderQueue/client-render-progress.d.ts +3 -0
  20. package/dist/components/RenderQueue/client-render-progress.js +31 -0
  21. package/dist/components/RenderQueue/client-side-render-types.d.ts +3 -0
  22. package/dist/components/RenderQueue/context.js +7 -1
  23. package/dist/components/SidebarRenderButton.js +1 -0
  24. package/dist/esm/{chunk-bd1bkakk.js → chunk-x88z6n54.js} +1063 -535
  25. package/dist/esm/internals.mjs +1063 -535
  26. package/dist/esm/previewEntry.mjs +634 -106
  27. package/dist/esm/renderEntry.mjs +1 -1
  28. package/dist/helpers/make-render-command.d.ts +51 -0
  29. package/dist/helpers/make-render-command.js +201 -0
  30. package/dist/helpers/retry-payload.js +3 -0
  31. package/dist/state/modals.d.ts +1 -0
  32. package/package.json +9 -9
  33. package/dist/helpers/should-show-render-button.d.ts +0 -1
  34. package/dist/helpers/should-show-render-button.js +0 -11
@@ -3787,6 +3787,15 @@ var sortItemsByNonceHistory = (items) => {
3787
3787
  return result;
3788
3788
  };
3789
3789
 
3790
+ // src/state/modals.ts
3791
+ import { createContext as createContext9 } from "react";
3792
+ var ModalsContext = createContext9({
3793
+ selectedModal: null,
3794
+ setSelectedModal: () => {
3795
+ return;
3796
+ }
3797
+ });
3798
+
3790
3799
  // src/components/CompositionSelectorItem.tsx
3791
3800
  import { useCallback as useCallback23, useContext as useContext8, useMemo as useMemo26, useState as useState20 } from "react";
3792
3801
 
@@ -3850,15 +3859,6 @@ var FilmIcon = ({ color, ...props }) => {
3850
3859
  });
3851
3860
  };
3852
3861
 
3853
- // src/state/modals.ts
3854
- import { createContext as createContext9 } from "react";
3855
- var ModalsContext = createContext9({
3856
- selectedModal: null,
3857
- setSelectedModal: () => {
3858
- return;
3859
- }
3860
- });
3861
-
3862
3862
  // src/components/CompositionContextButton.tsx
3863
3863
  import { useCallback as useCallback20, useContext as useContext6, useMemo as useMemo23 } from "react";
3864
3864
 
@@ -3977,7 +3977,13 @@ var RenderQueueContextProvider = ({ children }) => {
3977
3977
  setClientJobs((prev) => prev.map((job) => job.id === nextJob.id ? {
3978
3978
  ...job,
3979
3979
  status: "running",
3980
- progress: { encodedFrames: 0, totalFrames: 0 }
3980
+ progress: {
3981
+ encodedFrames: 0,
3982
+ totalFrames: 0,
3983
+ doneIn: null,
3984
+ renderEstimatedTime: 0,
3985
+ progress: 0
3986
+ }
3981
3987
  } : job));
3982
3988
  processJobCallbackRef.current(nextJob);
3983
3989
  }, [clientJobs, currentlyProcessing]);
@@ -5150,7 +5156,8 @@ var SidebarRenderButton = ({ composition, visible }) => {
5150
5156
  initialChromeMode: defaults.chromeMode,
5151
5157
  initialMediaCacheSizeInBytes: defaults.mediaCacheSizeInBytes,
5152
5158
  renderDefaults: defaults,
5153
- initialDarkMode: defaults.darkMode
5159
+ initialDarkMode: defaults.darkMode,
5160
+ readOnlyStudio: false
5154
5161
  });
5155
5162
  if (isMobileLayout) {
5156
5163
  setSidebarCollapsedState({ left: "collapsed", right: "collapsed" });
@@ -5196,7 +5203,8 @@ var itemStyle = {
5196
5203
  width: "100%",
5197
5204
  textAlign: "left",
5198
5205
  backgroundColor: BACKGROUND,
5199
- height: COMPOSITION_ITEM_HEIGHT
5206
+ height: COMPOSITION_ITEM_HEIGHT,
5207
+ userSelect: "none"
5200
5208
  };
5201
5209
  var labelStyle2 = {
5202
5210
  textAlign: "left",
@@ -5448,12 +5456,11 @@ var renderFrame = (frame2, fps) => {
5448
5456
 
5449
5457
  // src/components/CurrentComposition.tsx
5450
5458
  import { jsx as jsx53, jsxs as jsxs22 } from "react/jsx-runtime";
5451
- var CURRENT_COMPOSITION_HEIGHT = 80;
5459
+ var CURRENT_COMPOSITION_HEIGHT = 64;
5452
5460
  var container11 = {
5453
5461
  height: CURRENT_COMPOSITION_HEIGHT,
5454
5462
  display: "block",
5455
- borderBottom: `1px solid ${BORDER_COLOR}`,
5456
- padding: 12,
5463
+ padding: "6px 12px",
5457
5464
  color: "white",
5458
5465
  backgroundColor: BACKGROUND
5459
5466
  };
@@ -5555,6 +5562,29 @@ var container12 = {
5555
5562
  overflow: "hidden",
5556
5563
  backgroundColor: BACKGROUND
5557
5564
  };
5565
+ var QUICK_SWITCHER_TRIGGER_HEIGHT = 38;
5566
+ var quickSwitcherArea = {
5567
+ padding: "4px 12px",
5568
+ borderBottom: `1px solid ${BORDER_COLOR}`
5569
+ };
5570
+ var quickSwitcherTrigger = {
5571
+ backgroundColor: "rgba(255, 255, 255, 0.06)",
5572
+ borderRadius: 5,
5573
+ padding: "4px 10px",
5574
+ color: LIGHT_TEXT,
5575
+ fontSize: 12,
5576
+ cursor: "pointer",
5577
+ display: "flex",
5578
+ alignItems: "center",
5579
+ justifyContent: "space-between",
5580
+ border: "none",
5581
+ width: "100%",
5582
+ appearance: "none"
5583
+ };
5584
+ var shortcutLabel = {
5585
+ fontSize: 11,
5586
+ opacity: 0.6
5587
+ };
5558
5588
  var getKeysToExpand = (initialFolderName, parentFolderName, initial = []) => {
5559
5589
  initial.push(openFolderKey({
5560
5590
  folderName: initialFolderName,
@@ -5569,6 +5599,7 @@ var getKeysToExpand = (initialFolderName, parentFolderName, initial = []) => {
5569
5599
  var CompositionSelector = () => {
5570
5600
  const { compositions, canvasContent, folders } = useContext9(Internals9.CompositionManager);
5571
5601
  const { foldersExpanded } = useContext9(ExpandedFoldersContext);
5602
+ const { setSelectedModal } = useContext9(ModalsContext);
5572
5603
  const { tabIndex } = useZIndex();
5573
5604
  const selectComposition = useSelectComposition();
5574
5605
  const sortedCompositions = useMemo27(() => {
@@ -5582,17 +5613,43 @@ var CompositionSelector = () => {
5582
5613
  }, [sortedCompositions, sortedFolders, foldersExpanded]);
5583
5614
  const list = useMemo27(() => {
5584
5615
  return {
5585
- height: `calc(100% - ${CURRENT_COMPOSITION_HEIGHT}px)`,
5616
+ height: `calc(100% - ${CURRENT_COMPOSITION_HEIGHT}px - ${QUICK_SWITCHER_TRIGGER_HEIGHT}px)`,
5586
5617
  overflowY: "auto"
5587
5618
  };
5588
5619
  }, []);
5589
5620
  const toggleFolder = useCallback24((folderName, parentName) => {
5590
5621
  Internals9.compositionSelectorRef.current?.toggleFolder(folderName, parentName);
5591
5622
  }, []);
5623
+ const openQuickSwitcher = useCallback24(() => {
5624
+ setSelectedModal({
5625
+ type: "quick-switcher",
5626
+ mode: "compositions",
5627
+ invocationTimestamp: Date.now()
5628
+ });
5629
+ }, [setSelectedModal]);
5592
5630
  return /* @__PURE__ */ jsxs23("div", {
5593
5631
  style: container12,
5594
5632
  children: [
5595
5633
  /* @__PURE__ */ jsx54(CurrentComposition, {}),
5634
+ /* @__PURE__ */ jsx54("div", {
5635
+ style: quickSwitcherArea,
5636
+ children: /* @__PURE__ */ jsxs23("button", {
5637
+ type: "button",
5638
+ style: quickSwitcherTrigger,
5639
+ onClick: openQuickSwitcher,
5640
+ tabIndex,
5641
+ children: [
5642
+ "Search...",
5643
+ areKeyboardShortcutsDisabled() ? null : /* @__PURE__ */ jsxs23("span", {
5644
+ style: shortcutLabel,
5645
+ children: [
5646
+ cmdOrCtrlCharacter,
5647
+ "+K"
5648
+ ]
5649
+ })
5650
+ ]
5651
+ })
5652
+ }),
5596
5653
  /* @__PURE__ */ jsx54("div", {
5597
5654
  className: "__remotion-vertical-scrollbar",
5598
5655
  style: list,
@@ -11490,10 +11547,7 @@ var CurrentCompositionKeybindings = ({ readOnlyStudio }) => {
11490
11547
  if (!video) {
11491
11548
  return;
11492
11549
  }
11493
- if (readOnlyStudio) {
11494
- return showNotification("Studio is read-only", 2000);
11495
- }
11496
- if (type !== "connected") {
11550
+ if (type !== "connected" && !SHOW_BROWSER_RENDERING && !readOnlyStudio) {
11497
11551
  showNotification("Studio server is offline", 2000);
11498
11552
  return;
11499
11553
  }
@@ -17296,7 +17350,10 @@ var ClientRenderQueueProcessor = () => {
17296
17350
  onProgress: (progress) => {
17297
17351
  onProgress(job.id, {
17298
17352
  encodedFrames: progress.encodedFrames,
17299
- totalFrames
17353
+ totalFrames,
17354
+ doneIn: progress.doneIn,
17355
+ renderEstimatedTime: progress.renderEstimatedTime,
17356
+ progress: progress.progress
17300
17357
  });
17301
17358
  },
17302
17359
  outputTarget: "web-fs",
@@ -17587,8 +17644,7 @@ var RenderQueueItemStatus = ({ job }) => {
17587
17644
  if (job.status === "running") {
17588
17645
  let progressValue;
17589
17646
  if (isClientJob) {
17590
- const { encodedFrames, totalFrames } = job.progress;
17591
- progressValue = totalFrames > 0 ? encodedFrames / totalFrames : 0;
17647
+ progressValue = job.progress.progress;
17592
17648
  } else {
17593
17649
  progressValue = job.progress.value;
17594
17650
  }
@@ -17680,6 +17736,34 @@ var RenderQueueOutputName = ({ job }) => {
17680
17736
 
17681
17737
  // src/components/RenderQueue/RenderQueueProgressMessage.tsx
17682
17738
  import { useCallback as useCallback78, useContext as useContext43 } from "react";
17739
+
17740
+ // src/components/RenderQueue/client-render-progress.ts
17741
+ var formatEtaString = (timeRemainingInMilliseconds) => {
17742
+ const remainingTime = timeRemainingInMilliseconds / 1000;
17743
+ const remainingTimeHours = Math.floor(remainingTime / 3600);
17744
+ const remainingTimeMinutes = Math.floor(remainingTime % 3600 / 60);
17745
+ const remainingTimeSeconds = Math.floor(remainingTime % 60);
17746
+ return [
17747
+ remainingTimeHours ? `${remainingTimeHours}h` : null,
17748
+ remainingTimeMinutes ? `${remainingTimeMinutes}m` : null,
17749
+ `${remainingTimeSeconds}s`
17750
+ ].filter((value) => Boolean(value)).join(" ");
17751
+ };
17752
+ var getClientRenderProgressMessage = (progress) => {
17753
+ if (progress.totalFrames === 0) {
17754
+ return "Getting composition";
17755
+ }
17756
+ if (progress.doneIn !== null) {
17757
+ return `Encoded ${progress.totalFrames}/${progress.totalFrames}`;
17758
+ }
17759
+ if (progress.renderEstimatedTime > 0) {
17760
+ const etaString = `, time remaining: ${formatEtaString(progress.renderEstimatedTime)}`;
17761
+ return `Rendering ${progress.encodedFrames}/${progress.totalFrames}${etaString}`;
17762
+ }
17763
+ return `Encoded ${progress.encodedFrames}/${progress.totalFrames}`;
17764
+ };
17765
+
17766
+ // src/components/RenderQueue/RenderQueueProgressMessage.tsx
17683
17767
  import { jsx as jsx156 } from "react/jsx-runtime";
17684
17768
  var outputLocation2 = {
17685
17769
  ...renderQueueItemSubtitleStyle
@@ -17697,7 +17781,7 @@ var RenderQueueProgressMessage = ({ job }) => {
17697
17781
  jobId: job.id
17698
17782
  });
17699
17783
  }, [job.id, setSelectedModal]);
17700
- const message = isClientJob ? job.progress.totalFrames === 0 ? "Getting composition" : `Encoding frame ${job.progress.encodedFrames}/${job.progress.totalFrames}` : job.progress.message;
17784
+ const message = isClientJob ? getClientRenderProgressMessage(job.progress) : job.progress.message;
17701
17785
  return /* @__PURE__ */ jsx156("button", {
17702
17786
  onClick,
17703
17787
  type: "button",
@@ -17816,7 +17900,8 @@ var makeRetryPayload = (job) => {
17816
17900
  initialHardwareAcceleration: defaults.hardwareAcceleration,
17817
17901
  initialChromeMode: job.chromeMode,
17818
17902
  initialMediaCacheSizeInBytes: job.mediaCacheSizeInBytes,
17819
- renderDefaults: defaults
17903
+ renderDefaults: defaults,
17904
+ readOnlyStudio: false
17820
17905
  };
17821
17906
  }
17822
17907
  if (job.type === "sequence") {
@@ -17868,7 +17953,8 @@ var makeRetryPayload = (job) => {
17868
17953
  initialHardwareAcceleration: defaults.hardwareAcceleration,
17869
17954
  initialChromeMode: job.chromeMode,
17870
17955
  initialMediaCacheSizeInBytes: job.mediaCacheSizeInBytes,
17871
- renderDefaults: defaults
17956
+ renderDefaults: defaults,
17957
+ readOnlyStudio: false
17872
17958
  };
17873
17959
  }
17874
17960
  if (job.type === "video") {
@@ -17920,7 +18006,8 @@ var makeRetryPayload = (job) => {
17920
18006
  initialHardwareAcceleration: job.hardwareAcceleration,
17921
18007
  initialChromeMode: job.chromeMode,
17922
18008
  initialMediaCacheSizeInBytes: job.mediaCacheSizeInBytes,
17923
- renderDefaults: defaults
18009
+ renderDefaults: defaults,
18010
+ readOnlyStudio: false
17924
18011
  };
17925
18012
  }
17926
18013
  throw new Error(`Job ${JSON.stringify(job)} Not implemented`);
@@ -18813,14 +18900,6 @@ var OptionsPanel = ({ readOnlyStudio }) => {
18813
18900
  import { useContext as useContext57, useEffect as useEffect61, useRef as useRef32, useState as useState64 } from "react";
18814
18901
  import { Internals as Internals45 } from "remotion";
18815
18902
 
18816
- // src/helpers/should-show-render-button.ts
18817
- var shouldShowRenderButton = (readOnlyStudio) => {
18818
- if (readOnlyStudio) {
18819
- return SHOW_BROWSER_RENDERING;
18820
- }
18821
- return true;
18822
- };
18823
-
18824
18903
  // src/state/loop.ts
18825
18904
  var key = "remotion.loop";
18826
18905
  var persistLoopOption = (option) => {
@@ -19681,7 +19760,7 @@ var label8 = {
19681
19760
  var RENDER_TYPE_STORAGE_KEY = "remotion.renderType";
19682
19761
  var getInitialRenderType = (readOnlyStudio) => {
19683
19762
  if (!SHOW_BROWSER_RENDERING) {
19684
- return "server-render";
19763
+ return readOnlyStudio ? "render-command" : "server-render";
19685
19764
  }
19686
19765
  if (readOnlyStudio) {
19687
19766
  return "client-render";
@@ -19738,8 +19817,15 @@ var RenderButton = ({
19738
19817
  });
19739
19818
  }, [refresh]);
19740
19819
  const connectionStatus = useContext55(StudioServerConnectionCtx).previewServerState.type;
19741
- const canRender = connectionStatus === "connected" || SHOW_BROWSER_RENDERING;
19820
+ const canServerRender = connectionStatus === "connected";
19821
+ const canRender = canServerRender || SHOW_BROWSER_RENDERING || readOnlyStudio;
19742
19822
  const renderType = useMemo95(() => {
19823
+ if (readOnlyStudio) {
19824
+ if (!SHOW_BROWSER_RENDERING) {
19825
+ return "render-command";
19826
+ }
19827
+ return preferredRenderType === "render-command" ? "render-command" : "client-render";
19828
+ }
19743
19829
  if (connectionStatus === "disconnected" && SHOW_BROWSER_RENDERING) {
19744
19830
  return "client-render";
19745
19831
  }
@@ -19747,9 +19833,9 @@ var RenderButton = ({
19747
19833
  return "server-render";
19748
19834
  }
19749
19835
  return preferredRenderType;
19750
- }, [connectionStatus, preferredRenderType]);
19836
+ }, [connectionStatus, preferredRenderType, readOnlyStudio]);
19751
19837
  const shortcut = areKeyboardShortcutsDisabled() ? "" : "(R)";
19752
- const tooltip = canRender ? "Export the current composition " + shortcut : "Connect to the Studio server to render";
19838
+ const tooltip = renderType === "render-command" ? "Copy a CLI command to render this composition " + shortcut : canRender ? "Export the current composition " + shortcut : "Connect to the Studio server to render";
19753
19839
  const iconStyle4 = useMemo95(() => {
19754
19840
  return {
19755
19841
  style: {
@@ -19761,7 +19847,7 @@ var RenderButton = ({
19761
19847
  const video = Internals43.useVideo();
19762
19848
  const getCurrentFrame2 = PlayerInternals14.useFrameImperative();
19763
19849
  const { props } = useContext55(Internals43.EditorPropsContext);
19764
- const openServerRenderModal = useCallback91(() => {
19850
+ const openServerRenderModal = useCallback91((copyCommandOnly) => {
19765
19851
  if (!video) {
19766
19852
  return null;
19767
19853
  }
@@ -19771,6 +19857,7 @@ var RenderButton = ({
19771
19857
  }
19772
19858
  setSelectedModal({
19773
19859
  type: "server-render",
19860
+ readOnlyStudio: copyCommandOnly,
19774
19861
  compositionId: video.id,
19775
19862
  initialFrame: getCurrentFrame2(),
19776
19863
  initialStillImageFormat: defaults.stillImageFormat,
@@ -19854,8 +19941,12 @@ var RenderButton = ({
19854
19941
  });
19855
19942
  }, [video, setSelectedModal, getCurrentFrame2, props, inFrame, outFrame]);
19856
19943
  const onClick = useCallback91(() => {
19944
+ if (renderType === "render-command") {
19945
+ openServerRenderModal(true);
19946
+ return;
19947
+ }
19857
19948
  if (!SHOW_BROWSER_RENDERING || renderType === "server-render") {
19858
- openServerRenderModal();
19949
+ openServerRenderModal(false);
19859
19950
  } else {
19860
19951
  openClientRenderModal();
19861
19952
  }
@@ -19870,12 +19961,40 @@ var RenderButton = ({
19870
19961
  } catch {}
19871
19962
  setDropdownOpened(false);
19872
19963
  if (newType === "server-render") {
19873
- openServerRenderModal();
19964
+ openServerRenderModal(false);
19965
+ } else if (newType === "render-command") {
19966
+ openServerRenderModal(true);
19874
19967
  } else {
19875
19968
  openClientRenderModal();
19876
19969
  }
19877
- }, [openServerRenderModal, openClientRenderModal]);
19970
+ }, [openClientRenderModal, openServerRenderModal]);
19878
19971
  const dropdownValues = useMemo95(() => {
19972
+ if (readOnlyStudio) {
19973
+ return [
19974
+ {
19975
+ type: "item",
19976
+ id: "client-render",
19977
+ label: "Render on web",
19978
+ value: "client-render",
19979
+ onClick: () => handleRenderTypeChange("client-render"),
19980
+ keyHint: null,
19981
+ leftItem: null,
19982
+ subMenu: null,
19983
+ quickSwitcherLabel: null
19984
+ },
19985
+ {
19986
+ type: "item",
19987
+ id: "render-command",
19988
+ label: "Render via CLI",
19989
+ value: "render-command",
19990
+ onClick: () => handleRenderTypeChange("render-command"),
19991
+ keyHint: null,
19992
+ leftItem: null,
19993
+ subMenu: null,
19994
+ quickSwitcherLabel: null
19995
+ }
19996
+ ];
19997
+ }
19879
19998
  return [
19880
19999
  {
19881
20000
  type: "item",
@@ -19900,7 +20019,7 @@ var RenderButton = ({
19900
20019
  quickSwitcherLabel: null
19901
20020
  }
19902
20021
  ];
19903
- }, [handleRenderTypeChange]);
20022
+ }, [handleRenderTypeChange, readOnlyStudio]);
19904
20023
  const spaceToBottom = useMemo95(() => {
19905
20024
  const margin2 = 10;
19906
20025
  if (size4 && dropdownOpened) {
@@ -19942,10 +20061,10 @@ var RenderButton = ({
19942
20061
  cursor: canRender ? "pointer" : "inherit"
19943
20062
  };
19944
20063
  }, [canRender]);
19945
- const renderLabel = renderType === "server-render" ? "Render" : "Render on web";
20064
+ const renderLabel = renderType === "server-render" ? "Render" : renderType === "render-command" ? "Render via CLI" : "Render on web";
19946
20065
  const shouldShowDropdown = useMemo95(() => {
19947
20066
  if (readOnlyStudio) {
19948
- return false;
20067
+ return SHOW_BROWSER_RENDERING;
19949
20068
  }
19950
20069
  if (!SHOW_BROWSER_RENDERING) {
19951
20070
  return false;
@@ -19960,8 +20079,8 @@ var RenderButton = ({
19960
20079
  /* @__PURE__ */ jsx180("button", {
19961
20080
  style: { display: "none" },
19962
20081
  id: "render-modal-button-server",
19963
- disabled: !canRender,
19964
- onClick: openServerRenderModal,
20082
+ disabled: !canServerRender,
20083
+ onClick: () => openServerRenderModal(false),
19965
20084
  type: "button"
19966
20085
  }),
19967
20086
  " ",
@@ -20009,7 +20128,7 @@ var RenderButton = ({
20009
20128
  ref: dropdownRef,
20010
20129
  type: "button",
20011
20130
  style: dropdownTriggerStyle,
20012
- disabled: connectionStatus !== "connected",
20131
+ disabled: !readOnlyStudio && connectionStatus !== "connected",
20013
20132
  className: MENU_INITIATOR_CLASSNAME,
20014
20133
  onPointerDown,
20015
20134
  onClick: onClickDropdown,
@@ -20348,9 +20467,9 @@ var PreviewToolbar = ({ readOnlyStudio, bufferStateDelayInMilliseconds }) => {
20348
20467
  /* @__PURE__ */ jsx183(Spacing, {
20349
20468
  x: 2
20350
20469
  }),
20351
- shouldShowRenderButton(readOnlyStudio) ? /* @__PURE__ */ jsx183(RenderButton, {
20470
+ /* @__PURE__ */ jsx183(RenderButton, {
20352
20471
  readOnlyStudio
20353
- }) : null,
20472
+ }),
20354
20473
  /* @__PURE__ */ jsx183(Spacing, {
20355
20474
  x: 1.5
20356
20475
  })
@@ -27514,9 +27633,15 @@ var right3 = {
27514
27633
  textAlign: "right",
27515
27634
  flex: 1
27516
27635
  };
27517
- var EncodingProgress = ({ encodedFrames, totalFrames }) => {
27518
- const done = encodedFrames === totalFrames;
27519
- const progress = totalFrames > 0 ? encodedFrames / totalFrames : 0;
27636
+ var ProgressStatus = ({ encodedFrames, totalFrames, doneIn, renderEstimatedTime, progress }) => {
27637
+ const done = doneIn !== null;
27638
+ const message = getClientRenderProgressMessage({
27639
+ encodedFrames,
27640
+ totalFrames,
27641
+ doneIn,
27642
+ renderEstimatedTime,
27643
+ progress
27644
+ });
27520
27645
  return /* @__PURE__ */ jsxs121("div", {
27521
27646
  style: progressItem,
27522
27647
  children: [
@@ -27528,8 +27653,15 @@ var EncodingProgress = ({ encodedFrames, totalFrames }) => {
27528
27653
  }),
27529
27654
  /* @__PURE__ */ jsx240("div", {
27530
27655
  style: label11,
27531
- children: done ? `Encoded ${totalFrames} frames` : `Encoding ${encodedFrames} / ${totalFrames} frames`
27532
- })
27656
+ children: message
27657
+ }),
27658
+ doneIn !== null ? /* @__PURE__ */ jsxs121("div", {
27659
+ style: right3,
27660
+ children: [
27661
+ doneIn,
27662
+ "ms"
27663
+ ]
27664
+ }) : null
27533
27665
  ]
27534
27666
  });
27535
27667
  };
@@ -27587,15 +27719,18 @@ var ClientRenderProgress = ({ job }) => {
27587
27719
  ]
27588
27720
  });
27589
27721
  }
27590
- const { encodedFrames, totalFrames } = job.progress;
27722
+ const { encodedFrames, totalFrames, doneIn, renderEstimatedTime, progress } = job.progress;
27591
27723
  return /* @__PURE__ */ jsxs121("div", {
27592
27724
  children: [
27593
27725
  /* @__PURE__ */ jsx240(Spacing, {
27594
27726
  y: 0.5
27595
27727
  }),
27596
- job.type === "client-video" && /* @__PURE__ */ jsx240(EncodingProgress, {
27728
+ job.type === "client-video" && /* @__PURE__ */ jsx240(ProgressStatus, {
27597
27729
  encodedFrames,
27598
- totalFrames
27730
+ totalFrames,
27731
+ doneIn,
27732
+ renderEstimatedTime,
27733
+ progress
27599
27734
  }),
27600
27735
  /* @__PURE__ */ jsx240(Spacing, {
27601
27736
  y: 1
@@ -27953,7 +28088,7 @@ var RenderStatusModal = ({
27953
28088
  };
27954
28089
 
27955
28090
  // src/components/RenderModal/ServerRenderModal.tsx
27956
- import { BrowserSafeApis as BrowserSafeApis10 } from "@remotion/renderer/client";
28091
+ import { BrowserSafeApis as BrowserSafeApis11 } from "@remotion/renderer/client";
27957
28092
  import { getDefaultOutLocation } from "@remotion/studio-shared";
27958
28093
  import {
27959
28094
  useCallback as useCallback137,
@@ -27979,6 +28114,252 @@ var envVariablesArrayToObject = (envVariables) => {
27979
28114
  }, {});
27980
28115
  };
27981
28116
 
28117
+ // src/helpers/make-render-command.ts
28118
+ import { BrowserSafeApis } from "@remotion/renderer/client";
28119
+ var shellQuote = (value) => {
28120
+ return `'${value.replace(/'/g, "'\\''")}'`;
28121
+ };
28122
+ var addFlagWithValue = (flags, flag, value) => {
28123
+ if (value === null || value === undefined) {
28124
+ return;
28125
+ }
28126
+ flags.push(`--${flag}=${shellQuote(String(value))}`);
28127
+ };
28128
+ var addBooleanFlag = (flags, flag, value) => {
28129
+ if (value) {
28130
+ flags.push(`--${flag}`);
28131
+ }
28132
+ };
28133
+ var valueFlag = (flag, value, defaultValue) => {
28134
+ return { flag, value, defaultValue };
28135
+ };
28136
+ var booleanFlag = (flag, value, defaultValue) => {
28137
+ return { flag, value, defaultValue };
28138
+ };
28139
+ var addValueFlagsIfChanged = (flags, mappings) => {
28140
+ for (const mapping of mappings) {
28141
+ if (mapping.value !== mapping.defaultValue) {
28142
+ addFlagWithValue(flags, mapping.flag, mapping.value);
28143
+ }
28144
+ }
28145
+ };
28146
+ var addTrueBooleanFlagsIfChanged = (flags, mappings) => {
28147
+ for (const mapping of mappings) {
28148
+ if (mapping.value && mapping.value !== mapping.defaultValue) {
28149
+ addBooleanFlag(flags, mapping.flag, true);
28150
+ }
28151
+ }
28152
+ };
28153
+ var getNpmRemotionCommandPrefix = (version) => {
28154
+ return version.trim() === "" ? "bunx --yes --location=global -p @remotion/cli remotion" : `bunx --yes --location=global -p @remotion/cli@${version} remotion`;
28155
+ };
28156
+ var normalizeServeUrlForRenderCommand = ({
28157
+ locationHref,
28158
+ compositionId
28159
+ }) => {
28160
+ const parsed = new URL(locationHref);
28161
+ parsed.hash = "";
28162
+ parsed.search = "";
28163
+ const suffix2 = `/${compositionId}`;
28164
+ if (parsed.pathname === suffix2) {
28165
+ parsed.pathname = "/";
28166
+ } else if (parsed.pathname.endsWith(suffix2)) {
28167
+ const basePath = parsed.pathname.slice(0, -suffix2.length);
28168
+ parsed.pathname = basePath === "" ? "/" : basePath;
28169
+ }
28170
+ if (parsed.pathname !== "/" && parsed.pathname.endsWith("/")) {
28171
+ parsed.pathname = parsed.pathname.slice(0, -1);
28172
+ }
28173
+ return `${parsed.origin}${parsed.pathname === "/" ? "" : parsed.pathname}`;
28174
+ };
28175
+ var trimDefaultOutPrefix = (outName) => {
28176
+ if (outName.startsWith("out/")) {
28177
+ const trimmed = outName.slice("out/".length);
28178
+ return trimmed.length === 0 ? outName : trimmed;
28179
+ }
28180
+ if (outName.startsWith("./out/")) {
28181
+ const trimmed = outName.slice("./out/".length);
28182
+ return trimmed.length === 0 ? outName : trimmed;
28183
+ }
28184
+ return outName;
28185
+ };
28186
+ var getRenderMediaFlag = (option) => {
28187
+ return BrowserSafeApis.optionsMap.renderMedia[option].cliFlag;
28188
+ };
28189
+ var renderMediaValueFlag = (option, value, defaultValue) => {
28190
+ return valueFlag(getRenderMediaFlag(option), value, defaultValue);
28191
+ };
28192
+ var renderMediaBooleanFlag = (option, value, defaultValue) => {
28193
+ return booleanFlag(getRenderMediaFlag(option), value, defaultValue);
28194
+ };
28195
+ var makeReadOnlyStudioRenderCommand = ({
28196
+ remotionVersion,
28197
+ locationHref,
28198
+ compositionId,
28199
+ outName,
28200
+ renderMode,
28201
+ renderDefaults,
28202
+ durationInFrames,
28203
+ concurrency,
28204
+ frame: frame2,
28205
+ startFrame,
28206
+ endFrame,
28207
+ stillImageFormat,
28208
+ sequenceImageFormat,
28209
+ videoImageFormat,
28210
+ jpegQuality,
28211
+ codec,
28212
+ muted,
28213
+ enforceAudioTrack,
28214
+ proResProfile,
28215
+ x264Preset,
28216
+ pixelFormat,
28217
+ crf,
28218
+ videoBitrate,
28219
+ audioBitrate,
28220
+ audioCodec,
28221
+ everyNthFrame,
28222
+ numberOfGifLoops,
28223
+ disallowParallelEncoding,
28224
+ encodingBufferSize,
28225
+ encodingMaxRate,
28226
+ forSeamlessAacConcatenation,
28227
+ separateAudioTo,
28228
+ colorSpace,
28229
+ scale,
28230
+ logLevel,
28231
+ delayRenderTimeout,
28232
+ hardwareAcceleration,
28233
+ chromeMode,
28234
+ headless,
28235
+ disableWebSecurity,
28236
+ ignoreCertificateErrors,
28237
+ gl,
28238
+ userAgent,
28239
+ multiProcessOnLinux,
28240
+ darkMode,
28241
+ offthreadVideoCacheSizeInBytes,
28242
+ offthreadVideoThreads,
28243
+ mediaCacheSizeInBytes,
28244
+ beepOnFinish,
28245
+ repro,
28246
+ metadata,
28247
+ envVariables,
28248
+ inputProps
28249
+ }) => {
28250
+ const serveUrl = normalizeServeUrlForRenderCommand({
28251
+ locationHref,
28252
+ compositionId
28253
+ });
28254
+ const isStillRender = renderMode === "still";
28255
+ const isSequenceRender = renderMode === "sequence";
28256
+ const hasCodecSpecificOptions = !isSequenceRender;
28257
+ const commandType = isStillRender ? "still" : "render";
28258
+ const command = getNpmRemotionCommandPrefix(remotionVersion);
28259
+ const { options } = BrowserSafeApis;
28260
+ const flags = [];
28261
+ addValueFlagsIfChanged(flags, [
28262
+ valueFlag(options.scaleOption.cliFlag, scale, renderDefaults.scale),
28263
+ renderMediaValueFlag("logLevel", logLevel, renderDefaults.logLevel),
28264
+ renderMediaValueFlag("timeoutInMilliseconds", delayRenderTimeout, renderDefaults.delayRenderTimeout),
28265
+ renderMediaValueFlag("chromeMode", chromeMode, renderDefaults.chromeMode),
28266
+ valueFlag(options.glOption.cliFlag, gl, renderDefaults.openGlRenderer),
28267
+ valueFlag(options.userAgentOption.cliFlag, userAgent, renderDefaults.userAgent),
28268
+ renderMediaValueFlag("offthreadVideoCacheSizeInBytes", offthreadVideoCacheSizeInBytes, renderDefaults.offthreadVideoCacheSizeInBytes),
28269
+ renderMediaValueFlag("offthreadVideoThreads", offthreadVideoThreads, renderDefaults.offthreadVideoThreads),
28270
+ renderMediaValueFlag("mediaCacheSizeInBytes", mediaCacheSizeInBytes, renderDefaults.mediaCacheSizeInBytes)
28271
+ ]);
28272
+ if (headless !== renderDefaults.headless) {
28273
+ addFlagWithValue(flags, options.headlessOption.cliFlag, !headless);
28274
+ }
28275
+ addTrueBooleanFlagsIfChanged(flags, [
28276
+ booleanFlag(options.disableWebSecurityOption.cliFlag, disableWebSecurity, renderDefaults.disableWebSecurity),
28277
+ booleanFlag(options.ignoreCertificateErrorsOption.cliFlag, ignoreCertificateErrors, renderDefaults.ignoreCertificateErrors),
28278
+ booleanFlag(options.enableMultiprocessOnLinuxOption.cliFlag, multiProcessOnLinux, renderDefaults.multiProcessOnLinux),
28279
+ booleanFlag(options.darkModeOption.cliFlag, darkMode, renderDefaults.darkMode),
28280
+ booleanFlag(options.beepOnFinishOption.cliFlag, beepOnFinish, renderDefaults.beepOnFinish)
28281
+ ]);
28282
+ if (isStillRender) {
28283
+ addValueFlagsIfChanged(flags, [
28284
+ valueFlag(options.stillFrameOption.cliFlag, frame2, 0),
28285
+ valueFlag(options.stillImageFormatOption.cliFlag, stillImageFormat, renderDefaults.stillImageFormat),
28286
+ valueFlag(options.jpegQualityOption.cliFlag, jpegQuality, renderDefaults.jpegQuality)
28287
+ ]);
28288
+ } else {
28289
+ addValueFlagsIfChanged(flags, [
28290
+ valueFlag(options.concurrencyOption.cliFlag, concurrency, renderDefaults.concurrency)
28291
+ ]);
28292
+ if (isSequenceRender) {
28293
+ addBooleanFlag(flags, options.imageSequenceOption.cliFlag, true);
28294
+ if (sequenceImageFormat !== "jpeg") {
28295
+ addFlagWithValue(flags, options.videoImageFormatOption.cliFlag, sequenceImageFormat);
28296
+ }
28297
+ } else {
28298
+ addValueFlagsIfChanged(flags, [
28299
+ valueFlag(options.videoImageFormatOption.cliFlag, videoImageFormat, renderDefaults.videoImageFormat),
28300
+ renderMediaValueFlag("hardwareAcceleration", hardwareAcceleration, renderDefaults.hardwareAcceleration)
28301
+ ]);
28302
+ }
28303
+ if (hasCodecSpecificOptions && codec !== renderDefaults.codec) {
28304
+ addFlagWithValue(flags, getRenderMediaFlag("codec"), codec);
28305
+ }
28306
+ if (startFrame !== 0 || endFrame !== durationInFrames - 1) {
28307
+ addFlagWithValue(flags, options.framesOption.cliFlag, `${startFrame}-${endFrame}`);
28308
+ }
28309
+ if (hasCodecSpecificOptions) {
28310
+ addTrueBooleanFlagsIfChanged(flags, [
28311
+ renderMediaBooleanFlag("muted", muted, renderDefaults.muted),
28312
+ booleanFlag(options.enforceAudioOption.cliFlag, enforceAudioTrack, renderDefaults.enforceAudioTrack),
28313
+ renderMediaBooleanFlag("forSeamlessAacConcatenation", forSeamlessAacConcatenation, renderDefaults.forSeamlessAacConcatenation)
28314
+ ]);
28315
+ addValueFlagsIfChanged(flags, [
28316
+ valueFlag(options.pixelFormatOption.cliFlag, pixelFormat, renderDefaults.pixelFormat),
28317
+ renderMediaValueFlag("colorSpace", colorSpace, renderDefaults.colorSpace),
28318
+ valueFlag(options.proResProfileOption.cliFlag, proResProfile, renderDefaults.proResProfile),
28319
+ renderMediaValueFlag("x264Preset", x264Preset, renderDefaults.x264Preset),
28320
+ valueFlag(options.crfOption.cliFlag, crf, null),
28321
+ valueFlag(options.jpegQualityOption.cliFlag, jpegQuality, renderDefaults.jpegQuality),
28322
+ renderMediaValueFlag("videoBitrate", videoBitrate, renderDefaults.videoBitrate),
28323
+ renderMediaValueFlag("audioBitrate", audioBitrate, renderDefaults.audioBitrate),
28324
+ valueFlag(options.everyNthFrameOption.cliFlag, everyNthFrame, renderDefaults.everyNthFrame),
28325
+ renderMediaValueFlag("numberOfGifLoops", numberOfGifLoops, renderDefaults.numberOfGifLoops),
28326
+ renderMediaValueFlag("encodingBufferSize", encodingBufferSize, renderDefaults.encodingBufferSize),
28327
+ renderMediaValueFlag("encodingMaxRate", encodingMaxRate, renderDefaults.encodingMaxRate),
28328
+ renderMediaValueFlag("separateAudioTo", separateAudioTo, null)
28329
+ ]);
28330
+ const defaultAudioCodec = BrowserSafeApis.defaultAudioCodecs[codec]?.compressed;
28331
+ if (audioCodec !== defaultAudioCodec) {
28332
+ addFlagWithValue(flags, getRenderMediaFlag("audioCodec"), audioCodec);
28333
+ }
28334
+ }
28335
+ addTrueBooleanFlagsIfChanged(flags, [
28336
+ renderMediaBooleanFlag("disallowParallelEncoding", disallowParallelEncoding, false),
28337
+ renderMediaBooleanFlag("repro", repro, renderDefaults.repro)
28338
+ ]);
28339
+ }
28340
+ if (metadata) {
28341
+ for (const [key5, value] of Object.entries(metadata)) {
28342
+ addFlagWithValue(flags, options.metadataOption.cliFlag, `${key5}=${value}`);
28343
+ }
28344
+ }
28345
+ if (Object.keys(inputProps).length > 0) {
28346
+ addFlagWithValue(flags, options.propsOption.cliFlag, JSON.stringify(inputProps));
28347
+ }
28348
+ const envArgs = Object.entries(envVariables).sort(([a], [b]) => a.localeCompare(b)).map(([key5, value]) => shellQuote(`${key5}=${value}`));
28349
+ const renderCommand = [
28350
+ command,
28351
+ commandType,
28352
+ shellQuote(serveUrl),
28353
+ shellQuote(compositionId),
28354
+ shellQuote(trimDefaultOutPrefix(outName)),
28355
+ ...flags
28356
+ ].join(" ");
28357
+ if (envArgs.length === 0) {
28358
+ return renderCommand;
28359
+ }
28360
+ return ["env", ...envArgs, renderCommand].join(" ");
28361
+ };
28362
+
27982
28363
  // src/helpers/render-modal-sections.ts
27983
28364
  import { useMemo as useMemo128, useState as useState85 } from "react";
27984
28365
  var useRenderModalSections = (renderMode, codec) => {
@@ -28120,14 +28501,14 @@ var VerticalTab = ({ children, onClick, style: style12, selected }) => {
28120
28501
  };
28121
28502
 
28122
28503
  // src/components/RenderModal/CrfSetting.tsx
28123
- import { BrowserSafeApis as BrowserSafeApis2 } from "@remotion/renderer/client";
28504
+ import { BrowserSafeApis as BrowserSafeApis3 } from "@remotion/renderer/client";
28124
28505
  import { useState as useState88 } from "react";
28125
28506
 
28126
28507
  // src/components/RenderModal/NumberSetting.tsx
28127
28508
  import { useCallback as useCallback122 } from "react";
28128
28509
 
28129
28510
  // src/components/RenderModal/OptionExplainerBubble.tsx
28130
- import { BrowserSafeApis } from "@remotion/renderer/client";
28511
+ import { BrowserSafeApis as BrowserSafeApis2 } from "@remotion/renderer/client";
28131
28512
 
28132
28513
  // src/components/RenderModal/CliCopyButton.tsx
28133
28514
  import { useCallback as useCallback121, useEffect as useEffect84, useMemo as useMemo130, useState as useState87 } from "react";
@@ -28354,7 +28735,7 @@ var OptionExplainer = ({ option }) => {
28354
28735
  // src/components/RenderModal/OptionExplainerBubble.tsx
28355
28736
  import { jsx as jsx251 } from "react/jsx-runtime";
28356
28737
  var OptionExplainerBubble = ({ id }) => {
28357
- const option = BrowserSafeApis.options[id];
28738
+ const option = BrowserSafeApis2.options[id];
28358
28739
  return /* @__PURE__ */ jsx251(InfoBubble, {
28359
28740
  title: "Learn more about this option",
28360
28741
  children: /* @__PURE__ */ jsx251(OptionExplainer, {
@@ -28422,8 +28803,8 @@ var NumberSetting = ({ name, value, step, hint, onValueChanged, max, min, format
28422
28803
  // src/components/RenderModal/CrfSetting.tsx
28423
28804
  import { jsx as jsx253 } from "react/jsx-runtime";
28424
28805
  var getDefaultCrfState = () => {
28425
- return BrowserSafeApis2.validCodecs.map((c) => {
28426
- return [c, BrowserSafeApis2.getDefaultCrfForCodec(c)];
28806
+ return BrowserSafeApis3.validCodecs.map((c) => {
28807
+ return [c, BrowserSafeApis3.getDefaultCrfForCodec(c)];
28427
28808
  }).reduce((acc, [codec, crf]) => {
28428
28809
  return {
28429
28810
  ...acc,
@@ -28433,7 +28814,7 @@ var getDefaultCrfState = () => {
28433
28814
  };
28434
28815
  var useCrfState = (codec) => {
28435
28816
  const [state, setState] = useState88(() => getDefaultCrfState());
28436
- const range = BrowserSafeApis2.getValidCrfRanges(codec);
28817
+ const range = BrowserSafeApis3.getValidCrfRanges(codec);
28437
28818
  const setCrf = (updater) => {
28438
28819
  setState((q) => {
28439
28820
  const val = q[codec];
@@ -28466,7 +28847,7 @@ var CrfSetting = ({ crf, setCrf, min, max, option }) => {
28466
28847
  };
28467
28848
 
28468
28849
  // src/components/RenderModal/get-default-codecs.ts
28469
- import { BrowserSafeApis as BrowserSafeApis3 } from "@remotion/renderer/client";
28850
+ import { BrowserSafeApis as BrowserSafeApis4 } from "@remotion/renderer/client";
28470
28851
  import { NoReactAPIs } from "@remotion/renderer/pure";
28471
28852
  var getDefaultCodecs = ({
28472
28853
  defaultConfigurationVideoCodec,
@@ -28486,7 +28867,7 @@ var getDefaultCodecs = ({
28486
28867
  initialVideoCodecForVideoTab: NoReactAPIs.isAudioCodec(defaultConfigurationVideoCodec) ? "h264" : defaultConfigurationVideoCodec
28487
28868
  };
28488
28869
  }
28489
- const suitableAudioCodecForVideoCodec = BrowserSafeApis3.defaultAudioCodecs[userPreferredVideoCodec].compressed;
28870
+ const suitableAudioCodecForVideoCodec = BrowserSafeApis4.defaultAudioCodecs[userPreferredVideoCodec].compressed;
28490
28871
  return {
28491
28872
  initialAudioCodec: defaultConfigurationAudioCodec ?? suitableAudioCodecForVideoCodec,
28492
28873
  initialVideoCodec: userPreferredVideoCodec,
@@ -28506,7 +28887,7 @@ var getStringBeforeSuffix = (fileName) => {
28506
28887
  };
28507
28888
 
28508
28889
  // src/components/RenderModal/out-name-checker.ts
28509
- import { BrowserSafeApis as BrowserSafeApis4 } from "@remotion/renderer/client";
28890
+ import { BrowserSafeApis as BrowserSafeApis5 } from "@remotion/renderer/client";
28510
28891
  var invalidCharacters = ["?", "*", "+", ":", "%"];
28511
28892
  var isValidStillExtension = (extension, stillImageFormat) => {
28512
28893
  if (stillImageFormat === "jpeg" && extension === "jpg") {
@@ -28546,8 +28927,8 @@ var isValidOutName = ({
28546
28927
  }) => {
28547
28928
  const extension = outName.substring(outName.lastIndexOf(".") + 1);
28548
28929
  const prefix = outName.substring(0, outName.lastIndexOf("."));
28549
- const map = BrowserSafeApis4.defaultFileExtensionMap[codec];
28550
- if (BrowserSafeApis4.supportedAudioCodecs[codec].length > 0 && !(audioCodec in map.forAudioCodec)) {
28930
+ const map = BrowserSafeApis5.defaultFileExtensionMap[codec];
28931
+ if (BrowserSafeApis5.supportedAudioCodecs[codec].length > 0 && !(audioCodec in map.forAudioCodec)) {
28551
28932
  throw new Error(`Audio codec ${audioCodec} is not supported for codec ${codec}`);
28552
28933
  }
28553
28934
  const hasDotAfterSlash = () => {
@@ -28563,7 +28944,7 @@ var isValidOutName = ({
28563
28944
  return prefix.split("").some((char) => invalidCharacters.includes(char));
28564
28945
  };
28565
28946
  if (renderMode === "video" || renderMode === "audio") {
28566
- BrowserSafeApis4.validateOutputFilename({
28947
+ BrowserSafeApis5.validateOutputFilename({
28567
28948
  codec,
28568
28949
  audioCodecSetting: audioCodec ?? null,
28569
28950
  extension,
@@ -28651,7 +29032,7 @@ var flexer = {
28651
29032
  };
28652
29033
 
28653
29034
  // src/components/RenderModal/RenderModalAdvanced.tsx
28654
- import { BrowserSafeApis as BrowserSafeApis5 } from "@remotion/renderer/client";
29035
+ import { BrowserSafeApis as BrowserSafeApis6 } from "@remotion/renderer/client";
28655
29036
  import { useCallback as useCallback126, useMemo as useMemo131 } from "react";
28656
29037
 
28657
29038
  // src/helpers/presets-labels.ts
@@ -29084,7 +29465,7 @@ var RenderModalAdvanced = ({
29084
29465
  });
29085
29466
  }, [extendedOpenGlOptions, openGlOption, setOpenGlOption]);
29086
29467
  const chromeModeOptions = useMemo131(() => {
29087
- return BrowserSafeApis5.validChromeModeOptions.map((option) => {
29468
+ return BrowserSafeApis6.validChromeModeOptions.map((option) => {
29088
29469
  return {
29089
29470
  label: option,
29090
29471
  onClick: () => setChromeModeOption(option),
@@ -29100,7 +29481,7 @@ var RenderModalAdvanced = ({
29100
29481
  });
29101
29482
  }, [chromeModeOption, setChromeModeOption]);
29102
29483
  const x264PresetOptions = useMemo131(() => {
29103
- return BrowserSafeApis5.x264PresetOptions.map((option) => {
29484
+ return BrowserSafeApis6.x264PresetOptions.map((option) => {
29104
29485
  return {
29105
29486
  label: labelx264Preset(option),
29106
29487
  onClick: () => setx264Preset(option),
@@ -29116,7 +29497,7 @@ var RenderModalAdvanced = ({
29116
29497
  });
29117
29498
  }, [setx264Preset, x264Preset]);
29118
29499
  const hardwareAccelerationValues = useMemo131(() => {
29119
- return BrowserSafeApis5.hardwareAccelerationOptions.map((option) => {
29500
+ return BrowserSafeApis6.hardwareAccelerationOptions.map((option) => {
29120
29501
  return {
29121
29502
  label: option,
29122
29503
  onClick: () => setHardwareAcceleration(option),
@@ -29607,7 +29988,7 @@ var RenderModalAdvanced = ({
29607
29988
  };
29608
29989
 
29609
29990
  // src/components/RenderModal/RenderModalAudio.tsx
29610
- import { BrowserSafeApis as BrowserSafeApis7 } from "@remotion/renderer/client";
29991
+ import { BrowserSafeApis as BrowserSafeApis8 } from "@remotion/renderer/client";
29611
29992
  import { useCallback as useCallback130 } from "react";
29612
29993
 
29613
29994
  // src/components/RenderModal/EnforceAudioTrackSetting.tsx
@@ -29700,7 +30081,7 @@ var MutedSetting = ({ muted, setMuted, enforceAudioTrack }) => {
29700
30081
  };
29701
30082
 
29702
30083
  // src/components/RenderModal/SeparateAudioOption.tsx
29703
- import { BrowserSafeApis as BrowserSafeApis6 } from "@remotion/renderer/client";
30084
+ import { BrowserSafeApis as BrowserSafeApis7 } from "@remotion/renderer/client";
29704
30085
  import { useCallback as useCallback129, useMemo as useMemo132 } from "react";
29705
30086
 
29706
30087
  // src/helpers/use-file-existence.ts
@@ -29835,7 +30216,7 @@ var SeparateAudioOptionInput = ({ separateAudioTo, setSeparateAudioTo, audioCode
29835
30216
  setSeparateAudioTo(e.target.value);
29836
30217
  }, [setSeparateAudioTo]);
29837
30218
  const validationMessage = useMemo132(() => {
29838
- const expectedExtension = BrowserSafeApis6.getExtensionFromAudioCodec(audioCodec);
30219
+ const expectedExtension = BrowserSafeApis7.getExtensionFromAudioCodec(audioCodec);
29839
30220
  const actualExtension = separateAudioTo.split(".").pop();
29840
30221
  if (actualExtension !== expectedExtension) {
29841
30222
  return `Expected extension: .${expectedExtension}`;
@@ -29854,7 +30235,7 @@ var SeparateAudioOptionInput = ({ separateAudioTo, setSeparateAudioTo, audioCode
29854
30235
  var SeparateAudioOption = ({ separateAudioTo, setSeparateAudioTo, audioCodec, outName }) => {
29855
30236
  const onSeparateAudioChange = useCallback129((e) => {
29856
30237
  if (e.target.checked) {
29857
- const extension = BrowserSafeApis6.getExtensionFromAudioCodec(audioCodec);
30238
+ const extension = BrowserSafeApis7.getExtensionFromAudioCodec(audioCodec);
29858
30239
  setSeparateAudioTo(`${getStringBeforeSuffix(outName)}.${extension}`);
29859
30240
  } else {
29860
30241
  setSeparateAudioTo(null);
@@ -29932,7 +30313,7 @@ var RenderModalAudio = ({
29932
30313
  setForSeamlessAacConcatenation(e.target.checked);
29933
30314
  }, [setForSeamlessAacConcatenation]);
29934
30315
  const audioCodecOptions = useCallback130((currentCodec) => {
29935
- return BrowserSafeApis7.supportedAudioCodecs[currentCodec].map((audioCodecOption) => {
30316
+ return BrowserSafeApis8.supportedAudioCodecs[currentCodec].map((audioCodecOption) => {
29936
30317
  return {
29937
30318
  label: humanReadableAudioCodec(audioCodecOption),
29938
30319
  onClick: () => setAudioCodec(audioCodecOption),
@@ -30077,7 +30458,7 @@ var RenderModalAudio = ({
30077
30458
  };
30078
30459
 
30079
30460
  // src/components/RenderModal/RenderModalBasic.tsx
30080
- import { BrowserSafeApis as BrowserSafeApis8 } from "@remotion/renderer/client";
30461
+ import { BrowserSafeApis as BrowserSafeApis9 } from "@remotion/renderer/client";
30081
30462
  import { NoReactAPIs as NoReactAPIs2 } from "@remotion/renderer/pure";
30082
30463
  import { useCallback as useCallback133, useMemo as useMemo134 } from "react";
30083
30464
 
@@ -30338,11 +30719,12 @@ var RenderModalBasic = ({
30338
30719
  startFrame,
30339
30720
  validationMessage,
30340
30721
  setVerboseLogging,
30341
- logLevel
30722
+ logLevel,
30723
+ showOutputName
30342
30724
  }) => {
30343
30725
  const existence = useFileExistence(outName);
30344
30726
  const videoCodecOptions = useMemo134(() => {
30345
- return BrowserSafeApis8.validCodecs.filter((c) => {
30727
+ return BrowserSafeApis9.validCodecs.filter((c) => {
30346
30728
  return NoReactAPIs2.isAudioCodec(c) === (renderMode === "audio");
30347
30729
  }).map((codecOption) => {
30348
30730
  return {
@@ -30360,7 +30742,7 @@ var RenderModalBasic = ({
30360
30742
  });
30361
30743
  }, [renderMode, setCodec, codec]);
30362
30744
  const proResProfileOptions = useMemo134(() => {
30363
- return BrowserSafeApis8.proResProfileOptions.map((option) => {
30745
+ return BrowserSafeApis9.proResProfileOptions.map((option) => {
30364
30746
  return {
30365
30747
  label: labelProResProfile(option),
30366
30748
  onClick: () => setProResProfile(option),
@@ -30499,14 +30881,14 @@ var RenderModalBasic = ({
30499
30881
  setStartFrame,
30500
30882
  startFrame
30501
30883
  }),
30502
- /* @__PURE__ */ jsx266(RenderModalOutputName, {
30884
+ showOutputName ? /* @__PURE__ */ jsx266(RenderModalOutputName, {
30503
30885
  existence,
30504
30886
  inputStyle: input,
30505
30887
  outName,
30506
30888
  onValueChange,
30507
30889
  validationMessage,
30508
30890
  label: renderMode === "sequence" ? "Folder name" : "Output name"
30509
- }),
30891
+ }) : null,
30510
30892
  /* @__PURE__ */ jsxs136("div", {
30511
30893
  style: optionRow,
30512
30894
  children: [
@@ -30644,7 +31026,7 @@ var RenderModalGif = ({
30644
31026
  };
30645
31027
 
30646
31028
  // src/components/RenderModal/RenderModalPicture.tsx
30647
- import { BrowserSafeApis as BrowserSafeApis9 } from "@remotion/renderer/client";
31029
+ import { BrowserSafeApis as BrowserSafeApis10 } from "@remotion/renderer/client";
30648
31030
  import { useCallback as useCallback136, useMemo as useMemo136 } from "react";
30649
31031
 
30650
31032
  // src/components/RenderModal/JpegQualitySetting.tsx
@@ -30748,7 +31130,7 @@ var RenderModalPicture = ({
30748
31130
  compositionHeight
30749
31131
  }) => {
30750
31132
  const colorSpaceOptions = useMemo136(() => {
30751
- return BrowserSafeApis9.validColorSpaces.map((option) => {
31133
+ return BrowserSafeApis10.validColorSpaces.map((option) => {
30752
31134
  return {
30753
31135
  label: option,
30754
31136
  onClick: () => setColorSpace(option),
@@ -31041,6 +31423,7 @@ var reducer2 = (state, action) => {
31041
31423
  return state;
31042
31424
  };
31043
31425
  var RenderModal = ({
31426
+ readOnlyStudio,
31044
31427
  initialFrame,
31045
31428
  initialVideoImageFormat,
31046
31429
  initialStillImageFormat,
@@ -31131,7 +31514,7 @@ var RenderModal = ({
31131
31514
  const [initialOutName] = useState90(() => {
31132
31515
  return getDefaultOutLocation({
31133
31516
  compositionName: resolvedComposition.id,
31134
- defaultExtension: initialRenderType === "still" ? initialStillImageFormat : isVideo ? BrowserSafeApis10.getFileExtensionFromCodec(initialVideoCodec, initialAudioCodec) : initialStillImageFormat,
31517
+ defaultExtension: initialRenderType === "still" ? initialStillImageFormat : isVideo ? BrowserSafeApis11.getFileExtensionFromCodec(initialVideoCodec, initialAudioCodec) : initialStillImageFormat,
31135
31518
  type: "asset",
31136
31519
  compositionDefaultOutName: resolvedComposition.defaultOutName,
31137
31520
  outputLocation: renderDefaults.outputLocation
@@ -31216,8 +31599,8 @@ var RenderModal = ({
31216
31599
  }
31217
31600
  return null;
31218
31601
  }, [customTargetAudioBitrate, shouldHaveCustomTargetAudioBitrate]);
31219
- const supportsCrf = BrowserSafeApis10.codecSupportsCrf(codec);
31220
- const supportsVideoBitrate = BrowserSafeApis10.codecSupportsVideoBitrate(codec);
31602
+ const supportsCrf = BrowserSafeApis11.codecSupportsCrf(codec);
31603
+ const supportsVideoBitrate = BrowserSafeApis11.codecSupportsVideoBitrate(codec);
31221
31604
  const supportsBothQualityControls = useMemo137(() => {
31222
31605
  return supportsCrf && supportsVideoBitrate && hardwareAcceleration !== "if-possible" && hardwareAcceleration !== "required";
31223
31606
  }, [hardwareAcceleration, supportsCrf, supportsVideoBitrate]);
@@ -31303,10 +31686,10 @@ var RenderModal = ({
31303
31686
  return Math.max(0, Math.min(resolvedComposition.durationInFrames - 1, parsed));
31304
31687
  }, [resolvedComposition.durationInFrames, unclampedFrame]);
31305
31688
  const deriveFinalAudioCodec = useCallback137((passedVideoCodec, passedAudioCodec) => {
31306
- if (passedAudioCodec !== null && BrowserSafeApis10.supportedAudioCodecs[passedVideoCodec].includes(passedAudioCodec)) {
31689
+ if (passedAudioCodec !== null && BrowserSafeApis11.supportedAudioCodecs[passedVideoCodec].includes(passedAudioCodec)) {
31307
31690
  return passedAudioCodec;
31308
31691
  }
31309
- return BrowserSafeApis10.defaultAudioCodecs[passedVideoCodec].compressed;
31692
+ return BrowserSafeApis11.defaultAudioCodecs[passedVideoCodec].compressed;
31310
31693
  }, []);
31311
31694
  const setDefaultOutName = useCallback137((options) => {
31312
31695
  if (options.type === "still") {
@@ -31321,7 +31704,7 @@ var RenderModal = ({
31321
31704
  });
31322
31705
  } else {
31323
31706
  setOutName((prev) => {
31324
- const codecSuffix = BrowserSafeApis10.getFileExtensionFromCodec(options.codec, deriveFinalAudioCodec(options.codec, options.audioCodec));
31707
+ const codecSuffix = BrowserSafeApis11.getFileExtensionFromCodec(options.codec, deriveFinalAudioCodec(options.codec, options.audioCodec));
31325
31708
  const newFileName = getStringBeforeSuffix(prev) + "." + codecSuffix;
31326
31709
  return newFileName;
31327
31710
  });
@@ -31338,7 +31721,7 @@ var RenderModal = ({
31338
31721
  if (prev === null) {
31339
31722
  return null;
31340
31723
  }
31341
- const newExtension = BrowserSafeApis10.getExtensionFromAudioCodec(newAudioCodec);
31724
+ const newExtension = BrowserSafeApis11.getExtensionFromAudioCodec(newAudioCodec);
31342
31725
  const newFileName = getStringBeforeSuffix(prev) + "." + newExtension;
31343
31726
  return newFileName;
31344
31727
  });
@@ -31427,7 +31810,7 @@ var RenderModal = ({
31427
31810
  }, [codec, everyNthFrameSetting]);
31428
31811
  const audioCodec = deriveFinalAudioCodec(codec, userSelectedAudioCodec);
31429
31812
  const availablePixelFormats = useMemo137(() => {
31430
- return BrowserSafeApis10.validPixelFormatsForCodec(codec);
31813
+ return BrowserSafeApis11.validPixelFormatsForCodec(codec);
31431
31814
  }, [codec]);
31432
31815
  const pixelFormat = useMemo137(() => {
31433
31816
  if (availablePixelFormats.includes(userPreferredPixelFormat)) {
@@ -31754,8 +32137,134 @@ var RenderModal = ({
31754
32137
  });
31755
32138
  const { tab, setTab, shownTabs } = useRenderModalSections(renderMode, codec);
31756
32139
  const { registerKeybinding } = useKeybinding();
31757
- const renderDisabled = state.type === "load" || !outnameValidation.valid;
32140
+ const readOnlyRenderCommand = useMemo137(() => {
32141
+ if (!readOnlyStudio) {
32142
+ return null;
32143
+ }
32144
+ return makeReadOnlyStudioRenderCommand({
32145
+ remotionVersion: window.remotion_version,
32146
+ locationHref: window.location.href,
32147
+ compositionId: resolvedComposition.id,
32148
+ outName,
32149
+ renderMode,
32150
+ renderDefaults,
32151
+ durationInFrames: resolvedComposition.durationInFrames,
32152
+ concurrency,
32153
+ frame: frame2,
32154
+ startFrame,
32155
+ endFrame,
32156
+ stillImageFormat,
32157
+ sequenceImageFormat,
32158
+ videoImageFormat,
32159
+ jpegQuality: renderMode === "video" ? stillImageFormat === "jpeg" ? jpegQuality : null : renderMode === "audio" ? null : jpegQuality,
32160
+ codec,
32161
+ muted,
32162
+ enforceAudioTrack,
32163
+ proResProfile,
32164
+ x264Preset,
32165
+ pixelFormat,
32166
+ crf: qualityControlType === "crf" && hardwareAcceleration !== "if-possible" && hardwareAcceleration !== "required" ? crf : null,
32167
+ videoBitrate,
32168
+ audioBitrate,
32169
+ audioCodec,
32170
+ everyNthFrame,
32171
+ numberOfGifLoops,
32172
+ disallowParallelEncoding,
32173
+ encodingBufferSize,
32174
+ encodingMaxRate,
32175
+ forSeamlessAacConcatenation,
32176
+ separateAudioTo,
32177
+ colorSpace,
32178
+ scale,
32179
+ logLevel,
32180
+ delayRenderTimeout,
32181
+ hardwareAcceleration,
32182
+ chromeMode,
32183
+ headless,
32184
+ disableWebSecurity,
32185
+ ignoreCertificateErrors,
32186
+ gl: openGlOption === "default" ? null : openGlOption,
32187
+ userAgent,
32188
+ multiProcessOnLinux,
32189
+ darkMode,
32190
+ offthreadVideoCacheSizeInBytes,
32191
+ offthreadVideoThreads,
32192
+ mediaCacheSizeInBytes,
32193
+ beepOnFinish,
32194
+ repro,
32195
+ metadata,
32196
+ envVariables: envVariablesArrayToObject(envVariables),
32197
+ inputProps
32198
+ });
32199
+ }, [
32200
+ audioBitrate,
32201
+ audioCodec,
32202
+ beepOnFinish,
32203
+ chromeMode,
32204
+ codec,
32205
+ colorSpace,
32206
+ concurrency,
32207
+ crf,
32208
+ darkMode,
32209
+ delayRenderTimeout,
32210
+ disableWebSecurity,
32211
+ disallowParallelEncoding,
32212
+ endFrame,
32213
+ encodingBufferSize,
32214
+ encodingMaxRate,
32215
+ enforceAudioTrack,
32216
+ envVariables,
32217
+ everyNthFrame,
32218
+ frame2,
32219
+ forSeamlessAacConcatenation,
32220
+ hardwareAcceleration,
32221
+ headless,
32222
+ ignoreCertificateErrors,
32223
+ inputProps,
32224
+ jpegQuality,
32225
+ logLevel,
32226
+ mediaCacheSizeInBytes,
32227
+ metadata,
32228
+ multiProcessOnLinux,
32229
+ muted,
32230
+ numberOfGifLoops,
32231
+ offthreadVideoCacheSizeInBytes,
32232
+ offthreadVideoThreads,
32233
+ openGlOption,
32234
+ outName,
32235
+ pixelFormat,
32236
+ proResProfile,
32237
+ qualityControlType,
32238
+ readOnlyStudio,
32239
+ renderDefaults,
32240
+ renderMode,
32241
+ repro,
32242
+ resolvedComposition.durationInFrames,
32243
+ resolvedComposition.id,
32244
+ scale,
32245
+ separateAudioTo,
32246
+ sequenceImageFormat,
32247
+ startFrame,
32248
+ stillImageFormat,
32249
+ userAgent,
32250
+ videoImageFormat,
32251
+ videoBitrate,
32252
+ x264Preset
32253
+ ]);
32254
+ const [commandCopiedAt, setCommandCopiedAt] = useState90(null);
32255
+ const renderDisabled = readOnlyStudio ? false : !outnameValidation.valid || state.type === "load";
31758
32256
  const trigger = useCallback137(() => {
32257
+ if (readOnlyStudio) {
32258
+ if (!readOnlyRenderCommand) {
32259
+ return;
32260
+ }
32261
+ copyText(readOnlyRenderCommand).then(() => {
32262
+ setCommandCopiedAt(Date.now());
32263
+ }).catch((err) => {
32264
+ showNotification(`Could not copy: ${err.message}`, 2000);
32265
+ });
32266
+ return;
32267
+ }
31759
32268
  if (renderMode === "still") {
31760
32269
  onClickStill();
31761
32270
  } else if (renderMode === "sequence") {
@@ -31763,7 +32272,23 @@ var RenderModal = ({
31763
32272
  } else {
31764
32273
  onClickVideo();
31765
32274
  }
31766
- }, [renderMode, onClickStill, onClickSequence, onClickVideo]);
32275
+ }, [
32276
+ onClickSequence,
32277
+ onClickStill,
32278
+ onClickVideo,
32279
+ readOnlyRenderCommand,
32280
+ readOnlyStudio,
32281
+ renderMode
32282
+ ]);
32283
+ useEffect86(() => {
32284
+ if (commandCopiedAt === null) {
32285
+ return;
32286
+ }
32287
+ const timeout = setTimeout(() => {
32288
+ setCommandCopiedAt(null);
32289
+ }, 2000);
32290
+ return () => clearTimeout(timeout);
32291
+ }, [commandCopiedAt]);
31767
32292
  useEffect86(() => {
31768
32293
  if (renderDisabled) {
31769
32294
  return;
@@ -31824,7 +32349,7 @@ var RenderModal = ({
31824
32349
  backgroundColor: outnameValidation.valid ? BLUE : BLUE_DISABLED
31825
32350
  },
31826
32351
  children: [
31827
- state.type === "idle" ? `Render ${renderMode}` : "Rendering...",
32352
+ readOnlyStudio ? commandCopiedAt ? "Copied command!" : "Copy command" : state.type === "idle" ? `Render ${renderMode}` : "Rendering...",
31828
32353
  /* @__PURE__ */ jsx272(ShortcutHint, {
31829
32354
  keyToPress: "↵",
31830
32355
  cmdOrCtrl: true
@@ -31945,6 +32470,7 @@ var RenderModal = ({
31945
32470
  setStartFrame,
31946
32471
  setVerboseLogging: setLogLevel,
31947
32472
  logLevel,
32473
+ showOutputName: !readOnlyStudio,
31948
32474
  startFrame,
31949
32475
  validationMessage: outnameValidation.valid ? null : outnameValidation.error.message
31950
32476
  }) : tab === "picture" ? /* @__PURE__ */ jsx272(RenderModalPicture, {
@@ -32008,7 +32534,7 @@ var RenderModal = ({
32008
32534
  propsEditType: "input-props",
32009
32535
  saving,
32010
32536
  setSaving,
32011
- readOnlyStudio: false
32537
+ readOnlyStudio
32012
32538
  }) : /* @__PURE__ */ jsx272(RenderModalAdvanced, {
32013
32539
  x264Preset,
32014
32540
  setx264Preset,
@@ -32474,6 +33000,7 @@ var WebRenderModalAudio = ({
32474
33000
  const audioBitrateOptions = useMemo139(() => getQualityOptions(audioBitrate, setAudioBitrate), [audioBitrate, setAudioBitrate]);
32475
33001
  const isAudioOnly = renderMode === "audio";
32476
33002
  const showAudioSettings = isAudioOnly || !muted;
33003
+ const showAudioCodecSetting = !isAudioOnly || containerSupported.length > 1;
32477
33004
  return /* @__PURE__ */ jsxs144("div", {
32478
33005
  style: container63,
32479
33006
  className: VERTICAL_SCROLLBAR_CLASSNAME,
@@ -32508,7 +33035,7 @@ var WebRenderModalAudio = ({
32508
33035
  })
32509
33036
  ]
32510
33037
  }),
32511
- /* @__PURE__ */ jsxs144("div", {
33038
+ showAudioCodecSetting ? /* @__PURE__ */ jsxs144("div", {
32512
33039
  style: optionRow,
32513
33040
  children: [
32514
33041
  /* @__PURE__ */ jsxs144("div", {
@@ -32529,8 +33056,8 @@ var WebRenderModalAudio = ({
32529
33056
  })
32530
33057
  })
32531
33058
  ]
32532
- }),
32533
- effectiveAudioCodec !== audioCodec ? /* @__PURE__ */ jsxs144("div", {
33059
+ }) : null,
33060
+ showAudioCodecSetting && effectiveAudioCodec !== audioCodec ? /* @__PURE__ */ jsxs144("div", {
32534
33061
  style: fallbackNoticeStyle,
32535
33062
  children: [
32536
33063
  humanReadableWebAudioCodec(audioCodec),
@@ -34200,7 +34727,8 @@ var Modals = ({ readOnlyStudio }) => {
34200
34727
  modalContextType && modalContextType.type === "web-render" && /* @__PURE__ */ jsx288(WebRenderModalWithLoader, {
34201
34728
  ...modalContextType
34202
34729
  }),
34203
- modalContextType && canRender && modalContextType.type === "server-render" && /* @__PURE__ */ jsx288(RenderModalWithLoader, {
34730
+ modalContextType && modalContextType.type === "server-render" && (canRender || modalContextType.readOnlyStudio) ? /* @__PURE__ */ jsx288(RenderModalWithLoader, {
34731
+ readOnlyStudio: modalContextType.readOnlyStudio ?? false,
34204
34732
  initialFrame: modalContextType.initialFrame,
34205
34733
  initialDarkMode: modalContextType.initialDarkMode,
34206
34734
  compositionId: modalContextType.compositionId,
@@ -34248,7 +34776,7 @@ var Modals = ({ readOnlyStudio }) => {
34248
34776
  initialHardwareAcceleration: modalContextType.initialHardwareAcceleration,
34249
34777
  initialChromeMode: modalContextType.initialChromeMode,
34250
34778
  renderDefaults: modalContextType.renderDefaults
34251
- }),
34779
+ }) : null,
34252
34780
  modalContextType && modalContextType.type === "render-progress" && /* @__PURE__ */ jsx288(RenderStatusModal, {
34253
34781
  jobId: modalContextType.jobId
34254
34782
  }),