From 500bcca42f406dc8ac30c601208828af81fc12c1 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 16:07:05 -0700 Subject: [PATCH 1/7] added url support --- gradio/components.py | 33 ++++++++++++++++----------------- gradio/processing_utils.py | 14 ++++++++++---- gradio/routes.py | 7 +++++-- gradio/utils.py | 10 ++++++++++ 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index 7c67b53d34142..a408391beb365 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -35,7 +35,7 @@ class DataframeData(TypedDict): from ffmpy import FFmpeg from markdown_it import MarkdownIt -from gradio import media_data, processing_utils +from gradio import media_data, processing_utils, utils from gradio.blocks import Block from gradio.documentation import document, set_documentation_group from gradio.events import ( @@ -55,7 +55,6 @@ class DataframeData(TypedDict): Serializable, SimpleSerializable, ) -from gradio.utils import component_or_layout_class set_documentation_group("component") @@ -1349,27 +1348,20 @@ def preprocess(self, x: str | Dict) -> np.array | PIL.Image | str | None: def postprocess(self, y: np.ndarray | PIL.Image | str | Path) -> str: """ Parameters: - y: image as a numpy array, PIL Image, string filepath, or Path filepath + y: image as a numpy array, PIL Image, string/Path filepath, or string URL Returns: base64 url data """ if y is None: return None if isinstance(y, np.ndarray): - dtype = "numpy" + return processing_utils.encode_array_to_base64(y) elif isinstance(y, PIL.Image.Image): - dtype = "pil" + return processing_utils.encode_pil_to_base64(y) elif isinstance(y, (str, Path)): - dtype = "file" + return processing_utils.encode_url_or_file_to_base64(y) else: raise ValueError("Cannot process this value as an Image") - if dtype == "pil": - out_y = processing_utils.encode_pil_to_base64(y) - elif dtype == "numpy": - out_y = processing_utils.encode_array_to_base64(y) - elif dtype == "file": - out_y = processing_utils.encode_url_or_file_to_base64(y) - return out_y def set_interpret_parameters(self, segments: int = 16): """ @@ -1530,7 +1522,7 @@ class Video(Changeable, Clearable, Playable, IOComponent, FileSerializable): combinations are .mp4 with h264 codec, .ogg with theora codec, and .webm with vp9 codec. If the component detects that the output video would not be playable in the browser it will attempt to convert it to a playable mp4 video. If the conversion fails, the original video is returned. - Preprocessing: passes the uploaded video as a {str} filepath whose extension can be set by `format`. + Preprocessing: passes the uploaded video as a {str} filepath or URL whose extension can be modified by `format`. Postprocessing: expects a {str} filepath to a video which is displayed. Examples-format: a {str} filepath to a local file that contains the video. Demos: video_identity @@ -1656,7 +1648,7 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: Processes a video to ensure that it is in the correct format before returning it to the front end. Parameters: - y: a path to video file + y: a path or URL to the video file Returns: a dictionary with the following keys: 'name' (containing the file path to a temporary copy of the video), 'data' (None), and 'is_file` (True). @@ -1664,6 +1656,9 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: if y is None: return None + if utils.validate_url(y): + y = processing_utils.download_to_file(y, dir=self.temp_dir).name + returned_format = y.split(".")[-1].lower() if ( processing_utils.ffmpeg_installed() @@ -1933,6 +1928,10 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: """ if y is None: return None + + if utils.validate_url(y): + y = processing_utils.download_to_file(y, dir=self.temp_dir).name + if isinstance(y, tuple): sample_rate, data = y file = tempfile.NamedTemporaryFile( @@ -3974,7 +3973,7 @@ def update( def component(cls_name: str) -> Component: - obj = component_or_layout_class(cls_name)() + obj = utils.component_or_layout_class(cls_name)() return obj @@ -3986,7 +3985,7 @@ def get_component_instance(comp: str | dict | Component, render=True) -> Compone return component_obj elif isinstance(comp, dict): name = comp.pop("name") - component_cls = component_or_layout_class(name) + component_cls = utils.component_or_layout_class(name) component_obj = component_cls(**comp) if not (render): component_obj.unrender() diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py index 56251344d2f7e..845f06e56a6f9 100644 --- a/gradio/processing_utils.py +++ b/gradio/processing_utils.py @@ -14,7 +14,7 @@ from ffmpy import FFmpeg, FFprobe, FFRuntimeError from PIL import Image, ImageOps, PngImagePlugin -from gradio import encryptor +from gradio import encryptor, utils with warnings.catch_warnings(): warnings.simplefilter("ignore") # Ignore pydub warning if ffmpeg is not installed @@ -31,10 +31,9 @@ def decode_base64_to_image(encoding): def encode_url_or_file_to_base64(path, encryption_key=None): - try: - requests.get(path) + if utils.validate_url(path): return encode_url_to_base64(path, encryption_key=encryption_key) - except (requests.exceptions.MissingSchema, requests.exceptions.InvalidSchema): + else: return encode_file_to_base64(path, encryption_key=encryption_key) @@ -89,6 +88,13 @@ def encode_plot_to_base64(plt): base64_str = str(base64.b64encode(bytes_data), "utf-8") return "data:image/png;base64," + base64_str +def download_to_file(url, dir=None): + file_suffix = os.path.splitext(url)[1] + file_obj = tempfile.NamedTemporaryFile(delete=False, suffix=file_suffix, dir=dir) + with requests.get(url, stream=True) as r: + with open(file_obj.name, 'wb') as f: + shutil.copyfileobj(r.raw, f) + return file_obj def save_array_to_file(image_array, dir=None): pil_image = Image.fromarray(_convert(image_array, np.uint8, force_copy=False)) diff --git a/gradio/routes.py b/gradio/routes.py index c82b361f7efa4..e0bce5aa6ec03 100644 --- a/gradio/routes.py +++ b/gradio/routes.py @@ -17,6 +17,7 @@ import orjson import pkg_resources +import requests from fastapi import Depends, FastAPI, HTTPException, Request, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, HTMLResponse, JSONResponse @@ -28,9 +29,9 @@ from starlette.websockets import WebSocket, WebSocketState import gradio -from gradio import encryptor +from gradio import encryptor, utils from gradio.exceptions import Error -from gradio.queue import Estimation, Event, Queue +from gradio.queue import Estimation, Event mimetypes.init() @@ -221,6 +222,8 @@ async def favicon(): @app.get("/file={path:path}", dependencies=[Depends(login_check)]) def file(path: str): + if utils.validate_url(path): + return RedirectResponse(url=path, status_code=status.HTTP_302_FOUND) if ( app.blocks.encrypt and isinstance(app.blocks.examples, str) diff --git a/gradio/utils.py b/gradio/utils.py index ed8a1e8996c42..0b8b312101a07 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -10,6 +10,7 @@ import os import pkgutil import random +import urllib import warnings from contextlib import contextmanager from copy import deepcopy @@ -670,3 +671,12 @@ def append_unique_suffix(name: str, list_of_names: List[str]): suffix_counter += 1 new_name = name + f"_{suffix_counter}" return new_name + + +def validate_url(possible_url: str) -> bool: + try: + if requests.get(possible_url).status_code == 200: + return True + except Exception: + pass + return False From 28e52031933eef35eb0dfef228d24b8609de5278 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 16:17:14 -0700 Subject: [PATCH 2/7] avoid copying temp file --- gradio/components.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index a408391beb365..a6c1096a851bf 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1655,9 +1655,12 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: """ if y is None: return None + + is_temp_file = False if utils.validate_url(y): y = processing_utils.download_to_file(y, dir=self.temp_dir).name + is_temp_file = True returned_format = y.split(".")[-1].lower() if ( @@ -1674,7 +1677,8 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: ff.run() y = output_file_name - y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) + if not is_temp_file: + y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) return {"name": y.name, "data": None, "is_file": True} @@ -1704,7 +1708,7 @@ class Audio(Changeable, Clearable, Playable, Streamable, IOComponent, FileSerial """ Creates an audio component that can be used to upload/record audio (as an input) or display audio (as an output). Preprocessing: passes the uploaded audio as a {Tuple(int, numpy.array)} corresponding to (sample rate, data) or as a {str} filepath, depending on `type` - Postprocessing: expects a {Tuple(int, numpy.array)} corresponding to (sample rate, data) or as a {str} filepath to an audio file, which gets displayed + Postprocessing: expects a {Tuple(int, numpy.array)} corresponding to (sample rate, data) or as a {str} filepath or URL to an audio file, which gets displayed Examples-format: a {str} filepath to a local file that contains audio. Demos: main_note, generate_tone, reverse_audio Guides: real_time_speech_recognition @@ -1922,7 +1926,7 @@ def generate_sample(self): def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: """ Parameters: - y: audio data in either of the following formats: a tuple of (sample_rate, data), or a string of the path to an audio file, or None. + y: audio data in either of the following formats: a tuple of (sample_rate, data), or a string filepath or URL to an audio file, or None. Returns: base64 url data """ @@ -1931,16 +1935,15 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: if utils.validate_url(y): y = processing_utils.download_to_file(y, dir=self.temp_dir).name - - if isinstance(y, tuple): + elif isinstance(y, tuple): sample_rate, data = y file = tempfile.NamedTemporaryFile( prefix="sample", suffix=".wav", delete=False ) processing_utils.audio_to_file(sample_rate, data, file.name) y = file.name - - y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) + else: + y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) return {"name": y.name, "data": None, "is_file": True} From d6e6e934f5ad5b15795ecc9d089aeb993c74b16e Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 16:17:40 -0700 Subject: [PATCH 3/7] formatting --- gradio/components.py | 6 +++--- gradio/processing_utils.py | 4 +++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index a6c1096a851bf..3451bdfeea9fa 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1655,13 +1655,13 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: """ if y is None: return None - + is_temp_file = False if utils.validate_url(y): y = processing_utils.download_to_file(y, dir=self.temp_dir).name is_temp_file = True - + returned_format = y.split(".")[-1].lower() if ( processing_utils.ffmpeg_installed() @@ -1932,7 +1932,7 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: """ if y is None: return None - + if utils.validate_url(y): y = processing_utils.download_to_file(y, dir=self.temp_dir).name elif isinstance(y, tuple): diff --git a/gradio/processing_utils.py b/gradio/processing_utils.py index 845f06e56a6f9..db53d196894d5 100644 --- a/gradio/processing_utils.py +++ b/gradio/processing_utils.py @@ -88,14 +88,16 @@ def encode_plot_to_base64(plt): base64_str = str(base64.b64encode(bytes_data), "utf-8") return "data:image/png;base64," + base64_str + def download_to_file(url, dir=None): file_suffix = os.path.splitext(url)[1] file_obj = tempfile.NamedTemporaryFile(delete=False, suffix=file_suffix, dir=dir) with requests.get(url, stream=True) as r: - with open(file_obj.name, 'wb') as f: + with open(file_obj.name, "wb") as f: shutil.copyfileobj(r.raw, f) return file_obj + def save_array_to_file(image_array, dir=None): pil_image = Image.fromarray(_convert(image_array, np.uint8, force_copy=False)) file_obj = tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir) From d6676be415dc569c191d8d848dc228f9e5352e46 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 16:18:33 -0700 Subject: [PATCH 4/7] remove unused import --- gradio/utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/gradio/utils.py b/gradio/utils.py index 0b8b312101a07..625f659237541 100644 --- a/gradio/utils.py +++ b/gradio/utils.py @@ -10,7 +10,6 @@ import os import pkgutil import random -import urllib import warnings from contextlib import contextmanager from copy import deepcopy From bab500af2b2997cba3303554432c8a121f55a16f Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 16:50:26 -0700 Subject: [PATCH 5/7] fixes --- gradio/components.py | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index 3451bdfeea9fa..bb310660fb6dd 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1656,11 +1656,8 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: if y is None: return None - is_temp_file = False - if utils.validate_url(y): y = processing_utils.download_to_file(y, dir=self.temp_dir).name - is_temp_file = True returned_format = y.split(".")[-1].lower() if ( @@ -1677,9 +1674,7 @@ def postprocess(self, y: str | None) -> Dict[str, str] | None: ff.run() y = output_file_name - if not is_temp_file: - y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) - + y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) return {"name": y.name, "data": None, "is_file": True} def style( @@ -1934,7 +1929,7 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: return None if utils.validate_url(y): - y = processing_utils.download_to_file(y, dir=self.temp_dir).name + y = processing_utils.download_to_file(y, dir=self.temp_dir) elif isinstance(y, tuple): sample_rate, data = y file = tempfile.NamedTemporaryFile( From 269d80deb33bd10211845665cb9dc09eeb924e23 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 17:06:41 -0700 Subject: [PATCH 6/7] updated example --- demo/reverse_audio/run.py | 2 +- gradio/components.py | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/demo/reverse_audio/run.py b/demo/reverse_audio/run.py index f58e82f855e2a..ad1e3983a93af 100644 --- a/demo/reverse_audio/run.py +++ b/demo/reverse_audio/run.py @@ -14,7 +14,7 @@ def reverse_audio(audio): inputs="microphone", outputs="audio", examples=[ - os.path.join(os.path.dirname(__file__), "audio/cantina.wav"), + "https://file-examples.com/storage/fe6d784fb46320d949c245e/2017/11/file_example_MP3_700KB.mp3", os.path.join(os.path.dirname(__file__), "audio/recording1.wav") ], cache_examples=True) diff --git a/gradio/components.py b/gradio/components.py index bb310660fb6dd..9b2a3ba543912 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1929,18 +1929,17 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: return None if utils.validate_url(y): - y = processing_utils.download_to_file(y, dir=self.temp_dir) + file = processing_utils.download_to_file(y, dir=self.temp_dir) elif isinstance(y, tuple): sample_rate, data = y file = tempfile.NamedTemporaryFile( prefix="sample", suffix=".wav", delete=False ) processing_utils.audio_to_file(sample_rate, data, file.name) - y = file.name else: - y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) + file = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir) - return {"name": y.name, "data": None, "is_file": True} + return {"name": file.name, "data": None, "is_file": True} def stream( self, @@ -1990,6 +1989,9 @@ def style( rounded=rounded, ) + def as_example(self, input_data: str) -> str: + return os.path.basename(input_data) + @document("change", "clear", "style") class File(Changeable, Clearable, IOComponent, FileSerializable): @@ -2169,8 +2171,8 @@ def style( rounded=rounded, ) - def as_example(self, input_data): - return Path(input_data).name + def as_example(self, input_data: str) -> str: + return os.path.basename(input_data) @document("change", "style") @@ -3632,8 +3634,8 @@ def style( rounded=rounded, ) - def as_example(self, input_data): - return Path(input_data).name + def as_example(self, input_data: str) -> str: + return os.path.basename(input_data) @document("change", "clear") From 90f7345c9f90103a724f33fe0414e164da4f8788 Mon Sep 17 00:00:00 2001 From: Abubakar Abid Date: Tue, 13 Sep 2022 17:15:51 -0700 Subject: [PATCH 7/7] fix --- gradio/components.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradio/components.py b/gradio/components.py index 9b2a3ba543912..4ae5539d176b9 100644 --- a/gradio/components.py +++ b/gradio/components.py @@ -1933,7 +1933,7 @@ def postprocess(self, y: Tuple[int, np.array] | str | None) -> str | None: elif isinstance(y, tuple): sample_rate, data = y file = tempfile.NamedTemporaryFile( - prefix="sample", suffix=".wav", delete=False + suffix=".wav", dir=self.temp_dir, delete=False ) processing_utils.audio_to_file(sample_rate, data, file.name) else: @@ -1990,7 +1990,7 @@ def style( ) def as_example(self, input_data: str) -> str: - return os.path.basename(input_data) + return Path(input_data).name @document("change", "clear", "style") @@ -2172,7 +2172,7 @@ def style( ) def as_example(self, input_data: str) -> str: - return os.path.basename(input_data) + return Path(input_data).name @document("change", "style") @@ -3635,7 +3635,7 @@ def style( ) def as_example(self, input_data: str) -> str: - return os.path.basename(input_data) + return Path(input_data).name @document("change", "clear")