Categoría: Sin categoría

  • Generación de archivo .WAV de audio para entrenamiento de voz TTS

    En nuestra entrada anterior (Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui), exploramos cómo montar un estudio local de locución y síntesis mediante inteligencia artificial utilizando el motor XTTSv2 y contenedores en WSL2.

    Uno de los pilares fundamentales para lograr una clonación zero-shot exitosa —y evitar que la IA invente acentos extraños, balbucee o lea incorrectamente la puntuación— reside en la calidad de la muestra de audio que le entregamos al sistema.

    En este artículo, te comparto la guía definitiva para grabar, procesar y exportar tu archivo referencia.wav utilizando Audacity, optimizado matemáticamente para que el motor neuronal capture los graves y matices de tu voz sin distorsiones.

    1. El secreto de una buena muestra: Duración y Fonética

    Cuando nos enfrentamos a la clonación de voz por primera vez, solemos cometer el error de grabar textos demasiado largos (como páginas enteras o artículos técnicos). Irónicamente, menos es más.

    • Duración recomendada: Entre 6 y 8 segundos.
    • Por qué: Una muestra corta evita que cargues la lectura con entonaciones dramáticas innecesarias, pausas excesivas o variaciones de volumen que confunden al codificador de la IA.

    El Párrafo Fonético Sugerido

    Utiliza este texto exacto para tu grabación. Está diseñado con un ritmo conversacional completamente plano y natural, cubriendo las vocales abiertas, consonantes nasales y oclusivas clave de nuestro idioma:

    “Hola, soy Keivin. Hoy analizaremos cómo la tecnología y la educación transforman nuestra realidad a través de procesos simples.”

    2. Paso a Paso: Grabación y Edición en Audacity

    Abre Audacity en tu equipo y asegúrate de tener tu micrófono bien posicionado (a unos 10-15 cm, ligeramente ladeado para evitar ruidos de respiración directa).

    A. La interpretación vocal

    • Imagina que estás platicando tranquilamente con un colega en una charla de café.
    • Mantén un volumen constante y un tono estrictamente plano y neutral (cero dramatismo de locutor comercial, sin subidas de voz exageradas en los signos de puntuación).
    • Lee la frase de corrido y deja una micro-pausa limpia al terminar.

    B. Limpieza de ruido ambiente (Reducción de ruido)

    1. Selecciona con el cursor un pequeño fragmento de silencio al inicio o final de tu pista (donde solo se capture el ruido estático de fondo de tu habitación).
    2. Ve al menú superior Efecto > Reducción de ruido y haz clic en el botón Obtener perfil de ruido.
    3. Selecciona toda tu grabación presionando Ctrl + A.
    4. Vuelve a Efecto > Reducción de ruido, deja los valores por defecto y haz clic en Aceptar. Esto eliminará cualquier zumbido eléctrico sin afectar tu timbre de voz.

    3. La Exportación Quirúrgica (Requisito Indispensable para XTTSv2)

    Los motores de clonación neuronal son extremadamente estrictos con los metadatos y la tasa de muestreo. Si exportas el archivo con un formato incorrecto, la inferencia fallará, se escuchará robótica o el sistema arrojará errores de decodificación.

    Para cumplir exactamente con los requerimientos técnicos de Coqui XTTSv2, exporta tu archivo siguiendo estos pasos:

    1. Ve a Archivo > Exportar > Exportar como WAV.
    2. Nombra el archivo exactamente como referencia.wav y guárdalo dentro de tu directorio de trabajo del proyecto (por ejemplo, en ~/podcast_ia/).
    3. En la ventana de diálogo de exportación, configura los parámetros de formato de esta manera:
      • Formato: WAV (Microsoft)
      • Codificación: PCM de 16 bits (Signed 16-bit PCM)
      • Canales: Cámbialo obligatoriamente a Mono (1 canal). (Nota técnica: Los modelos de IA procesan el audio en un solo canal; si lo exportas en Estéreo, el desajuste de fases arruinará la clonación).

    Conclusión

    Con tu archivo referencia.wav limpio, grabado en mono a 16 bits y con una duración menor a 8 segundos, habrás construido el insumo perfecto para alimentar el pipeline que vimos en nuestra entrada principal de Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui.

    Prueba colocar este archivo en tu entorno de trabajo y corre tu script de síntesis. ¡Notarás de inmediato cómo la fidelidad de tu voz clonada alcanza un nivel hiperrealista!

  • Generación de texto a voz con XTTSv2 sobre WSL2 con Coqui

    En la era de la nube y los servicios de suscripción costosos, la filosofía del software libre y el desarrollo local nos invita a recuperar el control de nuestras herramientas. Bajo esta premisa , me propuse un reto técnico: montar un estudio de locución y síntesis de voz por inteligencia artificial completamente local, privado y automatizado, sin depender de APIs de pago ni de servidores externos.

    Después de experimentar con diversas configuraciones, hoy comparto la guía definitiva que me funcionó de manera infalible en producción.

    1. El Banco de Pruebas (Especificaciones de Hardware y Entorno)

    Para replicar este entorno con éxito, es fundamental conocer la arquitectura sobre la cual testeamos y validamos este pipeline:

    • Sistema Host: Windows 11.
    • Capa de Virtualización: Subsistema de Windows para Linux (WSL2).
    • Entorno Invitado (Guest): Ubuntu 22.04 LTS (que provee nativamente Python 3.10).
    • Aceleración Gráfica (GPU): Tarjeta gráfica NVIDIA GeForce GTX 1060 con 6 GB de VRAM y controladores actualizados con soporte WDDM.

    Ajuste preventivo de memoria en WSL2

    Para evitar que la IA desborde los recursos de tu equipo durante la inferencia en la GPU, es altamente recomendable limitar la memoria de WSL. Crea un archivo llamado .wslconfig en tu ruta de usuario de Windows (%userprofile%) con estos valores:

    [wsl2]
    memory=6GB
    swap=4GB

    Aplica los cambios abriendo tu terminal en Windows y ejecutando: wsl --shutdown

    2. Preparando el Entorno de Trabajo en Ubuntu (WSL2)

    Inicia tu terminal de Ubuntu 22.04 y actualiza los paquetes base del sistema, asegurando las herramientas de compilación y manipulación de audio:

    sudo apt update && sudo apt upgrade -y
    sudo apt install -y python3-pip python3-venv git ffmpeg

    Crea un directorio exclusivo para tu proyecto y aísla las dependencias en un entorno virtual (venv):

    mkdir ~/podcast_ia
    cd ~/podcast_ia
    python3 -m venv podcast_env

    ¿Cómo iniciar sesión en cada nueva sesión de trabajo?

    Cada vez que enciendas tu computadora o cierres la terminal y quieras volver a trabajar en tus pódcasts, recuerda seguir estos pasos para activar tu entorno virtual:

    1. Entra a la carpeta del proyecto:

    cd ~/podcast_ia

    2. Activa el entorno (env):

    source podcast_env/bin/activate

    (Verás que la terminal antepone (podcast_env) en la línea de comandos).

    3. Una vez terminado tu trabajo, puedes salir del entorno simplemente escribiendo: deactivate.

    3. Instalación de Dependencias de IA (PyTorch y Coqui TTS)

    Dentro de tu entorno virtual activo, instala PyTorch configurado específicamente para el soporte de CUDA (versión 11.8, óptima para la arquitectura de la GTX 1060), seguido por el motor de Coqui y utilidades de audio:

    pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118
    pip install TTS==0.22.0 transformers==4.33.0 pydub

    Nota técnica previa: Para que el motor neuronal capture con absoluta fidelidad los matices de tu voz y evite distorsiones, artefactos o lecturas erróneas de puntuación, es indispensable alimentar el sistema con una muestra de audio perfectamente calibrada. Si deseas profundizar en el proceso de captura, el tiempo de duración ideal y las especificaciones exactas para exportar tu muestra limpia en Audacity, puedes consultar mi guía detallada en el post sobre la Generación de archivo .WAV de audio para entrenamiento de voz TTS.

    4. El Script Básico: Síntesis Lineal con Archivo de Referencia .wav

    Para nuestra primera versión funcional del script, mantendremos las cosas simples. Este programa básico toma un archivo de texto plano (guion.txt) y un archivo de audio con tu propia voz (referencia.wav), aplica el parche de seguridad indispensable para versiones recientes de PyTorch, procesa el texto eliminando los puntos finales problemáticos y une los fragmentos inyectando silencios limpios mediante Pydub.

    Crea un archivo llamado estudio_basico.py y pega el siguiente código:

    """
    =============================================================================
    ESTUDIO BÁSICO DE PÓDCAST IA - CLONACIÓN ZERO-SHOT CON ARCHIVO WAV
    Descripción: Script lineal y directo para generar locuciones a partir de 
                 un guion en texto plano y una muestra de voz personalizada.
    =============================================================================
    """
    
    import os
    import torch
    import re
    from TTS.api import TTS
    from pydub import AudioSegment
    
    # --- 1. PARCHE DE SEGURIDAD PARA PYTORCH 2.6+ ---
    # Permite cargar de forma segura los pesos locales del modelo de Coqui.
    original_load = torch.load
    def parche_load(*args, **kwargs):
        kwargs['weights_only'] = False
        return original_load(*args, **kwargs)
    torch.load = parche_load
    
    os.environ["COQUI_TOS_AGREED"] = "1"
    
    def cargar_guion_simple(ruta_archivo):
        """
        Lee el guion línea por línea, eliminando puntos finales para evitar 
        que la IA verbalice la palabra 'punto'.
        """
        if not os.path.exists(ruta_archivo):
            return None
    
        with open(ruta_archivo, 'r', encoding='utf-8') as f:
            lineas = f.readlines()
    
        parrafos_limpios = []
        for linea in lineas:
            texto = linea.strip()
            if not texto or texto.startswith(('#', '[')):
                continue
                
            # Purgado de caracteres raros y eliminación del punto final
            texto = texto.replace('\xa0', ' ').replace('\t', ' ')
            texto = texto.rstrip('.')
            texto = re.sub(r'\s+', ' ', texto).strip()
            
            if texto:
                parrafos_limpios.append(texto)
                    
        return parrafos_limpios
    
    def main():
        print("\n=== ESTUDIO BÁSICO DE PÓDCAST (LOCAL) ===")
        
        # Detección de hardware (GPU NVIDIA)
        device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"Hardware en uso: {device.upper()}")
        
        # 1. Carga del guion
        archivo_entrada = input("Ingresa el nombre de tu archivo de guion (ej. guion.txt): ").strip()
        parrafos = cargar_guion_simple(archivo_entrada)
        
        if parrafos is None:
            print(f"❌ Error: No se encontró el archivo '{archivo_entrada}'.")
            return
    
        print(f"✅ Guion cargado: {len(parrafos)} bloques listos.")
        
        # 2. Verificación del archivo de voz de referencia
        audio_referencia = "referencia.wav"
        if not os.path.exists(audio_referencia):
            print(f"❌ Error crítico: No se encuentra el archivo '{audio_referencia}' en este directorio.")
            print("Coloca tu muestra de voz en formato .wav (Mono, 22050Hz) antes de continuar.")
            return
    
        # 3. Carga del motor XTTSv2 en la GPU
        print("\nCargando motor XTTSv2 en la GPU...")
        tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
    
        # --- PARÁMETROS DE CALIBRACIÓN ACÚSTICA ---
        CONFIG_VELOCIDAD = 0.85
        CONFIG_TEMPERATURA = 0.65
        CONFIG_PENALIZACION = 2.0
    
        print("\n🚀 Iniciando síntesis de audio...")
        audio_maestro = AudioSegment.empty()
        
        for i, p in enumerate(parrafos):
            print(f"[{i+1}/{len(parrafos)}] Procesando fragmento...")
            temp_file = f"temp_chunk_{i}.wav"
            
            # Inferencia utilizando tu archivo de voz personalizado
            tts.tts_to_file(
                text=p, 
                language="es", 
                speaker_wav=audio_referencia, 
                file_path=temp_file, 
                split_sentences=False,
                speed=CONFIG_VELOCIDAD,
                temperature=CONFIG_TEMPERATURA,
                repetition_penalty=CONFIG_PENALIZACION
            )
            
            # Ensamblaje con pausa natural de medio segundo entre frases
            chunk_audio = AudioSegment.from_wav(temp_file)
            silencio = AudioSegment.silent(duration=500) 
            audio_maestro += chunk_audio + silencio
            os.remove(temp_file)
            
        archivo_final = "episodio_maestro.wav"
        audio_maestro.export(archivo_final, format="wav")
        print(f"\n🎉 ¡Proceso finalizado! Audio guardado con éxito como: '{archivo_final}'")
    
    if __name__ == "__main__":
        main()

    5. Evolucionando al Siguiente Nivel: El Script Avanzado (estudio_voces.py)

    Una vez que dominas la síntesis básica con tu archivo de referencia, es probable que quieras un flujo de trabajo más versátil: poder elegir entre distintas voces oficiales integradas en el modelo, calibrar los parámetros de velocidad y temperatura al vuelo, y nombrar tus episodios personalizados sin el riesgo de perder producciones anteriores por sobrescritura accidental.

    Para lograrlo, creamos estudio_voces.py. Este script interactivo incluye un gestor de nombres seguro y un menú de selección múltiple.

    Crea este archivo en tu directorio de trabajo y pega el código completo:

    """
    =============================================================================
    ESTUDIO AVANZADO DE PÓDCAST IA - SELECTOR DE VOCES Y GESTIÓN INTELIGENTE
    Descripción: Script interactivo para la generación automatizada de locuciones 
                 con opciones de voces oficiales, purga de puntuación y protección 
                 de archivos.
    =============================================================================
    """
    
    import os
    import torch
    import re
    from TTS.api import TTS
    from pydub import AudioSegment
    
    # --- 1. PARCHE DE SEGURIDAD PARA PYTORCH 2.6+ ---
    original_load = torch.load
    def parche_load(*args, **kwargs):
        kwargs['weights_only'] = False
        return original_load(*args, **kwargs)
    torch.load = parche_load
    
    os.environ["COQUI_TOS_AGREED"] = "1"
    
    # --- 2. INGESTA Y LIMPIEZA QUIRÚRGICA DEL GUION ---
    def cargar_guion_por_parrafos(ruta_archivo):
        """
        Lee el guion línea por línea, limpiando caracteres especiales y 
        eliminando puntos finales para evitar la lectura fonética de 'punto'.
        """
        if not os.path.exists(ruta_archivo):
            return None
    
        with open(ruta_archivo, 'r', encoding='utf-8') as f:
            lineas = f.readlines()
    
        parrafos_limpios = []
        for linea in lineas:
            texto = linea.strip()
            if not texto or texto.startswith(('#', '[')):
                continue
                
            texto = texto.replace('\xa0', ' ').replace('\t', ' ')
            texto = re.sub(r'\[.*?\]', '', texto)
            texto = texto.rstrip('.') # Truco antipunto final
            texto = re.sub(r'\.{2,}', ',', texto)
            texto = re.sub(r'\s+', ' ', texto).strip()
            
            if texto:
                parrafos_limpios.append(texto)
                    
        return parrafos_limpios
    
    # --- 3. GESTOR INTELIGENTE DE NOMBRES DE ARCHIVO ---
    def obtener_nombre_archivo_seguro(nombre_base):
        """
        Previene la sobrescritura de archivos agregando un sufijo numérico 
        autoincremental si el nombre de salida ya existe.
        """
        if not nombre_base.endswith(".wav"):
            nombre_base += ".wav"
            
        if not os.path.exists(nombre_base):
            return nombre_base
            
        base, ext = os.path.splitext(nombre_base)
        contador = 1
        while True:
            nuevo_nombre = f"{base}_{contador}{ext}"
            if not os.path.exists(nuevo_nombre):
                return nuevo_nombre
            contador += 1
    
    # --- 4. FLUJO PRINCIPAL AVANZADO ---
    def main():
        print("\n==========================================================")
        print("=== ESTUDIO DE PÓDCAST IA - SELECTOR DE VOZ & AVANZADO ===")
        print("==========================================================\n")
        
        device = "cuda" if torch.cuda.is_available() else "cpu"
        print(f"Hardware detectado: {device.upper()}")
        if device == "cuda":
            print(f"Tarjeta gráfica en uso: {torch.cuda.get_device_name(0)}")
        
        # Paso 1: Carga del texto
        archivo_entrada = input("\n[1/5] Ingresa el nombre de tu archivo de guion (ej. guion.txt): ").strip()
        parrafos = cargar_guion_por_parrafos(archivo_entrada)
        
        if parrafos is None:
            print(f"❌ Error: No se encontró el archivo '{archivo_entrada}'.")
            return
    
        print(f"✅ Guion cargado: {len(parrafos)} bloques purgados y listos.")
        
        # Paso 2: Elección de fuente de voz (Oficiales del sistema vs. Archivo WAV)
        print("\n[2/5] Selecciona la fuente de voz para la locución:")
        print("[1] Voces oficiales del sistema (Luis Moray, Marcos Rudaski, Alma María)")
        print("[2] Usar mi propio archivo de audio de referencia (.wav personalizado)")
        tipo_voz = input("Elige una opción (1 o 2) [Por defecto 1]: ").strip()
        if tipo_voz == "":
            tipo_voz = "1"
    
        speaker_id = None
        speaker_wav_path = None
    
        if tipo_voz == '1':
            print("\n--- Voces Oficiales del Sistema ---")
            print("[A] Luis Moray (Masculino - Predeterminado)")
            print("[B] Marcos Rudaski (Masculino alternativo)")
            print("[C] Alma María (Femenino)")
            eleccion = input("Elige tu locutor (A, B o C) [Por defecto A]: ").strip().upper()
            
            if eleccion == 'B':
                speaker_id = "Marcos Rudaski"
            elif eleccion == 'C':
                speaker_id = "Alma María"
            else:
                speaker_id = "Luis Moray"
            print(f"🎙️ Locutor seleccionado: {speaker_id}")
        else:
            nombre_wav = input("Ingresa el nombre de tu archivo de referencia [Por defecto 'referencia.wav']: ").strip()
            if nombre_wav == "":
                nombre_wav = "referencia.wav"
            if not os.path.exists(nombre_wav):
                print(f"❌ Error: No se encuentra el archivo '{nombre_wav}'.")
                return
            speaker_wav_path = nombre_wav
            print(f"🎙️ Archivo de referencia seleccionado: {speaker_wav_path}")
    
        # Paso 3: Calibración acústica interactiva
        print("\n[3/5] Calibración acústica (Presiona Enter para usar los valores por defecto):")
        vel_input = input(" -> Velocidad [Por defecto 0.85]: ").strip()
        CONFIG_VELOCIDAD = float(vel_input) if vel_input else 0.85
    
        temp_input = input(" -> Temperatura [Por defecto 0.65]: ").strip()
        CONFIG_TEMPERATURA = float(temp_input) if temp_input else 0.65
    
        pen_input = input(" -> Penalización de repetición [Por defecto 2.0]: ").strip()
        CONFIG_PENALIZACION = float(pen_input) if pen_input else 2.0
    
        # Paso 4: Modo de exportación
        print("\n[4/5] ¿Cómo deseas exportar el audio final?")
        print("[1] Un solo archivo maestro continuo (con pausas automáticas)")
        print("[2] Múltiples archivos independientes (un .wav por cada párrafo)")
        opcion_exp = input("Elige una opción (1 o 2) [Por defecto 1]: ").strip()
        if opcion_exp == "":
            opcion_exp = "1"
    
        # Paso 5: Nombramiento inteligente del archivo
        nombre_personalizado = input("\n[5/5] ¿Cómo quieres nombrar tu archivo de salida? [Por defecto 'episodio_maestro.wav']: ").strip()
        if not nombre_personalizado:
            nombre_personalizado = "episodio_maestro.wav"
    
        archivo_final = obtener_nombre_archivo_seguro(nombre_personalizado)
        if archivo_final != nombre_personalizado:
            print(f"ℹ️ El archivo '{nombre_personalizado}' ya existía. Se guardará de forma segura como: '{archivo_final}'")
    
        # Carga del motor en la GPU
        print("\nCargando motor XTTSv2 en la GPU (esto tomará unos segundos)...")
        tts = TTS("tts_models/multilingual/multi-dataset/xtts_v2").to(device)
    
        print(f"\n🚀 Iniciando síntesis por lotes...\n")
    
        if opcion_exp == '1':
            audio_maestro = AudioSegment.empty()
            for i, p in enumerate(parrafos):
                print(f"[{i+1}/{len(parrafos)}] Procesando párrafo: {p[:50]}...")
                temp_file = f"temp_chunk_{i}.wav"
                
                if speaker_id:
                    tts.tts_to_file(
                        text=p, language="es", speaker=speaker_id, file_path=temp_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                else:
                    tts.tts_to_file(
                        text=p, language="es", speaker_wav=speaker_wav_path, file_path=temp_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                
                chunk_audio = AudioSegment.from_wav(temp_file)
                silencio = AudioSegment.silent(duration=500) # Pausa natural de respiración
                audio_maestro += chunk_audio + silencio
                os.remove(temp_file)
                
            audio_maestro.export(archivo_final, format="wav")
            print(f"\n🎉 ¡Proceso finalizado! Tu pódcast maestro está guardado como: '{archivo_final}'")
            
        elif opcion_exp == '2':
            carpeta_salida = os.path.splitext(archivo_final)[0] + "_fragmentos"
            os.makedirs(carpeta_salida, exist_ok=True)
            
            for i, p in enumerate(parrafos):
                print(f"[{i+1}/{len(parrafos)}] Procesando párrafo: {p[:50]}...")
                out_file = f"{carpeta_salida}/bloque_{i+1:03d}.wav"
                
                if speaker_id:
                    tts.tts_to_file(
                        text=p, language="es", speaker=speaker_id, file_path=out_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                else:
                    tts.tts_to_file(
                        text=p, language="es", speaker_wav=speaker_wav_path, file_path=out_file, 
                        split_sentences=False, speed=CONFIG_VELOCIDAD, temperature=CONFIG_TEMPERATURA, repetition_penalty=CONFIG_PENALIZACION
                    )
                
            print(f"\n🎉 ¡Proceso finalizado! Fragmentos independientes guardados en: '{carpeta_salida}/'")
    
    if __name__ == "__main__":
        main()

    Conclusión

    Con esta infraestructura montada en tu equipo, cuentas con un estudio de locución automatizado de nivel profesional. Al correr localmente sobre tu propia GPU, garantizas la privacidad absoluta de tus contenidos, controlas los tiempos de renderizado y eliminas por completo los costos recurrentes de servicios en la nube.

    Aquí demostramos que, combinando código abierto, herramientas de Linux en Windows (me habría gustado haber trabajado nativamente sobre GNU/Linux pero, no podía, ya que hice esto en una laptop prestada) y un procesamiento inteligente de textos, podemos crear soluciones potentes y elegantes. ¡Es momento de exportar tus guiones y comenzar a producir tus propios podcasts!

  • Cómo instalar TextSnatcher en Debian 13/MX Linux con soporte en Español (¡Parcheando el código fuente!)

    Si alguna vez has utilizado Windows, probablemente conozcas la comodidad de usar herramientas OCR (Reconocimiento Óptico de Caracteres) integradas como la de PowerToys (Text Extractor) para copiar texto directamente de imágenes, videos o PDFs bloqueados. En el ecosistema Linux, TextSnatcher es la alternativa perfecta que cumple esta misma función con tan solo arrastrar el ratón.

    Sin embargo, si eres usuario de MX Linux o Debian 13, es muy probable que te hayas topado con un muro frustrante al instalarlo desde Flathub: el idioma español no aparece por ningún lado y la aplicación no reconoce correctamente nuestras palabras, obligándonos a corregir el texto manualmente.

    Hoy te voy a explicar por qué sucede esto y, aplicando la regla de Simplicity First, cómo resolverlo de raíz compilando la aplicación de forma nativa y agregando nosotros mismos la opción faltante en su código fuente.

    El problema: Flatpak y un “olvido” en el código

    El principal inconveniente de instalar TextSnatcher mediante Flatpak es su naturaleza de sandbox. Al estar aislado, el paquete empaquetado a menudo no incluye diccionarios adicionales para ahorrar espacio y, peor aún, no tiene permisos para leer los paquetes de idioma (tessdata) que ya tienes instalados en tu sistema operativo.

    La solución lógica es purgar esa versión e instalar la herramienta de forma nativa desde su código fuente. Sin embargo, al hacerlo, descubrimos algo insólito: el motor OCR (Tesseract) sí detectaba el español, pero el menú desplegable de la aplicación no lo mostraba.

    Analizando el código fuente escrito en Vala, nos dimos cuenta de que el desarrollador sí había programado la lógica del botón para el español, pero olvidó agregarlo al contenedor visual de la interfaz.

    ¡Manos a la obra para solucionarlo!

    Paso 1: Purgar Flathub y limpiar residuos

    Primero, debemos eliminar la versión aislada y cualquier dato residual que pueda causar conflictos o quedarse en la memoria caché de nuestro menú de aplicaciones. Abre tu terminal y ejecuta:

    Bash

    flatpak uninstall --delete-data com.github.rajsolai.textsnatcher
    flatpak uninstall --unused
    

    Paso 2: Instalar dependencias base en Debian 13

    Ahora, dotaremos a nuestro sistema de las herramientas necesarias para compilar el programa y del motor OCR con su respectivo diccionario en español.

    Bash

    sudo apt update
    sudo apt install tesseract-ocr tesseract-ocr-spa scrot git meson valac libgranite-dev libgtk-3-dev libhandy-1-dev libportal-dev ninja-build
    

    Paso 3: Clonar el código fuente

    Descargamos el repositorio oficial de TextSnatcher en nuestro equipo:

    Bash

    git clone https://github.com/RajSolai/TextSnatcher.git
    cd TextSnatcher
    

    Paso 4: El Parche (Agregando el español a la interfaz)

    Aquí viene la magia. Vamos a corregir el pequeño olvido del desarrollador. Utiliza tu editor de texto favorito (por ejemplo, nano o Gedit) para abrir el archivo donde se gestionan los botones de los idiomas.

    Dependiendo de la versión del repositorio, suele estar en src/components/LanguageButton.vala o directamente en src/MainWindow.vala.

    Bash

    nano src/components/LanguageButton.vala
    

    Busca el bloque de código cerca del final del archivo donde verás una lista que dice menu_list.add (eng) ;, seguida de otros idiomas como chi_sim, jpn, fra, etc.

    El botón spa ya está declarado más arriba en el código, así que solo necesitas insertar esta línea dentro de ese bloque para que se muestre en pantalla:

    Vala

            menu_list.add (spa) ;
    

    Guarda los cambios (en nano: Ctrl+O, Enter, y Ctrl+X).

    Paso 5: Compilar e Instalar

    Con el código modificado, procedemos a construir la aplicación.

    Bash

    meson setup build --prefix=/usr
    cd build
    meson compile
    sudo ninja install
    

    Nota para solucionar errores de permisos: Si en algún momento ejecutaste comandos previos como administrador y ahora meson compile te arroja un error de escritura (ej. unable to open [...] for writing), simplemente devuélvele la propiedad de la carpeta a tu usuario con sudo chown -R $USER:$USER /ruta/a/tu/TextSnatcher y vuelve a intentarlo, o compila con sudo meson compile.

    ¡Resultado óptimo!

    ¡Y eso es todo! Ahora puedes buscar TextSnatcher en tu lanzador de aplicaciones de MX Linux. Al abrirlo y hacer clic en el ícono de traductor, verás con orgullo que el idioma Spanish ya está disponible en la lista.

    Al ser una instalación nativa, la herramienta se comunicará perfectamente con Tesseract OCR y tus capturas de texto en español serán rápidas, precisas y sin necesidad de correcciones manuales.

    ¡Espero que este tutorial te haya ahorrado un buen dolor de cabeza! Déjame tus comentarios si te funcionó o si tienes alguna duda con el proceso de compilación.