Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions client/python/gradio_client/documentation.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ def extract_instance_attr_doc(cls, attr):
("gradio_client.", "py-client"),
("gradio.utils", "helpers"),
("gradio.renderable", "renderable"),
("gradio.validators", "validators"),
]


Expand Down
3 changes: 2 additions & 1 deletion gradio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import gradio.processing_utils
import gradio.sketch
import gradio.templates
from gradio import components, layouts, mcp, themes
from gradio import components, layouts, mcp, themes, validators
from gradio.blocks import Blocks
from gradio.chat_interface import ChatInterface
from gradio.cli import deploy
Expand Down Expand Up @@ -277,4 +277,5 @@
"get_video",
"get_model3d",
"get_file",
"validators",
]
19 changes: 0 additions & 19 deletions gradio/components/audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
from gradio.components.base import Component, StreamingInput, StreamingOutput
from gradio.data_classes import FileData, FileDataDict, MediaStreamChunk
from gradio.events import Events
from gradio.exceptions import Error
from gradio.i18n import I18nData

if TYPE_CHECKING:
Expand Down Expand Up @@ -106,8 +105,6 @@ def __init__(
show_download_button: bool | None = None,
show_share_button: bool | None = None,
editable: bool = True,
min_length: int | None = None,
max_length: int | None = None,
waveform_options: WaveformOptions | dict | None = None,
loop: bool = False,
recording: bool = False,
Expand Down Expand Up @@ -138,8 +135,6 @@ def __init__(
show_download_button: If True, will show a download button in the corner of the component for saving audio. If False, icon does not appear. By default, it will be True for output components and False for input components.
show_share_button: If True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
editable: If True, allows users to manipulate the audio file if the component is interactive. Defaults to True.
min_length: The minimum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no minimum length.
max_length: The maximum length of audio (in seconds) that the user can pass into the prediction function. If None, there is no maximum length.
waveform_options: A dictionary of options for the waveform display. Options include: waveform_color (str), waveform_progress_color (str), skip_length (int), trim_region_color (str). Default is None, which uses the default values for these options. [See `gr.WaveformOptions` docs](#waveform-options).
loop: If True, the audio will loop when it reaches the end and continue playing from the beginning.
recording: If True, the audio component will be set to record audio from the microphone if the source is set to "microphone". Defaults to False.
Expand Down Expand Up @@ -193,8 +188,6 @@ def __init__(
self.waveform_options = WaveformOptions(**waveform_options)
else:
self.waveform_options = waveform_options
self.min_length = min_length
self.max_length = max_length
self.recording = recording
super().__init__(
label=label,
Expand Down Expand Up @@ -253,18 +246,6 @@ def preprocess(
if self.format is not None and original_suffix != f".{self.format}":
needs_conversion = True

if self.min_length is not None or self.max_length is not None:
sample_rate, data = processing_utils.audio_from_file(payload.path)
duration = len(data) / sample_rate
if self.min_length is not None and duration < self.min_length:
raise Error(
f"Audio is too short, and must be at least {self.min_length} seconds"
)
if self.max_length is not None and duration > self.max_length:
raise Error(
f"Audio is too long, and must be at most {self.max_length} seconds"
)

if self.type == "numpy":
return processing_utils.audio_from_file(payload.path)
elif self.type == "filepath":
Expand Down
20 changes: 0 additions & 20 deletions gradio/components/video.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from gradio_client import utils as client_utils
from gradio_client.documentation import document

import gradio as gr
from gradio import processing_utils, utils
from gradio.components.base import Component, StreamingOutput
from gradio.components.image_editor import WatermarkOptions, WebcamOptions
Expand Down Expand Up @@ -92,8 +91,6 @@ def __init__(
autoplay: bool = False,
show_share_button: bool | None = None,
show_download_button: bool | None = None,
min_length: int | None = None,
max_length: int | None = None,
loop: bool = False,
streaming: bool = False,
watermark: WatermarkOptions | None = None,
Expand Down Expand Up @@ -123,8 +120,6 @@ def __init__(
autoplay: whether to automatically play the video when the component is used as an output. Note: browsers will not autoplay video files if the user has not interacted with the page yet.
show_share_button: if True, will show a share icon in the corner of the component that allows user to share outputs to Hugging Face Spaces Discussions. If False, icon does not appear. If set to None (default behavior), then the icon appears if this Gradio app is launched on Spaces, but not otherwise.
show_download_button: if True, will show a download icon in the corner of the component that allows user to download the output. If False, icon does not appear. By default, it will be True for output components and False for input components.
min_length: the minimum length of video (in seconds) that the user can pass into the prediction function. If None, there is no minimum length.
max_length: the maximum length of video (in seconds) that the user can pass into the prediction function. If None, there is no maximum length.
loop: if True, the video will loop when it reaches the end and continue playing from the beginning.
streaming: when used set as an output, takes video chunks yielded from the backend and combines them into one streaming video output. Each chunk should be a video file with a .ts extension using an h.264 encoding. Mp4 files are also accepted but they will be converted to h.264 encoding.
watermark: A `gr.WatermarkOptions` instance that includes an image file and position to be used as a watermark on the video. The image is not scaled and is displayed on the provided position on the video. Valid formats for the image are: jpeg, png.
Expand Down Expand Up @@ -171,8 +166,6 @@ def __init__(
else show_share_button
)
self.show_download_button = show_download_button
self.min_length = min_length
self.max_length = max_length
self.streaming = streaming
super().__init__(
label=label,
Expand Down Expand Up @@ -208,19 +201,6 @@ def preprocess(self, payload: VideoData | None) -> str | None:
uploaded_format = file_name.suffix.replace(".", "")
needs_formatting = self.format is not None and uploaded_format != self.format
flip = self.sources == ["webcam"] and self.webcam_options.mirror

if self.min_length is not None or self.max_length is not None:
# With this if-clause, avoid unnecessary execution of `processing_utils.get_video_length`.
# This is necessary for the Wasm-mode, because it uses ffprobe, which is not available in the browser.
duration = processing_utils.get_video_length(file_name)
if self.min_length is not None and duration < self.min_length:
raise gr.Error(
f"Video is too short, and must be at least {self.min_length} seconds"
)
if self.max_length is not None and duration > self.max_length:
raise gr.Error(
f"Video is too long, and must be at most {self.max_length} seconds"
)
# TODO: Check other image extensions to see if they work.
valid_watermark_extensions = [".png", ".jpg", ".jpeg"]
if self.watermark.watermark is not None:
Expand Down
8 changes: 0 additions & 8 deletions gradio/templates.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,8 +410,6 @@ def __init__(
autoplay: bool = False,
show_share_button: bool | None = None,
show_download_button: bool | None = None,
min_length: int | None = None,
max_length: int | None = None,
loop: bool = False,
streaming: bool = False,
watermark: str | Path | None = None,
Expand Down Expand Up @@ -441,8 +439,6 @@ def __init__(
autoplay=autoplay,
show_share_button=show_share_button,
show_download_button=show_download_button,
min_length=min_length,
max_length=max_length,
loop=loop,
streaming=streaming,
watermark=watermark,
Expand Down Expand Up @@ -492,8 +488,6 @@ def __init__(
show_download_button: bool | None = None,
show_share_button: bool | None = None,
editable: bool = True,
min_length: int | None = None,
max_length: int | None = None,
waveform_options: WaveformOptions | dict | None = None,
loop: bool = False,
recording: bool = False,
Expand Down Expand Up @@ -524,8 +518,6 @@ def __init__(
show_download_button=show_download_button,
show_share_button=show_share_button,
editable=editable,
min_length=min_length,
max_length=max_length,
waveform_options=waveform_options,
loop=loop,
recording=recording,
Expand Down
71 changes: 71 additions & 0 deletions gradio/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
from typing import TYPE_CHECKING, Any

from gradio_client.documentation import document

if TYPE_CHECKING:
import numpy as np


@document()
def is_audio_correct_length(
audio: tuple[int, "np.ndarray"], min_length: float | None, max_length: float | None
) -> dict[str, Any]:
"""
Validates that the audio length is within the specified min and max length (in seconds).

Parameters:
audio: A tuple of (sample rate in Hz, audio data as numpy array).
min_length: Minimum length of audio in seconds. If None, no minimum length check is performed.
max_length: Maximum length of audio in seconds. If None, no maximum length check is performed.
Returns:
A dict corresponding to `gr.validate()` indicating whether the audio length is valid and an optional message.
"""
if min_length is not None or max_length is not None:
sample_rate, data = audio
duration = len(data) / sample_rate
if min_length is not None and duration < min_length:
return {
"__type__": "validate",
"is_valid": False,
"message": f"Audio is too short. It must be at least {min_length} seconds",
}
if max_length is not None and duration > max_length:
return {
"__type__": "validate",
"is_valid": False,
"message": f"Audio is too long. It must be at most {max_length} seconds",
}
return {"__type__": "validate", "is_valid": True}


@document()
def is_video_correct_length(
video: str, min_length: float | None, max_length: float | None
) -> dict[str, Any]:
"""
Validates that the video file length is within the specified min and max length (in seconds).

Parameters:
video: The path to the video file.
min_length: Minimum length of video in seconds. If None, no minimum length check is performed.
max_length: Maximum length of video in seconds. If None, no maximum length check is performed.
Returns:
A dict corresponding to `gr.validate()` indicating whether the audio length is valid and an optional message.
"""
from gradio.processing_utils import get_video_length

if min_length is not None or max_length is not None:
duration = get_video_length(video)
if min_length is not None and duration < min_length:
return {
"__type__": "validate",
"is_valid": False,
"message": f"Video is too short. It must be at least {min_length} seconds",
}
if max_length is not None and duration > max_length:
return {
"__type__": "validate",
"is_valid": False,
"message": f"Video is too long. It must be at most {max_length} seconds",
}
return {"__type__": "validate", "is_valid": True}
2 changes: 2 additions & 0 deletions js/_website/generate_jsons/src/docs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def add_component_shortcuts():
"Uses default values",
)
]
if not hasattr(component["class"], "__subclasses__"):
continue
for subcls in component["class"].__subclasses__():
if getattr(subcls, "is_template", False):
_, tags, _ = document_cls(subcls)
Expand Down
22 changes: 22 additions & 0 deletions js/_website/src/lib/templates/gradio/03_components/audio.svx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

let obj = get_object("audio");
let waveform_obj = get_object("waveformoptions");
let validator_obj = get_object("is_audio_correct_length")
</script>

<!--- Title -->
Expand Down Expand Up @@ -103,6 +104,27 @@ gradio.WaveformOptions(···)
#### Initialization
<ParamTable parameters={waveform_obj.parameters} anchor_links={waveform_obj.name}/>

<!--- Title -->
### is_audio_correct_length

Validates that the audio length is within the specified min and max length (in seconds).
You can use this to construct a validator that will check if the user-provided audio is either too short or too long.

<!--- Usage -->
```python
import gradio as gr
demo = gr.Interface(
lambda x: x,
inputs="audio",
outputs="audio",
validator=lambda audio: gr.validators.is_audio_correct_length(audio, min_length=1, max_length=5)
)
demo.launch()
```

<!--- Initialization -->
#### Initialization
<ParamTable parameters={validator_obj.parameters} anchor_links={validator_obj.name}/>
</div>


Expand Down
23 changes: 23 additions & 0 deletions js/_website/src/lib/templates/gradio/03_components/video.svx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

let obj = get_object("video");
let webcam_options_obj = get_object("webcamoptions")
let validator_obj = get_object("is_video_correct_length")
</script>

<!--- Title -->
Expand Down Expand Up @@ -103,6 +104,28 @@ gradio.WebcamOptions(···)
#### Initialization
<ParamTable parameters={webcam_options_obj.parameters} anchor_links={webcam_options_obj.name}/>

<!--- Title -->
### is_video_correct_length

Validates that the audio length is within the specified min and max length (in seconds).
You can use this to construct a validator that will check if the user-provided audio is either too short or too long.

<!--- Usage -->
```python
import gradio as gr
demo = gr.Interface(
lambda x: x,
inputs="video",
outputs="video",
validator=lambda video: gr.validators.is_video_correct_length(video, min_length=1, max_length=5)
)
demo.launch()
```

<!--- Initialization -->
#### Initialization
<ParamTable parameters={validator_obj.parameters} anchor_links={validator_obj.name}/>

</div>

{#if obj.guides && obj.guides.length > 0}
Expand Down
16 changes: 12 additions & 4 deletions test/components/test_audio.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ async def test_component_functions(self, gradio_temp_dir):
"format": None,
"recording": False,
"streamable": False,
"max_length": None,
"min_length": None,
"waveform_options": {
"sample_rate": 44100,
"show_recording_waveform": True,
Expand Down Expand Up @@ -103,8 +101,6 @@ async def test_component_functions(self, gradio_temp_dir):
"streaming": False,
"show_label": True,
"label": None,
"max_length": None,
"min_length": None,
"container": True,
"editable": True,
"min_width": 160,
Expand Down Expand Up @@ -210,3 +206,15 @@ async def test_combine_stream_audio(self, gradio_temp_dir):
bytes_output, desired_output_format=None
)
assert str(output.path).endswith("mp3")


def test_duration_validator():
assert gr.validators.is_audio_correct_length((8000, np.zeros((8000,))), 1, 2)[
"is_valid"
]
assert not gr.validators.is_audio_correct_length((8000, np.zeros((8000,))), 2, 3)[
"is_valid"
]
assert not gr.validators.is_audio_correct_length(
(8000, np.zeros((8000,))), 0.25, 0.75
)["is_valid"]
30 changes: 28 additions & 2 deletions test/components/test_video.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ async def test_component_functions(self):
"webcam_options": {"constraints": None, "mirror": True},
"include_audio": True,
"format": None,
"min_length": None,
"max_length": None,
"_selectable": False,
"key": None,
"preserved_by_key": ["value"],
Expand Down Expand Up @@ -254,3 +252,31 @@ def test_video_preprocessing_flips_video_for_webcam(self, mock_ffmpeg):
assert "flip" not in Path(list(output_params.keys())[0]).name
assert ".avi" in list(output_params.keys())[0]
assert ".avi" in output_file


def test_is_video_correct_length():
test_file_dir = Path(__file__).parent.parent / "test_files"
video_path = str(test_file_dir / "muted_video_sample.mp4")
assert (
gr.validators.is_video_correct_length(video_path, None, None)["is_valid"]
is True
)
assert (
gr.validators.is_video_correct_length(video_path, 1, None)["is_valid"] is True
)
assert (
gr.validators.is_video_correct_length(video_path, 1000, None)["is_valid"]
is False
)
assert (
gr.validators.is_video_correct_length(video_path, None, 1000)["is_valid"]
is True
)
assert (
gr.validators.is_video_correct_length(video_path, None, 1)["is_valid"] is False
)
assert (
gr.validators.is_video_correct_length(video_path, 1, 1000)["is_valid"] is True
)
assert gr.validators.is_video_correct_length(video_path, 1, 5)["is_valid"] is True
assert gr.validators.is_video_correct_length(video_path, 1, 2)["is_valid"] is False
Loading