CoolFace
Apppublic

ASesYusuf1/SESA_Audio_Separation

sourceHugging Facemitupdated 6mo agoView on Hugging Face
14likes
helpers.py420 linesDownload Raw Back to root
1import os2import shutil3import glob4import re5import subprocess6import random7import yaml8from pathlib import Path9import torch10import gradio as gr11import threading12import time13import librosa14import soundfile as sf15import numpy as np16import requests17import json18import locale19from datetime import datetime20import yt_dlp21import validators22from pytube import YouTube23 24# Google API imports (optional - for Colab/Google Drive support)25try:26    from googleapiclient.discovery import build27    from googleapiclient.http import MediaIoBaseDownload28    from google.oauth2.credentials import Credentials29    GOOGLE_API_AVAILABLE = True30except ImportError:31    GOOGLE_API_AVAILABLE = False32    build = None33    MediaIoBaseDownload = None34    Credentials = None35 36import io37import math38import hashlib39import gc40import psutil41import concurrent.futures42from tqdm import tqdm43import tempfile44from urllib.parse import urlparse, quote45import argparse46from tqdm.auto import tqdm47import torch.nn as nn48from model import get_model_config, MODEL_CONFIGS, get_all_model_configs_with_custom, load_custom_models49from assets.i18n.i18n import I18nAuto50import matchering as mg51from scipy.signal import find_peaks52 53i18n = I18nAuto()54 55# Temel dizinler56BASE_DIR = os.path.dirname(os.path.abspath(__file__))57INPUT_DIR = os.path.join(BASE_DIR, "input")58OUTPUT_DIR = os.path.join(BASE_DIR, "output")59OLD_OUTPUT_DIR = os.path.join(BASE_DIR, "old_output")60AUTO_ENSEMBLE_TEMP = os.path.join(BASE_DIR, "auto_ensemble_temp")61AUTO_ENSEMBLE_OUTPUT = os.path.join(BASE_DIR, "ensemble_folder")62VIDEO_TEMP = os.path.join(BASE_DIR, "video_temp")63ENSEMBLE_DIR = os.path.join(BASE_DIR, "ensemble")64COOKIE_PATH = os.path.join(BASE_DIR, "cookies.txt")65INFERENCE_SCRIPT_PATH = os.path.join(BASE_DIR, "inference.py")66 67def extract_model_name_from_checkpoint(checkpoint_path):68    if not checkpoint_path:69        return "Unknown"70    base_name = os.path.basename(checkpoint_path)71    model_name = os.path.splitext(base_name)[0]72    return model_name.strip()73 74for directory in [BASE_DIR, INPUT_DIR, OUTPUT_DIR, OLD_OUTPUT_DIR, AUTO_ENSEMBLE_TEMP, AUTO_ENSEMBLE_OUTPUT, VIDEO_TEMP, ENSEMBLE_DIR]:75    os.makedirs(directory, exist_ok=True)76 77class IndentDumper(yaml.Dumper):78    def increase_indent(self, flow=False, indentless=False):79        return super(IndentDumper, self).increase_indent(flow, False)80 81def tuple_constructor(loader, node):82    """YAML'dan bir tuple yükler."""83    values = loader.construct_sequence(node)84    return tuple(values)85 86yaml.SafeLoader.add_constructor('tag:yaml.org,2002:python/tuple', tuple_constructor)87 88def clean_model(model):89    """90    Cleans a model name by removing unwanted characters like ⭐ and extra whitespace.91    92    Args:93        model (str): The model name to clean.94    95    Returns:96        str: The cleaned model name, or None if input is invalid.97    """98    if not model or not isinstance(model, str):99        return None100    # Remove ⭐ and extra whitespace101    cleaned = model.replace("⭐", "").strip()102    # Remove any additional unwanted characters if needed103    cleaned = cleaned.replace("\t", " ").replace("\n", " ")104    return cleaned105 106def get_original_category(translated_category):107    all_configs = get_all_model_configs_with_custom()108    for original_cat in all_configs.keys():109        if i18n(original_cat) == translated_category:110            return original_cat111    return None112 113def clamp_percentage(value):114    """Clamp percentage values to the 0-100 range."""115    try:116        return min(max(float(value), 0), 100)117    except (ValueError, TypeError):118        print(f"Warning: Invalid percentage value {value}, defaulting to 0")119        return 0    120 121def update_model_dropdown(category, favorites=None):122    # Get all configs including custom models123    all_configs = get_all_model_configs_with_custom()124    # Map translated category back to English125    eng_cat = next((k for k in all_configs.keys() if i18n(k) == category), list(all_configs.keys())[0])126    models = all_configs.get(eng_cat, {})127    choices = []128    favorite_models = []129    non_favorite_models = []130    131    for model in models:132        model_name = f"{model} ⭐" if favorites and model in favorites else model133        if favorites and model in favorites:134            favorite_models.append(model_name)135        else:136            non_favorite_models.append(model_name)137    138    choices = favorite_models + non_favorite_models139    return {"choices": choices}140 141def get_model_categories():142    """Get all model categories including Custom Models if any exist."""143    all_configs = get_all_model_configs_with_custom()144    return list(all_configs.keys())145 146def handle_file_upload(uploaded_file, file_path, is_auto_ensemble=False):147    clear_temp_folder("/tmp", exclude_items=["gradio", "config.json"])148    clear_directory(INPUT_DIR)149    os.makedirs(INPUT_DIR, exist_ok=True)150    clear_directory(INPUT_DIR)151    if uploaded_file:152        target_path = save_uploaded_file(uploaded_file, is_input=True)153        return target_path, target_path154    elif file_path and os.path.exists(file_path):155        target_path = os.path.join(INPUT_DIR, os.path.basename(file_path))156        shutil.copy(file_path, target_path)157        return target_path, target_path158    return None, None159 160    if torch.cuda.is_available():161        torch.cuda.empty_cache()162 163def clear_directory(directory):164    """Verilen dizindeki tüm dosyaları siler."""165    files = glob.glob(os.path.join(directory, '*'))166    for f in files:167        try:168            os.remove(f)169        except Exception as e:170            print(i18n("file_deletion_error").format(f, e))171 172def clear_temp_folder(folder_path, exclude_items=None):173    """Dizinin içeriğini güvenli bir şekilde temizler ve belirtilen öğeleri korur."""174    try:175        if not os.path.exists(folder_path):176            print(i18n("directory_not_exist_warning").format(folder_path))177            return False178        if not os.path.isdir(folder_path):179            print(i18n("not_a_directory_warning").format(folder_path))180            return False181        exclude_items = exclude_items or []182        for item_name in os.listdir(folder_path):183            item_path = os.path.join(folder_path, item_name)184            if item_name in exclude_items:185                continue186            try:187                if os.path.isfile(item_path) or os.path.islink(item_path):188                    os.unlink(item_path)189                elif os.path.isdir(item_path):190                    shutil.rmtree(item_path)191            except Exception as e:192                print(i18n("item_deletion_error").format(item_path, e))193        return True194    except Exception as e:195        print(i18n("critical_error").format(e))196        return False197 198def clear_old_output():199    old_output_folder = os.path.join(BASE_DIR, 'old_output')200    try:201        if not os.path.exists(old_output_folder):202            return i18n("old_output_not_exist")203        shutil.rmtree(old_output_folder)204        os.makedirs(old_output_folder, exist_ok=True)205        return i18n("old_outputs_cleared")206    except Exception as e:207        return i18n("error").format(e)208 209def shorten_filename(filename, max_length=30):210    """Dosya adını belirtilen maksimum uzunluğa kısaltır."""211    base, ext = os.path.splitext(filename)212    if len(base) <= max_length:213        return filename214    return base[:15] + "..." + base[-10:] + ext215 216def clean_filename(title):217    """Dosya adından özel karakterleri kaldırır."""218    return re.sub(r'[^\w\-_\. ]', '', title).strip()219 220def sanitize_filename(filename):221    base, ext = os.path.splitext(filename)222    base = re.sub(r'\.+', '_', base)223    base = re.sub(r'[#<>:"/\\|?*]', '_', base)224    base = re.sub(r'\s+', '_', base)225    base = re.sub(r'_+', '_', base)226    base = base.strip('_')227    return f"{base}{ext}"228 229def convert_to_wav(file_path):230    """Ses dosyasını WAV formatına dönüştürür."""231    original_filename = os.path.basename(file_path)232    filename, ext = os.path.splitext(original_filename)233    if ext.lower() == '.wav':234        return file_path235    wav_output = os.path.join(ENSEMBLE_DIR, f"{filename}.wav")236    try:237        command = [238            'ffmpeg', '-y', '-i', file_path,239            '-acodec', 'pcm_s16le', '-ar', '44100', wav_output240        ]241        subprocess.run(command, check=True, capture_output=True)242        return wav_output243    except subprocess.CalledProcessError as e:244        print(i18n("ffmpeg_error").format(e.returncode, e.stderr.decode()))245        return None246 247def generate_random_port():248    """Rastgele bir port numarası oluşturur."""249    return random.randint(1000, 9000)250 251def save_segment(audio, sr, path):252    """253    Save audio segment to a file.254    255    Args:256        audio (np.ndarray): Audio data.257        sr (int): Sample rate.258        path (str): Output file path.259    """260    sf.write(path, audio, sr)261 262def run_matchering(reference_path, target_path, output_path, passes=1, bit_depth=24):263    """264    Run Matchering to master the target audio using the reference audio.265    266    Args:267        reference_path (str): Path to the reference audio (clear segment).268        target_path (str): Path to the target audio to be mastered.269        output_path (str): Path for the mastered output.270        passes (int): Number of Matchering passes (1-4).271        bit_depth (int): Output bit depth (16 or 24).272    273    Returns:274        str: Path to the mastered output file.275    """276    # Ensure inputs are WAV files277    ref_audio, sr = librosa.load(reference_path, sr=44100, mono=False)278    tgt_audio, sr = librosa.load(target_path, sr=44100, mono=False)279    280    # Save temporary WAV files281    temp_ref = os.path.join(tempfile.gettempdir(), "matchering_ref.wav")282    temp_tgt = os.path.join(tempfile.gettempdir(), "matchering_tgt.wav")283    save_segment(ref_audio.T if ref_audio.ndim > 1 else ref_audio, sr, temp_ref)284    save_segment(tgt_audio.T if tgt_audio.ndim > 1 else tgt_audio, sr, temp_tgt)285    286    # Configure Matchering with default settings287    config = mg.Config()  # No parameters, use defaults288    289    # Select bit depth for output290    result_format = mg.pcm24 if bit_depth == 24 else mg.pcm16291    292    # Run Matchering for multiple passes293    current_tgt = temp_tgt294    for i in range(passes):295        temp_out = os.path.join(tempfile.gettempdir(), f"matchering_out_pass_{i}.wav")296        mg.process(297            reference=temp_ref,298            target=current_tgt,299            results=[result_format(temp_out)],  # Bit depth control300            config=config301        )302        current_tgt = temp_out303    304    # Move final output to desired path305    shutil.move(current_tgt, output_path)306    307    # Clean up temporary files308    for temp_file in [temp_ref, temp_tgt] + [os.path.join(tempfile.gettempdir(), f"matchering_out_pass_{i}.wav") for i in range(passes-1)]:309        if os.path.exists(temp_file):310            os.remove(temp_file)311    312    return output_path     313 314def find_clear_segment(audio_path, segment_duration=15, sr=44100):315    """316    Find the clearest (high-energy, low-noise) segment in an audio file.317    318    Args:319        audio_path (str): Path to the original audio file.320        segment_duration (float): Duration of the segment to extract (in seconds).321        sr (int): Sample rate for loading audio.322    323    Returns:324        tuple: (start_time, end_time, segment_audio) of the clearest segment.325    """326    # Load audio327    audio, sr = librosa.load(audio_path, sr=sr, mono=True)328    329    # Compute RMS energy in windows330    window_size = int(5 * sr)  # 5-second windows331    hop_length = window_size // 2332    rms = librosa.feature.rms(y=audio, frame_length=window_size, hop_length=hop_length)[0]333    334    # Compute spectral flatness for noise detection335    flatness = librosa.feature.spectral_flatness(y=audio, n_fft=window_size, hop_length=hop_length)[0]336    337    # Combine metrics: high RMS and low flatness indicate clear, high-energy segments338    score = rms / (flatness + 1e-6)  # Avoid division by zero339    340    # Find peaks in the score341    peaks, _ = find_peaks(score, height=np.mean(score), distance=5)342    if len(peaks) == 0:343        # Fallback: Use the middle of the track344        peak_idx = len(score) // 2345    else:346        peak_idx = peaks[np.argmax(score[peaks])]347    348    # Calculate start and end times349    start_sample = peak_idx * hop_length350    end_sample = start_sample + int(segment_duration * sr)351    352    # Ensure the segment fits within the audio353    if end_sample > len(audio):354        end_sample = len(audio)355        start_sample = max(0, end_sample - int(segment_duration * sr))356    357    start_time = start_sample / sr358    end_time = end_sample / sr359    segment_audio = audio[start_sample:end_sample]360    361    return start_time, end_time, segment_audio362 363def update_file_list():364    output_files = glob.glob(os.path.join(OUTPUT_DIR, "*.wav"))365    old_output_files = glob.glob(os.path.join(OLD_OUTPUT_DIR, "*.wav"))366    files = output_files + old_output_files367    return gr.Dropdown(choices=files)368 369def save_uploaded_file(uploaded_file, is_input=False, target_dir=None):370    """Yüklenen dosyayı belirtilen dizine kaydeder."""371    media_extensions = ['.mp3', '.wav', '.flac', '.aac', '.ogg', '.m4a', '.mp4']372    target_dir = target_dir or (INPUT_DIR if is_input else OUTPUT_DIR)373    timestamp_patterns = [374        r'_\d{8}_\d{6}_\d{6}$', r'_\d{14}$', r'_\d{10}$', r'_\d+$'375    ]376 377    if hasattr(uploaded_file, 'name'):378        original_filename = os.path.basename(uploaded_file.name)379    else:380        original_filename = os.path.basename(str(uploaded_file))381 382    if is_input:383        base_filename = original_filename384        for pattern in timestamp_patterns:385            base_filename = re.sub(pattern, '', base_filename)386        for ext in media_extensions:387            base_filename = base_filename.replace(ext, '')388        file_ext = next(389            (ext for ext in media_extensions if original_filename.lower().endswith(ext)),390            '.wav'391        )392        clean_filename = f"{base_filename.strip('_- ')}{file_ext}"393    else:394        clean_filename = original_filename395 396    target_path = os.path.join(target_dir, clean_filename)397    os.makedirs(target_dir, exist_ok=True)398 399    if os.path.exists(target_path):400        os.remove(target_path)401 402    if hasattr(uploaded_file, 'read'):403        with open(target_path, "wb") as f:404            f.write(uploaded_file.read())405    else:406        shutil.copy(uploaded_file, target_path)407 408    print(i18n("file_saved_successfully").format(os.path.basename(target_path)))409    return target_path410 411def move_old_files(output_folder):412    """Eski dosyaları old_output dizinine taşır."""413    os.makedirs(OLD_OUTPUT_DIR, exist_ok=True)414    for filename in os.listdir(output_folder):415        file_path = os.path.join(output_folder, filename)416        if os.path.isfile(file_path):417            new_filename = f"{os.path.splitext(filename)[0]}_old{os.path.splitext(filename)[1]}"418            new_file_path = os.path.join(OLD_OUTPUT_DIR, new_filename)419            shutil.move(file_path, new_file_path)420