Fix live interfaces for audio/image streaming (#9883)

* Fix live interfaces for audio/image streaming

* add changeset

* code

* code

---------

Co-authored-by: gradio-pr-bot <gradio-pr-bot@users.noreply.github.com>
Co-authored-by: Abubakar Abid <abubakar@huggingface.co>
This commit is contained in:
Freddy Boulton 2024-11-04 16:20:48 -05:00 committed by GitHub
parent dcfa7ad3e8
commit e10bbd236f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 59 additions and 4 deletions

View File

@ -0,0 +1,6 @@
---
"@gradio/audio": patch
"gradio": patch
---
fix:Fix live interfaces for audio/image streaming

View File

@ -643,6 +643,17 @@ class EventListener(str):
event_trigger.event_name = _event_name # type: ignore
event_trigger.has_trigger = _has_trigger # type: ignore
event_trigger.callback = _callback # type: ignore
event_trigger.connection = _connection # type: ignore
event_specific_args = (
[
d["name"]
for d in _event_specific_args
if d.get("component_prop", "true") != "false"
]
if _event_specific_args
else None
)
event_trigger.event_specific_args = event_specific_args # type: ignore
return event_trigger
@ -675,6 +686,8 @@ def on(
concurrency_limit: int | None | Literal["default"] = "default",
concurrency_id: str | None = None,
show_api: bool = True,
time_limit: int | None = None,
stream_every: float = 0.5,
) -> Dependency:
"""
Sets up an event listener that triggers a function when the specified event(s) occur. This is especially
@ -700,6 +713,8 @@ def on(
concurrency_limit: If set, this is the maximum number of this event that can be running simultaneously. Can be set to None to mean no concurrency_limit (any number of this event can be running simultaneously). Set to "default" to use the default concurrency limit (defined by the `default_concurrency_limit` parameter in `Blocks.queue()`, which itself is 1 by default).
concurrency_id: If set, this is the id of the concurrency group. Events with the same concurrency_id will be limited by the lowest set concurrency_limit.
show_api: whether to show this event in the "view API" page of the Gradio app, or in the ".view_api()" method of the Gradio clients. Unlike setting api_name to False, setting show_api to False will still allow downstream apps as well as the Clients to use this event. If fn is None, show_api will automatically be set to False.
time_limit: The time limit for the function to run. Parameter only used for the `.stream()` event.
stream_every: The latency (in seconds) at which stream chunks are sent to the backend. Defaults to 0.5 seconds. Parameter only used for the `.stream()` event.
Example:
import gradio as gr
with gr.Blocks() as demo:
@ -746,6 +761,8 @@ def on(
concurrency_id=concurrency_id,
show_api=show_api,
trigger_mode=trigger_mode,
time_limit=time_limit,
stream_every=stream_every,
)
@wraps(func)
@ -793,6 +810,16 @@ def on(
max_batch_size=max_batch_size,
show_api=show_api,
trigger_mode=trigger_mode,
connection="stream"
if any(t.connection == "stream" for t in (triggers_typed or []))
else "sse",
event_specific_args=[
a
for t in (triggers_typed or [])
for a in cast(list[str], t.event_specific_args or [])
],
time_limit=time_limit,
stream_every=stream_every,
)
set_cancel_events(methods, cancels)
return Dependency(None, dep.get_config(), dep_index, fn)

View File

@ -142,6 +142,8 @@ class Interface(Blocks):
| Literal["auto"]
| Literal["manual"]
| None = None,
time_limit: int | None = 30,
stream_every: float = 0.5,
**kwargs,
):
"""
@ -182,6 +184,8 @@ class Interface(Blocks):
show_progress: how to show the progress animation while event is running: "full" shows a spinner which covers the output component area as well as a runtime display in the upper right corner, "minimal" only shows the runtime display, "hidden" shows no progress animation at all
example_labels: a list of labels for each example. If provided, the length of this list should be the same as the number of examples, and these labels will be used in the UI instead of rendering the example values.
fill_width: whether to horizontally expand to fill container fully. If False, centers and constrains app to a maximum width.
time_limit: The time limit for the stream to run. Default is 30 seconds. Parameter only used for streaming images or audio if the interface is live and the input components are set to "streaming=True".
stream_every: The latency (in seconds) at which stream chunks are sent to the backend. Defaults to 0.5 seconds. Parameter only used for streaming images or audio if the interface is live and the input components are set to "streaming=True".
"""
super().__init__(
analytics_enabled=analytics_enabled,
@ -197,6 +201,8 @@ class Interface(Blocks):
fill_width=fill_width,
**kwargs,
)
self.time_limit = time_limit
self.stream_every = stream_every
self.api_name: str | Literal[False] | None = api_name
self.interface_type = InterfaceTypes.STANDARD
if (inputs is None or inputs == []) and (outputs is None or outputs == []):
@ -702,7 +708,9 @@ class Interface(Blocks):
preprocess=not (self.api_mode),
postprocess=not (self.api_mode),
show_progress="hidden" if streaming_event else self.show_progress,
trigger_mode="always_last",
trigger_mode="always_last" if not streaming_event else "multiple",
time_limit=self.time_limit,
stream_every=self.stream_every,
)
else:
if _submit_btn is None:
@ -716,6 +724,12 @@ class Interface(Blocks):
if component.has_event(Events.submit)
]
for component in self.input_components:
if getattr(component, "streaming", None):
warnings.warn(
"Streaming components are only supported in live interfaces."
)
if _stop_btn:
extra_output = [_submit_btn, _stop_btn]

View File

@ -666,7 +666,9 @@ class Queue:
else ServerMessage.process_streaming,
output=old_response,
success=old_response is not None,
time_limit=cast(int, fn.time_limit) - first_iteration
time_limit=None
if not fn.time_limit
else cast(int, fn.time_limit) - first_iteration
if event.streaming
else None,
),
@ -679,7 +681,11 @@ class Queue:
if awake_events[0].streaming:
awake_events, closed_events = await Queue.wait_for_batch(
awake_events,
[cast(float, fn.time_limit) - first_iteration]
# We need to wait for all of the events to have the latest input data
# the max time is the time limit of the function or 30 seconds (arbitrary) but should
# never really take that long to make a request from the client to the server unless
# the client disconnected.
[cast(float, fn.time_limit or 30) - first_iteration]
* len(awake_events),
)
for closed_event in closed_events:

View File

@ -57,6 +57,8 @@ To make this a *streaming* demo, we need to make these changes:
2. Set `live=True` in the `Interface`
3. Add a `state` to the interface to store the recorded audio of a user
Tip: You can also set `time_limit` and `stream_every` parameters in the interface. The `time_limit` caps the amount of time each user's stream can take. The default is 30 seconds so users won't be able to stream audio for more than 30 seconds. The `stream_every` parameter controls how frequently data is sent to your function. By default it is 0.5 seconds.
Take a look below.
$code_stream_asr

View File

@ -201,7 +201,7 @@
dispatch("start_recording");
if (!inited) await prepare_audio();
header = undefined;
if (streaming) {
if (streaming && recorder.state != "recording") {
recorder.start(stream_every * 1000);
}
}