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:
parent
6640cfd79e
commit
0bf399cb79
|
|
@ -27,12 +27,24 @@ class EnrollmentError(RuntimeError):
|
||||||
|
|
||||||
def prepare_wav(in_path: str, out_path: str, target_s: float = OV_TARGET_S) -> dict:
|
def prepare_wav(in_path: str, out_path: str, target_s: float = OV_TARGET_S) -> dict:
|
||||||
import librosa, soundfile as sf
|
import librosa, soundfile as sf
|
||||||
|
cap = min(target_s, _HARD_MAX_S)
|
||||||
y, _ = librosa.load(in_path, sr=_PREP_SR, mono=True)
|
y, _ = librosa.load(in_path, sr=_PREP_SR, mono=True)
|
||||||
orig_s = len(y) / _PREP_SR
|
orig_s = len(y) / _PREP_SR
|
||||||
yt, _ = librosa.effects.trim(y, top_db=30)
|
yt, _ = librosa.effects.trim(y, top_db=30)
|
||||||
if yt.size == 0:
|
if yt.size == 0:
|
||||||
yt = y
|
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
|
final_s = len(yt) / _PREP_SR
|
||||||
sf.write(out_path, yt, _PREP_SR, subtype="PCM_16")
|
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}
|
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:
|
if C != 8:
|
||||||
raise EnrollmentError(f"attendu 8 codebooks, eu {C}")
|
raise EnrollmentError(f"attendu 8 codebooks, eu {C}")
|
||||||
ref_text = (getattr(vcp, "ref_text", "") or text or "").strip()
|
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")
|
txt = ref_text.encode("utf-8")
|
||||||
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
|
os.makedirs(os.path.dirname(os.path.abspath(out_path)), exist_ok=True)
|
||||||
with open(out_path, "wb") as f:
|
with open(out_path, "wb") as f:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue