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": "摄像头圆角",