Free anime streaming site — FastAPI (Python) backend + React (Vite) frontend with a custom hls.js player. Scrapes anikoto.cz, proxies its HLS streams and poster images, and serves a modern mobile-friendly UI.
Brand: kenpyTV · "The K-way to watch anime" · by kenpydev
- 🎬 Custom hls.js player with quality selector (all master-playlist variants), subtitle (CC) menu, and skip intro/outro buttons
- 🗣️ Sub / Dub tabs — servers grouped by language, with per-language server bars
- 🔀 Random anime — "Random" button in the nav picks a random title
- 📺 Episode picker — search + pagination (60/page) on the detail page; a modal on the Watch page that opens on the page of the episode you're watching and scrolls it into view
- 🛟 Original-embed fallback — when HLS fails, per-server backup: iframe the original player or open it in a new tab
- 🔍 Search, paginated browse tabs (New Release, Top Airing, Most Popular, Recently Added, Completed), home hero slider with left/right arrows
- ⚡ Fast backend — parallel server resolution, threaded search, aggressive caching (streams 30 min, meta 24h), fast-fail timeouts
- 📱 Fully responsive with a hamburger menu on mobile
backend/ FastAPI — scrapes anikoto.cz, exposes JSON APIs + HLS/image proxies
frontend/ React (Vite) — Home, Browse/Search, Anime detail, Watch page with player
Browser ──▶ React (Vite) ──▶ FastAPI /api ──▶ anikoto.cz (scrape)
│ │
│ /api/hls-proxy (rewrites m3u8, chains segments)
└──▶ hls.js ───▶ CDN segments (via proxy)
GET /api/streamresolves the episode's servers (server-list + mapper endpoints, in parallel).- Each server's HLS URL is wrapped as
/api/redirect?id=...→/api/hls-proxy?url=.... /api/hls-proxyfetches the master playlist, rewrites every variant/segment URL back through itself (adding the correct Referer/Origin headers to bypass 403s), and serves it to hls.js.- Subtitles (VTT) and download links get the same proxying treatment.
| Route | Description |
|---|---|
GET /api |
Home page sections (featured, latest, new release, upcoming, top anime…) |
GET /api/info?id=<slug> |
Anime details + episode list + recommendations |
GET /api/listing?type=<slug>&page=<n> |
Paginated listing. Types: new-release, status/currently-airing, most-viewed, latest-updated, status/finished-airing |
GET /api/search?q=<query> |
Search (3 parallel candidate URLs) |
GET /api/random |
Random anime (follows anikoto.cz /random redirect) |
GET /api/episodes?id=<slug> |
Raw episode list |
GET /api/stream?id=<slug>&ep=<n> |
Stream servers (HLS + subtitles + skip data + downloads) |
GET /api/schedule?date= |
Airing schedule |
GET /api/hls-proxy?url=&referer= |
Proxies + rewrites HLS playlists (master & segments) |
GET /api/redirect?id= |
Temporary 302 redirect for stream/subtitle URLs |
GET /api/image-proxy?url= |
Proxies poster images (per-domain Referer handling) |
GET /api/health |
Health check (service: kenpyTV) |
Backend — http://localhost:8000
cd backend
pip install -r requirements.txt
python app.py # or: uvicorn app:app --reloadFrontend — http://localhost:5173
cd frontend
npm install
npm run devOpen http://localhost:5173. The Vite dev server proxies /api to http://localhost:8000 (see frontend/vite.config.js).
- Frontend:
npm run build(must succeed before shipping) - Backend:
python -m py_compile app.py scraper.py - Smoke test the API while the server runs:
curl "http://localhost:8000/api/random" curl "http://localhost:8000/api/listing?type=status/currently-airing&page=1"
.
├── .gitignore
├── README.md
├── backend/
│ ├── app.py # FastAPI routes, HLS/image/redirect proxies, caching
│ ├── scraper.py # anikoto.cz scraping + stream resolution
│ ├── requirements.txt
│ ├── Procfile # Railway / Heroku start command
│ ├── Dockerfile # Fly.io / any Docker host
│ └── render.yaml # Render blueprint (one-click backend)
└── frontend/
├── index.html # brand, fonts, meta
├── vite.config.js # dev proxy
├── vercel.json # SPA rewrites for Vercel
├── .env.example # VITE_API_URL sample
└── src/
├── App.jsx # nav, mobile menu, random, routes
├── index.css # full theme (black + orange "juice" palette)
├── api.js # API client + imgProxy helper
├── pages/ # Home, Browse, Anime, Watch
└── components/ # Card, VideoPlayer, EpisodePicker
kenpyTV is two deployables: the FastAPI backend (persistent Python process) and the React frontend (static files). They connect through VITE_API_URL. The repo ships ready-to-use configs for the most common hosts.
The backend must be a persistent process (not serverless) because it streams large HLS responses and keeps a hot cache. Container/VM hosts work best.
- Render — use
backend/render.yaml(Blueprints): Import repo → it builds & runsuvicorn app:appautomatically. - Railway — add a service from the
backend/directory. Railway readsbackend/Procfile; set the start command if needed:uvicorn app:app --host 0.0.0.0 --port $PORT. - Fly.io / any Docker host —
backend/Dockerfileis provided:fly launchfrombackend/, ordocker build -t kenpytv-api backend && docker run -p 8000:8000 kenpytv-api. - Any VPS —
uvicorn app:app --host 0.0.0.0 --port 8000behind nginx/Caddy.
CORS is already wide open (allow_origins=["*"]), and all proxy URLs are built from the incoming request host, so the same backend works on any deployed domain.
Vercel:
- Import the repo, set Root Directory to
frontend. - Vite is auto-detected;
frontend/vercel.jsonadds an SPA rewrite so deep links like/anime/<id>work. - Add an environment variable:
VITE_API_URL= your backend URL, e.g.https://your-api.onrender.com
- Deploy. Each deploy runs
npm run buildwith that env baked in.
Netlify / Cloudflare Pages / GitHub Pages: same idea — build frontend with npm run build, set VITE_API_URL at build time, and add an SPA rewrite to index.html (Netlify: _redirects with /* /index.html 200).
The Vite
/apiproxy only exists for local dev (frontend/vite.config.js). In production the app calls the backend viaVITE_API_URL(seefrontend/.env.example).
If you leave VITE_API_URL empty in production, the frontend will call /api/* on its own domain and get 404s. Always set VITE_API_URL to the backend URL.
- Some embed hosts send
X-Frame-Options/frame-ancestors headers — the "Original player" iframe fallback may be blocked for them, but "Open in New Tab" always works. - HLS raw CDN URLs return 403 without a proper Referer — always go through
/api/hls-proxy. - The orange-on-black theme is pure CSS (
index.css:roottokens); tweak the palette there.