Repository: gradio-app/gradio

Issue:
Mirroring for webcam inputs
- Mirror webcam's by default with necessary preprocessing.
- kwarg to disable mirror


Discussion:

Resolving patch:
diff --git a/demo/webcam/run.py b/demo/webcam/run.py
--- a/demo/webcam/run.py
+++ b/demo/webcam/run.py
@@ -3,11 +3,15 @@
 import gradio as gr
 
 
-def snap(image):
-    return np.flipud(image)
+def snap(image, video):
+    return [image, video]
 
 
-demo = gr.Interface(snap, gr.Image(source="webcam", tool=None), "image")
+demo = gr.Interface(
+    snap,
+    [gr.Image(source="webcam", tool=None), gr.Video(source="webcam")],
+    ["image", "video"],
+)
 
 if __name__ == "__main__":
     demo.launch()
diff --git a/gradio/components.py b/gradio/components.py
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -10,6 +10,7 @@
 import numbers
 import operator
 import os
+import pathlib
 import shutil
 import tempfile
 import warnings
@@ -1337,6 +1338,7 @@ def __init__(
         visible: bool = True,
         streaming: bool = False,
         elem_id: Optional[str] = None,
+        mirror_webcam: bool = True,
         **kwargs,
     ):
         """
@@ -1354,7 +1356,9 @@ def __init__(
         visible (bool): If False, component will be hidden.
         streaming (bool): If True when used in a `live` interface, will automatically stream webcam feed. Only valid is source is 'webcam'.
         elem_id (Optional[str]): An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+        mirror_webcam (bool): If True webcam will be mirrored. Default is True.
         """
+        self.mirror_webcam = mirror_webcam
         self.type = type
         self.value = self.postprocess(value)
         self.shape = shape
@@ -1388,6 +1392,7 @@ def get_config(self):
             "tool": self.tool,
             "value": self.value,
             "streaming": self.streaming,
+            "mirror_webcam": self.mirror_webcam,
             **IOComponent.get_config(self),
         }
 
@@ -1461,6 +1466,8 @@ def preprocess(self, x: Optional[str]) -> np.array | PIL.Image | str | None:
             im = processing_utils.resize_and_crop(im, self.shape)
         if self.invert_colors:
             im = PIL.ImageOps.invert(im)
+        if self.source == "webcam" and self.mirror_webcam is True:
+            im = PIL.ImageOps.mirror(im)
 
         if not (self.tool == "sketch"):
             return self.format_image(im, fmt)
@@ -1693,6 +1700,7 @@ def __init__(
         interactive: Optional[bool] = None,
         visible: bool = True,
         elem_id: Optional[str] = None,
+        mirror_webcam: bool = True,
         **kwargs,
     ):
         """
@@ -1705,9 +1713,11 @@ def __init__(
         interactive (Optional[bool]): if True, will allow users to upload a video; if False, can only be used to display videos. If not provided, this is inferred based on whether the component is used as an input or output.
         visible (bool): If False, component will be hidden.
         elem_id (Optional[str]): An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
+        mirror_webcam (bool): If True webcma will be mirrored. Default is True.
         """
         self.format = format
         self.source = source
+        self.mirror_webcam = mirror_webcam
         self.value = self.postprocess(value)
         IOComponent.__init__(
             self,
@@ -1723,6 +1733,7 @@ def get_config(self):
         return {
             "source": self.source,
             "value": self.value,
+            "mirror_webcam": self.mirror_webcam,
             **IOComponent.get_config(self),
         }
 
@@ -1771,11 +1782,21 @@ def preprocess(self, x: Dict[str, str] | None) -> str | None:
             )
         file_name = file.name
         uploaded_format = file_name.split(".")[-1].lower()
+
         if self.format is not None and uploaded_format != self.format:
             output_file_name = file_name[0 : file_name.rindex(".") + 1] + self.format
             ff = FFmpeg(inputs={file_name: None}, outputs={output_file_name: None})
             ff.run()
             return output_file_name
+        elif self.source == "webcam" and self.mirror_webcam is True:
+            path = pathlib.Path(file_name)
+            output_file_name = str(path.with_stem(f"{path.stem}_flip"))
+            ff = FFmpeg(
+                inputs={file_name: None},
+                outputs={output_file_name: ["-vf", "hflip", "-c:a", "copy"]},
+            )
+            ff.run()
+            return output_file_name
         else:
             return file_name
 


Test patch:
diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py
--- a/gradio/test_data/blocks_configs.py
+++ b/gradio/test_data/blocks_configs.py
@@ -46,6 +46,7 @@
                 "source": "upload",
                 "tool": "editor",
                 "streaming": False,
+                "mirror_webcam": True,
                 "show_label": True,
                 "name": "image",
                 "visible": True,
@@ -86,6 +87,7 @@
                 "source": "upload",
                 "tool": "editor",
                 "streaming": False,
+                "mirror_webcam": True,
                 "show_label": True,
                 "name": "image",
                 "visible": True,
@@ -232,6 +234,7 @@
                 "source": "upload",
                 "tool": "editor",
                 "streaming": False,
+                "mirror_webcam": True,
                 "show_label": True,
                 "name": "image",
                 "visible": True,
@@ -277,6 +280,7 @@
                 "source": "upload",
                 "tool": "editor",
                 "streaming": False,
+                "mirror_webcam": True,
                 "show_label": True,
                 "name": "image",
                 "visible": True,
@@ -433,6 +437,7 @@
                 "image_mode": "RGB",
                 "source": "upload",
                 "streaming": False,
+                "mirror_webcam": True,
                 "tool": "editor",
                 "name": "image",
                 "style": {},
@@ -480,6 +485,7 @@
                 "source": "upload",
                 "tool": "editor",
                 "streaming": False,
+                "mirror_webcam": True,
                 "name": "image",
                 "style": {},
             },
diff --git a/test/test_components.py b/test/test_components.py
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -610,6 +610,7 @@ def test_component_functions(self):
                 "visible": True,
                 "value": None,
                 "interactive": None,
+                "mirror_webcam": True,
             },
         )
         self.assertIsNone(image_input.preprocess(None))
@@ -1174,6 +1175,7 @@ def test_component_functions(self):
                 "visible": True,
                 "value": None,
                 "interactive": None,
+                "mirror_webcam": True,
             },
         )
         self.assertIsNone(video_input.preprocess(None))