I wanted MPV for playback, Trakt for tracking, AniList for anime, subtitles when I needed them, and exactly zero browser tabs.
So I built Kari.
Built with Bubble Tea.
git clone https://github.com/Dhairya3391/kari.git
cd kari
go build -o kari ./cmd/kari
./kariType a query, press Space, pick a result.
- Multi-source search — Searches multiple providers at once (MovieBox, VidKing, Miruro, WCO, and optional Jellyfin). Results grouped by source.
- 5 media modes — Anime, Movies, TV Shows, Cartoons, Jellyfin. Switch with Tab.
- Episode browsing — Season/episode lists with filler badges and sub/dub toggles.
- Parallel source resolution — Queries every provider at once, shows all available qualities.
- External player support — MPV (any OS), IINA (macOS), VLC (Windows), MX Player (Android).
- IPC position tracking — MPV reports back via Unix socket. Close mid-episode, resume where you left off.
- Auto-skip intro/outro — Pulls timestamps from AniSkip, generates an MPV Lua script on the fly.
- Scrobbling — Syncs watch status to Trakt.tv (movies/TV) via device auth (no tokens to copy) and AniList (anime) via OAuth.
- Watch history — Local JSON store, grouped by series, remembers your position.
- Downloading — yt-dlp powered downloads with aria2c multi-connection acceleration for HLS, DASH, and direct HTTP sources.
- Posters & metadata — TMDB/AniList poster art in search results and the preview screen, plus overview, genres, and rating. Renders as real pixel images on Kitty/WezTerm/Ghostty, and falls back to Unicode half-block art everywhere else — no config needed, capability is auto-detected.
- Customizable accent color — Pick from curated presets or type your own hex code in Settings; applies immediately across the whole UI.
- Download progress bar — Live byte-percentage (single downloads) or episode-count (batch downloads) progress bar during downloads.
- Subtitles — Tries the playback provider's own subtitles first (matching your preferred language, then English), falls back to other providers, then OpenSubtitles, then Yify — never a random unrelated language. Re-syncs automatically if you switch source or change the subtitle language setting.
- Self-update —
./kari -ufetches the latest release from GitHub. - Cross-platform — Linux, macOS, Windows, Android (Termux).
- Go 1.26+ — Install Go
At least one media player in $PATH:
| Player | Platforms | Notes |
|---|---|---|
| MPV | Linux, macOS, Windows, Android | Full support |
| IINA | macOS | MPV-based; position tracking works |
| VLC | Windows | No position tracking |
| MX Player | Android | Launched via am start; no position tracking |
Optional tools:
| Tool | Used for | In $PATH |
|---|---|---|
| yt-dlp | All downloads (HLS, DASH, MP4, etc.) | Required for downloads |
| aria2c | Multi-connection parallel downloads — strongly recommended for fast speeds (16 connections per file) | Recommended |
| curl | MPV pipe playback fallback | Yes |
| upx | Compressing build artifacts (build script only) | No |
Tip: Install aria2c for significantly faster downloads. yt-dlp uses it as an external downloader with 16 parallel connections per file. Without it, downloads use yt-dlp's single-connection HTTP client which is much slower.
# macOS brew install aria2 # Ubuntu/Debian sudo apt install aria2 # Arch sudo pacman -S aria2
git clone https://github.com/Dhairya3391/kari.git
cd kari
go build -o kari ./cmd/kariThe binary lands in the current directory as ./kari.
macOS / Linux / Termux:
curl -fsSL https://raw.githubusercontent.com/Dhairya3391/kari/main/install.sh | bashWindows (PowerShell):
irm https://raw.githubusercontent.com/Dhairya3391/kari/main/install.ps1 | iexBoth scripts detect your OS/architecture, download the matching binary from the latest release, and put kari on your PATH — no admin/sudo required. Set KARI_INSTALL_DIR before running to install somewhere other than the default.
Or grab a binary manually from the releases page.
All config is through environment variables. No config files to wrangle.
| Variable | Description | Default |
|---|---|---|
KARI_PLAYER |
Preferred player (mpv, iina, vlc) |
Auto-detected |
| Variable | Description |
|---|---|
TMDB_API_KEY |
Your own TMDB key. If unset, a pool of built-in keys is rotated through. |
OPENSUBTITLES_API_KEY |
OpenSubtitles API key. If unset, falls back to Yify. |
OPENSUBTITLES_USERNAME |
Required if you set the API key. |
OPENSUBTITLES_PASSWORD |
Same. |
JELLYFIN_URL |
Your Jellyfin server URL (example: "https://jfn.fqdn.com/"). When set, a JELLYFIN mode appears. |
JELLYFIN_API_KEY |
Jellyfin API key. Required if JELLYFIN_URL is set. Generate one in Dashboard → API Keys. |
TRAKT_CLIENT_ID / TRAKT_ID |
Trakt.tv OAuth client ID |
TRAKT_CLIENT_SECRET / TRAKT_SECRET |
Trakt.tv OAuth client secret |
ANILIST_CLIENT_ID / ANILIST_ID |
AniList OAuth client ID |
ANILIST_CLIENT_SECRET / ANILIST_SECRET |
AniList OAuth client secret |
The binary ships with fallback keys for Trakt, AniList, and TMDB. They work fine for casual use. If you hit rate limits, set your own.
| Variable | Description | Default |
|---|---|---|
KARI_DOWNLOAD_DIR |
Where downloaded episodes land | ./downloads |
KARI_LOG_FILE |
Log file path | ~/.config/kari/kari.log |
KARI_LOG_DEBUG |
Enable debug logging (1, true) |
false |
KARI_LOG_STDERR |
Also write logs to stderr (1, true) |
false |
./kari # Opens the TUI. Start typing to search.
./kari "one piece" # Skips straight to search results.
./kari -v --version # Print version.
./kari -u --update # Self-update from GitHub releases.| Key | Action |
|---|---|
↑/↓ or j/k |
Move through lists |
Enter |
Select series / episode / play |
Esc |
Go back |
/ |
Filter list items |
| Key | Action |
|---|---|
Space |
Start search |
Tab / Shift+Tab |
Cycle media mode (Anime → Movies → TV → Cartoon → Jellyfin) |
Ctrl+H |
Go to home/search |
| Key | Action |
|---|---|
n |
Play next episode |
A |
Toggle autoplay |
a |
Toggle sub/dub (anime only) |
r |
Restart episode from beginning |
Ctrl+P |
Switch players |
| Key | Action |
|---|---|
d |
Download selected episode |
x |
Stop active download / cancel |
| Key | Action |
|---|---|
h / H |
Open watch history |
s / S |
Settings (Trakt/AniList auth) |
Ctrl+D |
Toggle debug panel |
q / Ctrl+C |
Quit |
- Search — Type a query, hit Space. Results come back grouped by provider.
- Episodes — Season/episode list with filler badges. Sub/dub toggle is there for anime.
- Preview — Poster, description, genres, rating, resolved sources, selected subtitle, and any saved position. Hit Enter to play.
- History — Grouped by series. Resume or delete.
- Settings — Authenticate with Trakt or AniList via device auth (no manual token fiddling), set quality mode, toggle MovieBox languages, pick a preferred subtitle language, toggle image rendering, and pick an accent color (curated presets or your own hex code).
| Provider | Mode | Method | Priority |
|---|---|---|---|
| Miruro | Anime | API | 1 |
| MovieBox | Movies, TV | API (via TMDB) | 2 |
| VidKing | Movies, TV | API (via TMDB) | 2 |
| WCO | Cartoons, Anime | HTML scraping | 2 |
| Jellyfin | Movies, TV | Jellyfin API | 1 |
Lower priority = queried first. All providers are queried in parallel regardless — priority only affects result ordering when multiple providers return the same content.
Want to add another provider? See docs/PROVIDERS.md and PROVIDER_GUIDE.md.
Kari supports MPV and MX Player on Android via Termux intents and automatic configuration injection:
-
Install Termux (source) from F-Droid (do not use Play Store — the Play Store version is obsolete).
-
Install required packages:
pkg install golang curl termux-api yt-dlp aria2
Package Why golangBuild kari curlDirect/pipe fallback playback termux-apiProvides the termux-am/termux-am-starterfallback for launching MPV/MX Player via Android intents (source)yt-dlpRequired for downloads aria2Multi-connection parallel downloads for fast speeds Kari launches players by trying
am startfirst (works out of the box on most devices), then falling back totermux-am/termux-am-starterifamis blocked. That fallback needs the Termux:API app — a separate APK from thetermux-apipackage above, install it from F-Droid too — and it must be opened once after install to start its background socket service. If you ever see a playback error mentioningCould not connect to socket, that service isn't running: reopen the Termux:API app (or reboot). -
Grant storage access:
termux-setup-storage
Allow storage permission when prompted. This allows Kari to write headers (Referer, Origin, User-Agent, Cookies) and subtitle files directly to the MPV Android media folder.
-
Clone and build:
git clone https://github.com/Dhairya3391/kari.git cd kari go build -o kari ./cmd/kari -
Install MPV Android (or MX Player) from Play Store.
-
(Required, one-time) Link Kari's playback config into MPV Android — no root needed:
Open the MPV Android app → Settings → Advanced → Edit mpv.conf, and add this single line:
include=/storage/emulated/0/Android/media/is.xyz.mpv/.mpv.confWhy this step matters: mpv-android only reads configuration from its own app-data directory, which Termux apps cannot write (and it has no Intent-extra for HTTP headers). This one
include=line is the sole bridge — Kari rewrites.mpv.confin the MPV media folder on every play with fresh playback headers (Referer,Origin,User-Agent,Cookie), the title, resume position, and the subtitle track, so they reach MPV on the next launch. Do it once and every episode just works.
Note: Kari writes the playback config to
.mpv.conf(and a mirroredmpv.conf) under/storage/emulated/0/Android/media/is.xyz.mpv/on every play launch. It does not write to Termux's~/.config/mpv/— mpv-android's libmpv never reads that path, so headers only apply once your own app config above contains theinclude=line.
- Run
./kari
Kari launches MPV via Android am start intents (falling back to termux-am/termux-am-starter automatically if am is blocked on your device). Since Android intents can't carry stream headers, Kari injects them through the config bridge from step 6 (the include= line). See docs/PLAYERS.md for architectural details.
Note: On some Android versions, DNS resolution may fail for downloads. Kari includes a built-in fallback to Cloudflare (1.1.1.1) and Google (8.8.8.8) DNS for Android builds.
Kari is split into three layers. Providers fetch data from streaming sites (API calls or HTML scraping). Players send media URLs to external apps (MPV, IINA, VLC, MX Player) and track playback position via IPC. Between them sits a Bubble Tea TUI that handles search, episode browsing, and download management. Each provider and player is a self-contained package — adding a new one doesn't touch anything outside its own directory.
cmd/kari/main.go
|
internal/
├── app/ — Wires everything together
├── config/ — Environment config + API constants
├── service/ — Media, download, subtitle orchestration
├── provider/ — One package per streaming site
├── player/ — Platform-specific player backends
├── tui/ — Bubble Tea model-view-update
├── scrobble/ — Trakt.tv + AniList sync
├── history/ — Local JSON watch storage
├── downloader/ — yt-dlp wrapper with aria2c acceleration
├── subtitles/ — OpenSubtitles + Yify clients
├── aniskip/ — Fetches intro/outro timestamps
├── tmdb/ — Key pool with rotation
├── httpclient/ — Shared retryable HTTP client
└── logging/ — Structured slog wrapper
See docs/ARCHITECTURE.md for more detail.
Same layout as above. cmd/kari/main.go is the entry point. Everything else lives under internal/ — one package per concern.
chmod +x build.sh
./build.sh all # linux/{amd64,arm64}, windows/{amd64,arm64},
# darwin/{amd64,arm64}, android/arm64Or pick one:
./build.sh target linux amd64Or build for whatever machine you're on:
./build.shVersion is derived from git tags and baked in via -ldflags:
| Scenario | Example |
|---|---|
Tagged release (v1.0.0) |
1.0.0 |
| 5 commits after tag | 1.0.5 (patch increments) |
| No tags yet | 1.0.42 (commit count) |
| Uncommitted changes | 1.0.0-dirty |
go run / go build (no ldflags) |
0.0.0-dev |
To cut a release:
git tag v1.0.0
git push origin v1.0.0The CI builds all platforms, creates a GitHub Release, and auto-generates release notes from commits since the last tag.
go build ./...
go vet ./...
go test ./...See docs/CONVENTIONS.md and AGENTS.md (AI assistant rules for this repo) for the full list. Key rules:
- No global state — pass dependencies explicitly
- Wire everything in
internal/app/app.go - Use
internal/httpclientinstead of rawhttp.Client - Log through
internal/logging - Wrap errors with
%w
Pull requests are welcome. The best place to start is adding a new media provider — see PROVIDER_GUIDE.md for the full walkthrough.
A few ground rules:
- Run
go build ./... && go vet ./... && go test ./...before opening a PR - Don't add global state. Wire new components in
internal/app/app.go - Wrap errors with
%w - Kari does not host or distribute copyrighted content. Contributions that add DRM circumvention or direct content hosting will not be accepted.
Downloaded binaries get flagged by Gatekeeper. Clear the quarantine attribute:
xattr -d com.apple.quarantine ./kariIf you built from source, this doesn't apply.
chmod +x ./kariClick More info → Run anyway. SmartScreen flags unsigned binaries — the binary is clean, it just isn't code-signed.
Ensure termux-api is installed (pkg install termux-api) and that you've run termux-setup-storage. MPV Android must be installed from the Play Store, not from Termux packages. On some devices, the am binary is blocked by SELinux — kari will automatically fall back to termux-am or termux-am-starter if available.
Kari uses a built-in DNS fallback (Cloudflare/Google) on Android. If downloads still fail, check that your network connection is active. The system DNS resolver in Termux can be unreliable — this is a known Termux limitation.
Make sure your player is in $PATH. Run which mpv (or which iina, which vlc) to verify.
Written to ~/.config/kari/kari.log. Set KARI_LOG_DEBUG=true for verbose output. The log rotates once it passes 10MB — the previous file is kept as kari.log.1.
MIT
