video support

This commit is contained in:
Ali Abid 2021-01-27 16:16:35 -08:00
parent b1045a9e28
commit 9adf4df8fd
11 changed files with 239 additions and 29 deletions

13
demo/video_flip.py Normal file
View File

@ -0,0 +1,13 @@
# Demo: (Video) -> (Image)
import gradio as gr
def video_flip(video):
return video[:-4]
iface = gr.Interface(video_flip, "video", "video")
if __name__ == "__main__":
iface.launch()

View File

@ -87,6 +87,7 @@ gradio/static/js/interfaces/input/radio.js
gradio/static/js/interfaces/input/sketchpad.js
gradio/static/js/interfaces/input/slider.js
gradio/static/js/interfaces/input/textbox.js
gradio/static/js/interfaces/input/video.js
gradio/static/js/interfaces/input/webcam.js
gradio/static/js/interfaces/output/audio.js
gradio/static/js/interfaces/output/dataframe.js
@ -98,6 +99,7 @@ gradio/static/js/interfaces/output/json.js
gradio/static/js/interfaces/output/key_values.js
gradio/static/js/interfaces/output/label.js
gradio/static/js/interfaces/output/textbox.js
gradio/static/js/interfaces/output/video.js
gradio/static/js/vendor/Chart.min.js
gradio/static/js/vendor/FileSaver.min.js
gradio/static/js/vendor/black-theme.js

View File

@ -10,4 +10,5 @@ IPython
scikit-image
analytics-python
pandas
ffmpy
markdown2

View File

@ -17,6 +17,7 @@ from skimage.segmentation import slic
import scipy.io.wavfile
from gradio import processing_utils, test_data
import pandas as pd
from ffmpy import FFmpeg
import math
import tempfile
from pandas.api.types import is_bool_dtype, is_numeric_dtype, is_string_dtype
@ -590,7 +591,7 @@ class Dropdown(InputComponent):
class Image(InputComponent):
"""
Component creates an image upload box with editing capabilities.
Input type: Union[numpy.array, PIL.Image, str]
Input type: Union[numpy.array, PIL.Image, file-object]
"""
def __init__(self, shape=None, image_mode='RGB', invert_colors=False, source="upload", tool="editor", labeled_segments=False, type="numpy", label=None):
@ -654,16 +655,6 @@ class Image(InputComponent):
def preprocess_example(self, x):
return processing_utils.encode_file_to_base64(x)
def rebuild(self, dir, data):
"""
Default rebuild method to decode a base64 image
"""
im = processing_utils.decode_base64_to_image(data)
timestamp = datetime.datetime.now()
filename = f'input_{timestamp.strftime("%Y-%m-%d-%H-%M-%S")}.png'
im.save(f'{dir}/{filename}', 'PNG')
return filename
def interpret(self, segments=16):
"""
Calculates interpretation score of image subsections by splitting the image into subsections, then using a "leave one out" method to calculate the score of each subsection by whiting out the subsection and measuring the delta of the output value.
@ -722,10 +713,52 @@ class Image(InputComponent):
im = processing_utils.resize_and_crop(im, (shape[0], shape[1]))
return np.asarray(im).flatten()
class Video(InputComponent):
"""
Component creates a video file upload that is converted to a file path.
Input type: filepath
"""
def __init__(self, type="avi", label=None):
'''
Parameters:
type (str): Type of video format to be returned by component, such as 'avi' or 'mp4'. If set to None, video will keep uploaded format.
label (str): component name in interface.
'''
self.type = type
super().__init__(label)
@classmethod
def get_shortcut_implementations(cls):
return {
"video": {},
}
def get_template_context(self):
return {
**super().get_template_context()
}
def preprocess(self, x):
file = processing_utils.decode_base64_to_file(x)
file_name = file.name
uploaded_format = file_name.split(".")[-1].lower()
if self.type is not None and uploaded_format != self.type:
ff = FFmpeg(
inputs={file_name: None},
outputs={file_name + "." + self.type: None}
)
file_name += "." + self.type
ff.run()
return file_name
def preprocess_example(self, x):
return processing_utils.encode_file_to_base64(x)
class Audio(InputComponent):
"""
Component accepts audio input files.
Input type: Union[Tuple[int, numpy.array], str, numpy.array]
Input type: Union[Tuple[int, numpy.array], file-object, numpy.array]
"""
def __init__(self, source="upload", type="numpy", label=None):
@ -825,7 +858,7 @@ class Audio(InputComponent):
class File(InputComponent):
"""
Component accepts generic file uploads.
Input type: Union[str, bytes]
Input type: Union[file-object, bytes]
"""
def __init__(self, type="file", label=None):
@ -855,7 +888,7 @@ class File(InputComponent):
if is_local_example:
with open(name, "rb") as file_data:
return file_data.read()
return processing_utils.decode_base64_to_binary(data)
return processing_utils.decode_base64_to_binary(data)[0]
else:
raise ValueError("Unknown type: " + str(self.type) + ". Please choose from: 'file', 'bytes'.")

View File

@ -186,15 +186,28 @@ class Image(OutputComponent):
raise ValueError("Unknown type: " + dtype + ". Please choose from: 'numpy', 'pil', 'file', 'plot'.")
return out_y, coordinates
def rebuild(self, dir, data):
"""
Default rebuild method to decode a base64 image
"""
im = processing_utils.decode_base64_to_image(data)
timestamp = datetime.datetime.now()
filename = 'output_{}_{}.png'.format(self.label, timestamp.strftime("%Y-%m-%d-%H-%M-%S"))
im.save('{}/{}'.format(dir, filename), 'PNG')
return filename
class Video(OutputComponent):
'''
Used for video output.
Output type: filepath
'''
def __init__(self, label=None):
'''
Parameters:
label (str): component name in interface.
'''
super().__init__(label)
@classmethod
def get_shortcut_implementations(cls):
return {
"video": {},
}
def postprocess(self, y):
return processing_utils.encode_file_to_base64(y, type="video")
class KeyValues(OutputComponent):
'''

View File

@ -68,17 +68,25 @@ def resize_and_crop(img, size, crop_type='center'):
##################
def decode_base64_to_binary(encoding):
inp = encoding.split(';')[1].split(',')[1]
return base64.b64decode(inp)
header, data = encoding.split(",")
header = header[5:]
if ";base64" in header:
header = header[0:header.index(";base64")]
extension = None
if "/" in header:
extension = header[header.index("/") + 1:]
return base64.b64decode(data), extension
def decode_base64_to_file(encoding):
file_obj = tempfile.NamedTemporaryFile(delete=False)
file_obj.write(decode_base64_to_binary(encoding))
data, extension = decode_base64_to_binary(encoding)
if extension is None:
file_obj = tempfile.NamedTemporaryFile(delete=False)
else:
file_obj = tempfile.NamedTemporaryFile(delete=False, suffix="."+extension)
file_obj.write(data)
file_obj.flush()
return file_obj
##################
# AUDIO FILES
##################

View File

@ -71,6 +71,7 @@ function gradio(config, fn, target, example_file_path) {
let input_to_object_map = {
"csv" : {},
"image" : image_input,
"video" : video_input,
"sketchpad" : sketchpad_input,
"textbox" : textbox_input,
"number" : number_input,
@ -88,6 +89,7 @@ function gradio(config, fn, target, example_file_path) {
let output_to_object_map = {
"csv" : {},
"image" : image_output,
"video" : video_output,
"label" : label_output,
"keyvalues" : key_values,
"textbox" : textbox_output,

View File

@ -0,0 +1,108 @@
const video_input = {
html: `
<div class="interface_box">
<div class="upload_zone drop_zone hide">
<div class="input_caption">Drop Video Here<br>- or -<br>Click to Upload</div>
</div>
<div class="webcam upload_zone hide">
<div class="webcam_box">
</div>
<span>Click to Record!</span>
</div>
<div class="image_display hide">
<div class="view_holders">
<div class="image_preview_holder">
<video class="image_preview" controls /></video>
</div>
</div>
</div>
<input class="hidden_upload" type="file" accept="video/mp4,video/x-m4v,video/*" />
</div>
`
,
init: function(opts) {
var io = this;
io.target.find(".drop_zone").removeClass("hide");
this.target.find(".drop_zone").click(function (e) {
io.target.find(".hidden_upload").click();
});
this.target.on('drag dragstart dragend dragover dragenter dragleave drop',
".drop_zone", function(e) {
e.preventDefault();
e.stopPropagation();
})
this.target.on('drop', '.drop_zone', function(e) {
files = e.originalEvent.dataTransfer.files;
io.load_preview_from_files(files)
});
this.target.find('.hidden_upload').on('change', function (e) {
if (this.files) {
io.load_preview_from_files(this.files);
}
})
},
submit: function() {
var io = this;
if (this.state == "VIDEO_LOADED") {
io.io_master.input(io.id, this.video_data);
} else {
io.io_master.no_input();
}
},
clear: function() {
this.target.find(".upload_zone").show();
this.target.find(".image_preview").attr('src', '');
this.target.find(".image_display").addClass("hide");
this.target.find(".hidden_upload").prop("value", "")
this.state = "NO_VIDEO";
this.video_data = null;
},
state: "NO_VIDEO",
video_data: null,
set_video_data: function(video_data) {
let io = this;
io.video_data = video_data
io.target.find(".image_preview").attr('src', video_data);
},
load_preview_from_files: function(files) {
if (!files.length || !window.FileReader || !/^video/.test(files[0].type)) {
return
}
var ReaderObj = new FileReader()
ReaderObj.readAsDataURL(files[0])
ReaderObj.io = this;
this.state = "VIDEO_LOADING"
ReaderObj.onloadend = function() {
let io = this.io;
io.target.find(".upload_zone").hide();
io.target.find(".image_display").removeClass("hide");
io.set_video_data(this.result);
io.state = "VIDEO_LOADED"
}
},
load_example_preview: function(data) {
return "<video src='"+this.io_master.example_file_path+data+"' height=100>"
},
load_example: function(example_data) {
example_data = this.io_master.example_file_path + example_data;
let io = this;
toDataURL(example_data, function(data) {
if (io.source == "canvas") {
io.clear();
let ctx = this.context;
var img = new Image;
let dimension = io.target.find(".canvas_holder canvas").width();
img.onload = function(){
ctx.clearRect(0,0,dimension,dimension);
ctx.drawImage(img,0,0,dimension,dimension);
};
img.src = data;
} else {
io.target.find(".upload_zone").hide();
io.target.find(".image_display").removeClass("hide");
io.set_video_data(data, /*update_editor=*/true);
io.state = "VIDEO_LOADED";
}
})
}
}

View File

@ -0,0 +1,27 @@
const video_output = {
html: `
<div class="view_holder_parent">
<div class="view_holders">
<div class="output_image_holder hide">
<video controls class="output_image">
</div>
</div>
</div>
`,
init: function(opts) {},
output: function(data) {
this.target.find(".view_holder_parent").addClass("interface_box");
this.target.find(".output_image_holder").removeClass("hide");
this.target.find(".output_image").attr('src', data);
console.log(data);
},
clear: function() {
this.target.find(".view_holder_parent").removeClass("interface_box");
this.target.find(".output_image_holder").addClass("hide");
this.target.find(".output_image").attr('src', "")
},
load_example_preview: function(data) {
return "<video controls src='"+data[0]+"' height=100>"
},
}

View File

@ -99,6 +99,7 @@
<script src="{{ url_for('static', filename='js/utils.js') }}"></script>
<script src="{{ url_for('static', filename='js/all_io.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/image.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/video.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/sketchpad.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/textbox.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/number.js') }}"></script>
@ -113,6 +114,7 @@
<script src="{{ url_for('static', filename='js/interfaces/input/webcam.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/input/microphone.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/output/image.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/output/video.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/output/label.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/output/key_values.js') }}"></script>
<script src="{{ url_for('static', filename='js/interfaces/output/textbox.js') }}"></script>

View File

@ -26,6 +26,7 @@ setup(
'scikit-image',
'analytics-python',
'pandas',
'ffmpy',
'markdown2'
],
)