A desktop app for browsing and watching anime.
ani-gui is a Rust + SvelteKit desktop application for browsing and watching anime — discovery, search, an embedded player, downloads, persistent watch history, Picture-in-Picture, and OP/ED skip. It began as a graphical front end over pystardust/ani-cli and resolves streams itself now, in Rust.
The repository no longer carries the script; anyone who wants the terminal flow installs upstream's. See docs/architecture.md for the full picture.
| Discovery | Trending, This Season, Top Rated, Recently Released — AniList + Kitsu. |
| Search | Full-text against Kitsu, instant as you type. |
| Detail page | Synopsis, episodes with thumbnails, similar-titles strip. |
| Embedded player | HLS / MP4, quality switch, native or custom controls — no mpv window. |
| Subtitles | Upstream .vtt via <track kind="subtitles">. |
| OP / ED skip | aniskip intervals — one-click or fully automatic. |
| Picture-in-Picture | Persists across navigation. |
| Background prefetch | Adjacent episodes warm in advance. |
| Downloads | Per-episode or ranged, progress dock. yt-dlp bundled; ffmpeg sourced per platform (apt Recommends: on .deb, installer-time fetch on Windows, system PATH on AppImage). |
| Watch history | Continue Watching picks up where you left off. Remove a single card or clear the lot from the rail. |
| External player | One click to mpv / VLC / IINA / custom. |
| Watch together | Hand the current stream to Syncplay for a watch party. |
| Trackers | Connect AniList or MyAnimeList — a Watch Later rail on the home page, and your progress synced back automatically as you watch. |
| Localised | English, Brazilian Portuguese, Latin American Spanish, Russian. |
| No telemetry | No analytics or tracking. Outbound traffic is metadata, the stream you picked, a launch-time check of the app's own GitHub releases, and — with an account connected — your tracker's list + progress sync. Localhost-only listener on a kernel-assigned port. See the privacy policy. |
ani-gui is distributed as a desktop bundle. The transport it uses to reach the provider ships inside it, so browsing and playback need nothing else. Downloads additionally want ffmpeg, which each platform sources differently — see the tier notes below.
Platform support tiers:
| Tier | Platform | Status |
|---|---|---|
| 1 | Linux | Actively tested on Ubuntu. Other distros work via AppImage. |
| 2 | Windows | Most features verified end-to-end. Edge cases may surface. |
| 3 | macOS | Untested. Builds the same way; should work — please file an issue if it doesn't. |
Linux — tier 1 (tested on Ubuntu)
- AppImage — download from the releases page,
chmod +x, double-click. Installffmpegfrom your distro if you want downloads: the AppImage carries yt-dlp but not ffmpeg, and yt-dlp hands off to it whenever a stream arrives as MPEG-TS and has to be repackaged as MP4. Playback itself needs neither. The bundle launches with Chromium's setuid sandbox disabled (AppImage's read-only FUSE mount can't carry the SUID bitchrome-sandboxrequires); the localhost-only architecture means the sandbox isn't load-bearing for the threat model. If you'd rather keep the sandbox, install the.debinstead. - Debian / Ubuntu (
.deb) —sudo apt install ./ani-gui_<version>_amd64.deb. apt pulls in the recommendedffmpegpackage (needed for the download feature) along the way; the post-install script sets thechrome-sandboxSUID bit Electron needs, so the sandbox stays on.sudo dpkg -i …still works but won't auto-install ffmpeg — drop intoapt --fix-broken installor runsudo apt install ffmpegseparately if you used dpkg directly.
Windows — tier 2 (most functions tested)
NSIS installer (.exe). Run it; it installs per-user by default and creates Start menu and desktop shortcuts.
The installer will fetch ffmpeg automatically the first time it runs (~80 MB) so the download feature works out of the box; the impersonating transport and yt-dlp are bundled directly. The ffmpeg fetch runs even when you already have ffmpeg installed via a per-user package manager (scoop, winget user-scope) — the installer's elevated context doesn't see per-user PATH entries, and the bundled copy is what the app uses at runtime in either case.
macOS — untested
A .dmg is produced by the same electron-builder config and should install via the standard drag-into-Applications flow. macOS isn't part of the regular acceptance pass, so if you hit a problem please open an issue — the app is shipped for it but unverified.
Tested on Linux. The dev loop (steps 5–6) works the same on macOS and Windows; the packaging scripts (step 7) build per-host artifacts — run on Linux for .AppImage / .deb, on Windows for the NSIS installer.
- Install Rust (toolchain pinned by
rust-toolchain.toml):curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh . "$HOME/.cargo/env" # (or re-open the shell) so `cargo` is on PATH
- Install Node 20+ and pnpm (via nvm — skip the curl step if you already have nvm or installed Node another way):
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash # re-open the shell (or `source ~/.bashrc`) so nvm is on PATH nvm install 20 && nvm use 20 corepack enable && corepack prepare pnpm@latest --activate
- System build deps (Linux):
sudo apt install -y build-essential libssl-dev pkg-config
- Clone and install JS deps:
git clone https://github.com/JoaoPucci/ani-gui.git && cd ani-gui (cd frontend && pnpm install) (cd electron && pnpm install)
- Build the backend binary (required before the first run, and after every Rust change):
cd backend && cargo build --bin ani-gui-backend
- Run the dev app — two terminals, started in this order:
# Terminal A — Vite dev server, HMR on :5173 cd frontend && pnpm dev # Terminal B — Electron shell, spawns the backend binary from step 5 cd electron && pnpm dev
- Build a distributable bundle:
macOS
cd electron # Linux host pnpm package # .AppImage — fast iteration pnpm package:release # .AppImage + .deb # Windows host (Git Bash / PowerShell, with Rust + Node + pnpm installed natively; # `fetch:win-deps` needs `bsdtar`, which Git for Windows already ships) pnpm package:win # NSIS installer
.dmgis produced by CI on amacos-*runner — for local mac builds seedocs/development.md.
For lints, git hooks, and the bash test toolchain see docs/development.md.
On first launch the app:
- Spawns the Rust sidecar on a kernel-assigned localhost port (no fixed port, no internet-reachable service).
- Loads the discovery surface.
After that, click anything that looks clickable. The app routes the click through Kitsu / AniList for metadata, its own resolver for the stream, and the embedded player for playback.
Upgrading from a version before 0.12 adds one step: those releases kept their own copy of the ani-cli script under your cache directory and refreshed it at every launch. Nothing reads it now, so the first launch after upgrading deletes it and says so on the /diagnostics page.
Connecting a list provider is optional — the app works fully without an account. From the Account page you can connect AniList or MyAnimeList (OAuth in your browser; the token is stored with your OS keychain via Electron's safeStorage, never in plaintext).
Once connected:
- Watch Later rail — your Plan-to-Watch list surfaces as a rail on the home page, bridged to local cards you can play in one click.
- Automatic progress sync — as you watch, the episode is pushed back to the tracker. The sync only ever moves progress forward (replaying or stepping back never lowers your count), promotes a Plan-to-Watch title to Watching on first play, preserves a Rewatching row, and marks a series Completed when you start the last episode of a finished show.
Everything stays on your machine: your OAuth token is encrypted through your OS keychain and written to the app's user-data directory (the Rust backend never persists it — each request carries its own bearer), and your tracker list is cached in a local SQLite database to render the Watch Later rail. ani-gui runs no server of its own. See the privacy policy for exactly what's sent where.
User settings live in $XDG_CONFIG_HOME/ani-gui/config.toml. The Settings page exposes everything you'd normally edit:
- audio mode (
sub/dub) and quality (best,1080,720,480,worst) - UI locale
- external-player kind and command
- image-cache size cap
- auto-play next episode
- auto-skip OP / ED
- custom-vs-native player controls
- whether to enter PiP automatically when you navigate away from a playing video
Full table with defaults and effects is in docs/architecture.md.
A two-line summary: a Rust sidecar embedded inside an Electron shell speaks to Kitsu / AniList / aniskip and resolves streams from the provider itself. A streaming proxy in the sidecar adds the right Referer: headers and rewrites HLS playlists so the embedded <video> element can play upstream content without CORS or referer issues. SQLite caches metadata; the filesystem caches images.
For the long version — diagrams, cache TTLs, the title-resolution bridge, the PiP architecture — see docs/architecture.md, docs/title-resolution.md, and the rest of docs/.
See docs/development.md.
ani-gui only exists because of the projects it builds on:
- pystardust/ani-cli — the Bash scraper this project grew out of, and the source of the provider pipeline the Rust resolver reimplements.
- Kitsu and AniList for the metadata, posters, and trending data behind the discovery surface.
- aniskip for the community-submitted OP/ED intervals.
- hls.js for the HLS playback inside the embedded player.
ani-gui is a tool. Like any tool, the responsibility for how it's used lies with the user. The app makes no claim on the content it surfaces — it hosts nothing, talks to the same providers you'd reach in a browser, and routes their output through your machine.
GPL-3.0, inheriting from upstream pystardust/ani-cli.

