Publica clips no YouTube em qualquer número de canais com fila de jobs, horários por canal, retry e aviso no Telegram. Sem 35 processos, sem 17 cópias do código.

O yt-pub-livesx corta lives em clips e republica em outro canal. Funcionou tão bem que virou 17 instâncias copiadas, 35 processos e 18 portas. A v2 mantém os scripts que fazem o trabalho e troca só a orquestração.
O hub.py lê canais/*.yaml a cada 30 s, agenda um job por horário e executa em ordem. Editar o YAML basta: não há restart.
Cada canal aponta config_dir para a pasta que já tem o OAuth. Nada é copiado, nada é migrado. Os 17 canais do v1 entram sem reautenticar.
Jobs idempotentes por chave, claim atômico em SQLite, retry para falha transitória e recuperação se o processo morrer no meio de um upload.
| v1 (yt-pub-livesx) | v2 (yt-pub-livesx2) | |
|---|---|---|
| processos | 35 | 2 |
| portas TCP | 18 | 1 |
| bancos SQLite | 17 | 1 |
| cópias do código | 19 | 1 |
| linhas de orquestração | 11.363 | 965 |
| testes automatizados | 0 | 15 |
Qualquer coisa que gere um MP4 com título é um produtor: a CLI, a pasta entrada/<canal>/, um workflow n8n, o cortador de lives. O hub só cuida do quando e do estado.
./hub publicar, ou copiar o arquivo para entrada/<canal>/ com um .json opcional ao lado (título, descrição, tags, capa).
Para cada canal ativo e cada horário do YAML, um job pub:<canal>:<dia HH:MM>. Reiniciar o hub não dobra nem perde publicação.
O runner é o único que publica. Falha de rede volta para a fila; OAuth recusado vira erro visível no painel e na CLI.
Nada de Redis, Docker ou framework. Testado em Ubuntu com Python 3.12.
cryptography (mesmo formato de credencial do v1) e PyYAML.
# na pasta do projeto pip install -r requirements.txt
Um projeto Google Cloud por canal, com .env e credentials.enc. Quem vem do v1 já tem; quem começa do zero usa o script.
GWS_CONFIG_DIR=/caminho/config scripts/yt-authUm bot dedicado para avisar publicações. Token único, na raiz do projeto.
cp .env.example .env # TELEGRAM_NOTIFY_BOT_TOKEN=...
Todos os comandos abaixo existem no repo e foram executados. Nenhum faz upload real até você pedir com --agora.
O projeto é só Python e stdlib, mais duas bibliotecas.
git clone https://github.com/inematds/yt-pub-livesx2 ~/projetos/yt-pub-livesx2 cd ~/projetos/yt-pub-livesx2 && pip install -r requirements.txt
Copie o exemplo e aponte config_dir para a pasta que tem o OAuth. Quem tem instâncias v1 pula para o passo 3, que gera os YAML sozinho.
cp canais/_exemplo.yaml canais/meucanal.yaml # destino_nome, config_dir, horarios: ["08:10","16:10"], privacy, telegram_chat_ids, ativo
Lê o lives.db de cada yt-pub-livesN em modo read-only, cria canais/livesN.yaml com ativo: false e traz os publicados. Pode rodar de novo quando quiser.
scripts/importar-historico --todos # 17 instancias, 12.466 publicacoes importadas
Um comando mostra qual canal tem OAuth válido. Foi assim que apareceram 4 canais do v1 falhando em silêncio.
./hub status # fila, publicados, erros e ultimo por canal ./hub canais --token # refresca o token de cada canal: OK ou motivo da falha
Direto pela CLI, na hora ou na fila. O --dry-run decripta, refresca o token e monta a metadata, mas para antes do upload.
./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --dry-run # valida tudo ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --agora # sobe agora ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" # fila, sai no proximo horario
Mesmo efeito sem CLI, para n8n, scripts ou uma pessoa arrastando arquivos. Um .json com o mesmo nome traz título, descrição, tags e capa.
cp video.mp4 video.json entrada/lives8/ # video.json: {"titulo":"...","descricao":"...","tags":["a","b"],"privacy":"public"}
Dois serviços de usuário no systemd. Com todos os canais inativos o hub só observa; ative um canal por vez trocando ativo: true no YAML e parando o scheduler v1 correspondente.
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 # painel: http://localhost:8200
Fila e erros pela CLI ou pelo painel único. Clip com erro volta para a fila com um comando.
./hub fila # clips pendentes e com erro ./hub jobs 20 # ultimos jobs executados ./hub retry 12467 # clip com erro volta pra fila python3 tests/test_jobs.py # 15 testes, sem rede
Capturas de texto da primeira execução, em 2026-09-06, contra as 17 instâncias 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 canais OK, 5 com OAuth recusado ou expirado:
# 4 deles publicavam "erro" a cada horario sem ninguem saberclip #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 quando parar o yt-scheduler8Teto: o núcleo fica abaixo de 1.500 linhas. O que passar disso vira script chamado por um job.
yt-clip e yt-thumbnail do v1 já estão copiados; viram tipos de job cortar_live e thumb com LIVES_DIR por canal.docs/PUBLICADORES.md.yt-schedulerN, trocar ativo: true, observar. As pastas v1 ficam como cofre de credenciais e de lives em disco.