Spaces:
Running
Running
File size: 10,952 Bytes
9e7a5e0 ff30a18 0d7faff ff30a18 6298a92 ff30a18 9e7a5e0 5a4ff48 ff30a18 5cb5b80 ff30a18 5a4ff48 ff30a18 9e7a5e0 ff30a18 5a4ff48 9e7a5e0 6298a92 ff30a18 5a4ff48 ff30a18 5a4ff48 ff30a18 5a4ff48 9e7a5e0 5a4ff48 ff30a18 5a4ff48 9e7a5e0 ff30a18 5a4ff48 9e7a5e0 ff30a18 5a4ff48 6298a92 5a4ff48 6298a92 5a4ff48 6298a92 0d7faff 6298a92 5a4ff48 6298a92 0d7faff 6298a92 5a4ff48 ff30a18 6298a92 5a4ff48 9e7a5e0 5a4ff48 9e7a5e0 6298a92 9e7a5e0 ff30a18 0d7faff 9e7a5e0 6298a92 9e7a5e0 ff30a18 5a4ff48 9e7a5e0 5a4ff48 0d7faff 5a4ff48 9e7a5e0 5a4ff48 9e7a5e0 6298a92 ff30a18 9e7a5e0 29ffa88 6298a92 9e7a5e0 0d7faff ff30a18 6298a92 5a4ff48 0d7faff 5a4ff48 0d7faff 29ffa88 ff30a18 6298a92 5a4ff48 6298a92 0d7faff 9e7a5e0 5a4ff48 0d7faff 5a4ff48 9e7a5e0 5a4ff48 9e7a5e0 5a4ff48 0d7faff 9e7a5e0 0d7faff 5a4ff48 6298a92 5a4ff48 6298a92 0d7faff 6298a92 5a4ff48 29ffa88 6298a92 29ffa88 6298a92 5a4ff48 29ffa88 9e7a5e0 5a4ff48 6298a92 5a4ff48 9e7a5e0 5a4ff48 9e7a5e0 6298a92 0d7faff 9e7a5e0 6298a92 29ffa88 6298a92 ff30a18 6298a92 5a4ff48 29ffa88 6298a92 29ffa88 6298a92 5a4ff48 29ffa88 9e7a5e0 6298a92 9e7a5e0 6298a92 9e7a5e0 5a4ff48 9e7a5e0 6298a92 5a4ff48 9e7a5e0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 |
from flask import Flask, request, jsonify
import torch
import torchaudio
import librosa
import os
import base64
import tempfile
from transformers import AutoProcessor, AutoModelForSpeechSeq2Seq
from huggingface_hub import login
import logging
# Configuration du logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
app = Flask(__name__)
# Configuration du modèle - EXACTEMENT comme dans votre Gradio qui marchait
MODEL_NAME = "Ronaldodev/speech-to-text-fongbe"
HF_TOKEN = os.environ.get("HF_TOKEN")
# Variables globales pour le modèle
model = None
processor = None
model_loaded = False
def load_model():
"""Charger le modèle privé au démarrage - EXACTEMENT votre code qui marchait"""
global model, processor, model_loaded
try:
logger.info("🔄 Chargement du modèle privé...")
if not HF_TOKEN:
raise ValueError("HF_TOKEN non configuré dans les secrets")
login(token=HF_TOKEN)
logger.info("✅ Authentification HF réussie")
# EXACTEMENT comme dans votre Gradio - pas d'optimisation qui casse
model = AutoModelForSpeechSeq2Seq.from_pretrained(MODEL_NAME)
processor = AutoProcessor.from_pretrained(MODEL_NAME)
logger.info("✅ Modèle chargé avec succès!")
model_loaded = True
return True
except Exception as e:
logger.error(f"❌ Erreur chargement: {e}")
model_loaded = False
return False
def transcribe_audio_file(audio_path):
"""Fonction principale de transcription - EXACTEMENT votre code qui marchait"""
if model is None or processor is None:
return {"success": False, "error": "Modèle non chargé. Vérifiez les logs."}
if audio_path is None:
return {"success": False, "error": "Aucun fichier audio fourni"}
try:
logger.info(f"🎵 Traitement audio: {audio_path}")
# EXACTEMENT votre logique qui marchait
try:
waveform, sample_rate = torchaudio.load(audio_path)
logger.info(f"✅ Audio chargé avec torchaudio: {sample_rate}Hz")
except Exception as e:
logger.warning(f"⚠️ Torchaudio échoué, essai librosa: {e}")
waveform, sample_rate = librosa.load(audio_path, sr=None)
waveform = torch.tensor(waveform).unsqueeze(0)
logger.info(f"✅ Audio chargé avec librosa: {sample_rate}Hz")
# EXACTEMENT votre traitement qui marchait
if waveform.shape[0] > 1:
waveform = waveform.mean(dim=0, keepdim=True)
logger.info("🔄 Conversion stéréo → mono")
if sample_rate != 16000:
logger.info(f"🔄 Resampling {sample_rate}Hz → 16000Hz")
resampler = torchaudio.transforms.Resample(sample_rate, 16000)
waveform = resampler(waveform)
# EXACTEMENT votre processing qui marchait
inputs = processor(
waveform.squeeze(),
sampling_rate=16000,
return_tensors="pt"
)
logger.info("🔄 Génération de la transcription...")
with torch.no_grad():
# EXACTEMENT vos paramètres qui marchaient
result = model.generate(
**inputs,
max_length=500,
do_sample=False,
num_beams=1
)
transcription = processor.batch_decode(result, skip_special_tokens=True)[0]
logger.info(f"✅ Transcription réussie: '{transcription}'")
return {
"success": True,
"transcription": transcription.strip(),
"model_name": MODEL_NAME
}
except Exception as e:
error_msg = f"❌ Erreur de transcription: {str(e)}"
logger.error(error_msg)
return {"success": False, "error": error_msg}
# ENDPOINTS FLASK - Juste l'enrobage, le cœur reste intact
@app.route("/", methods=["GET"])
def health_check():
"""Point d'entrée principal de l'API"""
return jsonify({
"status": "OK",
"message": "🎤 API STT Fongbé - Reconnaissance vocale pour la langue Fongbé",
"model_name": MODEL_NAME,
"model_loaded": model_loaded,
"version": "1.0.0"
})
@app.route("/health", methods=["GET"])
def health():
"""Vérification de l'état de santé de l'API"""
return jsonify({
"status": "healthy" if model_loaded else "model_not_loaded",
"model_loaded": model_loaded,
"model_name": MODEL_NAME,
"message": "Modèle chargé et prêt" if model_loaded else "Modèle sera chargé à la première utilisation"
})
@app.route("/load-model", methods=["POST"])
def load_model_endpoint():
"""Charger le modèle manuellement"""
success = load_model()
return jsonify({
"success": success,
"model_loaded": model_loaded,
"message": "Modèle chargé avec succès" if success else "Erreur lors du chargement"
})
@app.route("/transcribe/base64", methods=["POST"])
def transcribe_base64():
"""Transcription audio à partir de données base64"""
try:
# Charger le modèle si pas encore fait
if not model_loaded:
if not load_model():
return jsonify({"success": False, "error": "Impossible de charger le modèle"}), 503
data = request.get_json()
if not data or "audio_base64" not in data:
return jsonify({"success": False, "error": "Paramètre 'audio_base64' requis"}), 400
logger.info("🎵 Transcription via base64...")
audio_base64 = data["audio_base64"].strip()
remove_prefix = data.get("remove_prefix", True)
# Supprimer le préfixe data:audio/... si présent
if remove_prefix and audio_base64.startswith('data:'):
audio_base64 = audio_base64.split(',')[1]
# Décoder le base64
try:
audio_bytes = base64.b64decode(audio_base64)
except Exception as e:
return jsonify({"success": False, "error": f"Données base64 invalides: {str(e)}"}), 400
# Créer un fichier temporaire
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
temp_file.write(audio_bytes)
temp_path = temp_file.name
try:
# Utiliser EXACTEMENT votre fonction qui marchait
result = transcribe_audio_file(temp_path)
return jsonify(result)
finally:
# Nettoyer le fichier temporaire
if os.path.exists(temp_path):
os.unlink(temp_path)
except Exception as e:
logger.error(f"❌ Erreur transcription base64: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/transcribe/file", methods=["POST"])
def transcribe_file():
"""Transcription audio à partir d'un fichier uploadé"""
try:
# Charger le modèle si pas encore fait
if not model_loaded:
if not load_model():
return jsonify({"success": False, "error": "Impossible de charger le modèle"}), 503
# Vérifier qu'un fichier est présent
if 'audio_file' not in request.files:
return jsonify({"success": False, "error": "Aucun fichier 'audio_file' fourni"}), 400
audio_file = request.files['audio_file']
if audio_file.filename == '':
return jsonify({"success": False, "error": "Aucun fichier sélectionné"}), 400
logger.info(f"🎵 Transcription du fichier: {audio_file.filename}")
# Sauvegarder le fichier temporairement
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
audio_file.save(temp_file.name)
temp_path = temp_file.name
try:
# Utiliser EXACTEMENT votre fonction qui marchait
result = transcribe_audio_file(temp_path)
if result["success"]:
result["filename"] = audio_file.filename
return jsonify(result)
finally:
# Nettoyer le fichier temporaire
if os.path.exists(temp_path):
os.unlink(temp_path)
except Exception as e:
logger.error(f"❌ Erreur transcription fichier: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/transcribe/url", methods=["POST"])
def transcribe_url():
"""Transcription audio à partir d'une URL"""
try:
# Charger le modèle si pas encore fait
if not load_model():
return jsonify({"success": False, "error": "Impossible de charger le modèle"}), 503
data = request.get_json()
if not data or "url" not in data:
return jsonify({"success": False, "error": "Paramètre 'url' requis"}), 400
url = data["url"]
logger.info(f"🌐 Téléchargement depuis URL: {url}")
import requests
# Télécharger le fichier
response = requests.get(url, timeout=30)
response.raise_for_status()
# Créer un fichier temporaire
with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_file:
temp_file.write(response.content)
temp_path = temp_file.name
try:
# Utiliser EXACTEMENT votre fonction qui marchait
result = transcribe_audio_file(temp_path)
if result["success"]:
result["url"] = url
return jsonify(result)
finally:
# Nettoyer le fichier temporaire
if os.path.exists(temp_path):
os.unlink(temp_path)
except Exception as e:
logger.error(f"❌ Erreur transcription URL: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@app.route("/test", methods=["GET"])
def test():
"""Endpoint de test simple"""
return jsonify({
"status": "API fonctionnelle",
"message": "Test réussi ✅",
"model_loaded": model_loaded,
"timestamp": "2025-01-04"
})
if __name__ == "__main__":
print("🚀 DÉMARRAGE API STT FONGBÉ - FLASK")
print("=" * 50)
print("🌐 Port: 7860")
print("📖 Endpoints disponibles:")
print(" GET / - Statut de l'API")
print(" GET /health - Santé de l'API")
print(" GET /test - Test simple")
print(" POST /load-model - Charger le modèle")
print(" POST /transcribe/base64 - Transcription base64")
print(" POST /transcribe/file - Transcription fichier")
print(" POST /transcribe/url - Transcription URL")
print("=" * 50)
# Essayer de charger le modèle au démarrage (optionnel)
print("🔄 Tentative de chargement du modèle au démarrage...")
if load_model():
print("✅ Modèle chargé au démarrage")
else:
print("⚠️ Modèle sera chargé à la première utilisation")
app.run(host="0.0.0.0", port=7860, debug=True) |