From 3be195cc15f54bff333acaa3d296943d1f3d0104 Mon Sep 17 00:00:00 2001 From: xKeCo Date: Wed, 1 Apr 2026 01:41:20 -0500 Subject: [PATCH 01/20] =?UTF-8?q?=E2=9C=A8=20feat:=20smooth=20auto-follow?= =?UTF-8?q?=20zoom=20with=20export=20parity?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/video-editor/SettingsPanel.tsx | 41 ++++++++++++++ src/components/video-editor/VideoEditor.tsx | 24 ++++++++ src/components/video-editor/VideoPlayback.tsx | 49 ++++++++++++++++- .../video-editor/projectPersistence.ts | 1 + src/components/video-editor/types.ts | 2 + .../video-editor/videoPlayback/constants.ts | 2 + .../videoPlayback/cursorFollowUtils.ts | 54 ++++++++++++++++++ .../videoPlayback/overlayUtils.ts | 2 +- .../videoPlayback/zoomRegionUtils.ts | 55 +++++++++++++++---- src/i18n/locales/en/settings.json | 8 ++- src/i18n/locales/es/settings.json | 8 ++- src/i18n/locales/zh-CN/settings.json | 8 ++- src/lib/exporter/frameRenderer.ts | 47 +++++++++++++++- src/lib/exporter/gifExporter.ts | 2 + src/lib/exporter/videoExporter.ts | 2 + 15 files changed, 287 insertions(+), 18 deletions(-) create mode 100644 src/components/video-editor/videoPlayback/cursorFollowUtils.ts diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index f5afe35..bec50c2 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -92,6 +92,9 @@ interface SettingsPanelProps { onWallpaperChange: (path: string) => void; selectedZoomDepth?: ZoomDepth | null; onZoomDepthChange?: (depth: ZoomDepth) => void; + selectedZoomFocusMode?: import("./types").ZoomFocusMode | null; + onZoomFocusModeChange?: (mode: import("./types").ZoomFocusMode) => void; + hasCursorTelemetry?: boolean; selectedZoomId?: string | null; onZoomDelete?: (id: string) => void; selectedTrimId?: string | null; @@ -161,6 +164,9 @@ export function SettingsPanel({ onWallpaperChange, selectedZoomDepth, onZoomDepthChange, + selectedZoomFocusMode, + onZoomFocusModeChange, + hasCursorTelemetry = false, selectedZoomId, onZoomDelete, selectedTrimId, @@ -500,6 +506,41 @@ export function SettingsPanel({ {!zoomEnabled && (

{t("zoom.selectRegion")}

)} + {zoomEnabled && hasCursorTelemetry && ( +
+ + {t("zoom.focusMode.title")} + +
+ {(["manual", "auto"] as const).map((mode) => { + const isActive = selectedZoomFocusMode === mode; + return ( + + ); + })} +
+ {selectedZoomFocusMode === "auto" && ( +

+ {t("zoom.focusMode.autoDescription")} +

+ )} +
+ )} {zoomEnabled && ( + ))} + + + )} )} diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 304d10f..469f892 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -84,6 +84,7 @@ export default function VideoEditor() { padding, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, } = editorState; @@ -195,6 +196,7 @@ export default function VideoEditor() { annotationRegions: normalizedEditor.annotationRegions, aspectRatio: normalizedEditor.aspectRatio, webcamLayoutPreset: normalizedEditor.webcamLayoutPreset, + webcamMaskShape: normalizedEditor.webcamMaskShape, webcamPosition: normalizedEditor.webcamPosition, }); setExportQuality(normalizedEditor.exportQuality); @@ -264,6 +266,7 @@ export default function VideoEditor() { annotationRegions, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, exportQuality, exportFormat, @@ -287,6 +290,7 @@ export default function VideoEditor() { annotationRegions, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, exportQuality, exportFormat, @@ -380,6 +384,7 @@ export default function VideoEditor() { annotationRegions, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, exportQuality, exportFormat, @@ -434,6 +439,7 @@ export default function VideoEditor() { annotationRegions, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, exportQuality, exportFormat, @@ -1090,6 +1096,7 @@ export default function VideoEditor() { cropRegion, annotationRegions, webcamLayoutPreset, + webcamMaskShape, webcamPosition, previewWidth, previewHeight, @@ -1221,6 +1228,7 @@ export default function VideoEditor() { cropRegion, annotationRegions, webcamLayoutPreset, + webcamMaskShape, webcamPosition, previewWidth, previewHeight, @@ -1289,6 +1297,7 @@ export default function VideoEditor() { isPlaying, aspectRatio, webcamLayoutPreset, + webcamMaskShape, webcamPosition, exportQuality, handleExportSaved, @@ -1473,6 +1482,7 @@ export default function VideoEditor() { videoPath={videoPath || ""} webcamVideoPath={webcamVideoPath || undefined} webcamLayoutPreset={webcamLayoutPreset} + webcamMaskShape={webcamMaskShape} webcamPosition={webcamPosition} onWebcamPositionChange={(pos) => updateState({ webcamPosition: pos })} onWebcamPositionDragEnd={commitState} @@ -1613,6 +1623,8 @@ export default function VideoEditor() { webcamPosition: preset === "vertical-stack" ? null : webcamPosition, }) } + webcamMaskShape={webcamMaskShape} + onWebcamMaskShapeChange={(shape) => pushState({ webcamMaskShape: shape })} videoElement={videoPlaybackRef.current?.video || null} exportQuality={exportQuality} onExportQualityChange={setExportQuality} diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 71030bb..3bc4048 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -25,6 +25,7 @@ import { type StyledRenderRect, type WebcamLayoutPreset, } from "@/lib/compositeLayout"; +import { getCssClipPath } from "@/lib/webcamMaskShapes"; import { type AspectRatio, formatAspectRatioForCSS, @@ -63,6 +64,7 @@ interface VideoPlaybackProps { videoPath: string; webcamVideoPath?: string; webcamLayoutPreset: WebcamLayoutPreset; + webcamMaskShape?: import("./types").WebcamMaskShape; webcamPosition?: { cx: number; cy: number } | null; onWebcamPositionChange?: (position: { cx: number; cy: number }) => void; onWebcamPositionDragEnd?: () => void; @@ -111,6 +113,7 @@ const VideoPlayback = forwardRef( videoPath, webcamVideoPath, webcamLayoutPreset, + webcamMaskShape, webcamPosition, onWebcamPositionChange, onWebcamPositionDragEnd, @@ -272,6 +275,7 @@ const VideoPlayback = forwardRef( webcamDimensions, webcamLayoutPreset, webcamPosition, + webcamMaskShape, }); if (result) { @@ -302,6 +306,7 @@ const VideoPlayback = forwardRef( webcamDimensions, webcamLayoutPreset, webcamPosition, + webcamMaskShape, ]); useEffect(() => { @@ -1154,31 +1159,47 @@ const VideoPlayback = forwardRef( : "none", }} /> - {webcamVideoPath && ( -