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;
}