Publishes clips to YouTube across any number of channels, with a job queue, per-channel schedules, retries, and Telegram notifications. No 35 processes, no 17 copies of the code.

O yt-pub-livesx cuts livestreams into clips and reposts them to another channel. It worked so well that it grew into 17 copied instances, 35 processes, and 18 ports. V2 keeps the scripts that do the work and changes only the orchestration.
O hub.py reads canais/*.yaml every 30 seconds, schedules a job for each time slot and runs them in order. Just edit the YAML: no restart is needed.
Each channel points config_dir to the folder that already has OAuth. Nothing is copied, nothing is migrated. The 17 v1 channels are included without reauthentication.
Idempotent jobs keyed by ID, atomic claims in SQLite, retries for transient failures, and recovery if the process dies during an upload.
| v1 (yt-pub-livesx) | v2 (yt-pub-livesx2) | |
|---|---|---|
| processes | 35 | 2 |
| TCP ports | 18 | 1 |
| SQLite databases | 17 | 1 |
| copies of the code | 19 | 1 |
| orchestration lines | 11.363 | 965 |
| automated tests | 0 | 15 |
Anything that generates an MP4 with a title is a producer: the CLI, the folder entrada/<canal>/, an n8n workflow, the live stream cutter. The hub only handles the when and the state.
./hub publicar, or copy the file to entrada/<canal>/ with one .json optional alongside it (title, description, tags, thumbnail).
For each active channel and each time slot in the YAML, a job pub:<canal>:<dia HH:MM>. Restarting the hub won't duplicate or lose a post.
O runner is the only one that publishes. Network failures return to the queue; rejected OAuth credentials become an error visible in the dashboard and CLI.
No Redis, Docker, or framework. Tested on Ubuntu with Python 3.12.
cryptography (same credential format as v1) and PyYAML.
# in the project folder pip install -r requirements.txt
One Google Cloud project per channel, with .env e credentials.enc. Anyone coming from v1 already has it; anyone starting from scratch uses the script.
GWS_CONFIG_DIR=/caminho/config scripts/yt-authA dedicated bot for publication notifications. One token, in the project root.
cp .env.example .env # TELEGRAM_NOTIFY_BOT_TOKEN=...
All the commands below are in the repo and have been run. None uploads anything for real until you ask with --agora.
The project uses only Python and the standard library, plus two libraries.
git clone https://github.com/inematds/yt-pub-livesx2 ~/projetos/yt-pub-livesx2 cd ~/projetos/yt-pub-livesx2 && pip install -r requirements.txt
Copy the example and point config_dir to the folder with OAuth. If you have v1 instances, skip to step 3, which generates the YAML files automatically.
cp canais/_exemplo.yaml canais/meucanal.yaml # destination_name, config_dir, schedules: ["08:10","16:10"], privacy, telegram_chat_ids, active
Reads the lives.db of each yt-pub-livesN in read-only mode, creates canais/livesN.yaml with ativo: false and fetches the published videos. You can run it again whenever you want.
scripts/importar-historico --todos # 17 instances, 12,466 posts imported
One command shows which channels have valid OAuth. Thatβs how 4 v1 channels failing silently came to light.
./hub status # queue, published, errors, and latest per channel ./hub canais --token # refreshes each channel's token: OK or reason for failure
Directly through the CLI, immediately or in the queue. The --dry-run decrypts, refreshes the token, and builds the metadata, but stops before upload.
./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --dry-run # validates everything ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" --agora # start now ./hub publicar video.mp4 --canal lives8 --titulo "Titulo" # queue, runs at the next scheduled time
Same functionality without the CLI, for n8n, scripts, or someone dragging and dropping files. A .json with the same name brings over the title, description, tags, and thumbnail.
cp video.mp4 video.json entrada/lives8/ # video.json: {"titulo":"...","descricao":"...","tags":["a","b"],"privacy":"public"}
Two systemd user services. With all channels inactive, the hub only monitors; activate one channel at a time by changing ativo: true in the YAML and stopping the corresponding v1 scheduler.
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 # dashboard: http://localhost:8200
Queue and errors through the CLI or the unified dashboard. A clip with an error goes back in the queue with one command.
./hub fila # pending and errored clips ./hub jobs 20 # latest jobs run ./hub retry 12467 # clip with an error goes back in the queue python3 tests/test_jobs.py # 15 tests, no network
Text captures from the first run, on 2026-09-06, compared with the 17 v1 instances.
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 channels OK, 5 with OAuth denied or expired:
# 4 of them were publishing "error" on every scheduled run without anyone knowingclip #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 when yt-scheduler8 stopsLimit: the core stays under 1,500 lines. Anything beyond that becomes a script called by a job.
yt-clip e yt-thumbnail from v1 have already been copied; they become job types cortar_live e thumb with LIVES_DIR per channel.docs/PUBLICADORES.md.yt-schedulerN, change ativo: true, monitor it. The v1 folders remain as a vault for credentials and live streams on disk.