Files
InkFlow/backend/inkflow/config.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

106 lines
4.0 KiB
Python

"""Configuration centrale d'InkFlow.
Toutes les constantes (chemins, identifiants de modeles MLX, parametres par
defaut) sont regroupees ici pour rester facilement surchargeables via variables
d'environnement.
"""
from __future__ import annotations
import os
from pathlib import Path
# --- Racines du projet -------------------------------------------------------
# config.py est dans backend/inkflow/, la racine projet est donc deux niveaux
# au-dessus de backend/.
BACKEND_DIR = Path(__file__).resolve().parents[1]
PROJECT_ROOT = BACKEND_DIR.parent
def _env_path(var: str, default: Path) -> Path:
return Path(os.environ.get(var, default)).expanduser().resolve()
# Donnees de travail (etat par livre : json, db, wav intermediaires)
DATA_DIR = _env_path("INKFLOW_DATA_DIR", PROJECT_ROOT / "data")
# Sortie finale (1 dossier par livre, 1 mp3 par chapitre)
OUTPUT_DIR = _env_path("INKFLOW_OUTPUT_DIR", PROJECT_ROOT / "output")
# Banque de voix de reference (clips + metadata.json)
VOICEBANK_DIR = _env_path("INKFLOW_VOICEBANK_DIR", PROJECT_ROOT / "voicebank")
# Echantillons fournis
SAMPLES_DIR = PROJECT_ROOT / "samples"
# --- Moteur LLM d'analyse ----------------------------------------------------
# Backend par defaut : "mlx" (mlx-lm, Apple Silicon) ou "lmstudio" (API OpenAI
# locale de LM Studio, sert GGUF *et* MLX charges via sa GUI).
GEMMA_BACKEND = os.environ.get("INKFLOW_GEMMA_BACKEND", "mlx")
# Endpoint OpenAI-compatible de LM Studio (onglet Developer > Start Server).
LMSTUDIO_BASE_URL = os.environ.get(
"INKFLOW_LMSTUDIO_BASE_URL", "http://127.0.0.1:1234/v1"
)
# --- Modeles MLX (HuggingFace mlx-community) ---------------------------------
# Analyse de texte : Gemma via mlx-lm (backend "mlx").
GEMMA_MODEL = os.environ.get(
"INKFLOW_GEMMA_MODEL", "mlx-community/gemma-3-4b-it-4bit"
)
# TTS : Qwen3-TTS (rendu final, clonage) et Kokoro (preview rapide).
QWEN3_TTS_MODEL = os.environ.get(
"INKFLOW_QWEN3_MODEL", "mlx-community/Qwen3-TTS-12Hz-1.7B-Base-8bit"
)
KOKORO_MODEL = os.environ.get(
"INKFLOW_KOKORO_MODEL", "mlx-community/Kokoro-82M-bf16"
)
# --- Parametres TTS ----------------------------------------------------------
DEFAULT_LANGUAGE = os.environ.get("INKFLOW_LANGUAGE", "French")
# Code langue Kokoro (misaki) : 'f' = francais.
KOKORO_LANG_CODE = os.environ.get("INKFLOW_KOKORO_LANG", "f")
# Voix Kokoro par defaut pour les previews / mono-narrateur rapide.
KOKORO_DEFAULT_VOICE = os.environ.get("INKFLOW_KOKORO_VOICE", "ff_siwis")
# Voix Qwen3 par defaut (narrateur) si aucun clip de reference fourni.
QWEN3_DEFAULT_VOICE = os.environ.get("INKFLOW_QWEN3_VOICE", "Chelsie")
# Frequence d'echantillonnage cible pour la concatenation (Hz). Les backends
# renvoient leur propre sr ; postprocess reechantillonne au besoin.
TARGET_SAMPLE_RATE = int(os.environ.get("INKFLOW_SAMPLE_RATE", "24000"))
# Encodage mp3 final.
MP3_BITRATE = os.environ.get("INKFLOW_MP3_BITRATE", "128k")
# Cible de normalisation loudness (LUFS approx via pydub gain).
TARGET_DBFS = float(os.environ.get("INKFLOW_TARGET_DBFS", "-18.0"))
def book_data_dir(book_slug: str) -> Path:
"""Dossier de travail pour un livre (artefacts intermediaires)."""
return DATA_DIR / book_slug
def book_output_dir(book_title: str) -> Path:
"""Dossier de sortie final pour un livre (mp3 par chapitre)."""
return OUTPUT_DIR / book_title
def ensure_dirs() -> None:
for d in (DATA_DIR, OUTPUT_DIR, VOICEBANK_DIR):
d.mkdir(parents=True, exist_ok=True)
def setup_espeak() -> None:
"""Localise libespeak-ng pour phonemizer (requis par Kokoro non-anglais).
phonemizer ne trouve pas toujours la lib installee via brew ; on pointe
explicitement PHONEMIZER_ESPEAK_LIBRARY si la variable n'est pas deja fixee.
"""
if os.environ.get("PHONEMIZER_ESPEAK_LIBRARY"):
return
candidates = [
"/opt/homebrew/lib/libespeak-ng.dylib",
"/usr/local/lib/libespeak-ng.dylib",
"/opt/homebrew/lib/libespeak-ng.1.dylib",
]
for path in candidates:
if os.path.exists(path):
os.environ["PHONEMIZER_ESPEAK_LIBRARY"] = path
return