james 1523bdd48d Fetch YouTube at up to 1080p instead of 360p
YouTube's only muxed mp4 is format 18 (360p), so the muxed-first selector
was posting 360p even when 1080p was available. Split streams are now
preferred for YouTube and merged locally; every other host keeps the
muxed-first chain unchanged (verified identical selection for Instagram,
X and Reddit).

Three things the obvious version got wrong:

- A height<=1080 cap excludes portrait video: a 1080p Short or reel is
  1080x1920. Resolution is capped with -S res:1080 instead, which keys
  off the smaller dimension.

- h264 is a hard first preference, not an -S sort key. At equal
  resolution YouTube's VP9/AV1 renditions are smaller and win on size,
  but neither reliably plays in an mp4 container on older Signal
  clients. Dropping a resolution rung to stay on h264 is the better
  trade, and is usually smaller too: a 10-min video takes 480p h264
  (37 MB) over 720p AV1 (78 MB).

- The audio cap matters. YouTube's 388k track is 29 MB by itself on a
  10-min video, enough to push an otherwise-fine merge past the 100 MB
  Signal limit; abr<=160 takes the 129k track instead.

The 88 MB video cap makes yt-dlp step down automatically on long videos,
so a 3-min video lands at 1080p (80 MB) and a 10-min one at 480p.

Clips get the same treatment — they're YouTube-only, and 60s at 1080p is
only ~29 MB. /force is deliberately left muxed-first: it exists for the
biggest, slowest videos, where 1080p would risk the rsync timeout.

Measured, old -> new: 3-min 360p/11 MB -> 1080p/80 MB; 10-min 360p/27 MB
-> 480p/37 MB; portrait Short 360x640/6 MB -> 1080x1920/23 MB. Verified
end to end: real downloads decode as h264+aac, clip lands at exactly
60.000s.
2026-08-14 14:30:11 -04:00

signal-bot

A Signal group-chat bot that grabs videos from links people post (X/Twitter, Instagram, YouTube, TikTok, Reddit) and posts them back into the chat, plus a couple of toys for messing with the last video.

What it does

  • Auto-download — watches every group message for a supported link and replies with the video. Powered by yt-dlp. Videos over 100 MB are auto re-encoded with ffmpeg to fit. YouTube is fetched as h264 up to 1080p, stepping down a resolution rung on longer videos so the merge lands under the Signal size limit without a re-encode; other hosts take their muxed rendition as-is.
  • /speed [n] — re-posts the last video sped up n× (default 2×), pitch-shifted like tape. e.g. /speed 4, /speed 0.5.
  • /rev — re-posts the last video reversed (audio too).
  • /cookies (DM, admins only) — paste exported .instagram.com cookie lines to refresh Instagram auth without touching the server.

Supported links: x.com / twitter.com (and fxtwitter/vxtwitter/fixupx wrappers), instagram.com/reel|p, youtube.com / youtu.be / Shorts, TikTok, and Reddit (/comments/ permalinks, /s/ app share links, redd.it and v.redd.it shortlinks). Reddit posts with no video stay silent.

How it's wired

Signal  ──►  signal-cli-rest-api (json-rpc, :8080)  ──►  bot.py (signalbot lib)
                  [docker-compose.yml]                      [systemd service]
                                                                  │
                                                       yt-dlp + ffmpeg + cookies.txt
  • bot.py connects to a signal-cli-rest-api daemon over a websocket in json-rpc mode.
  • docker-compose.yml runs that daemon; the linked-account data lives in a ./signal-cli-data volume (gitignored — it holds private keys).
  • signal-bot.service.example is the systemd unit that runs bot.py.

Setup

  1. Run signal-cli-rest-api and link it as a device to your Signal account:

    docker compose up -d
    

    Then link via the QR at http://127.0.0.1:8080/v1/qrcodelink?device_name=bot (scan from Signal → Settings → Linked devices). Keep signal-cli up to date — an out-of-date signal-cli silently stops receiving messages when Signal changes its server protocol (getServerGuid must not be null in the logs); docker compose pull && docker compose up -d fixes it.

  2. Python env:

    python3 -m venv venv
    venv/bin/pip install -r requirements.txt
    

    YouTube also needs Node >= 22 on PATH (for yt-dlp-ejs's challenge solver). If your distro only ships an older Node, install a standalone Node 22+ and point bot.py's _NODE22 at it. Note: YouTube cookies are deliberately withheld (_cookie_args) — sending account cookies forces yt-dlp onto a player client whose media URLs 403; cookieless YouTube requests work.

  3. Cookies: copy cookies.txt.example to cookies.txt and fill in real exported cookies for the sites you want auth'd. (gitignored)

  4. Service: copy signal-bot.service.example to signal-bot.service, set your user / phone number / paths, install it:

    sudo cp signal-bot.service /etc/systemd/system/
    sudo systemctl enable --now signal-bot
    

Notes

  • The bot only acts in group chats (except /cookies, which is DM-only).
  • Reply-targeting (e.g. "speed up this video") was intentionally dropped: Signal Desktop strips quote metadata from synced transcripts, so /speed and /rev always act on the last video seen in the group.
S
Description
Signal group-chat video-grabber bot
Readme
240 KiB
Languages
Python 100%