Spaces:
Runtime error
Runtime error
"""Common utility functions for the core of the Ultimate RVC project.""" | |
import hashlib | |
import json | |
import shutil | |
from collections.abc import Sequence | |
from pathlib import Path | |
import requests | |
from pydantic import AnyHttpUrl, TypeAdapter, ValidationError | |
import gradio as gr | |
from rich import print as rprint | |
from ultimate_rvc.common import AUDIO_DIR, RVC_MODELS_DIR | |
from ultimate_rvc.core.exceptions import Entity, HttpUrlError, NotFoundError | |
from ultimate_rvc.typing_extra import Json, StrPath | |
RVC_DOWNLOAD_URL = "https://huggingface.co/lj1995/VoiceConversionWebUI/resolve/main/" | |
INTERMEDIATE_AUDIO_BASE_DIR = AUDIO_DIR / "intermediate" | |
OUTPUT_AUDIO_DIR = AUDIO_DIR / "output" | |
FLAG_FILE = RVC_MODELS_DIR / ".initialized" | |
def display_progress( | |
message: str, | |
percentage: float | None = None, | |
progress_bar: gr.Progress | None = None, | |
) -> None: | |
""" | |
Display progress message and percentage in console and potentially | |
also Gradio progress bar. | |
Parameters | |
---------- | |
message : str | |
Message to display. | |
percentage : float, optional | |
Percentage to display. | |
progress_bar : gr.Progress, optional | |
The Gradio progress bar to update. | |
""" | |
rprint(message) | |
if progress_bar is not None: | |
progress_bar(percentage, desc=message) | |
def remove_suffix_after(text: str, occurrence: str) -> str: | |
""" | |
Remove suffix after the first occurrence of a substring in a string. | |
Parameters | |
---------- | |
text : str | |
The string to remove the suffix from. | |
occurrence : str | |
The substring to remove the suffix after. | |
Returns | |
------- | |
str | |
The string with the suffix removed. | |
""" | |
location = text.rfind(occurrence) | |
if location == -1: | |
return text | |
return text[: location + len(occurrence)] | |
def copy_files_to_new_dir(files: Sequence[StrPath], directory: StrPath) -> None: | |
""" | |
Copy files to a new directory. | |
Parameters | |
---------- | |
files : Sequence[StrPath] | |
Paths to the files to copy. | |
directory : StrPath | |
Path to the directory to copy the files to. | |
Raises | |
------ | |
NotFoundError | |
If a file does not exist. | |
""" | |
dir_path = Path(directory) | |
dir_path.mkdir(parents=True) | |
for file in files: | |
file_path = Path(file) | |
if not file_path.exists(): | |
raise NotFoundError(entity=Entity.FILE, location=file_path) | |
shutil.copyfile(file_path, dir_path / file_path.name) | |
def copy_file_safe(src: StrPath, dest: StrPath) -> Path: | |
""" | |
Copy a file to a new location, appending a number if a file with the | |
same name already exists. | |
Parameters | |
---------- | |
src : strPath | |
The source file path. | |
dest : strPath | |
The candidate destination file path. | |
Returns | |
------- | |
Path | |
The final destination file path. | |
""" | |
dest_path = Path(dest) | |
src_path = Path(src) | |
dest_dir = dest_path.parent | |
dest_dir.mkdir(parents=True, exist_ok=True) | |
dest_file = dest_path | |
counter = 1 | |
while dest_file.exists(): | |
dest_file = dest_dir / f"{dest_path.stem} ({counter}){src_path.suffix}" | |
counter += 1 | |
shutil.copyfile(src, dest_file) | |
return dest_file | |
def json_dumps(thing: Json) -> str: | |
""" | |
Dump a JSON-serializable object to a JSON string. | |
Parameters | |
---------- | |
thing : Json | |
The JSON-serializable object to dump. | |
Returns | |
------- | |
str | |
The JSON string representation of the object. | |
""" | |
return json.dumps(thing, ensure_ascii=False, indent=4) | |
def json_dump(thing: Json, file: StrPath) -> None: | |
""" | |
Dump a JSON-serializable object to a JSON file. | |
Parameters | |
---------- | |
thing : Json | |
The JSON-serializable object to dump. | |
file : StrPath | |
The path to the JSON file. | |
""" | |
with Path(file).open("w", encoding="utf-8") as fp: | |
json.dump(thing, fp, ensure_ascii=False, indent=4) | |
def json_load(file: StrPath, encoding: str = "utf-8") -> Json: | |
""" | |
Load a JSON-serializable object from a JSON file. | |
Parameters | |
---------- | |
file : StrPath | |
The path to the JSON file. | |
encoding : str, default='utf-8' | |
The encoding of the JSON file. | |
Returns | |
------- | |
Json | |
The JSON-serializable object loaded from the JSON file. | |
""" | |
with Path(file).open(encoding=encoding) as fp: | |
return json.load(fp) | |
def get_hash(thing: Json, size: int = 5) -> str: | |
""" | |
Get the hash of a JSON-serializable object. | |
Parameters | |
---------- | |
thing : Json | |
The JSON-serializable object to hash. | |
size : int, default=5 | |
The size of the hash in bytes. | |
Returns | |
------- | |
str | |
The hash of the JSON-serializable object. | |
""" | |
return hashlib.blake2b( | |
json_dumps(thing).encode("utf-8"), | |
digest_size=size, | |
).hexdigest() | |
# NOTE consider increasing size to 16 otherwise we might have problems | |
# with hash collisions | |
def get_file_hash(file: StrPath, size: int = 5) -> str: | |
""" | |
Get the hash of a file. | |
Parameters | |
---------- | |
file : StrPath | |
The path to the file. | |
size : int, default=5 | |
The size of the hash in bytes. | |
Returns | |
------- | |
str | |
The hash of the file. | |
""" | |
with Path(file).open("rb") as fp: | |
file_hash = hashlib.file_digest(fp, lambda: hashlib.blake2b(digest_size=size)) | |
return file_hash.hexdigest() | |
def validate_url(url: str) -> None: | |
""" | |
Validate a HTTP-based URL. | |
Parameters | |
---------- | |
url : str | |
The URL to validate. | |
Raises | |
------ | |
HttpUrlError | |
If the URL is invalid. | |
""" | |
try: | |
TypeAdapter(AnyHttpUrl).validate_python(url) | |
except ValidationError: | |
raise HttpUrlError(url) from None | |
def _download_base_model(url: str, name: str, directory: StrPath) -> None: | |
""" | |
Download a base model and save it to an existing directory. | |
Parameters | |
---------- | |
url : str | |
An URL pointing to a location where a base model is hosted. | |
name : str | |
The name of the base model to download. | |
directory : str | |
The path to the directory where the base model should be saved. | |
""" | |
dir_path = Path(directory) | |
with requests.get(f"{url}{name}", timeout=10) as r: | |
r.raise_for_status() | |
with (dir_path / name).open("wb") as f: | |
for chunk in r.iter_content(chunk_size=8192): | |
f.write(chunk) | |
def download_base_models() -> None: | |
"""Download base models.""" | |
RVC_MODELS_DIR.mkdir(parents=True, exist_ok=True) | |
base_model_names = ["hubert_base.pt", "rmvpe.pt"] | |
for base_model_name in base_model_names: | |
if not Path(RVC_MODELS_DIR / base_model_name).is_file(): | |
rprint(f"Downloading {base_model_name}...") | |
_download_base_model(RVC_DOWNLOAD_URL, base_model_name, RVC_MODELS_DIR) | |