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:
@@ -119,6 +119,7 @@ class VoiceEntry(BaseModel):
|
||||
label: Optional[str] = None # libelle lisible
|
||||
ref_audio: Optional[str] = None # chemin du clip (relatif a voicebank/)
|
||||
ref_text: Optional[str] = None # transcription du clip
|
||||
anonymous: bool = False # voix reservee aux figurants "anonyme (...)"
|
||||
|
||||
|
||||
class Voicebank(BaseModel):
|
||||
@@ -127,8 +128,11 @@ class Voicebank(BaseModel):
|
||||
def by_id(self, voice_id: str) -> Optional[VoiceEntry]:
|
||||
return next((e for e in self.entries if e.id == voice_id), None)
|
||||
|
||||
def by_gender(self, gender: str) -> list[VoiceEntry]:
|
||||
return [e for e in self.entries if e.gender == gender]
|
||||
def by_gender(self, gender: str, *, anonymous: Optional[bool] = None) -> list[VoiceEntry]:
|
||||
"""Voix d'un genre. `anonymous=False`/`True` filtre le pool reserve aux
|
||||
figurants ; None ne filtre pas."""
|
||||
return [e for e in self.entries
|
||||
if e.gender == gender and (anonymous is None or e.anonymous == anonymous)]
|
||||
|
||||
|
||||
class PronunciationEntry(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user