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:
61
backend/scripts/delta_alternation.py
Normal file
61
backend/scripts/delta_alternation.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Mesure l'effet de la passe d'alternance sur l'attribution (avant/apres).
|
||||
|
||||
Pour chaque modele : charge une fois, analyse le chapitre, intercepte les
|
||||
locuteurs JUSTE avant `_repair_alternation` (etat "avant") puis lit l'etat
|
||||
"apres", et score les deux contre la reference. Isole le gain de la passe
|
||||
deterministe, independamment du cout du modele.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import sys
|
||||
|
||||
from inkflow.analysis import segmenter
|
||||
from inkflow.analysis.benchmark import _load_reference, _score_counts, _counts_to_score
|
||||
from inkflow.analysis.llm.client import LLM
|
||||
from inkflow.analysis.llm.factory import reset_llm_cache
|
||||
from inkflow.epub.parser import load_book, load_chapter_text
|
||||
from inkflow.store import artifacts
|
||||
|
||||
SLUG = "la-colere-de-tiamat"
|
||||
CH = int(__import__("os").environ.get("DELTA_CH", "5"))
|
||||
|
||||
|
||||
def main(model_ids: list[str]) -> None:
|
||||
book = load_book(SLUG)
|
||||
chapter = next(c for c in book.chapters if c.index == CH)
|
||||
ct = load_chapter_text(SLUG, chapter)
|
||||
cast = artifacts.load_cast(SLUG)
|
||||
ref = _load_reference(SLUG, CH)
|
||||
|
||||
orig_repair = segmenter._repair_alternation
|
||||
print(f"{'modele':<40} {'avant':>7} {'apres':>7} {'delta':>7}")
|
||||
for model_id in model_ids:
|
||||
captured: dict[str, list] = {}
|
||||
|
||||
def spy(segments, **kw): # capture l'etat avant reparation
|
||||
captured["before"] = copy.deepcopy(segments)
|
||||
orig_repair(segments, **kw)
|
||||
|
||||
segmenter._repair_alternation = spy
|
||||
try:
|
||||
gemma = LLM(model_id=model_id)
|
||||
analysis, _ = segmenter.analyze_chapter(
|
||||
chapter, ct, gemma, book_chars=list(cast.characters),
|
||||
dedup_gemma=None)
|
||||
finally:
|
||||
segmenter._repair_alternation = orig_repair
|
||||
reset_llm_cache()
|
||||
|
||||
from inkflow.models import ChapterAnalysis
|
||||
before = ChapterAnalysis(index=CH, title=ct.title,
|
||||
segments=captured["before"])
|
||||
s_before = _counts_to_score(CH, _score_counts(ref, before, cast))
|
||||
s_after = _counts_to_score(CH, _score_counts(ref, analysis, cast))
|
||||
b = s_before.speaker_acc_dialogue
|
||||
a = s_after.speaker_acc_dialogue
|
||||
print(f"{model_id:<40} {b:>6.1%} {a:>6.1%} {a - b:>+6.1%}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.argv[1:] or ["mlx-community/gemma-3-4b-it-4bit"])
|
||||
Reference in New Issue
Block a user