diff --git a/demo/webcam/run.py b/demo/webcam/run.py
index 9adb522e31..4f2a9e0622 100644
--- a/demo/webcam/run.py
+++ b/demo/webcam/run.py
@@ -3,11 +3,15 @@ import numpy as np
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
index 673dd154c2..cd5416e96d 100644
--- a/gradio/components.py
+++ b/gradio/components.py
@@ -10,6 +10,7 @@ import math
import numbers
import operator
import os
+import pathlib
import shutil
import tempfile
import warnings
@@ -1337,6 +1338,7 @@ class Image(Editable, Clearable, Changeable, Streamable, IOComponent):
visible: bool = True,
streaming: bool = False,
elem_id: Optional[str] = None,
+ mirror_webcam: bool = True,
**kwargs,
):
"""
@@ -1354,7 +1356,9 @@ class Image(Editable, Clearable, Changeable, Streamable, IOComponent):
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 @@ class Image(Editable, Clearable, Changeable, Streamable, IOComponent):
"tool": self.tool,
"value": self.value,
"streaming": self.streaming,
+ "mirror_webcam": self.mirror_webcam,
**IOComponent.get_config(self),
}
@@ -1461,6 +1466,8 @@ class Image(Editable, Clearable, Changeable, Streamable, IOComponent):
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 @@ class Video(Changeable, Clearable, Playable, IOComponent):
interactive: Optional[bool] = None,
visible: bool = True,
elem_id: Optional[str] = None,
+ mirror_webcam: bool = True,
**kwargs,
):
"""
@@ -1705,9 +1713,11 @@ class Video(Changeable, Clearable, Playable, IOComponent):
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 @@ class Video(Changeable, Clearable, Playable, IOComponent):
return {
"source": self.source,
"value": self.value,
+ "mirror_webcam": self.mirror_webcam,
**IOComponent.get_config(self),
}
@@ -1771,11 +1782,21 @@ class Video(Changeable, Clearable, Playable, IOComponent):
)
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
diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py
index 2ecfb8afaa..6003aaf0de 100644
--- a/gradio/test_data/blocks_configs.py
+++ b/gradio/test_data/blocks_configs.py
@@ -46,6 +46,7 @@ XRAY_CONFIG = {
"source": "upload",
"tool": "editor",
"streaming": False,
+ "mirror_webcam": True,
"show_label": True,
"name": "image",
"visible": True,
@@ -86,6 +87,7 @@ XRAY_CONFIG = {
"source": "upload",
"tool": "editor",
"streaming": False,
+ "mirror_webcam": True,
"show_label": True,
"name": "image",
"visible": True,
@@ -232,6 +234,7 @@ XRAY_CONFIG_DIFF_IDS = {
"source": "upload",
"tool": "editor",
"streaming": False,
+ "mirror_webcam": True,
"show_label": True,
"name": "image",
"visible": True,
@@ -277,6 +280,7 @@ XRAY_CONFIG_DIFF_IDS = {
"source": "upload",
"tool": "editor",
"streaming": False,
+ "mirror_webcam": True,
"show_label": True,
"name": "image",
"visible": True,
@@ -433,6 +437,7 @@ XRAY_CONFIG_WITH_MISTAKE = {
"image_mode": "RGB",
"source": "upload",
"streaming": False,
+ "mirror_webcam": True,
"tool": "editor",
"name": "image",
"style": {},
@@ -480,6 +485,7 @@ XRAY_CONFIG_WITH_MISTAKE = {
"source": "upload",
"tool": "editor",
"streaming": False,
+ "mirror_webcam": True,
"name": "image",
"style": {},
},
diff --git a/test/test_components.py b/test/test_components.py
index 2a83d8fc33..1a1d81d916 100644
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -610,6 +610,7 @@ class TestImage(unittest.TestCase):
"visible": True,
"value": None,
"interactive": None,
+ "mirror_webcam": True,
},
)
self.assertIsNone(image_input.preprocess(None))
@@ -1174,6 +1175,7 @@ class TestVideo(unittest.TestCase):
"visible": True,
"value": None,
"interactive": None,
+ "mirror_webcam": True,
},
)
self.assertIsNone(video_input.preprocess(None))
diff --git a/ui/packages/app/src/components/Image/Image.svelte b/ui/packages/app/src/components/Image/Image.svelte
index 700d0095c2..6db206d6fe 100644
--- a/ui/packages/app/src/components/Image/Image.svelte
+++ b/ui/packages/app/src/components/Image/Image.svelte
@@ -17,6 +17,7 @@
export let streaming: boolean;
export let pending: boolean;
export let style: Styles = {};
+ export let mirror_webcam: boolean;
export let loading_status: LoadingStatus;
@@ -58,6 +59,7 @@
drop_text={$_("interface.drop_image")}
or_text={$_("or")}
upload_text={$_("interface.click_to_upload")}
+ {mirror_webcam}
/>
{/if}
diff --git a/ui/packages/app/src/components/Video/Video.svelte b/ui/packages/app/src/components/Video/Video.svelte
index 7f7d8744c0..da54f6742b 100644
--- a/ui/packages/app/src/components/Video/Video.svelte
+++ b/ui/packages/app/src/components/Video/Video.svelte
@@ -18,6 +18,7 @@
export let show_label: boolean;
export let loading_status: LoadingStatus;
export let style: Styles = {};
+ export let mirror_webcam: boolean;
export let mode: "static" | "dynamic";
@@ -52,6 +53,7 @@
drop_text={$_("interface.drop_video")}
or_text={$_("or")}
upload_text={$_("interface.click_to_upload")}
+ {mirror_webcam}
on:change
on:clear
on:play
diff --git a/ui/packages/icons/src/Maximise.svelte b/ui/packages/icons/src/Maximise.svelte
new file mode 100644
index 0000000000..7063a9c259
--- /dev/null
+++ b/ui/packages/icons/src/Maximise.svelte
@@ -0,0 +1,14 @@
+
diff --git a/ui/packages/icons/src/Pause.svelte b/ui/packages/icons/src/Pause.svelte
new file mode 100644
index 0000000000..59270e9b92
--- /dev/null
+++ b/ui/packages/icons/src/Pause.svelte
@@ -0,0 +1,17 @@
+
diff --git a/ui/packages/icons/src/Play.svelte b/ui/packages/icons/src/Play.svelte
new file mode 100644
index 0000000000..3ccaac8884
--- /dev/null
+++ b/ui/packages/icons/src/Play.svelte
@@ -0,0 +1,11 @@
+
diff --git a/ui/packages/icons/src/index.ts b/ui/packages/icons/src/index.ts
index a890031c44..d153445193 100644
--- a/ui/packages/icons/src/index.ts
+++ b/ui/packages/icons/src/index.ts
@@ -17,3 +17,6 @@ export { default as JSON } from "./JSON.svelte";
export { default as Tree } from "./Tree.svelte";
export { default as Chat } from "./Chat.svelte";
export { default as Plot } from "./Plot.svelte";
+export { default as Play } from "./Play.svelte";
+export { default as Pause } from "./Pause.svelte";
+export { default as Maximise } from "./Maximise.svelte";
diff --git a/ui/packages/image/src/Image.svelte b/ui/packages/image/src/Image.svelte
index a59cfd509c..55ba11d583 100644
--- a/ui/packages/image/src/Image.svelte
+++ b/ui/packages/image/src/Image.svelte
@@ -25,6 +25,7 @@
export let upload_text: string = "click to upload";
export let streaming: boolean = false;
export let pending: boolean = false;
+ export let mirror_webcam: boolean;
let sketch: Sketch;
@@ -133,6 +134,7 @@
on:stream={handle_save}
{streaming}
{pending}
+ {mirror_webcam}
/>
{/if}
{:else if tool === "select"}
@@ -145,13 +147,19 @@
editable
/>
-
+
{:else if tool === "sketch" && value !== null}
{#if img_width > 0}