diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index bd203291..c2e23f70 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -1227,12 +1227,14 @@ export function SettingsPanel({ {webcamLayoutPreset !== "no-webcam" && (
-
Mirror webcam
+
+ {t("layout.mirrorWebcam")} +
)} diff --git a/src/i18n/locales/ar/settings.json b/src/i18n/locales/ar/settings.json index 6cd90b6a..84937fbb 100644 --- a/src/i18n/locales/ar/settings.json +++ b/src/i18n/locales/ar/settings.json @@ -45,7 +45,8 @@ "dualFrame": "إطار مزدوج", "webcamShape": "شكل الكاميرا", "webcamSize": "حجم كاميرا الويب", - "noWebcam": "بدون كاميرا" + "noWebcam": "بدون كاميرا", + "mirrorWebcam": "عكس كاميرا الويب" }, "effects": { "title": "تأثيرات الفيديو", diff --git a/src/i18n/locales/en/settings.json b/src/i18n/locales/en/settings.json index 3e2541aa..42f1695a 100644 --- a/src/i18n/locales/en/settings.json +++ b/src/i18n/locales/en/settings.json @@ -45,7 +45,8 @@ "dualFrame": "Dual Frame", "noWebcam": "No Webcam", "webcamShape": "Camera Shape", - "webcamSize": "Webcam Size" + "webcamSize": "Webcam Size", + "mirrorWebcam": "Mirror webcam" }, "effects": { "title": "Video Effects", diff --git a/src/i18n/locales/es/settings.json b/src/i18n/locales/es/settings.json index 99cff77e..04593e68 100644 --- a/src/i18n/locales/es/settings.json +++ b/src/i18n/locales/es/settings.json @@ -45,7 +45,8 @@ "dualFrame": "Marco dual", "webcamShape": "Forma de cámara", "webcamSize": "Tamaño de cámara", - "noWebcam": "Sin cámara" + "noWebcam": "Sin cámara", + "mirrorWebcam": "Reflejar cámara" }, "effects": { "title": "Efectos de video", diff --git a/src/i18n/locales/fr/settings.json b/src/i18n/locales/fr/settings.json index c968c68d..28a9b76b 100644 --- a/src/i18n/locales/fr/settings.json +++ b/src/i18n/locales/fr/settings.json @@ -46,7 +46,8 @@ "dualFrame": "Double cadre", "webcamShape": "Forme de la caméra", "webcamSize": "Taille de la caméra", - "noWebcam": "Sans webcam" + "noWebcam": "Sans webcam", + "mirrorWebcam": "Inverser la webcam" }, "effects": { "title": "Effets vidéo", diff --git a/src/i18n/locales/it/settings.json b/src/i18n/locales/it/settings.json index 0515a765..b079b18c 100644 --- a/src/i18n/locales/it/settings.json +++ b/src/i18n/locales/it/settings.json @@ -1,5 +1,6 @@ { "zoom": { + "previewHold": "Tieni premuto per vedere lo zoom", "level": "Livello zoom", "customScale": "Zoom personalizzato", "selectRegion": "Seleziona una regione zoom da regolare", @@ -44,7 +45,8 @@ "dualFrame": "Doppio frame", "noWebcam": "Nessuna webcam", "webcamShape": "Forma fotocamera", - "webcamSize": "Dimensione webcam" + "webcamSize": "Dimensione webcam", + "mirrorWebcam": "Specchia webcam" }, "effects": { "title": "Effetti video", diff --git a/src/i18n/locales/ja-JP/settings.json b/src/i18n/locales/ja-JP/settings.json index 697e1ac5..e8181f17 100644 --- a/src/i18n/locales/ja-JP/settings.json +++ b/src/i18n/locales/ja-JP/settings.json @@ -45,7 +45,8 @@ "dualFrame": "デュアルフレーム", "webcamShape": "カメラの形状", "webcamSize": "カメラのサイズ", - "noWebcam": "Webカメラなし" + "noWebcam": "Webカメラなし", + "mirrorWebcam": "Webカメラを反転" }, "effects": { "title": "動画効果", diff --git a/src/i18n/locales/ko-KR/settings.json b/src/i18n/locales/ko-KR/settings.json index df4c6a27..e961fef2 100644 --- a/src/i18n/locales/ko-KR/settings.json +++ b/src/i18n/locales/ko-KR/settings.json @@ -45,7 +45,8 @@ "webcamShape": "카메라 모양", "webcamSize": "웹캠 크기", "dualFrame": "듀얼 프레임", - "noWebcam": "웹캠 없음" + "noWebcam": "웹캠 없음", + "mirrorWebcam": "웹캠 미러링" }, "effects": { "title": "비디오 효과", diff --git a/src/i18n/locales/ru/settings.json b/src/i18n/locales/ru/settings.json index c0dbba8f..e451d224 100644 --- a/src/i18n/locales/ru/settings.json +++ b/src/i18n/locales/ru/settings.json @@ -45,7 +45,8 @@ "dualFrame": "Двойной кадр", "webcamShape": "Форма камеры", "webcamSize": "Размер веб-камеры", - "noWebcam": "Без веб-камеры" + "noWebcam": "Без веб-камеры", + "mirrorWebcam": "Зеркалить веб-камеру" }, "effects": { "title": "Видеоэффекты", diff --git a/src/i18n/locales/tr/settings.json b/src/i18n/locales/tr/settings.json index 587f2668..0e245706 100644 --- a/src/i18n/locales/tr/settings.json +++ b/src/i18n/locales/tr/settings.json @@ -45,7 +45,8 @@ "webcamShape": "Kamera Şekli", "dualFrame": "Çift Kare", "webcamSize": "Webcam Boyutu", - "noWebcam": "Web kamerası yok" + "noWebcam": "Web kamerası yok", + "mirrorWebcam": "Web kamerasını aynala" }, "effects": { "title": "Video Efektleri", diff --git a/src/i18n/locales/vi/settings.json b/src/i18n/locales/vi/settings.json index e83c799d..0897e369 100644 --- a/src/i18n/locales/vi/settings.json +++ b/src/i18n/locales/vi/settings.json @@ -45,7 +45,8 @@ "dualFrame": "Khung kép", "webcamShape": "Hình dạng máy ảnh", "webcamSize": "Kích thước Webcam", - "noWebcam": "Không có webcam" + "noWebcam": "Không có webcam", + "mirrorWebcam": "Lật webcam" }, "effects": { "title": "Hiệu ứng video", diff --git a/src/i18n/locales/zh-CN/settings.json b/src/i18n/locales/zh-CN/settings.json index b9b516a0..304dc309 100644 --- a/src/i18n/locales/zh-CN/settings.json +++ b/src/i18n/locales/zh-CN/settings.json @@ -45,7 +45,8 @@ "dualFrame": "双画框", "webcamShape": "摄像头形状", "webcamSize": "摄像头大小", - "noWebcam": "无摄像头" + "noWebcam": "无摄像头", + "mirrorWebcam": "镜像摄像头" }, "effects": { "title": "视频效果", diff --git a/src/i18n/locales/zh-TW/settings.json b/src/i18n/locales/zh-TW/settings.json index ee56459e..0ff018b3 100644 --- a/src/i18n/locales/zh-TW/settings.json +++ b/src/i18n/locales/zh-TW/settings.json @@ -46,7 +46,8 @@ "dualFrame": "雙畫框", "webcamShape": "攝影機形狀", "webcamSize": "攝影機大小", - "noWebcam": "無網路攝影機" + "noWebcam": "無網路攝影機", + "mirrorWebcam": "鏡像攝影機" }, "effects": { "title": "影片效果", diff --git a/src/lib/exporter/frameRenderer.test.ts b/src/lib/exporter/frameRenderer.test.ts index 4fff433d..16ba6bde 100644 --- a/src/lib/exporter/frameRenderer.test.ts +++ b/src/lib/exporter/frameRenderer.test.ts @@ -66,4 +66,32 @@ describe("drawWebcamFrameImage", () => { ["restore"], ]); }); + + it("restores the canvas context if mirrored drawing fails", () => { + const { calls, ctx } = createMockCanvasContext(); + const frame = {} as CanvasImageSource; + const error = new Error("draw failed"); + ctx.drawImage = () => { + calls.push(["drawImage", frame, 12, 8, 640, 360, 0, 0, 320, 180]); + throw error; + }; + + expect(() => + drawWebcamFrameImage( + ctx, + frame, + { x: 12, y: 8, width: 640, height: 360 }, + { x: 100, y: 50, width: 320, height: 180 }, + true, + ), + ).toThrow(error); + + expect(calls).toEqual([ + ["save"], + ["translate", 420, 50], + ["scale", -1, 1], + ["drawImage", frame, 12, 8, 640, 360, 0, 0, 320, 180], + ["restore"], + ]); + }); }); diff --git a/src/lib/exporter/webcamFrameDrawing.ts b/src/lib/exporter/webcamFrameDrawing.ts index 9a99d38d..41f98d8f 100644 --- a/src/lib/exporter/webcamFrameDrawing.ts +++ b/src/lib/exporter/webcamFrameDrawing.ts @@ -19,10 +19,13 @@ export function drawWebcamFrameImage( ) { if (mirrored) { ctx.save(); - ctx.translate(dest.x + dest.width, dest.y); - ctx.scale(-1, 1); - ctx.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0, 0, dest.width, dest.height); - ctx.restore(); + try { + ctx.translate(dest.x + dest.width, dest.y); + ctx.scale(-1, 1); + ctx.drawImage(image, crop.x, crop.y, crop.width, crop.height, 0, 0, dest.width, dest.height); + } finally { + ctx.restore(); + } return; }