import gradio as gr import requests import random import os import zipfile import librosa import time from infer_rvc_python import BaseLoader from pydub import AudioSegment from tts_voice import tts_order_voice import edge_tts import tempfile from audio_separator.separator import Separator import model_handler import logging import aiohttp import asyncio # Configure logging logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") logger = logging.getLogger(__name__) # Constants TEMP_DIR = "temp" MODEL_PREFIX = "model" UVR_5_MODELS = [ {"model_name": "BS-Roformer-Viperx-1297", "checkpoint": "model_bs_roformer_ep_317_sdr_12.9755.ckpt"}, {"model_name": "MDX23C-InstVoc HQ 2", "checkpoint": "MDX23C-8KFFT-InstVoc_HQ_2.ckpt"}, {"model_name": "Kim Vocal 2", "checkpoint": "Kim_Vocal_2.onnx"}, {"model_name": "5_HP-Karaoke", "checkpoint": "5_HP-Karaoke-UVR.pth"}, {"model_name": "UVR-DeNoise by FoxJoy", "checkpoint": "UVR-DeNoise.pth"}, {"model_name": "UVR-DeEcho-DeReverb by FoxJoy", "checkpoint": "UVR-DeEcho-DeReverb.pth"}, ] MODELS = [ {"model": "model.pth", "index": "model.index", "model_name": "Test Model"}, ] BAD_WORDS = ['puttana', 'whore', 'badword3', 'badword4'] MAX_FILE_SIZE = 500_000_000 # 500 MB os.makedirs(TEMP_DIR, exist_ok=True) try: import spaces spaces_status = True except ImportError: spaces_status = False logger.warning("Spaces module not found; running in CPU mode") separator = Separator() converter = BaseLoader(only_cpu=not spaces_status, hubert_path=None, rmvpe_path=None) class BadWordError(Exception): pass async def text_to_speech_edge(text, language_code): if not text.strip(): raise ValueError("Text input cannot be empty") voice = tts_order_voice.get(language_code, tts_order_voice[list(tts_order_voice.keys())[0]]) communicate = edge_tts.Communicate(text, voice) with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmp_file: tmp_path = tmp_file.name await communicate.save(tmp_path) return tmp_path async def download_from_url(url, name, progress=gr.Progress()): if not url.startswith("https://huggingface.co"): raise ValueError("URL must be from Hugging Face") if not name.strip(): raise ValueError("Model name cannot be empty") if any(bad_word in url.lower() or bad_word in name.lower() for bad_word in BAD_WORDS): raise BadWordError("Input contains restricted words") filename = os.path.join(TEMP_DIR, f"{MODEL_PREFIX}{random.randint(1, 1000)}.zip") async with aiohttp.ClientSession() as session: async with session.get(url.replace("/blob/", "/resolve/")) as response: if response.status != 200: raise ValueError("Failed to download file") total = int(response.headers.get('content-length', 0)) if total > MAX_FILE_SIZE: raise ValueError(f"File size exceeds {MAX_FILE_SIZE / 1_000_000} MB limit") current = 0 with open(filename, "wb") as f: async for data in response.content.iter_chunked(4096): f.write(data) current += len(data) progress(current / total, desc="Downloading model") try: with zipfile.ZipFile(filename, 'r') as zip_ref: zip_ref.extractall(os.path.join(TEMP_DIR, os.path.basename(filename).split(".")[0])) except Exception as e: logger.error(f"Failed to unzip file: {e}") raise ValueError("Failed to unzip file") unzipped_dir = os.path.join(TEMP_DIR, os.path.basename(filename).split(".")[0]) pth_files = [os.path.join(root, file) for root, _, files in os.walk(unzipped_dir) for file in files if file.endswith(".pth")] index_files = [os.path.join(root, file) for root, _, files in os.walk(unzipped_dir) for file in files if file.endswith(".index")] if not pth_files or not index_files: raise ValueError("No .pth or .index files found in the zip") pth_file = pth_files[0] index_file = index_files[0] name = name or os.path.basename(pth_file).split(".")[0] MODELS.append({"model": pth_file, "index": index_file, "model_name": name}) return [f"Downloaded as {name}", pth_file, index_file] def inf_handler(audio, model_name): model_found = False for model_info in UVR_5_MODELS: if model_info["model_name"] == model_name: separator.load_model(model_info["checkpoint"]) model_found = True break if not model_found: separator.load_model() output_files = separator.separate(audio) return output_files[0], output_files[1] def run(model, audio_files, pitch_alg, pitch_lvl, index_inf, r_m_f, e_r, c_b_p): if not audio_files: raise ValueError("Please upload an audio file") if isinstance(audio_files, str): audio_files = [audio_files] random_tag = f"USER_{random.randint(10000000, 99999999)}" file_m = model file_index = None for m in MODELS: if m["model_name"] == file_m: file_m = m["model"] file_index = m["index"] break if not file_m.endswith(".pth"): raise ValueError("Model file must be a .pth file") logger.info(f"Running inference with model: {file_m}, tag: {random_tag}") converter.apply_conf( tag=random_tag, file_model=file_m, pitch_algo=pitch_alg, pitch_lvl=pitch_lvl, file_index=file_index, index_influence=index_inf, respiration_median_filtering=r_m_f, envelope_ratio=e_r, consonant_breath_protection=c_b_p, resample_sr=44100 if audio_files[0].endswith('.mp3') else 0, ) time.sleep(0.1) result = convert_now(audio_files, random_tag, converter) return result[0] def convert_now(audio_files, random_tag, converter): return converter( audio_files, random_tag, overwrite=False, parallel_workers=8 ) def upload_model(index_file, pth_file, model_name): if not index_file or not pth_file: raise ValueError("Both index and model files are required") if not model_name.strip(): raise ValueError("Model name cannot be empty") MODELS.append({"model": pth_file.name, "index": index_file.name, "model_name": model_name}) return "Model uploaded successfully!" def json_to_markdown_table(json_data): table = "| Key | Value |\n| --- | --- |\n" for key, value in json_data.items(): table += f"| {key} | {value} |\n" return table def model_info(name): for model in MODELS: if model["model_name"] == name: info = model_handler.model_info(model["model"]) info2 = { "Model Name": model["model_name"], "Model Config": info['config'], "Epochs Trained": info['epochs'], "Sample Rate": info['sr'], "Pitch Guidance": info['f0'], "Model Precision": info['size'], } return json_to_markdown_table(info2) return "Model not found" with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="rose"), title="Ilaria RVC 💖") as app: gr.Markdown("# Ilaria RVC 💖") gr.Markdown("Support the project by donating on [Ko-Fi](https://ko-fi.com/ilariaowo)") with gr.Tab("Inference"): with gr.Row(equal_height=True): models_dropdown = gr.Dropdown(label="Select Model", choices=[m["model_name"] for m in MODELS], value=MODELS[0]["model_name"]) refresh_button = gr.Button("Refresh Models", variant="secondary") refresh_button.click(lambda: gr.Dropdown(choices=[m["model_name"] for m in MODELS]), outputs=models_dropdown) sound_gui = gr.Audio(label="Input Audio", type="filepath") with gr.Accordion("Text-to-Speech", open=False): text_tts = gr.Textbox(label="Text Input", placeholder="Enter text to convert to speech", lines=3) dropdown_tts = gr.Dropdown(label="Language and Voice", choices=list(tts_order_voice.keys()), value=list(tts_order_voice.keys())[0]) button_tts = gr.Button("Generate Speech", variant="primary") button_tts.click(text_to_speech_edge, inputs=[text_tts, dropdown_tts], outputs=sound_gui) with gr.Accordion("Conversion Settings", open=False): pitch_algo_conf = gr.Radio(choices=["pm", "harvest", "crepe", "rmvpe", "rmvpe+"], value="rmvpe", label="Pitch Algorithm", info="Select the algorithm for pitch detection") with gr.Row(equal_height=True): pitch_lvl_conf = gr.Slider(label="Pitch Level", minimum=-24, maximum=24, step=1, value=0, info="Adjust pitch: negative for male, positive for female") index_inf_conf = gr.Slider(minimum=0, maximum=1, value=0.75, label="Index Influence", info="Controls how much accent is applied") with gr.Row(equal_height=True): respiration_filter_conf = gr.Slider(minimum=0, maximum=7, value=3, step=1, label="Respiration Median Filtering") envelope_ratio_conf = gr.Slider(minimum=0, maximum=1, value=0.25, label="Envelope Ratio") consonant_protec_conf = gr.Slider(minimum=0, maximum=0.5, value=0.5, label="Consonant Breath Protection") with gr.Row(equal_height=True): button_conf = gr.Button("Convert Audio", variant="primary") output_conf = gr.Audio(type="filepath", label="Converted Audio") button_conf.click(run, inputs=[models_dropdown, sound_gui, pitch_algo_conf, pitch_lvl_conf, index_inf_conf, respiration_filter_conf, envelope_ratio_conf, consonant_protec_conf], outputs=output_conf) with gr.Tab("Model Loader"): with gr.Accordion("Download Model", open=False): gr.Markdown("Download a model from Hugging Face (RVC model, max 500 MB)") model_url = gr.Textbox(label="Hugging Face Model URL", placeholder="https://huggingface.co/username/model") model_name = gr.Textbox(label="Model Name", placeholder="Enter a unique model name") download_button = gr.Button("Download Model", variant="primary") status = gr.Textbox(label="Status", interactive=False) model_pth = gr.Textbox(label="Model .pth File", interactive=False) index_pth = gr.Textbox(label="Index .index File", interactive=False) download_button.click(download_from_url, [model_url, model_name], [status, model_pth, index_pth]) with gr.Accordion("Upload Model", open=False): index_file_upload = gr.File(label="Index File (.index)") pth_file_upload = gr.File(label="Model File (.pth)") model_name_upload = gr.Textbox(label="Model Name", placeholder="Enter a unique model name") upload_button = gr.Button("Upload Model", variant="primary") upload_status = gr.Textbox(label="Status", interactive=False) upload_button.click(upload_model, [index_file_upload, pth_file_upload, model_name_upload], upload_status) with gr.Tab("Vocal Separator"): gr.Markdown("Separate vocals and instruments using UVR models (CPU only)") uvr5_audio_file = gr.Audio(label="Input Audio", type="filepath") with gr.Row(equal_height=True): uvr5_model = gr.Dropdown(label="UVR Model", choices=[m["model_name"] for m in UVR_5_MODELS]) uvr5_button = gr.Button("Separate", variant="primary") uvr5_output_voc = gr.Audio(label="Vocals", type="filepath") uvr5_output_inst = gr.Audio(label="Instrumental", type="filepath") uvr5_button.click(inf_handler, [uvr5_audio_file, uvr5_model], [uvr5_output_voc, uvr5_output_inst]) app.queue().launch(share=True)