Skip to content

Releases: sundeepyt2/SNI

v2.1.1 — 7 bug fixes (config roundtrip, dub episodes, TUI crashes)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 10:52

7 bugs found and fixed through systematic code audit

Bug #1: Dead code (minor)

_select_episode_range() function was defined but never called. Removed.

Bug #2: Wrong behavior for invalid episode number (minor)

When user requests episode 9999 but anime only has 12 episodes, SNI would silently play from episode 1. Now reports: Episode 9999 not found. This anime has episodes 1-12.

Bug #3: TUI crash — play_episode doesn't exist (major)

EpisodeScreen.on_list_view_selected called self.play_episode(idx) but only play_range is defined. Clicking an episode in the TUI list would crash. Fixed.

Bug #4: TUI freeze during playback (major)

TUI play_range called player.wait() synchronously, which blocks the Textual event loop during playback. The entire UI froze until mpv exited. Fixed by running player.wait() in a thread executor: await loop.run_in_executor(None, player.wait)

Bug #5: TUI auto-next logic broken (minor)

play_range had broken auto-next logic with continue that skipped episodes. Replaced with clean if ep.number >= end_ep: break.

Bug #6: Dub episodes always showed sub count (major)

get_episodes() always used detail.get('sub') even when dub=True. Now accepts dub parameter and uses detail.get('dub') with fallback to sub. Verified: One Piece has 1168 sub episodes but 1155 dub episodes — dub count is now correct.

Bug #7: CRITICAL — Config roundtrip lost CF Worker URL (critical)

Config.save() wrote the CF Worker URL with key cf_worker_url but the model field is allanime_cf_worker_url. Config.load() flattens all sections, so the key mismatch meant the CF Worker URL was silently lost every time config was saved and reloaded. This means sni config --update allanime_cf_worker_url=... would work for the current session but be gone next time. Fixed by using the full model field name as the TOML key. Verified with roundtrip test.

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 6 passed (1.01s)
  • Live E2E: AniList → AllAnime → 1168 sub + 1155 dub episodes → stream URL resolved
  • Config roundtrip: CF Worker URL preserved ✅
  • Dub episodes: correct count returned ✅

Install

pip install --upgrade sni-cli

Full changelog: v2.1.0...v2.1.1

v2.1.0 — Rich TUI + watch history + episode ranges + player controls

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 10:27

What's new

Brings back the best features from v1.x, merged with v2.0's clean backend (AniList search + minimal mpv + proxy fallback).

1. Watch history

  • Tracks what you've watched (~/.config/sni/history.json)
  • Resume prompt: "Resume from episode X?" when re-watching
  • sni history command shows continue-watching list

2. Episode ranges

sni play "one piece" -e 1-12  # play episodes 1 through 12
sni play "one piece" -e 5     # start from episode 5

3. MPV IPC controls (restored)

  • Player overlay with n=next, p=prev, r=replay, q=quit
  • Uses --input-ipc-server for mpv communication

4. Rich Textual TUI (full rewrite)

  • SearchScreen: search bar + results list with scores/episode counts
  • EpisodeScreen: episode list + input for ranges
  • PlayerOverlay: modal with playback controls
  • Header + Footer with keybindings
  • CSS styling (borders, colors, padding)
  • Falls back to interactive CLI if Textual not installed

5. Config wizard

sni config --interactive  # interactive setup

6. Better CLI display

  • Rich tables for search results and history
  • Episode count + score in search results
  • fzf integration with fallback to numbered selection

Backend (unchanged from v2.0 — all the fixes that work)

  • AniList for search (no captcha, 100% reliable)
  • AllAnime for streams (with proxy.cors.sh + CF Worker fallback)
  • Minimal mpv (no demuxer flags that caused fatal errors)
  • Clean error messages (no raw tracebacks)

Install

pip install --upgrade sni-cli
sni "one piece"          # search and play
sni tui                  # rich terminal UI
sni play "X" -e 1-12    # play episode range
sni history              # show watch history

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 6 passed (0.82s)
  • python -m build → sdist + wheel both pass twine check

Full changelog: v2.0.0...v2.1.0

v2.0.0 — Complete rewrite (AniList search + minimal mpv)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 09:52

SNI v2.0 — Ground-up rewrite

SNI v2.0 eliminates ALL the bugs from v1.x by redesigning the architecture from scratch.

Architecture changes

  • Search via AniList API (graphql.anilist.co) — public, no auth, no captcha, 100% reliable. Gives proper English titles, episode counts, scores, cover images.
  • AllAnime used ONLY for stream extraction (not search). Simplified to a single allanime.py file with clean fallback chain: direct → proxy.cors.sh → CF Worker (optional).
  • Minimal mpv invocation: just URL + headers. NO demuxer flags (the source of multiple fatal errors in v1.x). mpv auto-detects format.

Bugs eliminated

  1. NEED_CAPTCHA — AniList search has no captcha. AllAnime has automatic proxy.cors.sh fallback.
  2. mpv exit code 2 (octet-stream) — removed --demuxer-lavf-format flag. mpv auto-detects.
  3. mpv fatal error (analyzeduration out of range) — removed the flag entirely.
  4. mp4upload 403 (wrong Referer) — _scrape_embed uses embed URL as Referer.
  5. Dead provider errors — only AllAnime, no dead hianime/animepahe.
  6. Raw tracebacks — all errors wrapped in clean messages.

CLI commands

sni "one piece"          # Search and play (sub)
sni play "one piece"     # Same as above
sni-d "one piece"        # Search and play (dub)
sni search "one piece"   # Search only
sni tui                  # Terminal UI mode
sni config               # Show/set config
sni --version            # Show version
sni --debug play "X"     # Debug mode (verbose mpv output)

Install

pip install --upgrade sni-cli
sni "one piece"

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 6 passed (0.90s)
  • Live end-to-end: AniList search → AllAnime search → 1168 episodes → stream URL extracted → mpv command verified (no breaking flags)
  • python -m build → sdist + wheel both pass twine check

Full changelog: v1.3.1...v2.0.0

v1.3.1 — Fix mpv fatal error (demuxer-lavf-analyzeduration out of range)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 08:45

Root cause

From the user's mpv logs:

[cplayer] Setting option 'demuxer-lavf-analyzeduration' = '2000000' (flags = 8)
The demuxer-lavf-analyzeduration option is out of range: 2000000
Error parsing option demuxer-lavf-analyzeduration (parameter is outside values allowed for option)
Setting commandline option --demuxer-lavf-analyzeduration=2000000 failed.
Exiting... (Fatal error)
Playback error: mpv exited with code 1.

The v1.2.8 fix added --demuxer-lavf-analyzeduration=2000000 thinking the unit was microseconds (2 seconds). But mpv's option range for this flag caps at 100000 (100ms). A value of 2000000 is out of range and causes mpv to exit with a FATAL error before it even tries to play.

The fix

Changed --demuxer-lavf-analyzeduration=2000000100000 (the max allowed value). Added a comment explaining the unit + range constraint so this doesn't break again.

Good news from the logs

The user IS now getting the Yt-mp4 stream (tools.fast4speed.rsvp) which is the highest-priority source — so the API/captcha/network side is working correctly. The stream URL resolved successfully. The only issue was this one mpv flag value being out of range.

Install

pip install --upgrade sni-cli
sni play "Tsue to Tsurugi no Wistoria"

Playback should now work — mpv will no longer exit with the fatal analyzeduration error.

Full changelog: v1.3.0...v1.3.1

v1.3.0 — Route stream URLs through CF Worker (fixes 403 from all video CDNs)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 08:21

Root cause

Even with the correct Referer (v1.2.9 fix), mp4upload returned 403 because the user's IP is blocked by the CDN — the same IP blocking that affects api.allanime.day. The Referer fix was necessary but not sufficient.

The user falls through to mp4upload because ALL higher-priority sources also fail:

  1. Yt-mp4 (tools.fast4speed.rsvp) → 403 (IP blocked by Cloudflare)
  2. S-mp4 (allanime.day clock.json) → blocked (redirect loop / IP)
  3. Mp4 (a4.mp4upload.com:183) → 403 (IP blocked by mp4upload CDN)

ALL direct stream URLs fail for the user's IP.

The fix

When a CF Worker is configured (allanime_cf_worker_url), SNI now wraps the stream URL through the worker too — not just the API calls. The worker has a clean IP that video CDNs don't block.

Applied to all three extraction paths:

  1. _extract_source (Yt-mp4): wraps the fast4speed URL through the worker
  2. _fetch_clock_json (S-mp4): wraps the clock.json stream URL
  3. _scrape_embed (mp4upload/filemoon/etc): wraps the scraped video URL

The worker URL format:

https://your-worker.deno.dev/?url=<encoded_stream_url>&h_Referer=...&h_Origin=...

mpv plays the worker URL → worker proxies the request to the real CDN with correct headers + its own clean IP → streams the video back to mpv.

What you need to do

1. Deploy a CF Worker (if you haven't already)

Follow the instructions at https://github.com/sundeepyt2/SNI/blob/main/worker/README.md — use Deno Deploy Playground (easiest, ~2 min).

2. Save the worker URL

sni config --update allanime_cf_worker_url='https://your-project.deno.dev'

3. Upgrade + play

pip install --upgrade sni-cli
sni play "Tsue to Tsurugi no Wistoria"

SNI will now route BOTH the API calls AND the stream URLs through your worker, bypassing all IP blocking.

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 44 passed (3.75s)
  • python -m build → sdist + wheel both pass twine check
  • Stream URL wrapping logic verified for Yt-mp4, S-mp4, and mp4upload sources

Full changelog: v1.2.9...v1.3.0

v1.2.9 — Fix mp4upload 403 Forbidden (wrong Referer)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 08:03

Root cause (from user's actual mpv logs)

[ffmpeg] https: HTTP error 403 Forbidden
Failed to open https://a4.mp4upload.com:183/d/.../video.mp4

SNI was scraping the mp4upload embed page successfully, but when setting up the playback stream it was sending:

Referer: https://youtu-chan.com   <- WRONG

mp4upload's CDN checks the Referer header and only allows requests from its own embed page. With the wrong Referer, it returns 403 Forbidden.

The fix

_scrape_embed() now uses the effective embed URL (after redirects) as the Referer when building the playback Stream. So for an mp4upload stream, mpv now sends:

Referer: https://www.mp4upload.com/embed-bxsct3uxo8tg.html   <- CORRECT
Origin:  https://www.mp4upload.com

Added _origin_for() helper to derive the Origin header from the embed URL (scheme + host), since that also needs to match the embed site.

This same fix applies to filemoon, vidnest, vizcloud, mycloud — all the sources that go through _scrape_embed. They all check the Referer header and would have had the same 403 issue.

Verified

Mocked test with realistic mp4upload embed response:

Stream URL: https://a4.mp4upload.com:183/d/.../video.mp4
Referer: https://www.mp4upload.com/embed-bxsct3uxo8tg.html  ✅
Origin:  https://www.mp4upload.com  ✅

mpv will now successfully play mp4upload streams (no more 403 Forbidden).

  • ruff check sni/ → All checks passed
  • pytest -q → 44 passed (3.29s)
  • python -m build → sdist + wheel both pass twine check

Install

pip install --upgrade sni-cli
sni play "Tsue to Tsurugi no Wistoria"

Full changelog: v1.2.8...v1.2.9

v1.2.8 — Actual fix for 'mpv exited with code 2'

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 07:52

Root cause (finally found)

AllAnime stream URLs (tools.fast4speed.rsvp) return:

content-type: application/octet-stream

and the URL has no .mp4 extension either (ends with /sub/1?Authorization=...).

mpv uses content-type + file extension to auto-detect the demuxer. With application/octet-stream and no extension, mpv can't figure out the format and exits with code 2 (failed to recognize file format).

The stream URL itself is perfectly fine — curl returns HTTP 200 with content-length: 309MB. mpv just needed to be told it's an MP4.

The fix

When the stream URL matches AllAnime patterns (tools.fast4speed.rsvp, /media, fast4speed, allanime, allmanga), SNI now adds:

--demuxer-lavf-format=mp4              (force MP4 demuxer)
--demuxer-lavf-probesize=32000         (probe 32KB to detect streams)
--demuxer-lavf-analyzeduration=2000000 (2s analysis timeout)

The mpv command now built for a real AllAnime stream

mpv https://tools.fast4speed.rsvp/.../sub/1?Authorization=...
  --ytdl=no
  --force-seekable=yes
  --demuxer-lavf-format=mp4        <- THE FIX
  --demuxer-lavf-probesize=32000
  --demuxer-lavf-analyzeduration=2000000
  --http-header-fields=Authorization: ...
  --http-header-fields=Referer: https://youtu-chan.com
  --http-header-fields=Origin: https://youtu-chan.com

Install

pip install --upgrade sni-cli
sni play "one piece"

Playback should now work — mpv will know the stream is MP4 and play it correctly.

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 44 passed (3.23s)
  • Stream URL confirmed reachable: curl -I returns HTTP 200, content-length 309MB
  • Command-building verified: --demuxer-lavf-format=mp4 IS present for AllAnime URLs
  • python -m build → sdist + wheel both pass twine check

Full changelog: v1.2.7...v1.2.8

v1.2.7 — Capture mpv stderr + stream compatibility flags

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 03 Jul 07:10

What was broken

Users got mpv exited with code 2 with no explanation. SNI was throwing away mpv's stderr output (stderr=subprocess.DEVNULL), so when mpv failed the user couldn't see the actual error reason.

What's fixed

1. Capture mpv stderr

Player now pipes mpv's stdout+stderr instead of DEVNULL. When mpv fails, the error message includes the last 20 lines of mpv output so you can see WHY it failed (expired URL, format error, network issue, etc.).

2. Stream compatibility flags

Added common AllAnime playback fixes:

  • --ytdl=no — don't invoke yt-dlp for direct stream URLs
  • --force-seekable=yes — allow seeking on non-seekable streams
  • --demuxer-lavf-o=fflags=+seekable — lavf seekable flag
  • --stream-buffer-size=4096 — larger read buffer

3. Debug mode

sni --debug play "X" now shows mpv's full verbose output live in the terminal (--msg-level=all=v + --terminal=yes). The --debug flag is wired from CLI → Player constructor.

4. Better error message

When mpv fails, the StreamError now includes:

  • mpv's actual output (last 20 lines)
  • Common fixes list (expired URL, try --debug, different episode, etc.)
  • Instead of just mpv exited with code 2

Install

pip install --upgrade sni-cli
sni play "one piece"

If playback still fails, run with debug to see full mpv output:

sni --debug play "one piece"

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 44 passed (3.32s)
  • Live end-to-end: search → episodes → stream URL resolved
  • python -m build → sdist + wheel both pass twine check

Full changelog: v1.2.6...v1.2.7

v1.2.6 — Auto-disable dead CF Worker (5xx)

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 02 Jul 21:05

What was broken

If your CF Worker (Cloudflare/Deno/Vercel) account gets suspended or the worker goes down, SNI kept trying the dead worker URL on every request, getting 503 errors in a loop — even though direct + proxy.cors.sh might have worked.

What's fixed

AllAnimeProvider now tracks a _worker_disabled flag. When the CF Worker returns any 5xx response (502/503/504 — indicates the worker is suspended/deleted/down), the flag is set to True and the worker is skipped for the rest of the session. Subsequent requests go directly to direct → proxy.cors.sh without wasting time on the dead worker URL.

The error message also tells you how to clear the dead URL:

Worker returned HTTP 503 (worker may be suspended or down). Worker disabled
for this session. Clear it with: sni config --update allanime_cf_worker_url=''

What you should do right now

Immediate fix (stop the 503 errors)

# Clear the dead worker URL from your config
sni config --update allanime_cf_worker_url=''

# Upgrade to v1.2.6 (so this auto-disables in the future)
pip install --upgrade sni-cli

# Try playing — should use direct + proxy.cors.sh fallback
sni play "one piece"

If SNI still can't reach AllAnime

Your remaining options (since Deno Deploy suspended your account):

  1. Try Vercel Edge Functionshttps://vercel.com (free, no card). Deploy worker/api/proxy.js from the repo. Instructions in worker/README.md.

  2. Try a VPN — your IP is captcha-walled by AllAnime's Cloudflare. A VPN gives you a different IP that might not be flagged. Free VPNs like ProtonVPN work.

  3. Try a mobile hotspot — switch from WiFi to mobile data. Mobile IPs are rarely captcha-walled.

  4. Browser cookies from allmanga.to — open https://allmanga.to in your browser, solve any captcha, copy cookies from DevTools → Application → Cookies, then:

    sni config --update allanime_cookies='cf_clearance=...;'

Verified

  • ruff check sni/ → All checks passed
  • pytest -q → 44 passed (3.18s)
  • Mock test: worker returns 503 → _worker_disabled set to True ✅
  • Mock test: subsequent call skips worker entirely ✅
  • Live end-to-end: search → episodes → streams all work ✅

Full changelog: v1.2.5...v1.2.6

v1.2.5 — Fix Deno Deploy 'entrypoint not found' error

Choose a tag to compare

@sundeepyt2 sundeepyt2 released this 02 Jul 20:50

What was broken

Users deploying the worker on Deno Deploy got:

Entrypoint at '/tmp/build/src/main.ts' not found

Deno Deploy project mode looks for main.ts (or src/main.ts) as the entrypoint by default. The previous proxy.deno.js was a JavaScript file with the wrong name — Deno Deploy couldn't find the expected entrypoint.

What's fixed

  • Created worker/main.ts — TypeScript with full type annotations (Deno's type checker requires explicit types; .js files with implicit any fail deno check)
  • Also created worker/src/main.ts as a fallback (in case Deno Deploy looks there instead)
  • Added worker/deno.json with entrypoint: "main.ts" to make the entrypoint explicit
  • Updated worker/README.md with clearer instructions emphasizing the Playground method (which avoids the entrypoint issue entirely) + troubleshooting for the error

Verified: deno lint and deno check both pass with 0 errors. Runtime test confirms health check + POST search + GET persisted-query all work.

What you should do

If you're deploying on Deno Deploy, use the Playground method (not "New Project"):

  1. Go to https://dash.deno.com → "New Project" → "Playground" (top-right)
  2. Delete the default code
  3. Paste the contents of worker/main.ts
  4. Click "Save & Deploy"

The Playground doesn't have a build step, so the entrypoint issue doesn't apply.

If you prefer the GitHub repo method, fork SNI and Deno Deploy will auto-detect main.ts at the repo root.

Install

pip install --upgrade sni-cli

Full changelog: v1.2.4...v1.2.5