Upload inference.py with huggingface_hub
Browse files- inference.py +170 -0
inference.py
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import argparse
|
| 4 |
+
import torch
|
| 5 |
+
import soundfile as sf
|
| 6 |
+
|
| 7 |
+
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 8 |
+
TTS_ROOT = os.path.abspath(os.path.join(CURRENT_DIR, ".."))
|
| 9 |
+
if TTS_ROOT not in sys.path:
|
| 10 |
+
sys.path.insert(0, TTS_ROOT)
|
| 11 |
+
|
| 12 |
+
# HuggingFace Cache ve Proxy ayarları (Ağ bloklanması / takılmasını önlemek için)
|
| 13 |
+
if "HF_HOME" not in os.environ:
|
| 14 |
+
scratch_hf = "/gpfs/scratch/ehpc540/users/huseyin/.cache/huggingface"
|
| 15 |
+
if os.path.exists(scratch_hf):
|
| 16 |
+
os.environ["HF_HOME"] = scratch_hf
|
| 17 |
+
|
| 18 |
+
if "HTTP_PROXY" not in os.environ and "http_proxy" not in os.environ:
|
| 19 |
+
os.environ["HTTP_PROXY"] = "http://127.0.0.1:9595"
|
| 20 |
+
os.environ["HTTPS_PROXY"] = "http://127.0.0.1:9595"
|
| 21 |
+
os.environ["http_proxy"] = "http://127.0.0.1:9595"
|
| 22 |
+
os.environ["https_proxy"] = "http://127.0.0.1:9595"
|
| 23 |
+
|
| 24 |
+
try:
|
| 25 |
+
from model.model import TTSModel, TTSConfig
|
| 26 |
+
from dataset.tokenizer import TextTokenizer, AudioCodecTokenizer, TTSProcessor
|
| 27 |
+
except (ImportError, ModuleNotFoundError):
|
| 28 |
+
from model import TTSModel, TTSConfig
|
| 29 |
+
from tokenizer import TextTokenizer, AudioCodecTokenizer, TTSProcessor
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
import numpy as np
|
| 33 |
+
|
| 34 |
+
def trim_silence_and_noise(wav: np.ndarray, sr: int = 24000, frame_ms: int = 50, pad_ms: int = 250) -> np.ndarray:
|
| 35 |
+
"""
|
| 36 |
+
Konuşma bittikten sonra autoregressive modelin ürettiği boşluk veya cızırtıyı (statik gürültü)
|
| 37 |
+
kısa süreli RMS enerji analiziyle tespit edip yumuşak bir fade-out ile temizler.
|
| 38 |
+
"""
|
| 39 |
+
frame_len = int(sr * (frame_ms / 1000.0))
|
| 40 |
+
num_frames = len(wav) // frame_len
|
| 41 |
+
if num_frames == 0:
|
| 42 |
+
return wav
|
| 43 |
+
|
| 44 |
+
frames = [wav[i * frame_len : (i + 1) * frame_len] for i in range(num_frames)]
|
| 45 |
+
energies = [np.sqrt(np.mean(f**2)) for f in frames]
|
| 46 |
+
peak_energy = max(energies) if len(energies) > 0 else 0.0
|
| 47 |
+
|
| 48 |
+
if peak_energy < 0.01:
|
| 49 |
+
return wav
|
| 50 |
+
|
| 51 |
+
# Aktif konuşma eşiği: tepe enerjinin %20'si (en az 0.032)
|
| 52 |
+
thresh = max(0.032, peak_energy * 0.20)
|
| 53 |
+
|
| 54 |
+
last_active_frame = 0
|
| 55 |
+
for i in range(num_frames - 1, -1, -1):
|
| 56 |
+
if energies[i] > thresh:
|
| 57 |
+
last_active_frame = i
|
| 58 |
+
break
|
| 59 |
+
|
| 60 |
+
# Konuşmanın doğal sönümlenmesi için 250ms tampon (pad) bırak
|
| 61 |
+
pad_frames = int(pad_ms / frame_ms)
|
| 62 |
+
cut_frame = min(num_frames, last_active_frame + 1 + pad_frames)
|
| 63 |
+
cut_sample = min(len(wav), cut_frame * frame_len)
|
| 64 |
+
|
| 65 |
+
trimmed = wav[:cut_sample].copy()
|
| 66 |
+
# Tıklama/pop sesini önlemek için son 50ms'ye yumuşak fade-out
|
| 67 |
+
fade_len = min(int(sr * 0.05), len(trimmed))
|
| 68 |
+
if fade_len > 0:
|
| 69 |
+
fade_curve = np.linspace(1.0, 0.0, fade_len)
|
| 70 |
+
trimmed[-fade_len:] *= fade_curve
|
| 71 |
+
|
| 72 |
+
cut_sec = (len(wav) - len(trimmed)) / sr
|
| 73 |
+
if cut_sec > 0.3:
|
| 74 |
+
print(f"[Inference] ✂️ Akıllı Kırpıcı: Orijinal {len(wav)/sr:.2f}s -> Temizlenmiş {len(trimmed)/sr:.2f}s ({cut_sec:.2f} saniye cızırtı/statik kesildi)", flush=True)
|
| 75 |
+
|
| 76 |
+
return trimmed
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def main():
|
| 80 |
+
parser = argparse.ArgumentParser(description="TTS Inference and Audio Synthesis Pipeline")
|
| 81 |
+
parser.add_argument("--text", type=str, required=True, help="Seslendirilecek metin")
|
| 82 |
+
parser.add_argument("--instruction", type=str, default=None, help="Ses tasarımı talimatı (Voice Design - Örn: 'Kalın ve tok bir ses')")
|
| 83 |
+
parser.add_argument("--ref_audio", type=str, default=None, help="Klonlanacak referans ses dosyası (Voice Clone - .wav)")
|
| 84 |
+
parser.add_argument("--ref_max_sec", type=float, default=None, help="Referans sesin maksimum süresi (Varsayılan: None - sesin tamamı kullanılır)")
|
| 85 |
+
parser.add_argument("--checkpoint", type=str, default=None, help="Model ağırlık dosyası (.pt)")
|
| 86 |
+
parser.add_argument("--use_qwen_backbone", action="store_true", help="Qwen2.5-0.5B pretrained ağırlıklarını omurgaya yükler")
|
| 87 |
+
parser.add_argument("--output", type=str, default="output.wav", help="Kaydedilecek ses dosyası yolu")
|
| 88 |
+
parser.add_argument("--max_tokens", type=int, default=120, help="Maksimum üretilecek ses karesi (12.5 Hz'de 120 kare ~ 9.6 saniye)")
|
| 89 |
+
parser.add_argument("--temperature", type=float, default=0.6, help="Sampling sıcaklığı (0.6 doğal tonlama ve melodi sağlar)")
|
| 90 |
+
parser.add_argument("--depth_temperature", type=float, default=0.6, help="Akustik katman sıcaklığı (Yumuşak sampling ile robotikliği kırar)")
|
| 91 |
+
parser.add_argument("--top_k", type=int, default=30, help="Top-k sampling parametresi")
|
| 92 |
+
parser.add_argument("--no_trim", action="store_true", help="Cümle sonundaki sessizlik/cızırtı kırpmasını devre dışı bırakır")
|
| 93 |
+
parser.add_argument("--device", type=str, default="cuda" if torch.cuda.is_available() else "cpu")
|
| 94 |
+
args = parser.parse_args()
|
| 95 |
+
|
| 96 |
+
device = torch.device(args.device)
|
| 97 |
+
print(f"[Inference] Cihaz: {device}")
|
| 98 |
+
print(f"[Inference] Metin: '{args.text}'")
|
| 99 |
+
if args.instruction:
|
| 100 |
+
print(f"[Inference] Voice Design Talimatı: '{args.instruction}'")
|
| 101 |
+
if args.ref_audio:
|
| 102 |
+
print(f"[Inference] Voice Clone Referansı: '{args.ref_audio}'")
|
| 103 |
+
|
| 104 |
+
# 1. Model ve Tokenizer Yükleme
|
| 105 |
+
if args.use_qwen_backbone:
|
| 106 |
+
print("[Inference] Qwen2.5-0.5B omurgası ile model konfigüre ediliyor...")
|
| 107 |
+
config = TTSConfig.from_qwen("Qwen/Qwen2.5-0.5B")
|
| 108 |
+
model = TTSModel(config).to(device)
|
| 109 |
+
if not args.checkpoint:
|
| 110 |
+
model.load_qwen_backbone("Qwen/Qwen2.5-0.5B")
|
| 111 |
+
else:
|
| 112 |
+
config = TTSConfig()
|
| 113 |
+
model = TTSModel(config).to(device)
|
| 114 |
+
|
| 115 |
+
if args.checkpoint and os.path.exists(args.checkpoint):
|
| 116 |
+
print(f"[Inference] Checkpoint yükleniyor: {args.checkpoint}", flush=True)
|
| 117 |
+
ckpt = torch.load(args.checkpoint, map_location=device)
|
| 118 |
+
state_dict = ckpt.get("model", ckpt) if isinstance(ckpt, dict) else ckpt
|
| 119 |
+
if any(k.startswith("module.") for k in state_dict.keys()):
|
| 120 |
+
state_dict = {k.replace("module.", ""): v for k, v in state_dict.items()}
|
| 121 |
+
model.load_state_dict(state_dict)
|
| 122 |
+
print("[Inference] Model ağırlıkları başarıyla yüklendi!", flush=True)
|
| 123 |
+
elif not args.use_qwen_backbone:
|
| 124 |
+
print("[Inference] Bilgi: Checkpoint verilmedi, model başlangıç ağırlıklarıyla çalışacak.", flush=True)
|
| 125 |
+
|
| 126 |
+
print("[Inference] 1/4 Metin tokenizer hazırlanıyor...", flush=True)
|
| 127 |
+
text_tokenizer = TextTokenizer()
|
| 128 |
+
print("[Inference] 2/4 Kyutai Mimi ses çözücü (Audio Codec) yükleniyor...", flush=True)
|
| 129 |
+
audio_tokenizer = AudioCodecTokenizer(device=args.device)
|
| 130 |
+
processor = TTSProcessor(text_tokenizer, audio_tokenizer)
|
| 131 |
+
|
| 132 |
+
# 2. Girdileri Hazırla
|
| 133 |
+
print("[Inference] 3/4 Girdiler hazırlanıyor...", flush=True)
|
| 134 |
+
text_ids, ref_codes = processor.prepare_inference_inputs(
|
| 135 |
+
text=args.text,
|
| 136 |
+
instruction=args.instruction,
|
| 137 |
+
ref_audio_path=args.ref_audio,
|
| 138 |
+
max_ref_sec=args.ref_max_sec,
|
| 139 |
+
device=args.device,
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
# 3. Autoregressive Üretim (CB0 + CB1..7)
|
| 143 |
+
print(f"[Inference] 4/4 Ses tokenları üretiliyor (Maksimum {args.max_tokens} kare)...", flush=True)
|
| 144 |
+
generated_codes = model.generate(
|
| 145 |
+
text_ids=text_ids,
|
| 146 |
+
ref_audio_codes=ref_codes,
|
| 147 |
+
max_new_tokens=args.max_tokens,
|
| 148 |
+
temperature=args.temperature,
|
| 149 |
+
depth_temperature=args.depth_temperature,
|
| 150 |
+
top_k=args.top_k,
|
| 151 |
+
)
|
| 152 |
+
print(f"[Inference] Üretilen ses token matrisi boyutu: {generated_codes.shape}", flush=True)
|
| 153 |
+
|
| 154 |
+
# 4. Codec ile Sese Dönüştürme
|
| 155 |
+
print("[Inference] Ses dalgasına (waveform / 24 kHz) dönüştürülüyor...", flush=True)
|
| 156 |
+
wav = audio_tokenizer.decode(generated_codes)
|
| 157 |
+
if isinstance(wav, torch.Tensor):
|
| 158 |
+
wav = wav.squeeze().cpu().numpy()
|
| 159 |
+
|
| 160 |
+
# 5. Otomatik Cızırtı ve Sessizlik Kırpma (VAD / Energy Trimmer)
|
| 161 |
+
if not args.no_trim:
|
| 162 |
+
wav = trim_silence_and_noise(wav, sr=audio_tokenizer.sample_rate)
|
| 163 |
+
|
| 164 |
+
# 6. Dosyaya Yazma
|
| 165 |
+
sf.write(args.output, wav, samplerate=audio_tokenizer.sample_rate)
|
| 166 |
+
print(f"[Inference] 🎉 Başarılı! Temiz ve optimize edilmiş ses kaydedildi -> {args.output}", flush=True)
|
| 167 |
+
|
| 168 |
+
|
| 169 |
+
if __name__ == "__main__":
|
| 170 |
+
main()
|