Files
Transcriptarr/CLAUDE.md
Dasemu 529af217e9 docs: add comprehensive documentation and test suite
- Add CLAUDE.md with project architecture and operation modes
- Add backend/README.md with setup and usage instructions
- Add test_backend.py with automated tests for config, database, and queue
- Update requirements.txt with optional dependencies structure
- Update .env.example with all configuration options
2026-01-11 21:23:58 +01:00

747 lines
22 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# CLAUDE.md - TranscriptorIO
## ¿Qué es TranscriptorIO?
TranscriptorIO es un sistema completo de generación automática de subtítulos para contenido multimedia usando IA (Whisper + modelos de traducción). Es un **hard fork** de [SubGen](https://github.com/McCloudS/subgen) con una arquitectura completamente rediseñada inspirada en Tdarr.
## Motivación
SubGen es funcional pero tiene limitaciones fundamentales de diseño:
### Problemas de SubGen
- **Procesamiento síncrono**: Bloquea threads mientras transcribe
- **Sin cola persistente**: Los trabajos se pierden al reiniciar
- **Sin WebUI**: Removida en marzo 2024, solo tiene Swagger docs
- **Sin visibilidad**: No sabes progreso, ETA, o estado de trabajos
- **Sin priorización**: No puedes reordenar trabajos
- **Timeouts en Bazarr**: Si un episodio tarda >5min, throttle de 24 horas
- **Configuración compleja**: 40+ variables ENV sin validación
### Visión de TranscriptorIO
Un sistema tipo **Tdarr pero para subtítulos**, con:
- ✅ Sistema de cola asíncrona persistente (SQLite)
- ✅ Workers configurables (múltiples GPUs/CPUs)
- ✅ WebUI moderna con progreso en tiempo real
- ✅ Múltiples pipelines de calidad (Fast/Balanced/Best)
- ✅ Integración asíncrona con Bazarr
- ✅ Procesamiento batch (temporadas completas)
- ✅ API REST completa
- ✅ WebSocket para updates en vivo
## Casos de uso
### Caso principal: Anime japonés → Subtítulos español
**Problema**: Anime sin fansubs en español, solo tiene audio japonés.
**Pipeline**:
```
Audio japonés
Whisper (task="translate") → Texto inglés
Helsinki-NLP (en→es) → Texto español
Generar .srt con timestamps
```
**Alternativas configurables**:
- **Fast** (4GB VRAM): ja→en→es con Helsinki-NLP
- **Balanced** (6GB VRAM): ja→ja→es con M2M100
- **Best** (10GB+ VRAM): ja→es directo con SeamlessM4T
### Integración con stack existente
```
Sonarr descarga episodio
Bazarr detecta: faltan subtítulos español
Bazarr → TranscriptorIO (provider asíncrono)
TranscriptorIO encola trabajo
Worker procesa cuando está libre
Callback a Bazarr con .srt generado
Jellyfin detecta nuevo subtítulo
```
## Modos de Operación
TranscriptorIO soporta dos modos de operación distintos que se configuran vía environment variables:
### Modo Standalone (Tdarr-like)
**Descripción**: TranscriptorIO escanea automáticamente tu biblioteca de medios y genera subtítulos según reglas configurables.
**Casos de uso**:
- Procesamiento batch de biblioteca existente
- Monitoreo automático de nuevos archivos
- Control total sobre qué se transcribe sin depender de Bazarr
**Funcionamiento**:
```
1. Escaneo periódico con ffprobe
└─> Detecta archivos que cumplen criterios
(Ej: audio japonés + sin subs español)
2. Encolado automático
└─> Añade a cola con prioridad configurada
3. Procesamiento batch
└─> Workers procesan según disponibilidad
4. Escritura directa
└─> Guarda .srt junto al archivo origen
```
**Configuración**:
```env
# Habilitar modo standalone
TRANSCRIPTARR_MODE=standalone
# Carpetas a escanear (separadas por |)
LIBRARY_PATHS=/media/anime|/media/movies
# Reglas de filtrado
REQUIRED_AUDIO_LANGUAGE=ja
REQUIRED_MISSING_SUBTITLE=spa
SKIP_IF_SUBTITLE_EXISTS=true
# Escaneo automático
AUTO_SCAN_ENABLED=true
SCAN_INTERVAL_MINUTES=30
```
**Ventajas**:
- ✅ No depende de integraciones externas
- ✅ Procesamiento batch eficiente
- ✅ Monitoreo automático de nueva media
- ✅ Control granular con reglas de filtrado
### Modo Provider (Bazarr-slave)
**Descripción**: TranscriptorIO actúa como provider de subtítulos para Bazarr mediante una API asíncrona mejorada.
**Casos de uso**:
- Integración con stack *arr existente
- Gestión centralizada de subtítulos en Bazarr
- Fallback cuando no hay subtítulos pre-hechos
**Funcionamiento**:
```
1. Bazarr solicita subtítulo (API call)
└─> POST /api/provider/request
2. TranscriptorIO encola trabajo
└─> Retorna job_id inmediatamente
└─> No bloquea thread de Bazarr
3. Procesamiento asíncrono
└─> Worker transcribe cuando hay capacidad
4. Callback a Bazarr
└─> POST {bazarr_callback_url} con .srt
└─> O polling de Bazarr cada 30s
```
**Configuración**:
```env
# Habilitar modo provider
TRANSCRIPTARR_MODE=provider
# API de Bazarr para callbacks
BAZARR_URL=http://bazarr:6767
BAZARR_API_KEY=your_api_key_here
# Configuración del provider
PROVIDER_TIMEOUT_SECONDS=600
PROVIDER_CALLBACK_ENABLED=true
PROVIDER_POLLING_INTERVAL=30
```
**Ventajas vs SubGen original**:
-**No bloquea**: Retorna inmediatamente con job_id
-**Sin timeouts**: Bazarr no throttle por trabajos lentos
-**Visibilidad**: Bazarr puede consultar progreso
-**Reintentos**: Manejo automático de errores
-**Priorización**: Trabajos manuales tienen mayor prioridad
### Modo Híbrido (Recomendado)
Puedes habilitar ambos modos simultáneamente:
```env
TRANSCRIPTARR_MODE=standalone,provider
```
**Beneficios**:
- Bazarr maneja media nueva automáticamente
- Standalone procesa biblioteca existente
- Cola unificada con priorización inteligente
- Mejor aprovechamiento de recursos
## Arquitectura técnica
### Stack tecnológico
**Backend**:
- FastAPI (API REST + WebSocket)
- SQLAlchemy (ORM multi-backend)
- SQLite / PostgreSQL / MariaDB (queue persistente)
- faster-whisper (transcripción optimizada)
- Helsinki-NLP/opus-mt-en-es (traducción ligera)
- stable-ts (mejora de timestamps)
**Frontend**:
- Vue 3 + Vite
- Tailwind CSS
- Chart.js (estadísticas)
- Socket.io-client (updates en tiempo real)
**Infraestructura**:
- Docker + Docker Compose
- NVIDIA GPU support (opcional, también CPU)
- Multi-container: backend + workers + frontend
### Componentes principales
```
transcriptorio/
├── backend/
│ ├── core/
│ │ ├── pipelines/
│ │ │ ├── whisper_fast.py # ja→en→es (Helsinki)
│ │ │ ├── whisper_balanced.py # ja→ja→es (M2M100)
│ │ │ └── seamless.py # ja→es directo
│ │ ├── queue_manager.py # Cola SQLite
│ │ ├── worker_pool.py # Gestión de workers
│ │ └── transcriber.py # Core Whisper
│ ├── api/
│ │ ├── legacy.py # /asr (compat SubGen/Bazarr)
│ │ ├── queue.py # /api/queue/*
│ │ ├── jobs.py # /api/jobs/*
│ │ └── websocket.py # /ws (real-time)
│ └── main.py
├── frontend/
│ ├── src/
│ │ ├── components/
│ │ │ ├── Dashboard.vue # Stats + current job
│ │ │ ├── QueueManager.vue # Lista de trabajos
│ │ │ ├── JobDetails.vue # Detalles + logs
│ │ │ └── Settings.vue # Configuración
│ │ ├── App.vue
│ │ └── main.js
│ └── package.json
├── bazarr-integration/
│ └── transcriptorio_provider.py # Custom provider asíncrono
└── docker-compose.yml
```
### Base de datos (SQLite)
```sql
CREATE TABLE jobs (
id TEXT PRIMARY KEY,
file_path TEXT NOT NULL,
file_name TEXT NOT NULL,
status TEXT DEFAULT 'queued', -- queued, processing, completed, failed
priority INTEGER DEFAULT 0,
-- Config
source_lang TEXT,
target_lang TEXT,
quality_preset TEXT DEFAULT 'fast',
-- Progress
progress REAL DEFAULT 0,
current_stage TEXT, -- transcribing, translating, generating
eta_seconds INTEGER,
-- Timestamps
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
started_at TIMESTAMP,
completed_at TIMESTAMP,
-- Results
output_path TEXT,
srt_content TEXT,
segments_count INTEGER,
-- Error handling
error TEXT,
retry_count INTEGER DEFAULT 0,
-- Metadata
worker_id TEXT,
vram_used_mb INTEGER,
processing_time_seconds REAL
);
CREATE INDEX idx_status ON jobs(status);
CREATE INDEX idx_priority ON jobs(priority DESC, created_at ASC);
CREATE INDEX idx_created ON jobs(created_at DESC);
```
### API Endpoints
#### Legacy (compatibilidad SubGen/Bazarr)
```http
POST /asr?task=translate&language=ja&output=srt
Content-Type: multipart/form-data
→ Respuesta síncrona con .srt
```
#### Modernos (TranscriptorIO)
```http
# Añadir trabajo a cola
POST /api/queue/add
{
"files": ["/media/anime/episode.mkv"],
"source_lang": "ja",
"target_lang": "es",
"quality_preset": "fast",
"priority": 0
}
→ { "job_ids": ["uuid-1234"], "queued": 1 }
# Estado de la cola
GET /api/queue/status
→ {
"pending": 3,
"processing": 1,
"completed_today": 12,
"failed_today": 0,
"vram_available": "1.5GB/4GB"
}
# Detalles de trabajo
GET /api/jobs/{job_id}
→ {
"id": "uuid-1234",
"status": "processing",
"progress": 45.2,
"current_stage": "translating",
"eta_seconds": 120,
"file_name": "anime_ep01.mkv"
}
# Historial
GET /api/jobs/history?limit=50
→ [ { job }, { job }, ... ]
# WebSocket updates
WS /ws
→ Stream continuo de updates
```
### WebUI
#### Dashboard
```
┌─────────────────────────────────────────────────────────┐
│ TranscriptorIO 🟢 │
├─────────────────────────────────────────────────────────┤
│ │
│ 📊 Stats │
│ ┌─────────┬──────────┬──────────┬─────────────────┐ │
│ │ Queue: 3│Processing│Completed │ VRAM: 2.8/4.0GB │ │
│ │ │ 1 │ Today │ │ │
│ │ │ │ 12 │ │ │
│ └─────────┴──────────┴──────────┴─────────────────┘ │
│ │
│ 🎬 Current Job │
│ ┌─────────────────────────────────────────────────┐ │
│ │ File: Anime_S01E05.mkv │ │
│ │ Stage: Translating segments │ │
│ │ Progress: ████████████░░░░░░ 65% │ │
│ │ ETA: 2m 15s │ │
│ │ Model: whisper-medium + helsinki-nlp │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ 📋 Queue (3 pending) │
│ ┌──┬─────────────────────┬────────┬──────────────┐ │
│ │#1│Anime_S01E06.mkv │ Fast │ Priority: 0 │ │
│ │#2│Movie_2024.mkv │ Best │ Priority: 0 │ │
│ │#3│Show_S02E01.mkv │ Fast │ Priority: -1 │ │
│ └──┴─────────────────────┴────────┴──────────────┘ │
│ │
│ [+ Add Files] [⚙️ Settings] [📊 Stats] [📖 Logs] │
└─────────────────────────────────────────────────────────┘
```
#### Settings
```
┌─────────────────────────────────────────────────────────┐
│ Settings │
├─────────────────────────────────────────────────────────┤
│ │
│ 🎯 Default Quality Preset │
│ ○ Fast (4GB VRAM, ~3min/episode) │
│ Whisper medium + Helsinki-NLP │
│ Best for: GTX 1650, RTX 3050 │
│ │
│ ● Balanced (6GB VRAM, ~5min/episode) │
│ Whisper medium + M2M100 │
│ Best for: RTX 3060, RTX 4060 │
│ │
│ ○ Best (10GB+ VRAM, ~10min/episode) │
│ SeamlessM4T direct translation │
│ Best for: RTX 4070+, professional GPUs │
│ │
│ ⚡ Workers Configuration │
│ GPU Workers: [2] ▾ │
│ CPU Workers: [1] ▾ │
│ Concurrent jobs per worker: [1] ▾ │
│ │
│ 🌐 Default Languages │
│ Source: [Japanese ▾] Target: [Spanish ▾] │
│ │
│ 📁 Paths │
│ Watch folders: /media/anime │
│ /media/movies │
│ Output format: {filename}.{lang}.srt │
│ │
│ 🔔 Notifications │
│ ☑ Discord webhook on completion │
│ ☑ Email on failure │
│ │
│ [Save Changes] [Reset Defaults] │
└─────────────────────────────────────────────────────────┘
```
## Pipeline de transcripción detallado
### Flujo Fast Preset (ja→en→es)
```python
# 1. Extracción de audio (si es video)
ffprobe detecta pistas de audio
Selecciona pista japonesa
Extrae con ffmpeg (opcional, Whisper acepta video directo)
# 2. Whisper transcripción
WhisperModel("medium", compute_type="int8")
transcribe(audio, language="ja", task="translate")
Output: Segmentos con timestamps en INGLÉS
Ejemplo:
[0.00s -> 3.50s] "Hello, welcome to today's episode"
[3.50s -> 7.80s] "We're going to see something interesting"
# 3. Traducción en→es (batch)
Helsinki-NLP/opus-mt-en-es
Batch de 32 segmentos a la vez
Mantiene timestamps originales
Ejemplo:
[0.00s -> 3.50s] "Hola, bienvenido al episodio de hoy"
[3.50s -> 7.80s] "Vamos a ver algo interesante"
# 4. Generación SRT
Formato timestamps + texto
Guarda archivo.es.srt
# 5. Post-processing (opcional)
- Aeneas re-sync (ajuste fino de timestamps)
- Subtitle styling (ASS format)
- Quality check (detección de errores)
```
### Uso de VRAM esperado
**GTX 1650 (4GB VRAM)**:
```
Fast preset:
- Whisper medium INT8: ~2.5GB
- Helsinki-NLP: ~1GB
- Overhead sistema: ~0.5GB
Total: ~4GB ✅ Cabe perfecto
Tiempo: ~3-5 min por episodio 24min
```
**RTX 3060 (12GB VRAM)**:
```
Balanced preset:
- Whisper large-v3 INT8: ~5GB
- M2M100: ~2GB
- Overhead: ~1GB
Total: ~8GB ✅ Sobra espacio
Tiempo: ~4-7 min por episodio 24min
```
## Integración con Bazarr
### Custom Provider (asíncrono)
```python
# bazarr/libs/subliminal_patch/providers/transcriptorio.py
class TranscriptorIOProvider(Provider):
"""
Provider asíncrono para TranscriptorIO
A diferencia del provider Whisper original, NO bloquea
"""
provider_name = 'transcriptorio'
def download_subtitle(self, subtitle):
# Si es búsqueda automática → async (no bloquea)
if not subtitle.manual_search:
job_id = self._queue_job(subtitle)
raise SubtitlePending(
job_id=job_id,
eta=self._estimate_time(subtitle)
)
# Si es búsqueda manual → sync con long polling
return self._process_sync(subtitle, timeout=600)
def _queue_job(self, subtitle):
"""Encola trabajo sin esperar"""
response = requests.post(
f"{self.endpoint}/api/queue/add",
json={
"file": subtitle.video.name,
"source_lang": "ja",
"target_lang": "es",
"quality_preset": self.quality_preset,
"callback_url": self._get_callback_url(subtitle.id)
},
headers={"X-API-Key": self.api_key}
)
return response.json()["job_ids"][0]
# Background task en Bazarr (cada 30s)
@scheduler.scheduled_job('interval', seconds=30)
def poll_transcriptorio_jobs():
"""Revisar trabajos completados"""
pending = db.get_pending_transcriptorio_jobs()
for job in pending:
status = get_job_status(job.provider_job_id)
if status['status'] == 'completed':
save_subtitle(job.subtitle_id, status['srt_content'])
db.mark_completed(job.id)
```
### Ventajas vs provider Whisper original
| Feature | Whisper (original) | TranscriptorIO |
|---------|-------------------|----------------|
| Bloquea thread Bazarr | ✅ Sí (3-10min) | ❌ No (async) |
| Timeout 24h si tarda | ✅ Sí | ❌ No |
| Cola visible | ❌ No | ✅ Sí (WebUI) |
| Retry automático | ❌ No | ✅ Sí |
| Priorización | ❌ No | ✅ Sí |
| Múltiples GPUs | ❌ No | ✅ Sí |
| WebUI | ❌ No | ✅ Sí |
## Roadmap de desarrollo
### Fase 1: MVP Backend (2-3 semanas)
**Objetivos**:
- [ ] Queue manager con SQLite
- [ ] Worker pool básico
- [ ] Pipeline Fast (Whisper + Helsinki-NLP)
- [ ] API REST completa
- [ ] Endpoint legacy `/asr` compatible
**Entregables**:
- Backend funcional headless
- Docker Compose para testing
- Documentación API
### Fase 2: WebUI (2-3 semanas)
**Objetivos**:
- [ ] Dashboard con stats
- [ ] Queue viewer con drag&drop
- [ ] Job details con logs
- [ ] Settings page
- [ ] WebSocket integration
**Entregables**:
- WebUI completa y funcional
- Mobile responsive
- Tema dark/light
### Fase 3: Bazarr Integration (1-2 semanas)
**Objetivos**:
- [ ] Custom provider asíncrono
- [ ] Background polling task
- [ ] Callback webhook support
- [ ] Testing con Bazarr real
**Entregables**:
- Provider plugin para Bazarr
- Documentación integración
- PR al repo de Bazarr (si aceptan)
### Fase 4: Features Avanzados (3-4 semanas)
**Objetivos**:
- [ ] Pipeline Balanced (M2M100)
- [ ] Pipeline Best (SeamlessM4T)
- [ ] Batch operations (temporadas)
- [ ] Scanner automático (inotify)
- [ ] Post-processing (Aeneas sync)
- [ ] Notificaciones (Discord, email)
**Entregables**:
- Sistema completo production-ready
- Docs completas
- Tests automatizados
### Fase 5: Release & Community (ongoing)
**Objetivos**:
- [ ] Docker Hub releases
- [ ] GitHub Actions CI/CD
- [ ] Documentación completa
- [ ] Video tutoriales
- [ ] Anuncio en comunidades
**Canales**:
- /r/selfhosted
- /r/homelab
- Discord de Bazarr
- LinuxServer.io
## Métricas de éxito
**Técnicas**:
- ✅ Procesa episodio 24min en <5min (GTX 1650)
- Uso VRAM <4GB total
- Queue persiste entre reinicios
- API response time <100ms
- WebUI load time <2s
**UX**:
- Setup en <15min para usuario promedio
- Zero-config con defaults razonables
- WebUI intuitiva (no necesita docs)
**Comunidad**:
- 🎯 100 stars en primer mes
- 🎯 500 stars en 6 meses
- 🎯 10+ contributors
- 🎯 Featured en LinuxServer.io
## Diferenciadores clave
### vs SubGen
- WebUI moderna vs Sin UI
- Cola asíncrona vs Queue simple
- Múltiples presets vs Config manual
- Worker pool vs Single process
### vs Tdarr
- Específico para subtítulos vs 🔧 General transcoding
- Integración Bazarr nativa vs Solo webhooks
- Traducción multilingüe vs No traduce
### vs Whisper-ASR-Webservice
- Cola persistente vs Stateless
- WebUI vs Solo API
- Múltiples pipelines vs Solo Whisper
## Consideraciones técnicas
### Limitaciones conocidas
**Whisper**:
- Solo traduce a inglés (limitación del modelo)
- Necesita audio limpio (música de fondo degrada calidad)
- Nombres propios se traducen mal
- Honoríficos japoneses se pierden
**Traducción**:
- Helsinki-NLP a veces muy literal
- Expresiones idiomáticas se pierden
- Sin contexto entre segmentos
**Hardware**:
- GPU mínima: GTX 1050 Ti (4GB VRAM)
- Recomendada: RTX 3060 (12GB VRAM)
- CPU funciona pero 10x más lento
### Mitigaciones
**Mejorar calidad**:
- Usar Balanced/Best presets si hay VRAM
- Post-processing con Aeneas para mejor sync
- Manual review de nombres propios
- Context prompting en Whisper
**Optimizar velocidad**:
- Batch translation (32 segments)
- Cache de modelos en VRAM
- Pipeline paralelo (transcribe + traduce simultáneo)
## Stack de desarrollo
### Backend
```
Python 3.11+
FastAPI 0.100+
SQLite 3.40+
faster-whisper 1.0+
transformers 4.35+
torch 2.1+ (CUDA 12.x)
```
### Frontend
```
Node 20+
Vue 3.4+
Vite 5+
Tailwind CSS 3.4+
Socket.io-client 4.7+
Chart.js 4.4+
```
### DevOps
```
Docker 24+
Docker Compose 2.20+
GitHub Actions
Docker Hub
```
## Licencia
**Apache 2.0** (misma que SubGen)
Permite:
- Uso comercial
- Modificación
- Distribución
- Uso privado
Requiere:
- Incluir licencia y copyright
- Documentar cambios
## Contacto
- **GitHub**: `github.com/[tu-usuario]/transcriptorio`
- **Discord**: [crear servidor]
- **Email**: [configurar]
## Referencias
- SubGen original: https://github.com/McCloudS/subgen
- Bazarr: https://github.com/morpheus65535/bazarr
- Whisper: https://github.com/openai/whisper
- faster-whisper: https://github.com/guillaumekln/faster-whisper
- stable-ts: https://github.com/jianfch/stable-ts
- Tdarr: https://github.com/HaveAGitGat/Tdarr
---
**Última actualización**: 2026-01-11
**Versión**: 0.1.0-planning
**Estado**: En diseño