Tags: madeof:bits
Some time ago I installed minidlna on our media server: it was pretty easy to do, but quite limited in its support for the formats I use most, so I ended up using other solutions such as mounting the directory with sshfs.
Now, doing that from a phone, even a pinephone running debian, may not be as convenient as doing it from the laptop where I already have my ssh key :D and I needed to listed to music from the pinephone.
So, in anger, I decided to configure a web server to serve the files.
I installed lighttpd because I already had a role for this kind of
configuration in my ansible directory, and configured it to serve the
relevant directory in /etc/lighttpd/conf-available/20-music.conf
:
$HTTP["host"] =~ "music.example.org" {
server.name = "music.example.org"
server.document-root = "/path/to/music"
}
the domain was already configured in my local dns (since everything is
only available to the local network), and I enabled both
20-music.conf
and 10-dir-listing.conf
.
And. That’s it. It works. I can play my CD rips on a single flac exactly in the same way as I was used to (by ssh-ing to the media server and using alsaplayer).
Then this evening I was talking to normal people1, and they mentioned that they wouldn’t mind being able to skip tracks and fancy things like those :D and I’ve found one possible improvement.
For the directories with the generated single-track ogg files I’ve
added some playlists with the command ls *.ogg > playlist.m3u
, then
in the directory above I’ve run ls */*.m3u > playlist.m3u
and that
also works.
With vlc I can now open http://music.example.org/band/album/playlist.m3u to listen to an album that I have in ogg, being able to move between tracks, or I can open http://music.example.org/band/playlist.m3u and in the playlist view I can browse between the different albums.
Left as an exercise to the reader2 are writing a bash script to generate all of the playlist.m3u files (and running it via some git hook when the files change) or writing a php script to generate them on the fly.
Update 2025-01-10: another reader3 wrote the php script and has authorized me to post it here.
<?php
define("MUSIC_FOLDER", __DIR__);
define("ID3v2", false);
function dd() {
echo "<pre>"; call_user_func_array("var_dump", func_get_args());
die();
}
function getinfo($file) {
$cmd = 'id3info "' . MUSIC_FOLDER . "/" . $file . '"';
exec($cmd, $output);
$res = [];
foreach($output as $line) {
if (str_starts_with($line, "=== ")) {
$key = explode(" ", $line)[1];
$val = end(explode(": ", $line, 2));
$res[$key] = $val;
}
}if (isset($res['TPE1']) || isset($res['TIT2']))
echo "#EXTINF: , " . ($res['TPE1'] ?? "Unk") . " - " . ($res['TIT2'] ?? "Untl") . "\r\n";
if (isset($res['TALB']))
echo "#EXTALB: " . $res['TALB'] . "\r\n";
}
function pathencode($path, $name) {
$path = urlencode($path);
$path = str_replace("%2F", "/", $path);
$name = urlencode($name);
if ($path != "") $path = "/" . $path;
return $path . "/" . $name;
}
function serve_playlist($path) {
echo "#EXTM3U";
echo "# PATH: $path\n\r";
foreach (glob(MUSIC_FOLDER . "/$path/*") as $filename) {
$name = basename($filename);
if (is_dir($filename)) {
echo pathencode($path, $name) . ".m3u\r\n";
}$t = explode(".", $filename);
$ext = array_pop($t);
if (in_array($ext, ["mp3", "ogg", "flac", "mp4", "m4a"])) {
if (ID3v2) {
$path . "/" . $name);
getinfo(else {
} echo "#EXTINF: , " . $path . "/" . $name . "\r\n";
}echo pathencode($path, $name) . "\r\n";
}
}die();
}
$path = $_SERVER["REQUEST_URI"];
$path = urldecode($path);
$path = trim($path, "/");
if (str_ends_with($path, ".m3u")) {
$path = str_replace(".m3u", "", $path);
$path);
serve_playlist(
}
$path = MUSIC_FOLDER . "/" . $path;
if (file_exists($path) && is_file($path)) {
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($path));
readfile($path);
}
It’s php, so I assume no responsability for it :D