mirror of
https://github.com/gradio-app/gradio.git
synced 2025-02-23 11:39:17 +08:00
video support
This commit is contained in:
parent
b1045a9e28
commit
9adf4df8fd
13
demo/video_flip.py
Normal file
13
demo/video_flip.py
Normal 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()
|
@ -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
|
||||
|
@ -10,4 +10,5 @@ IPython
|
||||
scikit-image
|
||||
analytics-python
|
||||
pandas
|
||||
ffmpy
|
||||
markdown2
|
||||
|
@ -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'.")
|
||||
|
||||
|
@ -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):
|
||||
'''
|
||||
|
@ -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
|
||||
##################
|
||||
|
@ -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,
|
||||
|
108
gradio/static/js/interfaces/input/video.js
Normal file
108
gradio/static/js/interfaces/input/video.js
Normal 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";
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
27
gradio/static/js/interfaces/output/video.js
Normal file
27
gradio/static/js/interfaces/output/video.js
Normal 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>"
|
||||
},
|
||||
|
||||
}
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user