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/
|
||||||
env.bak/
|
env.bak/
|
||||||
venv.bak/
|
venv.bak/
|
||||||
|
.venv311/
|
||||||
|
config.toml
|
||||||
|
|
||||||
# Spyder project settings
|
# Spyder project settings
|
||||||
.spyderproject
|
.spyderproject
|
||||||
@ -168,4 +170,5 @@ cython_debug/
|
|||||||
.Spotlight-V100
|
.Spotlight-V100
|
||||||
.Trashes
|
.Trashes
|
||||||
ehthumbs.db
|
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
|
# Whisper Pipeline — Documentación
|
||||||
|
|
||||||
Bienvenido: esta carpeta contiene la documentación generada para la canalización multimedia (Whisper + Kokoro).
|
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 dataclasses import dataclass
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@ -10,7 +11,14 @@ class Segment:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PipelineResult:
|
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
|
workdir: str
|
||||||
dub_wav: str
|
dub_wav: Optional[str] = None
|
||||||
replaced_video: str
|
replaced_video: Optional[str] = None
|
||||||
burned_video: str
|
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.usecases.orchestrator import PipelineOrchestrator
|
||||||
from whisper_project.infra.kokoro_adapter import KokoroHttpClient
|
from whisper_project.infra.kokoro_adapter import KokoroHttpClient
|
||||||
|
from whisper_project import config as project_config
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
@ -28,13 +29,21 @@ def main():
|
|||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--kokoro-endpoint",
|
"--kokoro-endpoint",
|
||||||
required=False,
|
required=False,
|
||||||
default="https://kokoro.example/api/synthesize",
|
default=project_config.KOKORO_ENDPOINT or "https://kokoro.example/api/synthesize",
|
||||||
help=(
|
help=(
|
||||||
"Endpoint HTTP de Kokoro (por defecto: "
|
"Endpoint HTTP de Kokoro. Si existe en ./config.toml se usará por defecto. "
|
||||||
"https://kokoro.example/api/synthesize)"
|
"(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("--voice", default="em_alex")
|
||||||
p.add_argument("--kokoro-model", default="model")
|
p.add_argument("--kokoro-model", default="model")
|
||||||
p.add_argument("--whisper-model", default="base")
|
p.add_argument("--whisper-model", default="base")
|
||||||
@ -57,7 +66,7 @@ def main():
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
p.add_argument("--mix", action="store_true")
|
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-chunks", action="store_true")
|
||||||
p.add_argument("--keep-temp", action="store_true")
|
p.add_argument("--keep-temp", action="store_true")
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
@ -65,6 +74,11 @@ def main():
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="Simular pasos sin ejecutar",
|
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(
|
p.add_argument(
|
||||||
"--verbose",
|
"--verbose",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -117,6 +131,7 @@ def main():
|
|||||||
mix_background_volume=args.mix_background_volume,
|
mix_background_volume=args.mix_background_volume,
|
||||||
keep_chunks=args.keep_chunks,
|
keep_chunks=args.keep_chunks,
|
||||||
dry_run=args.dry_run,
|
dry_run=args.dry_run,
|
||||||
|
srt_only=args.srt_only,
|
||||||
)
|
)
|
||||||
|
|
||||||
logging.info("Orquestador finalizado; resultado (burned_video): %s", getattr(result, "burned_video", None))
|
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
|
# En dry-run o sin resultado, no movemos nada
|
||||||
final_path = getattr(result, "burned_video", None)
|
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)
|
logging.info("Flujo completado. Vídeo final: %s", final_path)
|
||||||
finally:
|
finally:
|
||||||
if not args.keep_temp:
|
if not args.keep_temp:
|
||||||
|
|||||||
@ -1,12 +1,59 @@
|
|||||||
# Dependencias básicas para ejecutar Whisper en CPU
|
# Dependencias comunes del paquete `whisper_project`.
|
||||||
torch>=1.12.0
|
# Este fichero está orientado a instalaciones en CPU. Algunas librerías
|
||||||
ffmpeg-python
|
# publican ruedas separadas para GPU y CPU (p.ej. `torch`). Para forzar
|
||||||
numpy
|
# la instalación CPU de PyTorch ejecuta:
|
||||||
# Optional backends (comment/uncomment as needed)
|
#
|
||||||
openai-whisper
|
# python -m pip install torch --index-url https://download.pytorch.org/whl/cpu
|
||||||
transformers
|
#
|
||||||
faster-whisper
|
# O bien instala todo de una vez usando el índice CPU:
|
||||||
# TTS (opcional)
|
#
|
||||||
TTS
|
# python -m pip install --index-url https://download.pytorch.org/whl/cpu -r whisper_project/requirements.txt
|
||||||
pyttsx3
|
#
|
||||||
huggingface-hub
|
# 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`.
|
This file provides: parse_srt_file, synth_chunk (thin wrappers) and a
|
||||||
Contiene `parse_srt_file` y `synth_chunk` delegando a infra.kokoro_utils.
|
CLI entrypoint that uses `whisper_project.config` (config.toml) and CLI
|
||||||
Se incluye una función `synthesize_from_srt` que documenta la compatibilidad
|
flags. It intentionally does NOT read environment variables.
|
||||||
con `KokoroHttpClient` (nombre esperado por otros módulos).
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
import argparse
|
||||||
import logging
|
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):
|
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)
|
return _parse_srt_file(path)
|
||||||
|
|
||||||
|
|
||||||
def synth_chunk(endpoint: str, text: str, headers: dict, payload_template: Any, timeout: int = 60) -> bytes:
|
def synth_chunk(
|
||||||
"""Envía texto al endpoint y devuelve bytes de audio.
|
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)
|
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 = ""):
|
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`.
|
The canonical implementation lives in `KokoroHttpClient`. Call that class
|
||||||
Esta función delega a `parse_srt_file` y `synth_chunk` si se necesita.
|
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"]
|
def _build_arg_parser() -> argparse.ArgumentParser:
|
||||||
#!/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():
|
|
||||||
p = argparse.ArgumentParser()
|
p = argparse.ArgumentParser()
|
||||||
p.add_argument("--srt", required=True, help="Ruta al archivo .srt traducido")
|
p.add_argument("--srt", required=True, help="Path to input .srt file")
|
||||||
p.add_argument("--endpoint", required=False, help="URL directa del endpoint de síntesis (opcional)")
|
p.add_argument("--endpoint", required=False, help="Direct synthesis endpoint (optional)")
|
||||||
p.add_argument("--api-key", required=False, help="Valor para autorización (se envía como header Authorization: Bearer <key>)")
|
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("--voice", default="em_alex")
|
||||||
p.add_argument("--model", default="model")
|
p.add_argument("--model", default="model")
|
||||||
p.add_argument("--out", required=True, help="Ruta de salida WAV final")
|
p.add_argument("--out", required=True, help="Output WAV path")
|
||||||
p.add_argument("--video", required=False, help="Ruta al vídeo original (opcional)")
|
p.add_argument("--video", required=False, help="Optional original video path to mix or align with")
|
||||||
p.add_argument("--align", action="store_true", help="Alinear segmentos con timestamps del SRT")
|
p.add_argument("--align", action="store_true", help="Align segments using SRT timestamps")
|
||||||
p.add_argument("--keep-chunks", action="store_true")
|
p.add_argument("--keep-chunks", action="store_true")
|
||||||
p.add_argument("--mix-with-original", 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("--mix-background-volume", type=float, default=0.2)
|
||||||
p.add_argument("--replace-original", action="store_true")
|
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()
|
args = p.parse_args()
|
||||||
|
|
||||||
# Construir cliente Kokoro HTTP y delegar la síntesis completa
|
# Resolve configuration: only CLI flags and config.toml are used.
|
||||||
endpoint = args.endpoint or os.environ.get("KOKORO_ENDPOINT")
|
kokoro_ep = getattr(args, "endpoint", None)
|
||||||
api_key = args.api_key or os.environ.get("KOKORO_API_KEY")
|
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:
|
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)
|
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:
|
try:
|
||||||
client.synthesize_from_srt(
|
client.synthesize_from_srt(
|
||||||
srt_path=args.srt,
|
srt_path=args.srt,
|
||||||
@ -115,11 +127,11 @@ def main():
|
|||||||
mix_with_original=args.mix_with_original,
|
mix_with_original=args.mix_with_original,
|
||||||
mix_background_volume=args.mix_background_volume,
|
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:
|
except Exception:
|
||||||
logging.getLogger(__name__).exception("Error durante la síntesis desde SRT")
|
logging.getLogger(__name__).exception("Error synthesizing from SRT")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|||||||
@ -155,12 +155,65 @@ class PipelineOrchestrator:
|
|||||||
# Si no se inyectan adaptadores, crear implementaciones por defecto
|
# Si no se inyectan adaptadores, crear implementaciones por defecto
|
||||||
# Sólo importar adaptadores pesados si no se inyectan implementaciones.
|
# Sólo importar adaptadores pesados si no se inyectan implementaciones.
|
||||||
if transcriber is None:
|
if transcriber is None:
|
||||||
|
# Definir un transcriptor en-proceso de respaldo que pruebe varias
|
||||||
|
# estrategias antes de fallar (faster-whisper -> openai -> segmentado).
|
||||||
try:
|
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:
|
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
|
self.transcriber = None
|
||||||
else:
|
else:
|
||||||
self.transcriber = transcriber
|
self.transcriber = transcriber
|
||||||
@ -207,6 +260,7 @@ class PipelineOrchestrator:
|
|||||||
mix_background_volume: float = 0.2,
|
mix_background_volume: float = 0.2,
|
||||||
keep_chunks: bool = False,
|
keep_chunks: bool = False,
|
||||||
dry_run: bool = False,
|
dry_run: bool = False,
|
||||||
|
srt_only: bool = False,
|
||||||
) -> PipelineResult:
|
) -> PipelineResult:
|
||||||
"""Run the pipeline.
|
"""Run the pipeline.
|
||||||
|
|
||||||
@ -325,6 +379,17 @@ class PipelineOrchestrator:
|
|||||||
else:
|
else:
|
||||||
raise ValueError("translate_method not supported in this orchestrator")
|
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
|
# 4) sintetizar por segmento
|
||||||
dub_wav = os.path.join(workdir, "dub_final.wav")
|
dub_wav = os.path.join(workdir, "dub_final.wav")
|
||||||
if dry_run:
|
if dry_run:
|
||||||
@ -360,4 +425,6 @@ class PipelineOrchestrator:
|
|||||||
dub_wav=dub_wav,
|
dub_wav=dub_wav,
|
||||||
replaced_video=replaced,
|
replaced_video=replaced,
|
||||||
burned_video=burned,
|
burned_video=burned,
|
||||||
|
srt_translated=srt_translated,
|
||||||
|
srt_original=srt_in,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user