Update Docker configuration and documentation after removing Streamlit frontend
This commit is contained in:
parent
6e3d3356e7
commit
f7cb65cbc0
@ -1,3 +1,7 @@
|
|||||||
|
# Nota: Streamlit eliminado
|
||||||
|
|
||||||
|
> El panel Streamlit fue eliminado en esta rama; las instrucciones que mencionan Streamlit se mantienen solo como referencia histórica. Usa la API (main.py) para operaciones actuales.
|
||||||
|
|
||||||
# 🐳 Guía de Uso con Docker - TubeScript-API
|
# 🐳 Guía de Uso con Docker - TubeScript-API
|
||||||
|
|
||||||
## 🎯 Descripción
|
## 🎯 Descripción
|
||||||
@ -77,11 +81,6 @@ docker-compose logs -f
|
|||||||
|
|
||||||
Una vez iniciados los contenedores:
|
Una vez iniciados los contenedores:
|
||||||
|
|
||||||
### Panel Web Streamlit (Frontend)
|
|
||||||
```
|
|
||||||
http://localhost:8501
|
|
||||||
```
|
|
||||||
|
|
||||||
### API FastAPI (Backend)
|
### API FastAPI (Backend)
|
||||||
```
|
```
|
||||||
http://localhost:8080
|
http://localhost:8080
|
||||||
@ -102,9 +101,9 @@ http://localhost:8080/docs
|
|||||||
├─────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────┤
|
||||||
│ │
|
│ │
|
||||||
│ ┌──────────────────┐ ┌──────────────────┐ │
|
│ ┌──────────────────┐ ┌──────────────────┐ │
|
||||||
│ │ Streamlit Panel │ │ FastAPI Backend │ │
|
│ │ FastAPI Backend │ ◄──│ Streamlit Panel │ │
|
||||||
│ │ (Puerto 8501) │◄────►│ (Puerto 8000) │ │
|
│ │ (Puerto 8000) │ │ (Puerto 8501) │ │
|
||||||
│ │ streamlit_panel │ │ tubescript_api │ │
|
│ │ tubescript_api │ │ streamlit_panel │ │
|
||||||
│ └──────────────────┘ └──────────────────┘ │
|
│ └──────────────────┘ └──────────────────┘ │
|
||||||
│ │ │ │
|
│ │ │ │
|
||||||
│ └─────────┬───────────────┘ │
|
│ └─────────┬───────────────┘ │
|
||||||
@ -155,9 +154,6 @@ docker-compose logs -f
|
|||||||
# O usar el script
|
# O usar el script
|
||||||
./docker-logs.sh
|
./docker-logs.sh
|
||||||
|
|
||||||
# Solo el panel Streamlit
|
|
||||||
docker-compose logs -f streamlit-panel
|
|
||||||
|
|
||||||
# Solo la API
|
# Solo la API
|
||||||
docker-compose logs -f tubescript-api
|
docker-compose logs -f tubescript-api
|
||||||
```
|
```
|
||||||
@ -178,7 +174,6 @@ docker-compose down
|
|||||||
docker-compose restart
|
docker-compose restart
|
||||||
|
|
||||||
# Reiniciar uno específico
|
# Reiniciar uno específico
|
||||||
docker-compose restart streamlit-panel
|
|
||||||
docker-compose restart tubescript-api
|
docker-compose restart tubescript-api
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -198,11 +193,10 @@ docker-compose up -d --build
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Acceder al shell del contenedor
|
# Acceder al shell del contenedor
|
||||||
docker exec -it streamlit_panel bash
|
|
||||||
docker exec -it tubescript_api bash
|
docker exec -it tubescript_api bash
|
||||||
|
|
||||||
# Ejecutar comando en el contenedor
|
# Ejecutar comando en el contenedor
|
||||||
docker exec streamlit_panel ls -la
|
docker exec tubescript_api ls -la
|
||||||
```
|
```
|
||||||
|
|
||||||
### Limpiar Todo
|
### Limpiar Todo
|
||||||
@ -239,10 +233,6 @@ Edita `docker-compose.yml`:
|
|||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
services:
|
services:
|
||||||
streamlit-panel:
|
|
||||||
ports:
|
|
||||||
- "9090:8501" # Cambiar puerto del host a 9090
|
|
||||||
|
|
||||||
tubescript-api:
|
tubescript-api:
|
||||||
ports:
|
ports:
|
||||||
- "9091:8000" # Cambiar puerto del host a 9091
|
- "9091:8000" # Cambiar puerto del host a 9091
|
||||||
@ -255,7 +245,6 @@ services:
|
|||||||
Los servicios tienen health checks configurados:
|
Los servicios tienen health checks configurados:
|
||||||
|
|
||||||
- **FastAPI**: Verifica `/docs` cada 30 segundos
|
- **FastAPI**: Verifica `/docs` cada 30 segundos
|
||||||
- **Streamlit**: Verifica puerto 8501 cada 30 segundos
|
|
||||||
|
|
||||||
Ver estado de salud:
|
Ver estado de salud:
|
||||||
|
|
||||||
|
|||||||
53
DOCKER_RUN.md
Normal file
53
DOCKER_RUN.md
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# Ejecutar servicios Docker (API y herramientas) por separado
|
||||||
|
|
||||||
|
Este archivo explica cómo levantar el API de forma separada para probar cookies/proxy y cómo pasar la variable `API_PROXY` para usar Tor u otro proxy.
|
||||||
|
|
||||||
|
Levantar solo el API (recomendado para desarrollo):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reconstruir la imagen del API y levantar solo el servicio tubescript-api
|
||||||
|
API_PROXY="" docker compose -f docker-compose.yml build --no-cache tubescript-api
|
||||||
|
API_PROXY="" docker compose -f docker-compose.yml up -d tubescript-api
|
||||||
|
|
||||||
|
# Ver logs
|
||||||
|
docker logs -f tubescript_api
|
||||||
|
```
|
||||||
|
|
||||||
|
Levantar el API con proxy (Tor ejemplo):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Asegúrate de tener Tor corriendo en tu host (socks5 en 127.0.0.1:9050)
|
||||||
|
# macOS: brew install tor && tor &
|
||||||
|
|
||||||
|
# Levantar con API_PROXY apuntando a tor
|
||||||
|
API_PROXY="socks5h://host.docker.internal:9050" \
|
||||||
|
docker compose -f docker-compose.yml up -d --build tubescript-api
|
||||||
|
|
||||||
|
# Nota: en macOS dentro de contenedores, usa host.docker.internal para apuntar al host
|
||||||
|
```
|
||||||
|
|
||||||
|
Montar cookies y probar endpoints
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Asegúrate de que ./cookies.txt existe en la raíz del proyecto o súbelo con la API
|
||||||
|
curl -X POST "http://127.0.0.1:8000/upload_cookies" -F "file=@/ruta/a/cookies.txt"
|
||||||
|
|
||||||
|
# Probar metadata
|
||||||
|
curl -s "http://127.0.0.1:8000/debug/metadata/K08TM4OVLyo" | jq .
|
||||||
|
|
||||||
|
# Intento de subtítulos verboso
|
||||||
|
curl -s "http://127.0.0.1:8000/debug/fetch_subs/K08TM4OVLyo?lang=es" | jq .
|
||||||
|
|
||||||
|
# Obtener stream URL
|
||||||
|
curl -s "http://127.0.0.1:8000/stream/K08TM4OVLyo" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Rebuild del API (cuando hagas cambios en `main.py` o dependencias):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Reconstruir imagen (sin cache) y levantar
|
||||||
|
docker compose -f docker-compose.yml build --no-cache tubescript-api
|
||||||
|
docker compose -f docker-compose.yml up -d tubescript-api
|
||||||
|
|
||||||
|
# Si cambias requirements.txt, reconstruye la imagen para que pip instale nuevas dependencias
|
||||||
|
```
|
||||||
218
GUIA_CHROME_TRANSCRIPTS.md
Normal file
218
GUIA_CHROME_TRANSCRIPTS.md
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# 🎯 Guía Rápida: Obtener Transcripts de YouTube usando Chrome
|
||||||
|
|
||||||
|
## ✅ Método Recomendado: Usar cookies desde Chrome directamente
|
||||||
|
|
||||||
|
Este método es el **MÁS FÁCIL** porque no necesitas exportar cookies manualmente. yt-dlp lee las cookies directamente desde tu navegador.
|
||||||
|
|
||||||
|
### 📋 Requisitos
|
||||||
|
- Chrome, Firefox o Brave instalado
|
||||||
|
- Estar logueado en YouTube en el navegador
|
||||||
|
- yt-dlp actualizado: `pip install -U yt-dlp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Opción 1: Usar el script automático (Recomendado)
|
||||||
|
|
||||||
|
### Paso 1: Ejecutar el script
|
||||||
|
```bash
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ejemplos:**
|
||||||
|
```bash
|
||||||
|
# Usar perfil por defecto de Chrome
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo
|
||||||
|
|
||||||
|
# Especificar idioma
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo es
|
||||||
|
|
||||||
|
# Usar Firefox en lugar de Chrome
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo es firefox
|
||||||
|
|
||||||
|
# Usar un perfil específico de Chrome
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo es chrome Default
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo es chrome "Profile 1"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Paso 2: Resultado
|
||||||
|
El script generará:
|
||||||
|
- `VIDEO_ID.LANG.vtt` - Archivo de subtítulos
|
||||||
|
- `VIDEO_ID_transcript.txt` - Texto plano del transcript
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Opción 2: Comando manual de yt-dlp
|
||||||
|
|
||||||
|
### Comando básico
|
||||||
|
```bash
|
||||||
|
yt-dlp --cookies-from-browser chrome \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Con perfil específico
|
||||||
|
```bash
|
||||||
|
yt-dlp --cookies-from-browser "chrome:Profile 1" \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📂 Encontrar tus perfiles de Chrome
|
||||||
|
|
||||||
|
### macOS:
|
||||||
|
```bash
|
||||||
|
ls -la ~/Library/Application\ Support/Google/Chrome/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux:
|
||||||
|
```bash
|
||||||
|
ls -la ~/.config/google-chrome/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows:
|
||||||
|
```cmd
|
||||||
|
dir "%LOCALAPPDATA%\Google\Chrome\User Data"
|
||||||
|
```
|
||||||
|
|
||||||
|
Los perfiles típicos son:
|
||||||
|
- `Default` - Perfil por defecto
|
||||||
|
- `Profile 1`, `Profile 2`, etc. - Perfiles adicionales
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 Ver qué perfil estás usando en Chrome
|
||||||
|
|
||||||
|
1. Abre Chrome
|
||||||
|
2. Haz clic en tu avatar (esquina superior derecha)
|
||||||
|
3. El nombre del perfil aparece en el menú
|
||||||
|
4. O ve a `chrome://version/` y busca "Profile Path"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ Solución de Problemas
|
||||||
|
|
||||||
|
### Problema 1: "ERROR: Unable to extract cookies"
|
||||||
|
**Solución**: Cierra Chrome completamente antes de ejecutar el comando.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# macOS: Cerrar Chrome por completo
|
||||||
|
killall "Google Chrome"
|
||||||
|
|
||||||
|
# Luego ejecuta el comando
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
### Problema 2: "HTTP Error 429"
|
||||||
|
Significa que YouTube está bloqueando tu IP. **Soluciones**:
|
||||||
|
|
||||||
|
1. **Usa Tor/VPN** (más efectivo):
|
||||||
|
```bash
|
||||||
|
# Instalar y arrancar Tor
|
||||||
|
brew install tor && tor &
|
||||||
|
|
||||||
|
# Usar con proxy
|
||||||
|
yt-dlp --cookies-from-browser chrome \
|
||||||
|
--proxy "socks5h://127.0.0.1:9050" \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Espera unas horas** y vuelve a intentar
|
||||||
|
3. **Usa otra red** (móvil 4G/5G, otra WiFi)
|
||||||
|
|
||||||
|
### Problema 3: No se generan archivos
|
||||||
|
Verifica que:
|
||||||
|
- Estés logueado en YouTube en ese navegador
|
||||||
|
- El video tenga subtítulos (prueba con otro video)
|
||||||
|
- Tengas la última versión de yt-dlp: `pip install -U yt-dlp`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📖 Ejemplos Prácticos
|
||||||
|
|
||||||
|
### Obtener transcript de un video en vivo
|
||||||
|
```bash
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID_EN_VIVO es chrome
|
||||||
|
```
|
||||||
|
|
||||||
|
### Obtener en múltiples idiomas
|
||||||
|
```bash
|
||||||
|
# Español
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID es chrome
|
||||||
|
|
||||||
|
# Inglés
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID en chrome
|
||||||
|
|
||||||
|
# Español (variantes latinoamericanas)
|
||||||
|
yt-dlp --cookies-from-browser chrome \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang "es,es-419,es-MX" --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Convertir VTT a texto plano
|
||||||
|
```bash
|
||||||
|
# Usando grep (rápido)
|
||||||
|
grep -v "WEBVTT" VIDEO_ID.es.vtt | grep -v "^$" | grep -v "^[0-9][0-9]:" > transcript.txt
|
||||||
|
|
||||||
|
# Usando el script de Python del proyecto
|
||||||
|
python3 << 'EOF'
|
||||||
|
from main import parse_subtitle_format
|
||||||
|
with open('VIDEO_ID.es.vtt', 'r') as f:
|
||||||
|
vtt = f.read()
|
||||||
|
segments = parse_subtitle_format(vtt, 'vtt')
|
||||||
|
text = '\n'.join([s['text'] for s in segments])
|
||||||
|
print(text)
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 Integración con la API
|
||||||
|
|
||||||
|
Una vez que obtengas el archivo VTT, puedes subirlo al API:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://127.0.0.1:8000/upload_vtt/VIDEO_ID" \
|
||||||
|
-F "file=@VIDEO_ID.es.vtt" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
La API te devolverá:
|
||||||
|
- `segments`: Array de segmentos parseados con timestamps
|
||||||
|
- `text`: Texto concatenado completo
|
||||||
|
- `count`: Número de segmentos
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 Resumen: ¿Qué método usar?
|
||||||
|
|
||||||
|
| Situación | Método Recomendado |
|
||||||
|
|-----------|-------------------|
|
||||||
|
| **Uso diario, varios videos** | Script `get_transcript_chrome.sh` |
|
||||||
|
| **Comando rápido, un video** | `yt-dlp --cookies-from-browser chrome ...` |
|
||||||
|
| **Tienes HTTP 429** | Usar Tor/VPN + cookies desde Chrome |
|
||||||
|
| **No puedes ejecutar comandos** | Subir VTT manualmente al API |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📞 Ayuda Adicional
|
||||||
|
|
||||||
|
Ver documentación completa: `SOLUCION_HTTP_429_TRANSCRIPT.md`
|
||||||
|
|
||||||
|
**Comando de ayuda del script:**
|
||||||
|
```bash
|
||||||
|
./get_transcript_chrome.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización**: 2025-02-22
|
||||||
@ -1,3 +1,7 @@
|
|||||||
|
# Nota: Streamlit eliminado
|
||||||
|
|
||||||
|
> El panel Streamlit fue eliminado en esta rama; este documento se mantiene solo como referencia histórica. Usa la API (main.py) para operar.
|
||||||
|
|
||||||
# 📺 TubeScript - Panel de Control Web
|
# 📺 TubeScript - Panel de Control Web
|
||||||
|
|
||||||
Panel de control web interactivo para gestionar transmisiones en vivo desde YouTube hacia múltiples plataformas de redes sociales simultáneamente.
|
Panel de control web interactivo para gestionar transmisiones en vivo desde YouTube hacia múltiples plataformas de redes sociales simultáneamente.
|
||||||
|
|||||||
173
QUICKSTART_TRANSCRIPTS.md
Normal file
173
QUICKSTART_TRANSCRIPTS.md
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
# 🚀 INICIO RÁPIDO: Obtener Transcripts de YouTube
|
||||||
|
|
||||||
|
## ⚡ Método Más Rápido (30 segundos)
|
||||||
|
|
||||||
|
### Usando cookies de Chrome directamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Asegúrate de estar logueado en YouTube en Chrome
|
||||||
|
# 2. Ejecuta este comando:
|
||||||
|
|
||||||
|
yt-dlp --cookies-from-browser chrome --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=K08TM4OVLyo"
|
||||||
|
|
||||||
|
# 3. Listo! Verás el archivo: K08TM4OVLyo.es.vtt
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 3 Formas de Obtener Transcripts
|
||||||
|
|
||||||
|
### 1️⃣ Script Bash (Recomendado - MÁS FÁCIL)
|
||||||
|
```bash
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ventajas**:
|
||||||
|
- ✅ Genera VTT + TXT automáticamente
|
||||||
|
- ✅ Muestra preview del contenido
|
||||||
|
- ✅ Maneja múltiples perfiles de Chrome
|
||||||
|
|
||||||
|
### 2️⃣ Script Python
|
||||||
|
```bash
|
||||||
|
python3 fetch_transcript.py VIDEO_ID es chrome
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ventajas**:
|
||||||
|
- ✅ Genera JSON + TXT
|
||||||
|
- ✅ Integrado con el proyecto
|
||||||
|
- ✅ Maneja formatos automáticamente
|
||||||
|
|
||||||
|
### 3️⃣ Comando directo yt-dlp
|
||||||
|
```bash
|
||||||
|
yt-dlp --cookies-from-browser chrome \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Ventajas**:
|
||||||
|
- ✅ Control total
|
||||||
|
- ✅ Personalizable
|
||||||
|
- ✅ Para usuarios avanzados
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔑 Usar Perfil Específico de Chrome
|
||||||
|
|
||||||
|
### Ver perfiles disponibles:
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
ls ~/Library/Application\ Support/Google/Chrome/
|
||||||
|
|
||||||
|
# Verás algo como:
|
||||||
|
# Default
|
||||||
|
# Profile 1
|
||||||
|
# Profile 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### Usar un perfil específico:
|
||||||
|
```bash
|
||||||
|
# Opción 1: Script bash
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID es chrome "Profile 1"
|
||||||
|
|
||||||
|
# Opción 2: Python
|
||||||
|
python3 fetch_transcript.py VIDEO_ID es "chrome:Profile 1"
|
||||||
|
|
||||||
|
# Opción 3: yt-dlp directo
|
||||||
|
yt-dlp --cookies-from-browser "chrome:Profile 1" \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ❌ Si te sale "HTTP Error 429"
|
||||||
|
|
||||||
|
YouTube está bloqueando tu IP. **Solución rápida con Tor**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Instalar Tor
|
||||||
|
brew install tor # macOS
|
||||||
|
# sudo apt install tor # Linux
|
||||||
|
|
||||||
|
# 2. Iniciar Tor
|
||||||
|
tor &
|
||||||
|
|
||||||
|
# 3. Usar con proxy
|
||||||
|
yt-dlp --cookies-from-browser chrome \
|
||||||
|
--proxy "socks5h://127.0.0.1:9050" \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Ejemplo Completo
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Obtener transcript
|
||||||
|
./get_transcript_chrome.sh K08TM4OVLyo es chrome
|
||||||
|
|
||||||
|
# Salida:
|
||||||
|
# ✅ Archivo generado: K08TM4OVLyo.es.vtt
|
||||||
|
# 💾 Texto guardado en: K08TM4OVLyo_transcript.txt
|
||||||
|
|
||||||
|
# 2. Ver el transcript
|
||||||
|
cat K08TM4OVLyo_transcript.txt
|
||||||
|
|
||||||
|
# 3. Subir al API (opcional)
|
||||||
|
curl -X POST "http://127.0.0.1:8000/upload_vtt/K08TM4OVLyo" \
|
||||||
|
-F "file=@K08TM4OVLyo.es.vtt" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🆘 Problemas Comunes
|
||||||
|
|
||||||
|
| Problema | Solución |
|
||||||
|
|----------|----------|
|
||||||
|
| "Unable to extract cookies" | Cierra Chrome: `killall "Google Chrome"` |
|
||||||
|
| "HTTP Error 429" | Usa Tor/VPN (ver arriba) |
|
||||||
|
| No se genera archivo | Verifica que estés logueado en YouTube |
|
||||||
|
| "yt-dlp not found" | Instala: `pip install yt-dlp` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 Más Información
|
||||||
|
|
||||||
|
- **Guía completa Chrome**: `GUIA_CHROME_TRANSCRIPTS.md`
|
||||||
|
- **Soluciones HTTP 429**: `SOLUCION_HTTP_429_TRANSCRIPT.md`
|
||||||
|
- **API Endpoints**: Consulta `/docs` de la API
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎬 Demo de 30 segundos
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Instalar yt-dlp (si no lo tienes)
|
||||||
|
pip install yt-dlp
|
||||||
|
|
||||||
|
# Obtener transcript
|
||||||
|
yt-dlp --cookies-from-browser chrome --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=K08TM4OVLyo"
|
||||||
|
|
||||||
|
# Convertir a texto plano
|
||||||
|
grep -v "WEBVTT" K08TM4OVLyo.es.vtt | grep -v "^$" | grep -v "^[0-9][0-9]:" > transcript.txt
|
||||||
|
|
||||||
|
# Ver el transcript
|
||||||
|
cat transcript.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
**¡Listo! 🎉**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización**: 2025-02-22
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
NOTA: El frontend Streamlit fue eliminado en esta rama. El documento conserva información histórica sobre el panel, pero el flujo actual se basa en la API (main.py).
|
||||||
|
|
||||||
╔══════════════════════════════════════════════════════════════════════════════╗
|
╔══════════════════════════════════════════════════════════════════════════════╗
|
||||||
║ ║
|
║ ║
|
||||||
║ ✅ ACTUALIZACIÓN COMPLETADA CON ÉXITO ║
|
║ ✅ ACTUALIZACIÓN COMPLETADA CON ÉXITO ║
|
||||||
|
|||||||
291
SOLUCION_HTTP_429_TRANSCRIPT.md
Normal file
291
SOLUCION_HTTP_429_TRANSCRIPT.md
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
# 🎯 Solución HTTP 429 - Extracción de Subtítulos YouTube
|
||||||
|
|
||||||
|
## 📋 Estado Actual
|
||||||
|
|
||||||
|
### ✅ Implementado
|
||||||
|
- ✅ Endpoint `/transcript/{video_id}` - Obtiene transcript parseado (segmentos + texto)
|
||||||
|
- ✅ Endpoint `/transcript_vtt/{video_id}` - Descarga VTT con yt-dlp y devuelve crudo + parseado
|
||||||
|
- ✅ Endpoint `/stream/{video_id}` - Obtiene URL m3u8 para streaming
|
||||||
|
- ✅ Endpoint `/upload_vtt/{video_id}` - Permite subir VTT manualmente y parsearlo
|
||||||
|
- ✅ Endpoint `/debug/metadata/{video_id}` - Muestra metadata de yt-dlp
|
||||||
|
- ✅ Endpoint `/debug/fetch_subs/{video_id}` - Intenta descargar con verbose y devuelve logs
|
||||||
|
- ✅ Soporte de cookies (`API_COOKIES_PATH=/app/cookies.txt`)
|
||||||
|
- ✅ Soporte de proxy (`API_PROXY=socks5h://127.0.0.1:9050`)
|
||||||
|
- ✅ Script `fetch_transcript.py` - CLI para obtener transcript y guardarlo en JSON/TXT
|
||||||
|
- ✅ Script `docker-update-ytdlp.sh` - Actualiza yt-dlp en contenedores sin rebuild
|
||||||
|
|
||||||
|
### ❌ Problema Actual
|
||||||
|
**HTTP Error 429: Too Many Requests** al intentar descargar subtítulos desde YouTube.
|
||||||
|
|
||||||
|
- **Causa**: YouTube está limitando peticiones al endpoint `timedtext` desde tu IP
|
||||||
|
- **Afectado**: Tanto `requests` como `yt-dlp` reciben 429
|
||||||
|
- **Ocurre**: Al intentar descargar subtítulos automáticos (ASR) de videos
|
||||||
|
|
||||||
|
## 🔧 Soluciones Disponibles
|
||||||
|
|
||||||
|
### Opción 1: Usar Proxy/Tor (Recomendado)
|
||||||
|
Evita el rate-limit cambiando la IP de salida.
|
||||||
|
|
||||||
|
#### Setup rápido con Tor:
|
||||||
|
```bash
|
||||||
|
# Instalar Tor
|
||||||
|
brew install tor # macOS
|
||||||
|
# sudo apt install tor # Linux
|
||||||
|
|
||||||
|
# Iniciar Tor
|
||||||
|
tor &
|
||||||
|
|
||||||
|
# Exportar proxy y arrancar API
|
||||||
|
export API_PROXY="socks5h://127.0.0.1:9050"
|
||||||
|
docker compose -f docker-compose.yml up -d --build tubescript-api
|
||||||
|
|
||||||
|
# Probar
|
||||||
|
curl "http://127.0.0.1:8000/transcript_vtt/K08TM4OVLyo?lang=es" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opción 2: Usar Cookies Directamente desde Chrome/Firefox (Recomendado)
|
||||||
|
`yt-dlp` puede leer cookies directamente desde tu navegador sin necesidad de exportarlas.
|
||||||
|
|
||||||
|
#### Opción 2A: Usar cookies del navegador directamente
|
||||||
|
```bash
|
||||||
|
# Chrome (macOS)
|
||||||
|
yt-dlp --cookies-from-browser chrome --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
|
||||||
|
# Chrome con perfil específico
|
||||||
|
yt-dlp --cookies-from-browser chrome:Profile1 --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
|
||||||
|
# Firefox
|
||||||
|
yt-dlp --cookies-from-browser firefox --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
|
||||||
|
# Brave
|
||||||
|
yt-dlp --cookies-from-browser brave --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=VIDEO_ID"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Encontrar perfiles de Chrome:**
|
||||||
|
```bash
|
||||||
|
# macOS
|
||||||
|
ls -la ~/Library/Application\ Support/Google/Chrome/
|
||||||
|
|
||||||
|
# Linux
|
||||||
|
ls -la ~/.config/google-chrome/
|
||||||
|
|
||||||
|
# Los perfiles típicos son: Default, Profile 1, Profile 2, etc.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Opción 2B: Exportar cookies manualmente (si la opción 2A no funciona)
|
||||||
|
1. Instala extensión "cookies.txt" en Chrome/Firefox
|
||||||
|
2. Abre YouTube estando logueado en tu cuenta
|
||||||
|
3. Exporta cookies (extensión → Export → `cookies.txt`)
|
||||||
|
4. Reemplaza `./cookies.txt` en la raíz del proyecto
|
||||||
|
5. Reinicia contenedor:
|
||||||
|
```bash
|
||||||
|
docker compose down
|
||||||
|
docker compose up -d --build tubescript-api
|
||||||
|
```
|
||||||
|
|
||||||
|
### Opción 3: Cambiar de IP
|
||||||
|
- Usar VPN
|
||||||
|
- Tethering móvil (4G/5G)
|
||||||
|
- Esperar algunas horas (el rate-limit puede ser temporal)
|
||||||
|
|
||||||
|
### Opción 4: Workaround Manual (Más Rápido)
|
||||||
|
Si necesitas el transcript YA y no puedes resolver el 429:
|
||||||
|
|
||||||
|
#### Opción 4A: Subir VTT manualmente al API
|
||||||
|
```bash
|
||||||
|
# Descarga el VTT desde otro equipo/navegador donde no esté bloqueado
|
||||||
|
# o pídele a alguien que te lo pase
|
||||||
|
|
||||||
|
# Súbelo al API
|
||||||
|
curl -X POST "http://127.0.0.1:8000/upload_vtt/VIDEO_ID" \
|
||||||
|
-H "Content-Type: multipart/form-data" \
|
||||||
|
-F "file=@/ruta/al/archivo.vtt" | jq .
|
||||||
|
|
||||||
|
# El API responde con: { segments, text, count, path }
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Opción 4B: Usar youtube-transcript-api (Python alternativo)
|
||||||
|
```bash
|
||||||
|
pip install youtube-transcript-api
|
||||||
|
|
||||||
|
python3 << 'EOF'
|
||||||
|
from youtube_transcript_api import YouTubeTranscriptApi
|
||||||
|
import json
|
||||||
|
|
||||||
|
video_id = "K08TM4OVLyo"
|
||||||
|
try:
|
||||||
|
transcript = YouTubeTranscriptApi.get_transcript(video_id, languages=['es'])
|
||||||
|
with open(f"{video_id}_transcript.json", 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(transcript, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"✅ Guardado: {video_id}_transcript.json")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Error: {e}")
|
||||||
|
EOF
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Opción 4C: Script usando cookies desde Chrome directamente
|
||||||
|
```bash
|
||||||
|
# Crear script que usa cookies del navegador
|
||||||
|
cat > get_transcript_chrome.sh << 'SCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
VIDEO_ID="${1:-K08TM4OVLyo}"
|
||||||
|
LANG="${2:-es}"
|
||||||
|
BROWSER="${3:-chrome}" # chrome, firefox, brave, etc.
|
||||||
|
|
||||||
|
echo "🔍 Obteniendo transcript de: $VIDEO_ID"
|
||||||
|
echo " Idioma: $LANG"
|
||||||
|
echo " Navegador: $BROWSER"
|
||||||
|
|
||||||
|
yt-dlp --cookies-from-browser "$BROWSER" \
|
||||||
|
--skip-download --write-auto-sub \
|
||||||
|
--sub-lang "$LANG" --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=$VIDEO_ID" 2>&1 | grep -E "Writing|ERROR|✓"
|
||||||
|
|
||||||
|
if [ -f "${VIDEO_ID}.${LANG}.vtt" ]; then
|
||||||
|
echo "✅ Archivo generado: ${VIDEO_ID}.${LANG}.vtt"
|
||||||
|
echo "📝 Primeras líneas:"
|
||||||
|
head -n 20 "${VIDEO_ID}.${LANG}.vtt"
|
||||||
|
else
|
||||||
|
echo "❌ No se generó el archivo VTT"
|
||||||
|
fi
|
||||||
|
SCRIPT
|
||||||
|
|
||||||
|
chmod +x get_transcript_chrome.sh
|
||||||
|
|
||||||
|
# Usar el script
|
||||||
|
./get_transcript_chrome.sh VIDEO_ID es chrome
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📚 Uso de Endpoints
|
||||||
|
|
||||||
|
### 1. Obtener Transcript (intenta automáticamente con yt-dlp)
|
||||||
|
```bash
|
||||||
|
curl "http://127.0.0.1:8000/transcript/K08TM4OVLyo?lang=es" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Respuesta:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"video_id": "K08TM4OVLyo",
|
||||||
|
"count": 150,
|
||||||
|
"segments": [...],
|
||||||
|
"text": "texto concatenado de todos los segmentos"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Obtener VTT Crudo + Parseado
|
||||||
|
```bash
|
||||||
|
curl "http://127.0.0.1:8000/transcript_vtt/K08TM4OVLyo?lang=es" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Respuesta:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"video_id": "K08TM4OVLyo",
|
||||||
|
"vtt": "WEBVTT\n\n00:00:00.000 --> 00:00:02.000\nHola...",
|
||||||
|
"count": 150,
|
||||||
|
"segments": [...],
|
||||||
|
"text": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Debug: Ver Metadata
|
||||||
|
```bash
|
||||||
|
curl "http://127.0.0.1:8000/debug/metadata/K08TM4OVLyo" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Debug: Intentar Descarga Verbose
|
||||||
|
```bash
|
||||||
|
curl "http://127.0.0.1:8000/debug/fetch_subs/K08TM4OVLyo?lang=es" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
Respuesta incluye:
|
||||||
|
- `rc`: código de salida de yt-dlp
|
||||||
|
- `stdout_tail`: últimas 2000 chars de stdout
|
||||||
|
- `stderr_tail`: últimas 2000 chars de stderr (aquí verás "HTTP Error 429")
|
||||||
|
- `generated`: lista de archivos generados (si hubo éxito)
|
||||||
|
|
||||||
|
### 5. Subir VTT Manualmente
|
||||||
|
```bash
|
||||||
|
curl -X POST "http://127.0.0.1:8000/upload_vtt/K08TM4OVLyo" \
|
||||||
|
-F "file=@K08TM4OVLyo.vtt" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🐳 Docker
|
||||||
|
|
||||||
|
### Comandos útiles
|
||||||
|
```bash
|
||||||
|
# Rebuild y levantar (aplica cambios en main.py)
|
||||||
|
docker compose -f docker-compose.yml build --no-cache tubescript-api
|
||||||
|
docker compose -f docker-compose.yml up -d tubescript-api
|
||||||
|
|
||||||
|
# Ver logs
|
||||||
|
docker logs -f tubescript_api
|
||||||
|
|
||||||
|
# Actualizar yt-dlp (sin rebuild)
|
||||||
|
bash docker-update-ytdlp.sh
|
||||||
|
|
||||||
|
# Entrar al contenedor
|
||||||
|
docker exec -it tubescript_api /bin/sh
|
||||||
|
|
||||||
|
# Verificar cookies montadas
|
||||||
|
docker exec -it tubescript_api cat /app/cookies.txt | head -n 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### Variables de Entorno
|
||||||
|
```yaml
|
||||||
|
environment:
|
||||||
|
- API_COOKIES_PATH=/app/cookies.txt
|
||||||
|
- API_PROXY=socks5h://127.0.0.1:9050 # opcional
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔍 Diagnóstico
|
||||||
|
|
||||||
|
### Verificar HTTP 429
|
||||||
|
```bash
|
||||||
|
# Host (local)
|
||||||
|
yt-dlp --verbose --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
--cookies ./cookies.txt -o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=K08TM4OVLyo" 2>&1 | grep -i "error\|429"
|
||||||
|
|
||||||
|
# Dentro del contenedor
|
||||||
|
docker exec -it tubescript_api sh -c \
|
||||||
|
"yt-dlp --verbose --skip-download --write-auto-sub \
|
||||||
|
--sub-lang es --sub-format vtt \
|
||||||
|
--cookies /app/cookies.txt -o '/tmp/%(id)s.%(ext)s' \
|
||||||
|
'https://www.youtube.com/watch?v=K08TM4OVLyo'" 2>&1 | grep -i "error\|429"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Probar con otro video
|
||||||
|
```bash
|
||||||
|
# Prueba con un video de noticias 24/7 (menos probabilidad de 429)
|
||||||
|
curl "http://127.0.0.1:8000/transcript/NNL3iiDf1HI?lang=es" | jq .
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📖 Referencias
|
||||||
|
|
||||||
|
- [yt-dlp GitHub](https://github.com/yt-dlp/yt-dlp)
|
||||||
|
- [Guía PO Token (si yt-dlp requiere)](https://github.com/yt-dlp/yt-dlp/wiki/PO-Token-Guide)
|
||||||
|
- [youtube-transcript-api](https://github.com/jdepoix/youtube-transcript-api)
|
||||||
|
|
||||||
|
## 🎯 Próximos Pasos
|
||||||
|
|
||||||
|
1. **Inmediato**: Probar Opción 1 (Tor) o Opción 4B (youtube-transcript-api)
|
||||||
|
2. **Corto plazo**: Re-exportar cookies válidas (Opción 2)
|
||||||
|
3. **Mediano plazo**: Implementar rotación de IPs/proxies automática
|
||||||
|
4. **Largo plazo**: Considerar usar YouTube Data API v3 (requiere API key pero evita rate-limits)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Última actualización**: 2025-02-22
|
||||||
|
**Estado**: HTTP 429 confirmado; soluciones alternativas implementadas
|
||||||
@ -5,18 +5,42 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile.api
|
dockerfile: Dockerfile.api
|
||||||
image: tubescript-api:local
|
|
||||||
container_name: tubescript-api
|
container_name: tubescript-api
|
||||||
|
image: tubescript-api:local
|
||||||
ports:
|
ports:
|
||||||
- "8000:8000"
|
- "8000:8000"
|
||||||
environment:
|
|
||||||
- TZ=UTC
|
|
||||||
- API_BASE_URL=${API_BASE_URL:-http://localhost:8000}
|
|
||||||
volumes:
|
volumes:
|
||||||
- ./cookies.txt:/app/cookies.txt
|
- ./cookies.txt:/app/cookies.txt
|
||||||
|
- ./stream_config.json:/app/stream_config.json:ro
|
||||||
|
- ./streams_state.json:/app/streams_state.json:rw
|
||||||
|
environment:
|
||||||
|
API_BASE_URL: http://localhost:8000
|
||||||
|
TZ: UTC
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8000/docs"]
|
test: ["CMD", "curl", "-f", "http://localhost:8000/docs"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
timeout: 10s
|
timeout: 10s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
|
||||||
|
streamlit:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.streamlit
|
||||||
|
container_name: tubescript-streamlit
|
||||||
|
image: tubescript-streamlit:local
|
||||||
|
depends_on:
|
||||||
|
- api
|
||||||
|
ports:
|
||||||
|
- "8501:8501"
|
||||||
|
volumes:
|
||||||
|
- ./stream_config.json:/app/stream_config.json:ro
|
||||||
|
- ./cookies.txt:/app/cookies.txt:ro
|
||||||
|
environment:
|
||||||
|
API_BASE_URL: http://localhost:8000
|
||||||
|
TZ: UTC
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: tubescript-api_default
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
# Servicio FastAPI - Backend API
|
# Servicio FastAPI - Backend API
|
||||||
tubescript-api:
|
tubescript-api:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile.api
|
||||||
container_name: tubescript_api
|
container_name: tubescript_api
|
||||||
|
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
ports:
|
ports:
|
||||||
- "8080:8000"
|
- "8000:8000"
|
||||||
volumes:
|
volumes:
|
||||||
- ./cookies.txt:/app/cookies.txt:ro # Solo lectura
|
- ./:/app:rw
|
||||||
- ./stream_config.json:/app/stream_config.json
|
- ./cookies.txt:/app/cookies.txt:ro
|
||||||
|
- ./stream_config.json:/app/stream_config.json:ro
|
||||||
- ./streams_state.json:/app/streams_state.json
|
- ./streams_state.json:/app/streams_state.json
|
||||||
- ./data:/app/data # Directorio para datos persistentes
|
- ./data:/app/data
|
||||||
environment:
|
environment:
|
||||||
- PYTHONUNBUFFERED=1
|
- PYTHONUNBUFFERED=1
|
||||||
|
- API_COOKIES_PATH=/app/cookies.txt
|
||||||
|
# Optional: set API_PROXY when you want the container to use a SOCKS/HTTP proxy (e.g. tor)
|
||||||
|
- API_PROXY=${API_PROXY:-}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- tubescript-network
|
- tubescript-network
|
||||||
|
|||||||
@ -8,16 +8,20 @@ echo "🛑 Deteniendo servicios..."
|
|||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Detener servicios individuales
|
# Detener servicios individuales
|
||||||
echo "Deteniendo FastAPI..."
|
services=(tubescript_api streamlit_panel)
|
||||||
docker stop tubescript_api 2>/dev/null && echo "✅ FastAPI detenido" || echo "⚠️ FastAPI no estaba corriendo"
|
|
||||||
|
|
||||||
echo "Deteniendo Streamlit..."
|
for s in "${services[@]}"; do
|
||||||
docker stop streamlit_panel 2>/dev/null && echo "✅ Streamlit detenido" || echo "⚠️ Streamlit no estaba corriendo"
|
if docker ps -a --format '{{.Names}}' | grep -q "^$s$"; then
|
||||||
|
echo "Deteniendo $s..."
|
||||||
|
docker stop $s 2>/dev/null && echo "✅ $s detenido" || echo "⚠️ $s no estaba corriendo"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "🗑️ Eliminando contenedores..."
|
echo "🗑️ Eliminando contenedores..."
|
||||||
docker rm tubescript_api 2>/dev/null
|
for s in "${services[@]}"; do
|
||||||
docker rm streamlit_panel 2>/dev/null
|
docker rm $s 2>/dev/null
|
||||||
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Todos los servicios han sido detenidos"
|
echo "✅ Todos los servicios han sido detenidos"
|
||||||
|
|||||||
@ -29,21 +29,23 @@ print_error() {
|
|||||||
echo "🔍 Verificando contenedores..."
|
echo "🔍 Verificando contenedores..."
|
||||||
|
|
||||||
if ! docker ps | grep -q streamlit_panel; then
|
if ! docker ps | grep -q streamlit_panel; then
|
||||||
print_error "El contenedor streamlit_panel no está corriendo"
|
print_warning "El contenedor streamlit_panel no está corriendo"
|
||||||
echo "Inicia los contenedores con: docker-compose up -d"
|
else
|
||||||
exit 1
|
print_success "Contenedor streamlit_panel encontrado"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! docker ps | grep -q tubescript_api; then
|
if ! docker ps | grep -q tubescript_api; then
|
||||||
print_error "El contenedor tubescript_api no está corriendo"
|
print_error "El contenedor tubescript_api no está corriendo"
|
||||||
echo "Inicia los contenedores con: docker-compose up -d"
|
echo "Inicia los contenedores con: docker-compose up -d"
|
||||||
exit 1
|
exit 1
|
||||||
|
else
|
||||||
|
print_success "Contenedor tubescript_api encontrado"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
print_success "Contenedores encontrados"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Actualizar yt-dlp en streamlit_panel
|
# Actualizar yt-dlp en streamlit_panel si existe
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q '^streamlit_panel$'; then
|
||||||
echo "📦 Actualizando yt-dlp en streamlit_panel..."
|
echo "📦 Actualizando yt-dlp en streamlit_panel..."
|
||||||
docker exec streamlit_panel pip install --upgrade yt-dlp
|
docker exec streamlit_panel pip install --upgrade yt-dlp
|
||||||
|
|
||||||
@ -58,10 +60,14 @@ if [ $? -eq 0 ]; then
|
|||||||
else
|
else
|
||||||
print_error "Error al actualizar yt-dlp en streamlit_panel"
|
print_error "Error al actualizar yt-dlp en streamlit_panel"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo "streamlit_panel no encontrado — omitiendo actualización en Streamlit"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
|
|
||||||
# Actualizar yt-dlp en tubescript_api
|
# Actualizar yt-dlp en tubescript_api
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q '^tubescript_api$'; then
|
||||||
echo "📦 Actualizando yt-dlp en tubescript_api..."
|
echo "📦 Actualizando yt-dlp en tubescript_api..."
|
||||||
docker exec tubescript_api pip install --upgrade yt-dlp
|
docker exec tubescript_api pip install --upgrade yt-dlp
|
||||||
|
|
||||||
@ -76,12 +82,16 @@ if [ $? -eq 0 ]; then
|
|||||||
else
|
else
|
||||||
print_error "Error al actualizar yt-dlp en tubescript_api"
|
print_error "Error al actualizar yt-dlp en tubescript_api"
|
||||||
fi
|
fi
|
||||||
|
else
|
||||||
|
echo "tubescript_api no encontrado — omitiendo actualización en API"
|
||||||
|
fi
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════"
|
||||||
print_success "Actualización completada"
|
print_success "Actualización completada"
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
echo "═══════════════════════════════════════════════════════════"
|
||||||
echo ""
|
echo ""
|
||||||
echo "💡 Ahora puedes probar con un video en vivo en:"
|
echo "💡 Ahora puedes probar con un video en vivo en la API Docs:"
|
||||||
echo " http://localhost:8501"
|
echo " http://localhost:8080/docs"
|
||||||
|
echo " Para obtener stream URL: curl http://localhost:8080/stream/VIDEO_ID"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
123
fetch_transcript.py
Executable file
123
fetch_transcript.py
Executable file
@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Script para obtener transcript de un video de YouTube usando las funciones del proyecto.
|
||||||
|
Maneja HTTP 429 y guarda el resultado en JSON.
|
||||||
|
|
||||||
|
Uso:
|
||||||
|
python3 fetch_transcript.py VIDEO_ID [LANG] [BROWSER]
|
||||||
|
|
||||||
|
Ejemplos:
|
||||||
|
python3 fetch_transcript.py K08TM4OVLyo es
|
||||||
|
python3 fetch_transcript.py K08TM4OVLyo es chrome
|
||||||
|
python3 fetch_transcript.py K08TM4OVLyo es "chrome:Profile 1"
|
||||||
|
"""
|
||||||
|
import sys
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import glob
|
||||||
|
from main import parse_subtitle_format
|
||||||
|
|
||||||
|
def fetch_with_browser_cookies(video_id, lang="es", browser="chrome"):
|
||||||
|
"""Intenta obtener transcript usando cookies desde el navegador directamente."""
|
||||||
|
print(f"🔑 Usando cookies desde navegador: {browser}")
|
||||||
|
|
||||||
|
with tempfile.TemporaryDirectory() as tmpdir:
|
||||||
|
cmd = [
|
||||||
|
"yt-dlp",
|
||||||
|
"--cookies-from-browser", browser,
|
||||||
|
"--skip-download",
|
||||||
|
"--write-auto-sub",
|
||||||
|
"--write-sub",
|
||||||
|
"--sub-lang", lang,
|
||||||
|
"--sub-format", "vtt",
|
||||||
|
"-o", os.path.join(tmpdir, "%(id)s.%(ext)s"),
|
||||||
|
f"https://www.youtube.com/watch?v={video_id}"
|
||||||
|
]
|
||||||
|
|
||||||
|
try:
|
||||||
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=180)
|
||||||
|
|
||||||
|
# Buscar archivos VTT generados
|
||||||
|
files = glob.glob(os.path.join(tmpdir, f"{video_id}*.vtt"))
|
||||||
|
if files:
|
||||||
|
with open(files[0], 'r', encoding='utf-8') as f:
|
||||||
|
vtt_content = f.read()
|
||||||
|
segments = parse_subtitle_format(vtt_content, 'vtt')
|
||||||
|
return segments, None
|
||||||
|
else:
|
||||||
|
stderr = result.stderr or ''
|
||||||
|
return None, f"No se generaron archivos. Error: {stderr[:500]}"
|
||||||
|
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return None, "Timeout al ejecutar yt-dlp"
|
||||||
|
except FileNotFoundError:
|
||||||
|
return None, "yt-dlp no está instalado. Ejecuta: pip install yt-dlp"
|
||||||
|
except Exception as e:
|
||||||
|
return None, f"Error: {str(e)[:200]}"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if len(sys.argv) < 2:
|
||||||
|
print("Uso: python3 fetch_transcript.py VIDEO_ID [LANG] [BROWSER]")
|
||||||
|
print("")
|
||||||
|
print("Ejemplos:")
|
||||||
|
print(" python3 fetch_transcript.py K08TM4OVLyo")
|
||||||
|
print(" python3 fetch_transcript.py K08TM4OVLyo es")
|
||||||
|
print(" python3 fetch_transcript.py K08TM4OVLyo es chrome")
|
||||||
|
print(" python3 fetch_transcript.py K08TM4OVLyo es 'chrome:Profile 1'")
|
||||||
|
print(" python3 fetch_transcript.py K08TM4OVLyo es firefox")
|
||||||
|
print("")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
video_id = sys.argv[1]
|
||||||
|
lang = sys.argv[2] if len(sys.argv) > 2 else "es"
|
||||||
|
browser = sys.argv[3] if len(sys.argv) > 3 else None
|
||||||
|
|
||||||
|
print(f"🔍 Intentando obtener transcript para: {video_id}")
|
||||||
|
print(f" Idioma: {lang}")
|
||||||
|
|
||||||
|
if browser:
|
||||||
|
print(f" Método: Cookies desde {browser}")
|
||||||
|
segments, error = fetch_with_browser_cookies(video_id, lang, browser)
|
||||||
|
else:
|
||||||
|
print(f" Método: API del proyecto")
|
||||||
|
print(f" Cookies: {os.getenv('API_COOKIES_PATH', './cookies.txt')}")
|
||||||
|
from main import get_transcript_data
|
||||||
|
segments, error = get_transcript_data(video_id, lang)
|
||||||
|
|
||||||
|
print("")
|
||||||
|
|
||||||
|
# Intentar obtener transcript
|
||||||
|
segments, error = get_transcript_data(video_id, lang)
|
||||||
|
|
||||||
|
if error:
|
||||||
|
print(f"❌ ERROR: {error}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not segments:
|
||||||
|
print("❌ No se obtuvieron segmentos")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print(f"✅ Éxito: {len(segments)} segmentos obtenidos")
|
||||||
|
|
||||||
|
# Guardar a JSON
|
||||||
|
output_file = f"{video_id}_transcript.json"
|
||||||
|
with open(output_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(segments, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"💾 Guardado en: {output_file}")
|
||||||
|
|
||||||
|
# Guardar texto concatenado
|
||||||
|
text_file = f"{video_id}_transcript.txt"
|
||||||
|
combined_text = "\n".join([seg.get('text', '') for seg in segments])
|
||||||
|
with open(text_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(combined_text)
|
||||||
|
print(f"📄 Texto guardado en: {text_file}")
|
||||||
|
|
||||||
|
# Mostrar primeros 10 segmentos
|
||||||
|
print("\n📝 Primeros 10 segmentos:")
|
||||||
|
for seg in segments[:10]:
|
||||||
|
print(f" [{seg.get('start', 0):.1f}s] {seg.get('text', '')}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
132
fix-ytdlp.sh
132
fix-ytdlp.sh
@ -1,132 +1,20 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Script para forzar reinstalación de yt-dlp en contenedores
|
# Script de arreglo de yt-dlp - solo actúa si el contenedor existe
|
||||||
|
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
if docker ps --format '{{.Names}}' | grep -q '^streamlit_panel$'; then
|
||||||
echo " 🔧 Reinstalación Forzada de yt-dlp"
|
echo "Actualizando yt-dlp en streamlit_panel..."
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
docker exec streamlit_panel pip uninstall -y yt-dlp yt_dlp 2>/dev/null || true
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Colores
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
YELLOW='\033[1;33m'
|
|
||||||
RED='\033[0;31m'
|
|
||||||
NC='\033[0m'
|
|
||||||
|
|
||||||
print_success() {
|
|
||||||
echo -e "${GREEN}✅ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_error() {
|
|
||||||
echo -e "${RED}❌ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
print_info() {
|
|
||||||
echo -e "${YELLOW}ℹ️ $1${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verificar Docker
|
|
||||||
if ! command -v docker &> /dev/null; then
|
|
||||||
print_error "Docker no está instalado"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Verificar contenedores
|
|
||||||
echo "🔍 Verificando contenedores..."
|
|
||||||
if ! docker ps | grep -q streamlit_panel; then
|
|
||||||
print_error "El contenedor streamlit_panel no está corriendo"
|
|
||||||
print_info "Inicia con: docker-compose up -d"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if ! docker ps | grep -q tubescript_api; then
|
|
||||||
print_error "El contenedor tubescript_api no está corriendo"
|
|
||||||
print_info "Inicia con: docker-compose up -d"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
print_success "Contenedores encontrados"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Desinstalar yt-dlp actual
|
|
||||||
echo "🗑️ Desinstalando yt-dlp antiguo en streamlit_panel..."
|
|
||||||
docker exec streamlit_panel pip uninstall -y yt-dlp 2>/dev/null
|
|
||||||
docker exec streamlit_panel pip uninstall -y yt_dlp 2>/dev/null
|
|
||||||
|
|
||||||
echo "🗑️ Desinstalando yt-dlp antiguo en tubescript_api..."
|
|
||||||
docker exec tubescript_api pip uninstall -y yt-dlp 2>/dev/null
|
|
||||||
docker exec tubescript_api pip uninstall -y yt_dlp 2>/dev/null
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Limpiar cache de pip
|
|
||||||
echo "🧹 Limpiando cache de pip..."
|
|
||||||
docker exec streamlit_panel pip cache purge 2>/dev/null
|
|
||||||
docker exec tubescript_api pip cache purge 2>/dev/null
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Reinstalar yt-dlp desde cero
|
|
||||||
echo "📦 Reinstalando yt-dlp en streamlit_panel..."
|
|
||||||
docker exec streamlit_panel pip install --no-cache-dir --force-reinstall yt-dlp
|
docker exec streamlit_panel pip install --no-cache-dir --force-reinstall yt-dlp
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
print_success "yt-dlp reinstalado en streamlit_panel"
|
|
||||||
|
|
||||||
# Verificar versión
|
|
||||||
version=$(docker exec streamlit_panel python3 -c "import yt_dlp; print(yt_dlp.version.__version__)" 2>/dev/null)
|
|
||||||
if [ ! -z "$version" ]; then
|
|
||||||
print_info "Versión instalada: $version"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
print_error "Error al reinstalar yt-dlp en streamlit_panel"
|
echo "Contenedor streamlit_panel no encontrado — saltando acciones relacionadas con Streamlit"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
# Actualizar en el contenedor de API si existe
|
||||||
|
if docker ps --format '{{.Names}}' | grep -q '^tubescript_api$'; then
|
||||||
echo "📦 Reinstalando yt-dlp en tubescript_api..."
|
echo "Actualizando yt-dlp en tubescript_api..."
|
||||||
|
docker exec tubescript_api pip uninstall -y yt-dlp yt_dlp 2>/dev/null || true
|
||||||
docker exec tubescript_api pip install --no-cache-dir --force-reinstall yt-dlp
|
docker exec tubescript_api pip install --no-cache-dir --force-reinstall yt-dlp
|
||||||
|
|
||||||
if [ $? -eq 0 ]; then
|
|
||||||
print_success "yt-dlp reinstalado en tubescript_api"
|
|
||||||
|
|
||||||
# Verificar versión
|
|
||||||
version=$(docker exec tubescript_api python3 -c "import yt_dlp; print(yt_dlp.version.__version__)" 2>/dev/null)
|
|
||||||
if [ ! -z "$version" ]; then
|
|
||||||
print_info "Versión instalada: $version"
|
|
||||||
fi
|
|
||||||
else
|
else
|
||||||
print_error "Error al reinstalar yt-dlp en tubescript_api"
|
echo "Contenedor tubescript_api no encontrado — asegúrate que la API esté corriendo si deseas actualizar yt-dlp"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Verificar instalación
|
|
||||||
echo "🔍 Verificando instalación..."
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "Streamlit Panel:"
|
|
||||||
docker exec streamlit_panel yt-dlp --version 2>&1 | head -1
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
echo "Tubescript API:"
|
|
||||||
docker exec tubescript_api yt-dlp --version 2>&1 | head -1
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
# Reiniciar contenedores
|
|
||||||
echo "🔄 Reiniciando contenedores para aplicar cambios..."
|
|
||||||
docker-compose restart streamlit-panel tubescript-api
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
|
||||||
print_success "Reinstalación completada"
|
|
||||||
echo "═══════════════════════════════════════════════════════════"
|
|
||||||
echo ""
|
|
||||||
print_info "Ahora puedes probar con un video en vivo en:"
|
|
||||||
echo " http://localhost:8501"
|
|
||||||
echo ""
|
|
||||||
print_info "Si el error persiste, ejecuta:"
|
|
||||||
echo " docker-compose down"
|
|
||||||
echo " docker-compose build --no-cache"
|
|
||||||
echo " docker-compose up -d"
|
|
||||||
echo ""
|
|
||||||
|
|||||||
92
get_transcript_chrome.sh
Normal file
92
get_transcript_chrome.sh
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# Script para obtener transcripts de YouTube usando cookies desde Chrome/Firefox directamente
|
||||||
|
# Uso: ./get_transcript_chrome.sh VIDEO_ID [LANG] [BROWSER] [PROFILE]
|
||||||
|
|
||||||
|
VIDEO_ID="${1}"
|
||||||
|
LANG="${2:-es}"
|
||||||
|
BROWSER="${3:-chrome}"
|
||||||
|
PROFILE="${4:-}"
|
||||||
|
|
||||||
|
if [ -z "$VIDEO_ID" ]; then
|
||||||
|
echo "❌ Error: Debes proporcionar un VIDEO_ID"
|
||||||
|
echo ""
|
||||||
|
echo "Uso: $0 VIDEO_ID [LANG] [BROWSER] [PROFILE]"
|
||||||
|
echo ""
|
||||||
|
echo "Ejemplos:"
|
||||||
|
echo " $0 K08TM4OVLyo"
|
||||||
|
echo " $0 K08TM4OVLyo es chrome"
|
||||||
|
echo " $0 K08TM4OVLyo es chrome:Profile1"
|
||||||
|
echo " $0 K08TM4OVLyo es firefox"
|
||||||
|
echo " $0 K08TM4OVLyo es brave"
|
||||||
|
echo ""
|
||||||
|
echo "Perfiles disponibles de Chrome (macOS):"
|
||||||
|
ls -1 ~/Library/Application\ Support/Google/Chrome/ 2>/dev/null | grep -E "^(Default|Profile)" || echo " (no se encontraron)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Construir el argumento de browser
|
||||||
|
if [ -n "$PROFILE" ]; then
|
||||||
|
BROWSER_ARG="${BROWSER}:${PROFILE}"
|
||||||
|
else
|
||||||
|
BROWSER_ARG="${BROWSER}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "🔍 Obteniendo transcript de YouTube"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo " 📹 Video ID: $VIDEO_ID"
|
||||||
|
echo " 🌐 Idioma: $LANG"
|
||||||
|
echo " 🔑 Navegador: $BROWSER_ARG"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Ejecutar yt-dlp
|
||||||
|
yt-dlp --cookies-from-browser "$BROWSER_ARG" \
|
||||||
|
--skip-download --write-auto-sub --write-sub \
|
||||||
|
--sub-lang "$LANG" --sub-format vtt \
|
||||||
|
-o "%(id)s.%(ext)s" \
|
||||||
|
"https://www.youtube.com/watch?v=$VIDEO_ID"
|
||||||
|
|
||||||
|
EXIT_CODE=$?
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
# Buscar archivos VTT generados
|
||||||
|
VTT_FILES=$(ls ${VIDEO_ID}*.vtt 2>/dev/null)
|
||||||
|
|
||||||
|
if [ -n "$VTT_FILES" ]; then
|
||||||
|
echo "✅ Éxito: Archivos VTT generados"
|
||||||
|
echo ""
|
||||||
|
for file in $VTT_FILES; do
|
||||||
|
LINES=$(wc -l < "$file")
|
||||||
|
SIZE=$(du -h "$file" | cut -f1)
|
||||||
|
echo " 📄 $file ($LINES líneas, $SIZE)"
|
||||||
|
done
|
||||||
|
|
||||||
|
# Mostrar preview del primer archivo
|
||||||
|
FIRST_VTT=$(echo "$VTT_FILES" | head -n 1)
|
||||||
|
echo ""
|
||||||
|
echo "📝 Preview de $FIRST_VTT:"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
head -n 30 "$FIRST_VTT"
|
||||||
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||||
|
|
||||||
|
# Crear archivo de texto plano
|
||||||
|
TXT_FILE="${VIDEO_ID}_transcript.txt"
|
||||||
|
grep -v "WEBVTT" "$FIRST_VTT" | grep -v "^$" | grep -v "^[0-9][0-9]:" | grep -v "^Kind:" | grep -v "^Language:" > "$TXT_FILE"
|
||||||
|
echo ""
|
||||||
|
echo "💾 Texto guardado en: $TXT_FILE"
|
||||||
|
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "❌ Error: No se generaron archivos VTT"
|
||||||
|
echo ""
|
||||||
|
echo "💡 Posibles soluciones:"
|
||||||
|
echo " 1. Verifica que estés logueado en YouTube en $BROWSER"
|
||||||
|
echo " 2. Prueba con otro navegador: chrome, firefox, brave"
|
||||||
|
echo " 3. Si usas múltiples perfiles, especifica el perfil:"
|
||||||
|
echo " $0 $VIDEO_ID $LANG chrome Profile1"
|
||||||
|
echo " 4. Cierra el navegador antes de ejecutar este script"
|
||||||
|
echo ""
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
@ -4,4 +4,5 @@ requests
|
|||||||
yt-dlp
|
yt-dlp
|
||||||
python-multipart
|
python-multipart
|
||||||
pydantic
|
pydantic
|
||||||
|
youtube-transcript-api
|
||||||
# Nota: streamlit y paquetes relacionados fueron removidos porque el frontend fue eliminado
|
# Nota: streamlit y paquetes relacionados fueron removidos porque el frontend fue eliminado
|
||||||
|
|||||||
232
yt_wrap.py
Normal file
232
yt_wrap.py
Normal file
@ -0,0 +1,232 @@
|
|||||||
|
"""Utility wrapper for yt-dlp calls with robust cookie handling via cookiejar.
|
||||||
|
|
||||||
|
Provides:
|
||||||
|
- CookieManager: reads cookie string from RedisArchivist and writes a Netscape cookie file
|
||||||
|
that can be passed to yt-dlp via the --cookiefile option (path).
|
||||||
|
- YtDlpClient: base class to perform download/extract calls to yt-dlp with consistent
|
||||||
|
error handling and optional automatic cookie injection.
|
||||||
|
|
||||||
|
Usage example:
|
||||||
|
from yt_wrap import YtDlpClient
|
||||||
|
client = YtDlpClient(config=config_dict)
|
||||||
|
info, err = client.extract_info('https://www.youtube.com/watch?v=K08TM4OVLyo')
|
||||||
|
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import typing as t
|
||||||
|
from http import cookiejar
|
||||||
|
|
||||||
|
import yt_dlp
|
||||||
|
|
||||||
|
# Import project-specific RedisArchivist if available; otherwise provide a local stub
|
||||||
|
try:
|
||||||
|
from common.src.ta_redis import RedisArchivist
|
||||||
|
except Exception:
|
||||||
|
class RedisArchivist:
|
||||||
|
"""Fallback stub for environments without the project's RedisArchivist.
|
||||||
|
|
||||||
|
Methods mimic the interface used by CookieManager: get_message_str, set_message,
|
||||||
|
del_message, get_message_dict. These stubs are no-ops and return None/False.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def get_message_str(self, key: str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def set_message(self, key: str, value, expire: int = None, save: bool = False):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def del_message(self, key: str):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_message_dict(self, key: str):
|
||||||
|
return None
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
logger.addHandler(logging.NullHandler())
|
||||||
|
|
||||||
|
|
||||||
|
class CookieManager:
|
||||||
|
"""Manage cookie string storage and provide a cookiefile path for yt-dlp.
|
||||||
|
|
||||||
|
This writes the Netscape cookie string to a temporary file (Netscape format)
|
||||||
|
which is compatible with `yt-dlp`'s `--cookiefile` option and with
|
||||||
|
`http.cookiejar.MozillaCookieJar`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEFAULT_MESSAGE_KEY = "cookie"
|
||||||
|
|
||||||
|
def __init__(self, redis_archivist: t.Optional[object] = None):
|
||||||
|
# Accept a RedisArchivist-like object for testability; otherwise try to create one
|
||||||
|
if redis_archivist is not None:
|
||||||
|
self.redis = redis_archivist
|
||||||
|
else:
|
||||||
|
if RedisArchivist is None:
|
||||||
|
self.redis = None
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
self.redis = RedisArchivist()
|
||||||
|
except Exception:
|
||||||
|
self.redis = None
|
||||||
|
self._temp_files: list[str] = []
|
||||||
|
|
||||||
|
def get_cookiefile_path(self, message_key: str | None = None) -> t.Optional[str]:
|
||||||
|
"""Return a filesystem path to a Netscape cookie file written from the stored cookie string.
|
||||||
|
|
||||||
|
If no cookie is available, returns None.
|
||||||
|
"""
|
||||||
|
key = message_key or self.DEFAULT_MESSAGE_KEY
|
||||||
|
cookie_str = None
|
||||||
|
if self.redis is not None and hasattr(self.redis, "get_message_str"):
|
||||||
|
try:
|
||||||
|
cookie_str = self.redis.get_message_str(key)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.debug("CookieManager: error reading from redis: %s", exc)
|
||||||
|
cookie_str = None
|
||||||
|
|
||||||
|
if not cookie_str:
|
||||||
|
# No cookie stored
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Ensure cookie string ends with newline
|
||||||
|
cookie_str = cookie_str.strip("\x00")
|
||||||
|
if not cookie_str.endswith("\n"):
|
||||||
|
cookie_str = cookie_str + "\n"
|
||||||
|
|
||||||
|
# Write to a temp file in the system temp dir
|
||||||
|
tf = tempfile.NamedTemporaryFile(mode="w", delete=False, prefix="yt_cookies_", suffix=".txt")
|
||||||
|
tf.write(cookie_str)
|
||||||
|
tf.flush()
|
||||||
|
tf.close()
|
||||||
|
self._temp_files.append(tf.name)
|
||||||
|
|
||||||
|
# Validate it's a Netscape cookie file by attempting to load with MozillaCookieJar
|
||||||
|
try:
|
||||||
|
jar = cookiejar.MozillaCookieJar()
|
||||||
|
jar.load(tf.name, ignore_discard=True, ignore_expires=True)
|
||||||
|
except Exception:
|
||||||
|
# It's okay if load fails; yt-dlp expects the netscape format; keep the file anyway
|
||||||
|
logger.debug("CookieManager: written cookie file but couldn't load with MozillaCookieJar")
|
||||||
|
|
||||||
|
return tf.name
|
||||||
|
|
||||||
|
def cleanup(self) -> None:
|
||||||
|
"""Remove temporary cookie files created by get_cookiefile_path."""
|
||||||
|
for p in getattr(self, "_temp_files", []):
|
||||||
|
try:
|
||||||
|
os.unlink(p)
|
||||||
|
except Exception:
|
||||||
|
logger.debug("CookieManager: failed to unlink temp cookie file %s", p)
|
||||||
|
self._temp_files = []
|
||||||
|
|
||||||
|
|
||||||
|
class YtDlpClient:
|
||||||
|
"""Base client to interact with yt-dlp.
|
||||||
|
|
||||||
|
- `base_opts` are merged with per-call options.
|
||||||
|
- If `use_redis_cookies` is True, the client will try to fetch a cookiefile
|
||||||
|
path from Redis via `CookieManager` and inject `cookiefile` into options.
|
||||||
|
|
||||||
|
Methods return tuples like (result, error) where result is data or True/False and
|
||||||
|
error is a string or None.
|
||||||
|
"""
|
||||||
|
|
||||||
|
DEFAULT_OPTS: dict = {
|
||||||
|
"quiet": True,
|
||||||
|
"socket_timeout": 10,
|
||||||
|
"extractor_retries": 2,
|
||||||
|
"retries": 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, base_opts: dict | None = None, use_redis_cookies: bool = True, redis_archivist: t.Optional[object] = None):
|
||||||
|
self.base_opts = dict(self.DEFAULT_OPTS)
|
||||||
|
if base_opts:
|
||||||
|
self.base_opts.update(base_opts)
|
||||||
|
self.use_redis_cookies = use_redis_cookies
|
||||||
|
self.cookie_mgr = CookieManager(redis_archivist)
|
||||||
|
|
||||||
|
def _build_opts(self, extra: dict | None = None) -> dict:
|
||||||
|
opts = dict(self.base_opts)
|
||||||
|
if extra:
|
||||||
|
opts.update(extra)
|
||||||
|
|
||||||
|
# If cookie management is enabled, attempt to attach a cookiefile path
|
||||||
|
if self.use_redis_cookies:
|
||||||
|
cookiefile = self.cookie_mgr.get_cookiefile_path()
|
||||||
|
if cookiefile:
|
||||||
|
opts["cookiefile"] = cookiefile
|
||||||
|
return opts
|
||||||
|
|
||||||
|
def extract_info(self, url: str, extra_opts: dict | None = None) -> tuple[dict | None, str | None]:
|
||||||
|
"""Extract info for a url using yt-dlp.extract_info.
|
||||||
|
|
||||||
|
Returns (info_dict, error_str). If successful, error_str is None.
|
||||||
|
"""
|
||||||
|
opts = self._build_opts(extra_opts)
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
info = ydl.extract_info(url, download=False)
|
||||||
|
except cookiejar.LoadError as exc:
|
||||||
|
logger.error("Cookie load error: %s", exc)
|
||||||
|
return None, f"cookie_load_error: {exc}"
|
||||||
|
except yt_dlp.utils.ExtractorError as exc:
|
||||||
|
logger.warning("ExtractorError for %s: %s", url, exc)
|
||||||
|
return None, str(exc)
|
||||||
|
except yt_dlp.utils.DownloadError as exc:
|
||||||
|
msg = str(exc)
|
||||||
|
logger.warning("DownloadError for %s: %s", url, msg)
|
||||||
|
if "Temporary failure in name resolution" in msg:
|
||||||
|
raise ConnectionError("lost the internet, abort!") from exc
|
||||||
|
# Detect rate limiting
|
||||||
|
if "HTTP Error 429" in msg or "too many requests" in msg.lower():
|
||||||
|
return None, "HTTP 429: rate limited"
|
||||||
|
return None, msg
|
||||||
|
except Exception as exc: # pragma: no cover - defensive
|
||||||
|
logger.exception("Unexpected error in extract_info: %s", exc)
|
||||||
|
return None, str(exc)
|
||||||
|
finally:
|
||||||
|
# Clean up temp cookie files after the call
|
||||||
|
try:
|
||||||
|
self.cookie_mgr.cleanup()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return info, None
|
||||||
|
|
||||||
|
def download(self, url: str, extra_opts: dict | None = None) -> tuple[bool, str | None]:
|
||||||
|
"""Invoke ydl.download for the provided url. Returns (success, error_message).
|
||||||
|
"""
|
||||||
|
opts = self._build_opts(extra_opts)
|
||||||
|
try:
|
||||||
|
with yt_dlp.YoutubeDL(opts) as ydl:
|
||||||
|
ydl.download([url])
|
||||||
|
except yt_dlp.utils.DownloadError as exc:
|
||||||
|
msg = str(exc)
|
||||||
|
logger.warning("DownloadError while downloading %s: %s", url, msg)
|
||||||
|
if "Temporary failure in name resolution" in msg:
|
||||||
|
raise ConnectionError("lost the internet, abort!") from exc
|
||||||
|
if "HTTP Error 429" in msg or "too many requests" in msg.lower():
|
||||||
|
return False, "HTTP 429: rate limited"
|
||||||
|
return False, msg
|
||||||
|
except Exception as exc:
|
||||||
|
logger.exception("Unexpected error during download: %s", exc)
|
||||||
|
return False, str(exc)
|
||||||
|
finally:
|
||||||
|
try:
|
||||||
|
self.cookie_mgr.cleanup()
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return True, None
|
||||||
|
|
||||||
|
|
||||||
|
# If running as a script, show a tiny demo (no network calls are performed here)
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.basicConfig(level=logging.DEBUG)
|
||||||
|
client = YtDlpClient()
|
||||||
|
print(client._build_opts())
|
||||||
Loading…
x
Reference in New Issue
Block a user