Skip to content

Managing music

The media-service watches uploads/ in your music folder. You can copy with any tool: a Samba share, scp, rsync or a file manager. Copied files need write access for the group hifi (GID 10001), so bitwerk can move them into the library; the installer prepares the folder for this (permissions). A library that bitwerk only reads (read-only or NAS, MUSIC_READ_ONLY=true) has no uploads.

  • It only processes a file once it has been unchanged for 2.5 seconds and its size no longer changes (UPLOAD_DEBOUNCE_MS). Half-copied files are therefore never touched.
  • Temporary files from rsync (.name.XXXX), browsers (*.crdownload) or WinSCP (*.filepart) are ignored.
  • You can push in whole folders at once. The service cleans up empty folders afterwards.

In the web UI, Settings → Library → Upload music takes files and whole folders: drop them on the box or use Choose files or Choose folder. Folders keep their structure, so cue sheets and cover images stay with their album. The files are collected first and only reach uploads/ once all of them have arrived, then they are sorted like anything else you copy in; the results appear under Recent uploads. Hidden files (.DS_Store) and system files (Thumbs.db, desktop.ini, the macOS folder icon) are left out. A file may be up to 4 GB (UPLOAD_MAX_FILE_MB); bigger ones you copy into uploads/ as before.

The upload keeps running while you look at other tabs, and the Library tab shows its progress again when you come back. Closing or reloading the page while it runs asks first. An upload you abandon (page closed, network lost) is removed from the server after a day (UPLOAD_STAGING_TTL_HOURS).

To drop albums from Windows or macOS (Finder: Go → Connect to Server, smb://<server>/bitwerk-uploads), share uploads/ with Samba on the server:

Terminal window
sudo apt install samba

Add a share to /etc/samba/smb.conf; force group and the masks make every file writable for the group hifi, so bitwerk can sort it:

[bitwerk-uploads]
path = /srv/bitwerk/music/uploads
valid users = @hifi
read only = no
force group = hifi
create mask = 0664
force create mode = 0664
directory mask = 2775
force directory mode = 2775

Give your user (a member of hifi, the installer adds the one that ran it) a Samba password and reload:

Terminal window
sudo smbpasswd -a "$USER"
Terminal window
sudo systemctl reload smbd

Share only uploads/: library/ is where bitwerk sorts to. Windows and macOS write temporary and hidden files (.DS_Store, ._*); bitwerk ignores hidden files.

library/
└── Pink Floyd/
└── The Dark Side of the Moon/
├── 01 - Speak to Me.flac
├── 02 - Breathe.flac
└── cover.jpg

The rules for this:

  • Artist folder: album artist, otherwise artist. This keeps compilations together.
  • Without an album tag: folder “Unknown Album”.
  • Without a track number: no prefix. From disc 2 the prefix is D-NN, for example 2-01.
  • Invalid characters (/ \ : * ? " < > |) are replaced.
  • Name collision: append (2). Nothing is ever overwritten.

Required tags are title and artist. If one of them is missing, the file is unreadable or the format unknown, it ends up in uploads/_unsorted/. The list under Settings → Uploads shows the reason. It is best to fix tags beforehand with MusicBrainz Picard and then put the file into uploads/ again.

In the web UI, Settings → Library → Unsorted files lists what waits in uploads/_unsorted/. Enter tags opens a form that starts from the tags the file has; fill in at least the title and the artist and choose Import. bitwerk sorts the file into the library with these tags and keeps them on every rescan, but it never changes the audio file itself, so other programs still see its original tags.

FLAC, ALAC/AAC (.m4a), WAV, AIFF, MP3, Ogg Vorbis, Opus, WavPack, APE and DSD (.dsf). The following is captured:

  • codec and container,
  • sample rate and bit depth (incl. float),
  • channel layout and duration.

From this the quality badge is built, for example “24-bit / 96 kHz FLAC” or “DSD128 (5.6 MHz)”.

  1. Embedded image: the front cover takes priority.
  2. Image in the album folder: cover, folder, front, album or albumart with the extension .jpg, .png or .webp.

Folder images in an upload wait until the audio files of the folder are imported, and then go into the album with them. Covers are cached once per content (SHA-256).

  • Existing tags are taken over: REPLAYGAIN_*, for Opus R128_*.
  • If they are missing, the media-service measures the loudness according to EBU R128 (integrated loudness, true peak) and computes ReplayGain 2.0 with −18 LUFS as the reference.
  • The album gain is derived from all tracks of the album.
  • In the background: a scan makes new tracks browsable and playable right after their tags are read; the loudness and file quality are measured afterwards at the lowest CPU priority, one core left free. A track that is about to play is measured first. Until then a player uses the ReplayGain tags where the file has them.
  • Settings: in the web UI’s server settings an admin switches the loudness and the quality analysis on or off and sets how many CPU cores the analysis uses (auto: all but one). A change applies within seconds, without a restart.

Whether matching is applied (off, track, album) is set per player under Settings → Player.

The scan also checks what each file really holds: lossy files and their bitrate, lossless files made from an MP3 or a CD (“fake hi-res”), 24-bit files that carry only 16 bits, clipping, very compressed masters and files that do not decode cleanly (broken frames, cut off, FLAC checksum errors). The first scan after an update analyzes the existing library once; SKIP_QUALITY_ANALYSIS=true turns it off.

An album as a single file with a cue sheet appears in the library as individual tracks:

  • Album.flac with Album.cue next to it (UTF-8 or Windows-1252),
  • or an embedded cuesheet (FLAC CUESHEET block, Vorbis comment CUESHEET).

The file stays unchanged, so checksums and AccurateRip logs remain valid. The tracks play sample-accurately and run into each other gaplessly. A faulty cue sheet leads to a warning, and the file is imported as a single track.

DSF files are imported with their ID3 tags. How they are played depends on the DAC:

  • native: HIFI_DSD=native
  • as DoP: HIFI_DSD=dop
  • as PCM: DSD→PCM conversion if the DAC cannot do DSD

On start the media-service reconciles library/ with the catalog. The scan runs in the background: web UI, playback and uploads work normally in the meantime.

  • New and changed files are read and measured. If tags are missing, the values come from the folder structure.
  • Unchanged files (same size and modification time) are not even opened. A restart with a large library therefore takes only seconds.
  • New cover art or a changed cue sheet in a folder: that folder is read again.
  • Deleted files disappear from the catalog.

You can see the progress in the web UI under Settings → Uploads: status, “x of y files”, how many are unchanged or newly indexed, and the files with errors.

The media-service also looks again every 10 minutes (RESCAN_INTERVAL_SECS, 0 turns it off), and Rescan in Settings → Library of the web UI starts a rescan right away. A rescan picks up uploads the watcher missed (a NAS or network share that sends no file events), files copied into or deleted from library/ and fixed permissions. When it finds something while the Library tab is open, a notice says what.

The initial import measures the loudness of every file without ReplayGain tags, which is the expensive part. For this the scan processes as many files in parallel as the server has CPU cores. You set this with SCAN_CONCURRENCY, for example lower on a NAS with a slow hard drive or when the server has other tasks on the side:

Terminal window
SCAN_CONCURRENCY=2 # e.g. in /opt/bitwerk/.env, then: sudo docker compose up -d

If you want to re-read all files regardless of modification time, start once with FULL_LIBRARY_SCAN=true. Measurements of unchanged files are kept.

If library/ is completely empty, for example because the disk is not mounted, nothing is deleted. Files you copy directly into library/ appear after a restart of the media-service:

Terminal window
cd /opt/bitwerk && sudo docker compose restart media-service