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