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

@@ -58,7 +58,7 @@ render → output/<titre>/NN-....mp3 (pipeline/render.py + audio/postproc
### Deux niveaux de configuration (distinction importante)
- **`config.py`** : constantes figées lues à l'import, surchargeables **uniquement** par variables d'environnement au démarrage (`INKFLOW_*`). Chemins, IDs de modèles MLX par défaut, params audio.
- **`settings.py`** : objet `Settings` **persisté** dans `data/settings.json`, éditable au runtime depuis l'UI. Contient aussi les **prompts système Gemma** (éditables). Le pipeline consulte `get_settings()` au moment de l'exécution. `save_settings()` invalide les caches modèles (`get_backend.cache_clear()`, `gemma._load.cache_clear()`) pour appliquer les changements **sans redémarrage**.
- **`settings.py`** : objet `Settings` **persisté** dans `data/settings.json`, éditable au runtime depuis l'UI. Contient aussi les **prompts système Gemma** (éditables). Le pipeline consulte `get_settings()` au moment de l'exécution. `save_settings()` invalide les caches modèles (`get_backend.cache_clear()`, `analysis.llm.factory.reset_llm_cache()`) pour appliquer les changements **sans redémarrage**.
### Orchestration (UI temps réel)
@@ -83,6 +83,12 @@ Cette logique est **pure et testée** (`tests/test_incises.py`, 30+ cas sans Gem
`tts/base.py` définit `TTSBackend.synthesize(text, VoiceSpec) -> (audio mono float32, sample_rate)` et `VoiceSpec` (preset pour Kokoro, ref_audio/ref_text pour le clonage Qwen3). `tts/factory.get_backend(name)` est `lru_cache` par **nom** (pas par id de modèle — d'où l'invalidation explicite dans settings). `pipeline/render.py` construit des `RenderUnit` (mono ou multi-voix), où `glued_to_prev` réduit le silence pour les incises rattachées à la réplique précédente.
### LLM d'analyse pluggable
`analysis/llm/` suit le **même pattern que le TTS**. La façade `client.LLM` (anciennement `Gemma`) expose `generate`/`generate_json` consommés par tout le pipeline ; elle porte toute la logique agnostique du moteur (calcul des params depuis `Settings`, retrait de la pensée des modèles à raisonnement, extraction JSON tolérante — helpers dans `_text.py`, testés purs dans `tests/test_gemma_reasoning.py`). Sous elle, `base.LLMBackend.complete(messages, ...) -> str` (texte brut) a deux implémentations : **`mlx_backend`** (mlx-lm, défaut) et **`lmstudio_backend`** (API OpenAI locale de LM Studio, sert GGUF *et* MLX). Sélection par nom via `factory.get_llm_backend(backend, model_ref)` (`lru_cache`, `reset_llm_cache()` pour invalider). Backend choisi dans `settings.gemma_backend` (`mlx`/`lmstudio`), surchargeable par `--backend`/`--model` sur les commandes CLI `analyze`/`pronounce`/`cast`/`benchmark`. LM Studio doit tourner avec son serveur local actif (onglet Developer).
**Qui possède la config de génération ?** Les réglages `gemma_temperature`/`gemma_max_tokens`/`gemma_reasoning*` pilotent le backend **MLX** (seule source de config pour `mlx-lm`). Pour **LM Studio**, c'est la config du modèle **dans LM Studio** qui prime : par défaut (`settings.lmstudio_defer_config=True`) le backend **n'impose ni `temperature` ni `max_tokens`** dans la requête — imposer `max_tokens` tronquait la réponse des modèles à raisonnement (pensée non terminée → « aucun JSON »). Le **contexte** se règle aussi côté LM Studio (au chargement : `lms load <m> --context-length N` ou l'UI) — InkFlow ne peut pas le porter, d'où l'erreur « context length » si le modèle est JIT-chargé avec un contexte trop court pour un chapitre. Mettre `lmstudio_defer_config=False` pour réimposer les réglages InkFlow (benchmarks reproductibles). LM Studio sépare déjà la pensée (`reasoning_content`) de la réponse (`content`) : le backend ne renvoie que `content`.
## Fichiers de référence (vérité terrain pour l'attribution)
`data/<slug>/reference/chNN.json` contient des **versions corrigées à la main** de la sortie d'analyse `analysis/chNN.json`**même schéma `ChapterAnalysis`** (`index`, `title`, `segments` avec `type`/`text`/`speaker`/`incises`). Ce sont des **fixtures de vérité terrain** servant à juger la qualité de la segmentation, des incises et de l'attribution des locuteurs : on compare la sortie réelle du pipeline à la référence.