Files
InkFlow/backend/scripts/delta_alternation.py
colgora ba1813c583 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
2026-06-21 21:32:31 +02:00

62 lines
2.3 KiB
Python

"""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"])