UnseenStream

UnseenStream

Watch videos nobody has seen.
A swipe feed of YouTube and archive.org uploads with zero views, served once and then gone for everyone.

Live: unseen.bitmancer.net GitHub stars

Only zero-view videos At least two hours old No Shorts Runs on Docker License: MIT

Buy me a coffee


unseen.bitmancer.net shows one video at a time: zero views, at least two hours old, longer than three minutes. As soon as a video has been served it is consumed globally, so the next visitor gets something else. The feed mixes YouTube with archive.org, and the footer switches between them.

How it works

The expensive part of “find unseen videos” is not serving them, it is discovering them: one YouTube search request costs 100 quota units and the free daily budget is 10,000. Storing a pool is therefore not the bottleneck, quota is.

YouTube Data API  --100 units-->  discoverer  -->  channels (SQLite)
YouTube RSS       ---0 units--->  poller      <--
Internet Archive  ---0 units--->  archive     <--
                                      |
                                videos (SQLite)
                                      |
                        api  <--------
                          |
                        Caddy  -->  browser
Component Job Cost
us-discoverer 4 searches per hour, enriched with videos.list, writes channels to the watchlist ~9,700 units/day
us-poller polls channel RSS feeds (views included), keeps candidates fresh, backfills durations no API quota
archive pulls mediatype:movies items with downloads: 0, walks whole years backwards, extracts thumbnails with ffmpeg no API key, polite rate limit
us-api selection, /current-video, downloads, counters, static frontend none

YouTube and archive.org land in the same videos table. The API filters by the source field the frontend sends (all, youtube, archive, or a comma-separated subset).

What gets served

Features

Domain

https://unseen.bitmancer.net is the site. The old unseenstream.bitmancer.net host still reaches the same container but returns web/migrate.html, which copies localStorage (saved videos, seen list, settings) into the new origin through the URL hash and then redirects. Old bookmarks keep working and land with their library intact.

Downloads

archive.org items redirect straight to the public MP4. YouTube has no file URL, so the API pulls the video with yt-dlp (best stream up to 720p, merged to MP4) and streams it back.

YouTube challenges datacenter IPs with “confirm you’re not a bot”. The API retries with the tv_embedded client and, when present, uses cookies:

/opt/unseenstream/data/youtube-cookies.txt   # Netscape format, readable by uid 10001

Without cookies some videos stay blocked; the browser shows a toast instead of an error page.

Endpoints

Endpoint Purpose
GET\|POST /current-video one candidate; POST accepts excluded_ids, exclude_channels, source, sort
GET /video/<id> one video by id, ?refresh=1 pulls a fresh view count
GET /download/<id> streams the video (YouTube via yt-dlp, archive.org via redirect)
GET /sources providers this instance can serve
GET /unseen-count size of the in-memory candidate pool, optional ?source=
GET /health pool size, channels, last discovery, quota used today
GET /stats per-provider counts, tiers, quota, uptime

Configuration

Variable Default Meaning
YOUTUBE_API_KEY — required for discovery
US_DAILY_QUOTA 9700 API budget per Pacific day
US_SEARCHES_PER_HOUR 4 discovery pace
US_MAX_VIEWS 100 discovery filter: keep channels producing low-view videos
US_SERVE_MAX_VIEWS 0 serving filter: only videos with this many views
US_MIN_AGE_HOURS 2 serving filter: ignore younger videos
US_MAX_AGE_HOURS 48 serving filter: ignore older videos
US_MIN_DURATION_S 180 serving filter: exclude Shorts
US_ARCHIVE_ENABLED 1 enable archive.org
US_ARCHIVE_LIMIT 200 items per archive run
US_ARCHIVE_INTERVAL 600 seconds between archive runs
US_ARCHIVE_VINTAGE_START / _END 2025 / 2015 years harvested for old unseen items
US_YTDLP_COOKIES /data/youtube-cookies.txt cookie file for YouTube downloads
US_HOT_INTERVAL / US_WARM_INTERVAL / US_COLD_INTERVAL 600 / 3600 / 21600 poll intervals
US_LOG_LEVEL INFO logging

Discovery and serving use different view thresholds on purpose: a channel that produced a 40-view video is still worth tracking, but only zero-view videos are shown.

Development

python -m venv .venv
.venv/Scripts/pip install -r requirements-dev.txt   # Windows
pytest
python -m app.cli initdb

Roles: python -m app.cli {seed,discoverer,poller,enrich,bootstrap,verify,initdb}.

Deployment

Docker Compose on a VPS behind Caddy:

scp -r app web Dockerfile requirements.txt server:/opt/unseenstream/
ssh server "cd /opt/unseenstream && docker compose up -d --build"

Three containers share one SQLite volume: us-api (Flask, port 8000), us-poller and us-discoverer. Caddy terminates TLS:

unseen.bitmancer.net {
    encode zstd gzip
    reverse_proxy us-api:8000
}

License

MIT