Lucy-TTS/F5: Skripte + Batches versionieren, schwere Assets ignoriert
- pocket_server.py (Produktions-TTS mit Stimmen-Waechter), text_norm, Bench-/Diag-Skripte - lucy-f5: f5_server/f5_test/bench_dml (DirectML-Experiment, Phase C/D offen) - .gitignore: venvs/Modelle/Audio/Logs der beiden Ordner + box_recon/gemma_swap-Scratch Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Fix: (1) 'vorne abgeschnitten' = Modell startet mid-Phonem -> Lead-Wort voranstellen,
|
||||
in flow generieren, dann in der STILLE-LÜCKE davor schneiden (voller Onset bleibt).
|
||||
(2) 'zu laut' = RMS-Normalisierung auf Zielpegel statt Peak 0.95."""
|
||||
import os, numpy as np, soundfile as sf, librosa
|
||||
from pocket_tts import TTSModel
|
||||
from faster_whisper import WhisperModel
|
||||
|
||||
BASE = r"F:\Coding Stuff\mission-control-2\client\lucy-tts"
|
||||
OUT = r"C:\Users\TobisPC\Desktop\lucy_front_loud"; os.makedirs(OUT, exist_ok=True)
|
||||
src, _ = librosa.load(os.path.join(BASE, "ref.mp3"), sr=24000, mono=True)
|
||||
srt, _ = librosa.effects.trim(src, top_db=30); ref = os.path.join(BASE, "ref.wav")
|
||||
sf.write(ref, srt[:int(18*24000)], 24000)
|
||||
|
||||
SENT = {
|
||||
"kurz": "Hallo Commander, ich höre dich.",
|
||||
"lang": "Natürlich kümmere ich mich darum, Commander. Ich starte den Dienst neu, prüfe die Protokolle und melde mich, sobald alles wieder läuft.",
|
||||
}
|
||||
LEAD = "Tja. " # Wegwerf-Lead -> erzeugt natürlichen Onset fürs echte erste Wort
|
||||
|
||||
def drop_tail_blip(a, sr):
|
||||
for _ in range(3):
|
||||
iv = librosa.effects.split(a, top_db=35)
|
||||
if len(iv) < 2: break
|
||||
srms = float(np.median([np.sqrt(np.mean(a[s:e]**2)) for s,e in iv[:-1]]))
|
||||
s,e = iv[-1]; dur=(e-s)/sr; rms=float(np.sqrt(np.mean(a[s:e]**2))); gap=(s-iv[-2][1])/sr
|
||||
if gap>0.35 and rms<0.30*srms and dur<0.35: a = a[:iv[-2][1]]
|
||||
else: break
|
||||
return a
|
||||
|
||||
def crop_lead(a, sr):
|
||||
"""Schneide in der Lücke NACH dem Lead-Wort -> echtes 1. Wort voll erhalten."""
|
||||
iv = librosa.effects.split(a, top_db=35)
|
||||
if len(iv) >= 2:
|
||||
# Ende des 1. (Lead-)Segments + kleiner Sicherheitsabstand in die Lücke
|
||||
cut = iv[0][1] + int(0.03*sr)
|
||||
nxt = iv[1][0]
|
||||
cut = min(cut, max(iv[0][1], nxt - int(0.04*sr))) # mind. 40ms Stille vor echtem Wort lassen
|
||||
a = a[cut:]
|
||||
return a
|
||||
|
||||
def finalize(a, sr, target_rms, peak_cap=0.9, front_trim=False):
|
||||
a = np.asarray(a, dtype=np.float32).reshape(-1)
|
||||
if front_trim:
|
||||
yt,_ = librosa.effects.trim(a, top_db=45); a = yt if yt.size else a
|
||||
else:
|
||||
# nur HINTEN trimmen (vorne unangetastet lassen)
|
||||
rev,_ = librosa.effects.trim(a[::-1], top_db=45); a = rev[::-1] if rev.size else a
|
||||
# RMS-Normalisierung auf Zielpegel
|
||||
rms = float(np.sqrt(np.mean(a**2))) or 1e-9
|
||||
a = a * (target_rms / rms)
|
||||
peak = float(np.max(np.abs(a)))
|
||||
if peak > peak_cap: a = a * (peak_cap / peak) # Sicherheits-Clamp
|
||||
fi = min(int(0.008*sr), a.size//2)
|
||||
if fi>0:
|
||||
a[:fi]*=np.linspace(0.,1.,fi,dtype=np.float32); a[-fi:]*=np.linspace(1.,0.,fi,dtype=np.float32)
|
||||
pad = np.zeros(int(0.08*sr), dtype=np.float32)
|
||||
return np.concatenate([pad, a, pad])
|
||||
|
||||
m = TTSModel.load_model(language="german_24l", lsd_decode_steps=6, temp=0.9)
|
||||
vs = m.get_state_for_audio_prompt(ref); sr = m.sample_rate
|
||||
w = WhisperModel("small", device="cpu", compute_type="int8")
|
||||
|
||||
for name, text in SENT.items():
|
||||
# Onset-Fix-Roh: mit Lead generieren, Tail-Blip weg, Lead wegschneiden
|
||||
raw = m.generate_audio(vs, LEAD + text, frames_after_eos=4)
|
||||
raw = raw.numpy() if hasattr(raw,"numpy") else np.asarray(raw)
|
||||
raw = np.asarray(raw, dtype=np.float32).reshape(-1)
|
||||
raw = drop_tail_blip(raw, sr)
|
||||
cropped = crop_lead(raw, sr)
|
||||
for tr in [0.12, 0.09, 0.06]:
|
||||
out = finalize(cropped, sr, tr, front_trim=False)
|
||||
sf.write(os.path.join(OUT, f"{name}_onset_rms{int(tr*100):02d}.wav"), out, sr)
|
||||
# Whisper-Check: Output darf NICHT mit Lead beginnen
|
||||
sf.write(os.path.join(OUT, f"_chk_{name}.wav"), finalize(cropped, sr, 0.09), sr)
|
||||
seg,_ = w.transcribe(os.path.join(OUT, f"_chk_{name}.wav"), language="de", beam_size=5)
|
||||
txt = " ".join(s.text for s in seg).strip()
|
||||
print(f"[{name}] Whisper-Start: {txt[:55]!r}", flush=True)
|
||||
print("FRONT_LOUD_DONE", flush=True)
|
||||
Reference in New Issue
Block a user