kahawai

10 min read Original article ↗

kahawai

kah-hah-why · Hawaiian for stream; in te reo Māori, a strong fish of strong water

A self-hosted server for streaming your own movies, series, anime and music. It plays the file as it is whenever it can, and when it can't, it changes only the stream that's in the way.

pre-release Latest: v0.0.20-rc.1 MIT licensed Rust + GStreamer

What it is

One hub for your devices. Storage and compute wherever they live.

Kahawai is a media server you run yourself, for media you own. Point it at folders of films, shows, anime and albums; it identifies them, fetches artwork and metadata, and streams them to a browser, an Android phone or a Google TV. Its web interface is compiled into the server binary, its state lives in SQLite, and it needs no external database and no account with anybody.

Your devices

Browserembedded web app

Android & Google TVnative client by tuxx

⇄⇅ /api/v1

The only thing clients talk to

hub users, libraries, metadata, watch state, playback decisions, direct play & remux

⇠⇡ dial in · mTLS

Satellites

mediahostindexes read-only media roots, serves bytes

mediahostthe NAS in the closet, the box at a friend's

transcoderwhatever has the GPU

Just one machine? The all-in-one mode runs the hub, a mediahost and a transcoder in a single process, with no network hop between them. That's the default in the container image, and it behaves exactly like the split deployment does.

The cheapest path that plays

Every client tells the hub what it can actually decode, probed on the device itself and not looked up in a table of device models. The hub compares that with every stream of every copy of the file, and picks the cheapest plan that will work.

A film whose video your TV handles but whose DTS track it doesn't keeps its video untouched while only the audio is re-encoded. A different container is a remux, not a transcode. The GPU only gets involved when the picture itself has to change.

  1. 1

    Direct playThe original bytes, with range requests end to end.

  2. 2

    RemuxThe same streams in a container the client takes. Done by the hub.

  3. 3

    Audio-only transcodeVideo copied, only the sound re-encoded. Still the hub.

  4. 4

    Video transcodeOn a transcoder, hardware first, tone-mapped if HDR meets an SDR screen.

Every session reports what was actually done, not what was planned.

Because my library was almost, but not quite, what media servers expect.

My collection is the messy kind: fansub anime with carefully typeset ASS subtitles and their own fonts, Blu-ray rips with image-based subtitles, HDR sources, and a lot of music. It lives on a low-power NAS, while the hardware that can actually transcode sits in a different machine.

The existing servers mostly cope. But "mostly" meant subtitles flattened to plain text, an entire film transcoded because one audio track didn't fit, a transcoder that had to sit on the same box as the disks, and anime matched by guessing at file names when a hash could say exactly what the file is.

So I started again, from a few rules I wasn't willing to bend: never touch the media files; decide from what was measured, never from what was assumed; do the least work that gives a correct result; and let storage and compute live wherever they already are.

Your media, played as it is, from wherever it lives.

What makes it different inside

Features

Per-stream playback decisions

Direct play, remux, audio-only or full transcode, chosen stream by stream from capabilities the client measured. Among several copies of a film, the one that needs no transcode wins.

direct playremuxHLS

Storage and compute, split

Mediahosts and transcoders dial out to the hub, so they work behind NAT without port forwarding. One mediahost can serve several independent hubs, each seeing only the collections you publish to it.

mTLSNAT-friendlymulti-hub

Hardware transcoding that's verified

VA-API, Quick Sync, NVENC, V4L2 and VideoToolbox, each dry-run before it's trusted. Jobs go to the machine that can do them in real time, not just the one that can do them at all. HDR is tone-mapped to SDR on the GPU.

IntelAMDNVIDIAApple

Anime is a first-class media type

AniDB and AniList metadata, fansub naming conventions, absolute numbering, sequel and side-story relations, and ED2K hashes as the final word on what a file is. Sub-or-dub is remembered per user, per library.

AniDBAniListED2K

Subtitles, kept faithful

ASS/SSA keeps its typesetting and embedded fonts and renders in the browser. PGS and VobSub are drawn as overlays or OCR'd into text in any language Tesseract knows. Burn-in is the last resort, not the first.

ASS + fontsPGSOCR

Subtitle search, hash-exact

Search OpenSubtitles from the player and see which results match your exact file. Downloads happen only when someone asks for one, and are kept on the hub, never written next to your media.

OpenSubtitlesuser-initiated

Skip the recap, intro and credits

Each season is compared against itself in the background to find what repeats. Where nothing has been measured yet, viewers can opt in to TheIntroDB's community timings.

background analysisTheIntroDB

Measured loudness

Every audio track is measured once against EBU R 128, so a re-encoded stream comes out at a consistent level instead of whatever gain the downmix happened to give it.

EBU R 128downmix

Music, gaplessly

Artists, albums and tracks from your tags, with a play queue that follows you around the web app and gapless delivery between tracks.

gaplessqueue

Your files stay yours

Media roots are mounted read-only and never written to. Nothing is renamed, no artwork or NFO files are sprinkled around, and moving a file keeps its watch history.

read-onlycontent identity

Easy to run, easy to diagnose

One binary, SQLite state, online backup and restore. kahawai doctor lists every decode, encode and tone-map path it found, and every playback session can be downloaded as a single diagnostic bundle.

doctorbackupSQLite

An API without back doors

The web app uses only the public, versioned API, so any client can do what it does. The hub publishes its full OpenAPI 3.2 contract and a Swagger UI.

OpenAPI 3.2SSE

Compared with Jellyfin, Plex and Emby

How it differs

They're good, mature projects, and if one of them serves you well there's no reason to switch. Kahawai makes some different choices at the foundations.

Typical media server

kahawai~

Media engine

usuallyFFmpeg, driven by building command lines.

kahawaiGStreamer pipelines built from a typed plan, with FFmpeg's decoders inside it for the stubborn files.

Shape

usuallyOne server process holds the files, the database and the transcoder. Remote transcoding is an add-on.

kahawaiHub, mediahosts and transcoders are separate roles by design, on as many machines as you like, or all in one.

Deciding what to send

usuallyDevice profiles and whole-file transcodes when one thing doesn't fit.

kahawaiMeasured capabilities, decided per stream: the cheapest of direct play, remux, audio-only or video transcode.

Hardware acceleration

usuallyConfigured by hand; on Plex and Emby, part of a paid tier.

kahawaiProbed and dry-run at startup, ranked by measured speed, and free.

Anime

usuallyA plugin or a naming workaround on top of TV series.

kahawaiIts own media type: AniDB/AniList, fansub names, hash identity, dual audio, typeset subtitles.

Accounts

usuallySometimes a vendor account or cloud relay for sign-in and remote access.

kahawaiNo vendor account, no relay. Users live in your hub's own database.

Your media folders

usuallyOften allowed to write artwork, metadata or subtitles next to your files.

kahawaiRead-only, always. Everything Kahawai learns stays in its own state.

Where they're ahead, honestly: years of maturity, a large community, clients on almost every platform, plugins, and live TV and DVR, which Kahawai doesn't do at all. Kahawai is pre-release software, and it isn't trying to be a drop-in replacement for any of them.

How to install it

All-in-one, in Docker

One container running the hub, a mediahost and a transcoder. This is the quickest way to a working server; you can split the roles across machines later without losing users, libraries or watch history.

  1. Make a home for its state

    Three persistent directories: configuration, data and cache.

    shell

    mkdir -p runtime/config/kahawai runtime/data runtime/cache
  2. Describe your media

    Save this as runtime/config/kahawai/kahawai.toml. Each collection is a media type (movies, series, anime or music) and the folders it lives in, as seen from inside the container.

    kahawai.toml

    [all_in_one]
    transcoder = true
    
    [hub]
    bind = "0.0.0.0:8420"
    satellite_bind = "0.0.0.0:8421"
    
    [mediahost]
    name = "local"
    
    [[mediahost.collections]]
    name = "movies"
    media_type = "movies"
    roots = ["/media/movies"]
    
    [[mediahost.collections]]
    name = "series"
    media_type = "series"
    roots = ["/media/series"]
  3. Start the container

    Replace /srv/media with wherever your media is. It's mounted read-only. Port 8421 is only needed if you'll attach satellites later.

    docker run -d \
      --name kahawai \
      --restart unless-stopped \
      --device=/dev/dri:/dev/dri \
      -p 127.0.0.1:8420:8420 \
      -p 8421:8421 \
      -v "$PWD/runtime/config:/config" \
      -v "$PWD/runtime/data:/data" \
      -v "$PWD/runtime/cache:/cache" \
      -v /srv/media:/media:ro \
      iksteen/kahawai:0.0.20-rc.1

    Intel or AMD--device=/dev/dri:/dev/dri

    NVIDIA--gpus all, with the NVIDIA Container Toolkit

    No GPUleave the device out; it encodes in software

    Curious what your hardware can do? doctor lists every decode, encode, remux and tone-mapping path it finds.

    shell

    docker run --rm --device=/dev/dri:/dev/dri iksteen/kahawai:0.0.20-rc.1 doctor
  4. Create the first administrator

    This goes through a private control socket inside the container, so the public address never offers a way to claim your server.

    shell

    docker exec -it kahawai kahawai hub init-admin
  5. Open it

    Go to http://localhost:8420, sign in, and build your libraries from the collections it found. Before you let anything outside the machine reach it, put a TLS reverse proxy in front.

On Arch Linux, from the AUR

The kahawai package builds Kahawai and its own patched GStreamer from source, kept apart from Arch's, and runs it as a systemd service under a kahawai user. Add your collections to /etc/kahawai/kahawai.toml, which explains how, then start it. The AUR packages are updated separately from the releases, so they may lag behind the latest one.

shell

yay -S kahawai      # or: paru -S kahawai
sudoedit /etc/kahawai/kahawai.toml
sudo systemctl enable --now kahawai
sudo -u kahawai kahawai --config /etc/kahawai/kahawai.toml hub init-admin