Upload audio_enhance.py
Browse files- audio_enhance.py +141 -0
audio_enhance.py
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""audio_enhance.py — Voice cloning audio preprocessor
|
| 2 |
+
=======================================================
|
| 3 |
+
Прилага 3 стъпки преди CODEC encode при клониране на глас:
|
| 4 |
+
1. Noise reduction — премахва фонов шум (spectral gating)
|
| 5 |
+
2. De-essing — намалява съскането (5–10 kHz notch)
|
| 6 |
+
3. Warming — леко усилва ниските средни (200–800 Hz)
|
| 7 |
+
|
| 8 |
+
Всичко работи само с numpy + scipy (без външни зависимости).
|
| 9 |
+
noisereduce е опционален — ако не е инсталиран, се пропуска стъпка 1.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
from scipy import signal as sig
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# ── 1. Noise reduction ────────────────────────────────────────
|
| 17 |
+
def reduce_noise(audio: np.ndarray, sr: int, prop_decrease: float = 0.75) -> np.ndarray:
|
| 18 |
+
"""
|
| 19 |
+
Spectral gating noise reduction.
|
| 20 |
+
prop_decrease: 0.0 = без ефект, 1.0 = пълно заглушаване на шума.
|
| 21 |
+
По-консервативна стойност (0.75) запазва естествеността на гласа.
|
| 22 |
+
"""
|
| 23 |
+
try:
|
| 24 |
+
import noisereduce as nr
|
| 25 |
+
# non-stationary = следи шума динамично (по-добре за записи с вариращ шум)
|
| 26 |
+
reduced = nr.reduce_noise(
|
| 27 |
+
y=audio,
|
| 28 |
+
sr=sr,
|
| 29 |
+
prop_decrease=prop_decrease,
|
| 30 |
+
stationary=False,
|
| 31 |
+
freq_mask_smooth_hz=500,
|
| 32 |
+
time_mask_smooth_ms=50,
|
| 33 |
+
)
|
| 34 |
+
return reduced.astype(np.float32)
|
| 35 |
+
except ImportError:
|
| 36 |
+
# noisereduce не е инсталиран — пропускаме стъпката
|
| 37 |
+
print(" [enhance] noisereduce не е намерен — пропускане на noise reduction")
|
| 38 |
+
return audio
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
# ── 2. De-esser (намаляване на съскане) ──────────────────────
|
| 42 |
+
def de_ess(audio: np.ndarray, sr: int,
|
| 43 |
+
freq_low: float = 5000.0,
|
| 44 |
+
freq_high: float = 10000.0,
|
| 45 |
+
reduction_db: float = 6.0) -> np.ndarray:
|
| 46 |
+
"""
|
| 47 |
+
Намалява съскащите честоти (сибиланс) чрез band-stop EQ.
|
| 48 |
+
freq_low / freq_high: диапазон на съскането в Hz (обикновено 5–10 kHz).
|
| 49 |
+
reduction_db: колко dB да намали (6 dB = наполовина по амплитуда).
|
| 50 |
+
"""
|
| 51 |
+
nyq = sr / 2.0
|
| 52 |
+
low = freq_low / nyq
|
| 53 |
+
high = freq_high / nyq
|
| 54 |
+
|
| 55 |
+
# Клипваме до валиден диапазон
|
| 56 |
+
low = max(0.01, min(low, 0.98))
|
| 57 |
+
high = max(low + 0.01, min(high, 0.99))
|
| 58 |
+
|
| 59 |
+
# Band-stop (notch) филтър
|
| 60 |
+
b, a = sig.butter(2, [low, high], btype='bandstop')
|
| 61 |
+
filtered = sig.filtfilt(b, a, audio).astype(np.float32)
|
| 62 |
+
|
| 63 |
+
# Смесваме — не заместваме изцяло, за да запазим натуралност
|
| 64 |
+
gain = 10 ** (-reduction_db / 20.0) # amplitude gain за подтиснатия обхват
|
| 65 |
+
# Изчисляваме само sibilant band и го добавяме обратно редуциран
|
| 66 |
+
sibilant = audio - filtered # само съскащите честоти
|
| 67 |
+
result = filtered + sibilant * gain # filtered + намален сибилант
|
| 68 |
+
return result.astype(np.float32)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
# ── 3. Warming (топлина на гласа) ────────────────────────────
|
| 72 |
+
def warm_voice(audio: np.ndarray, sr: int,
|
| 73 |
+
freq_low: float = 200.0,
|
| 74 |
+
freq_high: float = 800.0,
|
| 75 |
+
boost_db: float = 2.5) -> np.ndarray:
|
| 76 |
+
"""
|
| 77 |
+
Леко усилва ниско-средните честоти (200–800 Hz) за по-топъл глас.
|
| 78 |
+
boost_db: колко dB усилване (2–3 dB е естествено, над 4 е прекалено).
|
| 79 |
+
"""
|
| 80 |
+
nyq = sr / 2.0
|
| 81 |
+
low = freq_low / nyq
|
| 82 |
+
high = freq_high / nyq
|
| 83 |
+
|
| 84 |
+
low = max(0.01, min(low, 0.98))
|
| 85 |
+
high = max(low + 0.01, min(high, 0.99))
|
| 86 |
+
|
| 87 |
+
# Band-pass — изолираме средните
|
| 88 |
+
b, a = sig.butter(2, [low, high], btype='bandpass')
|
| 89 |
+
warm_band = sig.filtfilt(b, a, audio).astype(np.float32)
|
| 90 |
+
|
| 91 |
+
gain = 10 ** (boost_db / 20.0) - 1.0 # само добавката (gain - 1)
|
| 92 |
+
result = audio + warm_band * gain
|
| 93 |
+
return result.astype(np.float32)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# ── 4. Нормализация ───────────────────────────────────────────
|
| 97 |
+
def normalize(audio: np.ndarray, target_peak: float = 0.95) -> np.ndarray:
|
| 98 |
+
"""Пиково нормализиране — не позволява клипване след EQ."""
|
| 99 |
+
peak = np.max(np.abs(audio))
|
| 100 |
+
if peak > 1e-6:
|
| 101 |
+
audio = audio / peak * target_peak
|
| 102 |
+
return audio.astype(np.float32)
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
# ── 5. Главна функция ─────────────────────────────────────────
|
| 106 |
+
def enhance_voice_for_cloning(
|
| 107 |
+
audio: np.ndarray,
|
| 108 |
+
sr: int,
|
| 109 |
+
do_denoise: bool = True,
|
| 110 |
+
do_deess: bool = True,
|
| 111 |
+
do_warm: bool = True,
|
| 112 |
+
denoise_strength: float = 0.75, # 0.0–1.0
|
| 113 |
+
deess_reduction_db: float = 6.0, # dB намаляване на съскане
|
| 114 |
+
warm_boost_db: float = 2.5, # dB усилване на топлина
|
| 115 |
+
) -> np.ndarray:
|
| 116 |
+
"""
|
| 117 |
+
Пълен pipeline за почистване на референтен глас преди клониране.
|
| 118 |
+
Връща почистено np.float32 аудио.
|
| 119 |
+
"""
|
| 120 |
+
# Осигуряваме float32 mono
|
| 121 |
+
audio = audio.astype(np.float32)
|
| 122 |
+
if audio.ndim > 1:
|
| 123 |
+
audio = audio.mean(axis=0) # stereo → mono
|
| 124 |
+
|
| 125 |
+
print(f" [enhance] Входен сигнал: {len(audio)/sr:.1f}s @ {sr}Hz")
|
| 126 |
+
|
| 127 |
+
if do_denoise:
|
| 128 |
+
print(f" [enhance] Noise reduction (сила={denoise_strength:.0%})...")
|
| 129 |
+
audio = reduce_noise(audio, sr, prop_decrease=denoise_strength)
|
| 130 |
+
|
| 131 |
+
if do_deess:
|
| 132 |
+
print(f" [enhance] De-essing ({deess_reduction_db:.0f}dB)...")
|
| 133 |
+
audio = de_ess(audio, sr, reduction_db=deess_reduction_db)
|
| 134 |
+
|
| 135 |
+
if do_warm:
|
| 136 |
+
print(f" [enhance] Warming (+{warm_boost_db:.1f}dB @ 200–800Hz)...")
|
| 137 |
+
audio = warm_voice(audio, sr, boost_db=warm_boost_db)
|
| 138 |
+
|
| 139 |
+
audio = normalize(audio)
|
| 140 |
+
print(" [enhance] Готово ✓")
|
| 141 |
+
return audio
|