Panel Alexamigo
← Memory

transcrip-beepeek.md

# Transcrip Beepeek — Detalle completo

## Info General
- **URL**: `https://transcrip.beepeek.com` | **Puerto**: 5020
- **VPS path**: `/home/ubuntu/proyectos-cloud/transcrip-beepeek/`
- **Stack**: FastAPI + Jinja2 + SQLite + yt-dlp + RunPod GPU (Whisper)
- **Auth**: HTTP Basic `alex` / `BeePanel2026!`
- **Service**: `sudo systemctl restart transcrip-beepeek`

## Arquitectura
1. **Frontend**: Jinja2 template (`templates/index.html`) con tabs Main + API Keys
2. **Backend**: FastAPI (`main.py`) con endpoints web + API para IAs
3. **Processor**: Daemon thread (`job_processor.py`) que procesa jobs en background
4. **Downloader**: `downloader.py` — yt-dlp con proxy Webshare rotativo
5. **GPU Worker**: `gpu_worker.py` — RunPod para transcribir audio sin subtitulos
6. **DB**: SQLite (`transcrip.db`) via `db.py`
7. **Apify**: `apify_subs.py` — modulo opcional, NO en flujo principal

## Flujo de procesamiento (YouTube)
1. Job creado como `pending`
2. Processor cambia a `extracting_subs`
3. yt-dlp intenta extraer subtitulos (manual primero, auto-generados despues) via proxy Webshare
4. Si hay subs → `done` (la mayoria caen aqui)
5. Si no hay subs → descarga audio (`downloading`) → `queued_gpu`
6. GPU batch con RunPod Whisper → `done`

## Proxy System (Webshare Rotating Residential)
- **Cuenta**: user `ivgivhed` / pass `s11pm2bew047`
- **API Token**: `3w9vqsu3vxwy7oo10800hyhtjqg31r7rd9lno02u`
- **Formato**: `http://ivgivhed-{N}:s11pm2bew047@p.webshare.io:80` donde N = 1..50000
- **Cada -N da una IP residencial diferente** (215K+ backbone proxies, 80M+ IPs)
- **Rotacion**: `_get_proxy()` genera N aleatorio en cada llamada = IP diferente cada vez
- **Retry**: `_run_ytdlp()` reintenta hasta 2 veces con proxy fresco en 429/timeout/HTTP error
- **Fallback**: proxies gratis `34.96.238.40:8080`, `159.223.71.162:8080`
- **Config**: `WEBSHARE_MAX_ID = 50000`, `YOUTUBE_RATE_LIMIT = 1`
- **Paises disponibles**: 830K+ IPs ES, 6.6M+ US, 1.5M+ DE/GB

## API Keys (aislamiento por caller)
- Tabla `api_keys` (id, name, key, created_at) | Prefijo `btk_`
- Cada job tiene `api_key_id` → filtra por caller en todos los endpoints `/ai/*`
- Frontend: tab "API Keys" con generar/listar/borrar + instrucciones copiables para IA
- Endpoints gestion: `POST /api-keys`, `GET /api-keys`, `DELETE /api-keys/{id}` (auth HTTP Basic)

## Endpoints API para IAs
- `POST /ai/transcribe` — enviar URLs YouTube (body: `{"urls": [...], "language": "es"}`)
- `GET /ai/jobs/{job_id}` — consultar estado de un job
- `GET /ai/transcripts` — listar transcripciones completadas
- `GET /ai/transcripts/{id}` — leer transcripcion individual
- `POST /ai/translate/{job_id}` — traducir transcripcion (Gemini)
- Header: `Authorization: Bearer btk_XXXXX`

## Archivos principales
| Archivo | Que hace |
|---------|----------|
| `main.py` | FastAPI app, endpoints web + API, auth |
| `db.py` | SQLite ORM: jobs, api_keys, CRUD |
| `config.py` | Config: auth, RunPod, Whisper, proxies, paths |
| `downloader.py` | yt-dlp: title, subs, audio download, proxy rotation |
| `job_processor.py` | Daemon thread: pending→extracting→done/gpu |
| `gpu_worker.py` | RunPod GPU: crear pod, subir audio, Whisper, bajar transcript |
| `apify_subs.py` | Apify YouTube Scraper (opcional, no en flujo principal) |
| `templates/index.html` | Frontend Jinja2: jobs table, API keys management |

## Credenciales
- **Apify**: token `apify_api_0IUWCEIrYVteBEJ2pFUiAgLXsBeWTW3POHof` | Actor `h7sDV53CddomktSi5`
- **RunPod**: key en config.py (la nueva de abr 2026)
- **Gemini**: `AIzaSyB84Vfxlh12JY-Ni_byuoCnFFlQeWZjoEo` (para traduccion)

## Decisiones de diseno
- **Apify descartado del flujo principal**: perdia ~10s por video buscando subs que no encontraba. Disponible como modulo si se necesita.
- **youtube-transcript-api descartado**: YouTube bloquea IP del VPS (ni con proxy funciona bien desde server)
- **Webshare > proxies gratis**: IPs residenciales no se banean tan facil, rotacion real por cada request
- **Rate limit 1s**: con IPs diferentes cada vez no hace falta esperar mas
- **Retry con proxy fresco**: si un proxy da timeout o 429, se cambia IP y reintenta (hasta 2 veces)