mirror of
https://github.com/R0m1k3/Socialflow.git
synced 2026-10-23 23:18:12 +02:00
Voix : plus d'accélération sur les SRT, moteur local Qwen3-TTS
SRT : la voix n'est plus jamais accélérée (atempo sans plafond, jusqu'à 2× et plus, rendait la voix incompréhensible). Un morceau plus long que son sous-titre déborde et décale les suivants ; les sous-titres suivent la voix, la vidéo s'allonge et un avertissement signale le décalage. Rythme : style par défaut « neutre », consignes Gemini sans « rythme entraînant », débit Edge des styles dynamique/promo ramené à +0 %/+3 %. Nouveau moteur « qwen » : service qwen-tts (Qwen3-TTS sur CPU, gratuit, français, ton piloté par consigne, clonage de voix depuis qwen-tts/voices). Même vérification Whisper que Gemini, généralisée ; repli qwen → gemini → edge, et une lecture sous 85 % du texte est écartée (mots sautés/faux). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Upu97wMmsRkBoj6iVM4rH6
This commit is contained in:
20 files changed
+579
-98
No files matched your search
@@ -52,13 +52,27 @@ EDGE_VOICES: tuple[Voice, ...] = (
|
||||
Voice("fr-FR-HenriNeural", "Henri", "male"),
|
||||
)
|
||||
|
||||
# Voix prédéfinies de Qwen3-TTS (service local). Les voix clonées
|
||||
# (« clone:nom ») sont listées par le service lui-même.
|
||||
QWEN_VOICES: tuple[Voice, ...] = (
|
||||
Voice("serena", "Serena — douce et chaleureuse", "female"),
|
||||
Voice("vivian", "Vivian — vive et lumineuse", "female"),
|
||||
Voice("sohee", "Sohee — expressive", "female"),
|
||||
Voice("ono_anna", "Anna — espiègle", "female"),
|
||||
Voice("aiden", "Aiden — solaire", "male"),
|
||||
Voice("ryan", "Ryan — dynamique", "male"),
|
||||
Voice("uncle_fu", "Fu — grave et posée", "male"),
|
||||
Voice("dylan", "Dylan — jeune et naturelle", "male"),
|
||||
Voice("eric", "Eric — légèrement voilée", "male"),
|
||||
)
|
||||
|
||||
# Consignes de lecture ajoutées au texte envoyé à Gemini.
|
||||
STYLES: dict[str, tuple[str, str]] = {
|
||||
"neutral": ("Neutre", ""),
|
||||
"dynamic": (
|
||||
"Dynamique",
|
||||
"Lis ce texte en français sur un ton dynamique et enthousiaste, avec un rythme "
|
||||
"entraînant, comme une vidéo courte sur les réseaux sociaux",
|
||||
"Lis ce texte en français sur un ton dynamique et enthousiaste, comme une vidéo "
|
||||
"courte sur les réseaux sociaux, à un débit naturel, sans accélérer, en articulant bien",
|
||||
),
|
||||
"warm": (
|
||||
"Chaleureux",
|
||||
@@ -71,12 +85,25 @@ STYLES: dict[str, tuple[str, str]] = {
|
||||
"promo": (
|
||||
"Promo",
|
||||
"Lis ce texte en français comme une annonce promotionnelle énergique, en "
|
||||
"insistant sur les offres et les prix",
|
||||
"insistant sur les offres et les prix, sans accélérer, en articulant bien",
|
||||
),
|
||||
}
|
||||
|
||||
_GEMINI_BY_ID = {v.id.lower(): v for v in GEMINI_VOICES}
|
||||
_EDGE_BY_ID = {v.id: v for v in EDGE_VOICES}
|
||||
_QWEN_BY_ID = {v.id: v for v in QWEN_VOICES}
|
||||
|
||||
# Consignes de ton pour Qwen (le modèle comprend l'anglais mieux que le français)
|
||||
QWEN_STYLES: dict[str, str] = {
|
||||
"neutral": "Speak natural, native French with clear articulation and a relaxed, natural pace.",
|
||||
"dynamic": "Speak native French in an upbeat, enthusiastic and smiling tone, like a social media video, "
|
||||
"at a natural pace without rushing, clearly articulated.",
|
||||
"warm": "Speak native French in a warm, friendly and caring tone, as if advising a friend, "
|
||||
"at a relaxed pace.",
|
||||
"calm": "Speak native French in a calm, soothing and reassuring tone, slowly and clearly.",
|
||||
"promo": "Speak native French like an energetic promotional announcement, emphasizing offers and prices, "
|
||||
"at a natural pace without rushing, clearly articulated.",
|
||||
}
|
||||
|
||||
|
||||
def resolve_gemini_voice(voice: str | None) -> Voice:
|
||||
@@ -87,7 +114,7 @@ def resolve_gemini_voice(voice: str | None) -> Voice:
|
||||
"""
|
||||
if voice and voice.lower() in _GEMINI_BY_ID:
|
||||
return _GEMINI_BY_ID[voice.lower()]
|
||||
if voice and voice.endswith(("-B", "-D")) or voice == "male":
|
||||
if voice and voice.endswith(("-B", "-D")) or voice_gender(voice) == "male":
|
||||
return _GEMINI_BY_ID["charon"]
|
||||
return _GEMINI_BY_ID["kore"]
|
||||
|
||||
@@ -100,6 +127,8 @@ def voice_gender(voice: str | None) -> str:
|
||||
return _GEMINI_BY_ID[voice.lower()].gender
|
||||
if voice in _EDGE_BY_ID:
|
||||
return _EDGE_BY_ID[voice].gender
|
||||
if voice.lower() in _QWEN_BY_ID:
|
||||
return _QWEN_BY_ID[voice.lower()].gender
|
||||
if voice == "male" or voice.endswith(("-B", "-D")):
|
||||
return "male"
|
||||
return "male" if any(name in voice for name in ("Remy", "Henri", "Paul")) else "female"
|
||||
@@ -113,6 +142,20 @@ def resolve_edge_voice(voice: str | None) -> Voice:
|
||||
return next(v for v in EDGE_VOICES if v.gender == gender)
|
||||
|
||||
|
||||
def resolve_qwen_voice(voice: str | None) -> str:
|
||||
"""Voix Qwen à utiliser : voix clonée ou prédéfinie ; une voix d'un autre
|
||||
moteur est remplacée par une voix Qwen du même genre."""
|
||||
if voice and voice.startswith("clone:"):
|
||||
return voice
|
||||
if voice and voice.lower() in _QWEN_BY_ID:
|
||||
return voice.lower()
|
||||
return "aiden" if voice_gender(voice) == "male" else "serena"
|
||||
|
||||
|
||||
def qwen_instruction(style: str | None) -> str:
|
||||
return QWEN_STYLES.get(style or "neutral", QWEN_STYLES["neutral"])
|
||||
|
||||
|
||||
def edge_fallbacks(primary: Voice) -> list[Voice]:
|
||||
"""Voix françaises de secours du même genre (jamais d'anglais)."""
|
||||
same = [v for v in EDGE_VOICES if v.gender == primary.gender and v != primary]
|
||||
@@ -123,8 +166,11 @@ def style_instruction(style: str | None) -> str:
|
||||
return STYLES.get(style or "neutral", STYLES["neutral"])[1]
|
||||
|
||||
|
||||
def catalog() -> dict:
|
||||
def catalog(qwen_voices: list[dict] | None = None) -> dict:
|
||||
"""Voix par moteur ; `qwen_voices` vient du service Qwen (None s'il ne répond pas)."""
|
||||
return {
|
||||
"qwen": qwen_voices or [v.__dict__ for v in QWEN_VOICES],
|
||||
"qwen_available": qwen_voices is not None,
|
||||
"gemini": [v.__dict__ for v in GEMINI_VOICES],
|
||||
"edge": [v.__dict__ for v in EDGE_VOICES],
|
||||
"styles": [{"id": k, "label": v[0]} for k, v in STYLES.items()],
|
||||
|
||||
Reference in new issue
Block a user