Spaces:
Build error
Build error
""" | |
Module containing common utility functions and classes for the frontend. | |
""" | |
from typing import Any, Callable, Concatenate, Literal, Sequence | |
from typings.extra import ( | |
ComponentVisibilityKwArgs, | |
DropdownChoices, | |
DropdownValue, | |
F0Method, | |
P, | |
T, | |
TextBoxArgs, | |
UpdateDropdownArgs, | |
) | |
from dataclasses import dataclass | |
from functools import partial | |
import gradio as gr | |
from gradio.components.base import Component | |
from gradio.events import Dependency | |
from backend.generate_song_cover import get_named_song_dirs, get_song_cover_name | |
from backend.manage_audio import get_output_audio | |
PROGRESS_BAR = gr.Progress() | |
def exception_harness(fun: Callable[P, T]) -> Callable[P, T]: | |
""" | |
Wrap a function in a harness that catches exceptions | |
and re-raises them as instances of `gradio.Error`. | |
Parameters | |
---------- | |
fun : Callable[P, T] | |
The function to wrap. | |
Returns | |
------- | |
Callable[P, T] | |
The wrapped function. | |
""" | |
def _wrapped_fun(*args: P.args, **kwargs: P.kwargs) -> T: | |
try: | |
return fun(*args, **kwargs) | |
except Exception as e: | |
raise gr.Error(str(e)) | |
return _wrapped_fun | |
def confirmation_harness(fun: Callable[P, T]) -> Callable[Concatenate[bool, P], T]: | |
""" | |
Wrap a function in a harness that requires a confirmation | |
before executing and catches exceptions, | |
re-raising them as instances of `gradio.Error`. | |
Parameters | |
---------- | |
fun : Callable[P, T] | |
The function to wrap. | |
Returns | |
------- | |
Callable[Concatenate[bool, P], T] | |
The wrapped function. | |
""" | |
def _wrapped_fun(confirm: bool, *args: P.args, **kwargs: P.kwargs) -> T: | |
if confirm: | |
return exception_harness(fun)(*args, **kwargs) | |
else: | |
raise gr.Error("Confirmation missing!") | |
return _wrapped_fun | |
def confirm_box_js(msg: str) -> str: | |
""" | |
Generate JavaScript code for a confirmation box. | |
Parameters | |
---------- | |
msg : str | |
Message to display in the confirmation box. | |
Returns | |
------- | |
str | |
JavaScript code for the confirmation box. | |
""" | |
formatted_msg = f"'{msg}'" | |
return f"(x) => confirm({formatted_msg})" | |
def identity(x: T) -> T: | |
""" | |
Identity function. | |
Parameters | |
---------- | |
x : T | |
Value to return. | |
Returns | |
------- | |
T | |
The value. | |
""" | |
return x | |
def update_value(x: Any) -> dict[str, Any]: | |
""" | |
Update the value of a component. | |
Parameters | |
---------- | |
x : Any | |
New value for the component. | |
Returns | |
------- | |
dict[str, Any] | |
Dictionary which updates the value of the component. | |
""" | |
return gr.update(value=x) | |
def update_dropdowns( | |
fn: Callable[P, DropdownChoices], | |
num_components: int, | |
value: DropdownValue = None, | |
value_indices: Sequence[int] = [], | |
*args: P.args, | |
**kwargs: P.kwargs, | |
) -> gr.Dropdown | tuple[gr.Dropdown, ...]: | |
""" | |
Update the choices and optionally the value of one or more dropdown components. | |
Parameters | |
---------- | |
fn : Callable[P, DropdownChoices] | |
Function to get updated choices for the dropdown components. | |
num_components : int | |
Number of dropdown components to update. | |
value : DropdownValue, optional | |
New value for dropdown components. | |
value_indices : Sequence[int], default=[] | |
Indices of dropdown components to update the value for. | |
args : P.args | |
Positional arguments to pass to the function used to update choices. | |
kwargs : P.kwargs | |
Keyword arguments to pass to the function used to update choices. | |
Returns | |
------- | |
gr.Dropdown|tuple[gr.Dropdown,...] | |
Updated dropdown component or components. | |
Raises | |
------ | |
ValueError | |
If value indices are not unique or if an index exceeds the number of components. | |
""" | |
if len(value_indices) != len(set(value_indices)): | |
raise ValueError("Value indices must be unique.") | |
if value_indices and max(value_indices) >= num_components: | |
raise ValueError( | |
"Index of a component to update value for exceeds number of components." | |
) | |
updated_choices = fn(*args, **kwargs) | |
update_args: list[UpdateDropdownArgs] = [ | |
{"choices": updated_choices} for _ in range(num_components) | |
] | |
for index in value_indices: | |
update_args[index]["value"] = value | |
if len(update_args) == 1: | |
# NOTE This is a workaround as gradio does not support | |
# singleton tuples for components. | |
return gr.Dropdown(**update_args[0]) | |
return tuple(gr.Dropdown(**update_arg) for update_arg in update_args) | |
def update_cached_input_songs( | |
num_components: int, value: DropdownValue = None, value_indices: Sequence[int] = [] | |
) -> gr.Dropdown | tuple[gr.Dropdown, ...]: | |
""" | |
Updates the choices of one or more dropdown components | |
to the current set of cached input songs. | |
Optionally updates the default value of one or more of these components. | |
Parameters | |
---------- | |
num_components : int | |
Number of dropdown components to update. | |
value : DropdownValue, optional | |
New value for dropdown components. | |
value_indices : Sequence[int], default=[] | |
Indices of dropdown components to update the value for. | |
Returns | |
------- | |
gr.Dropdown|tuple[gr.Dropdown,...] | |
Updated dropdown component or components. | |
""" | |
return update_dropdowns(get_named_song_dirs, num_components, value, value_indices) | |
def update_output_audio( | |
num_components: int, value: DropdownValue = None, value_indices: Sequence[int] = [] | |
) -> gr.Dropdown | tuple[gr.Dropdown, ...]: | |
""" | |
Updates the choices of one or more dropdown | |
components to the current set of output audio files. | |
Optionally updates the default value of one or more of these components. | |
Parameters | |
---------- | |
num_components : int | |
Number of dropdown components to update. | |
value : DropdownValue, optional | |
New value for dropdown components. | |
value_indices : Sequence[int], default=[] | |
Indices of dropdown components to update the value for. | |
Returns | |
------- | |
gr.Dropdown|tuple[gr.Dropdown,...] | |
Updated dropdown component or components. | |
""" | |
return update_dropdowns(get_output_audio, num_components, value, value_indices) | |
def toggle_visible_component( | |
num_components: int, visible_index: int | |
) -> dict[str, Any] | tuple[dict[str, Any], ...]: | |
""" | |
Reveal a single component from a set of components. | |
All other components are hidden. | |
Parameters | |
---------- | |
num_components : int | |
Number of components to set visibility for. | |
visible_index : int | |
Index of the component to reveal. | |
Returns | |
------- | |
dict|tuple[dict,...] | |
A single dictionary or a tuple of dictionaries | |
that update the visibility of the components. | |
""" | |
if visible_index >= num_components: | |
raise ValueError("Visible index must be less than number of components.") | |
update_args: list[ComponentVisibilityKwArgs] = [ | |
{"visible": False, "value": None} for _ in range(num_components) | |
] | |
update_args[visible_index]["visible"] = True | |
if num_components == 1: | |
return gr.update(**update_args[0]) | |
return tuple(gr.update(**update_arg) for update_arg in update_args) | |
def _toggle_component_interactivity( | |
num_components: int, interactive: bool | |
) -> dict[str, Any] | tuple[dict[str, Any], ...]: | |
""" | |
Toggle interactivity of one or more components. | |
Parameters | |
---------- | |
num_components : int | |
Number of components to toggle interactivity for. | |
interactive : bool | |
Whether to make the components interactive or not. | |
Returns | |
------- | |
dict|tuple[dict,...] | |
A single dictionary or a tuple of dictionaries | |
that update the interactivity of the components. | |
""" | |
if num_components == 1: | |
return gr.update(interactive=interactive) | |
return tuple(gr.update(interactive=interactive) for _ in range(num_components)) | |
def show_hop_slider(pitch_detection_algo: F0Method) -> gr.Slider: | |
""" | |
Show or hide a slider component based on the given pitch extraction algorithm. | |
Parameters | |
---------- | |
pitch_detection_algo : F0Method | |
Pitch detection algorithm to determine visibility of the slider. | |
Returns | |
------- | |
gr.Slider | |
Slider component with visibility set accordingly. | |
""" | |
if pitch_detection_algo == "mangio-crepe": | |
return gr.Slider(visible=True) | |
else: | |
return gr.Slider(visible=False) | |
def update_song_cover_name( | |
mixed_vocals: str | None = None, | |
song_dir: str | None = None, | |
voice_model: str | None = None, | |
update_placeholder: bool = False, | |
) -> gr.Textbox: | |
""" | |
Updates a textbox component so that it displays a suitable name for a cover of | |
a given song. | |
If the path of an existing song directory is provided, the original song | |
name is inferred from that directory. If a voice model is not provided | |
but the path of an existing song directory and the path of a mixed vocals file | |
in that directory are provided, then the voice model is inferred from | |
the mixed vocals file. | |
Parameters | |
---------- | |
mixed_vocals : str, optional | |
The path to a mixed vocals file. | |
song_dir : str, optional | |
The path to a song directory. | |
voice_model : str, optional | |
The name of a voice model. | |
update_placeholder : bool, default=False | |
Whether to update the placeholder text of the textbox component. | |
Returns | |
------- | |
gr.Textbox | |
Updated textbox component. | |
""" | |
update_args: TextBoxArgs = {} | |
update_key = "placeholder" if update_placeholder else "value" | |
if mixed_vocals or song_dir or voice_model: | |
name = exception_harness(get_song_cover_name)( | |
mixed_vocals, song_dir, voice_model, progress_bar=PROGRESS_BAR | |
) | |
update_args[update_key] = name | |
else: | |
update_args[update_key] = None | |
return gr.Textbox(**update_args) | |
class EventArgs: | |
""" | |
Data class to store arguments for setting up event listeners. | |
Attributes | |
---------- | |
fn : Callable[..., Any] | |
Function to call when an event is triggered. | |
inputs : Sequence[Component], optional | |
Components to serve as inputs to the function. | |
outputs : Sequence[Component], optional | |
Components where to store the outputs of the function. | |
name : Literal["click", "success", "then"], default="success" | |
Name of the event to listen for. | |
show_progress : Literal["full", "minimal", "hidden"], default="full" | |
Level of progress bar to show when the event is triggered. | |
""" | |
fn: Callable[..., Any] | |
inputs: Sequence[Component] | None = None | |
outputs: Sequence[Component] | None = None | |
name: Literal["click", "success", "then"] = "success" | |
show_progress: Literal["full", "minimal", "hidden"] = "full" | |
def setup_consecutive_event_listeners( | |
component: Component, event_args_list: list[EventArgs] | |
) -> Dependency | Component: | |
""" | |
Set up a chain of event listeners on a component. | |
Parameters | |
---------- | |
component : Component | |
The component to set up event listeners on. | |
event_args_list : list[EventArgs] | |
List of event arguments to set up event listeners with. | |
Returns | |
------- | |
Dependency | Component | |
The last dependency in the chain of event listeners. | |
""" | |
if len(event_args_list) == 0: | |
raise ValueError("Event args list must not be empty.") | |
dependency = component | |
for event_args in event_args_list: | |
event_listener = getattr(dependency, event_args.name) | |
dependency = event_listener( | |
event_args.fn, | |
inputs=event_args.inputs, | |
outputs=event_args.outputs, | |
show_progress=event_args.show_progress, | |
) | |
return dependency | |
def setup_consecutive_event_listeners_with_toggled_interactivity( | |
component: Component, | |
event_args_list: list[EventArgs], | |
toggled_components: Sequence[Component], | |
) -> Dependency | Component: | |
""" | |
Set up a chain of event listeners on a component | |
with interactivity toggled for a set of other components. | |
While the chain of event listeners is being executed, | |
the other components are made non-interactive. | |
When the chain of event listeners is completed, | |
the other components are made interactive again. | |
Parameters | |
---------- | |
component : Component | |
The component to set up event listeners on. | |
event_args_list : list[EventArgs] | |
List of event arguments to set up event listeners with. | |
toggled_components : Sequence[Component] | |
Components to toggle interactivity for. | |
Returns | |
------- | |
Dependency | Component | |
The last dependency in the chain of event listeners. | |
""" | |
if len(event_args_list) == 0: | |
raise ValueError("Event args list must not be empty.") | |
disable_event_args = EventArgs( | |
partial(_toggle_component_interactivity, len(toggled_components), False), | |
outputs=toggled_components, | |
name="click", | |
show_progress="hidden", | |
) | |
enable_event_args = EventArgs( | |
partial(_toggle_component_interactivity, len(toggled_components), True), | |
outputs=toggled_components, | |
name="then", | |
show_progress="hidden", | |
) | |
event_args_list_augmented = ( | |
[disable_event_args] + event_args_list + [enable_event_args] | |
) | |
return setup_consecutive_event_listeners(component, event_args_list_augmented) | |