|
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) |
|
|
|
|
|
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) |