No description
  • Rust 99.6%
  • Nix 0.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-07-13 22:21:40 +02:00
.github docs: remote controls gif 2026-07-03 22:41:51 +02:00
crates/media-controls fix: run rustfmt over all modified files 2026-06-18 12:53:35 +03:00
src fix: some smaller ux improvements 2026-07-13 21:53:46 +02:00
.gitignore feat: show dim status bar in zen mode 2026-07-10 20:51:48 +02:00
Cargo.lock chore: bump version 2026-07-13 22:21:40 +02:00
Cargo.toml chore: bump version 2026-07-13 22:21:40 +02:00
flake.lock nix: flake update inputs 2026-04-16 19:00:59 +01:00
flake.nix nix: get pname and version from cargo.toml 2026-03-11 20:52:17 +00:00
LICENSE Create LICENSE 2024-05-26 11:02:29 +02:00
README.md fix: comments about duration 2026-07-11 11:14:32 +02:00
rust-toolchain.toml style: reformat with rustfmt 2026-01-10 12:37:39 +01:00
rustfmt.toml fix: indents 2026-06-07 11:10:38 +02:00
shell.nix add nix shell 2026-02-20 20:11:10 +01:00

jellyfin-tui

Jellyfin-tui is a (music) streaming client for the Jellyfin media server. Inspired by CMUS and others, its goal is to offer a self-hosted, terminal music player with all the modern features you need.

Features

  • stream your music from Jellyfin
  • sixel cover image, courtesy of ratatui-image
  • lyrics with autoscroll (Jellyfin > 10.9)
  • custom themes, color extraction from album art + smooth interpolated transitions + tinted variants
  • spotify-like double queue with order control, etc.
  • full offline mode with metadata caching, track downloads, background updates and slow network fallback
  • last.fm scrobbling, you need jellyfin-plugin-lastfm
  • multi-library support
  • vim-style keybindings
  • MPRIS integration
  • playlists (play/create/edit/reorder)
  • transcoding, shuffle, repeat modes, the works
  • remote control from the Jellyfin web UI or any other Jellyfin client
  • vertical layout for narrow terminals, resize panes with Ctrl+Up/Down
  • works over ssh (and tmux)
  • sleep timer
  • fast and just kind of nifty really

Planned features

  • other media types (movies, tv shows)
  • if there is a feature you'd like to see, please open an issue :)

Screenshots

image

Installation

Pre-built binaries

Pre-built binaries for Linux and macOS are available on the releases page. Download the binary for your platform, make it executable, and place it somewhere on your PATH.

Arch Linux

jellyfin-tui is available as a package in the AUR. You can install it with your preferred AUR helper. Example:

paru -S jellyfin-tui

Nix

jellyfin-tui is available as a package in Nixpkgs.

Alpine Linux

jellyfin-tui is available as a package in the Alpine Linux community repository.

Other Linux

Jellyfin-tui depends on libmpv2 (audio playback) and sqlite3 (offline caching), both of which should be available in your distribution's package manager. On Debian/Ubuntu based systems, you may need to install libmpv-dev and libssl-dev as well for building.

# If you're new to rust:
# install rust from https://rustup.rs and make sure ~/.cargo/bin is in your PATH (add this to ~/.bashrc or ~/.zshrc etc.)
export PATH=$PATH:~/.cargo/bin/

# Arch
sudo pacman -S mpv sqlite
# Ubuntu/Debian
sudo apt install mpv libmpv-dev sqlite3 libssl-dev
# clone this repository
git clone https://github.com/dhonus/jellyfin-tui
cd jellyfin-tui

# optional: use latest tag
git fetch --tags
git checkout $(git tag | sort -V | tail -1)

cargo install --path .

macOS

brew install mpv
git clone https://github.com/dhonus/jellyfin-tui
cd jellyfin-tui
# add exports to your shell profile (~/.zshrc etc.)
export LIBRARY_PATH="$LIBRARY_PATH:$(brew --prefix)/lib"
export PATH=$PATH:~/.cargo/bin/
cargo install --path .

Configuration

When you run jellyfin-tui for the first time, it will guide you through creating a configuration file. You can authenticate using either username/password, password file, or jellyfin quick connect. Each of these options then uses locally stored auth tokens for future logins.

The program prints the config location when run. On linux, the configuration file is located at ~/.config/jellyfin-tui/config.yaml. Feel free to edit it manually if needed.

servers:
  - name: Password Server
    url: 'https://jellyfin.example.com'
    username: 'username'
    password: 'imcool123'
    default: true # Add to skip server picker on startup. Use --select-server to override
  - name: Quick Connect Server
    url: 'http://localhost:8096'
    quick_connect: true # use jellyfin quick connect
  - name: Password File Server
    url: 'http:/jellyfin.example2.com'
    username: 'username'
    password_file: /home/myusername/.jellyfin-tui-password # use a file containing the password

# All following settings are OPTIONAL. What you see here are the defaults.

# Show album cover image
art: true
# Save and restore the state of the player (queue, volume, etc.)
persist: true
# Grab the primary color from the cover image (false => uses the current theme's `accent` instead)
auto_color: true
# Time in milliseconds to fade between colors when the track changes
auto_color_fade_ms: 400

# Always show the lyrics pane, even if no lyrics are available
lyrics: 'always' # options: 'always', 'never', 'auto'

# Layout mode — 'auto' switches to vertical below vertical_threshold columns
layout: auto # options: 'auto', 'vertical', 'horizontal'
vertical_threshold: 100 # columns; only used when layout is 'auto'

# Auto-enter zen mode (see below) after N idle minutes. 0/omitted = disabled.
zen_mode_timeout_minutes: 0

# Auto-open the selected artist/album/playlist once the selection rests on it, so
# you can scroll a list without pressing enter. `true` uses a sane default delay;
# a number sets the delay in seconds (e.g. 0.8), and 0 opens instantly on rest.
# false/omitted = disabled.
auto_browse: false

# Swap the play and pause icons
swap_play_pause: false
# Custom symbols — useful for Nerd Font users. Each character of `spinner` is one animation frame.
symbols:
  favorite: "♥"
  shuffle: "⤮"
  play: "►"
  pause: "⏸︎"
  sleep: "⏾"
  downloaded: "⇊"
  queued: "◴"
  lyrics: "♪"
  spinner: "◰◳◲◱"
  separator: ""
  disc: "○"

rounded_corners: true

# jellyfin arbitrarily limits the bitrate of transcoded audio to 256kbps, so aac@256 is the best you can do
transcoding:
  bitrate: 256
  # container: aac

# Discord Rich Presence. Shows your listening status on your Discord profile if Discord is running.
discord: APPLICATION_ID
# Displays album art on your Discord profile.
# "off"          - no art (default)
# "musicbrainz"  - fetch from MusicBrainz/Cover Art Archive. Does not expose your server URL, but may occasionally miss.
# "local"        - use your Jellyfin server  !!CAUTION!! exposes your Jellyfin server URL to all Discord users
discord_art: "musicbrainz"
# Sets the text shown in your Discord status. (Listening to {})
# name: jellyfin-tui
# state: artist
# details: track title
discord_status: "state"

# Customize the title of the terminal window
window_title: true # default -> {title}  {artist} ({year})
# window_title: false # disable
# Custom title: choose from current track's {title} {artist} {album} {year}
# window_title: "\"{title}\" by {artist} ({year})  jellyfin-tui"

# Options specified here will be passed to mpv - https://mpv.io/manual/master/#options
mpv:
  replaygain: album
  af: lavfi=[loudnorm=I=-23:TP=-1]
  no-config: true
  log-file: /tmp/mpv.log
  # Load custom Lua scripts. Any script in ~/.local/share/jellyfin-tui/mpv-scripts/ is also loaded automatically.
  scripts:
    - /path/to/script.lua

Theming

Click to reveal theming documentation

Jellyfin-tui comes with several built-in themes in both light and dark variants. You can switch between themes in the global popup.

You can also define your own custom themes in the config by selecting a base theme and overriding any colors you want. Custom themes are hot-reloaded when you save the config file.

accent_color file

The accent color gets written to a file each time it changes. It is located in the DATA_DIR. (for example ~/.local/share/jellyfin-tui/accent_color on linux) and contains the #HEX rgb color. Use it with pywal or similar tools.

Color formats

  • "#rrggbb" (hex)
  • "red","white","gray" (named)
  • "auto" → uses the extracted accent from album art
  • "tinted" → keeps the inherited base color but blends it slightly towards the album accent (strength controlled by tint_strength)
  • "tinted #rrggbb" / "tinted:#rrggbb" → same as above but with an explicit base color
  • "none" → disables optional backgrounds (background,album_header_background only)

Overridable keys

Full list of keys
Key Description
background Main background color. Optional — none uses terminal bg.
foreground Primary text color.
foreground_secondary Secondary text (artists in player, ...).
foreground_dim Dimmed text for less important UI elements.
foreground_disabled Disabled or unavailable UI elements, disliked tracks.
section_title Titles of sections like Albums, Artists, etc.
accent Fallback color for "auto", applied when album art isn't available or if auto_color is disabled.
border Normal border color.
border_focused Border color when a widget is focused. "auto" uses primary (album) color.
selected_active_background Background of the currently selected row the the active section.
selected_active_foreground Text color of the selected row in the active section.
selected_inactive_background Background of selected rows in inactive sections.
selected_inactive_foreground Foreground of selected rows in inactive sections.
scrollbar_thumb Scrollbar handle color.
scrollbar_track Scrollbar track color.
progress_fill Played/filled portion of progress bars.
progress_track Unfilled portion of progress bars.
tab_active_foreground Text color of the active tab.
tab_inactive_foreground Text color of inactive tabs.
album_header_background Background for album/artist header rows (optional).
album_header_foreground Foreground for album/artist header rows.
tint_strength 0.01.0 float. Controls how much "tinted" colors shift towards the album accent. Stock 0.0, default tinted themes use 0.06.

Example themes

themes:
  - name: "Transparent Light"
    base: "Light"

    # remove background
    background: "none"

    # make active tab text use album accent color
    tab_active_foreground: "auto"

  - name: "Monochrome Dark (Tweaked)"
    base: "Monochrome Dark"

    # remove background and album header backgrounds
    background: "none"
    album_header_background: "none"

    # make progress bar follow album accent
    progress_fill: "auto"

    # high contrast row selection
    selected_active_background: "#eeeeee"
    selected_active_foreground: "black"

  - name: "Gruvbox Dark (Tinted)"
    base: "Gruvbox Dark"

    # surfaces shift subtly towards the album accent color
    tint_strength: 0.08
    background: "tinted"                       # inherits Gruvbox's background and tints it
    border: "tinted #3a3a3a"                   # explicit base color, tinted towards accent
    selected_inactive_background: "tinted"
    scrollbar_thumb: "tinted #808080"
    progress_track: "tinted"

Built-in tinted themesTinted Dark and Tinted Light are available out of the box and apply the same treatment to the standard dark/light palettes. Switch to them from the theme picker.

The "auto" accent color is derived from album art by default. You can disable this by setting

auto_color: false

in the config file. This will use the accent color defined in the theme instead for all ""auto"" usages.


Keymap / Custom key bindings

jellyfin-tui supports fully customizable key bindings via the config file.

You can:

  • add new bindings
  • override existing bindings
  • or disable defaults entirely

Press ? inside jellyfin-tui to see all the available actions and your current bindings. Use the action names exactly as shown there when defining your keymap.

The Help page is the authoritative reference for all actions and bindings.

How to customize

Basic example

keymap:
  ctrl-c: Quit
  j: Down
  k: Up
  space: PlayPause

Each entry maps a key combination to an action.

Adding new bindings

Adding a new key does not remove existing bindings. For example, if you want to add ctrl + s as an additional key for shuffling the queue, you can do:

keymap:
  ctrl-s: Shuffle

Both ctrl + s and the default s will now trigger the Shuffle action.

Overriding existing bindings

If you bind a key that already exists, it replaces the previous action.

keymap:
  ctrl-c: Reset

Now ctrl + c will trigger the Reset action instead of quitting.

Disabling bindings

To unbind a particular keybinding, you can set its value to null.

keymap:
  # the `Reset` action will now show (unbound) in the help page
  ctrl-x: null

To start with a completely empty keymap:

keymap_inherit: false
# only `Quit`, `Down` and `Up` will be bound
keymap:
  ctrl-q: Quit
  j: Down
  k: Up

Actions with parameters

Some actions accept parameters. It is important to use the correct syntax for these, which is !ActionName "parameters":

keymap:
  # seek 10 seconds forward or backward
  ctrl-h: !Seek -10
  ctrl-l: !Seek 10
  # adjust volume by a delta (positive or negative)
  ctrl-up: !Volume 5
  ctrl-down: !Volume -5
  # run a shell command, in this case detaching from tmux
  q: !Shell "tmux detach"

Only the bindings you define will exist.

Key syntax

The expected syntax format is modifier-modifier-key: ActionName. Modifiers can be ctrl, alt, or shift and can be combined in any order. For example, ctrl-shift-a or shift-ctrl-a are both valid.

The key can be any single character, function key (e.g., F1, F2), or special key (e.g., Enter, Space).

Examples

ctrl-c: Quit
shift-enter: QueueTempBack
alt-j: Down
ctrl-left: ShrinkPane
ctrl-shift-s: GlobalShuffle
':': !Shell "notify-send hello"

Special keys

enter
esc
tab
backtab
left right up down
home end
pageup pagedown
delete backspace
space

 image

Popup

There are only so many keys to bind, so some actions are hidden behind a popup. Press p to open it and ESC to close it. To open the Global Popup, press Shift+p. The popup is context-sensitive and will show different options depending on where you are in the program.

The Global Popup includes several toggleable preferences:

Option Description
Synchronize with Jellyfin (runs every 30 minutes) Manually trigger a library synchronization with the Jellyfin server. This updates the local cache with any changes made on the server, such as new tracks, metadata updates, etc.
Run a Jellyfin task Trigger any of the available Jellyfin background tasks, such as Library: Download missing lyrics or Media Analysis. Very useful for performing maintenance tasks without logging into the web interface.
Sleep Timer Fade out and pause after a set amount of time or pause when the current track ends. Great for listening before bed.
Switch to {large/small} artwork Toggles the cover art display size
Use {track/album} Determines whether to use the track's own artwork or the album's artwork when both are available. Also respected when downloading tracks for offline use.
Theme Opens the theme picker
Select music libraries If you have multiple music libraries, you can choose which one(s) to include in your library view.
Repair offline downloads (could take a minute) Checks the integrity of downloaded tracks and repairs any issues by re-downloading them from the server. Useful if you encounter problems with offline playback or if your library has changed significantly.
Stop downloading and abort queued Immediately stops all ongoing downloads and clears the download queue. Useful if you need to quickly free up bandwidth or system resources, or if you accidentally initiated a large number of downloads.
Reset section widths Resets the widths of all sections to their default values.

Regular popups include options relevant to the current context — album sort order, Instant Mix, track disliking, copying a track or Last.fm URL, and more. For example, in the discography view:

image

Queue

Jellyfin-tui has a double queue similar to Spotify. You can add songs to the queue by pressing e or shift + enter. Press Alt+Enter (PlayAll) to play the entire discography, album, or playlist at once — this respects shuffle mode. Learn more about what you can do with the queue by pressing ? and reading through the key bindings.

image

Zen Mode

Press Z for a fullscreen now-playing view — cover art, synced lyrics, progress bar, nothing else. Esc or Z to leave. Playback keys still work.

zen_mode_timeout_minutes in config.yaml auto-enters it after N minutes idle (fractional ok, e.g. 0.5). Off by default.

Global Shuffle

You can shuffle from your entire library with the Global Shuffle feature. Open it with Shift+S, select from the options it offers, and hit Play to start playing. You can filter by year range and downloaded-only tracks, and it works in offline mode.

.github/shuffle.png

Repeat & Radio

Repeat controls what happens when playback reaches the end.

  • Off → stop playback
  • Repeat One (R1) → loop the current track
  • Repeat All (R*) → loop the current queue
  • Radio (R~) → automatically add similar tracks to keep playback going

Press R to cycle repeat modes.

Radio Modes

  • Random (R~:Rand) → use a random track from the queue as the seed
  • Similar (R~:Sim) → always use the first track as the seed
  • Continues (R~:Cont) → each new track becomes the next seed

When radio is active, press Shift+R to cycle radio modes.

Remote Control

Jellyfin-tui registers itself as a Jellyfin session and can be controlled from the Jellyfin web UI or any other client that supports remote control (play/pause, stop, next/prev, seek, volume, play-queue).

remote-controls

MPRIS

Jellyfin-tui integrates with the OS media controls — MPRIS on Linux (controllable via playerctl etc.) and MPRemoteCommandCenter on macOS (media keys, Now Playing widget).

In the Artists and Tracks lists you can search by pressing / and typing your query. The search is case-insensitive, strips diacritics (so boa finds bôa), and will filter the results as you type. Pressing ESC will clear the search and keep the current item selected.

You can search globally by switching to the Search tab. The search is case-insensitive and will search for artists, albums and tracks. Tracks are paginated.

search

Downloading media / offline mode

Press d on a track or album to download it. Shift+d removes the download. Additional options are in the context popup.

downloading

jellyfin-tui keeps a local cache of library metadata. Pass --offline at launch to run fully offline — only downloaded tracks will be available. Playing a downloaded track always uses the local file instead of streaming.

Your library syncs in the background every 10 minutes — artists, albums and playlists refresh automatically. Opening a discography, album, or playlist loads from the local cache immediately and quietly fetches any changes from the server. You can also trigger a sync manually from the global popup.

Jellyfin is the source of truth — deleting music on the server will also remove it from jellyfin-tui, including any downloaded files.


Recommendations

Due to the nature of the project and jellyfin itself, there are some limitations and things to keep in mind:

  • jellyfin-tui assumes you correctly tag your music files. Please look at the jellyfin documentation on how to tag your music files. Before assuming the program is broken, verify that they show up correctly in Jellyfin itself.
  • lyrics: jellyfin-tui will show lyrics if they are available in jellyfin. To scroll automatically with the song, they need to contain timestamps. I recommend using the LrcLib Jellyfin plugin and running Download missing lyrics directly within jellyfin-tui (Global Popup > Run Jellyfin task > Library: Download missing lyrics), or alternatively the desktop application LRCGET, both by tranxuanthang. If you value their work, consider donating to keep this amazing free service running.

Supported terminals

Not all terminals have the features needed to cover every aspect of jellyfin-tui. While rare, some terminals lack sixel (or equivalent) support or have certain key event limitations. The following are tested and work well:

  • kitty (recommended)
  • iTerm2 (recommended)
  • ghostty
  • contour
  • wezterm
  • foot

The following have issues

  • konsole, alacritty, gnome console, terminator (no sixel support and occasional strange behavior)