Refactor SRT to Kokoro synthesis script for improved CLI functionality and compatibility
- Updated `srt_to_kokoro.py` to provide a CLI entrypoint with argument parsing. - Enhanced error handling and logging for better user feedback. - Introduced a compatibility layer for legacy scripts. - Added configuration handling via `config.toml` for endpoint and API key. - Improved documentation and comments for clarity. Enhance PipelineOrchestrator with in-process transcriber fallback - Implemented `InProcessTranscriber` to handle transcription using multiple strategies. - Added support for `srt_only` flag to return translated SRT without TTS synthesis. - Improved error handling and logging for transcriber initialization. Add installation and usage documentation - Created `INSTALLATION.md` for detailed setup instructions for CPU and GPU environments. - Added `USAGE.md` with practical examples for common use cases and command-line options. - Included a script for automated installation and environment setup. Implement SRT burning utility - Added `burn_srt.py` to facilitate embedding SRT subtitles into video files using ffmpeg. - Provided command-line options for style and codec customization. Update project configuration management - Introduced `config.py` to centralize configuration loading from `config.toml`. - Ensured that environment variables are not read to avoid implicit overrides. Enhance package management with `pyproject.toml` - Added `pyproject.toml` for modern packaging and dependency management. - Defined optional dependencies for CPU and TTS support. Add smoke test fixture for SRT - Created `smoke_test.srt` as a sample subtitle file for testing purposes. Update requirements and setup configurations - Revised `requirements.txt` and `setup.cfg` for better dependency management and clarity. - Included installation instructions for editable mode and local TTS support.
This commit is contained in:
parent
1264ae8587
commit
c22767d3d4
5
.gitignore
vendored
5
.gitignore
vendored
@ -127,6 +127,8 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
.venv311/
|
||||
config.toml
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
@ -168,4 +170,5 @@ cython_debug/
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
Thumbs.db
|
||||
output/
|
||||
|
||||
214
DOCS/INSTALLATION.md
Normal file
214
DOCS/INSTALLATION.md
Normal file
@ -0,0 +1,214 @@
|
||||
# Instalación y Quickstart
|
||||
|
||||
Este documento explica las opciones de instalación y comandos recomendados para entornos CPU-only y entornos con GPU. Incluye instrucciones para Coqui TTS (local) y el uso del `config.toml` para Kokoro.
|
||||
|
||||
Requisitos mínimos
|
||||
- Python 3.11 (recomendado para compatibilidad con Coqui TTS y dependencias).
|
||||
- `ffmpeg` y `ffprobe` en PATH.
|
||||
|
||||
Instalación recomendada (CPU-only)
|
||||
|
||||
1) Crear virtualenv y activarlo:
|
||||
|
||||
```bash
|
||||
python3.11 -m venv .venv311
|
||||
source .venv311/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
```
|
||||
|
||||
2) Instalar PyTorch CPU explícito (recomendado):
|
||||
|
||||
```bash
|
||||
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
```
|
||||
|
||||
3) Instalar el resto de dependencias (pinned):
|
||||
|
||||
```bash
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4) (Opcional) instalar dependencias del paquete editables:
|
||||
|
||||
```bash
|
||||
python -m pip install -e .
|
||||
```
|
||||
|
||||
Instalación para GPU
|
||||
|
||||
- Si dispones de GPU y quieres aprovecharla, instala la rueda de PyTorch adecuada para tu versión de CUDA siguiendo las instrucciones oficiales en https://pytorch.org/ y luego instala el resto de dependencias.
|
||||
|
||||
Coqui TTS (local)
|
||||
|
||||
- Coqui TTS permite ejecutar TTS localmente sin depender de un servicio externo.
|
||||
- Ten en cuenta:
|
||||
- Algunos modelos son pesados (centenas de MB). Hay variantes optimizadas para CPU (p.ej. ~326 MB fp32).
|
||||
- La primera ejecución puede descargar modelos; para uso en CI descárgalos y cachea con anticipación.
|
||||
|
||||
Instalación:
|
||||
|
||||
```bash
|
||||
# dentro del entorno virtual
|
||||
python -m pip install -r whisper_project/requirements.txt
|
||||
```
|
||||
|
||||
Uso del `config.toml`
|
||||
|
||||
- Coloca `config.toml` en la raíz con la sección `[kokoro]`. Usa `config.toml.example` como plantilla.
|
||||
- Precedencia por defecto: CLI > `config.toml` > ENV (modo `override-env`).
|
||||
|
||||
Comandos de ejemplo
|
||||
|
||||
- Ejecutar pipeline real con traducción local:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main --video output/dailyrutines/dailyrutines.mp4 --translate-method local
|
||||
```
|
||||
|
||||
- Ejecutar smoke test (fixture corto):
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main --video tests/fixtures/smoke_fixture.mp4 --translate-method none --dry-run
|
||||
```
|
||||
|
||||
- Forzar instalación CPU-only de PyTorch y dependencies en una sola línea:
|
||||
|
||||
```bash
|
||||
python -m pip install --index-url https://download.pytorch.org/whl/cpu -r requirements.txt
|
||||
```
|
||||
|
||||
Pinning y reproducibilidad
|
||||
|
||||
- `requirements.txt` (raíz) y `whisper_project/requirements.txt` contienen pins orientados a CPU. Revisa y actualiza si tu entorno requiere otras variantes.
|
||||
- Para CI reproducible, genera un lockfile con `pip-compile` o fija versiones a través de `pip freeze` en tu pipeline.
|
||||
|
||||
Notas y recomendaciones
|
||||
|
||||
- Para entornos con recursos limitados usa modelos `small` o `base`.
|
||||
- Si necesitas ayuda para crear un Dockerfile o un job de CI (GitHub Actions) para pre-cachear modelos, lo puedo generar.
|
||||
|
||||
## Instalación editable (pip install -e .) usando `pyproject.toml`
|
||||
|
||||
Este repositorio incluye un `pyproject.toml` para soportar instalaciones en modo editable (`pip install -e .`).
|
||||
La instalación editable facilita el desarrollo (cambios en el código se reflejan sin reinstalar).
|
||||
|
||||
Recomendaciones y ejemplos (CPU-first)
|
||||
|
||||
1) Activa tu entorno virtual (ejemplo `.venv311`) y actualiza pip:
|
||||
|
||||
```bash
|
||||
source .venv311/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
```
|
||||
|
||||
2) Instala primero la rueda CPU de PyTorch (recomendado):
|
||||
|
||||
```bash
|
||||
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
```
|
||||
|
||||
3) Instala el paquete en modo editable con el extra `cpu` (y opcionalmente `tts`):
|
||||
|
||||
```bash
|
||||
python -m pip install -e .[cpu]
|
||||
# o para incluir soporte TTS local:
|
||||
python -m pip install -e .[cpu,tts]
|
||||
```
|
||||
|
||||
Notas importantes
|
||||
- `pip install -e .[cpu]` usará el `pyproject.toml` para leer metadatos y extras definidos allí.
|
||||
- Si prefieres instalar PyTorch y otras dependencias con un único comando que fuerce las ruedas CPU, puedes usar:
|
||||
|
||||
```bash
|
||||
python -m pip install --index-url https://download.pytorch.org/whl/cpu -e .[cpu,tts]
|
||||
```
|
||||
|
||||
- Asegúrate de tener `setuptools` y `wheel` actualizados (el `pyproject.toml` ya declara `setuptools` como build-system). Si tienes problemas al instalar en modo editable, ejecuta:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade setuptools wheel
|
||||
```
|
||||
|
||||
- El extra `tts` instala dependencias opcionales para Coqui TTS y reproducción (puede descargar modelos a la primera ejecución).
|
||||
|
||||
- Recuerda no subir `config.toml` con credenciales reales; usa `config.toml.example`.
|
||||
|
||||
|
||||
## Script de instalación automática: `scripts/auto_install.sh`
|
||||
|
||||
Se incluye un script helper en `scripts/auto_install.sh` para automatizar la creación del virtualenv e instalación de dependencias con opciones CPU/GPU y soporte para TTS local o uso de Kokoro remoto.
|
||||
|
||||
Propósito
|
||||
- Simplificar la puesta en marcha del proyecto en entornos de desarrollo.
|
||||
- Permitir elegir instalación CPU-only (recomendado) o GPU, y decidir si instalar dependencias para Coqui TTS local.
|
||||
|
||||
Ubicación
|
||||
- `scripts/auto_install.sh` (hacer ejecutable con `chmod +x scripts/auto_install.sh`).
|
||||
|
||||
Uso (resumen)
|
||||
|
||||
```bash
|
||||
# instalar en venv por defecto (.venv311), CPU y soporte TTS local:
|
||||
./scripts/auto_install.sh --cpu --local-tts
|
||||
|
||||
# instalar en venv personalizado, GPU y usar Kokoro remoto (no instala TTS extra):
|
||||
./scripts/auto_install.sh --venv .venv_gpu --gpu --kokoro
|
||||
```
|
||||
|
||||
Opciones principales
|
||||
- `--venv PATH` : ruta del virtualenv a crear/usar (default: `.venv311`).
|
||||
- `--cpu` : instalar la build CPU de PyTorch (usa índice CPU de PyTorch).
|
||||
- `--gpu` : intentar instalar PyTorch (selección de rueda GPU queda a cargo de pip/usuario si requiere CUDA específica).
|
||||
- `--torch-version V` : opción para forzar una versión concreta de `torch`.
|
||||
- `--local-tts` : instala extras para Coqui TTS (`tts` extra) y soporte de reproducción.
|
||||
- `--kokoro` : indica que usarás Kokoro remoto (no fuerza instalación de TTS local).
|
||||
- `--no-editable` : instala dependencias sin modo editable (usa `requirements.txt`).
|
||||
|
||||
Qué hace internamente
|
||||
- Crea (si no existe) y activa el virtualenv.
|
||||
- Actualiza `pip`, `setuptools` y `wheel`.
|
||||
- Instala `torch` según la opción `--cpu`/`--gpu` y la `--torch-version` si se proporciona.
|
||||
- Instala el paquete en modo editable (`pip install -e .`) y añade extras `cpu` y/o `tts` según flags.
|
||||
- Si existe `config.toml.example` y no hay `config.toml`, copia el ejemplo a `config.toml` para que lo rellenes con tus credenciales.
|
||||
|
||||
Ejemplos
|
||||
|
||||
- Instalación mínima CPU y editable (sin TTS):
|
||||
|
||||
```bash
|
||||
./scripts/auto_install.sh --cpu
|
||||
```
|
||||
|
||||
- Instalación CPU y extras para Coqui TTS local:
|
||||
|
||||
```bash
|
||||
./scripts/auto_install.sh --cpu --local-tts
|
||||
```
|
||||
|
||||
- Instalación editable en un venv personalizado e instalar PyTorch GPU (nota: el script no elige la rueda CUDA exacta por ti):
|
||||
|
||||
```bash
|
||||
./scripts/auto_install.sh --venv .venv_gpu --gpu
|
||||
```
|
||||
|
||||
Notas y recomendaciones
|
||||
- Recomendado: para evitar problemas con ruedas de PyTorch, instala primero la rueda CPU/GPU apropiada siguiendo https://pytorch.org/ y luego ejecuta `--cpu`/`--gpu` sin forzar versión en el script.
|
||||
- Si necesitas reproducibilidad en CI, considera usar el comando combinado que fuerza el índice CPU para todo:
|
||||
|
||||
```bash
|
||||
python -m pip install --index-url https://download.pytorch.org/whl/cpu -e .[cpu,tts]
|
||||
```
|
||||
|
||||
- Si se detectan errores de compilación de dependencias nativas (p.ej. `av`, `soundfile`), instala las dependencias de sistema necesarias (headers de libav, libsndfile, etc.) antes de repetir la instalación.
|
||||
|
||||
- El script no elimina ni borra artefactos; solo prepara el entorno. Para ejecutar la pipeline usa la CLI:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main --video path/to/video.mp4 --translate-method local
|
||||
```
|
||||
|
||||
Soporte y troubleshooting
|
||||
- Si la instalación falla en una dependencia binaria, pega la salida de error y te indico el paquete del sistema que falta (por ejemplo `libsndfile1-dev`, `libavcodec-dev`, etc.).
|
||||
- Si el script no encuentra `python3.11`, intenta `python3` o instala Python 3.11 en el sistema.
|
||||
|
||||
Fin de la documentación del script.
|
||||
160
DOCS/USAGE.md
Normal file
160
DOCS/USAGE.md
Normal file
@ -0,0 +1,160 @@
|
||||
## Casos de uso y ejemplos de ejecución
|
||||
|
||||
Este documento muestra ejemplos de uso reales del proyecto y variantes de
|
||||
ejecución para cubrir los flujos más comunes: extraer sólo el SRT traducido,
|
||||
ejecutar la pipeline completa, modo dry-run, TTS local vs Kokoro, y cómo quemar
|
||||
el `.srt` en un vídeo por separado.
|
||||
|
||||
Prerequisitos
|
||||
- Tener Python 3.11 y el virtualenv activado (ejemplo: `.venv311`).
|
||||
- `ffmpeg` disponible en PATH.
|
||||
- Paquetes instalados en el venv (usar `requirements.txt` o `pip install -e .[cpu,tts]`).
|
||||
|
||||
Rutas/convención
|
||||
- El CLI principal es: `python -m whisper_project.main`.
|
||||
- El script para quemar SRT es: `python -m whisper_project.burn_srt`.
|
||||
- En los ejemplos uso `.venv311/bin/python` para indicar el venv; adáptalo a tu
|
||||
entorno si usas otro nombre de venv.
|
||||
|
||||
1) Extraer y traducir sólo el SRT (modo rápido, sin TTS)
|
||||
|
||||
Descripción: ejecuta la transcripción + traducción y deja como salida el
|
||||
archivo SRT traducido. Útil cuando sólo necesitas subtítulos.
|
||||
|
||||
Comando:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video output/dailyrutines/dailyrutines.mp4 \
|
||||
--translate-method local \
|
||||
--srt-only
|
||||
```
|
||||
|
||||
Salida esperada:
|
||||
- `output/dailyrutines/dailyrutines.translated.srt` (o un SRT en el workdir
|
||||
que se moverá a `output/<basename>/`).
|
||||
|
||||
Notas:
|
||||
- No se ejecuta síntesis (TTS) ni se modifica el vídeo.
|
||||
- Si quieres mantener temporales (chunks) añade `--keep-chunks --keep-temp`.
|
||||
|
||||
2) Pipeline completa (SRT -> TTS -> reemplazar audio -> quemar subtítulos)
|
||||
|
||||
Descripción: flujo completo por defecto (si no usas `--srt-only`).
|
||||
|
||||
Comando:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video input.mp4 \
|
||||
--translate-method local \
|
||||
--kokoro-endpoint https://kokoro.example/api/synthesize \
|
||||
--kokoro-key $KOKORO_KEY \
|
||||
--voice em_alex
|
||||
```
|
||||
|
||||
Salida esperada:
|
||||
- `input.replaced_audio.mp4` (vídeo con audio reemplazado)
|
||||
- `input.replaced_audio.subs.mp4` (vídeo final con subtítulos quemados)
|
||||
- `output/<basename>/` cuando la CLI mueve artefactos al terminar.
|
||||
|
||||
Opciones útiles:
|
||||
- `--mix` si quieres mezclar la pista sintetizada con la original.
|
||||
- `--mix-background-volume 0.1` para controlar volumen de fondo.
|
||||
- `--keep-chunks`/`--keep-temp` para conservar archivos intermedios.
|
||||
|
||||
3) Dry-run (simular pasos sin ejecutar)
|
||||
|
||||
Descripción: imprimirá los pasos planeados sin hacer trabajo pesado.
|
||||
|
||||
Comando:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main --video input.mp4 --dry-run
|
||||
```
|
||||
|
||||
4) Usar TTS local en lugar de Kokoro
|
||||
|
||||
Si tienes Coqui TTS instalado y quieres sintetizar localmente:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video input.mp4 \
|
||||
--translate-method local \
|
||||
--local-tts
|
||||
```
|
||||
|
||||
Notas:
|
||||
- Asegúrate de que en tu `pyproject`/requirements está la dependencia `TTS` y
|
||||
que tu Python es 3.11 (Coqui TTS no siempre es compatible con 3.12+).
|
||||
- Local TTS reduce dependencia de red y suele ser más rápido si el HW es
|
||||
suficiente.
|
||||
|
||||
5) Traducir con Gemini/Argos (servicios externos)
|
||||
|
||||
Ejemplo con Gemini (requiere clave):
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video input.mp4 \
|
||||
--translate-method gemini \
|
||||
--gemini-key $GEMINI_KEY
|
||||
```
|
||||
|
||||
Si falla el adaptador local, el orquestador cae a un wrapper que puede lanzar
|
||||
scripts auxiliares (ver `whisper_project/translate_*`).
|
||||
|
||||
6) Quemar un `.srt` en un vídeo por separado
|
||||
|
||||
Descripción: si ya tienes un `.srt` (p. ej. generado por `--srt-only`) y
|
||||
quieres incrustarlo en el vídeo sin ejecutar todo el pipeline.
|
||||
|
||||
Comando:
|
||||
|
||||
```bash
|
||||
python -m whisper_project.burn_srt \
|
||||
--video input.mp4 \
|
||||
--srt translated.srt \
|
||||
--out input.subbed.mp4 \
|
||||
--style "FontName=Arial,FontSize=24" \
|
||||
--codec libx264
|
||||
```
|
||||
|
||||
Notas:
|
||||
- El filtro `subtitles` de `ffmpeg` normalmente requiere reencodear el vídeo.
|
||||
- Si tu SRT contiene caracteres especiales, el script hace uso de rutas
|
||||
absolutas para evitar problemas.
|
||||
|
||||
7) Encadenar extracción SRT y quemado (ejemplo rápido)
|
||||
|
||||
Extraer SRT traducido y quemarlo en un solo pipeline usando tuberías de shell
|
||||
o pasos secuenciales:
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video input.mp4 --translate-method local --srt-only && \
|
||||
python -m whisper_project.burn_srt \
|
||||
--video input.mp4 --srt output/input/input.translated.srt --out input.subbed.mp4
|
||||
```
|
||||
|
||||
8) Flags de diagnóstico y tiempo de ejecución
|
||||
- `--verbose` para más logging.
|
||||
- Revisar `logs/` (si está configurado) para detalles de errores.
|
||||
|
||||
Problemas comunes y soluciones rápidas
|
||||
- Error: "This tokenizer cannot be instantiated — sentencepiece missing" →
|
||||
instalar `sentencepiece` en el venv: `.venv311/bin/python -m pip install sentencepiece`.
|
||||
- Error: problemas con import paths cuando se ejecutan scripts auxiliares →
|
||||
usar `python -m whisper_project.main` desde la raíz del repo para garantizar
|
||||
que el paquete se importa correctamente.
|
||||
- Si la síntesis via Kokoro tarda o da timeouts, considera `--local-tts` o
|
||||
aumentar timeouts en la configuración del adaptador Kokoro.
|
||||
|
||||
¿Qué artefactos debo buscar?
|
||||
- SRT transcrito: `*.srt` (original y traducido)
|
||||
- WAV sintetizado: `*.dub.wav` o `dub_final.wav` en workdir
|
||||
- Vídeos finales: `*.replaced_audio.mp4`, `*.replaced_audio.subs.mp4`
|
||||
|
||||
¿Quieres que ejecute alguno de estos ejemplos ahora en tu venv `.venv311` y con
|
||||
tu fichero `output/dailyrutines/dailyrutines.mp4`? Puedo ejecutar sólo la
|
||||
extracción SRT traducida (`--srt-only`) y traerte el log + ruta del SRT.
|
||||
97
README.md
97
README.md
@ -1,3 +1,100 @@
|
||||
<!-- README: Quickstart y decisiones CPU/GPU, configuración y ejemplos -->
|
||||
# Whisper Project (pipeline multimedia)
|
||||
|
||||
Resumen rápido
|
||||
----------------
|
||||
Canalización para: extraer audio de vídeo -> transcripción -> (opcional) traducción -> generar SRT -> sintetizar por segmentos (Kokoro o TTS local) -> reemplazar pista de audio -> quemar subtítulos.
|
||||
|
||||
Este repo prioriza instalaciones CPU-first por defecto. En `requirements.txt` y `whisper_project/requirements.txt` encontrarás pins y recomendaciones para instalaciones CPU-only.
|
||||
|
||||
Quick start (recomendado para CPU)
|
||||
---------------------------------
|
||||
1. Crear y activar virtualenv (Python 3.11 recomendado):
|
||||
|
||||
```bash
|
||||
python3.11 -m venv .venv311
|
||||
source .venv311/bin/activate
|
||||
python -m pip install --upgrade pip
|
||||
```
|
||||
|
||||
2. Instalar PyTorch (CPU wheel) explícitamente:
|
||||
|
||||
```bash
|
||||
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
```
|
||||
|
||||
3. Instalar las demás dependencias pinned:
|
||||
|
||||
```bash
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
4. (Opcional) instalar deps internas del paquete editable:
|
||||
|
||||
```bash
|
||||
python -m pip install -e .
|
||||
```
|
||||
|
||||
Nota: con `pyproject.toml` en la raíz ahora puedes instalar el paquete en modo editable usando:
|
||||
|
||||
```bash
|
||||
python -m pip install -e .
|
||||
```
|
||||
esto usará el `pyproject.toml` (TOML) como fuente de metadatos y extras.
|
||||
|
||||
Ejecutar la pipeline (ejemplo)
|
||||
------------------------------
|
||||
Usando `config.toml` en la raíz (si tienes Kokoro):
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main \
|
||||
--video output/dailyrutines/dailyrutines.mp4 \
|
||||
--translate-method local
|
||||
```
|
||||
|
||||
Ejemplo sin traducción (solo SRT->TTS):
|
||||
|
||||
```bash
|
||||
.venv311/bin/python -m whisper_project.main --video input.mp4 --translate-method none
|
||||
```
|
||||
|
||||
Uso de `config.toml`
|
||||
--------------------
|
||||
Coloca un `config.toml` en la raíz con:
|
||||
|
||||
```toml
|
||||
[kokoro]
|
||||
endpoint = "https://kokoro.example/synthesize"
|
||||
api_key = "sk-..."
|
||||
voice = "em_anna"
|
||||
model = "tacotron2"
|
||||
```
|
||||
|
||||
El CLI usa la precedencia: CLI > `config.toml` > ENV (modo `override-env` por defecto).
|
||||
|
||||
Coqui TTS (local) vs Kokoro (remoto)
|
||||
-----------------------------------
|
||||
- Kokoro: cliente HTTP que sintetiza por segmento. Bueno si tienes endpoint estable y quieres offload.
|
||||
- Local Coqui `TTS`: útil si no quieres dependencia de red y tienes CPU suficiente; requiere modelos locales y más espacio.
|
||||
|
||||
Para usar Coqui TTS local, instala `TTS` y dependencias (ya listadas en `whisper_project/requirements.txt`) y ejecuta con la flag `--local-tts`.
|
||||
|
||||
Recomendaciones y troubleshooting
|
||||
--------------------------------
|
||||
- Si ves problemas de memoria/tiempo, reduce el modelo de `faster-whisper` a `small` o `base`.
|
||||
- Para problemas con `torch`, instala explicitamente desde el índice CPU (ver arriba).
|
||||
- Si `ffmpeg` falla, revisa que `ffmpeg` y `ffprobe` estén en `PATH`.
|
||||
|
||||
Más documentación
|
||||
------------------
|
||||
Consulta `DOCS/architecture.md` para la documentación técnica completa, diagramas y guías de pruebas.
|
||||
Para instrucciones de instalación y quickstart (CPU/GPU) revisa `DOCS/INSTALLATION.md`.
|
||||
|
||||
Licencia y contribución
|
||||
-----------------------
|
||||
Revisa `CONTRIBUTING.md` (si existe) y respeta la política de no subir credenciales en `config.toml` — utiliza `config.toml.example` como plantilla.
|
||||
|
||||
Fin del README.
|
||||
# Whisper Pipeline — Documentación
|
||||
|
||||
Bienvenido: esta carpeta contiene la documentación generada para la canalización multimedia (Whisper + Kokoro).
|
||||
|
||||
10
config.toml.example
Normal file
10
config.toml.example
Normal file
@ -0,0 +1,10 @@
|
||||
# Example configuration file for whisper_project
|
||||
# Copy to `config.toml` and fill with your real values. Do NOT commit `config.toml` to VCS.
|
||||
|
||||
[kokoro]
|
||||
# The HTTP endpoint for Kokoro-compatible TTS services
|
||||
endpoint = "https://kokoro.example/api/v1/audio/speech"
|
||||
|
||||
# API key used for Authorization: Bearer <api_key>
|
||||
# Replace the value below with your real key before running the CLI.
|
||||
api_key = "PASTE_YOUR_API_KEY_HERE"
|
||||
42
pyproject.toml
Normal file
42
pyproject.toml
Normal file
@ -0,0 +1,42 @@
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "whisper_project"
|
||||
version = "0.1.0"
|
||||
description = "Canalización multimedia: extracción, transcripción, traducción y TTS (Kokoro/Coqui)."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
authors = [ { name = "Nextream" } ]
|
||||
license = { text = "MIT" }
|
||||
|
||||
dependencies = [
|
||||
"numpy==1.26.4",
|
||||
"ffmpeg-python==0.4.0",
|
||||
"faster-whisper==1.2.0",
|
||||
"transformers==4.34.0",
|
||||
"tokenizers==0.13.3",
|
||||
"sentencepiece==0.1.99",
|
||||
"huggingface-hub==0.16.4",
|
||||
"sacremoses==0.0.53",
|
||||
"ctranslate2==3.18.0",
|
||||
"onnxruntime==1.15.1",
|
||||
"requests==2.31.0",
|
||||
"tqdm==4.66.1",
|
||||
"coloredlogs==15.0.1",
|
||||
"humanfriendly==10.0",
|
||||
"flatbuffers==23.5.26",
|
||||
"av==10.0.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
cpu = [ "torch==2.2.2", "onnxruntime==1.15.1" ]
|
||||
tts = [ "TTS==0.13.0", "soundfile==0.12.1", "librosa==0.10.0.post2", "pyttsx3==2.90" ]
|
||||
dev = [ "pytest", "pre-commit", "black", "ruff" ]
|
||||
|
||||
[project.scripts]
|
||||
whisper_project = "whisper_project.main:main"
|
||||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
61
requirements.txt
Normal file
61
requirements.txt
Normal file
@ -0,0 +1,61 @@
|
||||
# Dependencias de alto nivel para instalar todo lo necesario en el proyecto
|
||||
# Instalar con:
|
||||
# python -m pip install -r requirements.txt
|
||||
|
||||
# IMPORTANT: This requirements file targets CPU-only installations.
|
||||
# For packages that provide GPU vs CPU wheels (notably `torch`), we
|
||||
# recommend installing the CPU builds explicitly. Example recommended
|
||||
# command to install CPU-only PyTorch:
|
||||
#
|
||||
# python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
#
|
||||
# If you prefer to keep everything in one step, run:
|
||||
#
|
||||
# python -m pip install --index-url https://download.pytorch.org/whl/cpu -r requirements.txt
|
||||
|
||||
# Core
|
||||
python-dateutil
|
||||
numpy
|
||||
|
||||
# Audio / multimedia
|
||||
ffmpeg-python
|
||||
av
|
||||
soundfile
|
||||
librosa
|
||||
|
||||
# Transcripción / modelos
|
||||
# Recomendado: instalar la build CPU de torch usando el índice oficial
|
||||
# de PyTorch (ver comentario arriba). No forzamos la URL aquí para
|
||||
# mantener compatibilidad, pero si instalas desde este fichero y no
|
||||
# quieres GPU, usa el comando recomendado.
|
||||
torch==2.2.2
|
||||
# faster-whisper: versión probada en este entorno
|
||||
faster-whisper==1.2.0
|
||||
transformers==4.34.0
|
||||
tokenizers==0.13.3
|
||||
sentencepiece==0.1.99
|
||||
huggingface-hub==0.16.4
|
||||
|
||||
# Traducción
|
||||
sacremoses==0.0.53
|
||||
ctranslate2==3.18.0
|
||||
# ONNX Runtime (CPU). Si necesitas GPU, instala la variante apropiada.
|
||||
onnxruntime==1.15.1
|
||||
|
||||
# TTS (Coqui TTS and fallbacks)
|
||||
TTS==0.13.0
|
||||
pyttsx3==2.90
|
||||
|
||||
# Networking / utils
|
||||
requests==2.31.0
|
||||
tqdm==4.66.1
|
||||
coloredlogs==15.0.1
|
||||
humanfriendly==10.0
|
||||
flatbuffers==23.5.26
|
||||
|
||||
# Notas:
|
||||
# - He fijado versiones compatibles con Python 3.11 y enfoque CPU. Si prefieres
|
||||
# otras versiones o quieres soporte GPU, dime y actualizo los pins.
|
||||
# - Para `torch` en CPU, usa el índice oficial de PyTorch como se muestra arriba.
|
||||
# - `TTS` (Coqui) puede descargar modelos en la primera ejecución; consulta la
|
||||
# documentación de Coqui para descargar modelos offline si lo deseas.
|
||||
119
scripts/auto_install.sh
Normal file
119
scripts/auto_install.sh
Normal file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Auto-install helper for the project.
|
||||
# Usage: scripts/auto_install.sh [--venv PATH] [--cpu|--gpu] [--local-tts] [--kokoro] [--no-editable]
|
||||
# Examples:
|
||||
# ./scripts/auto_install.sh --cpu --local-tts
|
||||
# ./scripts/auto_install.sh --venv .venv311 --gpu --kokoro
|
||||
|
||||
VENV=".venv311"
|
||||
MODE="cpu"
|
||||
USE_LOCAL_TTS="false"
|
||||
USE_KOKORO="false"
|
||||
EDITABLE="true"
|
||||
TORCH_VERSION=""
|
||||
|
||||
print_help(){
|
||||
cat <<'EOF'
|
||||
Usage: auto_install.sh [options]
|
||||
|
||||
Options:
|
||||
--venv PATH Path to virtualenv (default: .venv311)
|
||||
--cpu Install CPU-only PyTorch (default)
|
||||
--gpu Install PyTorch (GPU). You may need to pick the right CUDA wheel manually.
|
||||
--torch-version V Optional torch version to install (e.g. 2.2.2)
|
||||
--local-tts Install Coqui TTS and extras for local TTS
|
||||
--kokoro Assume you'll use Kokoro endpoint (no local TTS extras)
|
||||
--no-editable Do not install in editable mode; install packages normally
|
||||
-h, --help Show this help
|
||||
|
||||
Examples:
|
||||
./scripts/auto_install.sh --cpu --local-tts
|
||||
./scripts/auto_install.sh --venv .venv311 --gpu --kokoro
|
||||
EOF
|
||||
}
|
||||
|
||||
while [[ ${#} -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--venv) VENV="$2"; shift 2;;
|
||||
--cpu) MODE="cpu"; shift;;
|
||||
--gpu) MODE="gpu"; shift;;
|
||||
--torch-version) TORCH_VERSION="$2"; shift 2;;
|
||||
--local-tts) USE_LOCAL_TTS="false"; shift;;
|
||||
--kokoro) USE_KOKORO="true"; shift;;
|
||||
--no-editable) EDITABLE="false"; shift;;
|
||||
-h|--help) print_help; exit 0;;
|
||||
*) echo "Unknown arg: $1"; print_help; exit 2;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo "Installing into venv: ${VENV}"
|
||||
if [[ ! -d "${VENV}" ]]; then
|
||||
echo "Creating virtualenv ${VENV} (Python 3.11 recommended)..."
|
||||
python3.11 -m venv "${VENV}" || python3 -m venv "${VENV}"
|
||||
fi
|
||||
|
||||
echo "Activating virtualenv..."
|
||||
source "${VENV}/bin/activate"
|
||||
|
||||
echo "Upgrading pip, setuptools and wheel..."
|
||||
python -m pip install --upgrade pip setuptools wheel
|
||||
|
||||
install_torch_cpu(){
|
||||
if [[ -n "${TORCH_VERSION}" ]]; then
|
||||
echo "Installing CPU torch ${TORCH_VERSION} from PyTorch CPU index..."
|
||||
python -m pip install "torch==${TORCH_VERSION}" --index-url https://download.pytorch.org/whl/cpu
|
||||
else
|
||||
echo "Installing latest CPU torch from PyTorch CPU index..."
|
||||
python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
fi
|
||||
}
|
||||
|
||||
install_torch_gpu(){
|
||||
if [[ -n "${TORCH_VERSION}" ]]; then
|
||||
echo "Installing torch ${TORCH_VERSION} (GPU) - pip will try to pick a matching wheel"
|
||||
python -m pip install "torch==${TORCH_VERSION}"
|
||||
else
|
||||
echo "Installing torch (GPU) - pip will try to pick a matching wheel. If you need a specific CUDA wheel, install it manually following https://pytorch.org/"
|
||||
python -m pip install torch
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${MODE}" == "cpu" ]]; then
|
||||
install_torch_cpu
|
||||
else
|
||||
install_torch_gpu
|
||||
fi
|
||||
|
||||
EXTRAS=()
|
||||
if [[ "${USE_LOCAL_TTS}" == "true" ]]; then
|
||||
EXTRAS+=(tts)
|
||||
fi
|
||||
if [[ "${MODE}" == "cpu" ]]; then
|
||||
EXTRAS+=(cpu)
|
||||
fi
|
||||
|
||||
if [[ "${EDITABLE}" == "true" ]]; then
|
||||
if [[ ${#EXTRAS[@]} -gt 0 ]]; then
|
||||
IFS=, extras_str="${EXTRAS[*]}"
|
||||
echo "Installing package editable with extras: ${extras_str// /,}"
|
||||
python -m pip install -e .[${extras_str// /,}]
|
||||
else
|
||||
echo "Installing package editable without extras"
|
||||
python -m pip install -e .
|
||||
fi
|
||||
else
|
||||
echo "Installing dependencies from requirements.txt"
|
||||
python -m pip install -r requirements.txt
|
||||
fi
|
||||
|
||||
echo "Optional: if you plan to use Kokoro remote endpoint, copy and edit config.toml.example -> config.toml and fill kokoro values."
|
||||
if [[ -f config.toml.example && ! -f config.toml ]]; then
|
||||
echo "Copying config.toml.example -> config.toml (edit before running)"
|
||||
cp config.toml.example config.toml
|
||||
fi
|
||||
|
||||
echo "Setup complete. Next steps:\n source ${VENV}/bin/activate\n ./${VENV}/bin/python -m whisper_project.main --help"
|
||||
|
||||
echo "Done."
|
||||
11
setup.cfg
Normal file
11
setup.cfg
Normal file
@ -0,0 +1,11 @@
|
||||
[metadata]
|
||||
name = whisper-project
|
||||
version = 0.0.0
|
||||
description = Editable packaging for Whisper project
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
include-package-data = True
|
||||
|
||||
[options.packages.find]
|
||||
where = .
|
||||
7
tests/fixtures/smoke_test.srt
vendored
Normal file
7
tests/fixtures/smoke_test.srt
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
1
|
||||
00:00:00,000 --> 00:00:02,500
|
||||
Hola mundo.
|
||||
|
||||
2
|
||||
00:00:03,000 --> 00:00:07,000
|
||||
Esto es una prueba rápida de síntesis local usando Kokoro.
|
||||
Binary file not shown.
77
whisper_project/burn_srt.py
Normal file
77
whisper_project/burn_srt.py
Normal file
@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Herramienta simple para quemar un archivo .srt en un vídeo usando ffmpeg.
|
||||
|
||||
Uso:
|
||||
python -m whisper_project.burn_srt --video input.mp4 --srt subs.srt --out output.mp4
|
||||
|
||||
Opciones:
|
||||
--style: opcional, cadena de estilos para `subtitles` filter (por ejemplo "FontName=Arial,FontSize=24").
|
||||
--codec: codec de vídeo de salida (por defecto reencode con libx264).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
|
||||
def burn_subtitles_ffmpeg(video: str, srt: str, out: str, style: str | None = None, codec: str | None = None) -> None:
|
||||
# Asegurar rutas absolutas
|
||||
video_p = os.path.abspath(video)
|
||||
srt_p = os.path.abspath(srt)
|
||||
out_p = os.path.abspath(out)
|
||||
|
||||
# Construir filtro subtitles. Escapar correctamente la ruta con shlex.quote
|
||||
# para evitar problemas con espacios y caracteres especiales.
|
||||
quoted_srt = shlex.quote(srt_p)
|
||||
vf = f"subtitles={quoted_srt}"
|
||||
if style:
|
||||
# force_style debe ir separado por ':' en ffmpeg
|
||||
vf = vf + f":force_style='{style}'"
|
||||
|
||||
cmd = [
|
||||
"ffmpeg",
|
||||
"-y",
|
||||
"-i",
|
||||
video_p,
|
||||
"-vf",
|
||||
vf,
|
||||
]
|
||||
|
||||
# Si se especifica codec, usarlo; si no, reencode por defecto con libx264
|
||||
if codec:
|
||||
cmd += ["-c:v", codec, "-c:a", "copy", out_p]
|
||||
else:
|
||||
cmd += ["-c:v", "libx264", "-crf", "23", "-preset", "medium", out_p]
|
||||
|
||||
logging.info("Ejecutando ffmpeg para quemar SRT: %s", " ".join(shlex.quote(c) for c in cmd))
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
p = argparse.ArgumentParser(description="Quemar un .srt en un vídeo con ffmpeg")
|
||||
p.add_argument("--video", required=True, help="Vídeo de entrada")
|
||||
p.add_argument("--srt", required=True, help="Archivo SRT a quemar")
|
||||
p.add_argument("--out", required=True, help="Vídeo de salida")
|
||||
p.add_argument("--style", required=False, help="Force style para el filtro subtitles (ej: FontName=Arial,FontSize=24)")
|
||||
p.add_argument("--codec", required=False, help="Codec de vídeo para salida (ej: libx264)")
|
||||
p.add_argument("--verbose", action="store_true")
|
||||
args = p.parse_args(argv)
|
||||
|
||||
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
|
||||
|
||||
try:
|
||||
burn_subtitles_ffmpeg(args.video, args.srt, args.out, style=args.style, codec=args.codec)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logging.exception("ffmpeg falló al quemar subtítulos: %s", e)
|
||||
return 2
|
||||
|
||||
logging.info("Vídeo con subtítulos guardado en: %s", args.out)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
80
whisper_project/config.py
Normal file
80
whisper_project/config.py
Normal file
@ -0,0 +1,80 @@
|
||||
"""Central configuration loader.
|
||||
|
||||
This loader reads configuration exclusively from `config.toml` located in the
|
||||
current working directory. It intentionally does NOT read environment
|
||||
variables (per project configuration choice) to avoid implicit overrides.
|
||||
|
||||
Expected TOML structure:
|
||||
|
||||
[kokoro]
|
||||
endpoint = "https://..."
|
||||
api_key = "..."
|
||||
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Defaults (used when keys are missing in TOML)
|
||||
_DEFAULTS: dict[str, Any] = {
|
||||
"KOKORO_ENDPOINT": None,
|
||||
"KOKORO_API_KEY": None,
|
||||
}
|
||||
|
||||
|
||||
def _load_toml(path: Path) -> dict[str, Any]:
|
||||
try:
|
||||
import tomllib
|
||||
|
||||
with path.open("rb") as fh:
|
||||
data = tomllib.load(fh)
|
||||
return data if isinstance(data, dict) else {}
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
except Exception:
|
||||
logger.exception("Error leyendo config TOML: %s", path)
|
||||
return {}
|
||||
|
||||
|
||||
# Look for ./config.toml only (no environment-based path resolution)
|
||||
_config_path = Path.cwd() / "config.toml"
|
||||
_TOML: dict[str, Any]
|
||||
if _config_path.exists():
|
||||
_TOML = _load_toml(_config_path)
|
||||
else:
|
||||
_TOML = {}
|
||||
|
||||
|
||||
def _toml_get(*keys: str, default: Any = None) -> Any:
|
||||
node = _TOML
|
||||
for k in keys:
|
||||
if not isinstance(node, dict):
|
||||
return default
|
||||
node = node.get(k, {})
|
||||
return node or default
|
||||
|
||||
|
||||
# Public config values read exclusively from TOML (TOML > defaults)
|
||||
KOKORO_ENDPOINT = (
|
||||
_toml_get(
|
||||
"kokoro",
|
||||
"endpoint",
|
||||
default=_DEFAULTS["KOKORO_ENDPOINT"],
|
||||
)
|
||||
or None
|
||||
)
|
||||
KOKORO_API_KEY = (
|
||||
_toml_get(
|
||||
"kokoro",
|
||||
"api_key",
|
||||
default=_DEFAULTS["KOKORO_API_KEY"],
|
||||
)
|
||||
or None
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["KOKORO_ENDPOINT", "KOKORO_API_KEY"]
|
||||
@ -1,4 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -10,7 +11,14 @@ class Segment:
|
||||
|
||||
@dataclass
|
||||
class PipelineResult:
|
||||
"""Resultado de la pipeline.
|
||||
|
||||
Campos opcionales porque en ciertos modos (por ejemplo `--srt-only`) no se
|
||||
generan artefactos como WAVs o vídeos.
|
||||
"""
|
||||
workdir: str
|
||||
dub_wav: str
|
||||
replaced_video: str
|
||||
burned_video: str
|
||||
dub_wav: Optional[str] = None
|
||||
replaced_video: Optional[str] = None
|
||||
burned_video: Optional[str] = None
|
||||
srt_translated: Optional[str] = None
|
||||
srt_original: Optional[str] = None
|
||||
|
||||
@ -19,6 +19,7 @@ import logging
|
||||
|
||||
from whisper_project.usecases.orchestrator import PipelineOrchestrator
|
||||
from whisper_project.infra.kokoro_adapter import KokoroHttpClient
|
||||
from whisper_project import config as project_config
|
||||
|
||||
|
||||
def main():
|
||||
@ -28,13 +29,21 @@ def main():
|
||||
p.add_argument(
|
||||
"--kokoro-endpoint",
|
||||
required=False,
|
||||
default="https://kokoro.example/api/synthesize",
|
||||
default=project_config.KOKORO_ENDPOINT or "https://kokoro.example/api/synthesize",
|
||||
help=(
|
||||
"Endpoint HTTP de Kokoro (por defecto: "
|
||||
"https://kokoro.example/api/synthesize)"
|
||||
"Endpoint HTTP de Kokoro. Si existe en ./config.toml se usará por defecto. "
|
||||
"(override con esta opción para cambiar)"
|
||||
),
|
||||
)
|
||||
p.add_argument(
|
||||
"--kokoro-key",
|
||||
required=False,
|
||||
default=project_config.KOKORO_API_KEY,
|
||||
help=(
|
||||
"API key para Kokoro. Si existe en ./config.toml se usará por defecto. "
|
||||
"(override con esta opción para cambiar)"
|
||||
),
|
||||
)
|
||||
p.add_argument("--kokoro-key", required=False)
|
||||
p.add_argument("--voice", default="em_alex")
|
||||
p.add_argument("--kokoro-model", default="model")
|
||||
p.add_argument("--whisper-model", default="base")
|
||||
@ -57,7 +66,7 @@ def main():
|
||||
),
|
||||
)
|
||||
p.add_argument("--mix", action="store_true")
|
||||
p.add_argument("--mix-background-volume", type=float, default=0.2)
|
||||
p.add_argument("--mix-background-volume", type=float, default=0.1)
|
||||
p.add_argument("--keep-chunks", action="store_true")
|
||||
p.add_argument("--keep-temp", action="store_true")
|
||||
p.add_argument(
|
||||
@ -65,6 +74,11 @@ def main():
|
||||
action="store_true",
|
||||
help="Simular pasos sin ejecutar",
|
||||
)
|
||||
p.add_argument(
|
||||
"--srt-only",
|
||||
action="store_true",
|
||||
help="Solo extraer y traducir el SRT, devolver la ruta del SRT traducido y no ejecutar TTS ni quemado",
|
||||
)
|
||||
p.add_argument(
|
||||
"--verbose",
|
||||
action="store_true",
|
||||
@ -117,6 +131,7 @@ def main():
|
||||
mix_background_volume=args.mix_background_volume,
|
||||
keep_chunks=args.keep_chunks,
|
||||
dry_run=args.dry_run,
|
||||
srt_only=args.srt_only,
|
||||
)
|
||||
|
||||
logging.info("Orquestador finalizado; resultado (burned_video): %s", getattr(result, "burned_video", None))
|
||||
@ -167,6 +182,25 @@ def main():
|
||||
# En dry-run o sin resultado, no movemos nada
|
||||
final_path = getattr(result, "burned_video", None)
|
||||
|
||||
# Si el usuario pidió sólo el SRT traducido, mover el srt al output/<basename>/
|
||||
if args.srt_only and not args.dry_run and result and getattr(result, "srt_translated", None):
|
||||
base = os.path.splitext(os.path.basename(video))[0]
|
||||
project_out = os.path.join(os.getcwd(), "output", base)
|
||||
try:
|
||||
os.makedirs(project_out, exist_ok=True)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
src = result.srt_translated
|
||||
dest = os.path.join(project_out, os.path.basename(src)) if src else None
|
||||
try:
|
||||
if src and dest and os.path.abspath(src) != os.path.abspath(dest):
|
||||
logging.info("Moviendo SRT traducido: %s -> %s", src, dest)
|
||||
shutil.move(src, dest)
|
||||
final_path = dest or src
|
||||
except Exception:
|
||||
final_path = src
|
||||
|
||||
logging.info("Flujo completado. Vídeo final: %s", final_path)
|
||||
finally:
|
||||
if not args.keep_temp:
|
||||
|
||||
@ -1,12 +1,59 @@
|
||||
# Dependencias básicas para ejecutar Whisper en CPU
|
||||
torch>=1.12.0
|
||||
ffmpeg-python
|
||||
numpy
|
||||
# Optional backends (comment/uncomment as needed)
|
||||
openai-whisper
|
||||
transformers
|
||||
faster-whisper
|
||||
# TTS (opcional)
|
||||
TTS
|
||||
pyttsx3
|
||||
huggingface-hub
|
||||
# Dependencias comunes del paquete `whisper_project`.
|
||||
# Este fichero está orientado a instalaciones en CPU. Algunas librerías
|
||||
# publican ruedas separadas para GPU y CPU (p.ej. `torch`). Para forzar
|
||||
# la instalación CPU de PyTorch ejecuta:
|
||||
#
|
||||
# python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||
#
|
||||
# O bien instala todo de una vez usando el índice CPU:
|
||||
#
|
||||
# python -m pip install --index-url https://download.pytorch.org/whl/cpu -r whisper_project/requirements.txt
|
||||
#
|
||||
# Luego instala el resto de dependencias con:
|
||||
# python -m pip install -r whisper_project/requirements.txt
|
||||
#
|
||||
# Si deseas GPU en el futuro, instala la rueda apropiada de PyTorch
|
||||
# para tu versión de CUDA y, si procede, elimina el uso del índice CPU.
|
||||
#
|
||||
# Este archivo lista las librerías que el código del proyecto puede usar.
|
||||
# Instalar en el virtualenv del proyecto con:
|
||||
# python -m pip install -r whisper_project/requirements.txt
|
||||
|
||||
# Core / audio
|
||||
torch==2.2.2
|
||||
numpy==1.26.4
|
||||
ffmpeg-python==0.4.0
|
||||
|
||||
# Transcripción y modelos
|
||||
faster-whisper==1.2.0
|
||||
transformers==4.34.0
|
||||
tokenizers==0.13.3
|
||||
sentencepiece==0.1.99
|
||||
huggingface-hub==0.16.4
|
||||
|
||||
# Traducción (Marian / transformers)
|
||||
sacremoses==0.0.53
|
||||
|
||||
# TTS (opcional: Coqui TTS o fallbacks)
|
||||
TTS==0.13.0
|
||||
soundfile==0.12.1
|
||||
librosa==0.10.0.post2
|
||||
pyttsx3==2.90
|
||||
|
||||
# HTTP / utilidades
|
||||
requests==2.31.0
|
||||
tqdm==4.66.1
|
||||
|
||||
# Dependencias instaladas por `faster-whisper` en algunos entornos
|
||||
onnxruntime==1.15.1
|
||||
ctranslate2==3.18.0
|
||||
av==10.0.0
|
||||
coloredlogs==15.0.1
|
||||
humanfriendly==10.0
|
||||
flatbuffers==23.5.26
|
||||
|
||||
# Nota: estos pins se eligieron para compatibilidad con Python 3.11
|
||||
# y uso en CPU. Si prefieres versiones distintas (p.ej. ruedas GPU de
|
||||
# `torch`), indícamelo y actualizo los pins o añado instrucciones
|
||||
# para instalar desde índices alternativos.
|
||||
|
||||
|
||||
@ -1,110 +1,122 @@
|
||||
"""Funciones helper para sintetizar desde SRT.
|
||||
"""Small CLI shim for SRT -> Kokoro synthesis.
|
||||
|
||||
Este módulo mantiene compatibilidad con la antigua utilidad `srt_to_kokoro.py`.
|
||||
Contiene `parse_srt_file` y `synth_chunk` delegando a infra.kokoro_utils.
|
||||
Se incluye una función `synthesize_from_srt` que documenta la compatibilidad
|
||||
con `KokoroHttpClient` (nombre esperado por otros módulos).
|
||||
This file provides: parse_srt_file, synth_chunk (thin wrappers) and a
|
||||
CLI entrypoint that uses `whisper_project.config` (config.toml) and CLI
|
||||
flags. It intentionally does NOT read environment variables.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from whisper_project.infra.kokoro_utils import parse_srt_file as _parse_srt_file, synth_chunk as _synth_chunk
|
||||
from whisper_project.infra.kokoro_utils import (
|
||||
parse_srt_file as _parse_srt_file,
|
||||
synth_chunk as _synth_chunk,
|
||||
)
|
||||
from whisper_project.infra.kokoro_adapter import KokoroHttpClient
|
||||
from whisper_project import config
|
||||
|
||||
|
||||
def parse_srt_file(path: str):
|
||||
"""Parsea un .srt y devuelve la lista de subtítulos.
|
||||
"""Parse a .srt and return the list of subtitles.
|
||||
|
||||
Delegado a `whisper_project.infra.kokoro_utils.parse_srt_file`.
|
||||
Delegates to `whisper_project.infra.kokoro_utils.parse_srt_file`.
|
||||
"""
|
||||
return _parse_srt_file(path)
|
||||
|
||||
|
||||
def synth_chunk(endpoint: str, text: str, headers: dict, payload_template: Any, timeout: int = 60) -> bytes:
|
||||
"""Envía texto al endpoint y devuelve bytes de audio.
|
||||
def synth_chunk(
|
||||
endpoint: str,
|
||||
text: str,
|
||||
headers: dict,
|
||||
payload_template: Any,
|
||||
timeout: int = 60,
|
||||
) -> bytes:
|
||||
"""Send text to the endpoint and return audio bytes.
|
||||
|
||||
Delegado a `whisper_project.infra.kokoro_utils.synth_chunk`.
|
||||
Delegates to `whisper_project.infra.kokoro_utils.synth_chunk`.
|
||||
"""
|
||||
return _synth_chunk(endpoint, text, headers, payload_template, timeout=timeout)
|
||||
|
||||
|
||||
def synthesize_from_srt(srt_path: str, out_wav: str, endpoint: str = "", api_key: str = ""):
|
||||
"""Compat layer: función con el nombre esperado por scripts legacy.
|
||||
"""Compatibility layer name used historically by scripts.
|
||||
|
||||
Nota: la implementación completa se encuentra ahora en `KokoroHttpClient`.
|
||||
Esta función delega a `parse_srt_file` y `synth_chunk` si se necesita.
|
||||
The canonical implementation lives in `KokoroHttpClient`. Call that class
|
||||
method instead when integrating programmatically.
|
||||
"""
|
||||
raise NotImplementedError("Use KokoroHttpClient.synthesize_from_srt or the infra adapter instead")
|
||||
raise NotImplementedError(
|
||||
"Use KokoroHttpClient.synthesize_from_srt or the infra adapter"
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["parse_srt_file", "synth_chunk", "synthesize_from_srt"]
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
srt_to_kokoro.py
|
||||
|
||||
Leer un archivo .srt y sintetizar cada subtítulo usando una API OpenAPI-compatible (p. ej. Kokoro).
|
||||
- Intenta autodetectar un endpoint de síntesis en `--openapi` (URL JSON) buscando paths que contengan 'synth'|'tts'|'text' y que acepten POST.
|
||||
- Alternativamente usa `--endpoint` y un `--payload-template` con {text} como placeholder.
|
||||
- Guarda fragmentos temporales y los concatena con ffmpeg en un único WAV de salida.
|
||||
|
||||
Dependencias: requests, srt (pip install requests srt)
|
||||
Requiere ffmpeg en PATH.
|
||||
|
||||
Ejemplos:
|
||||
python srt_to_kokoro.py --srt subs.srt --openapi "https://kokoro.../openapi.json" --voice "alloy" --out out.wav --api-key "TOKEN"
|
||||
python srt_to_kokoro.py --srt subs.srt --endpoint "https://kokoro.../v1/synthesize" --payload-template '{"text": "{text}", "voice": "alloy"}' --out out.wav
|
||||
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from typing import Optional
|
||||
|
||||
"""
|
||||
Thin wrapper CLI que delega en `KokoroHttpClient.synthesize_from_srt`.
|
||||
|
||||
Conserva la interfaz CLI previa para compatibilidad, pero internamente usa
|
||||
el cliente HTTP nativo definido en `whisper_project.infra.kokoro_adapter`.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
from whisper_project.infra.kokoro_adapter import KokoroHttpClient
|
||||
import logging
|
||||
|
||||
|
||||
def main():
|
||||
def _build_arg_parser() -> argparse.ArgumentParser:
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument("--srt", required=True, help="Ruta al archivo .srt traducido")
|
||||
p.add_argument("--endpoint", required=False, help="URL directa del endpoint de síntesis (opcional)")
|
||||
p.add_argument("--api-key", required=False, help="Valor para autorización (se envía como header Authorization: Bearer <key>)")
|
||||
p.add_argument("--srt", required=True, help="Path to input .srt file")
|
||||
p.add_argument("--endpoint", required=False, help="Direct synthesis endpoint (optional)")
|
||||
p.add_argument(
|
||||
"--api-key",
|
||||
required=False,
|
||||
help=(
|
||||
"API key for Authorization header; if omitted the value from"
|
||||
" config.toml is used"
|
||||
),
|
||||
)
|
||||
p.add_argument("--voice", default="em_alex")
|
||||
p.add_argument("--model", default="model")
|
||||
p.add_argument("--out", required=True, help="Ruta de salida WAV final")
|
||||
p.add_argument("--video", required=False, help="Ruta al vídeo original (opcional)")
|
||||
p.add_argument("--align", action="store_true", help="Alinear segmentos con timestamps del SRT")
|
||||
p.add_argument("--out", required=True, help="Output WAV path")
|
||||
p.add_argument("--video", required=False, help="Optional original video path to mix or align with")
|
||||
p.add_argument("--align", action="store_true", help="Align segments using SRT timestamps")
|
||||
p.add_argument("--keep-chunks", action="store_true")
|
||||
p.add_argument("--mix-with-original", action="store_true")
|
||||
p.add_argument("--mix-background-volume", type=float, default=0.2)
|
||||
p.add_argument("--replace-original", action="store_true")
|
||||
p.add_argument(
|
||||
"--config-mode",
|
||||
choices=["defaults", "override-env", "force"],
|
||||
default="override-env",
|
||||
help=(
|
||||
"Configuration precedence: 'defaults' = CLI > TOML; "
|
||||
"'override-env' = CLI > TOML; 'force' = TOML > CLI"
|
||||
),
|
||||
)
|
||||
return p
|
||||
|
||||
|
||||
def main() -> None:
|
||||
p = _build_arg_parser()
|
||||
args = p.parse_args()
|
||||
|
||||
# Construir cliente Kokoro HTTP y delegar la síntesis completa
|
||||
endpoint = args.endpoint or os.environ.get("KOKORO_ENDPOINT")
|
||||
api_key = args.api_key or os.environ.get("KOKORO_API_KEY")
|
||||
# Resolve configuration: only CLI flags and config.toml are used.
|
||||
kokoro_ep = getattr(args, "endpoint", None)
|
||||
kokoro_key = getattr(args, "api_key", None)
|
||||
|
||||
mode = getattr(args, "config_mode", "defaults")
|
||||
if mode in ("defaults", "override-env"):
|
||||
# CLI > TOML
|
||||
endpoint = kokoro_ep or args.endpoint or config.KOKORO_ENDPOINT
|
||||
api_key = kokoro_key or args.api_key or config.KOKORO_API_KEY
|
||||
else:
|
||||
# force: TOML > CLI
|
||||
endpoint = config.KOKORO_ENDPOINT or kokoro_ep or args.endpoint
|
||||
api_key = config.KOKORO_API_KEY or kokoro_key or args.api_key
|
||||
|
||||
if not endpoint:
|
||||
logging.getLogger(__name__).error("Debe proporcionar --endpoint o la variable de entorno KOKORO_ENDPOINT")
|
||||
logging.getLogger(__name__).error(
|
||||
"Please provide --endpoint or set kokoro.endpoint in config.toml"
|
||||
)
|
||||
sys.exit(2)
|
||||
|
||||
client = KokoroHttpClient(endpoint, api_key=api_key, voice=args.voice, model=args.model)
|
||||
client = KokoroHttpClient(
|
||||
endpoint,
|
||||
api_key=api_key,
|
||||
voice=args.voice,
|
||||
model=args.model,
|
||||
)
|
||||
|
||||
try:
|
||||
client.synthesize_from_srt(
|
||||
srt_path=args.srt,
|
||||
@ -115,11 +127,11 @@ def main():
|
||||
mix_with_original=args.mix_with_original,
|
||||
mix_background_volume=args.mix_background_volume,
|
||||
)
|
||||
logging.getLogger(__name__).info("Archivo final generado en: %s", args.out)
|
||||
logging.getLogger(__name__).info("Output written to: %s", args.out)
|
||||
except Exception:
|
||||
logging.getLogger(__name__).exception("Error durante la síntesis desde SRT")
|
||||
logging.getLogger(__name__).exception("Error synthesizing from SRT")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@ -155,12 +155,65 @@ class PipelineOrchestrator:
|
||||
# Si no se inyectan adaptadores, crear implementaciones por defecto
|
||||
# Sólo importar adaptadores pesados si no se inyectan implementaciones.
|
||||
if transcriber is None:
|
||||
# Definir un transcriptor en-proceso de respaldo que pruebe varias
|
||||
# estrategias antes de fallar (faster-whisper -> openai -> segmentado).
|
||||
try:
|
||||
from ..infra.faster_whisper_adapter import FasterWhisperTranscriber
|
||||
from ..infra.transcribe_adapter import TranscribeService
|
||||
|
||||
self.transcriber = FasterWhisperTranscriber()
|
||||
class InProcessTranscriber:
|
||||
def __init__(self, model: str = "base") -> None:
|
||||
self._svc = TranscribeService(model=model)
|
||||
|
||||
def transcribe(self, file: str, *args, **kwargs):
|
||||
# Compatibilidad con llamadas posicionales: si se pasa
|
||||
# un segundo argumento posicional lo tratamos como srt_out
|
||||
srt_out = None
|
||||
if args:
|
||||
srt_out = args[0]
|
||||
srt_flag = kwargs.get("srt", bool(srt_out))
|
||||
srt_file = kwargs.get("srt_file", srt_out)
|
||||
|
||||
try:
|
||||
return self._svc.transcribe_faster(file)
|
||||
except Exception:
|
||||
try:
|
||||
return self._svc.transcribe_openai(file)
|
||||
except Exception:
|
||||
try:
|
||||
duration = self._svc.get_audio_duration(file) or 0
|
||||
segs = self._svc.make_uniform_segments(duration, seg_seconds=max(30, int(duration) or 30))
|
||||
results = self._svc.transcribe_segmented_with_tempfiles(file, segs, backend="openai-whisper", model=self._svc.model)
|
||||
if srt_flag and srt_file:
|
||||
self._svc.write_srt(results, srt_file)
|
||||
return results
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
# Intentar usar el adaptador rápido si existe, pero envolver su
|
||||
# llamada para detectar errores en tiempo de ejecución y caer
|
||||
# al InProcessTranscriber.
|
||||
try:
|
||||
from ..infra.faster_whisper_adapter import FasterWhisperTranscriber
|
||||
|
||||
fw = FasterWhisperTranscriber()
|
||||
|
||||
class TranscriberProxy:
|
||||
def __init__(self, fast_impl, fallback):
|
||||
self._fast = fast_impl
|
||||
self._fallback = fallback
|
||||
|
||||
def transcribe(self, *args, **kwargs):
|
||||
try:
|
||||
return self._fast.transcribe(*args, **kwargs)
|
||||
except Exception:
|
||||
return self._fallback.transcribe(*args, **kwargs)
|
||||
|
||||
self.transcriber = TranscriberProxy(fw, InProcessTranscriber())
|
||||
except Exception:
|
||||
# Si no existe el adaptador rápido, usar directamente el in-process
|
||||
self.transcriber = InProcessTranscriber()
|
||||
except Exception:
|
||||
# dejar como None para permitir fallback a subprocess en tiempo de ejecución
|
||||
# último recurso: no hay transcriptor en memoria
|
||||
self.transcriber = None
|
||||
else:
|
||||
self.transcriber = transcriber
|
||||
@ -207,6 +260,7 @@ class PipelineOrchestrator:
|
||||
mix_background_volume: float = 0.2,
|
||||
keep_chunks: bool = False,
|
||||
dry_run: bool = False,
|
||||
srt_only: bool = False,
|
||||
) -> PipelineResult:
|
||||
"""Run the pipeline.
|
||||
|
||||
@ -325,6 +379,17 @@ class PipelineOrchestrator:
|
||||
else:
|
||||
raise ValueError("translate_method not supported in this orchestrator")
|
||||
|
||||
# Si el usuario solicitó sólo el SRT traducido, devolvemos inmediatamente
|
||||
# tras la traducción y NO ejecutamos síntesis TTS / reemplazo / quemado.
|
||||
if srt_only:
|
||||
if dry_run:
|
||||
logger.info("[dry-run] srt-only mode: devolver %s", srt_translated)
|
||||
return PipelineResult(
|
||||
workdir=workdir,
|
||||
srt_translated=srt_translated,
|
||||
srt_original=srt_in,
|
||||
)
|
||||
|
||||
# 4) sintetizar por segmento
|
||||
dub_wav = os.path.join(workdir, "dub_final.wav")
|
||||
if dry_run:
|
||||
@ -360,4 +425,6 @@ class PipelineOrchestrator:
|
||||
dub_wav=dub_wav,
|
||||
replaced_video=replaced,
|
||||
burned_video=burned,
|
||||
srt_translated=srt_translated,
|
||||
srt_original=srt_in,
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user