fix(voice): §7 — coupe-silence + troncature phrase (anti mot parasite)

Le cap dur à 16s coupait la réf en plein mot ; l'auto-ASR transcrivait le bout tronqué
(ex. 'présenter') et OmniVoice le recrachait en tête de chaque génération.
- prepare_wav : couper sur une frontière de silence (librosa.effects.split), jamais à
  cap brut → clip finit en silence, mots entiers.
- enroll : tronquer ref_text à la dernière phrase complète (. ! ?).
Validé sur le consentement réel de richard : 24.4s→15.7s, ref_text finit '…des oiseaux.'
(plus de mot parasite). NB : voix enrôlées avec l'ancien cap = à ré-enrôler.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
alf 2026-06-23 23:30:45 +02:00
parent 6640cfd79e
commit 0bf399cb79
1 changed files with 19 additions and 1 deletions

View File

@ -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: