A small, local streaming project for personal use. It grabs anime (from aniworld.to), series (from S.to) and movies (from filmpalast.to) ad-free, neatly named and sorted by language and plays them back through its own web interface. You've got two ways to watch:
- Stream the links directly – the UI resolves the hoster embed and plays the stream right away, no download needed. Good if you just want to watch now.
- Download to real
.mp4files – the downloader saves every episode/movie to your disk, named and sorted by language. Once it's there you get fully offline, ad-free playback, and you can grab the file itself.
You don't have to download anything to start watching, but the .mp4 files are
there if you want them.
Heads up: this thing is really meant for your own local network only. So you run the server on one machine and then open it from your laptop, tablet or phone on the same WiFi via
http://<server-ip>:3000. Please don't expose it to the public internet – it's not just legally iffy, it also isn't built for that. There is no login, no encryption and no real protection of any kind. So: at home, on your own network, that's it.
Think of it as a little homemade Netflix. Plain vanilla JS up front, a bit of Node behind it. No framework, no database, no cloud.
- Scrape a catalog from three sources (anime / series / movies)
- Auto-download every episode and movie, one file per language
- A web UI to browse, search, filter (genre + language) and play
- Watch either way: stream straight from the hoster links, or play the local
.mp4files you downloaded (ad-free, offline). - Per-episode progress is saved (localStorage), next episode autoplays
- "Watch Party" / synced watching with friends on the same network
- Cover badges for new titles / new seasons / new episodes (7 days)
my-streams/
├── server.mjs # the small backend server (Node)
├── unified-scraper.mjs # builds the catalog (out/unified_catalog.jsonl)
├── unified-download.mjs # downloads the videos (Playwright + ffmpeg)
├── refresh-meta.mjs # small tool: refresh cover/title
├── public/ # the web UI (html/js/css)
│ ├── index.html
│ ├── app.js
│ └── style.css
├── out/ # the catalog lands here (ignored by .gitignore)
├── media/ # the videos land here (ignored by .gitignore)
├── package.json
├── .gitignore
├── LICENSE
└── README.md
The out/ and media/ folders are intentionally empty in the repo (that's
where the stuff you don't want in git ends up). On first run just scrape and
out/unified_catalog.jsonl gets created.
| Version | Description |
|---|---|
| v1.0.0 | Initial release: anime, series and movies from aniworld, S.to and filmpalast. Scraper, downloader, web UI and Watch Party included. |
| v1.1.0 | Manga support (MangaDex), faster scraper with worker pool, manga reader, crash protection, retry system, RAM monitoring and many UI improvements. |
You need:
- Node.js (v18 or newer, tested on v24)
- npm
- a little patience on the first download
one time:
npm install
npx playwright install chromiumOn Windows you can also just double-click run.bat, it does most of this for
you (install + chromium + start server). On Linux there's the same thing as
run.sh.
# everything at once (anime + series + movies):
node unified-scraper.mjs
# anime only:
SOURCE=aniworld node unified-scraper.mjs
# series only:
SOURCE=sto node unified-scraper.mjs
# movies only:
SOURCE=filmpalast node unified-scraper.mjs
# test run with the first 50 titles:
LIMIT=50 node unified-scraper.mjsTakes a while depending on how much you want. The scraper is incremental –
already-scraped titles get updated, not reloaded from scratch. Progress is
written to out/unified_catalog.jsonl as it goes.
# everything:
node unified-download.mjs
# anime / series / movies only:
SOURCE=aniworld node unified-download.mjs
SOURCE=sto node unified-download.mjs
SOURCE=filmpalast node unified-download.mjs
# a single title:
SLUG=tower-of-god node unified-download.mjs
# test run (first 3):
LIMIT=3 node unified-download.mjsPer episode it grabs up to three files (GerDub, EngSub, GerSub), for movies up to two (Ger, Eng). Already-existing files are skipped. If a hoster fails (e.g. VOE with its anti-bot), it automatically tries the next one.
node server.mjs
# then open http://localhost:3000 in your browserFrom your phone on the same WiFi use http://<pc-ip>:3000. Done.
- Dropdown on top: All / Anime / Series / Movies – filters the view
- Home / My List / Watch Party / Settings for quick access
- Search: searches titles, slugs and genres
- Genre chips: quickly filter by genre
- Language dropdown: show only GerDub / EngSub / GerSub
- In the player: if a local
.mp4exists it plays first (offline, ad-free), otherwise it streams the hoster link; next-episode autoplay, progress saved
Click "Watch Party" up top, create a lobby (or join with an ID) and send the link to people on the same network. The host controls play / pause / seek / what's playing – everyone else sees it in sync. Max 5 people per lobby, kept simple.
| Route | What it does |
|---|---|
GET /api/catalog |
list of all titles (filterable with ?q= and ?source=) |
GET /api/title/:source/:slug |
full record incl. episodes; local files are detected |
GET /api/reload |
re-read the catalog (after re-scraping) |
GET /api/status |
title count + last-changed time |
GET /api/resolve?path=/redirect/{id}&source=... |
resolves a hoster link |
GET /api/extract?path=/redirect/{id}&source=... |
gets the real stream URL (m3u8/mp4) |
GET /media/... |
serves the locally stored mp4 files |
Scraper:
| Variable | Meaning | Default |
|---|---|---|
SOURCE |
only this source (aniworld, sto, filmpalast) |
all |
LIMIT |
only the first N titles (test) | all |
CONCURRENCY |
titles processed at once | 3 |
DELAY_MS |
pause between requests | 300 |
--force |
scrape everything fresh | off |
Downloader:
| Variable | Meaning | Default |
|---|---|---|
SOURCE |
only this source | all |
SLUG |
only this title | all |
LIMIT |
only the first N | all |
CONCURRENCY |
parallel downloads | 5 |
DELAY_MS |
pause between languages | 0 |
RETRIES |
attempts per language | 2 |
FFMPEG_TIMEOUT_MS |
max time per file | 300000 |
TASK_TIMEOUT_MS |
watchdog per episode | 720000 |
- Filemoon often won't work in the headless browser because of anti-bot – the language is then pulled from Vidmoly / VOE / Dood instead.
- Not every episode exists in every language; only the ones that actually exist get downloaded.
- The download spins up a Chromium (Playwright) per attempt – that's resource
hungry. On a weak box, lower
CONCURRENCY. - S.to and filmpalast.to are unofficial sources; layout and availability can change whenever. So some stuff might just not be downloadable.
- Again: only run it on your own network. Don't make it publicly reachable.
do what you want with it, just don't pretend I paid you for it.
See LICENSE.