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} {/if} {:else} - + {/if} diff --git a/ui/packages/image/src/Webcam.svelte b/ui/packages/image/src/Webcam.svelte index 5f19cfda7e..71d270582a 100644 --- a/ui/packages/image/src/Webcam.svelte +++ b/ui/packages/image/src/Webcam.svelte @@ -8,6 +8,7 @@ export let pending: boolean = false; export let mode: "image" | "video" = "image"; + export let mirror_webcam: boolean; const dispatch = createEventDispatcher(); @@ -102,7 +103,11 @@
-
{:else} -