From 5f3e1e09e8695857c55e11a5a3a70c5b9239486e Mon Sep 17 00:00:00 2001 From: webadderall <131426131+webadderall@users.noreply.github.com> Date: Wed, 18 Mar 2026 14:07:47 +1100 Subject: [PATCH] feat: allow manual webcam footage import --- src/components/video-editor/SettingsPanel.tsx | 42 ++++++++++++++++++ src/components/video-editor/VideoEditor.tsx | 44 +++++++++++++++++++ src/i18n/locales/en/settings.json | 7 +++ src/i18n/locales/es/settings.json | 7 +++ src/i18n/locales/zh-CN/settings.json | 7 +++ 5 files changed, 107 insertions(+) diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index 12c49791..7926e0a0 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -141,6 +141,8 @@ interface SettingsPanelProps { onBorderRadiusChange?: (radius: number) => void; webcam?: WebcamOverlaySettings; onWebcamChange?: (webcam: WebcamOverlaySettings) => void; + onUploadWebcam?: () => void; + onClearWebcam?: () => void; padding?: number; onPaddingChange?: (padding: number) => void; cropRegion?: CropRegion; @@ -222,6 +224,8 @@ export function SettingsPanel({ onBorderRadiusChange, webcam, onWebcamChange, + onUploadWebcam, + onClearWebcam, padding = 50, onPaddingChange, cropRegion, @@ -356,6 +360,8 @@ export function SettingsPanel({ } }; + const webcamFileName = webcam?.sourcePath?.split(/[\\/]/).pop() ?? null; + const zoomEnabled = Boolean(selectedZoomDepth); const trimEnabled = Boolean(selectedTrimId); @@ -816,6 +822,42 @@ export function SettingsPanel({ className="data-[state=checked]:bg-[#2563EB] scale-90" /> +
+
+
+
+ {tSettings("effects.webcamFootage")} +
+
+ {webcamFileName ?? tSettings("effects.webcamFootageDescription")} +
+
+
+ + {webcam?.sourcePath ? ( + + ) : null} +
+
+
{ + const sourcePath = videoSourcePath ?? (videoPath ? fromFileUrl(videoPath) : null); + if (!sourcePath || !window.electronAPI.setCurrentRecordingSession) { + return; + } + + await window.electronAPI.setCurrentRecordingSession({ + videoPath: sourcePath, + webcamPath, + }); + }, + [videoPath, videoSourcePath], + ); + + const handleUploadWebcam = useCallback(async () => { + const result = await window.electronAPI.openVideoFilePicker(); + if (!result.success || !result.path) { + return; + } + + setWebcam((prev) => ({ + ...prev, + enabled: true, + sourcePath: result.path ?? null, + })); + + await syncRecordingSessionWebcam(result.path); + toast.success(t("settings.effects.webcamFootageAdded")); + }, [syncRecordingSessionWebcam, t]); + + const handleClearWebcam = useCallback(async () => { + setWebcam((prev) => ({ + ...prev, + enabled: false, + sourcePath: null, + })); + + await syncRecordingSessionWebcam(null); + toast.success(t("settings.effects.webcamFootageRemoved")); + }, [syncRecordingSessionWebcam, t]); + useEffect(() => { const snapshot = cloneSnapshot(buildHistorySnapshot()); @@ -2208,6 +2250,8 @@ export default function VideoEditor() { onBorderRadiusChange={setBorderRadius} webcam={webcam} onWebcamChange={setWebcam} + onUploadWebcam={handleUploadWebcam} + onClearWebcam={handleClearWebcam} padding={padding} onPaddingChange={setPadding} cropRegion={cropRegion} diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 7cd90cb4..f0eaf679 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -26,6 +26,13 @@ "cursorClickBounce": "Cursor Click Bounce", "cursorSway": "Cursor Sway", "webcam": "Webcam Overlay", + "webcamFootage": "Webcam Footage", + "webcamFootageDescription": "No webcam footage linked to this video", + "uploadWebcamFootage": "Upload footage", + "replaceWebcamFootage": "Replace footage", + "removeWebcamFootage": "Remove footage", + "webcamFootageAdded": "Webcam footage linked", + "webcamFootageRemoved": "Webcam footage removed", "webcamSize": "Webcam Size", "webcamReactToZoom": "Webcam Reacts To Zoom", "webcamRoundness": "Webcam Roundness", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index f41b385b..d5a6c708 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -26,6 +26,13 @@ "cursorClickBounce": "Rebote de clic del cursor", "cursorSway": "Balanceo del cursor", "webcam": "Superposición de cámara", + "webcamFootage": "Metraje de cámara", + "webcamFootageDescription": "No hay metraje de cámara vinculado a este video", + "uploadWebcamFootage": "Subir metraje", + "replaceWebcamFootage": "Reemplazar metraje", + "removeWebcamFootage": "Quitar metraje", + "webcamFootageAdded": "Metraje de cámara vinculado", + "webcamFootageRemoved": "Metraje de cámara eliminado", "webcamSize": "Tamaño de cámara", "webcamReactToZoom": "La cámara reacciona al zoom", "webcamRoundness": "Redondez de cámara", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index 7db0c6ad..7ed4bf4c 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -26,6 +26,13 @@ "cursorClickBounce": "光标点击弹跳", "cursorSway": "光标摆动", "webcam": "摄像头叠加", + "webcamFootage": "摄像头素材", + "webcamFootageDescription": "此视频尚未关联摄像头素材", + "uploadWebcamFootage": "上传素材", + "replaceWebcamFootage": "替换素材", + "removeWebcamFootage": "移除素材", + "webcamFootageAdded": "已关联摄像头素材", + "webcamFootageRemoved": "已移除摄像头素材", "webcamSize": "摄像头大小", "webcamReactToZoom": "摄像头随缩放变化", "webcamRoundness": "摄像头圆角",