import gradio as gr import os import asyncio import shutil from datetime import datetime import urllib.request import zipfile import gdown import requests import logging import subprocess # Настройка логирования logging.basicConfig(level=logging.INFO) # Константы f0_min = 50 f0_max = 1100 rvc_models_dir = './models' input_folder = './input' output_folder = './output' # Создание необходимых директорий os.makedirs(rvc_models_dir, exist_ok=True) os.makedirs(input_folder, exist_ok=True) os.makedirs(output_folder, exist_ok=True) # Общие функции async def run_command_async(command): """Асинхронный запуск команд.""" process = await asyncio.create_subprocess_shell( command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) stdout, stderr = await process.communicate() if process.returncode != 0: logging.error(f"Error: {stderr.decode()}") return stdout.decode() def get_models_list(): """Получение списка моделей.""" models = [] if os.path.exists(rvc_models_dir): models = [d for d in os.listdir(rvc_models_dir) if os.path.isdir(os.path.join(rvc_models_dir, d))] return models # Функции для первой вкладки def process_uploaded_files(files): """Обработка загруженных файлов.""" if os.path.exists(input_folder): shutil.rmtree(input_folder) os.makedirs(input_folder, exist_ok=True) for file in files: shutil.copy(file.name, input_folder) return f"Uploaded {len(files)} files to {input_folder}" async def convert_voice( voicemodel_name, pitch_vocal, method_pitch, hop_length, index_rate, filter_radius, rms, protect, output_format ): """Конвертация голоса.""" current_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S") output_ai_vocals_folder = os.path.join(output_folder, current_time) os.makedirs(output_ai_vocals_folder, exist_ok=True) output_test = { "mp3": "-ab 320k", "wav": "-c:a pcm_s32le", "flac": "-c:a flac -af aformat=s16" }[output_format] for filename in os.listdir(input_folder): if filename.endswith(("wav", "mp3", "flac")): input_path = os.path.join(input_folder, filename) base_name = os.path.splitext(filename)[0] output_filename = f"mono_{base_name}_{current_time}_{method_pitch}_{pitch_vocal}.{output_format}" output_path = os.path.join(output_ai_vocals_folder, output_filename) # Выполнение команды RVC await run_command_async( f"python3 -m rvc.cli.rvc_cli -i \"{input_path}\" -m \"{voicemodel_name}\" -p {pitch_vocal} " f"-ir {index_rate} -fr {filter_radius} -rms {rms} -f0 \"{method_pitch}\" " f"-hop {hop_length} -pro {protect} -f0min {f0_min} -f0max {f0_max} -f \"wav\"" ) # Конвертация в нужный формат await run_command_async( f"ffmpeg -y -i ./output/Voice_Converted.wav " f"-vn -ar 44100 -ac 1 {output_test} \"{output_path}\"" ) return [os.path.join(output_ai_vocals_folder, f) for f in os.listdir(output_ai_vocals_folder) if f.endswith(output_format)] # Функции для второй вкладки def extract_zip(extraction_folder, zip_name): """Распаковка архива.""" os.makedirs(extraction_folder, exist_ok=True) with zipfile.ZipFile(zip_name, 'r') as zip_ref: zip_ref.extractall(extraction_folder) os.remove(zip_name) model_file = next((f for f in os.listdir(extraction_folder) if f.endswith('.pth')), None) index_file = next((f for f in os.listdir(extraction_folder) if f.endswith('.index')), None) if model_file: os.rename(os.path.join(extraction_folder, model_file), os.path.join(extraction_folder, model_file)) if index_file: os.rename(os.path.join(extraction_folder, index_file), os.path.join(extraction_folder, index_file)) async def download_model(url, dir_name): """Скачивание модели.""" try: zip_path = os.path.join(rvc_models_dir, f"{dir_name}.zip") extraction_path = os.path.join(rvc_models_dir, dir_name) if os.path.exists(extraction_path): return f"Error: Directory {dir_name} already exists!" if 'drive.google.com' in url: file_id = url.split("file/d/")[1].split("/")[0] if "file/d/" in url else url.split("id=")[1].split("&")[0] gdown.download(id=file_id, output=zip_path, quiet=False) elif 'huggingface.co' in url: urllib.request.urlretrieve(url, zip_path) elif 'pixeldrain.com' in url: file_id = url.split("pixeldrain.com/u/")[1] response = requests.get(f"https://pixeldrain.com/api/file/{file_id}") with open(zip_path, 'wb') as f: f.write(response.content) extract_zip(extraction_path, zip_path) return f"Model {dir_name} successfully installed!" except Exception as e: return f"Error: {str(e)}" # Функции для третьей вкладки async def process_dual_voice( input_file, model1, model2, pitch1, pitch2, method1, method2, hop1, hop2, index1, index2 ): """Обработка двух моделей голоса.""" try: # Создаем временную папку для обработки temp_folder = os.path.join(output_folder, "dual_processing") os.makedirs(temp_folder, exist_ok=True) # Копируем входной файл во временную папку input_path = os.path.join(temp_folder, os.path.basename(input_file.name)) shutil.copy(input_file.name, input_path) # Обработка первой модели output1_path = os.path.join(temp_folder, "output1.wav") await run_command_async( f"python3 -m rvc.cli.rvc_cli -i \"{input_path}\" -m \"{model1}\" -p {pitch1} " f"-ir {index1} -f0 \"{method1}\" -hop {hop1} -f0min {f0_min} -f0max {f0_max} -f \"wav\"" ) shutil.move("./output/Voice_Converted.wav", output1_path) # Обработка второй модели output2_path = os.path.join(temp_folder, "output2.wav") await run_command_async( f"python3 -m rvc.cli.rvc_cli -i \"{input_path}\" -m \"{model2}\" -p {pitch2} " f"-ir {index2} -f0 \"{method2}\" -hop {hop2} -f0min {f0_min} -f0max {f0_max} -f \"wav\"" ) shutil.move("./output/Voice_Converted.wav", output2_path) return output1_path, output2_path, "Обработка завершена" except Exception as e: return None, None, f"Ошибка: {str(e)}" # Создание интерфейса with gr.Blocks() as demo: gr.Markdown("# VBach Lite") with gr.Tabs(): # Первая вкладка with gr.TabItem("Пакетная замена вокала"): with gr.Row(): with gr.Column(): file_input = gr.File(file_count="multiple", label="Загрузить один или несколько файлов") upload_status = gr.Textbox(label="Статус загрузки") file_input.upload( fn=process_uploaded_files, inputs=file_input, outputs=upload_status ) with gr.Accordion("Настройки RVC:", open=True): voicemodel_name = gr.Dropdown( choices=get_models_list(), label="Имя модели", value="senko" if "senko" in get_models_list() else None ) refresh_btn = gr.Button("Обновить") pitch_vocal = gr.Slider(-48, 48, value=0, step=12, label="Высота тона") method_pitch = gr.Dropdown( ["fcpe", "rmvpe+", "mangio-crepe"], value="rmvpe+", label="Метод извлечения тона" ) hop_length = gr.Slider(0, 255, value=73, step=1, label="Длина шага для mangio-crepe") index_rate = gr.Slider(0, 1, value=1, step=0.05, label="ИИ-акцент") filter_radius = gr.Slider(0, 7, value=7, step=1, label="Радиус фильтра") rms = gr.Slider(0, 1, value=0, step=0.1, label="Нормализация") protect = gr.Slider(0, 0.5, value=0.35, step=0.05, label="Защита согласных") output_format = gr.Dropdown( ["flac", "wav", "mp3"], value="mp3", label="Формат вывода" ) convert_btn = gr.Button("Преобразовать!", variant="primary") with gr.Column(): output_files = gr.Files(label="Аудио с преобразованным вокалом") # Обработчики refresh_btn.click( fn=lambda: gr.update(choices=get_models_list()), outputs=voicemodel_name ) convert_btn.click( fn=convert_voice, inputs=[ voicemodel_name, pitch_vocal, method_pitch, hop_length, index_rate, filter_radius, rms, protect, output_format ], outputs=output_files ) # Вторая вкладка (Двойное преобразование) with gr.TabItem("Двойное преобразование"): with gr.Row(): with gr.Column(): gr.Markdown("## Загрузите аудио для обработки") audio_input = gr.File(label="Аудио файл", type="filepath") with gr.Accordion("Настройки модели 1", open=True): model1_name = gr.Dropdown( choices=get_models_list(), label="Модель 1", value="senko" if "senko" in get_models_list() else None ) refresh_btn1 = gr.Button("Обновить") pitch1 = gr.Slider(-48, 48, value=0, step=12, label="Высота тона") method1 = gr.Dropdown( ["fcpe", "rmvpe+", "mangio-crepe"], value="rmvpe+", label="Метод извлечения тона" ) hop1 = gr.Slider(0, 255, value=73, step=1, label="Длина шага") index1 = gr.Slider(0, 1, value=1, step=0.05, label="ИИ-акцент") with gr.Accordion("Настройки модели 2", open=True): model2_name = gr.Dropdown( choices=get_models_list(), label="Модель 2", value="senko" if "senko" in get_models_list() else None ) refresh_btn2 = gr.Button("Обновить") pitch2 = gr.Slider(-48, 48, value=0, step=12, label="Высота тона") method2 = gr.Dropdown( ["fcpe", "rmvpe+", "mangio-crepe"], value="rmvpe+", label="Метод извлечения тона" ) hop2 = gr.Slider(0, 255, value=73, step=1, label="Длина шага") index2 = gr.Slider(0, 1, value=1, step=0.05, label="ИИ-акцент") convert_btn = gr.Button("ПРЕОБРАЗОВАТЬ", variant="primary") with gr.Column(): gr.Markdown("## Результаты преобразования") output1 = gr.Audio(label="Вывод модели 1", interactive=False) output2 = gr.Audio(label="Вывод модели 2", interactive=False) status = gr.Textbox(label="Статус", interactive=False) # Обработчики convert_btn.click( fn=process_dual_voice, inputs=[ audio_input, model1_name, model2_name, pitch1, pitch2, method1, method2, hop1, hop2, index1, index2 ], outputs=[output1, output2, status] ) refresh_btn1.click( fn=lambda: gr.update(choices=get_models_list()), outputs=model1_name ) refresh_btn2.click( fn=lambda: gr.update(choices=get_models_list()), outputs=model2_name ) # Третья вкладка with gr.TabItem("Скачать модель"): with gr.Row(): with gr.Column(): gr.Markdown("## Здесь можно скачать модель по ссылке на архив с нею") url_input = gr.Textbox( label="Ссылка на архив с моделью", placeholder="хаггингфейс.ко/модель.zip" ) dir_name_input = gr.Textbox( label="Имя модели", placeholder="Имя модели" ) download_btn = gr.Button("Скачать модель", variant="primary") install_status = gr.Textbox(label="Статус скачивания модели") download_btn.click( fn=download_model, inputs=[url_input, dir_name_input], outputs=install_status ) with gr.Column(): gr.Markdown("## Проверить список моделей") model_list = gr.Textbox( value="\n".join(get_models_list()), lines=10, label="Установленные модели" ) refresh_models_btn = gr.Button("Обновить") refresh_models_btn.click( fn=lambda: gr.update(value="\n".join(get_models_list())), outputs=model_list ) if __name__ == "__main__": demo.launch(share=True)