Skip to main content

streamlens

PyPI Python CI License: MIT

Live streams in one Telegram message that keeps itself up to date. Hand it a list of streams (or a list of channels) and a bot token; it posts who is live, how many viewers they have, how that changed, and a slideshow of frames from the last 20 minutes for each stream.

  • No server, no database. Pure Python; state is one small SQLite file.
  • Bring your own data (Publisher.publish([...])) or let it watch channels for you (streamlens watch).
  • YouTube, Twitch, Kick out of the box; any other platform if you pass the numbers yourself.
  • Safe updates: the new message is sent first, then the old one is deleted; a failed send never empties the chat.
  • English and Russian texts, time zone for the footer, size limits of Telegram handled for you.

It grew out of a production Airflow + Postgres pipeline that tracks streams around the clock; this is the same message format, extracted into something you can pip install.

Quick start

pip install streamlens

You need a bot token from @BotFather and a chat id. Add the bot to your group (or make it an admin of your channel) and check that it can post:

export STREAMLENS_TOKEN="123456:ABC..."
export STREAMLENS_CHAT_ID="-1001234567890"   # group / channel id, or your own user id
streamlens check

How to find the chat id: add @RawDataBot to the group, or open https://api.telegram.org/bot<TOKEN>/getUpdates after writing anything to your bot.

1. You have the data

from streamlens import Publisher, Stream

publisher = Publisher(token="123456:ABC...", chat_id="-1001234567890", lang="en", tz="Europe/Berlin")

publisher.publish([
    Stream(name="Some Twitch channel", url="https://www.twitch.tv/some_channel", online=12400),
    Stream(name="Some YouTube channel", url="https://www.youtube.com/@some_channel/live", online=830,
           title="Late night talk", image=open("frame.jpg", "rb").read()),
])

Call publish every ~5 minutes (cron, Airflow, a loop). Only name and url are required; the platform is detected from the URL. Each call stores the frame you pass in image, so the slideshow now / 5 / 10 / 15 / 20 min ago builds up by itself. The change in viewers versus ~10 minutes ago is computed from the history the same way.

From the shell, with a JSON or CSV file (or - for stdin):

streamlens send examples/streams.json
cat streams.csv | streamlens send -          # columns: name,url,online[,title,image,started_at,...]
streamlens send examples/streams.json --dry-run --chat-id 1   # print instead of sending
pip install "streamlens[frames]"     # + ffmpeg on PATH, for YouTube/Kick frames
streamlens watch examples/channels.txt --every 5

channels.txt is one URL per line (Name | URL to set a display name). Every 5 minutes it finds out who is live, grabs a frame and updates the message.

Platform Live status Viewers Frame Notes
YouTube yt-dlp yes from the stream (ffmpeg) use the channel /live URL
Twitch public preview with a free Twitch app: set TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET public preview works without keys, just no viewer count
Kick yt-dlp no (Kick has no public count) from the stream (ffmpeg) pass your own numbers via Publisher if you have them

Age-restricted YouTube streams need a cookies.txt of a YouTube account (--cookies); it is used only for those.

Options

Option (Publisher(...) / CLI flag) Default What it does
lang / --lang en en or ru
tz / --tz UTC time zone of the "updated" footer, e.g. Europe/Moscow
mode / --mode auto rich (frames), text (plain), auto = rich, falls back to text if the Bot API has no rich messages
split / --split one message several messages, e.g. --split twitch+kick --split youtube
sort / --sort asc asc: the biggest stream ends up at the bottom, next to the input field
min_online / --min-online 0 hide small streams
exclude / --exclude none names or URLs that are never shown
state / --state ./streamlens.db where message ids and frames are kept

streamlens clear deletes the messages the bot posted. --dry-run works everywhere and never touches Telegram.

Deploy

Anything that runs a command every few minutes works. Pick one.

Long-running process (systemd, tmux, a VPS): streamlens watch channels.txt --every 5

cron: */5 * * * * streamlens watch /path/channels.txt --once (env vars in the crontab or an env file)

Docker:

cp examples/channels.txt channels.local.txt        # edit
printf 'STREAMLENS_TOKEN=...\nSTREAMLENS_CHAT_ID=...\n' > .env
docker compose up -d

The image contains ffmpeg and Node.js (the JS runtime that yt-dlp needs for YouTube). State lives in a volume.

Existing pipeline (Airflow, a script): import Publisher and call publish() with your data on your schedule.

Telegram limits it handles for you

  • One rich message takes at most 50 pictures. With many streams the frames per stream go 5 → 4 → 3 → 2 → 1, and above 50 streams only the biggest ones get a frame.
  • Telegram clients fold long rich messages behind "Show more" after roughly 20 pictures in the visible part. This is a client behaviour; no Bot API setting for it is documented. Measured on real messages: the fewer frames per stream, the more streams stay visible before the fold.
  • Bot tokens never appear in error messages or logs.

Development

git clone https://github.com/klipbn/streamlens && cd streamlens
pip install -e ".[dev,frames]"
pytest && ruff check .

MIT © Alexey Voronko. Russian version: README.ru.md.

Release files for streamlens 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for streamlens 0.1.0
File Size Uploaded
streamlens-0.1.0.tar.gz 22.9 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for streamlens 0.1.0
File Interpreter ABI Platform
streamlens-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 46.1 kB

Release files / streamlens-0.1.0.tar.gz

Download URL streamlens-0.1.0.tar.gz
Size 22.9 kB
Tags Source
SHA-256 checksum
How to use checksums
13393cd232cf6b480ef6545714cb6439a357827c3d2390e9e263ecafae6c79a0
BLAKE2b-256 checksum
How to use checksums
8984bf5c3c6def817be0d1bff8d25529dc87fbce79a845064746dd3faf0fbbff
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.5

Release files / streamlens-0.1.0-py3-none-any.whl

Download URL streamlens-0.1.0-py3-none-any.whl
Size 23.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
3e147dc73bb47b1c3d6e92cd494d9e1dccab0b2d24239fdafce0640537963afa
BLAKE2b-256 checksum
How to use checksums
4578e99d7926ae57d32e4abac325d73aec4fe0584d5a1ee04d5fc83fd15fafd0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.5

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page