Publica clips en YouTube en cualquier cantidad de canales con una cola de trabajos, horarios por canal, reintentos y avisos por Telegram. Sin 35 procesos ni 17 copias del código.

O yt-pub-livesx corta transmisiones en vivo en clips y los vuelve a publicar en otro canal. Funcionó tan bien que terminó convertido en 17 instancias copiadas, 35 procesos y 18 puertos. La v2 mantiene los scripts que hacen el trabajo y solo cambia la orquestación.
O hub.py lee canais/*.yaml cada 30 s, programa un job por horario y los ejecuta en orden. Basta con editar el YAML: no hace falta reiniciar.
Cada canal apunta config_dir a la carpeta que ya tiene el OAuth. No se copia ni se migra nada. Los 17 canales de la v1 se incorporan sin volver a autenticarse.
Trabajos idempotentes por clave, claim atómico en SQLite, reintento ante fallas transitorias y recuperación si el proceso muere durante una carga.
| v1 (yt-pub-livesx) | v2 (yt-pub-livesx2) | |
|---|---|---|
| procesos | 35 | 2 |
| puertos TCP | 18 | 1 |
| bases de datos SQLite | 17 | 1 |
| copias del código | 19 | 1 |
| líneas de orquestación | 11.363 | 965 |
| pruebas automatizadas | 0 | 15 |
Cualquier cosa que genere un MP4 con título es un productor: la CLI, la carpeta entrada/<canal>/, un workflow de n8n, el cortador de lives. El hub solo se encarga del cuándo y del estado.
./hub publicar, o copiar el archivo a entrada/<canal>/ con un .json opcional al lado (título, descripción, etiquetas, portada).
Para cada canal activo y cada horario del YAML, un trabajo pub:<canal>:<dia HH:MM>. Reiniciar el hub no duplica ni pierde publicaciones.
O runner es el único que publica. Si falla la red, vuelve a la cola; si se rechaza el OAuth, el error queda visible en el panel y en la CLI.
Sin Redis, Docker ni framework. Probado en Ubuntu con Python 3.12.
cryptography (mismo formato de credenciales que la v1) y PyYAML.
# en la carpeta del proyecto pip install -r requirements.txt
Un proyecto de Google Cloud por canal, con .env e credentials.enc. Quien viene de v1 ya lo tiene; quien empieza desde cero usa el script.
GWS_CONFIG_DIR=/caminho/config scripts/yt-authUn bot dedicado para avisar sobre publicaciones. Un único token en la raíz del proyecto.
cp .env.example .env # TELEGRAM_NOTIFY_BOT_TOKEN=...
Todos los comandos de abajo existen en el repo y se ejecutaron. Ninguno sube contenido de verdad hasta que lo pidas con --agora.
El proyecto usa solo Python y la stdlib, más dos bibliotecas.
git clone https://github.com/inematds/yt-pub-livesx2 ~/projetos/yt-pub-livesx2 cd ~/projetos/yt-pub-livesx2 && pip install -r requirements.txt
Copia el ejemplo y apunta config_dir a la carpeta que tiene el OAuth. Si tienes instancias de la v1, pasa al paso 3, que genera los YAML automáticamente.
cp canais/_exemplo.yaml canais/meucanal.yaml # destino_nome, config_dir, horarios: ["08:10","16:10"], privacy, telegram_chat_ids, ativo
Lee el lives.db de cada yt-pub-livesN en modo read-only, crea canais/livesN.yaml con ativo: false y trae los publicados. Puedes volver a ejecutarlo cuando quieras.
scripts/importar-historico --todos # 17 instancias, 12.466 publicaciones importadas
Un comando muestra qué canal tiene un OAuth válido. Así aparecieron 4 canales de la v1 que fallaban en silencio.
./hub status # cola, publicados, errores y el último por canal ./hub canais --token # actualiza el token de cada canal: OK o motivo del fallo
Directamente desde la CLI, en el momento o en la cola. El --dry-run descifra, actualiza el token y prepara los metadatos, pero se detiene antes de subir.
./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --dry-run # valida todo ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --agora # inicia ahora ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" # cola, sale en el próximo horario
El mismo efecto sin CLI, para n8n, scripts o alguien que arrastra archivos. Un .json con el mismo nombre, trae el título, la descripción, las etiquetas y la portada.
cp video.mp4 video.json entrada/lives8/ # video.json: {"titulo":"...","descricao":"...","tags":["a","b"],"privacy":"public"}
Dos servicios de usuario en systemd. Con todos los canales inactivos, el hub solo observa; activa un canal a la vez cambiando ativo: true en el YAML y deteniendo el scheduler v1 correspondiente.
cp systemd/yt-hub.service systemd/yt-hub-dashboard.service ~/.config/systemd/user/ systemctl --user daemon-reload && systemctl --user enable --now yt-hub yt-hub-dashboard # panel: http://localhost:8200
Cola y errores por CLI o desde el panel único. Los clips con error vuelven a la cola con un comando.
./hub fila # clips pendientes y con error ./hub jobs 20 # últimos jobs ejecutados ./hub retry 12467 # el clip con error vuelve a la cola python3 tests/test_jobs.py # 15 pruebas, sin red
Capturas de texto de la primera ejecución, en 2026-09-06, frente a las 17 instancias v1.
canal destino fila public. erros ultimo lives1 INEMA TDS 0 2413 0 2026-09-04 17:15 lives6 INEMA Robot 0 1110 0 2026-09-06 17:40 lives8 Inema Vibe 0 1043 0 2026-09-06 16:10 lives11 INEMA Futuros 0 738 0 2026-09-06 21:40 ... 17 canais | fila 0 | publicados 12466 | erros 0 | hub v2.0.0
lives1 OK creds horarios=08:15,10:15,...
token: FALHOU — refresh_token recusado (400)
lives8 OK creds horarios=08:10,16:10
token: OK
...
# 12 canales OK, 5 con OAuth rechazado o vencido:
# 4 de ellos publicaban "error" a cada hora sin que nadie lo supieraclip #12467 na fila de lives8 [lives8] publicando #12467: Teste dry-run hub v2 [lives8] token: refresh via .../yt-pub-lives8/config [lives8] metadata: 'Teste dry-run hub v2' privacy=public [lives8] DRY RUN — parando antes do upload dry-run: ok
destino_id: UCJxL11aomXJiBZga-JXylgQ
destino_nome: Inema Vibe
config_dir: /home/.../yt-pub-lives8/config
horarios: ["08:10", "16:10"]
privacy: public
telegram_chat_ids: ["7852..."]
ativo: false # true cuando se detenga yt-scheduler8Límite: el núcleo se mantiene por debajo de 1.500 líneas. Lo que supere ese límite pasa a ser un script llamado por un trabajo.
yt-clip e yt-thumbnail de la v1 ya están copiadas; pasan a ser tipos de job cortar_live e thumb con LIVES_DIR por canal.docs/PUBLICADORES.md.yt-schedulerN, cambiar ativo: true, observar. Las carpetas v1 quedan como bóveda de credenciales y de lives en disco.