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

22 KiB

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 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:

# 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:

# 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:

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)

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)

POST /asr?task=translate&language=ja&output=srt
Content-Type: multipart/form-data

→ Respuesta síncrona con .srt

Modernos (TranscriptorIO)

# 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)

# 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)

# 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 No
Cola visible No Sí (WebUI)
Retry automático No
Priorización No
Múltiples GPUs No
WebUI No

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


Última actualización: 2026-01-11 Versión: 0.1.0-planning Estado: En diseño