import logging from flask import Flask, render_template, request, jsonify, after_this_request from functools import wraps from io import BytesIO import base64 import subprocess import os import random import string import re import sys # Configuración del registro logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') app = Flask(__name__) # Define el directorio donde se guardan los archivos file_folder = getattr(sys, '_MEIPASS', os.path.abspath(os.path.dirname(__file__))) temp_audio_folder = "/home/app/temp_audio/" model_folder = "/home/app/" piper_binary_path = "/home/app/piper" # Define los nombres asignados a modelos específicos model_names = { "Español México | Claude": "es_MX-claude-14947-epoch-high.onnx", "Español México | Cortana v1": "es_MX-cortana-19669-epoch-high.onnx", "Español México | TheGevy": "es_MX-gevy-10196-epoch-high.onnx", "English United States Example": "en_US-ljspeech-high.onnx" } def filter_text(text): filtered_text = re.sub(r'[^\w\s,.\(\):\u00C0-\u00FF]', '', text) # Filtra caracteres no deseados filtered_text = filtered_text.replace('\n', ' ') # Reemplaza saltos de línea con espacios return filtered_text def convert_text_to_speech(text, model): filtered_text = filter_text(text) random_name = ''.join(random.choices(string.ascii_letters + string.digits, k=8)) + '.wav' output_file = os.path.join(temp_audio_folder, random_name) if os.path.isfile(piper_binary_path) and model in model_names: model_path = os.path.join(model_folder, model_names[model]) if os.path.isfile(model_path): # Construye el comando para ejecutar Piper command = f'echo "{filtered_text}" | "{piper_binary_path}" -m {model_path} -f {output_file}' try: subprocess.run(command, shell=True, check=True) return output_file except subprocess.CalledProcessError as e: logging.error(f"Error al ejecutar el comando: {e}") else: logging.error(f"Modelo '{model}' no encontrado en la ubicación especificada.") else: logging.error(f"No se encontró el binario de Piper en la ubicación especificada o modelo no asignado.") return None # Define una función decoradora para restringir el acceso a la ruta /convert def restrict_access(func): @wraps(func) def wrapper(*args, **kwargs): # Verifica si la solicitud se hizo desde la página index.html referer = request.headers.get("Referer") if referer and referer.endswith("/"): # Permite el acceso a la función si la solicitud proviene de la página index.html return func(*args, **kwargs) else: # Devuelve un mensaje de error o redirecciona a otra página return "Acceso no autorizado", 403 # Código de respuesta HTTP 403 - Forbidden return wrapper @app.route('/') def index(): model_options = list(model_names.keys()) # Registra el contenido de la carpeta actual logging.info("Contents of current folder: %s", os.listdir(file_folder)) return render_template('index.html', model_options=model_options) @app.route('/convert', methods=['POST']) @restrict_access def convert_text(): try: text = request.form['text'] model = request.form['model'] output_file = convert_text_to_speech(text, model) @after_this_request def remove_file(response): try: if output_file and os.path.exists(output_file): os.remove(output_file) logging.info("Audio file deleted: %s", output_file) except Exception as error: logging.error("Error deleting file: %s", error) return response if output_file: with open(output_file, 'rb') as audio_file: audio_content = audio_file.read() audio_base64 = base64.b64encode(audio_content).decode('utf-8') return jsonify({'audio_base64': audio_base64}) else: return jsonify({'error': 'Error al convertir texto a voz'}), 500 # Internal Server Error except Exception as e: logging.error("Error processing request: %s", e) return jsonify({'error': 'Error interno del servidor'}), 500 # Internal Server Error if __name__ == '__main__': logging.info("Se está iniciando la aplicación.") app.run(host='0.0.0.0', port=7860, debug=False)