ani-py is a standalone Python CLI for searching, selecting, streaming, and downloading anime from the terminal. The Python runtime code uses only the standard library; external tools handle networking, menus, playback, and downloads.
It is an independent, AI-assisted implementation inspired by terminal anime launchers and is not affiliated with upstream ani-cli.
- Search and select anime from the terminal
fzf,rofi,dmenu, or numbered fallback menus- Single episodes or ranges such as
-e 3,-e 1-12,-e 0, and-e -1 - Sub and dub modes
- Quality selection from available HLS variants
- Watch history and
--continue - mpv, VLC, IINA, and custom desktop players
- VLC for Android and mpv-android from Termux
- Android HLS relay for Referer-protected streams and subtitles
- Interactive next / previous / replay / episode / quality / subtitle controls
- Provider-independent
--auto-nextfor desktop mpv, advancing only after natural EOF - Private mpv IPC by default, with persistent desktop-mpv detach/reattach controls
- Downloads through
yt-dlpwithffmpegfallback - Optional
ani-skipintegration for desktop mpv - Provider-aware history and opt-in experimental provider adapters
Install ani-py:
curl -fsSL https://github.com/Onehand-Coding/ani-py/releases/latest/download/install.sh | shInstall recommended dependencies too when your package manager is supported:
curl -fsSL https://github.com/Onehand-Coding/ani-py/releases/latest/download/install.sh | sh -s -- --depsThe default Linux/Unix target is ~/.local/bin/ani-py. Use --prefix DIR to choose another prefix.
Core requirements are Python 3.10+ and curl. fzf and mpv are recommended; VLC, rofi/dmenu, yt-dlp, ffmpeg, and ani-skip are optional.
The same installer detects Termux and installs to $PREFIX/bin/ani-py:
curl -fsSL https://github.com/Onehand-Coding/ani-py/releases/latest/download/install.sh | sh -s -- --depsInstall VLC for Android or mpv-android separately from your preferred Android app source. Shizuku/rish is optional and is not required.
git clone https://github.com/Onehand-Coding/ani-py.git
cd ani-py
chmod +x ani-py
./ani-py "frieren"Build the single-file distribution:
make build
./dist/ani-py -Vani-py installs a copy of itself, so an installed copy never changes on its
own. Published release assets include SHA256SUMS; the installer and
--update verify the standalone script before replacing an existing copy.
Update it with:
ani-py --updateThis fetches the latest published release, verifies its SHA-256 digest, and
replaces the installed script in place. To also install external tools, re-run
the installer with --deps:
curl -fsSL https://github.com/Onehand-Coding/ani-py/releases/latest/download/install.sh | sh -s -- --deps--update refuses to run when the latest published release is older than the
installed copy, so a local build ahead of the release is never silently rolled
back. Re-run install.sh if you want to force the latest published release.
The version is a calendar date, so ani-py --version reports something like
ani-py 2026.10.4 rather than a semver string. That date is what --update
compares to decide whether an update would actually be a downgrade.
curl -fsSL https://github.com/Onehand-Coding/ani-py/releases/latest/download/uninstall.sh | shThe uninstaller removes ani-py only; it does not remove system packages or Android players.
ani-py "frieren"
ani-py -q 1080 "dandadan"
ani-py -e 3 "one piece"
ani-py --dub -e 1-4 "bleach"
ani-py -c
ani-py -d -e 1-12 "pluto"
ani-py --skip "summer time rendering"
ani-py --auto-next -e 3 "frieren"
ani-py --sub-lang de -d -e 1 "one piece"
ani-py --attach
ani-py --update
ani-py --list-providersUseful player/menu examples:
ani-py -p vlc "frieren" # VLC
ani-py --menu rofi "frieren"
ani-py --menu dmenu "frieren"
ani-py -p mpv "frieren" # mpvRun ani-py --help for the complete current option list.
Cancelling a results or episode list steps back one level (Esc at the Anime list returns to the search prompt); only cancelling the search prompt itself quits.
--auto-next is controlled by ani-py rather than by a streaming provider.
ani-py keeps the episode list, waits for desktop mpv to report natural EOF over
its private IPC socket, then lazily resolves and starts the next episode. This
preserves quality/subtitle preferences, per-episode ani-skip behavior,
history updates, and normal provider failover.
Completion comes from mpv's end-file event, whose reason tells a finished
episode apart from a stream that ended in an error, a closed player, or a source
that stopped delivering bytes. Those cases are reported as such and never
advance the queue.
With one selected episode, auto-next continues from that episode through the currently loaded episode list. With an explicit multi-episode selection or range, only that queue is played. Closing/stopping mpv or losing IPC stops the queue; ani-py never treats a player exit as completed playback.
The queue is capped so one selection cannot silently become a hundred
episodes. --auto-next-limit N sets the ceiling (default 12); 0 disables it.
Trimming always reports how many later episodes were dropped. The cap applies
only when a single selection expands into the remaining episodes — an explicit
multi-episode selection or range is always played exactly as you chose it,
however long.
Set ANI_PY_AUTO_NEXT=1 to make the flag the environment default, and
ANI_PY_AUTO_NEXT_LIMIT to set the ceiling.
While an episode plays, ani-py keeps a now-playing line redrawn in place and mirrors it into the terminal tab title, so the current episode, its position, and what plays next stay visible even when the terminal is in the background:
▶ Frieren Ep 3 (3/12) 14:32/24:10 (59%) → Ep 4
Read it like this:
| Part | Meaning |
|---|---|
Ep 3 |
which episode of the season is playing |
(3/12) |
the 3rd of the 12 episodes in this queue — not 3 of 12 in the season |
14:32/24:10 (59%) |
playback position, total runtime, percentage |
→ Ep 4 |
what plays next; absent on the last one |
The two numbers are separate on purpose. Starting mid-season, -e 24 --auto-next-limit 2 shows Ep 24 (1/2) then Ep 25 (2/2): episode 24 of 25,
with the two episodes it queued. Rendering the episode number as a ratio with
the queue length (Ep 24/2) would read as "24 out of 2", which is not a
number.
This is --auto-next only; ordinary playback still opens its interactive
menu. Both displays are suppressed when output is not a terminal, so piping to
a file or a log stays clean.
If mpv is configured with save-position-on-quit, relaunching a file already
watched to the end would resume at the end and report immediate EOF. ani-py
detects that and rewinds to the start, so an episode is never skipped just
because mpv remembered where it stopped.
The feature currently requires desktop mpv on POSIX systems. VLC, IINA, custom
players, Windows named-pipe mpv control, and Android intent players do not
expose the same reliable completion signal through ani-py, so --auto-next
refuses those combinations instead of guessing. The check runs before the
search, so an unusable player fails immediately rather than after a lookup.
ani-py -c picks up where you left off. If you stopped partway through an
episode, it reopens that same episode and mpv restores your saved position. If
you watched it to the end, it moves on to the next episode.
History therefore records whether each episode was finished, not merely that it
was opened, and the Continue list labels each one as watched or unfinished.
Rows written by older versions carry no such information and are treated as
finished, so they keep advancing exactly as they did before.
Completion is noticed in two ways, because closing the player is something you can do at any moment. ani-py records it when mpv reports the episode has ended, and it also checks once a second while a detached player is running. That second check is what makes closing the mpv window count: an X click is not something ani-py can hook, and once the player is gone it can no longer be asked what state it was in. The cost is one small query per second on a connection that is already open; the trade-off is that closing the window in the second immediately after an episode ends may still be recorded as unfinished.
If you quit mid-episode, on purpose or by closing the window, the episode stays
marked unfinished and --continue resumes it with mpv's saved position.
To browse history, ani-py --history shows the same list and asks what to do with the entry you pick: Play resumes it like -c, Forget removes it after a confirmation. Cancelling backs out to the list.
To prune history, ani-py --forget shows the same list as --continue, but in
multi-select: tab the entries you want gone, press Enter, and confirm. It uses
whatever menu frontend you already have, so with fzf you tab to toggle and with
the plain-terminal fallback you type 2,5-7. ani-py -D still wipes everything
at once when that is what you want.
ani-py "frieren" # Android resolver/default player
ani-py -p vlc "frieren" # VLC for Android
ani-py -p mpv "frieren" # mpv-androidFor streams that require provider headers, ani-py uses a loopback-only relay on 127.0.0.1. It keeps Referer/User-Agent handling inside Termux and rewrites HLS requests through the relay.
WebVTT subtitles are attached to the relayed HLS stream as a native subtitle rendition. This has been live-tested on the project owner's device with both VLC for Android and mpv-android.
On the tested VLC 3.7.1 setup, English subtitles only auto-selected after adding this one-time VLC option:
--sub-language=eng
VLC: Settings → Advanced → custom libVLC options, then restart VLC. This is a VLC preference observed on the tested device, not a requirement asserted for every VLC version.
For Android architecture, diagnostics, relay behavior, and limitations, see docs/android.md.
Recorded device flows are available for VLC for Android and mpv-android. Additional screenshots and diagnostics are in docs/screenshots/android.
The default automatic provider chain is deliberately HiAnime only.
| Provider | Status | Default auto chain |
|---|---|---|
| HiAnime | primary | yes |
| AniLight | experimental / opt-in | no |
Examples:
ani-py --provider hianime "frieren"
ani-py --provider anilight "frieren"
ani-py --provider-order hianime,anilight "frieren"AniLight is registered as an experimental direct provider. The initial adapter
uses AniLight's current JSON API and its portable ryu/AnimeGG source path,
which returns quality-labelled progressive streams through AniLight's own proxy.
Sub and dub are supported where that source has coverage; the sub route is
hard-subbed. Broader soft-sub/MegaPlay backends are intentionally deferred until
their changing CDN/proxy behavior is proven against ani-py's desktop and Android
playback paths. AniLight is not part of the default automatic chain yet.
Kuhi and AnimeKai were removed in 0.5.2-rc11: their upstreams went away and neither could return a stream, so keeping them only cost failover time.
Provider availability changes independently of ani-py's mocked tests. See docs/providers.md for current policy and live-check guidance.
After a normal single-episode launch, ani-py offers:
- Next episode
- Previous episode
- Replay
- Choose episode
- Search another anime
- Change quality
- Change subtitle
- Detach & exit
- Stop & quit
Desktop mpv uses a private JSON IPC socket and replaces the active item in place when possible. Multi-episode ranges run sequentially. Search another anime keeps the current player running while you search and replaces playback only after you choose a new title and episode.
When a provider exposes multiple soft-subtitle tracks, Change subtitle can switch them from the controller. Desktop mpv switches live over IPC; other supported players may be relaunched with the selected subtitle. --sub-lang de (or another language/label) selects a track for playback and downloads, while --sub-lang off disables external subtitles.
For desktop mpv, Detach & exit stores enough session metadata to reconnect later. Run ani-py --attach to restore the same controller without restarting the video. Launching ani-py with no query also offers reattachment when a saved detached mpv session is still alive.
Download:
ani-py -d -e 1 "frieren"
ANI_PY_DOWNLOAD_DIR="$HOME/Videos/anime" ani-py -d -e 1-3 "frieren"ani-py prefers yt-dlp and falls back to ffmpeg -c copy. Provider Referer/User-Agent headers are passed to the download backend. When a selected provider exposes soft subtitles, --sub-lang <language-or-label> downloads that subtitle beside the video (for example Episode 1.de.vtt) without burning it into the media file.
Intro/outro skipping is a desktop-mpv integration:
ani-py --skip "frieren"ani-py calls ani-skip -i <mal-id> -e <episode> when the active provider exposes a MAL id. Android intent players, VLC, IINA, and generic custom players do not receive ani-skip mpv flags.
For desktop mpv, ani-py parses ani-skip's output itself: chapter markers are passed as mpv chapters, and skip intervals are driven by a small embedded mpv script (ani-py-skip.lua in the state directory) reading a per-session <socket>.skip state file. The user's own skip.lua is left in place but receives no script-opts, so its intervals never fire.
Common environment variables:
ANI_PY_PLAYER
ANI_PY_PLAYER_FLAGS
ANI_PY_IPC_SOCKET
ANI_PY_MENU
ANI_PY_MENU_FLAGS
ANI_PY_DOWNLOAD_DIR
ANI_PY_SUB_LANG
ANI_PY_HIST_DIR
ANI_PY_CURL
ANI_PY_PROVIDER
ANI_PY_PROVIDER_ORDER
ANI_PY_ANILIGHT_URL
ANI_PY_ANILIGHT_API_URL
ANI_PY_MODE
ANI_PY_QUALITY
ANI_PY_SKIP_INTRO
ANI_PY_NO_DETACH
ANI_PY_EXIT_AFTER_PLAY
ANI_PY_ANDROID_DEBUG
NO_COLOR
Use --player-flag='--flag' and --menu-flags='--flag' when a passthrough value itself begins with a dash.
make test
make buildCI checks Python 3.10, 3.11, 3.12, and 3.13. Automated tests mock external players/downloads and use local HTTP fixtures; real-device/provider acceptance is tracked separately.
See docs/development.md for the release checklist and test/build notes. If you want to contribute, start with CONTRIBUTING.md.
Check external tools:
./scripts/check-tools.shAndroid diagnostics:
ani-py --android-debug -p vlc -e 1 "frieren"If mpv already uses /tmp/mpvsocket, no change is needed: ani-py uses a separate per-process IPC socket by default.
Provider failures should be checked against the specific provider and network; an offline parser test does not prove a third-party endpoint is live.
Use ani-py in accordance with applicable law, provider terms, and your network environment.
MIT




