Spaces:
Running
on
Zero
Running
on
Zero
import os | |
import glob | |
import subprocess | |
import time | |
import gc | |
import shutil | |
import sys | |
from assets.i18n.i18n import I18nAuto | |
i18n = I18nAuto() | |
current_dir = os.path.dirname(os.path.abspath(__file__)) | |
sys.path.append(current_dir) | |
from datetime import datetime | |
from helpers import INPUT_DIR, OLD_OUTPUT_DIR, ENSEMBLE_DIR, AUTO_ENSEMBLE_TEMP, move_old_files, clear_directory, BASE_DIR, clean_model, extract_model_name_from_checkpoint, sanitize_filename, find_clear_segment, save_segment, run_matchering, clamp_percentage | |
from model import get_model_config | |
from apollo_processing import process_with_apollo # Import Apollo processing | |
import torch | |
import yaml | |
import gradio as gr | |
import threading | |
import random | |
import librosa | |
import soundfile as sf | |
import numpy as np | |
import requests | |
import json | |
import locale | |
import re | |
import psutil | |
import concurrent.futures | |
from google.oauth2.credentials import Credentials | |
import tempfile | |
import traceback | |
from urllib.parse import urlparse, quote | |
try: | |
from google.colab import drive | |
IS_COLAB = True | |
except ImportError: | |
IS_COLAB = False | |
import matchering as mg | |
import warnings | |
warnings.filterwarnings("ignore") | |
BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
INFERENCE_PATH = os.path.join(BASE_DIR, "inference.py") | |
ENSEMBLE_PATH = os.path.join(BASE_DIR, "ensemble.py") | |
if IS_COLAB: | |
AUTO_ENSEMBLE_OUTPUT = "/content/drive/MyDrive/ensemble_output" | |
OUTPUT_DIR = "/content/drive/MyDrive/!output_file" | |
else: | |
AUTO_ENSEMBLE_OUTPUT = os.path.join(BASE_DIR, "ensemble_output") | |
OUTPUT_DIR = os.path.join(BASE_DIR, "output") | |
os.makedirs(AUTO_ENSEMBLE_OUTPUT, exist_ok=True) | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
def setup_directories(): | |
"""Create necessary directories and check Google Drive access.""" | |
if IS_COLAB: | |
try: | |
if not os.path.exists('/content/drive/MyDrive'): | |
print("Mounting Google Drive...") | |
from google.colab import drive | |
drive.mount('/content/drive', force_remount=True) | |
if not os.path.exists('/content/drive/MyDrive'): | |
raise RuntimeError("Google Drive mount failed. Please mount manually with 'from google.colab import drive; drive.mount('/content/drive', force_remount=True)'.") | |
except Exception as e: | |
raise RuntimeError(f"Failed to mount Google Drive: {str(e)}") | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
os.makedirs(INPUT_DIR, exist_ok=True) | |
os.makedirs(OLD_OUTPUT_DIR, exist_ok=True) | |
os.makedirs(AUTO_ENSEMBLE_OUTPUT, exist_ok=True) | |
def refresh_auto_output(): | |
try: | |
output_files = glob.glob(os.path.join(AUTO_ENSEMBLE_OUTPUT, "*.wav")) | |
if not output_files: | |
return None, "No output files found" | |
latest_file = max(output_files, key=os.path.getctime) | |
return latest_file, "Output refreshed successfully" | |
except Exception as e: | |
return None, f"Error refreshing output: {str(e)}" | |
import logging | |
# Loglama ayarları | |
logging.basicConfig( | |
level=logging.DEBUG, | |
filename='utils.log', | |
filemode='a', | |
format='%(asctime)s - %(levelname)s - %(message)s' | |
) | |
def update_progress_html(progress_label, progress_percent): | |
"""Gradio arayüzü için ilerleme çubuğu HTML'si oluşturur.""" | |
progress_percent = min(max(round(progress_percent), 0), 100) # %1 hassasiyet | |
return f""" | |
<div id="custom-progress" style="margin-top: 10px;"> | |
<div style="font-size: 1rem; color: #C0C0C0; margin-bottom: 5px;" id="progress-label">{progress_label}</div> | |
<div style="width: 100%; background-color: #444; border-radius: 5px; overflow: hidden;"> | |
<div id="progress-bar" style="width: {progress_percent}%; height: 20px; background-color: #6e8efb; transition: width 0.3s; max-width: 100%;"></div> | |
</div> | |
</div> | |
""" | |
def extract_model_name_from_checkpoint(checkpoint_path): | |
if not checkpoint_path: | |
return "Unknown" | |
base_name = os.path.basename(checkpoint_path) | |
model_name = os.path.splitext(base_name)[0] | |
print(f"Original checkpoint path: {checkpoint_path}, extracted model_name: {model_name}") | |
return model_name.strip() | |
def run_command_and_process_files( | |
model_type, | |
config_path, | |
start_check_point, | |
INPUT_DIR, | |
OUTPUT_DIR, | |
extract_instrumental=False, | |
use_tta=False, | |
demud_phaseremix_inst=False, | |
progress=None, | |
use_apollo=True, | |
apollo_normal_model="Apollo Universal Model", | |
inference_chunk_size=352800, | |
inference_overlap=2, | |
apollo_chunk_size=19, | |
apollo_overlap=2, | |
apollo_method="normal_method", | |
apollo_midside_model=None, | |
output_format="wav" | |
): | |
try: | |
logging.info(f"Starting run_command_and_process_files: model_type={model_type}, config_path={config_path}, inference_chunk_size={inference_chunk_size}, inference_overlap={inference_overlap}") | |
# Doğrulama | |
for path, name in [ | |
(config_path, "Configuration file"), | |
(start_check_point, "Checkpoint file"), | |
(INPUT_DIR, "Input directory") | |
]: | |
if not path: | |
raise ValueError(f"{name} is empty") | |
if not os.path.exists(path): | |
raise FileNotFoundError(f"{name} not found: {path}") | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
# Komut oluştur | |
INFERENCE_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "inference.py") | |
cmd_parts = [ | |
"python", INFERENCE_PATH, | |
"--model_type", str(model_type), | |
"--config_path", str(config_path), | |
"--start_check_point", str(start_check_point), | |
"--input_folder", str(INPUT_DIR), | |
"--store_dir", str(OUTPUT_DIR), | |
"--chunk_size", str(inference_chunk_size), | |
"--overlap", str(inference_overlap), | |
"--export_format", f"{output_format} FLOAT" | |
] | |
for flag, value in [ | |
("--extract_instrumental", extract_instrumental), | |
("--use_tta", use_tta), | |
("--demud_phaseremix_inst", demud_phaseremix_inst) | |
]: | |
if value: | |
cmd_parts.append(flag) | |
logging.info(f"Executing command: {' '.join(cmd_parts)}") | |
start_time = time.time() | |
# Subprocess başlat | |
process = subprocess.Popen( | |
cmd_parts, | |
cwd=os.path.dirname(os.path.abspath(__file__)), | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True, | |
bufsize=1, | |
universal_newlines=True | |
) | |
# İlerleme takibi | |
mixture_paths = sorted(glob.glob(os.path.join(INPUT_DIR, '*.*'))) | |
total_files = len(mixture_paths) | |
processed_files = 0 | |
base_progress_per_file = 80 / total_files if total_files > 0 else 80 # 0-80% ayrıştırma | |
stderr_output = "" | |
stdout_output = "" | |
while process.poll() is None: | |
line = process.stdout.readline().strip() | |
if line: | |
stdout_output += line + "\n" | |
if i18n("loaded_audio").lower() in line.lower(): | |
processed_files += 1 | |
progress_value = round(processed_files * base_progress_per_file) | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(progress_value / 100, desc=i18n("running_separation").format(processed_files, total_files)) | |
update_progress_html(i18n("running_separation").format(processed_files, total_files), progress_value) | |
logging.debug(line) | |
err_line = process.stderr.readline().strip() | |
if err_line: | |
stderr_output += err_line + "\n" | |
logging.error(err_line) | |
# Kalan çıktıları topla | |
stdout, stderr = process.communicate() | |
stdout_output += stdout | |
stderr_output += stderr | |
logging.debug(f"Subprocess stdout: {stdout_output}") | |
if stderr_output: | |
logging.error(f"Subprocess stderr: {stderr_output}") | |
if process.returncode != 0: | |
raise RuntimeError(f"Subprocess failed with code {process.returncode}: {stderr_output}") | |
elapsed_time = time.time() - start_time | |
logging.info(f"Subprocess completed in {elapsed_time:.2f} seconds") | |
# Çıktıları işle | |
filename_model = extract_model_name_from_checkpoint(start_check_point) | |
output_files = sorted(os.listdir(OUTPUT_DIR)) | |
if not output_files: | |
raise FileNotFoundError(i18n("no_output_files").format(OUTPUT_DIR)) | |
# Dosya yeniden adlandırma: 80-90% | |
total_output_files = len(output_files) | |
renamed_files = 0 | |
for filename in output_files: | |
file_path = os.path.join(OUTPUT_DIR, filename) | |
if not any(filename.lower().endswith(ext) for ext in ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a']): | |
continue | |
base, ext = os.path.splitext(filename) | |
detected_type = None | |
for type_key in ['vocals', 'instrumental', 'phaseremix', 'drum', 'bass', 'other', 'effects', 'speech', 'music', 'dry', 'male', 'female', 'bleed', 'karaoke']: | |
if type_key.lower() in base.lower(): | |
detected_type = type_key | |
break | |
type_suffix = detected_type.capitalize() if detected_type else 'Processed' | |
clean_base = sanitize_filename(base.split('_')[0]).rsplit('.', 1)[0] | |
new_filename = f"{clean_base}_{type_suffix}_{filename_model}{ext}" | |
new_file_path = os.path.join(OUTPUT_DIR, new_filename) | |
try: | |
os.rename(file_path, new_file_path) | |
renamed_files += 1 | |
progress_value = round(80 + (renamed_files / total_output_files) * 10) | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(progress_value / 100, desc=i18n("renaming_files").format(renamed_files, total_output_files)) | |
update_progress_html(i18n("renaming_files").format(renamed_files, total_output_files), progress_value) | |
except Exception as e: | |
logging.error(f"Could not rename {file_path} to {new_file_path}: {e}") | |
output_files = sorted(os.listdir(OUTPUT_DIR)) | |
if not output_files: | |
raise FileNotFoundError(i18n("no_output_files_after_rename").format(OUTPUT_DIR)) | |
# Çıktıları eşleştir | |
def find_file(keyword): | |
matching_files = [ | |
os.path.join(OUTPUT_DIR, f) for f in output_files | |
if keyword.lower() in f.lower() | |
] | |
return matching_files[0] if matching_files else None | |
output_list = [ | |
find_file('vocals'), find_file('instrumental'), find_file('phaseremix'), | |
find_file('drum'), find_file('bass'), find_file('other'), find_file('effects'), | |
find_file('speech'), find_file('music'), find_file('dry'), find_file('male'), | |
find_file('female'), find_file('bleed'), find_file('karaoke') | |
] | |
# Normalizasyon: 90-95% | |
normalized_outputs = [] | |
for i, output_file in enumerate(output_list): | |
if output_file and os.path.exists(output_file): | |
normalized_file = os.path.join(OUTPUT_DIR, f"{sanitize_filename(os.path.splitext(os.path.basename(output_file))[0])}.{output_format}") | |
try: | |
if output_file.endswith(f".{output_format}") and output_file != normalized_file: | |
shutil.copy(output_file, normalized_file) | |
elif output_file != normalized_file: | |
audio, sr = librosa.load(output_file, sr=None, mono=False) | |
sf.write(normalized_file, audio.T if audio.ndim > 1 else audio, sr) | |
else: | |
normalized_file = output_file | |
normalized_outputs.append(normalized_file) | |
progress_value = round(90 + (i + 1) / len(output_list) * 5) | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(progress_value / 100, desc=i18n("normalizing_output").format(i + 1, len(output_list))) | |
update_progress_html(i18n("normalizing_output").format(i + 1, len(output_list), progress_value)) | |
except Exception as e: | |
logging.error(f"Normalization failed for {output_file}: {e}") | |
normalized_outputs.append(None) | |
else: | |
normalized_outputs.append(None) | |
# Apollo işlemi: 95-100% | |
if use_apollo: | |
try: | |
from apollo_processing import process_with_apollo | |
normalized_outputs = process_with_apollo( | |
output_files=normalized_outputs, | |
output_dir=OUTPUT_DIR, | |
apollo_chunk_size=apollo_chunk_size, | |
apollo_overlap=apollo_overlap, | |
apollo_method=apollo_method, | |
apollo_normal_model=apollo_normal_model, | |
apollo_midside_model=apollo_midside_model, | |
output_format=output_format, | |
progress=lambda p, desc: progress((95 + p * 5) / 100, desc=desc) if progress else None, | |
total_progress_start=95, | |
total_progress_end=100 | |
) | |
except ImportError: | |
logging.warning("apollo_processing module not found, skipping Apollo processing") | |
except Exception as e: | |
logging.error(f"Apollo processing failed: {e}") | |
# Tamamlandı | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(1.0, desc=i18n("separation_complete")) | |
update_progress_html(i18n("separation_complete"), 100) | |
logging.info(f"Processing completed successfully. Outputs: {normalized_outputs}") | |
return tuple(normalized_outputs) | |
except Exception as e: | |
logging.error(f"run_command_and_process_files error: {str(e)}") | |
traceback.print_exc(file=sys.stderr) | |
return (None,) * 14 | |
def process_audio( | |
input_audio_file, | |
model, | |
chunk_size, | |
overlap, | |
export_format, | |
use_tta, | |
demud_phaseremix_inst, | |
extract_instrumental, | |
use_apollo, | |
apollo_chunk_size, | |
apollo_overlap, | |
apollo_method, | |
apollo_normal_model, | |
apollo_midside_model, | |
use_matchering=False, | |
matchering_passes=1, | |
progress=gr.Progress(track_tqdm=True), | |
*args, | |
**kwargs | |
): | |
try: | |
# Check Google Drive connection | |
setup_directories() | |
if input_audio_file is not None: | |
audio_path = input_audio_file.name if hasattr(input_audio_file, 'name') else input_audio_file | |
else: | |
return ( | |
None, None, None, None, None, None, None, None, None, None, None, None, None, None, | |
"No audio file provided", | |
update_progress_html("No input provided", 0) | |
) | |
os.makedirs(OUTPUT_DIR, exist_ok=True) | |
os.makedirs(OLD_OUTPUT_DIR, exist_ok=True) | |
move_old_files(OUTPUT_DIR) | |
print(f"process_audio: model parameter received: {model}") | |
# Clean model name, remove ⭐ and other unwanted characters | |
clean_model_name = clean_model(model) if not model.startswith("/") else extract_model_name_from_checkpoint(model) | |
print(f"Processing audio: {audio_path}, model: {clean_model_name}") | |
print(f"Raw UI inputs - chunk_size: {chunk_size}, overlap: {overlap}, apollo_chunk_size: {apollo_chunk_size}, apollo_overlap: {apollo_overlap}, apollo_method: {apollo_method}") | |
# Validate inference parameters | |
try: | |
inference_chunk_size = int(chunk_size) | |
except (TypeError, ValueError): | |
print(f"Invalid chunk_size: {chunk_size}. Defaulting to: 352800.") | |
inference_chunk_size = 352800 | |
try: | |
inference_overlap = int(overlap) | |
except (TypeError, ValueError): | |
print(f"Invalid overlap: {overlap}. Defaulting to: 2.") | |
inference_overlap = 2 | |
# Validate Apollo parameters | |
try: | |
apollo_chunk_size = int(apollo_chunk_size) | |
except (TypeError, ValueError): | |
print(f"Invalid apollo_chunk_size: {apollo_chunk_size}. Defaulting to: 19.") | |
apollo_chunk_size = 19 | |
try: | |
apollo_overlap = int(apollo_overlap) | |
except (TypeError, ValueError): | |
print(f"Invalid apollo_overlap: {apollo_overlap}. Defaulting to: 2.") | |
apollo_overlap = 2 | |
# Map apollo_method to backend values | |
if apollo_method in ["Mid-side method", "2", 2, "mid_side_method"]: | |
apollo_method = "mid_side_method" | |
elif apollo_method in ["Normal method", "1", 1, "normal_method"]: | |
apollo_method = "normal_method" | |
else: | |
print(f"Invalid apollo_method: {apollo_method}. Defaulting to: normal_method.") | |
apollo_method = "normal_method" | |
print(f"Parsed apollo_method: {apollo_method}") | |
print(f"Corrected values - inference_chunk_size: {inference_chunk_size}, inference_overlap: {inference_overlap}, apollo_chunk_size: {apollo_chunk_size}, apollo_overlap: {apollo_overlap}") | |
# Copy input file to INPUT_DIR | |
input_filename = os.path.basename(audio_path) | |
dest_path = os.path.join(INPUT_DIR, input_filename) | |
shutil.copy(audio_path, dest_path) | |
print(f"Input file copied: {dest_path}") | |
# Get model configuration with cleaned model name | |
model_type, config_path, start_check_point = get_model_config(clean_model_name, inference_chunk_size, inference_overlap) | |
print(f"Model configuration: model_type={model_type}, config_path={config_path}, start_check_point={start_check_point}") | |
outputs = run_command_and_process_files( | |
model_type=model_type, | |
config_path=config_path, | |
start_check_point=start_check_point, | |
INPUT_DIR=INPUT_DIR, | |
OUTPUT_DIR=OUTPUT_DIR, | |
extract_instrumental=extract_instrumental, | |
use_tta=use_tta, | |
demud_phaseremix_inst=demud_phaseremix_inst, | |
progress=progress, | |
use_apollo=use_apollo, | |
apollo_normal_model=apollo_normal_model, | |
inference_chunk_size=inference_chunk_size, | |
inference_overlap=inference_overlap, | |
apollo_chunk_size=apollo_chunk_size, | |
apollo_overlap=apollo_overlap, | |
apollo_method=apollo_method, | |
apollo_midside_model=apollo_midside_model, | |
output_format=export_format.split()[0].lower() | |
) | |
if outputs is None or all(output is None for output in outputs): | |
raise ValueError("run_command_and_process_files returned None or all None outputs") | |
# Apply Matchering (if enabled) | |
if use_matchering: | |
# Progress update for Matchering | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(90, desc="Applying Matchering") | |
# Find clean segment from original audio | |
segment_start, segment_end, segment_audio = find_clear_segment(audio_path) | |
segment_path = os.path.join(tempfile.gettempdir(), "matchering_segment.wav") | |
save_segment(segment_audio, 44100, segment_path) | |
# Process each output with Matchering | |
mastered_outputs = [] | |
for output in outputs: | |
if output and os.path.exists(output): | |
output_base = sanitize_filename(os.path.splitext(os.path.basename(output))[0]) | |
mastered_path = os.path.join(OUTPUT_DIR, f"{output_base}_mastered.wav") | |
mastered_output = run_matchering( | |
reference_path=segment_path, | |
target_path=output, | |
output_path=mastered_path, | |
passes=matchering_passes, | |
bit_depth=24 | |
) | |
mastered_outputs.append(mastered_path) | |
else: | |
mastered_outputs.append(output) | |
# Clean up segment file | |
if os.path.exists(segment_path): | |
os.remove(segment_path) | |
outputs = tuple(mastered_outputs) | |
if progress is not None and callable(getattr(progress, '__call__', None)): | |
progress(100, desc="Processing complete") | |
return ( | |
outputs[0], outputs[1], outputs[2], outputs[3], outputs[4], outputs[5], outputs[6], | |
outputs[7], outputs[8], outputs[9], outputs[10], outputs[11], outputs[12], outputs[13], | |
"Audio processing completed", | |
update_progress_html("Audio processing completed", 100) | |
) | |
except Exception as e: | |
print(f"process_audio error: {str(e)}") | |
import traceback | |
traceback.print_exc() | |
return ( | |
None, None, None, None, None, None, None, None, None, None, None, None, None, None, | |
f"Error occurred: {str(e)}", | |
update_progress_html("Error occurred", 0) | |
) | |
def ensemble_audio_fn(files, method, weights, progress=gr.Progress()): | |
try: | |
if len(files) < 2: | |
return None, "Minimum two files required" | |
valid_files = [f for f in files if os.path.exists(f)] | |
if len(valid_files) < 2: | |
return None, "Valid files not found" | |
output_dir = os.path.join(BASE_DIR, "ensembles") | |
os.makedirs(output_dir, exist_ok=True) | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
output_path = f"{output_dir}/ensemble_{timestamp}.wav" | |
ensemble_args = [ | |
"--files", *valid_files, | |
"--type", method.lower().replace(' ', '_'), | |
"--output", output_path | |
] | |
if weights and weights.strip(): | |
weights_list = [str(w) for w in map(float, weights.split(','))] | |
ensemble_args += ["--weights", *weights_list] | |
progress(0, desc="Starting ensemble process", total=100) | |
result = subprocess.run( | |
["python", "ensemble.py"] + ensemble_args, | |
capture_output=True, | |
text=True | |
) | |
start_time = time.time() | |
total_estimated_time = 10.0 # Adjust based on actual ensemble duration | |
elapsed_time = 0 | |
while elapsed_time < total_estimated_time: | |
elapsed_time = time.time() - start_time | |
progress_value = (elapsed_time / total_estimated_time) * 100 | |
progress_value = clamp_percentage(progress_value) | |
progress(progress_value, desc=f"Ensembling progress: {progress_value}%") | |
time.sleep(0.1) | |
progress(100, desc="Finalizing ensemble output") | |
log = f"Success: {result.stdout}" if not result.stderr else f"Error: {result.stderr}" | |
return output_path, log | |
except Exception as e: | |
return None, f"Critical error: {str(e)}" | |
finally: | |
progress(100, desc="Ensemble process completed") | |
def auto_ensemble_process( | |
auto_input_audio_file, | |
selected_models, | |
auto_chunk_size, | |
auto_overlap, | |
export_format, | |
auto_use_tta, | |
auto_extract_instrumental, | |
auto_ensemble_type, | |
_state, | |
auto_use_apollo=True, | |
auto_apollo_normal_model="Apollo Universal Model", | |
auto_apollo_chunk_size=19, | |
auto_apollo_overlap=2, | |
auto_apollo_method="normal_method", | |
auto_use_matchering=False, | |
auto_matchering_passes=1, | |
apollo_midside_model=None, | |
progress=gr.Progress(track_tqdm=True) | |
): | |
"""Process audio with multiple models and ensemble the results, saving output to Google Drive.""" | |
try: | |
# Check Google Drive connection and setup directories | |
setup_directories() | |
if not selected_models or len(selected_models) < 1: | |
yield None, i18n("no_models_selected"), update_progress_html(i18n("error_occurred"), 0) | |
return | |
if auto_input_audio_file is None: | |
existing_files = os.listdir(INPUT_DIR) | |
if not existing_files: | |
yield None, i18n("no_input_audio_provided"), update_progress_html(i18n("error_occurred"), 0) | |
return | |
audio_path = os.path.join(INPUT_DIR, existing_files[0]) | |
else: | |
audio_path = auto_input_audio_file.name if hasattr(auto_input_audio_file, 'name') else auto_input_audio_file | |
# Copy input file to INPUT_DIR | |
input_filename = os.path.basename(audio_path) | |
dest_path = os.path.join(INPUT_DIR, input_filename) | |
shutil.copy(audio_path, dest_path) | |
print(f"Input file copied: {dest_path}") | |
# Parse apollo method | |
if auto_apollo_method in ["2", 2]: | |
auto_apollo_method = "mid_side_method" | |
elif auto_apollo_method in ["1", 1]: | |
auto_apollo_method = "normal_method" | |
print(f"Parsed auto_apollo_method: {auto_apollo_method}") | |
corrected_auto_chunk_size = int(auto_apollo_chunk_size) | |
corrected_auto_overlap = int(auto_apollo_overlap) | |
print(f"Corrected values - auto_apollo_chunk_size: {corrected_auto_chunk_size}, auto_apollo_overlap: {corrected_auto_overlap}") | |
# Setup temporary directories | |
auto_ensemble_temp = os.path.join(BASE_DIR, "auto_ensemble_temp") | |
os.makedirs(auto_ensemble_temp, exist_ok=True) | |
clear_directory(auto_ensemble_temp) | |
all_outputs = [] | |
total_models = len(selected_models) | |
model_progress_range = 60 | |
model_progress_per_step = model_progress_range / total_models if total_models > 0 else 0 | |
for i, model in enumerate(selected_models): | |
clean_model_name = clean_model(model) | |
print(f"Processing model {i+1}/{total_models}: Original={model}, Cleaned={clean_model_name}") | |
model_output_dir = os.path.join(auto_ensemble_temp, clean_model_name) | |
os.makedirs(model_output_dir, exist_ok=True) | |
current_progress = i * model_progress_per_step | |
current_progress = clamp_percentage(current_progress) | |
yield None, i18n("loading_model").format(i+1, total_models, clean_model_name), update_progress_html( | |
i18n("loading_model_progress").format(i+1, total_models, clean_model_name, current_progress), | |
current_progress | |
) | |
model_type, config_path, start_check_point = get_model_config(clean_model_name, auto_chunk_size, auto_overlap) | |
print(f"Model configuration: model_type={model_type}, config_path={config_path}, start_check_point={start_check_point}") | |
cmd = [ | |
"python", INFERENCE_PATH, | |
"--model_type", model_type, | |
"--config_path", config_path, | |
"--start_check_point", start_check_point, | |
"--input_folder", INPUT_DIR, | |
"--store_dir", model_output_dir, | |
"--chunk_size", str(auto_chunk_size), | |
"--overlap", str(auto_overlap), | |
"--export_format", f"{export_format.split()[0].lower()} FLOAT" | |
] | |
if auto_use_tta: | |
cmd.append("--use_tta") | |
if auto_extract_instrumental: | |
cmd.append("--extract_instrumental") | |
print(f"Running command: {' '.join(cmd)}") | |
process = subprocess.Popen( | |
cmd, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True, | |
bufsize=1, | |
universal_newlines=True | |
) | |
stderr_output = "" | |
for line in process.stdout: | |
print(line.strip()) | |
if "Progress:" in line: | |
try: | |
percentage = float(re.search(r"Progress: (\d+\.\d+)%", line).group(1)) | |
model_percentage = (percentage / 100) * model_progress_per_step | |
current_progress = (i * model_progress_per_step) + model_percentage | |
current_progress = clamp_percentage(current_progress) | |
yield None, i18n("loading_model").format(i+1, total_models, clean_model_name), update_progress_html( | |
i18n("loading_model_progress").format(i+1, total_models, clean_model_name, current_progress), | |
current_progress | |
) | |
except (AttributeError, ValueError) as e: | |
print(f"Progress parsing error: {e}") | |
for line in process.stderr: | |
stderr_output += line | |
print(line.strip()) | |
process.wait() | |
if process.returncode != 0: | |
print(f"Error: {stderr_output}") | |
yield None, i18n("model_failed").format(clean_model_name, stderr_output), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
return | |
gc.collect() | |
if torch.cuda.is_available(): | |
torch.cuda.empty_cache() | |
current_progress = (i + 1) * model_progress_per_step | |
current_progress = clamp_percentage(current_progress) | |
yield None, i18n("completed_model").format(i+1, total_models, clean_model_name), update_progress_html( | |
i18n("completed_model_progress").format(i+1, total_models, clean_model_name, current_progress), | |
current_progress | |
) | |
model_outputs = glob.glob(os.path.join(model_output_dir, "*.wav")) | |
if not model_outputs: | |
raise FileNotFoundError(i18n("model_output_failed").format(clean_model_name)) | |
all_outputs.extend(model_outputs) | |
# Select compatible files for ensemble | |
preferred_type = 'instrumental' if auto_extract_instrumental else 'vocals' | |
ensemble_files = [output for output in all_outputs if preferred_type.lower() in output.lower()] | |
print(f"Selected ensemble files: {ensemble_files}") | |
if len(ensemble_files) < 2: | |
print(f"Warning: Insufficient {preferred_type} files ({len(ensemble_files)}). Falling back to all outputs.") | |
ensemble_files = all_outputs | |
if len(ensemble_files) < 2: | |
raise ValueError(i18n("insufficient_files_for_ensemble").format(len(ensemble_files))) | |
# Enhanced outputs with Apollo (if enabled) | |
if auto_use_apollo: | |
yield None, i18n("enhancing_with_apollo").format(0, len(all_outputs)), update_progress_html( | |
i18n("waiting_for_files"), 60 | |
) | |
all_outputs = process_with_apollo( | |
output_files=all_outputs, | |
output_dir=auto_ensemble_temp, | |
apollo_chunk_size=corrected_auto_chunk_size, | |
apollo_overlap=corrected_auto_overlap, | |
apollo_method=auto_apollo_method, | |
apollo_normal_model=auto_apollo_normal_model, | |
apollo_midside_model=apollo_midside_model, | |
output_format=export_format.split()[0].lower(), | |
progress=progress, | |
total_progress_start=60, | |
total_progress_end=90 | |
) | |
# Perform ensemble | |
yield None, i18n("performing_ensemble"), update_progress_html( | |
i18n("performing_ensemble"), 90 | |
) | |
quoted_files = [f'"{f}"' for f in ensemble_files] | |
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") | |
output_path = os.path.join(AUTO_ENSEMBLE_OUTPUT, f"auto_ensemble_output_{timestamp}.wav") | |
ensemble_cmd = [ | |
"python", ENSEMBLE_PATH, | |
"--files", *quoted_files, | |
"--type", auto_ensemble_type, | |
"--output", f'"{output_path}"' | |
] | |
print(f"Running ensemble command: {' '.join(ensemble_cmd)}") | |
try: | |
process = subprocess.Popen( | |
" ".join(ensemble_cmd), | |
shell=True, | |
stdout=subprocess.PIPE, | |
stderr=subprocess.PIPE, | |
text=True, | |
bufsize=1, | |
universal_newlines=True | |
) | |
stdout_output = "" | |
stderr_output = "" | |
for line in process.stdout: | |
stdout_output += line | |
print(f"Ensemble stdout: {line.strip()}") | |
for line in process.stderr: | |
stderr_output += line | |
print(f"Ensemble stderr: {line.strip()}") | |
process.wait() | |
if process.returncode != 0: | |
print(f"Ensemble subprocess failed with code {process.returncode}: {stderr_output}") | |
yield None, i18n("ensemble_error").format(stderr_output), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
return | |
print(f"Checking if output file exists: {output_path}") | |
if not os.path.exists(output_path): | |
raise RuntimeError(f"Ensemble output file not created at {output_path}. Stdout: {stdout_output}, Stderr: {stderr_output}") | |
except Exception as e: | |
print(f"Ensemble command execution failed: {str(e)}") | |
yield None, i18n("ensemble_error").format(str(e)), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
return | |
# Apply Matchering (if enabled) | |
if auto_use_matchering and os.path.exists(output_path): | |
yield None, i18n("applying_matchering"), update_progress_html( | |
i18n("applying_matchering"), 98 | |
) | |
try: | |
# Find clean segment | |
segment_start, segment_end, segment_audio = find_clear_segment(audio_path) | |
segment_path = os.path.join(tempfile.gettempdir(), "matchering_segment.wav") | |
save_segment(segment_audio, 44100, segment_path) | |
# Master the ensemble output | |
mastered_output_path = os.path.join(AUTO_ENSEMBLE_OUTPUT, f"auto_ensemble_output_{timestamp}_mastered.wav") | |
print(f"Running Matchering: reference={segment_path}, target={output_path}, output={mastered_output_path}") | |
mastered_output = run_matchering( | |
reference_path=segment_path, | |
target_path=output_path, | |
output_path=mastered_output_path, | |
passes=auto_matchering_passes, | |
bit_depth=24 | |
) | |
# Verify mastered output | |
if not os.path.exists(mastered_output_path): | |
raise RuntimeError(f"Matchering failed to create output at {mastered_output_path}") | |
# Clean up segment file | |
if os.path.exists(segment_path): | |
os.remove(segment_path) | |
output_path = mastered_output_path | |
print(f"Matchering completed: {mastered_output_path}") | |
except Exception as e: | |
print(f"Matchering error: {str(e)}") | |
yield None, i18n("error").format(f"Matchering failed: {str(e)}"), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
return | |
yield None, i18n("finalizing_ensemble_output"), update_progress_html( | |
i18n("finalizing_ensemble_output"), 98 | |
) | |
if not os.path.exists(output_path): | |
raise RuntimeError(i18n("ensemble_file_creation_failed").format(output_path)) | |
# Verify write permissions for Google Drive directory | |
try: | |
print(f"Verifying write permissions for {AUTO_ENSEMBLE_OUTPUT}") | |
test_file = os.path.join(AUTO_ENSEMBLE_OUTPUT, "test_write.txt") | |
with open(test_file, "w") as f: | |
f.write("Test") | |
os.remove(test_file) | |
print(f"Write permissions verified for {AUTO_ENSEMBLE_OUTPUT}") | |
except Exception as e: | |
print(f"Write permission error for {AUTO_ENSEMBLE_OUTPUT}: {str(e)}") | |
yield None, i18n("error").format(f"Write permission error: {str(e)}"), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
return | |
# Verify file in Google Drive | |
print(f"Final output file: {output_path}") | |
if IS_COLAB: | |
drive_output_path = os.path.join("/content/drive/MyDrive/ensemble_output", os.path.basename(output_path)) | |
print(f"Checking if file exists in Google Drive: {drive_output_path}") | |
if not os.path.exists(drive_output_path): | |
print(f"File not found in Google Drive, copying from local path: {output_path}") | |
shutil.copy(output_path, drive_output_path) | |
print(f"Copied to Google Drive: {drive_output_path}") | |
yield output_path, i18n("success_output_created") + f" Saved to {drive_output_path if IS_COLAB else output_path}", update_progress_html( | |
i18n("ensemble_completed"), 100 | |
) | |
except Exception as e: | |
print(f"auto_ensemble_process error: {str(e)}") | |
import traceback | |
traceback.print_exc() | |
yield None, i18n("error").format(str(e)), update_progress_html( | |
i18n("error_occurred"), 0 | |
) | |
finally: | |
shutil.rmtree(auto_ensemble_temp, ignore_errors=True) | |
gc.collect() | |
if torch.cuda.is_available(): | |
torch.cuda.empty_cache() | |