"""Transcription par l'API Gemini — l'audio part chez Google. Contrairement à `mlx` et `faster`, ce moteur N'EST PAS local : chaque dictée est envoyée à `generativelanguage.googleapis.com`. Il est donc toujours enveloppé d'un `TranscripteurAvecRepli` : réseau coupé, clé refusée, quota épuisé ou réponse vide, la dictée repasse par le moteur local au lieu d'être perdue. Aucune dépendance nouvelle : l'appel HTTP passe par `urllib` de la bibliothèque standard, et la clé se lit dans le trousseau macOS (`security`) ou, à défaut, dans la variable d'environnement GEMINI_API_KEY. """ import base64 import io import json import os import subprocess import sys import urllib.error import urllib.request import wave import numpy as np from core import clean_transcript URL_API = "https://generativelanguage.googleapis.com/v1beta/models/{modele}:generateContent" SERVICE_TROUSSEAU = "zonza-gemini" # Borné sous `duree_max_transcription_s` (20 s) : si Gemini ne répond pas à # temps, il doit rester de quoi basculer sur le moteur local avant que la # garde du halo ne tombe. DELAI_RESEAU_S = 12.0 CONSIGNE = ( "Tu es un moteur de dictée. Transcris mot pour mot ce que dit la personne, " "dans la langue parlée ({langue}), avec la ponctuation et les majuscules " "d'un texte écrit. Ne réponds pas, ne résume pas, ne commente pas, " "n'ajoute rien : rends UNIQUEMENT le texte dicté. Si l'enregistrement ne " "contient aucune parole, rends une chaîne vide. Termes techniques à " "orthographier exactement : {amorce}" ) class ErreurGemini(RuntimeError): """Tout échec côté Gemini : clé, réseau, quota, réponse inexploitable.""" def audio_en_wav(audio, sample_rate): """Encode un signal float32 mono dans [-1, 1] en WAV PCM 16 bits.""" pcm = (np.clip(audio, -1.0, 1.0) * 32767).astype("