move server code into server/, update docs
- app.py + upstream.py -> server/ (relative import, __init__.py) - static/ resolved relative to repo root, works from any CWD - watch history migrated to server/data/history.json - README.md: quick start, features, layout, dev notes - SPEC.md: marked implemented, updated endpoints/run/deviations - ignore *.tar.gz (local curl-impersonate download at repo root)
This commit is contained in:
1 parent
a27ff5c931
commit
2c203990bc
7 files changed
+101
-37
No files matched your search
@@ -3,6 +3,12 @@
|
||||
Browse, search, and stream anime from the browser, using the same
|
||||
anidb.app data source that ani-cli scrapes.
|
||||
|
||||
**Status: implemented and running.** Deviations from the original
|
||||
draft: hls.js is vendored locally (CDN was a silent failure point),
|
||||
history is server-side with watch-position resume (was: localStorage,
|
||||
last 20), and the front-end lives in `static/` with the server code in
|
||||
`server/`. See README.md.
|
||||
|
||||
## Verified data flow (spiked 2026-07, all 200 OK with Chrome TLS impersonation)
|
||||
|
||||
| Step | Request | Yields |
|
||||
@@ -37,47 +43,54 @@ Two moving parts, no database, no framework on the frontend.
|
||||
|
||||
## Backend
|
||||
|
||||
Python 3.12, deps: `fastapi`, `uvicorn`, `curl_cffi`.
|
||||
`server/app.py` + `server/upstream.py`. Python 3.12, deps:
|
||||
`fastapi`, `uvicorn`, `curl_cffi`.
|
||||
|
||||
- `GET /api/search?q=` → `[{id, slug, name, poster}]` (parse step 1)
|
||||
- `GET /api/anime/{slug_id}` → `{name, synopsis, mal_id, seasons:[{id,name}]}`
|
||||
- `GET /api/search?q=` → `[{id, slug_id, name, poster}]` (parse step 1)
|
||||
- `GET /api/anime/{slug_id}` → `{id, name, synopsis, mal_id, seasons:[{slug_id,name,id}]}`
|
||||
- `GET /api/anime/{id}/episodes` → `[{id, number, filler}]` (step 3, passthrough)
|
||||
- `GET /api/episode/{ep_id}/languages` → `[{code, name, embed_url}]` (step 4)
|
||||
- `GET /stream/{token}` → proxied HLS. Token = base64(nats) of the
|
||||
upstream URL. Rewrites `#EXT-X-...` bodies: every relative/absolute
|
||||
URL in the playlist is re-pointed at `/stream/...`. Serves master,
|
||||
variants, and `.ts` segments through the same endpoint (bytes
|
||||
proxied, no transcoding).
|
||||
- Upstream cache: embed tokens expire — cache embed→m3u8 mapping for
|
||||
~10 min; cache nothing else (search is cheap).
|
||||
- `GET /api/episode/{ep_id}/stream?lang=eng|jpn` → `{token, qualities}`
|
||||
(steps 5+6; token is base64url of the master m3u8 URL)
|
||||
- `GET /api/history` / `POST /api/history` → watch history with
|
||||
position, persisted to `server/data/history.json` (50 kept, top 5
|
||||
returned; upsert per anime+episode)
|
||||
- `GET /stream/{token}` → proxied HLS. Token = base64url of the
|
||||
upstream URL. Rewrites every non-comment line of m3u8 bodies to
|
||||
point back at `/stream/`; proxies master, variants, and segments
|
||||
(bytes, no transcoding).
|
||||
|
||||
## Frontend
|
||||
|
||||
Single `index.html` + `app.js` + `style.css`. hls.js from CDN.
|
||||
`static/`: single `index.html` + `app.js` + `style.css`. hls.js is
|
||||
**vendored** (`static/hls.min.js`) — a CDN dependency silently
|
||||
produced black screens.
|
||||
|
||||
Flow: search box → results grid (poster + title, click) → detail view
|
||||
(synopsis, season tabs) → episode list (number + filler badge,
|
||||
sub/dub toggle) → `<video>` with hls.js pointed at
|
||||
`/stream/{token}`; quality follows the master playlist (auto,
|
||||
hls.js handles ABR).
|
||||
(synopsis under the episode grid, season tabs) → episode grid (number
|
||||
+ filler badge, sub/dub toggle) → `<video>` with hls.js pointed at
|
||||
`/stream/{token}`; quality follows the master playlist (auto ABR).
|
||||
|
||||
- History: last 20 `{title, season, episode}` in `localStorage`
|
||||
(mirrors ani-cli's `-c` continue feature).
|
||||
- No login, no accounts, no build step.
|
||||
- Default play: opening an anime resumes the last watched episode of
|
||||
the season (seeking to the saved position unless ≥95% through),
|
||||
else autoplays episode 1. If the browser blocks autoplay, a
|
||||
"click to play" overlay appears.
|
||||
- Watch position saved to `/api/history` every ~10s + on pause/unload;
|
||||
home page lists the 5 most recent entries with timecode.
|
||||
|
||||
## Run
|
||||
|
||||
```
|
||||
uv venv .venv && uv pip install --python .venv/bin/python fastapi uvicorn curl_cffi
|
||||
.venv/bin/python -m uvicorn app:app --host 0.0.0.0 --port 8321
|
||||
```sh
|
||||
uv venv .venv && uv pip install --python .venv/bin/python -r requirements.txt
|
||||
./run.sh # launch/restart, logs to server.log
|
||||
```
|
||||
|
||||
Open `http://localhost:8321`.
|
||||
Open `http://localhost:8321` (bound to 0.0.0.0 for LAN access).
|
||||
|
||||
## Out of scope (v1)
|
||||
|
||||
Downloads (`-d`), next-episode countdown (`-N`), syncplay, intro skip,
|
||||
ani-schedule integration, auth, multi-user. Add when someone asks.
|
||||
ani-schedule integration, auth, multi-user, episode titles (not in
|
||||
anidb's API — needs a MyAnimeList API key). Add when someone asks.
|
||||
|
||||
## Risks
|
||||
|
||||
@@ -89,12 +102,12 @@ ani-schedule integration, auth, multi-user. Add when someone asks.
|
||||
- **ToS** — anidb.app is a third-party scraper site; personal use,
|
||||
no redistribution, bind to LAN only by default.
|
||||
|
||||
## Milestones
|
||||
## Milestones (all done)
|
||||
|
||||
1. **Spike (done)** — curl_cffi passes Cloudflare; all 7 steps verified.
|
||||
2. **upstream.py** — the 6 fetch functions above, one assert-based
|
||||
self-check (`python upstream.py` prints a chainsaw-man episode m3u8).
|
||||
3. **API** — FastAPI routes over upstream.py.
|
||||
4. **HLS proxy** — `/stream/{token}` with playlist rewriting.
|
||||
5. **UI** — search → detail → episodes → player.
|
||||
6. Polish — history, sub/dub toggle, error states.
|
||||
1. Spike — curl_cffi passes Cloudflare; all 7 steps verified.
|
||||
2. `server/upstream.py` — the 6 fetch functions, assert self-check.
|
||||
3. API — FastAPI routes over upstream.py.
|
||||
4. HLS proxy — `/stream/{token}` with playlist rewriting.
|
||||
5. UI — search → detail → episodes → player.
|
||||
6. Polish — history with position + resume, sub/dub toggle, error
|
||||
states, autoplay default, `run.sh`.
|
||||
Reference in new issue
Block a user