@remotion/studio 4.0.442 → 4.0.443

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.
@@ -4,8 +4,11 @@ exports.MenuBuildIndicator = void 0;
4
4
  const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const client_id_1 = require("../helpers/client-id");
7
+ const get_git_menu_item_1 = require("../helpers/get-git-menu-item");
8
+ const open_in_editor_1 = require("../helpers/open-in-editor");
7
9
  const layout_1 = require("./layout");
8
- const OpenEditorButton_1 = require("./OpenEditorButton");
10
+ const MenuCompositionName_1 = require("./MenuCompositionName");
11
+ const NotificationCenter_1 = require("./Notifications/NotificationCenter");
9
12
  const Spinner_1 = require("./Spinner");
10
13
  const cwd = {
11
14
  fontSize: 13,
@@ -25,10 +28,61 @@ const noSpinner = {
25
28
  position: 'relative',
26
29
  width: spinnerSize,
27
30
  };
31
+ const projectNameLinkBase = {
32
+ color: 'inherit',
33
+ textDecoration: 'none',
34
+ cursor: 'pointer',
35
+ fontSize: 'inherit',
36
+ textUnderlineOffset: 2,
37
+ };
38
+ const projectNameLink = {
39
+ ...projectNameLinkBase,
40
+ };
41
+ const projectNameLinkHovered = {
42
+ ...projectNameLinkBase,
43
+ textDecoration: 'underline',
44
+ };
28
45
  const MenuBuildIndicator = () => {
29
46
  const [isBuilding, setIsBuilding] = (0, react_1.useState)(false);
47
+ const [projectNameHovered, setProjectNameHovered] = (0, react_1.useState)(false);
30
48
  const ctx = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx).previewServerState;
31
- const showButton = window.remotion_editorName && ctx.type === 'connected';
49
+ const showEditorLink = window.remotion_editorName && ctx.type === 'connected';
50
+ const showGitLink = !showEditorLink && Boolean(window.remotion_gitSource);
51
+ const handleProjectNameClick = (0, react_1.useCallback)(async () => {
52
+ if (showEditorLink) {
53
+ await (0, open_in_editor_1.openInEditor)({
54
+ originalFileName: `${window.remotion_cwd}`,
55
+ originalLineNumber: 1,
56
+ originalColumnNumber: 1,
57
+ originalFunctionName: null,
58
+ originalScriptCode: null,
59
+ })
60
+ .then((res) => res.json())
61
+ .then(({ success }) => {
62
+ if (!success) {
63
+ (0, NotificationCenter_1.showNotification)(`Could not open ${window.remotion_editorName}`, 2000);
64
+ }
65
+ })
66
+ .catch((err) => {
67
+ // eslint-disable-next-line no-console
68
+ console.error(err);
69
+ (0, NotificationCenter_1.showNotification)(`Could not open ${window.remotion_editorName}`, 2000);
70
+ });
71
+ }
72
+ else if (showGitLink) {
73
+ window.open((0, get_git_menu_item_1.getGitSourceBranchUrl)(window.remotion_gitSource), '_blank');
74
+ }
75
+ }, [showEditorLink, showGitLink]);
76
+ const projectNameTitle = (0, react_1.useMemo)(() => {
77
+ if (showEditorLink) {
78
+ return `Open in ${window.remotion_editorName}`;
79
+ }
80
+ if (showGitLink) {
81
+ return `Open ${(0, get_git_menu_item_1.getGitSourceName)(window.remotion_gitSource)} Repo`;
82
+ }
83
+ return undefined;
84
+ }, [showEditorLink, showGitLink]);
85
+ const isClickable = showEditorLink || showGitLink;
32
86
  (0, react_1.useEffect)(() => {
33
87
  window.remotion_isBuilding = () => {
34
88
  setIsBuilding(true);
@@ -41,6 +95,7 @@ const MenuBuildIndicator = () => {
41
95
  window.remotion_finishedBuilding = undefined;
42
96
  };
43
97
  }, []);
44
- return (jsx_runtime_1.jsxs("div", { style: cwd, title: window.remotion_cwd, children: [showButton ? jsx_runtime_1.jsx(layout_1.Spacing, { x: 2 }) : null, isBuilding ? (jsx_runtime_1.jsx("div", { style: spinner, children: jsx_runtime_1.jsx(Spinner_1.Spinner, { duration: 0.5, size: spinnerSize }) })) : (jsx_runtime_1.jsx("div", { style: noSpinner })), showButton ? jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }) : null, window.remotion_projectName, showButton ? jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.25 }) : null, showButton ? (jsx_runtime_1.jsx(OpenEditorButton_1.OpenEditorButton, { type: "editor" })) : window.remotion_gitSource ? (jsx_runtime_1.jsx(OpenEditorButton_1.OpenEditorButton, { type: "git" })) : null] }));
98
+ return (jsx_runtime_1.jsxs("div", { style: cwd, title: window.remotion_cwd, children: [isClickable ? jsx_runtime_1.jsx(layout_1.Spacing, { x: 2 }) : null, isBuilding ? (jsx_runtime_1.jsx("div", { style: spinner, children: jsx_runtime_1.jsx(Spinner_1.Spinner, { duration: 0.5, size: spinnerSize }) })) : (jsx_runtime_1.jsx("div", { style: noSpinner })), isClickable ? jsx_runtime_1.jsx(layout_1.Spacing, { x: 0.5 }) : null, isClickable ? (jsx_runtime_1.jsx("a", { style: projectNameHovered ? projectNameLinkHovered : projectNameLink, title: projectNameTitle, onClick: handleProjectNameClick, onPointerEnter: () => setProjectNameHovered(true), onPointerLeave: () => setProjectNameHovered(false), children: window.remotion_projectName })) : (window.remotion_projectName), jsx_runtime_1.jsx(MenuCompositionName_1.MenuCompositionName, {})
99
+ ] }));
45
100
  };
46
101
  exports.MenuBuildIndicator = MenuBuildIndicator;
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const MenuCompositionName: React.FC;
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MenuCompositionName = void 0;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const remotion_1 = require("remotion");
7
+ const client_id_1 = require("../helpers/client-id");
8
+ const colors_1 = require("../helpers/colors");
9
+ const open_in_editor_1 = require("../helpers/open-in-editor");
10
+ const NotificationCenter_1 = require("./Notifications/NotificationCenter");
11
+ const use_resolved_stack_1 = require("./Timeline/use-resolved-stack");
12
+ const baseStyle = {
13
+ color: 'inherit',
14
+ textDecoration: 'none',
15
+ fontSize: 'inherit',
16
+ textUnderlineOffset: 2,
17
+ };
18
+ const compositionNameStyle = {
19
+ ...baseStyle,
20
+ cursor: 'default',
21
+ };
22
+ const clickableStyle = {
23
+ ...baseStyle,
24
+ cursor: 'pointer',
25
+ };
26
+ const clickableHoveredStyle = {
27
+ ...clickableStyle,
28
+ textDecoration: 'underline',
29
+ };
30
+ const slashStyle = {
31
+ color: colors_1.LIGHT_TEXT,
32
+ marginInline: 4,
33
+ position: 'relative',
34
+ top: 1,
35
+ };
36
+ const MenuCompositionName = () => {
37
+ var _a;
38
+ const { canvasContent, compositions } = (0, react_1.useContext)(remotion_1.Internals.CompositionManager);
39
+ const connectionStatus = (0, react_1.useContext)(client_id_1.StudioServerConnectionCtx)
40
+ .previewServerState.type;
41
+ const [opening, setOpening] = (0, react_1.useState)(false);
42
+ const [hovered, setHovered] = (0, react_1.useState)(false);
43
+ const composition = (0, react_1.useMemo)(() => {
44
+ var _a;
45
+ if (canvasContent === null || canvasContent.type !== 'composition') {
46
+ return null;
47
+ }
48
+ return ((_a = compositions.find((c) => c.id === canvasContent.compositionId)) !== null && _a !== void 0 ? _a : null);
49
+ }, [canvasContent, compositions]);
50
+ const resolvedLocation = (0, use_resolved_stack_1.useResolvedStack)((_a = composition === null || composition === void 0 ? void 0 : composition.stack) !== null && _a !== void 0 ? _a : null);
51
+ const canOpen = resolvedLocation &&
52
+ window.remotion_editorName &&
53
+ connectionStatus === 'connected';
54
+ const handleClick = (0, react_1.useCallback)(async () => {
55
+ if (!canOpen || !resolvedLocation) {
56
+ return;
57
+ }
58
+ setOpening(true);
59
+ try {
60
+ await (0, open_in_editor_1.openOriginalPositionInEditor)(resolvedLocation);
61
+ }
62
+ catch (err) {
63
+ (0, NotificationCenter_1.showNotification)(err.message, 2000);
64
+ }
65
+ finally {
66
+ setOpening(false);
67
+ }
68
+ }, [canOpen, resolvedLocation]);
69
+ if (!composition) {
70
+ return null;
71
+ }
72
+ return (jsx_runtime_1.jsxs(jsx_runtime_1.Fragment, { children: [
73
+ jsx_runtime_1.jsx("span", { style: slashStyle, children: "/" }), jsx_runtime_1.jsx("a", { style: canOpen && !opening
74
+ ? hovered
75
+ ? clickableHoveredStyle
76
+ : clickableStyle
77
+ : compositionNameStyle, title: canOpen ? `Open in ${window.remotion_editorName}` : composition.id, onClick: handleClick, onPointerEnter: () => setHovered(true), onPointerLeave: () => setHovered(false), children: composition.id })
78
+ ] }));
79
+ };
80
+ exports.MenuCompositionName = MenuCompositionName;
@@ -5,7 +5,18 @@ const jsx_runtime_1 = require("react/jsx-runtime");
5
5
  const react_1 = require("react");
6
6
  const InputDragger_1 = require("../NewComposition/InputDragger");
7
7
  const timeline_field_utils_1 = require("./timeline-field-utils");
8
+ const unitPattern = /^([+-]?(?:\d+\.?\d*|\.\d+))(deg|rad|turn|grad)$/;
9
+ const unitToDegrees = {
10
+ deg: 1,
11
+ rad: 180 / Math.PI,
12
+ turn: 360,
13
+ grad: 360 / 400,
14
+ };
8
15
  const parseCssRotationToDegrees = (value) => {
16
+ const match = value.trim().match(unitPattern);
17
+ if (match) {
18
+ return Number(match[1]) * unitToDegrees[match[2]];
19
+ }
9
20
  try {
10
21
  const m = new DOMMatrix(`rotate(${value})`);
11
22
  return Math.round(Math.atan2(m.b, m.a) * (180 / Math.PI) * 1e6) / 1e6;
@@ -107,7 +107,7 @@ const Inner = ({ s, windowWidth }) => {
107
107
  )`,
108
108
  position: 'absolute',
109
109
  right: 0,
110
- } })) : null, s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
110
+ } })) : null, s.type === 'audio' ? (jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: s.src, doesVolumeChange: s.doesVolumeChange, visualizationWidth: width, startFrom: s.startMediaFrom, durationInFrames: s.duration, volume: s.volume, playbackRate: s.playbackRate })) : null, s.type === 'video' ? (jsx_runtime_1.jsx(TimelineVideoInfo_1.TimelineVideoInfo, { src: s.src, visualizationWidth: width, naturalWidth: naturalWidth, trimBefore: s.startMediaFrom, durationInFrames: s.duration, playbackRate: s.playbackRate, volume: s.volume, doesVolumeChange: s.doesVolumeChange, premountWidth: premountWidth !== null && premountWidth !== void 0 ? premountWidth : 0, postmountWidth: postmountWidth !== null && postmountWidth !== void 0 ? postmountWidth : 0 })) : null, s.type === 'image' ? (jsx_runtime_1.jsx(TimelineImageInfo_1.TimelineImageInfo, { src: s.src, visualizationWidth: width })) : null, s.loopDisplay === undefined ? null : (jsx_runtime_1.jsx(LoopedTimelineIndicators_1.LoopedTimelineIndicator, { loops: s.loopDisplay.numberOfTimes })), s.type !== 'audio' &&
111
111
  s.type !== 'video' &&
112
112
  s.type !== 'image' &&
113
113
  s.loopDisplay === undefined &&
@@ -149,7 +149,8 @@ const TimelineStack = ({ isCompact, sequence, originalLocation }) => {
149
149
  color: opening && isCompact ? colors_1.VERY_LIGHT_TEXT : colors_1.LIGHT_COLOR,
150
150
  userSelect: 'none',
151
151
  WebkitUserSelect: 'none',
152
- borderBottom: hoverEffect ? '1px solid #fff' : 'none',
152
+ textDecoration: hoverEffect ? 'underline' : 'none',
153
+ textUnderlineOffset: 2,
153
154
  cursor: hoverEffect ? 'pointer' : undefined,
154
155
  };
155
156
  }, [titleHoverable, isCompact, opening, titleHovered]);
@@ -6,4 +6,8 @@ export declare const TimelineVideoInfo: React.FC<{
6
6
  readonly trimBefore: number;
7
7
  readonly durationInFrames: number;
8
8
  readonly playbackRate: number;
9
+ readonly volume: string | number;
10
+ readonly doesVolumeChange: boolean;
11
+ readonly premountWidth: number;
12
+ readonly postmountWidth: number;
9
13
  }>;
@@ -8,21 +8,27 @@ const extract_frames_1 = require("../../helpers/extract-frames");
8
8
  const frame_database_1 = require("../../helpers/frame-database");
9
9
  const resize_video_frame_1 = require("../../helpers/resize-video-frame");
10
10
  const timeline_layout_1 = require("../../helpers/timeline-layout");
11
- const HEIGHT = (0, timeline_layout_1.getTimelineLayerHeight)('video') - 2;
12
- const containerStyle = {
13
- height: HEIGHT,
11
+ const AudioWaveform_1 = require("../AudioWaveform");
12
+ const FILMSTRIP_HEIGHT = timeline_layout_1.TIMELINE_LAYER_HEIGHT_IMAGE - 2;
13
+ const outerStyle = {
14
+ width: '100%',
15
+ height: '100%',
16
+ display: 'flex',
17
+ flexDirection: 'column',
18
+ };
19
+ const filmstripContainerStyle = {
20
+ height: FILMSTRIP_HEIGHT,
14
21
  width: '100%',
15
22
  backgroundColor: 'rgba(0, 0, 0, 0.3)',
16
23
  display: 'flex',
17
24
  borderTopLeftRadius: 2,
18
- borderBottomLeftRadius: 2,
19
25
  fontSize: 10,
20
26
  fontFamily: 'Arial, Helvetica',
21
27
  };
22
28
  const WEBCODECS_TIMESCALE = 1000000;
23
29
  const MAX_TIME_DEVIATION = WEBCODECS_TIMESCALE * 0.05;
24
30
  const getDurationOfOneFrame = ({ visualizationWidth, aspectRatio, segmentDuration, }) => {
25
- const framesFitInWidthUnrounded = visualizationWidth / (HEIGHT * aspectRatio);
31
+ const framesFitInWidthUnrounded = visualizationWidth / (FILMSTRIP_HEIGHT * aspectRatio);
26
32
  return (segmentDuration / framesFitInWidthUnrounded) * WEBCODECS_TIMESCALE;
27
33
  };
28
34
  const fixRounding = (value) => {
@@ -32,7 +38,7 @@ const fixRounding = (value) => {
32
38
  return Math.floor(value);
33
39
  };
34
40
  const calculateTimestampSlots = ({ visualizationWidth, fromSeconds, segmentDuration, aspectRatio, }) => {
35
- const framesFitInWidthUnrounded = visualizationWidth / (HEIGHT * aspectRatio);
41
+ const framesFitInWidthUnrounded = visualizationWidth / (FILMSTRIP_HEIGHT * aspectRatio);
36
42
  const framesFitInWidth = Math.ceil(framesFitInWidthUnrounded);
37
43
  const durationOfOneFrame = getDurationOfOneFrame({
38
44
  visualizationWidth,
@@ -70,8 +76,10 @@ const drawSlot = ({ frame, ctx, filledSlots, visualizationWidth, timestamp, segm
70
76
  });
71
77
  const relativeTimestamp = timestamp - fromSeconds * WEBCODECS_TIMESCALE;
72
78
  const frameIndex = relativeTimestamp / durationOfOneFrame;
73
- const left = Math.floor((frameIndex * frame.displayWidth) / window.devicePixelRatio); // round to avoid antialiasing
74
- ctx.drawImage(frame, left, 0, frame.displayWidth / window.devicePixelRatio, frame.displayHeight / window.devicePixelRatio);
79
+ const thumbnailWidth = frame.displayWidth / window.devicePixelRatio;
80
+ const left = Math.floor(frameIndex * thumbnailWidth);
81
+ const right = Math.ceil((frameIndex + 1) * thumbnailWidth);
82
+ ctx.drawImage(frame, left, 0, right - left, frame.displayHeight / window.devicePixelRatio);
75
83
  filledSlots.set(timestamp, frame.timestamp);
76
84
  };
77
85
  const fillWithCachedFrames = ({ ctx, naturalWidth, filledSlots, src, segmentDuration, fromSeconds, }) => {
@@ -138,7 +146,7 @@ const fillFrameWhereItFits = ({ frame, filledSlots, ctx, visualizationWidth, seg
138
146
  });
139
147
  }
140
148
  };
141
- const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore, durationInFrames, playbackRate, }) => {
149
+ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore, durationInFrames, playbackRate, volume, doesVolumeChange, premountWidth, postmountWidth, }) => {
142
150
  const { fps } = (0, remotion_1.useVideoConfig)();
143
151
  const ref = (0, react_1.useRef)(null);
144
152
  const [error, setError] = (0, react_1.useState)(null);
@@ -160,7 +168,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
160
168
  const controller = new AbortController();
161
169
  const canvas = document.createElement('canvas');
162
170
  canvas.width = visualizationWidth;
163
- canvas.height = HEIGHT;
171
+ canvas.height = FILMSTRIP_HEIGHT;
164
172
  const ctx = canvas.getContext('2d');
165
173
  if (!ctx) {
166
174
  return;
@@ -216,7 +224,7 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
216
224
  let frame;
217
225
  try {
218
226
  frame = sample.toVideoFrame();
219
- const scale = (HEIGHT / frame.displayHeight) * window.devicePixelRatio;
227
+ const scale = (FILMSTRIP_HEIGHT / frame.displayHeight) * window.devicePixelRatio;
220
228
  const transformed = (0, resize_video_frame_1.resizeVideoFrame)({
221
229
  frame,
222
230
  scale,
@@ -298,6 +306,17 @@ const TimelineVideoInfo = ({ src, visualizationWidth, naturalWidth, trimBefore,
298
306
  trimBefore,
299
307
  visualizationWidth,
300
308
  ]);
301
- return jsx_runtime_1.jsx("div", { ref: ref, style: containerStyle });
309
+ const audioWidth = visualizationWidth - premountWidth - postmountWidth;
310
+ const audioStyle = (0, react_1.useMemo)(() => {
311
+ return {
312
+ height: timeline_layout_1.TIMELINE_LAYER_HEIGHT_AUDIO,
313
+ width: audioWidth,
314
+ position: 'relative',
315
+ marginLeft: premountWidth,
316
+ };
317
+ }, [audioWidth, premountWidth]);
318
+ return (jsx_runtime_1.jsxs("div", { style: outerStyle, children: [
319
+ jsx_runtime_1.jsx("div", { ref: ref, style: filmstripContainerStyle }), jsx_runtime_1.jsx("div", { style: audioStyle, children: jsx_runtime_1.jsx(AudioWaveform_1.AudioWaveform, { src: src, visualizationWidth: audioWidth, startFrom: trimBefore, durationInFrames: durationInFrames, volume: volume, doesVolumeChange: doesVolumeChange, playbackRate: playbackRate }) })
320
+ ] }));
302
321
  };
303
322
  exports.TimelineVideoInfo = TimelineVideoInfo;