Refactor Docker setup: consolidate volume mounts, update API port, and enhance data directory management

This commit is contained in:
Cesar Mendivil 2026-03-07 10:08:02 -07:00
parent 8706a4f3f7
commit c9f8c9290b
13 changed files with 943 additions and 482 deletions

View File

@ -3,24 +3,43 @@ FROM python:3.11-slim
ENV PYTHONUNBUFFERED=1 ENV PYTHONUNBUFFERED=1
# Instalar ffmpeg y herramientas necesarias # Instalar ffmpeg, Node.js (LTS via NodeSource) y herramientas necesarias
# Node.js + yt-dlp-utils son requeridos para resolver el n-challenge y signature de YouTube
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
ffmpeg \ ffmpeg \
curl \ curl \
ca-certificates \ ca-certificates \
&& rm -rf /var/lib/apt/lists/* gnupg \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& rm -rf /var/lib/apt/lists/* \
&& npm install -g yt-dlp-utils 2>/dev/null || true
WORKDIR /app WORKDIR /app
# Copiar requirements y instalar dependencias # Copiar requirements y instalar dependencias Python
COPY requirements.txt /app/requirements.txt COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt \ RUN pip install --no-cache-dir -r /app/requirements.txt
&& pip install --no-cache-dir yt-dlp
# Instalar yt-dlp desde la última versión del binario oficial (no pip) para tener siempre la más reciente
RUN curl -L https://github.com/yt-dlp/yt-dlp/releases/latest/download/yt-dlp -o /usr/local/bin/yt-dlp \
&& chmod a+rx /usr/local/bin/yt-dlp
# ARG para invalidar caché del COPY al hacer rebuild con --build-arg CACHEBUST=$(date +%s)
ARG CACHEBUST=1
# Copiar el resto del código # Copiar el resto del código
COPY . /app COPY . /app
# Crear carpeta data con permisos abiertos para que cualquier UID pueda leer/escribir
RUN mkdir -p /app/data && chmod 777 /app/data
# Crear usuario appuser (UID 1000) y darle acceso a /app
RUN groupadd -g 1000 appgroup && useradd -u 1000 -g appgroup -s /bin/sh appuser \
&& chown -R appuser:appgroup /app
USER appuser
EXPOSE 8000 EXPOSE 8000
# Comando por defecto para ejecutar la API # Comando por defecto para ejecutar la API

View File

@ -27,7 +27,7 @@ chmod +x docker-start.sh
# 3. Abrir en navegador # 3. Abrir en navegador
# Panel Web: http://localhost:8501 # Panel Web: http://localhost:8501
# API: http://localhost:8080 # API: http://localhost:8282
``` ```
📚 Ver [DOCKER_README.md](DOCKER_README.md) para más información. 📚 Ver [DOCKER_README.md](DOCKER_README.md) para más información.
@ -48,8 +48,8 @@ chmod +x docker-start.sh
Esto iniciará: Esto iniciará:
- **Panel Web Streamlit**: http://localhost:8501 - **Panel Web Streamlit**: http://localhost:8501
- **API FastAPI**: http://localhost:8080 - **API FastAPI**: http://localhost:8282
- **Documentación API**: http://localhost:8080/docs - **Documentación API**: http://localhost:8282/docs
📚 Documentación completa: [DOCKER_GUIDE.md](DOCKER_GUIDE.md) 📚 Documentación completa: [DOCKER_GUIDE.md](DOCKER_GUIDE.md)
@ -214,18 +214,25 @@ docker-compose down
Esto iniciará: Esto iniciará:
- **Panel Streamlit**: http://localhost:8501 (Frontend) - **Panel Streamlit**: http://localhost:8501 (Frontend)
- **API FastAPI**: http://localhost:8080 (Backend) - **API FastAPI**: http://localhost:8282 (Backend)
- **Docs API**: http://localhost:8080/docs (Swagger UI) - **Docs API**: http://localhost:8282/docs (Swagger UI)
### Características Docker ### Volumen de configuración: `./data`
- ✅ Health checks automáticos A partir de la configuración actual, el proyecto monta una única carpeta local `./data` dentro del contenedor en `/app/data`.
- ✅ Auto-restart si falla Coloca ahí los archivos de configuración y persistencia (por ejemplo: `cookies.txt`, `stream_config.json`, `streams_state.json`).
- ✅ Red compartida entre servicios
- ✅ Volúmenes persistentes para configuración
- ✅ FFmpeg incluido en la imagen
📚 **Documentación completa**: [DOCKER_GUIDE.md](DOCKER_GUIDE.md) - Ventajas:
- Mantener todos los archivos de configuración en un solo lugar
- Puedes reemplazar `cookies.txt` desde fuera del servidor (host) sin editar el compose
- Evita montajes individuales de archivos que generen conflictos de permisos
- Ejemplo (crear la carpeta si no existe):
```bash
mkdir -p ./data
chmod 755 ./data
```
## 📁 Estructura del Proyecto ## 📁 Estructura del Proyecto
@ -235,15 +242,15 @@ TubeScript-API/
├── streamlit_app.py # Panel web de control ├── streamlit_app.py # Panel web de control
├── requirements.txt # Dependencias Python ├── requirements.txt # Dependencias Python
├── Dockerfile # Imagen Docker optimizada ├── Dockerfile # Imagen Docker optimizada
├── docker-compose.yml # Orquestación de servicios ├── docker-compose.yml # Orquestación de servicios (monta ./data -> /app/data)
├── docker-start.sh # Script de inicio automático ├── docker-start.sh # Script de inicio automático
├── docker-stop.sh # Script para detener ├── docker-stop.sh # Script para detener
├── docker-logs.sh # Script para ver logs ├── docker-logs.sh # Script para ver logs
├── Dockerfile # Configuración Docker ├── data/ # Carpeta montada en el contenedor (/app/data) para configuración persistente
├── docker-compose.yml # Orquestación de servicios │ ├── stream_config.json # Configuración de plataformas (generado/gestionado aquí)
├── stream_config.json # Configuración de plataformas (generado) │ ├── streams_state.json # Estado de transmisiones (generado/gestionado aquí)
├── streams_state.json # Estado de transmisiones (generado) │ └── cookies.txt # Cookies de YouTube (opcional — poner aquí o subir vía endpoint)
└── cookies.txt # Cookies de YouTube (opcional) └── README.md # Documentación
``` ```
## 🔧 Configuración Avanzada ## 🔧 Configuración Avanzada
@ -255,7 +262,23 @@ Para acceder a videos con restricciones, puedes proporcionar cookies:
1. Instala la extensión "Get cookies.txt" en tu navegador 1. Instala la extensión "Get cookies.txt" en tu navegador
2. Visita youtube.com e inicia sesión 2. Visita youtube.com e inicia sesión
3. Exporta las cookies como `cookies.txt` 3. Exporta las cookies como `cookies.txt`
4. Coloca el archivo en la raíz del proyecto 4. Coloca el archivo en `./data/cookies.txt` o súbelo mediante el endpoint `/upload_cookies`
Ejemplo: copiar manualmente al volumen montado:
```bash
cp /ruta/local/cookies.txt ./data/cookies.txt
# (si el servicio ya está corriendo, reinicia el contenedor para que los procesos usen la nueva cookie si es necesario)
```
O usar el endpoint de la API (si la API está expuesta en el host):
```bash
# Si usas docker-compose.yml (puerto 8282)
curl -v -X POST "http://127.0.0.1:8282/upload_cookies" -F "file=@/ruta/a/cookies.txt" -H "Accept: application/json"
# Si usas docker-compose.local.yml y expones en 8000, ajusta el puerto a 8000
```
### Personalizar Calidad de Video ### Personalizar Calidad de Video
@ -289,7 +312,7 @@ command = [
### Error: "No se pudo obtener la URL del stream" ### Error: "No se pudo obtener la URL del stream"
- Verifica que el video esté realmente en vivo - Verifica que el video esté realmente en vivo
- Intenta agregar cookies de YouTube - Intenta agregar cookies de YouTube (colocando `./data/cookies.txt` o subiéndolas vía `/upload_cookies`)
- Verifica tu conexión a internet - Verifica tu conexión a internet
### Error: "Transmisión con estado error" ### Error: "Transmisión con estado error"

View File

@ -7,14 +7,14 @@ services:
dockerfile: Dockerfile.api dockerfile: Dockerfile.api
container_name: tubescript-api container_name: tubescript-api
image: tubescript-api:local image: tubescript-api:local
user: "${LOCAL_UID:-1000}:${LOCAL_GID:-1000}"
ports: ports:
- "8000:8000" - "8000:8000"
volumes: volumes:
- ./cookies.txt:/app/cookies.txt - ./data:/app/data:rw
- ./stream_config.json:/app/stream_config.json:ro
- ./streams_state.json:/app/streams_state.json:rw
environment: environment:
API_BASE_URL: http://localhost:8000 API_BASE_URL: http://localhost:8000
API_COOKIES_PATH: /app/data/cookies.txt
TZ: UTC TZ: UTC
restart: unless-stopped restart: unless-stopped
healthcheck: healthcheck:
@ -34,8 +34,7 @@ services:
ports: ports:
- "8501:8501" - "8501:8501"
volumes: volumes:
- ./stream_config.json:/app/stream_config.json:ro - ./data:/app/data:ro
- ./cookies.txt:/app/cookies.txt:ro
environment: environment:
API_BASE_URL: http://localhost:8000 API_BASE_URL: http://localhost:8000
TZ: UTC TZ: UTC

View File

@ -4,20 +4,20 @@ services:
build: build:
context: . context: .
dockerfile: Dockerfile.api dockerfile: Dockerfile.api
args:
# Invalida solo la capa COPY . /app para que siempre tome el código más reciente
# sin necesidad de --no-cache (que descarga todo desde cero)
CACHEBUST: "${CACHEBUST:-1}"
image: tubescript-api:latest
container_name: tubescript_api container_name: tubescript_api
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
ports: ports:
- "8000:8000" - "8282:8000"
volumes: volumes:
- ./:/app:rw - ./data:/app/data:rw
- ./cookies.txt:/app/cookies.txt:ro
- ./stream_config.json:/app/stream_config.json:ro
- ./streams_state.json:/app/streams_state.json
- ./data:/app/data
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- API_COOKIES_PATH=/app/cookies.txt - API_COOKIES_PATH=/app/data/cookies.txt
# Optional: set API_PROXY when you want the container to use a SOCKS/HTTP proxy (e.g. tor) # Optional: set API_PROXY when you want the container to use a SOCKS/HTTP proxy
- API_PROXY=${API_PROXY:-} - API_PROXY=${API_PROXY:-}
restart: unless-stopped restart: unless-stopped
networks: networks:
@ -32,3 +32,4 @@ services:
networks: networks:
tubescript-network: tubescript-network:
name: tubescript-network name: tubescript-network
driver: bridge

View File

@ -42,9 +42,21 @@ fi
print_success "Docker encontrado" print_success "Docker encontrado"
echo "" echo ""
# Asegurar carpeta data para montajes de configuración
echo "📁 Asegurando carpeta './data' para montaje de configuración..."
if [ ! -d "./data" ]; then
mkdir -p ./data
chmod 755 ./data || true
print_success "Carpeta ./data creada"
else
print_success "Carpeta ./data ya existe"
fi
echo "Nota: coloca aquí archivos persistentes como stream_config.json, streams_state.json y cookies.txt (ej: ./data/cookies.txt)"
echo ""
# Detener contenedores # Detener contenedores
echo "🛑 Deteniendo contenedores existentes..." echo "🛑 Deteniendo contenedores existentes..."
docker-compose down 2>/dev/null || true docker compose down 2>/dev/null || true
print_success "Contenedores detenidos" print_success "Contenedores detenidos"
echo "" echo ""
@ -53,22 +65,24 @@ echo "🧹 ¿Deseas eliminar las imágenes antiguas? (s/N)"
read -p "> " clean_images read -p "> " clean_images
if [ "$clean_images" = "s" ] || [ "$clean_images" = "S" ]; then if [ "$clean_images" = "s" ] || [ "$clean_images" = "S" ]; then
echo "Eliminando imágenes antiguas..." echo "Eliminando imágenes antiguas..."
docker-compose down --rmi all 2>/dev/null || true docker compose down --rmi all 2>/dev/null || true
print_success "Imágenes antiguas eliminadas" print_success "Imágenes antiguas eliminadas"
fi fi
echo "" echo ""
# Reconstruir sin cache # Reconstruir con CACHEBUST para invalidar solo la capa COPY . /app
echo "🔨 Reconstruyendo imágenes sin cache..." # CACHEBUST=$(date +%s) se exporta para que docker-compose.yml lo tome via ${CACHEBUST:-1}
echo "Esto puede tardar varios minutos..." echo "🔨 Reconstruyendo imagen con código actualizado..."
echo "Usando CACHEBUST=$(date +%s) para forzar copia fresca del código..."
echo "" echo ""
docker-compose build --no-cache export CACHEBUST="$(date +%s)"
docker compose build
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
print_success "Imágenes reconstruidas exitosamente" print_success "Imagen reconstruida exitosamente"
else else
print_error "Error al reconstruir imágenes" print_error "Error al reconstruir imagen"
exit 1 exit 1
fi fi
echo "" echo ""
@ -79,22 +93,22 @@ read -p "> " start_services
if [ "$start_services" != "n" ] && [ "$start_services" != "N" ]; then if [ "$start_services" != "n" ] && [ "$start_services" != "N" ]; then
echo "" echo ""
echo "🚀 Iniciando servicios..." echo "🚀 Iniciando servicios..."
docker-compose up -d docker compose up -d
if [ $? -eq 0 ]; then if [ $? -eq 0 ]; then
print_success "Servicios iniciados" print_success "Servicios iniciados"
echo "" echo ""
echo "📊 Estado de los servicios:" echo "📊 Estado de los servicios:"
sleep 3 sleep 3
docker-compose ps docker compose ps
echo "" echo ""
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
print_success "¡Rebuild completado!" print_success "¡Rebuild completado!"
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
echo "" echo ""
echo "🌐 Servicios disponibles:" echo "🌐 Servicios disponibles:"
echo " Panel Web: http://localhost:8501" echo " API: http://localhost:8282"
echo " API: http://localhost:8080" echo " Docs API: http://localhost:8282/docs"
echo "" echo ""
else else
print_error "Error al iniciar servicios" print_error "Error al iniciar servicios"
@ -105,7 +119,7 @@ else
print_success "Rebuild completado (servicios no iniciados)" print_success "Rebuild completado (servicios no iniciados)"
echo "" echo ""
echo "Para iniciar los servicios:" echo "Para iniciar los servicios:"
echo " docker-compose up -d" echo " CACHEBUST=\$(date +%s) docker compose up -d --build"
fi fi
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"

View File

@ -21,11 +21,9 @@ docker run -d \
--name tubescript_api \ --name tubescript_api \
--network tubescript-network \ --network tubescript-network \
-p 8080:8000 \ -p 8080:8000 \
-v "$(pwd)/cookies.txt:/app/cookies.txt:ro" \ -v "$(pwd)/data:/app/data:rw" \
-v "$(pwd)/stream_config.json:/app/stream_config.json" \ -e API_COOKIES_PATH=/app/data/cookies.txt \
-v "$(pwd)/streams_state.json:/app/streams_state.json" \
-v "$(pwd)/process_state.json:/app/process_state.json" \ -v "$(pwd)/process_state.json:/app/process_state.json" \
-v "$(pwd)/data:/app/data" \
-e PYTHONUNBUFFERED=1 \ -e PYTHONUNBUFFERED=1 \
tubescript-api \ tubescript-api \
uvicorn main:app --host 0.0.0.0 --port 8000 --reload uvicorn main:app --host 0.0.0.0 --port 8000 --reload

View File

@ -30,11 +30,8 @@ docker run -d \
--name streamlit_panel \ --name streamlit_panel \
--network tubescript-network \ --network tubescript-network \
-p 8501:8501 \ -p 8501:8501 \
-v "$(pwd)/cookies.txt:/app/cookies.txt:ro" \ -v "$(pwd)/data:/app/data:ro" \
-v "$(pwd)/stream_config.json:/app/stream_config.json" \ -e API_COOKIES_PATH=/app/data/cookies.txt \
-v "$(pwd)/streams_state.json:/app/streams_state.json" \
-v "$(pwd)/process_state.json:/app/process_state.json" \
-v "$(pwd)/data:/app/data" \
-e PYTHONUNBUFFERED=1 \ -e PYTHONUNBUFFERED=1 \
-e API_URL="$API_URL" \ -e API_URL="$API_URL" \
tubescript-api \ tubescript-api \

View File

@ -1,180 +1,79 @@
#!/bin/bash #!/bin/bash
# Script para iniciar TubeScript-API con docker compose
# Script para iniciar el stack completo de TubeScript con Docker
set -e set -e
GREEN='\033[0;32m'; YELLOW='\033[1;33m'; RED='\033[0;31m'; NC='\033[0m'
print_success() { echo -e "${GREEN}$1${NC}"; }
print_warning() { echo -e "${YELLOW}⚠️ $1${NC}"; }
print_error() { echo -e "${RED}$1${NC}"; }
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
echo " 🐳 TubeScript-API - Inicio con Docker" echo " 🐳 TubeScript-API — docker compose up"
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
echo "" echo ""
# Colores para output # Verificar Docker
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
# Función para imprimir mensajes con color
print_success() {
echo -e "${GREEN}$1${NC}"
}
print_warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
print_error() {
echo -e "${RED}$1${NC}"
}
# Verificar que Docker esté instalado
if ! command -v docker &>/dev/null; then if ! command -v docker &>/dev/null; then
print_error "Docker no está instalado" print_error "Docker no está instalado"; exit 1
echo "Instala Docker desde: https://www.docker.com/get-started"
exit 1
fi fi
print_success "Docker encontrado: $(docker --version)"
if ! command -v docker-compose &> /dev/null; then
print_error "Docker Compose no está instalado"
exit 1
fi
print_success "Docker y Docker Compose encontrados"
# Solicitar URL de la API si no está configurada
echo "" echo ""
echo "🌐 Configuración de API URL..."
# Verificar si existe archivo .env # Crear carpeta data con permisos correctos (necesaria para cookies.txt y otros)
if [ ! -f ".env" ]; then
echo ""
echo "Por favor, ingresa la URL del dominio de la API:"
echo "(Ejemplos: https://api.tubescript.com, http://localhost:8080, https://mi-dominio.com)"
read -p "API URL [http://localhost:8080]: " api_url
api_url=${api_url:-http://localhost:8080}
echo "API_URL=$api_url" > .env
print_success "Creado archivo .env con API_URL=$api_url"
else
# Leer URL existente
source .env
print_success "Usando API_URL existente: $API_URL"
echo "¿Deseas cambiar la API URL? (s/N)"
read -p "> " change_url
if [ "$change_url" = "s" ] || [ "$change_url" = "S" ]; then
read -p "Nueva API URL: " new_api_url
if [ ! -z "$new_api_url" ]; then
sed -i.bak "s|API_URL=.*|API_URL=$new_api_url|" .env
print_success "API_URL actualizada a: $new_api_url"
fi
fi
fi
# Crear archivos de configuración si no existen
echo ""
echo "📝 Verificando archivos de configuración..."
if [ ! -f "stream_config.json" ]; then
echo '{
"platforms": {
"YouTube": {"rtmp_url": "", "stream_key": "", "enabled": false},
"Facebook": {"rtmp_url": "", "stream_key": "", "enabled": false},
"Twitch": {"rtmp_url": "", "stream_key": "", "enabled": false},
"X (Twitter)": {"rtmp_url": "", "stream_key": "", "enabled": false},
"Instagram": {"rtmp_url": "", "stream_key": "", "enabled": false},
"TikTok": {"rtmp_url": "", "stream_key": "", "enabled": false}
}
}' > stream_config.json
print_success "Creado stream_config.json"
else
print_success "stream_config.json ya existe"
fi
if [ ! -f "streams_state.json" ]; then
echo '{}' > streams_state.json
print_success "Creado streams_state.json"
else
print_success "streams_state.json ya existe"
fi
if [ ! -f "cookies.txt" ]; then
touch cookies.txt
print_warning "Creado cookies.txt vacío (opcional para videos restringidos)"
else
print_success "cookies.txt existe"
fi
# Crear directorio data si no existe
if [ ! -d "data" ]; then if [ ! -d "data" ]; then
mkdir -p data mkdir -p data && chmod 755 data
print_success "Creado directorio data/" print_success "Creado directorio ./data"
else
print_success "Directorio ./data ya existe"
fi fi
# Detener contenedores existentes si los hay # Sugerencia de cookies
if [ ! -f "data/cookies.txt" ]; then
touch data/cookies.txt
print_warning "data/cookies.txt vacío creado (sube cookies con POST /upload_cookies)"
else
print_success "data/cookies.txt encontrado"
fi
echo "" echo ""
# Detener contenedores existentes
echo "🛑 Deteniendo contenedores existentes..." echo "🛑 Deteniendo contenedores existentes..."
docker-compose down 2>/dev/null || true docker compose down 2>/dev/null || true
# Construir las imágenes
echo "" echo ""
echo "🔨 Construyendo imágenes Docker..."
docker-compose build
# Iniciar los servicios # Build + arranque con CACHEBUST para forzar copia fresca del código
export CACHEBUST="$(date +%s)"
echo "🔨 Construyendo e iniciando servicios..."
echo " (CACHEBUST=${CACHEBUST} — solo invalida la capa de código, no las capas de apt/pip)"
echo "" echo ""
echo "🚀 Iniciando servicios..." docker compose up -d --build
docker-compose up -d
# Esperar a que los servicios estén listos if [ $? -eq 0 ]; then
echo "" echo ""
echo "⏳ Esperando que los servicios inicien..." echo "⏳ Esperando arranque de uvicorn..."
sleep 5 sleep 8
# Verificar estado de los servicios
echo "" echo ""
echo "📊 Estado de los servicios:" echo "📊 Estado:"
docker-compose ps docker compose ps
# Mostrar logs iniciales
echo "" echo ""
echo "📋 Logs recientes:" echo "📋 Logs recientes:"
docker-compose logs --tail=10 docker compose logs --tail=6
echo "" echo ""
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
print_success "¡Servicios iniciados correctamente!" print_success "¡Listo!"
echo "════════════════════════════════════════════════════════════" echo "════════════════════════════════════════════════════════════"
echo "" echo ""
echo "📡 Servicios disponibles:" echo " 🌐 API: http://localhost:8282"
echo " 📖 Docs: http://localhost:8282/docs"
echo " 🍪 Subir cookies: curl -X POST http://localhost:8282/upload_cookies -F 'file=@cookies.txt'"
echo "" echo ""
echo " 🌐 Panel Web Streamlit:"
echo " http://localhost:8501"
echo ""
echo " 📡 API FastAPI:"
echo " http://localhost:8080"
echo " http://localhost:8080/docs (Documentación Swagger)"
echo ""
echo "────────────────────────────────────────────────────────────"
echo " 📝 Comandos útiles:" echo " 📝 Comandos útiles:"
echo " Logs en vivo: docker compose logs -f tubescript-api"
echo " Detener: docker compose down"
echo " Rebuild: CACHEBUST=\$(date +%s) docker compose up -d --build"
echo "" echo ""
echo " Ver logs en tiempo real:" else
echo " docker-compose logs -f" print_error "Error al iniciar servicios"
echo "" docker compose logs --tail=20
echo " Ver logs de un servicio:" exit 1
echo " docker-compose logs -f streamlit-panel" fi
echo " docker-compose logs -f tubescript-api"
echo ""
echo " Detener servicios:"
echo " docker-compose down"
echo ""
echo " Reiniciar servicios:"
echo " docker-compose restart"
echo ""
echo " Ver estado:"
echo " docker-compose ps"
echo ""
echo "════════════════════════════════════════════════════════════"
echo "🎉 ¡Listo para transmitir!"
echo "════════════════════════════════════════════════════════════"

View File

@ -17,7 +17,7 @@ import os
import subprocess import subprocess
import tempfile import tempfile
import glob import glob
from main import parse_subtitle_format from main import parse_subtitle_format, get_transcript_data
def fetch_with_browser_cookies(video_id, lang="es", browser="chrome"): def fetch_with_browser_cookies(video_id, lang="es", browser="chrome"):
"""Intenta obtener transcript usando cookies desde el navegador directamente.""" """Intenta obtener transcript usando cookies desde el navegador directamente."""
@ -78,18 +78,15 @@ def main():
print(f" Idioma: {lang}") print(f" Idioma: {lang}")
if browser: if browser:
print(f" Método: Cookies desde {browser}") print(" Método: Cookies desde {}".format(browser))
segments, error = fetch_with_browser_cookies(video_id, lang, browser) segments, error = fetch_with_browser_cookies(video_id, lang, browser)
else: else:
print(f" Método: API del proyecto") print(" Método: API del proyecto")
print(f" Cookies: {os.getenv('API_COOKIES_PATH', './cookies.txt')}") print(" Cookies: {}".format(os.getenv('API_COOKIES_PATH', './data/cookies.txt')))
from main import get_transcript_data
segments, error = get_transcript_data(video_id, lang) segments, error = get_transcript_data(video_id, lang)
print("") print("")
# Intentar obtener transcript
segments, error = get_transcript_data(video_id, lang)
if error: if error:
print(f"❌ ERROR: {error}") print(f"❌ ERROR: {error}")

124
fix-and-restart.sh Executable file
View File

@ -0,0 +1,124 @@
#!/bin/bash
# Script para reconstruir y levantar TubeScript-API con soporte correcto de YouTube
set -e
REPO_DIR="/home/xesar/PycharmProjects/TubeScript-API"
cd "$REPO_DIR"
echo "======================================================"
echo " TubeScript-API - Fix & Restart"
echo "======================================================"
# 1. Parar contenedor anterior si existe
echo ""
echo ">>> [1/7] Parando contenedor anterior..."
docker stop tubescript_api 2>/dev/null && echo " Parado." || echo " No estaba corriendo."
docker rm tubescript_api 2>/dev/null && echo " Eliminado." || echo " No existia."
# 2. Construir imagen con tag explícito (siempre sin cache para forzar yt-dlp latest)
echo ""
echo ">>> [2/7] Construyendo imagen tubescript-api:latest ..."
docker build -f Dockerfile.api -t tubescript-api:latest .
echo " Build OK."
# 3. Asegurar permisos de ./data
echo ""
echo ">>> [3/7] Asegurando permisos de ./data ..."
mkdir -p ./data
chown -R "$(id -u):$(id -g)" ./data 2>/dev/null || sudo chown -R "$(id -u):$(id -g)" ./data
chmod -R u+rwX ./data
ls -la ./data
echo " Permisos OK."
# 4. Crear red si no existe
echo ""
echo ">>> [4/7] Asegurando red tubescript-network ..."
docker network create tubescript-network 2>/dev/null && echo " Red creada." || echo " Red ya existe."
# 5. Levantar contenedor
echo ""
echo ">>> [5/7] Levantando contenedor ..."
docker run -d \
--name tubescript_api \
--network tubescript-network \
-p 8282:8000 \
-v "${REPO_DIR}/data:/app/data:rw" \
-e PYTHONUNBUFFERED=1 \
-e API_COOKIES_PATH=/app/data/cookies.txt \
--restart unless-stopped \
tubescript-api:latest
echo " Contenedor iniciado. Esperando arranque de uvicorn..."
sleep 6
# 6. Verificaciones internas
echo ""
echo ">>> [6/7] Verificaciones del contenedor ..."
echo ""
echo "-- Estado:"
docker ps --filter "name=tubescript_api" --format " ID={{.ID}} STATUS={{.Status}} PORTS={{.Ports}}"
echo ""
echo "-- Logs uvicorn:"
docker logs tubescript_api 2>&1 | tail -6
echo ""
echo "-- Versiones:"
docker exec tubescript_api sh -c "
echo ' node :' \$(node --version 2>/dev/null || echo 'no instalado')
echo ' yt-dlp :' \$(yt-dlp --version 2>/dev/null || echo 'no instalado')
"
# 7. Prueba real de yt-dlp con player_client=android (evita n-challenge sin Node extras)
echo ""
echo ">>> [7/7] Prueba yt-dlp (android client) ..."
echo ""
echo "-- Sin cookies (android client):"
docker exec tubescript_api yt-dlp \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=android" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1 \
&& echo " OK" || echo " FALLO"
echo ""
echo "-- Con cookies (mweb client — acepta cookies web sin n-challenge):"
if [ -s "${REPO_DIR}/data/cookies.txt" ]; then
docker exec tubescript_api yt-dlp \
--cookies /app/data/cookies.txt \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=mweb" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1 \
&& echo " OK - título obtenido con cookies" || echo " FALLO con cookies"
else
echo " AVISO: cookies.txt vacío o no existe."
echo " Sube tus cookies: curl 'http://127.0.0.1:8282/upload_cookies' -F 'file=@/ruta/cookies.txt'"
fi
echo ""
echo "-- Endpoint /debug/metadata:"
sleep 2
curl -s --max-time 30 "http://127.0.0.1:8282/debug/metadata/dQw4w9WgXcQ" \
| python3 -c "
import sys, json
try:
d = json.loads(sys.stdin.read())
print(' title :', d.get('title','?'))
print(' is_live :', d.get('is_live','?'))
print(' id :', d.get('id','?'))
except Exception as e:
print(' ERROR:', e)
" 2>&1
echo ""
echo "======================================================"
echo " LISTO."
echo " API: http://127.0.0.1:8282"
echo " Docs: http://127.0.0.1:8282/docs"
echo ""
echo " Subir cookies:"
echo " curl 'http://127.0.0.1:8282/upload_cookies' -F 'file=@./data/cookies.txt'"
echo "======================================================"

634
main.py

File diff suppressed because it is too large Load Diff

138
run-test.sh Normal file
View File

@ -0,0 +1,138 @@
#!/bin/bash
# Sin cookies → android (sin n-challenge, sin Node.js)
# Con cookies → web + Node.js (Node.js resuelve n-challenge/signature)
# for_stream → android (mejor HLS en lives)
# Script de prueba completo — guarda TODO en /tmp/resultado.txt
exec > /tmp/resultado.txt 2>&1
REPO="/home/xesar/PycharmProjects/TubeScript-API"
cd "$REPO"
echo "=== $(date) ==="
# ---------- 1. Rebuild imagen ----------
echo "--- Parando contenedor anterior ---"
docker rm -f tubescript_api 2>/dev/null || true
echo "--- Construyendo imagen (CACHEBUST para forzar COPY . /app fresco) ---"
# --build-arg CACHEBUST=$(date +%s) invalida solo la capa COPY . /app
# (mucho más rápido que --no-cache que descarga todo desde cero)
docker build \
--build-arg CACHEBUST="$(date +%s)" \
-f Dockerfile.api \
-t tubescript-api:latest . 2>&1 | tail -8
echo "BUILD_RC=$?"
# ---------- 2. Levantar ----------
echo "--- Levantando contenedor ---"
docker run -d \
--name tubescript_api \
--network tubescript-network \
-p 8282:8000 \
-v "${REPO}/data:/app/data:rw" \
-e PYTHONUNBUFFERED=1 \
-e API_COOKIES_PATH=/app/data/cookies.txt \
--restart unless-stopped \
tubescript-api:latest
echo "RC_RUN=$?"
sleep 10
echo "--- docker ps ---"
docker ps --format "{{.Names}} {{.Status}} {{.Ports}}" | grep tube || echo "NO CORRIENDO"
echo "--- uvicorn logs ---"
docker logs tubescript_api 2>&1 | tail -4
echo "--- _yt_client_args en imagen (verificar lógica nueva) ---"
docker exec tubescript_api grep -A12 "def _yt_client_args" /app/main.py
echo ""
echo "=== PRUEBA A: android SIN cookies ==="
docker exec tubescript_api yt-dlp \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=android" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1
echo "RC_A=$?"
echo ""
echo "=== PRUEBA B: web + Node.js CON cookies ==="
docker exec tubescript_api yt-dlp \
--cookies /app/data/cookies.txt \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=web" \
--js-runtimes "node:/usr/bin/node" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1
echo "RC_B=$?"
echo ""
echo "=== PRUEBA C: endpoint /debug/metadata ==="
sleep 2
curl -s --max-time 30 "http://127.0.0.1:8282/debug/metadata/dQw4w9WgXcQ" \
| python3 -c "
import sys,json
raw=sys.stdin.read()
try:
d=json.loads(raw)
if 'detail' in d:
print('ERROR:', d['detail'][:200])
else:
print('title :', d.get('title','?'))
print('uploader:', d.get('uploader','?'))
print('duration:', d.get('duration','?'))
except Exception as e:
print('PARSE ERROR:', e)
print('RAW:', raw[:300])
"
echo "RC_C=$?"
echo ""
echo "=== PRUEBA D: endpoint /transcript?lang=en ==="
curl -s --max-time 90 "http://127.0.0.1:8282/transcript/dQw4w9WgXcQ?lang=en" \
| python3 -c "
import sys,json
raw=sys.stdin.read()
try:
d=json.loads(raw)
if 'detail' in d:
print('ERROR:', d['detail'][:200])
else:
print('count :', d.get('count','?'))
print('preview:', str(d.get('text','?'))[:120])
except Exception as e:
print('PARSE ERROR:', e)
print('RAW:', raw[:200])
"
echo "RC_D=$?"
echo ""
echo "=== PRUEBA E: /transcript/QjK5wq8L3Ac (sin subtítulos — mensaje claro esperado) ==="
curl -s --max-time 60 "http://127.0.0.1:8282/transcript/QjK5wq8L3Ac?lang=es" \
| python3 -c "
import sys,json
raw=sys.stdin.read()
try:
d=json.loads(raw)
if 'detail' in d:
print('DETALLE:', d['detail'][:250])
else:
print('OK count:', d.get('count','?'))
except Exception as e:
print('RAW:', raw[:200])
"
echo "RC_E=$?"
echo ""
echo "=== PRUEBA F: /debug/metadata/QjK5wq8L3Ac (title con cookies) ==="
curl -s --max-time 30 "http://127.0.0.1:8282/debug/metadata/QjK5wq8L3Ac" \
| python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
print('title:',d.get('title','?')) if 'title' in d else print('ERROR:',d.get('detail','?')[:200])
"
echo "RC_F=$?"
echo ""
echo "=== FIN ==="

116
test-completo.sh Normal file
View File

@ -0,0 +1,116 @@
#!/bin/bash
# Test completo de TubeScript-API con cookies reales
set -e
REPO="/home/xesar/PycharmProjects/TubeScript-API"
cd "$REPO"
LOG="/tmp/tubescript_test_$(date +%H%M%S).log"
echo "======================================================"
echo " TubeScript-API — Test completo"
echo " Log: $LOG"
echo "======================================================"
# ---------- 1. Reconstruir imagen ----------
echo ""
echo ">>> [1/5] Parando contenedor anterior..."
docker stop tubescript_api 2>/dev/null && echo " Parado." || echo " No estaba corriendo."
docker rm tubescript_api 2>/dev/null && echo " Eliminado." || echo " No existia."
echo ""
echo ">>> [2/5] Construyendo imagen sin caché..."
docker build --no-cache -f Dockerfile.api -t tubescript-api:latest . 2>&1 \
| grep -E "^#|DONE|ERROR|naming|Built" || true
echo " Build OK."
# ---------- 2. Levantar ----------
echo ""
echo ">>> [3/5] Levantando contenedor..."
docker run -d \
--name tubescript_api \
--network tubescript-network \
-p 8282:8000 \
-v "${REPO}/data:/app/data:rw" \
-e PYTHONUNBUFFERED=1 \
-e API_COOKIES_PATH=/app/data/cookies.txt \
--restart unless-stopped \
tubescript-api:latest
echo " Esperando arranque (8s)..."
sleep 8
docker logs tubescript_api 2>&1 | grep -E "Uvicorn running|startup|ERROR" | head -5
# ---------- 3. Verificar código en imagen ----------
echo ""
echo ">>> [4/5] Verificando lógica de player_client en imagen..."
echo " Líneas clave en main.py:"
docker exec tubescript_api grep -n "mweb\|_yt_client_args\|client =" /app/main.py | head -10
# ---------- 4. Pruebas yt-dlp directas ----------
echo ""
echo ">>> [5/5] Pruebas yt-dlp..."
echo ""
echo " [A] android SIN cookies (cliente base, sin n-challenge):"
docker exec tubescript_api yt-dlp \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=android" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1 \
&& echo " ✅ OK" || echo " ❌ FALLO"
echo ""
echo " [B] mweb,android CON cookies (mweb acepta cookies web, android como fallback):"
docker exec tubescript_api yt-dlp \
--cookies /app/data/cookies.txt \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=mweb,android" \
--print title \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1 \
&& echo " ✅ OK" || echo " ❌ FALLO"
echo ""
echo " [C] dump-json CON cookies (para /debug/metadata):"
docker exec tubescript_api yt-dlp \
--cookies /app/data/cookies.txt \
--no-warnings --skip-download \
--extractor-args "youtube:player_client=mweb" \
--dump-json \
"https://www.youtube.com/watch?v=dQw4w9WgXcQ" 2>&1 \
| python3 -c "import sys,json; d=json.loads(sys.stdin.read()); print(' title:', d.get('title')); print(' uploader:', d.get('uploader'))" \
&& echo " ✅ OK" || echo " ❌ FALLO"
# ---------- 5. Endpoints API ----------
echo ""
echo " [D] Endpoint /debug/metadata:"
sleep 2
RESULT=$(curl -s --max-time 30 "http://127.0.0.1:8282/debug/metadata/dQw4w9WgXcQ")
echo "$RESULT" | python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
if 'detail' in d:
print(' ❌ ERROR:', d['detail'][:200])
else:
print(' ✅ title :', d.get('title','?'))
print(' ✅ uploader:', d.get('uploader','?'))
print(' ✅ is_live :', d.get('is_live','?'))
" 2>&1
echo ""
echo " [E] Endpoint /transcript/dQw4w9WgXcQ?lang=en:"
RESULT2=$(curl -s --max-time 60 "http://127.0.0.1:8282/transcript/dQw4w9WgXcQ?lang=en")
echo "$RESULT2" | python3 -c "
import sys,json
d=json.loads(sys.stdin.read())
if 'detail' in d:
print(' ❌ ERROR:', d['detail'][:200])
else:
print(' ✅ count :', d.get('count','?'))
print(' ✅ preview :', str(d.get('text',''))[:100])
" 2>&1
echo ""
echo "======================================================"
echo " DONE. API: http://127.0.0.1:8282 Docs: http://127.0.0.1:8282/docs"
echo "======================================================"