ebook2audiobook / api /predict.py
Book-Voice's picture
Update api/predict.py
bf6b15c verified
"""
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/<job_id>', 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)