Spaces:
Runtime error
Runtime error
""" | |
Module defining common utility functions and classes for the | |
web application of the Ultimate RVC project. | |
""" | |
from typing import Any, Concatenate | |
from collections.abc import Callable, Sequence | |
import gradio as gr | |
from ultimate_rvc.core.exceptions import NotProvidedError | |
from ultimate_rvc.core.generate.song_cover import ( | |
get_named_song_dirs, | |
get_song_cover_name, | |
) | |
from ultimate_rvc.core.manage.audio import get_saved_output_audio | |
from ultimate_rvc.web.typing_extra import ( | |
ComponentVisibilityKwArgs, | |
DropdownChoices, | |
DropdownValue, | |
TextBoxKwArgs, | |
UpdateDropdownKwArgs, | |
) | |
PROGRESS_BAR = gr.Progress() | |
def exception_harness[T, **P]( | |
fn: Callable[P, T], | |
info_msg: str | None = None, | |
) -> Callable[P, T]: | |
""" | |
Wrap a function in a harness that catches exceptions and re-raises | |
them as instances of `gradio.Error`. | |
Parameters | |
---------- | |
fn : Callable[P, T] | |
The function to wrap. | |
info_msg : str, optional | |
Message to display in an info-box pop-up after the function | |
executes successfully. | |
Returns | |
------- | |
Callable[P, T] | |
The wrapped function. | |
""" | |
def _wrapped_fn(*args: P.args, **kwargs: P.kwargs) -> T: | |
try: | |
res = fn(*args, **kwargs) | |
except gr.Error: | |
raise | |
except NotProvidedError as e: | |
msg = e.ui_msg or e | |
raise gr.Error(str(msg)) from None | |
except Exception as e: | |
raise gr.Error(str(e)) from e | |
else: | |
if info_msg: | |
gr.Info(info_msg, duration=0.5) | |
return res | |
return _wrapped_fn | |
def confirmation_harness[T, **P]( | |
fn: 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 | |
---------- | |
fn : Callable[P, T] | |
The function to wrap. | |
Returns | |
------- | |
Callable[Concatenate[bool, P], T] | |
The wrapped function. | |
""" | |
def _wrapped_fn(confirm: bool, *args: P.args, **kwargs: P.kwargs) -> T: | |
if confirm: | |
return exception_harness(fn)(*args, **kwargs) | |
err_msg = "Confirmation missing!" | |
raise gr.Error(err_msg) | |
return _wrapped_fn | |
def render_msg( | |
template: str, | |
*args: str, | |
display_info: bool = False, | |
**kwargs: str, | |
) -> str: | |
""" | |
Render a message template with the provided arguments. | |
Parameters | |
---------- | |
template : str | |
Message template to render. | |
args : str | |
Positional arguments to pass to the template. | |
display_info : bool, default=False | |
Whether to display the rendered message as an info message | |
in addition to returning it. | |
kwargs : str | |
Keyword arguments to pass to the template. | |
Returns | |
------- | |
str | |
Rendered message. | |
""" | |
msg = template.format(*args, **kwargs) | |
if display_info: | |
gr.Info(msg) | |
return msg | |
def confirm_box_js(msg: str) -> str: | |
""" | |
Generate a JavaScript code snippet which: | |
* defines an anonymous function that takes one named parameter and | |
zero or more unnamed parameters | |
* renders a confirmation box | |
* returns the choice selected by the user in that confirmation | |
box in addition to any unnamed parameters passed to the function. | |
Parameters | |
---------- | |
msg : str | |
Message to display in the confirmation box rendered by the | |
JavaScript code snippet. | |
Returns | |
------- | |
str | |
The JavaScript code snippet. | |
""" | |
return f"(x, ...args) => [confirm('{msg}'), ...args]" | |
def update_value(x: str) -> dict[str, Any]: | |
""" | |
Update the value of a component. | |
Parameters | |
---------- | |
x : str | |
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[**P]( | |
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 | |
dropdown choices. | |
kwargs : P.kwargs | |
Keyword arguments to pass to the function used to update | |
dropdown choices. | |
Returns | |
------- | |
gr.Dropdown | tuple[gr.Dropdown,...] | |
Updated dropdown component or components. | |
Raises | |
------ | |
ValueError | |
If not all provided indices are unique or if an index exceeds | |
or is equal to the number of dropdown components. | |
""" | |
if len(value_indices) != len(set(value_indices)): | |
err_msg = "Value indices must be unique." | |
raise ValueError(err_msg) | |
if value_indices and max(value_indices) >= num_components: | |
err_msg = ( | |
"Index of a dropdown component to update the value for exceeds the number" | |
" of dropdown components to update." | |
) | |
raise ValueError(err_msg) | |
updated_choices = fn(*args, **kwargs) | |
update_args_list: list[UpdateDropdownKwArgs] = [ | |
{"choices": updated_choices} for _ in range(num_components) | |
] | |
for index in value_indices: | |
update_args_list[index]["value"] = value | |
match update_args_list: | |
case [update_args]: | |
# NOTE This is a workaround as gradio does not support | |
# singleton tuples for components. | |
return gr.Dropdown(**update_args) | |
case _: | |
return tuple(gr.Dropdown(**update_args) for update_args in update_args_list) | |
def update_cached_songs( | |
num_components: int, | |
value: DropdownValue = None, | |
value_indices: Sequence[int] = [], | |
) -> gr.Dropdown | tuple[gr.Dropdown, ...]: | |
""" | |
Update the choices of one or more dropdown components to the set of | |
currently cached songs. | |
Optionally update 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 the 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, ...]: | |
""" | |
Update the choices of one or more dropdown components to the set of | |
currently saved output audio files. | |
Optionally update 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_saved_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[str, Any] | tuple[dict[str, Any], ...] | |
A single dictionary or a tuple of dictionaries that update the | |
visibility of the components. | |
Raises | |
------ | |
ValueError | |
If the visible index exceeds or is equal to the number of | |
components to set visibility for. | |
""" | |
if visible_index >= num_components: | |
err_msg = ( | |
"Visible index must be less than the number of components to set visibility" | |
" for." | |
) | |
raise ValueError(err_msg) | |
update_args_list: list[ComponentVisibilityKwArgs] = [ | |
{"visible": False, "value": None} for _ in range(num_components) | |
] | |
update_args_list[visible_index]["visible"] = True | |
match update_args_list: | |
case [update_args]: | |
return gr.update(**update_args) | |
case _: | |
return tuple(gr.update(**update_args) for update_args in update_args_list) | |
def update_song_cover_name( | |
effected_vocals_track: str | None = None, | |
song_dir: str | None = None, | |
model_name: str | None = None, | |
update_placeholder: bool = False, | |
) -> gr.Textbox: | |
""" | |
Update 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 name of | |
the song is inferred from that directory. If the name of a voice | |
model is not provided but the path of an existing song directory | |
and the path of an effected vocals track in that directory are | |
provided, then the voice model is inferred from the effected vocals | |
track. | |
Parameters | |
---------- | |
effected_vocals_track : str, optional | |
The path to an effected vocals track. | |
song_dir : str, optional | |
The path to a song directory. | |
model_name : str, optional | |
The name of a voice model. | |
update_placeholder : bool, default=False | |
Whether to update the placeholder text instead of the value of | |
the textbox component. | |
Returns | |
------- | |
gr.Textbox | |
Textbox component with updated value or placeholder text. | |
""" | |
update_args: TextBoxKwArgs = {} | |
update_key = "placeholder" if update_placeholder else "value" | |
if effected_vocals_track or song_dir or model_name: | |
song_cover_name = get_song_cover_name( | |
effected_vocals_track, | |
song_dir, | |
model_name, | |
) | |
update_args[update_key] = song_cover_name | |
else: | |
update_args[update_key] = None | |
return gr.Textbox(**update_args) | |