Galdr is a fast, keyboard-first terminal music player for local libraries. It is written in Go and designed for Linux terminals, with first-class support for Arch Linux and Omarchy.
It keeps music playback local, starts without mandatory configuration, and uses libmpv to work with the audio output already available on the system.
- Recursive local-library scanning with metadata and filename fallbacks.
- MP3, WAV and FLAC playback through libmpv.
- Gapless transitions between compatible adjacent tracks using one libmpv playlist and its conservative audio-device reuse mode.
- Missing durations appear progressively while playback is stopped, without delaying startup or competing with the active audio engine.
- Responsive three-panel interface for browsing the library, tracks and queue.
- Local album covers from
cover.jpg,cover.jpegorcover.png; artwork is never downloaded. - Automatic theme that follows Omarchy when available, then the terminal's ANSI palette; also usable in basic 16-color terminals and without color.
- Play / pause / stop, next / previous, volume up / down, mute.
- Optional track or album ReplayGain normalization with clipping protection.
- Seek: ±5s with
←/→, jump to start / end withhome/end. - Queue composition, reordering and removal, including add-to-end and play-next.
- Persistent, human-readable M3U8 playlists with explicit save and overwrite.
- Shuffle, repeat and manual library rescans.
- Automatic Linux MPRIS controls and metadata for
playerctl, desktop media keys, status bars and lock screens; D-Bus remains runtime-optional. - Volume and last-track state persisted between sessions.
- Incremental search (
/) over title, artist, album — case-insensitive substring match; the footer shows the active filter and matching count. - Minimal TOML config file (optional — sensible defaults out of the box).
- Embedded English, French, Spanish and German interface translations.
- No accounts, network calls, telemetry or background daemon.
The footer shows duration-loading progress such as Durations 42/118, then
briefly reports how many files were unavailable. Durations are kept for the
current session and recalculated the next time Galdr starts; no library database
or cache file is created.
- Go 1.26 or newer.
- Linux with a PipeWire, PulseAudio or ALSA-compatible audio stack.
- The system
mpvpackage, which provideslibmpv.so. On Arch / Omarchy:sudo pacman -S mpv
Galdr is built with CGO_ENABLED=0, but it is not a standalone binary:
go-mpv loads a compatible system libmpv.so dynamically at startup. If the
library is missing or its SONAME is incompatible, Galdr cannot start.
A user D-Bus session is optional. When present, Galdr automatically exposes
org.mpris.MediaPlayer2.galdr; when absent or already owned by another Galdr
instance, the terminal player continues normally after one diagnostic.
MPRIS clients can control play, pause, stop, next, previous, seek, shuffle, repeat and Galdr's internal volume. They also receive playback state, position, queue capabilities, track metadata and escaped local cover-art URIs.
playerctl -p galdr play-pause
playerctl -p galdr next
playerctl -p galdr previous
playerctl -p galdr metadataOn Omarchy, the existing play/pause/next/previous media-key bindings use
playerctl and work automatically while Galdr is running. System volume keys
remain system-volume controls.
Tagged releases provide packages for x86-64 and ARM64 Linux systems. Download the package for your architecture from the GitHub release, then install it with the distribution package manager.
On Arch Linux, Omarchy or Manjaro:
sudo pacman -U ./galdr-*.pkg.tar.zstOn Debian:
sudo apt install ./galdr_*.debThe Arch package installs mpv as a runtime dependency. The Debian package
installs libmpv2. Both provide the dynamically loaded libmpv.so required by
Galdr.
git clone https://github.com/kvitrvn/galdr.git
cd galdr
# Build ./bin/galdr
make build
# Run from source
make run
# Run the already-built binary
./bin/galdrThe config file is optional. If absent, galdr uses these defaults:
music_dir = ~/Musicplaylist_dir = <music_dir>/Playlistsvolume = 100theme = auto(auto|light|dark)language = auto(auto|en|fr|es|de)audio.replaygain = off(off|track|album)
theme = auto reads the active Omarchy palette from
~/.config/omarchy/current/theme/colors.toml without modifying it. Outside
Omarchy, or if that file is unavailable or invalid, Galdr inherits the
terminal's default foreground/background and ANSI palette. The explicit
light and dark modes keep their fixed palettes as configurable fallbacks.
Restart Galdr after changing the Omarchy theme to load the new palette.
Path: ~/.config/galdr/config.toml.
Example:
music_dir = "~/Music"
# Optional. Defaults to a Playlists directory inside music_dir.
playlist_dir = "~/Music/Playlists"
volume = 80
theme = "dark"
language = "auto"
[audio]
# Optional loudness normalization; disabled by default.
replaygain = "off"
[ui]
# Preferred side-panel widths in wide terminals.
left_width = 22
right_width = 22
min_width = 48
min_height = 14A leading ~ in music_dir or playlist_dir is expanded against the current
user's home directory. Keeping playlist_dir inside the music library makes
the relative M3U8 entries portable with that library. Set it to an XDG data
directory instead when the music library is read-only.
language = "auto" selects the interface language once at startup from
LC_ALL, then LC_MESSAGES, then LANG, with English as the fallback.
Regional tags and encodings such as fr_FR.UTF-8, es_MX and de-DE are
recognized; C and POSIX select English. Set en, fr, es or de to
override the environment. English and French translations are stable;
Spanish and German are currently beta and welcome native-speaker review.
Restart Galdr after changing the setting or the process locale.
ReplayGain uses loudness tags already stored in local files. track normalizes
each song independently, which is useful for a mixed queue. album preserves
the intended loudness differences between songs on an album and falls back to
track gain when album gain is unavailable. Files without ReplayGain tags play
at their original level, and Galdr asks mpv to lower normalization gain when
needed to prevent clipping. ReplayGain does not change the configured or saved
user volume.
If the config file is missing, Galdr starts with the defaults. A malformed file or invalid value prevents startup and is reported with the configuration path so it can be corrected.
The TUI adapts to the terminal: all three panels are visible from 110
columns, Library plus Tracks or Queue from 72 to 109 columns, and one
focused panel from 48 to 71 columns. Tab / shift+Tab cycles focus;
1, 2, and 3 jump directly to Library, Tracks, and Queue. Focus is
shown with a symbol, text weight, and color.
| Key | Library (focused) | Tracks / Queue (focused) |
|---|---|---|
↑ / k |
previous row | previous track |
↓ / j |
next row | next track |
← / h |
collapse artist / go to parent | seek -5s |
→ / l |
expand artist / drill in | seek +5s |
enter |
select artist or album | play selected track |
A |
add the artist or album to a playlist | add the track to a playlist |
tab |
cycle focus forward | cycle focus forward |
shift+tab |
cycle focus backward | cycle focus backward |
Global keys (work in any panel):
| Key | Action |
|---|---|
space |
Toggle play / pause |
x |
Stop playback |
n |
Next track (shuffle-aware, scope + filter) |
p |
Previous track (shuffle-aware, scope + filter) |
home / end |
Seek to start / end of current track |
+ / = |
Volume up (5) |
- / _ |
Volume down (5) |
m |
Toggle mute |
r |
Rescan the music directory |
s |
Toggle shuffle |
R |
Cycle repeat (off → all → one → off) |
P |
Open the playlist browser |
/ |
Enter search (filter by title / artist / album) |
esc |
Clear filter (or exit search input) |
ctrl+l |
Clear filter |
? |
Toggle help overlay |
q / ctrl+c |
Quit |
Tracks panel (when focused):
| Key | Action |
|---|---|
a |
Add the selected track to the queue tail |
A |
Add the selected track to a playlist |
N |
Insert the selected track after the current track |
enter |
Replace the queue with the visible scope and play |
Queue panel (when focused):
| Key | Action |
|---|---|
↑ / k |
Move cursor up |
↓ / j |
Move cursor down |
K / shift+↑ |
Move the highlighted track up in the queue |
J / shift+↓ |
Move the highlighted track down in the queue |
d |
Remove the highlighted track (except playing) |
c |
Clear the queue (keep the playing track) |
enter |
Play the highlighted track immediately |
Playlist browser:
| Key | Action |
|---|---|
P / esc |
Close the browser |
↑ / k |
Select the previous playlist |
↓ / j |
Select the next playlist |
enter |
Load the selected playlist into the queue |
S |
Save the current queue under a new name |
Playlist tracks (centre panel):
| Key | Action |
|---|---|
enter |
Play the selected playlist occurrence |
a |
Add the selected occurrence to the queue tail |
A |
Add the selected occurrence to another playlist |
N |
Insert the selected occurrence after the current track |
d |
Remove only the selected occurrence from the playlist |
The active queue is temporary working state. A saved playlist is changed only through an explicit playlist action; editing the queue after loading one never rewrites its file. Loading replaces the queue in file order but does not start playback. If the currently playing occurrence also exists in the loaded list, playback continues; otherwise Galdr stops it safely. Loading also disables shuffle so the queue visibly follows the stored order; shuffle can be enabled again afterwards.
Each playlist is one UTF-8 .m3u8 file. Entries are relative to the playlist
file, remain confined to music_dir, retain duplicates and can be edited or
backed up with ordinary filesystem tools. Missing, unsupported or escaping
entries are skipped and summarized while valid entries still load. Saves use a
same-directory temporary file and atomic rename, and existing playlists require
an explicit overwrite confirmation.
The search input is incremental: each keystroke updates the
filter live and the list shrinks in every panel. enter or esc
exits the input and keeps the filter active; a second esc (or
ctrl+l) clears it. While the filter is active, the Library
panel hides artists and albums with no matching track, and
n / p / ↑↓ in the Tracks panel operate on the visible
subset.
| Format | Extension | Tags | Duration | Playback |
|---|---|---|---|---|
| MP3 | .mp3 |
ID3v1, ID3v2 (all versions) | loaded progressively | libmpv / FFmpeg |
| WAV | .wav |
RIFF INFO | available from startup | libmpv / FFmpeg |
| FLAC | .flac |
Vorbis comments | available from startup | libmpv / FFmpeg |
Files with unsupported extensions are skipped during the scan. Although mpv supports many more inputs, Galdr deliberately accepts only local MP3, WAV and FLAC files; URLs, video and additional formats remain out of scope.
Galdr keeps the current track and only its resolved successor in libmpv. The successor follows the visible queue order, including shuffle and repeat, and is reconciled after queue edits or a rescan. Compatible files can therefore pass without an application-created pause. A codec, sample-rate, channel-layout or output-format change may still require libmpv to reconfigure the audio device; playback continues normally when that transition cannot be seamless. Galdr keeps automatic output-device selection but asks libmpv for a fixed 48 kHz output rate, so 44.1 kHz sources are resampled before reaching PipeWire.
Tags are read from every supported file at startup. When tags are missing, the title falls back to the filename so every track remains identifiable. A manual rescan refreshes the library and restarts duration loading for newly discovered tracks.
Galdr never downloads artwork or contacts an external cover service. To display
an album cover, place a JPEG or PNG image next to the music files and name it
exactly cover.jpg, cover.jpeg or cover.png.
When several supported files are present, Galdr prefers cover.jpg, then
cover.jpeg, then cover.png. The image appears in the Now Playing area when
the terminal is large enough; playback works normally without one.
Issues, bug reports and focused pull requests are welcome. Before submitting a change, keep it scoped to Galdr's local, lightweight terminal-player goals and run the standard checks:
make fmt # go fmt ./...
make vet # go vet ./...
make test # go test ./...
make build # produces bin/galdr
make run # runs the player
make tidy # go mod tidy
make clean # rm -rf bin/Tests do not require audio hardware, a graphical desktop or network access.
The test suite uses a fake mpv client for playback behavior. A system
libmpv.so is still required when loading the binding.
Releases are built by GoReleaser and published by GitHub Actions. Before creating a tag, validate the release configuration and build local snapshot packages. These local targets require GoReleaser v2 to be installed:
make release-check
make release-snapshotSnapshot artifacts are written to dist/. Publish a release by pushing a
semantic version tag:
git tag v0.1.0
git push origin v0.1.0The release workflow runs make vet, make test, and make build, then
publishes Arch Linux packages, Debian packages, portable tar archives, and
checksums.txt for AMD64 and ARM64. Tags containing a semantic-version
prerelease suffix, such as v0.2.0-rc.1, create a prerelease on GitHub.
- Install the runtime with
sudo pacman -S mpv; no development headers or CGO toolchain are needed to build Galdr. - libmpv automatically selects an available PipeWire, PulseAudio or ALSA output. Galdr does not force a backend, but uses a 48 kHz output rate to keep the audio-device format stable across tracks.
- Personal
mpv.conf, input bindings, scripts, OSC and video output are disabled so user mpv configuration cannot alter Galdr playback.
- No streaming, cloud sync, lyrics or visualizer.
- No library database, metadata cache or filesystem watcher (manual rescan only).
- Enriched durations are held in memory for the current session and are recalculated on the next launch.
- Files that libmpv cannot decode or whose duration remains unavailable keep
the
--:--placeholder. VBR MP3 duration is provided by libmpv. - Playback capabilities depend on the installed mpv/libmpv build and its codec and audio-output dependencies.
- The binary dynamically depends on a compatible
libmpv.soSONAME and is therefore not a self-contained portable artifact. Galdr does not bundle or statically distribute libmpv. - Volume and last track are saved on quit and restored on next launch, but the in-queue position is not.
Galdr is released under the MIT License.
The go-mpv binding is also MIT licensed. The dynamically loaded libmpv is
LGPL 2.1+ or GPL, depending on how the system package was compiled.

