Spaces:
Paused
Paused
| """ | |
| 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) | |