Voix : Qwen gardé, nombres en lettres reconnus, Gemini plus court

Vu en production avec Qwen sur GPU : la plupart des phrases basculaient sur
Gemini puis Edge (voix différente d'un sous-titre à l'autre).
- Vérification de lecture : les nombres en lettres (« trente », « vingt-cinq
  pour cent ») et leur signe (« plus », « moins ») sont comparés comme les
  chiffres écrits par Whisper, au lieu de passer pour des mots sautés.
- Prises emballées (14 s pour 34 caractères) écartées d'office ; côté
  qwen-tts, max_new_tokens borne la durée à « caractères / 6 + 3 » s.
- Le moteur choisi est gardé tant qu'il lit au moins 70 % du texte
  (avec avertissement), au lieu de 85 % ; 3 tentatives Qwen.
- Gemini : délai d'attente 45 s au lieu de 120 s, erreurs journalisées en repr.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Upu97wMmsRkBoj6iVM4rH6
This commit is contained in:
Claude committed 2026-09-29 12:01:42 +00:00
1 parent 90a9b6be4d
commit 338546662f
7 files changed
+93 -17

No files matched your search

+37 -8
View File
@@ -9,6 +9,7 @@ pas rogner une syllabe.
import asyncio
import difflib
import re
from dataclasses import dataclass, field
from pathlib import Path
@@ -80,19 +81,47 @@ UNITS = {
} # fmt: skip
# Nombres écrits en lettres : Whisper les écrit en chiffres (« trente » → « 30 »).
# « un »/« une » n'y sont pas : ce sont le plus souvent des articles.
NUMBER_WORDS = {
"zero", "deux", "trois", "quatre", "cinq", "six", "sept", "huit", "neuf", "dix",
"onze", "douze", "treize", "quatorze", "quinze", "seize", "vingt", "vingts",
"trente", "quarante", "cinquante", "soixante", "septante", "octante", "nonante",
"cent", "cents", "mille", "million", "millions", "milliard", "milliards",
} # fmt: skip
# Signe avant un nombre : « +30 » est lu « plus trente »
SIGNS = {"plus", "moins"}
# Nombres composés, écrits avec traits d'union puis normalisés (« trentecinq »)
_COMPOUND_NUMBER = re.compile(
"^(?:" + "|".join(sorted(NUMBER_WORDS | {"et", "un", "une"}, key=len, reverse=True)) + ")+$"
)
def _is_number(token: str) -> bool:
if any(c.isdigit() for c in token) or token in NUMBER_WORDS:
return True
return token not in {"un", "une", "et"} and bool(_COMPOUND_NUMBER.match(token))
def comparable_tokens(words: list[str]) -> list[str]:
"""Mots normalisés, sans les nombres ni l'unité qui les suit : Whisper écrit
un prix ou une mesure à sa façon (« 11 € 99 », « 11,99 euros », « 40 cm »),
ce qui passait pour des mots sautés et relançait la génération."""
"""Mots normalisés, sans les nombres (en chiffres ou en lettres), leur signe
ni l'unité qui les suit : Whisper écrit un prix ou une mesure à sa façon
(« 11 € 99 », « 11,99 euros », « 40 cm », « 30 » pour « trente »), ce qui
passait pour des mots sautés et relançait la génération."""
normalized = [t for t in (normalize(w) for w in words) if t]
tokens: list[str] = []
after_number = False
for word in words:
token = normalize(word)
if not token:
continue
if any(c.isdigit() for c in token):
for index, token in enumerate(normalized):
if _is_number(token):
after_number = True
continue
following = normalized[index + 1] if index + 1 < len(normalized) else ""
if token in SIGNS and _is_number(following):
continue
if after_number and token == "pour" and following in {"cent", "cents"}:
continue # « trente pour cent » ↔ « 30 % »
if after_number and token in UNITS:
continue
after_number = False