Added support for URLs for Video, Audio, and Image (#2256)

* added url support

* avoid copying temp file

* formatting

* remove unused import

* fixes

* updated example

* fix
This commit is contained in:
Abubakar Abid 2022-09-13 17:24:50 -07:00 committed by GitHub
parent b643ae77bf
commit 809230f25f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 54 additions and 35 deletions

View File

@ -14,7 +14,7 @@ demo = gr.Interface(fn=reverse_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)

View File

@ -35,7 +35,7 @@ import PIL
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 @@ from gradio.serializing import (
Serializable,
SimpleSerializable,
)
from gradio.utils import component_or_layout_class
set_documentation_group("component")
@ -1349,27 +1348,20 @@ class Image(Editable, Clearable, Changeable, Streamable, IOComponent, ImgSeriali
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 @@ class Video(Changeable, Clearable, Playable, IOComponent, FileSerializable):
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 @@ class Video(Changeable, Clearable, Playable, IOComponent, FileSerializable):
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()
@ -1680,7 +1675,6 @@ class Video(Changeable, Clearable, Playable, IOComponent, FileSerializable):
y = output_file_name
y = processing_utils.create_tmp_copy_of_file(y, dir=self.temp_dir)
return {"name": y.name, "data": None, "is_file": True}
def style(
@ -1709,7 +1703,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
@ -1927,23 +1921,25 @@ class Audio(Changeable, Clearable, Playable, Streamable, IOComponent, FileSerial
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
"""
if y is None:
return None
if isinstance(y, tuple):
if utils.validate_url(y):
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
suffix=".wav", dir=self.temp_dir, delete=False
)
processing_utils.audio_to_file(sample_rate, data, file.name)
y = file.name
else:
file = 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}
return {"name": file.name, "data": None, "is_file": True}
def stream(
self,
@ -1993,6 +1989,9 @@ class Audio(Changeable, Clearable, Playable, Streamable, IOComponent, FileSerial
rounded=rounded,
)
def as_example(self, input_data: str) -> str:
return Path(input_data).name
@document("change", "clear", "style")
class File(Changeable, Clearable, IOComponent, FileSerializable):
@ -2172,7 +2171,7 @@ class File(Changeable, Clearable, IOComponent, FileSerializable):
rounded=rounded,
)
def as_example(self, input_data):
def as_example(self, input_data: str) -> str:
return Path(input_data).name
@ -3635,7 +3634,7 @@ class Model3D(Changeable, Editable, Clearable, IOComponent, FileSerializable):
rounded=rounded,
)
def as_example(self, input_data):
def as_example(self, input_data: str) -> str:
return Path(input_data).name
@ -3974,7 +3973,7 @@ class StatusTracker(Component):
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()

View File

@ -14,7 +14,7 @@ import requests
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)
@ -90,6 +89,15 @@ def encode_plot_to_base64(plt):
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))
file_obj = tempfile.NamedTemporaryFile(delete=False, suffix=".png", dir=dir)

View File

@ -17,6 +17,7 @@ from typing import Any, List, Optional, Type
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.responses import RedirectResponse
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 @@ class App(FastAPI):
@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)

View File

@ -670,3 +670,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