This is an old revision of the document!
I'm a big music enjoyer. What I'm not a big enjoyer of is streaming services. Maybe I'll write another article about that, but the short of it is that instead of paying a company to rent temporary access to their music library, which they pay to license from a label who in turn pays only some tiny fraction of their revenue to the artist, I prefer a more traditional model:
That way the artist gets money for making art and you pay once to own a copy of it instead of renting temporary access.
Streaming is good for the first two steps. Finding new music is hard, and you have to listen to it a few times at different times, since it might only hit in a certain mood. Starting from music you already like and letting the recommender algorithm drive discovery is a good way to find new music. After that though, I prefer to pay the artist for their work instead of paying rent, and prefer to download the data once rather than every time I want to listen to it. Bandwidth is a limited resource!
One of the benefits of owning music is that you can put it on whatever device you want and use whatever program you prefer to play it. However, if you have multiple devices then getting your music collection available on all of them becomes an exercise in file management. Music also has its own inherent taxonomical structure (artist, album, label etc) that benefits from specific treatment in terms of organization. This article roughly depicts how I do all that.
Here's a chart that shows a rough outline of data flow:
In short:
beet import on the downloaded music; beets cleans up tags, pulls and attaches album art, and copies the cleaned files into my “Artist/Album/<files>” library directory structure, applying my file naming scheme.Since my music library is just a directory, it's then synced to all other devices using my syncing setup (except my phone; see below). This way it doesn't matter what device I download the music on. The arrow goes to laptop in the chart but it can go to any of them.
For playback on the computer, I prefer tauon music box.
On my iPhone, I use foobar2k. Although, it has a few drawbacks (okay just one - it doesn't support scrobbling) and in the age of AI I'll probably end up making my own when I have a few spare hours.
The phone is a little tricky. Ideally I would use the same approach as for my desktop and laptop - run a sync program to keep the music library up to date on my iPhone disk. Unfortunately, that's not how things work on iOS, for two reasons:
Regarding backgrounding, in general iOS is very strict about apps performing any work in the background. Since sync programs are designed around running in the background, this more or less precludes the file synchronization strategy. This could be worked around using iCloud, but I don't use iCloud.
Even if a sync program was viable on iOS, we would hit another blocker. iOS does not have the concept of a shared filesystem. Apps are only able to write to their own sandboxed filesystems. Consequently any files downloaded by a sync app would not be accessible by a music player app.
The upshot is that to solve this problem you need a music playback app that also has its own syncing service built in. Obviously this is heretical. A music player should focus solely on playing music, while a syncing application should handle data sync. The result of that being impossible is the app store has a bunch of terrible apps with names like “Network music player ULTIMATE” that have varying levels of support for playback and/or sync to/from various data sources - Samba, WebDAV, whatever else you can think of. I've tried most of them and they all suck.
I can already hear you saying, “why don't you just play back your music over the network using a network player”? In addition to the available apps sucking, network conditions on mobile are variable enough that streaming from a home server results in a generally poor experience. Industrial streaming services like Spotify have to go to extreme lengths to paper over the network enough to deliver a good experience. “Pinning” - where you stream but select specific items to keep locally on disk - doesn't really work for me because I don't want to choose what music to listen each time I anticipate a no-network scenario.
Anyway, as luck would have it, the best music player on iOS also has the best sync solution I've seen. The downside is that the companion program that runs on your server
However, it is surprisingly full featured and works very well. Since it's Windows only, I run it inside a windows VM on my hypervisor (which also hosts the file server with my music library). The VM has the music directory mounted from the file server via Samba.
The end result is that every time I open the foobar2000 app on iOS, any new music in my library downloads to my device. After that it's available for local playback. Since my phone is also on Tailscale, this works anywhere.
I used to do it a much more complicated way than depicted above because I had an Android phone with not enough storage to store my lossless music collection. Android meant I could run background file syncing utilities and not enough storage meant that I had to transcode my collection to something lossy in order to crunch it down small enough to fit on my phone. Buying an iPhone with 512gb of storage meant that 1) I lost the ability to run any kind of background syncing software because iOS doesn't really allow daemons to exist (unless Apple made them) and 2) I no longer needed to transcode as my music collection is only ~115gb which fits on my phone's internal storage. Thus all of the automatic cron jobs to do periodic transcoding and sync via syncthing etc are no more.
These are the unintelligible notes I took about how I used to do it, replete with ascii diagrams from a time before I caved a little bit on my static site elitism and just used mermaid:
1. Tx from source to local staging directory 2. `beet import` from staging directory into mounted remote share[0]
{ source }
|
.
--- (net) <1>
.
|
v
[ local:music ]
|
.
beet --- (net) <2>
import .
|
v
[ remote:music => local:remote/music]
3. cron job on remote periodically copies new files into a sync directory,
transcoding any lossless files to `-q6` ogg vorbis to reduce size[1]
[ remote:music ]
|
|
music-sync.sh | @ 2hr <3>
|
v
[ remote:music-sync ]
4. sync directory shared to all devices via syncthing[2]
[ remote:music-sync ]
|
|
.
syncthing --- (net) <4>
.
...
. . .
. . .
. . .
/ | \
v v v
phone laptop idk
[0] <https://beets.io/>
[2] <https://syncthing.net/>
#!/usr/bin/fish
set MUSICDIR "./music/"
set SYNCDIR "./music-sync"
for dir in (find "$MUSICDIR" -type d | cut -d'/' -f3-)
mkdir -p "$SYNCDIR/$dir"
end
for file in (find "$MUSICDIR" -type f -name '*.flac' -o -name '*.mp3' -o -name '*.ogg' | cut -d'/' -f3-)
set ifile (echo "$MUSICDIR/$file")
switch $file
case "*.flac"
set ofile (echo "$SYNCDIR/"(echo "$file" | sed "s/flac/ogg/"))
if test -e "$ofile"
echo "$ofile exists; skipping"
continue
end
echo ">> Transcoding '$ifile' to '$ofile'"
oggenc -q6 -o "$ofile" "$ifile"
case "*"
set ofile (echo "$SYNCDIR/$file")
if test -e "$ofile"
echo "$ofile exists; skipping"
continue
end
echo ">> Copying '$ifile' to $ofile"
cp "$ifile" "$ofile"
end
end
Except where otherwise noted, content on this wiki is licensed under the following license: CC Attribution-Noncommercial-Share Alike 4.0 International