""" ENDPOINT /api/predict para recibir trabajos desde tu servidor PHP """ from flask import Flask, request, jsonify import json import time import threading import requests import hashlib import hmac app = Flask(__name__) # Configuración - CAMBIAR ESTE VALOR WEBHOOK_SECRET = "AB7k9mJlVQ8xR5vL3wE1sT26Y4oI0pZ9c2V26Bn5M8k7" # EL MISMO que pusiste en webhook.php JOBS = {} # Almacenar estado de trabajos en memoria def create_webhook_signature(payload, secret): """Crear firma HMAC para webhook""" return 'sha256=' + hmac.new( secret.encode(), payload.encode(), hashlib.sha256 ).hexdigest() def send_webhook(webhook_url, data): """Enviar webhook a tu servidor""" try: payload = json.dumps(data) signature = create_webhook_signature(payload, WEBHOOK_SECRET) headers = { 'Content-Type': 'application/json', 'X-Signature': signature } response = requests.post(webhook_url, data=payload, headers=headers) print(f"Webhook enviado: {response.status_code}") return response.status_code == 200 except Exception as e: print(f"Error enviando webhook: {e}") return False def process_audiobook_background(job_id, text, voice, language, webhook_url): """Procesar audiolibro en background""" try: # Actualizar estado: iniciado JOBS[job_id]['status'] = 'processing' JOBS[job_id]['stage'] = 'preprocessing' JOBS[job_id]['progress'] = 0 # Notificar inicio send_webhook(webhook_url, { 'event_type': 'job_started', 'job_id': job_id, 'estimated_time': len(text) // 10 # Estimación simple }) # Simular progreso (reemplazar con tu lógica real) stages = [ ('preprocessing', 10), ('text_analysis', 25), ('voice_synthesis', 60), ('audio_processing', 85), ('finalizing', 95) ] for stage, progress in stages: JOBS[job_id]['stage'] = stage JOBS[job_id]['progress'] = progress # Enviar progreso send_webhook(webhook_url, { 'event_type': 'job_progress', 'job_id': job_id, 'progress': progress, 'stage': stage }) # Simular tiempo de procesamiento time.sleep(10) # En producción, aquí va tu lógica real # AQUÍ VA TU LÓGICA REAL DE PROCESAMIENTO # audio_file = tu_funcion_de_tts(text, voice, language) # Por ahora, simular archivo de audio audio_url = f"https://tu-space.hf.space/audio/{job_id}.mp3" # Completar trabajo JOBS[job_id]['status'] = 'completed' JOBS[job_id]['progress'] = 100 JOBS[job_id]['audio_url'] = audio_url JOBS[job_id]['duration'] = len(text) // 5 # Estimación de duración # Notificar completación send_webhook(webhook_url, { 'event_type': 'job_completed', 'job_id': job_id, 'audio_url': audio_url, 'duration': JOBS[job_id]['duration'] }) except Exception as e: # Error en procesamiento JOBS[job_id]['status'] = 'failed' JOBS[job_id]['error'] = str(e) # Notificar error send_webhook(webhook_url, { 'event_type': 'job_failed', 'job_id': job_id, 'error': str(e), 'error_code': 'PROCESSING_ERROR' }) @app.route('/api/predict', methods=['POST']) def predict(): """Endpoint principal para recibir trabajos""" try: data = request.get_json() # Validar datos requeridos required_fields = ['job_id', 'text', 'voice', 'language', 'webhook_url'] for field in required_fields: if field not in data: return jsonify({'error': f'Campo requerido: {field}'}), 400 job_id = data['job_id'] text = data['text'] voice = data['voice'] language = data['language'] webhook_url = data['webhook_url'] # Validaciones if len(text) < 10: return jsonify({'error': 'Texto demasiado corto'}), 400 if len(text) > 500000: # 500KB return jsonify({'error': 'Texto demasiado largo'}), 400 # Crear registro del trabajo JOBS[job_id] = { 'job_id': job_id, 'status': 'pending', 'text_length': len(text), 'voice': voice, 'language': language, 'created_at': time.time(), 'progress': 0, 'stage': 'pending' } # Iniciar procesamiento en background thread = threading.Thread( target=process_audiobook_background, args=(job_id, text, voice, language, webhook_url) ) thread.daemon = True thread.start() return jsonify({ 'success': True, 'job_id': job_id, 'status': 'started', 'estimated_time': len(text) // 10, 'message': 'Procesamiento iniciado' }) except Exception as e: return jsonify({'error': str(e)}), 500 @app.route('/api/status/', methods=['GET']) def get_status(job_id): """Obtener estado de un trabajo""" if job_id not in JOBS: return jsonify({'error': 'Trabajo no encontrado'}), 404 job = JOBS[job_id] return jsonify({ 'job_id': job_id, 'status': job['status'], 'progress': job.get('progress', 0), 'stage': job.get('stage', 'pending'), 'created_at': job['created_at'], 'audio_url': job.get('audio_url'), 'duration': job.get('duration'), 'error': job.get('error') }) @app.route('/api/voices', methods=['GET']) def get_voices(): """Obtener lista de voces disponibles""" voices = [ {'id': 'maria', 'name': 'María', 'language': 'es', 'gender': 'female'}, {'id': 'carlos', 'name': 'Carlos', 'language': 'es', 'gender': 'male'}, {'id': 'laura', 'name': 'Laura', 'language': 'es', 'gender': 'female'}, {'id': 'john', 'name': 'John', 'language': 'en', 'gender': 'male'}, {'id': 'sarah', 'name': 'Sarah', 'language': 'en', 'gender': 'female'}, {'id': 'james', 'name': 'James', 'language': 'en', 'gender': 'male'}, {'id': 'marie', 'name': 'Marie', 'language': 'fr', 'gender': 'female'}, {'id': 'pierre', 'name': 'Pierre', 'language': 'fr', 'gender': 'male'}, {'id': 'hans', 'name': 'Hans', 'language': 'de', 'gender': 'male'}, {'id': 'greta', 'name': 'Greta', 'language': 'de', 'gender': 'female'}, {'id': 'marco', 'name': 'Marco', 'language': 'it', 'gender': 'male'}, {'id': 'sofia', 'name': 'Sofia', 'language': 'it', 'gender': 'female'}, ] return jsonify({ 'success': True, 'voices': voices }) @app.route('/health', methods=['GET']) def health_check(): """Health check endpoint""" return jsonify({ 'status': 'healthy', 'active_jobs': len([j for j in JOBS.values() if j['status'] in ['pending', 'processing']]), 'total_jobs': len(JOBS) }) if __name__ == '__main__': app.run(host='0.0.0.0', port=7860)