Voicebank : vraies voix françaises (CML-TTS) + pool anonyme + garde-fou Qwen3

Remplace la voicebank générée par Kokoro (timbre anglais sur français phonémisé
-> accent que Qwen3 clonait) par 41 vraies voix FR issues de CML-TTS (livres
audio studio) : 1 narrateur dédié, 18F/14M nommées, 4F/4M anonymes réservées.

- scripts/import_voices.py : import multi-shards parquet, 1 clip/locuteur (le
  plus propre via levenshtein), genre estimé par F0 (YIN, anti-octave), filtre
  débit de parole (ref_text aligné sur l'audio).
- VoiceEntry.anonymous + assign_voices : les figurants « anonyme (...) » tirent
  dans un pool réservé, jamais mélangé avec les voix nommées ; narrateur dédié
  (fr_narrator remplace fr_f_siwis).
- dedup._anon_attrs : genre/âge déduits du nom anonyme (bon genre de voix).
- tts/qwen3.py : garde-fou anti-dérive (rejette/réessaie les sorties en boucle
  ou coupées en estimant la durée plausible du chunk).

Limite connue : Qwen3 ne sait pas synthétiser les fragments d'1-2 mots (incises,
titres) -> trous ; à traiter (repli Kokoro ou fusion des incises).

Inclut aussi du travail en cours antérieur (refacto backend LLM pluggable
mlx/lmstudio, benchmark, ajustements frontend/API).

Claude-Session: https://claude.ai/code/session_01XSVvcy1mfb4k1xDgib9vVU
This commit is contained in:
2026-06-21 21:32:31 +02:00
parent 141df5f04e
commit ba1813c583
91 changed files with 2558 additions and 442 deletions

View File

@@ -359,6 +359,7 @@ def _build_model_score(model_id: str, per_chapter: list[ChapterScore],
def run_benchmark(slug: str, model_ids: list[str], *,
backend: Optional[str] = None,
chapters: Optional[list[int]] = None,
temperature: Optional[float] = None,
reasoning: Optional[bool] = None,
@@ -414,7 +415,8 @@ def run_benchmark(slug: str, model_ids: list[str], *,
report.models.append(_build_model_score("<cached>", per_chapter, counts, 0.0))
return report
from .gemma import Gemma, _load
from .llm.client import LLM
from .llm.factory import reset_llm_cache
from .segmenter import analyze_chapter
book = load_book(slug)
@@ -435,7 +437,7 @@ def run_benchmark(slug: str, model_ids: list[str], *,
model_err: Optional[str] = None
emit(f"[{mi}/{len(model_ids)}] {model_id} — chargement du modele…")
try:
gemma = Gemma(model_id=model_id)
gemma = LLM(model_id=model_id, backend=backend)
for i in targets:
ch = by_index.get(i)
if ch is None:
@@ -457,7 +459,7 @@ def run_benchmark(slug: str, model_ids: list[str], *,
model_err = f"{type(exc).__name__}: {exc}"
emit(f" ! echec: {model_err[:120]}")
finally:
_load.cache_clear() # libere le modele avant le suivant
reset_llm_cache() # libere le modele avant le suivant
ms = _build_model_score(
model_id, per_chapter, counts, time.perf_counter() - t0)
ms.error = model_err