diff --git a/kazeia_central/voice/enroll.py b/kazeia_central/voice/enroll.py index 4a68014..23458aa 100644 --- a/kazeia_central/voice/enroll.py +++ b/kazeia_central/voice/enroll.py @@ -27,12 +27,24 @@ class EnrollmentError(RuntimeError): def prepare_wav(in_path: str, out_path: str, target_s: float = OV_TARGET_S) -> dict: import librosa, soundfile as sf + cap = min(target_s, _HARD_MAX_S) y, _ = librosa.load(in_path, sr=_PREP_SR, mono=True) orig_s = len(y) / _PREP_SR yt, _ = librosa.effects.trim(y, top_db=30) if yt.size == 0: yt = y - yt = yt[: int(min(target_s, _HARD_MAX_S) * _PREP_SR)] + # §7 FIX : couper sur une FRONTIÈRE DE SILENCE, jamais à `cap` brut (= en plein mot, + # ce qui faisait recracher le dernier mot tronqué en tête de chaque génération). + # Garder les segments non-silencieux jusqu'à ~cap, finir à la fin du dernier segment. + end = 0 + for _s, e in librosa.effects.split(yt, top_db=30): + if e / _PREP_SR <= cap: + end = e + else: + break + if end == 0: # un seul long segment → repli au cap dur + end = min(len(yt), int(cap * _PREP_SR)) + yt = yt[:end] final_s = len(yt) / _PREP_SR sf.write(out_path, yt, _PREP_SR, subtype="PCM_16") return {"orig_s": round(orig_s, 1), "final_s": round(final_s, 1), "too_short": final_s < _MIN_WARN_S} @@ -81,6 +93,12 @@ def enroll(wav_path: str, text: str, out_path: str) -> dict: if C != 8: raise EnrollmentError(f"attendu 8 codebooks, eu {C}") ref_text = (getattr(vcp, "ref_text", "") or text or "").strip() + # §7 FIX (ceinture+bretelles) : tronquer à la dernière phrase complète (. ! ?), pour + # qu'un éventuel mot tronqué de fin de réf ne soit jamais recraché en tête de synthèse. + import re + _ends = list(re.finditer(r"[.!?]", ref_text)) + if _ends: + ref_text = ref_text[: _ends[-1].end()].strip() txt = ref_text.encode("utf-8") os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True) with open(out_path, "wb") as f: