Skip to content

Repository files navigation

hifi

Overview

Lightweight MPD-backed music player

Instructions

Check config.env (copied from config.env.example)

Ensure mpd running and has indexed music (use mpc) For dev: brew services start mpd

indexer/.venv/bin/hifi-index

Run the web service

cd service .venv/bin/hifi-service (with sudo for root)

For dev, run vite for frontend

cd frontend yarn dev

Specs

@spec-frontend.md @spec-indexer.md @spec-web-service.md

Layout

fixtures/        committed test fixture tree + bootstrap script
  build.sh       run by hand to (re)build music/ — not run by tests
  music/         synthetic library (mp3/m4a, embedded + folder art, multi-disc, untagged)
  expected/      committed snapshot of indexer output
indexer/         Python indexer package
  hifi_indexer/  source
  tests/         end-to-end snapshot test (spins up a real mpd)

Indexer

Reads tags from MPD over a Unix socket, walks album directories for artwork, and emits:

  • index.json — client-facing artist/album/track tables (no file paths)
  • paths.json — backend-only {trackId: relative/path} map for playback
  • art/<albumId>/{thumb,large}.webp — 300px and 800px WebP, q=80

Album id is sha256("<AlbumArtist||Artist>|<Album>")[:12], which is also the artwork hash — so artwork URLs are /art/<album.i>/{thumb,large}.webp and stable across rebuilds.

Install

Requires python>=3.10, ffmpeg, and mpd on PATH.

cd indexer
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'

Run

The indexer talks to a running MPD via its Unix socket. By default it asks MPD for music_directory; pass --music-dir to override.

.venv/bin/hifi-index --mpd-socket /run/mpd/socket

Output goes to $XDG_DATA_HOME/hifi/ (or ~/.local/share/hifi/). Override with --out.

Flags

flag default notes
--mpd-socket PATH $MPD_HOST or /run/mpd/socket MPD Unix socket
--music-dir PATH (read from MPD) override MPD's music_directory
--out DIR $XDG_DATA_HOME/hifi output directory
--jobs N os.cpu_count() parallel ffmpeg workers
--force off rebuild artwork even if cached
--skip-mpd-update off skip the update + wait before reading tags

index.json and paths.json are always rebuilt from scratch and written atomically. Artwork is cached per album id — existing art/<id>/{thumb,large}.webp is left alone unless --force.

Refresh after rsync

rsync ... /path/to/music/
hifi-index --mpd-socket /run/mpd/socket

The indexer issues update to MPD and polls status until updating_db clears before reading tags.

Bad tags

A track missing both Album and (AlbumArtist or Artist) is skipped and logged to stderr; a summary count is printed at the end.

Web service

FastAPI app that serves index.json, album art, and proxies playback control to MPD over its Unix socket. Listens on port 80 (hard-coded — needs root or an equivalent capability/port-forward).

Install

cd service
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'

Run

Assumes the indexer has already populated $HIFI_DATA_DIR (default /var/lib/hifi) and MPD is reachable on its Unix socket (default /tmp/mpd.sock, override with MPD_SOCKET).

.venv/bin/hifi-service
use sudo if running on port 80

Tests

End-to-end: the test fixture spawns a real mpd against fixtures/music/, runs the indexer, and asserts equality with fixtures/expected/{index,paths}.json plus presence of every artwork file.

cd indexer
.venv/bin/pytest
.venv/bin/ruff check .
.venv/bin/ruff format --check .

Rebuilding fixtures

fixtures/music/ and fixtures/expected/ are committed. Don't regenerate casually. If a code change forces a snapshot update, call it out explicitly.

cd fixtures && ./build.sh           # regenerate the synthetic library
# then re-run the indexer against it and copy index.json / paths.json
# (and art/) into fixtures/expected/

About

Software for my custom music player appliance

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages