Files
qbz/README.md
T
vicrodh 24c0e91a1b docs(release): credit Maxou56800 in the README list and the 2.1.2 notes
The shared-layer conflict fence (#795) reaches the release notes beside the
QConnect fixes it belongs with, and the README contributor list gains its line.

cc
2026-09-16 21:24:26 -06:00

999 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<p align="center">
<img src="static/logo.png" alt="QBZ logo" width="180" />
</p>
<p align="center">
<a href="https://github.com/vicrodh/qbz"><img src="https://img.shields.io/badge/github-vicrodh%2Fqbz-0b0b0b?style=flat-square&logo=github" alt="GitHub repo" /></a>
<a href="https://github.com/vicrodh/qbz/releases"><img src="https://img.shields.io/github/v/release/vicrodh/qbz?style=flat-square" alt="Release" /></a>
<a href="https://aur.archlinux.org/packages/qbz-bin"><img src="https://img.shields.io/aur/version/qbz-bin?style=flat-square&logo=archlinux" alt="AUR" /></a>
<a href="https://snapcraft.io/qbz-player"><img src="https://img.shields.io/badge/snap-qbz--player-0b0b0b?style=flat-square&logo=snapcraft" alt="Snap" /></a>
<a href="https://flathub.org/apps/com.blitzfc.qbz"><img src="https://img.shields.io/flathub/v/com.blitzfc.qbz?style=flat-square&logo=flathub" alt="Flathub" /></a>
<a href="https://github.com/vicrodh/qbz"><img src="https://img.shields.io/github/license/vicrodh/qbz?style=flat-square" alt="License" /></a>
<a href="https://github.com/vicrodh/qbz"><img src="https://img.shields.io/badge/platform-Linux-0b0b0b?style=flat-square&logo=linux" alt="Platform" /></a>
<a href="https://github.com/vicrodh/qbz"><img src="https://img.shields.io/badge/macOS-stable-0b0b0b?style=flat-square&logo=apple" alt="macOS (stable)" /></a>
<a href="https://github.com/vicrodh/qbz/releases"><img src="https://img.shields.io/badge/Windows-experimental-0b0b0b?style=flat-square" alt="Windows (experimental)" /></a>
</p>
<p align="center">
<a href="https://techforpalestine.org/learn-more"><img src="https://raw.githubusercontent.com/Safouene1/support-palestine-banner/master/StandWithPalestine.svg" alt="StandWithPalestine" /></a>
</p>
# QBZ
QBZ is a free and open source HiFi music player. Originally built only for
Linux, it is now also available for macOS and Windows (experimental). The
application started as a Qobuz client for paying subscribers who wanted to
listen without the audio quality limits of web browsers. Its strengths were
DAC passthrough, sending audio directly to the DAC or sound card, and switching
sample rates for each track, all with a focus on bit-perfect audio delivery.
QBZ has always played both your Qobuz subscription and your local files.
Local playback was initially an extra; it has since become just as capable.
QBZ also supports self-hosted music through Plex, Jellyfin and
Subsonic/Navidrome, all through the same interface and the same audio pipeline.
The application requires no API keys, token extraction or anything like that.
It has zero telemetry, with opt-in integrations for scrobbling, metadata
enrichment and Discord Rich Presence. QBZ doesn't track you. It just plays
your music.
## Contents
<table>
<tr>
<td width="50%" valign="top">
- [What QBZ is and what it is not](#what-qbz-is-and-what-it-is-not)
- [Legal and branding](#legal-and-branding)
- [Installation](#installation)
- [Features](#features)
- [Headless daemon (qbzd)](#headless-daemon-qbzd)
- [Kiosk mode](#kiosk-mode)
- [Tech stack](#tech-stack)
- [Building from source](#building-from-source)
</td>
<td width="50%" valign="top">
- [Environment variables](#environment-variables)
- [Known issues](#known-issues)
- [Reporting a problem](#reporting-a-problem)
- [On LLMs and AI tools](#on-llms-and-ai-tools)
- [Documentation](#documentation)
- [Contributing](#contributing)
- [License](#license)
</td>
</tr>
</table>
## What QBZ is and what it is not
QBZ exists to fill the gap left by the lack of an official Linux client.
Although it is available on other platforms, QBZ will always be Linux first.
I believe Linux users are not second-class citizens, and we deserve software
that works, looks good and, every now and then, is easy to use.
But it is just as important to understand what QBZ is not. QBZ is not a
stream-ripping tool. Its purchase downloader lets you download music you have
bought on Qobuz, and only your purchases can be downloaded DRM-free. I built
QBZ rather than TDL, SPTFY, DZR or PPLMSC out of love for music, because I
believe it belongs to the artists who create it, and because Qobuz is perhaps
the fairest platform for artists so far.
You can keep tracks from the service for offline listening and access them
through your library. These are protected with QBZ's own encryption and cannot
be used outside the application. Offline listening remains subject to your
subscription: if Qobuz reports that it is no longer valid, QBZ allows a 30-day
grace period from that first invalid response, then removes the cached tracks
unless the subscription has been validated again.
QBZ will never be an all-in-one audio system with a built-in equalizer,
spatial filters, DSP or similar processing. The point is to send audio to your
DAC, or whichever output you prefer, untouched, bit for bit. Any processing
would break that contract. Of course, you can configure your output so that
once the music leaves QBZ, it goes wherever you want, including an EQ such as
EasyEffects. PipeWire and JACK make that easy.
Above all, I'm a software engineer, not an audio engineer. I can't ship
something when I don't understand how it works. I can't take responsibility
for an EQ copied from somewhere else or written by an AI model without being
sure I can test it and verify that it works 100%. I already did that with
another module, and it didn't go well.
## Legal and branding
- This application uses the Qobuz API but is not certified by Qobuz.
- Qobuz is a trademark of Qobuz. QBZ is not affiliated with, endorsed by, or
certified by Qobuz.
- **Offline cache** is a temporary playback store for listening without an
internet connection, protected with QBZ's own encryption. It is not a
DRM-free download of music from your subscription.
- **Local library** is a "bring your own music" feature — play your own files
with bit-perfect audio and the full QBZ interface, no streaming subscription
required.
- **SACD images** are user-supplied local files. QBZ does not provide disc
images or tools to bypass SACD copy protection.
- **SACD / DST decoding** uses `dst-decoder` 0.1.2 solely to decode MPEG-4
Direct Stream Transfer audio. The crate declares Apache-2.0 and is based
on the MPEG-4 DST reference implementation. QBZ includes the
[Apache license](licenses/Apache-2.0-dst-decoder.txt),
[provenance notice](licenses/dst-decoder-NOTICE.md) and
[full upstream MPEG notice](licenses/dst-decoder-upstream-NOTICE.txt),
including its patent notice, with its third-party licenses. QBZ does not
claim worldwide patent clearance.
- Qobuz Terms of Service: https://www.qobuz.com/us-en/legal/terms
## Installation
### One-line installer (Linux and macOS, from 2.1.2)
```bash
curl -fsSL https://qbz.lol/install.sh | bash
```
Installs the official AppImage on Linux or the upstream `.app` in
`~/Applications` on macOS, for the current user. It detects Intel/AMD or ARM64,
verifies the package SHA-256 against GitHub's release metadata, and preserves
existing installations from other sources. Future updates use **Settings →
Updates** in QBZ. Linux needs curl and Python 3 or jq; the launcher works without
FUSE. The existing glibc requirements still apply. The macOS package uses the
upstream ad-hoc signature; the notarized community channel remains available below.
To inspect the script before running it, or remove this installation while
keeping personal data:
```bash
curl -fsSL https://qbz.lol/install.sh -o qbz-install.sh
less qbz-install.sh
bash qbz-install.sh --check
bash qbz-install.sh
# Close QBZ before uninstalling:
bash qbz-install.sh --uninstall
```
### Arch Linux (AUR)
Install the prebuilt packages (recommended):
```bash
yay -S qbz-bin # desktop player; or: paru -S qbz-bin
yay -S qbzd-bin # optional headless daemon; or: paru -S qbzd-bin
```
To build from source instead:
```bash
yay -S qbz # desktop player; or: paru -S qbz
yay -S qbzd # optional headless daemon; or: paru -S qbzd
```
Pick either the source or `-bin` variant in each pair; both variants provide
the same package and therefore conflict with each other.
### Flatpak (Flathub)
```bash
flatpak install flathub com.blitzfc.qbz
```
> **Audiophiles:** bit-perfect works in Flatpak. The sandbox needs one
> permission grant so QBZ can ask PipeWire to hand the DAC over cleanly (D-Bus
> device reservation) — without it PipeWire keeps holding the device and other
> apps keep mixing through it, even with exclusive mode selected:
>
> ```bash
> flatpak override --user --own-name=org.freedesktop.ReserveDevice1.* com.blitzfc.qbz
> ```
>
> QBZ shows this and the other sandbox grants under **Settings → Flatpak** with
> copyable commands, and the HiFi Wizard covers the rest. Your host audio stack
> still has to be configured correctly.
### Snap
```bash
sudo snap install qbz-player
sudo snap connect qbz-player:alsa
sudo snap connect qbz-player:pipewire
```
> **Note:** After installing, connect ALSA and PipeWire interfaces for full
> audio support. MPRIS media keys work out of the box.
### APT (Debian / Ubuntu / Mint)
```bash
curl -fsSL https://vicrodh.github.io/qbz-apt/qbz-archive-keyring.gpg | gpg --dearmor | sudo tee /usr/share/keyrings/qbz-archive-keyring.gpg > /dev/null
cat <<EOF | sudo tee /etc/apt/sources.list.d/qbz.sources
Types: deb
URIs: https://vicrodh.github.io/qbz-apt
Suites: stable
Components: main
Architectures: $(dpkg --print-architecture)
Signed-By: /usr/share/keyrings/qbz-archive-keyring.gpg
EOF
sudo apt update && sudo apt install qbz
```
> **x86_64:** glibc 2.35+ (Ubuntu 22.04+, Debian 12+, Mint 21+).
> **arm64 (desktop app):** glibc 2.39+ (Ubuntu 24.04+, Debian 13+) — the Qt
> arm64 build needs it; Raspberry Pi OS *bookworm* is 2.36, so use `qbzd`
> there (2.35+, see below). For older systems, check Flatpak or Snap runtime
> support. The arm64 AppImage has the same glibc 2.39 minimum as the native
> packages.
### RPM (Fedora / openSUSE)
Add the signed QBZ repository and install with DNF:
```bash
sudo curl --fail --location \
--output /etc/yum.repos.d/qbz.repo \
https://vicrodh.github.io/qbz-rpm/qbz.repo
sudo dnf install qbz # desktop player, including qbzd
# or: sudo dnf install qbzd # headless daemon only
```
On openSUSE, use the same repository definition with Zypper:
```bash
sudo curl --fail --location \
--output /etc/zypp/repos.d/qbz.repo \
https://vicrodh.github.io/qbz-rpm/qbz.repo
sudo zypper refresh qbz
sudo zypper install qbz # or: sudo zypper install qbzd
```
The signing-key fingerprint is published on the
[repository landing page](https://vicrodh.github.io/qbz-rpm/). Individual RPM
files also remain available from [Releases](https://github.com/vicrodh/qbz/releases).
> **x86_64:** glibc 2.35+ (Fedora 36+, openSUSE Leap 15.6+ / Tumbleweed).
> **arm64 (desktop app):** glibc 2.39+ (Fedora 40+). `qbzd` packages stay at
> 2.35+ on both architectures.
### Gentoo
```bash
eselect repository add qbz-overlay git https://github.com/vicrodh/qbz-overlay.git
emerge --sync qbz-overlay
emerge media-sound/qbz-bin # prebuilt binary (recommended)
# or
emerge media-sound/qbz # build from source
```
For a headless installation, choose the matching daemon package:
```bash
emerge media-sound/qbzd-bin # prebuilt binary (recommended)
# or
emerge media-sound/qbzd # build from source
```
`qbzd` does not require systemd. The ebuild detects systemd, OpenRC or runit
and prints the appropriate post-install command; the daemon can generate
service definitions for all three init systems.
### NixOS / Nix
Add the flake input to your `flake.nix`:
```nix
inputs.qbz.url = "github:vicrodh/qbz";
```
**NixOS (system-wide):**
```nix
{pkgs, inputs, ...}:
{
environment.systemPackages = [
inputs.qbz.packages.${pkgs.system}.default
];
}
```
**Home Manager:**
```nix
{pkgs, inputs, ...}:
{
home.packages = [
inputs.qbz.packages.${pkgs.system}.default
];
}
```
> QBZ is also available in [nixpkgs](https://github.com/NixOS/nixpkgs) as `qbz`.
### AppImage
Download from [Releases](https://github.com/vicrodh/qbz/releases):
`chmod +x QBZ.AppImage && ./QBZ.AppImage`
### macOS
**QBZ is Linux-first, but macOS is a stable, fully supported platform** — a
proper player for Linux and Mac. PipeWire, ALSA and JACK are Linux-specific
backends; macOS plays through its own CoreAudio backend, including a Core Audio
Direct passthrough path for bit-perfect output. Casting (Chromecast/DLNA) and
Qobuz Connect work on macOS as well.
**Recommended — signed and notarized:** Afonso Ramos independently maintains a
[Homebrew Cask](https://github.com/afonsojramos/homebrew-qbz), which installs
without requiring a manual Gatekeeper bypass:
```bash
brew install --cask afonsojramos/qbz/qbz
```
You can also download the Apple Silicon or Intel DMG from the
[signed macOS releases](https://github.com/afonsojramos/qbz-macos/releases/latest).
These builds use the upstream QBZ application without recompiling it:
[@afonsojramos](https://github.com/afonsojramos) replaces its ad-hoc signature
and DMG container, then notarizes the result. This community-maintained signing
and distribution is the recommended way to install QBZ on macOS, and the macOS
version would likely not exist in its current form without Afonso's work. The
mirror publishes the source commit, original checksums, and signed checksums
for each release; see its
[trust and provenance documentation](https://github.com/afonsojramos/qbz-macos#trust-and-provenance).
**Official upstream alternative — ad-hoc signed, not notarized:** if you prefer
the artifact produced directly by the QBZ project, download the Apple Silicon
or Intel DMG from [QBZ Releases](https://github.com/vicrodh/qbz/releases) and
drag QBZ into Applications. Because the project has no Apple Developer
subscription, Gatekeeper blocks its first run. On recent macOS versions
(Sequoia / 15 and later), unlock it using either route:
- **Settings route:** try to open QBZ once (it gets blocked), then go to
**System Settings → Privacy & Security**, scroll down to the message that
QBZ was blocked, and click **Open Anyway**.
- **Terminal route** (what the settings toggle does, minus the clicking):
```bash
xattr -dr com.apple.quarantine /Applications/QBZ.app
```
This removes the quarantine attribute macOS stamps on downloaded files —
it's a one-time unlock for this copy of the app; updates installed through
QBZ's own updater don't need it again.
### Updates
Use **Check for updates now** in the app menu or **Settings → Updates**.
Launch checks are optional and preserve existing preferences. GitHub release
notifications wait until a stable release is at least 12 hours old. Manual checks find new stable
releases immediately and report network errors separately from “up to date”.
AppImage and upstream macOS `.app` installations can download and install
signed updates from QBZ. Close and reopen the app after installation. macOS
builds signed by another distributor, including the notarized community
builds, keep their original update channel. The one-line installer uses these
same update paths. Flatpak checks and updates its own installed ref through the
Flatpak portal, including progress, cancellation and already-deployed updates
that only need a restart. Permission changes require the system software manager.
Windows MSI installations stage a signed MSI and run Windows Installer after
QBZ closes, then reopen the app. The installer never requests a machine restart.
For Snap, APT/RPM, AUR, Gentoo, Nix, Homebrew and other manual installs, update through the original
installation source; its package may arrive after the GitHub release.
## Features
### Audio and playback
- **Bit-perfect playback** with DAC passthrough and per-track sample rate
switching (44.1–192 kHz)
- **Linux backends:** PipeWire, ALSA (with a Direct `hw:` bypass mode),
PulseAudio and JACK — PipeWire and PulseAudio work out of the box
- **macOS backend:** CoreAudio, including a Core Audio Direct passthrough path
for bit-perfect output
- **HiFi Wizard** — hardware auto-detection and a guided bit-perfect setup
- Native decoding: FLAC, MP3, AAC, ALAC, WavPack, Ogg Vorbis, Opus (Symphonia)
- **DSD support** — DSF/DFF playback with DSD-to-PCM conversion, DoP, and
native DSD passthrough (ALSA)
- **CD and SACD playback** — audio CDs and SACD discs play directly from the
drive; SACD goes out over DoP, with the titles read from the disc
- **CD ripping to FLAC** — a guided rip wizard with per-track progress
- Gapless playback on all backends
- **Loudness normalization** (EBU R128) with ReplayGain support
- Two-level audio cache with next-track prefetching
- Streaming playback — start listening before the download completes
### Your music, wherever it lives
QBZ treats every source the same way: one queue, one player, one interface.
- **Qobuz** — your subscription, favourites, playlists and purchases
- **Local files** — directory scanning, metadata extraction, CUE sheets and a
SQLite catalog; usable without ever logging into Qobuz
- **Plex** — browse and play your Plex music library
- **Jellyfin** — browse and play your Jellyfin music library
- **Subsonic / Navidrome** — any Subsonic-compatible server
- **Optical discs** — CD and SACD, played or ripped
### Queue and library
- Queue with shuffle, repeat (track/queue/off) and history
- Favorites and playlists from your Qobuz account
- **Qobuz playlist follow/unfollow** — subscribe natively, syncs across all
Qobuz clients
- **Playlist manager** with folders and tags, plus playlist import from
Spotify, Apple Music, Tidal and Deezer
- **Artist/album blacklist** — block artists or individual albums, not just
genres; fully reversible
- **Metadata editor** — a full tabular editor for your local files, with
MusicBrainz and Discogs lookup, album art from local files or from
Cover Art Archive / Discogs / Last.fm, and a sidecar mode that leaves your
original files untouched
- Virtualized lists for large libraries
### Qobuz Connect
Multi-device playback control using Qobuz's real-time streaming protocol. Full
1:1 parity with the official clients is still in progress.
- **Renderer mode** — receive playback commands from your phone, tablet or web
player
- **Controller mode** — control remote devices from QBZ
- Server-authoritative queue sync across all devices
- Bidirectional transport: play, pause, skip, seek, shuffle, repeat, volume
### Casting
- **Chromecast** and **DLNA/UPnP** discovery and streaming
- Seamless playback handoff to network devices
### Integrations
- **MPRIS** media controls and media keys
- **Last.fm** scrobbling and now-playing
- **ListenBrainz** scrobbling with offline queue
- **MusicBrainz** artist enrichment, musician credits, relationships (no
telemetry — one-way pull)
- **Discogs** artwork and metadata for the local library
- Desktop notifications with artwork
- **Listening history** is stored only on your disk, per Qobuz account; it
never leaves the machine unless you enable a scrobbler (Settings ›
Integrations › Privacy to pause or clear it)
### Immersive player
- Full-screen player with a tabbed panel system
- Multiple full-bleed view modes — Album Reactive, Coverflow, Static, Spectrum,
Wave Bed, Lyrics — plus GPU shader scenes (Plasma, Tunnel, Aurora, Spectral
Ribbon, Line Bed)
- **Search overlay works inside Immersive mode** — switch albums without
leaving the view
- Synchronized lyrics with line-by-line display
- Split-panel layouts: Lyrics, Track Info, Suggestions, Queue
### Discovery
- **Scene Discovery** — explore artists by location and musical scene
(MusicBrainz-powered)
- **3-tab Home:** customizable Home, Editor's Picks, personalized For You
- **Recommendations** — Last.fm and ListenBrainz/MusicBrainz-powered discovery
based on your listening history, similarities and local-listen vectorization
- **Live search overlay** with a small cache layer that learns your preferences
and stops surfacing results you never touch
- Genre filtering, artist similarity engine, radio stations
- Musician pages, label pages, album credits
### Interface
- 30+ themes (Dark, OLED, Nord, Dracula, Tokyo Night, Catppuccin, Breeze,
Adwaita...) plus a custom theme editor
- Auto-theme from DE, wallpaper, or custom image
- Mini player and system tray
- Album booklets download to your device
- Configurable keyboard shortcuts, UI scale presets (XS–XL)
- **8 languages:** English, Spanish, German, French, Portuguese, Russian,
Japanese, Dutch
- **Offline mode** usable without ever logging into Qobuz, with fully offline
playlists and automatic reconnection
### Playback memory cache
In **Settings → Playback → Playback cache**, choose a playback memory profile:
| Profile | Cache and prefetch behavior |
| --- | --- |
| **Auto — recommended** | Fixed 400 MiB on normal-memory hosts or 50 MiB below 2 GiB RAM. |
| **High — dynamic** | Starts at 400 MiB and can grow to 1600 MiB when RAM is available. |
| **Desktop** | Fixed 400 MiB with normal prefetch. |
| **Low — Pi style** | Fixed 50 MiB, smaller initial buffers and reduced prefetch; skips speculative Hi-Res warm-up. |
| **Custom** | Manual base/maximum and dynamic growth; prefetch follows the detected host class. |
Editing a preset's limits or growth toggle switches to Custom. Existing manual
cache settings are preserved as Custom. Restore defaults returns to Auto.
Profiles are shared by qbz and qbzd and preserve the requested audio quality.
Dynamic growth requires measured memory headroom. The cache shrinks after two
minutes without new demand and can fall below its base under memory pressure.
These budgets cover cached audio, not reserved RAM or the whole application's
memory. New buffers follow the selected profile; an existing track continues
with its current storage. Changing the profile does not restart the DAC.
L2 disk cache remains enabled unless **Streaming only** is selected. Tracks
outside the memory budget stream through temporary files; L2 playback and
catalog gapless read files without loading entire tracks into RAM. Keep the
cache directory on real storage when saving RAM: tmpfs consumes RAM too.
Offline downloads retain their separate settings. Streaming only keeps gapless
available, uses less memory and skips cache-based protection against network
interruptions and instant replay.
```bash
qbzd settings set audio.playback_memory_profile high
qbzd status --json
qbzd settings set audio.playback_memory_profile low
qbzd settings set audio.playback_memory_profile auto
```
The CLI also accepts `desktop` and `custom`. Advanced keys remain
`audio.playback_cache_dynamic`, `audio.playback_cache_min_mib` and
`audio.playback_cache_max_mib`; editing them selects Custom. Use `none` for an
automatic limit. Custom dynamic mode defaults to a ceiling four times the base,
capped at 16384 MiB. `status.playback_cache` reports the active profile, usage,
and effective/base/maximum budgets in bytes.
## Headless daemon (qbzd)
<p align="center">
<img src="static/readme-qbzd.png" alt="qbzd — headless daemon CLI and TUI" width="760" />
</p>
Run QBZ without a screen: `qbzd` is a standalone ~25 MB binary (shipped inside
the deb/rpm packages and as its own tarball) that turns any Linux box — a
Raspberry Pi, a NAS, the living-room mini-PC — into a bit-perfect **Qobuz
Connect endpoint** that appears in the official Qobuz apps like a hardware
streamer. It needs only **glibc 2.35+ on x86_64 and arm64** (Raspberry Pi OS
*bookworm* and *trixie*, 1 GB boards included) — deliberately lower than the
arm64 desktop app's 2.39, so a trimmed Pi image stays a valid target.
- Daemon + full CLI + terminal setup wizard (TUI) in one binary
- Browser-based login that works over SSH; one-file settings hand-off from
desktop QBZ
- HiFi wizard with copyable audio-stack config blocks (clipboard works over SSH)
- MPRIS out of the box, live JSON events (`qbzd watch`), service files for
systemd/OpenRC/runit
- Event hooks: `qbzd settings set hooks.script /path/to/script` runs your script
on playback/session events with `QBZ_*` environment variables — push
integration for audio-box distros (moOde, Volumio, DIY setups), no polling
required
Full manual:
**[Headless Daemon (qbzd) — Wiki](https://github.com/vicrodh/qbz/wiki/Headless-Daemon)**
## Kiosk mode
For Raspberry screens and handheld consoles.
<p align="center">
<img src="static/readme-kiosk.jpeg" alt="QBZ Kiosk Mode on a handheld console" width="720" />
</p>
A touch-first face for touchscreens and small panels: set `QBZ_PROFILE=kiosk`
and QBZ boots a big-target shell with its own NavRail, touch scrolling, an
on-screen keyboard, and a centerpiece Now Playing with cover↔lyrics toggle and
queue/history tabs. Switch between Kiosk and Desktop live from the Now Playing
layout menu; fullscreen is opt-in via `QBZ_KIOSK_FULLSCREEN=1`.
## Tech stack
QBZ is a single native Rust process. The UI is **Qt/QML**, bound to the Rust
core through [cxx-qt](https://github.com/KDAB/cxx-qt): the QML scene graph and
the Rust backend live in the same process and talk through generated QObject
bridges — there is no browser engine, no webview and no IPC boundary to
serialize across.
| Layer | Technology |
|-------|-----------|
| **Desktop shell + UI** | Qt 6 / QML via cxx-qt (native, single process — no webview, no IPC) |
| **Custom rendering** | Qt RHI scene-graph items + baked `.qsb` shaders (visualizers, immersive scenes, waveform) |
| **Audio decoding** | Symphonia (all codecs) via rodio |
| **Audio backends** | Linux: PipeWire, ALSA (alsa-rs, incl. Direct `hw:`), PulseAudio, JACK. macOS: CoreAudio (incl. Core Audio Direct) |
| **Networking** | reqwest (rustls-tls) |
| **Database** | rusqlite (bundled SQLite, WAL mode) |
| **Desktop** | mpris-server (Linux MPRIS), souvlaki (macOS media controls), ksni (Linux tray), keyring |
| **Casting** | rust_cast (Chromecast), rupnp (DLNA/UPnP), mdns-sd |
| **i18n** | qbz-i18n, gettext-style `.po` bundles compiled into the binary (8 locales) |
### Multi-crate architecture
The Rust workspace lives entirely under `crates/` (manifest
`crates/Cargo.toml`). `qbz-qt` is the application crate — it owns the QML tree,
the cxx-qt bridges and the RHI items, and produces the `qbz` binary. Everything
below it is frontend-agnostic. A representative slice of the workspace:
```
crates/
qbz-qt/ Application crate: QML tree, cxx-qt bridges, RHI items
qbz-app/ Application-level orchestration (non-UI)
qbz-core/ Orchestrator (player + audio + API)
qbz-models/ Shared domain types
qbz-theme/ Theme engine (30+ themes)
qbz-i18n/ Bundled translations (8 locales)
qbz-audio/ Audio backends, loudness, device management
qbz-player/ Playback engine, streaming, queue
qbz-dsd/ DSD (DSF/DFF) decoding, DoP, native DSD packing
qbz-disc/ Optical media: CD-DA and SACD
qbz-rip/ CD ripping to FLAC
qbz-cmaf/ CMAF/DASH streaming
qbz-cache/ L1 memory + L2 disk audio caching
qbz-offline-cache/ Offline downloads and their lifecycle
qbz-qobuz/ Qobuz API client and auth
qbz-source/ Source-agnostic seam over every backend
qbz-library/ Local file scanning and metadata
qbz-local-catalog/ Derived read projection for the local library
qbz-plex/ Plex integration
qbz-jellyfin/ Jellyfin integration
qbz-subsonic/ Subsonic / Navidrome integration
qbz-media-cache/ Shared cache for remote media servers
qbz-integrations/ Last.fm, ListenBrainz, MusicBrainz, Discogs
qbz-reco/ qbz-external-reco/ Recommendations engine
qbz-lyrics/ Lyrics (Qobuz-native, external fallback)
qbz-radio/ Radio stations
qbz-mixtape/ Mixtape/DJ-mix generation
qbz-playlist-import/ Spotify, Apple Music, Tidal, Deezer import
qbz-media-controls/ MPRIS / SMTC / MPNowPlayingInfoCenter
qbz-cast/ Chromecast, DLNA/UPnP
qbz-dac-wizard-core/ HiFi Wizard (hardware auto-detection)
qbz-credentials/ qbz-secrets/ Auth/token storage
qconnect-protocol/ Qobuz Connect protobuf wire format
qconnect-core/ Queue and renderer domain models
qconnect-app/ Application logic and concurrency
qconnect-transport-ws/ WebSocket transport with qcloud framing
qbzd/ Headless daemon + CLI + setup TUI
```
## Building from source
QBZ is a pure Rust workspace — there is no Node.js, no `npm install`, no
webview. The workspace manifest is `crates/Cargo.toml` and the application
crate is `qbz-qt`, which builds a binary called `qbz`.
### Prerequisites
- **Rust stable.** No nightly, no `mold`, no custom `RUSTFLAGS` — the build
needs none of them, and setting any of them invalidates the whole build cache
for no gain.
- **Qt 6.8 or newer** (6.9+ on Windows), including its development headers
*and* the private headers (`<rhi/qrhi.h>` lives in Qt's private tree and the
custom scene-graph items need it).
- **Python 3**, for the Qt SDK cache guard and QML audits.
- Linux, macOS or Windows x64 (experimental), with audio support.
- No Node.js/npm required.
### System dependencies
**Debian / Ubuntu:**
```bash
sudo apt install build-essential pkg-config cmake clang libclang-dev nasm python3 \
qt6-base-dev qt6-base-private-dev \
qt6-declarative-dev qt6-declarative-private-dev \
qt6-shadertools-dev \
libasound2-dev libjack-jackd2-dev libdbus-1-dev libssl-dev
```
`qt6-shadertools-dev` provides `qsb`, the shader baker. It is optional: the
`.qsb` files are committed, so a build without `qsb` simply keeps them (the
build prints a warning). You need it if you intend to modify a shader.
**Arch Linux:**
```bash
sudo pacman -Syu --needed base-devel git rust pkgconf cmake clang nasm python \
qt6-base qt6-declarative qt6-svg qt6-wayland alsa-lib dbus openssl
```
If you manage Rust with rustup, omit `rust` from that command.
Also install a JACK development provider: `pipewire-jack` if you use PipeWire's
JACK support, or `jack2` for JACK itself. Keep the provider your system already
uses. Both supply the headers and `jack.pc`. Arch's Qt packages include the
private headers; use `QMAKE=/usr/bin/qmake6` if another Qt version is selected.
For shader editing, add `qt6-shadertools`.
**Gentoo:**
With the standard Gentoo compiler toolchain and a stable Rust toolchain
installed (`dev-lang/rust`, `dev-lang/rust-bin` or rustup):
```bash
sudo emerge --ask dev-vcs/git dev-build/cmake dev-lang/nasm dev-lang/python \
virtual/pkgconfig '>=dev-qt/qtbase-6.8:6[gui,network,opengl]' \
'>=dev-qt/qtdeclarative-6.8:6[network,opengl]' \
'>=dev-qt/qtsvg-6.8:6' '>=dev-qt/qtwayland-6.8:6' \
media-libs/alsa-lib virtual/jack sys-apps/dbus dev-libs/openssl
```
Keep the Qt modules on the same version and enable the USE flags for your
display session (`X` or `wayland`). The Qt packages include their private
headers. If `qmake6` is not on PATH, set `QMAKE` to the Qt 6 executable, usually
`/usr/lib64/qt6/bin/qmake`. For shader editing, add `dev-qt/qtshadertools:6`.
**NixOS / Nix:**
Use a development shell with a nixpkgs revision that provides Qt 6.8 or newer.
For a manual checkout, the build dependencies can be loaded without installing
QBZ or adding development libraries to the system configuration:
```bash
nix-shell -p rustc cargo python3 pkg-config cmake nasm \
qt6.qmake qt6.wrapQtAppsHook qt6.qtbase qt6.qtdeclarative \
qt6.qtsvg qt6.qtwayland alsa-lib libjack2 dbus openssl
```
The Qt setup hooks select `qmake` and expose the development headers. For
shader editing, add `qt6.qtshadertools`. When packaging the application, keep
`qt6.wrapQtAppsHook` so the installed binary can find its Qt plugins and QML
modules; see the [Nixpkgs Qt documentation](https://nixos.org/manual/nixpkgs/unstable/#sec-language-qt).
**Fedora and other Linux distributions:** package names differ; look for the
equivalents of the list above — a C/C++ compiler plus clang/libclang, cmake,
nasm, the Qt 6 Base and Declarative modules with their development *and*
private headers, Qt Shader Tools, and ALSA, JACK, D-Bus and OpenSSL development
headers. Please open a PR if you confirm exact package names for your distro.
**macOS:** Xcode Command Line Tools (`xcode-select --install`), a Rust
toolchain, and Qt 6 — Homebrew's `qt` is what the build is tested against
(`brew install qt`); the build script finds it at `/opt/homebrew/opt/qt`
without any `PATH` fiddling.
**Windows x64 (experimental):**
To compile and run against an installed Qt SDK instead of using the QBZ bundle,
install:
- Visual Studio 2022 Build Tools with **Desktop development with C++**,
including the MSVC v143 x64 toolchain and a Windows 10 or 11 SDK.
- Rust stable for `x86_64-pc-windows-msvc`, Git, Python 3, CMake and NASM.
- Qt **6.9 or newer**, using the **MSVC 2022 64-bit** kit, with Qt Base,
Declarative/Quick, SVG and the private headers. CI currently uses **6.9.3**.
Qt Shader Tools is optional unless you are editing shaders.
Use Qt's installer or an existing SDK with that kit. QBZ's Windows build uses
MSVC throughout; select the matching [Qt for Windows kit](https://doc.qt.io/archives/qt-6.9/windows.html).
Open an **x64 Native Tools Command Prompt for VS 2022**, then start PowerShell
from it. In the repository root, adjust the SDK path and run:
```powershell
$env:QT_ROOT_DIR = 'C:\Qt\6.9.3\msvc2022_64'
$env:QMAKE = "$env:QT_ROOT_DIR\bin\qmake.exe"
$env:CMAKE_PREFIX_PATH = $env:QT_ROOT_DIR
$env:PATH = "$env:QT_ROOT_DIR\bin;$env:PATH"
$env:CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER = "$env:VCToolsInstallDir\bin\Hostx64\x64\link.exe"
python scripts/qt-cargo.py build --release --manifest-path crates/Cargo.toml -p qbz-qt --target x86_64-pc-windows-msvc
.\crates\target\x86_64-pc-windows-msvc\release\qbz.exe
```
Keep the SDK's `bin` directory on PATH when running this build so Qt's DLLs
can be found. WiX and .NET are only needed to build the MSI installer.
### Build and run
On Linux and macOS:
```bash
git clone https://github.com/vicrodh/qbz.git && cd qbz
NORUN=1 ./scripts/qt-run.sh
./crates/target/release/qbz
```
A release build from scratch is on the order of ten minutes on a modern
desktop; incremental builds are a couple of minutes. The UI is QML and is
loaded at runtime, so a change that touches only `.qml` does not go through
`rustc` at all.
### The build script: `scripts/qt-run.sh`
The repo ships the build script we use ourselves. It runs the static QML audits
first (they catch the class of mistake `cargo check` cannot see — a missing
component, or a call to a bridge member that does not exist — in about a
second), then builds and executes the binary directly:
```bash
./scripts/qt-run.sh # build (release) and run
DEBUG=1 ./scripts/qt-run.sh # debug profile
NORUN=1 ./scripts/qt-run.sh # build only
TEST=1 ./scripts/qt-run.sh # also run the crate's tests
SMOKE=1 ./scripts/qt-run.sh # offscreen smoke run instead of the GUI
NO_AUDIT=1 ./scripts/qt-run.sh # skip the QML audits
JOBS=4 ./scripts/qt-run.sh # cargo build jobs
```
It works on Linux and macOS.
Qt builds use `scripts/qt-cargo.py` to include the installed SDK's header
contents in the native build cache. This prevents mixing stale C++ objects
with new Qt headers after a system update, without deleting `target/` or
changing Rust optimization flags. For custom Cargo commands, use
`python3 scripts/qt-cargo.py build --release --manifest-path crates/Cargo.toml -p qbz-qt`
(or `test` instead of `build`). On Linux, the smoke uses an isolated profile
and private D-Bus session, and fails on early exits, including segmentation
faults. The CI runtime gate checks both debug and release.
### Nix / NixOS
`flake.nix` builds the same binary via `rustPlatform.buildRustPackage` with the
crate root at `crates/` — see the [NixOS / Nix](#nixos--nix) install section
above, or run `nix build` / `nix develop` directly from a checkout.
### API proxy
Last.fm, Discogs, Tidal, Spotify-import and MusicBrainz traffic goes through a
hosted Cloudflare Workers proxy (`qbz-api-proxy.blitzkriegfc.workers.dev`) that
holds all credentials server-side. Both pre-built releases and source builds use
it out of the box — **no API keys or `.env` file required**.
If you want to run against your own proxy (for development, or if you fork
QBZ), the proxy source lives at
[`vicrodh/qbz-api-proxy`](https://github.com/vicrodh/qbz-api-proxy). Deploy it
with `wrangler deploy` and then edit the `*_PROXY_URL` constants in
`crates/qbz-integrations/src/lastfm/client.rs`,
`crates/qbz-integrations/src/discogs/mod.rs`,
`crates/qbz-playlist-import/src/providers/tidal.rs` and
`crates/qbz-integrations/src/musicbrainz/client.rs` to point at your worker
before rebuilding.
## Environment variables
QBZ picks a working renderer at startup and reverts automatically if a forced
choice fails to produce frames, so there is normally nothing to configure. The
renderer can also be set from **Settings → Appearance**; the environment
variable overrides it for one launch.
| Variable | Effect |
|----------|--------|
| `QBZ_RENDERER=auto` (or `gpu`, `hardware`, `hw`) | Qt's default backend — the GPU path. This is the default |
| `QBZ_RENDERER=gl` | Force the OpenGL backend (`QSG_RHI_BACKEND=opengl`) on Linux. On macOS this resolves to Metal |
| `QBZ_RENDERER=software` (or `cpu`, `soft`) | Force the software renderer (`QT_QUICK_BACKEND=software`) — for VMs and broken GPU stacks |
| `QBZ_PROFILE=kiosk` | Boot the touch-first Kiosk shell (`desktop` for the normal one) |
| `QBZ_KIOSK_FULLSCREEN=1` | Start Kiosk mode fullscreen |
Qt's own variables (`QSG_RHI_BACKEND`, `QT_QUICK_BACKEND`, `QT_QPA_PLATFORM`,
`QT_SCALE_FACTOR`…) work as usual and take precedence over `QBZ_RENDERER`.
If QBZ fails to start, try `QBZ_RENDERER=software qbz` first.
## Known issues
- **Hi-Res seeking** — seeking in tracks above 96 kHz can take 10–20 s (the
decoder must scan from the start). Use prev/next for instant navigation.
- **ALSA Direct** — exclusive access blocks other apps. Use your DAC's or
amplifier's physical volume control.
- **DSD DoP / native mode** — seeking is disabled and volume is fixed while a
DoP or native-DSD stream is active (any sample manipulation would corrupt the
DSD stream). Convert-to-PCM mode has no such limits.
- **Multichannel DSD comes out as stereo** — DoP and native passthrough are
two-channel by design (a DoP receiver is a stereo device). Multichannel
sources therefore play through the convert-to-PCM path, which folds up to 5.1
down to stereo (ITU-R BS.775, LFE dropped); 7.1 and above are not supported
and the file will not load. SACD discs play their stereo area.
## Supporting QBZ
QBZ is deeply grateful for every sponsor and supporter. There is no premium
edition and no sponsor-only functionality; your support helps sustain the
project while the same complete app remains available to everyone. Starting
with QBZ 2.1.0, public sponsors and supporters are thanked by name in the
app's **About** dialog. Private or anonymous support remains unnamed — and is
just as appreciated.
## Reporting a problem
Having trouble with the app? Please report it in the
[issue tracker](https://github.com/vicrodh/qbz/issues).
What makes a problem solvable: a title that names the actual symptom, the logs,
and a screenshot when it is something you can see. QBZ ships a tool for the logs
so you don't have to go hunting for files — **Settings → Share logs**, pinned at
the bottom of the Settings sidebar, opens the in-app log viewer. From there you
can filter by level, search, copy the log with secrets redacted, or upload it and
get a link to paste into the issue.
## On LLMs and AI tools
This project started about two years ago. I wrote the first version in Python,
which is my main stack. Then it became my Rust learning project — most of the
logic was written by hand, as a hobby, a proof to myself of what I could build
and how much I could learn. It then sat frozen for a long time.
Then LLMs went mainstream. They helped me put a real UX on top of what I already
had, they unblocked problems I had never found the time for, and they compressed
work that, at the pace and the hours I could actually give it, would have taken
months — or would have left the project sitting in a folder on my NAS forever.
Somewhere along the way QBZ filled a gap, and that made it relevant. Paying
attention and caring about the details also means more responsibility, and more
responsibility means more transparency: the project started by hand, but the
amount of code I type myself today is minimal. That does not mean the code is
careless. It goes through the same standards I use in my day job — the real one
— it is under continuous improvement, and it is always open to comments,
suggestions, requests and issue reports.
Using AI tools does not mean I ask an LLM for every feature or change that
crosses my mind, and it does not mean every request for a change or a new
feature gets accepted without analysis. I go out of my way to avoid that rabbit
hole — have a look at the [issues](https://github.com/vicrodh/qbz/issues). So if
you come up with something new, help me by explaining it and justifying it. If I
don't understand it, it is very unlikely to go in, because I cannot measure it
or test it properly.
I know we all want a lot of things. But keeping this app from turning into
[The Homer](https://tenor.com/IeFy.gif) is real work.
Almost all of the documentation, except this README, is AI slop. Seriously,
help writing or improving it is welcome. I hate writing documentation, and
who doesn't?
One thing is not negotiable, and it applies to me as much as to anyone sending
a PR: **the human who signs a commit is the author and is responsible for it.**
Models are tools, so they do not get co-author credit here — not to hide their
part, which this whole section is about, but because credit without
accountability is worth nothing. If your agent wrote it, you still read it, you
still ran it, and you answer for it.
That also means an agent's output is not evidence on its own. A report that
nobody verified, or a fix that passes the model's own tests and nothing else,
costs more time to unpick than it saves. Findings are genuinely welcome — bring
them as issues with what you observed. A fix is welcome too, once you have run
it yourself and can say what you tested.
If you have a problem using software built with AI tools, this software is
probably not for you. If you expect unsupervised AI output to be merged because
it came from a model, that is not for you either.
## Documentation
User guides, audio configuration, integrations and troubleshooting:
**[QBZ Wiki](https://github.com/vicrodh/qbz/wiki)** (work in progress).
## Contributing
QBZ is MIT-licensed. No telemetry, no tracking, no hidden services. Built for
Linux and macOS audio enthusiasts.
Contributions welcome. Please read `CONTRIBUTING.md` before submitting issues or
pull requests.
### Contributors
- [@vorce](https://github.com/vorce)
- [@boxdot](https://github.com/boxdot)
- [@arminfelder](https://github.com/arminfelder)
- [@afonsojramos](https://github.com/afonsojramos) — macOS port
- [@GwendalBeaumont](https://github.com/GwendalBeaumont) — i18n
- [@AdamArstall](https://github.com/AdamArstall)
- [@Vudgekek](https://github.com/Vudgekek) — macOS audio
- [@DoubleGate](https://github.com/DoubleGate)
- [@hoyon](https://github.com/hoyon) — classical work grouping
- [@mxnix](https://github.com/mxnix) — Russian translation
- [@TerminalTilt](https://github.com/TerminalTilt) — Catppuccin themes
- [@Alexandre-Menigault](https://github.com/Alexandre-Menigault) — active lyrics wrapping
- [@MarkusAbtion](https://github.com/MarkusAbtion) — opt-in section navigation
- [@fengalin](https://github.com/fengalin) — crypto-provider test coverage
- [@luukvanderduim](https://github.com/luukvanderduim) — applied-filter visibility
- [@pbaart](https://github.com/pbaart) — Dutch translation
- [@b0bbywan](https://github.com/b0bbywan) — ALSA buffer sizing
- [@stshow](https://github.com/stshow) — DLNA strict-renderer casting
- [@Ronjar](https://github.com/Ronjar) — deb822 APT instructions
- [@eldios](https://github.com/eldios) — Nix packaging
- [@herder](https://github.com/herder) — Spotify-parity hotkeys and the Vim keymap
- [@PhilipVinc](https://github.com/PhilipVinc) — daemon event hooks and QConnect reporting diagnostics
- [@Mazipani](https://github.com/Mazipani) — Chromecast X.509 v1 certificates
- [@RayneGit](https://github.com/RayneGit) — Wayland clipboard
- [@LuckyTheCoder](https://github.com/LuckyTheCoder) — macOS Liquid Glass icon
- [@Maxou56800](https://github.com/Maxou56800) — QConnect local-takeover fence
## License
MIT