Abstract
I use a podcast player to queue up media from my computer to watch on my phone. I've recently written a new CLI tool to streamline my process.
Like many of us, I have an archive of various media - audio and video - that I keep on my computer. I also have a smartphone which is a great device to consume this media. As an example, say I have an audiobook. It's nice to archive those files on my computer, for cheap mass storage and easy backups. But it's also nice to listen to the book while I'm in the car or doing chores, when I have my phone with me.
For a few years now, the solution that I've used to this dilemma is what I call my "Localhost Podcast". I've been using a few different programs tied together with a bash script to create an RSS feed for the media, start a local web server, and download it into the podcast player on my phone. It's been working reasonably well, except for a few niggles.
I was in the mood for a small project, and so I've put my whole end to end flow into a single new Rust program. As a bonus, I could fix the rough edges which were annoying me.
I've called my new CLI tool Localhost Podcast, and it's available on Crates.io, with code available on SourceHut.
What does this CLI app do?
I have a directory on my computer which is my podcast directory. What that really
means is that it has a localhost-podcast.toml file describing my various
feeds.
This is what my config looks like:
host = "192.168.0.12"
port = 9090
["Localhost Books"]
description = "Audiobooks from Justin's collection"
image = "books/thumb.jpg"
dir = "books"
extensions = ["mp3", "ogg"]
out = "books/rss.xml"
["Localhost Cool Tunes"]
description = "Only the coolest tunes from Justin's collection"
image = "music/thumb.png"
dir = "music"
extensions = ["mp3", "ogg"]
out = "music/rss.xml"
["Localhost Movies"]
description = "The best video content from Justin's collection"
image = "movies/thumb.jpg"
dir = "movies"
extensions = ["mp4", "mkv", "avi"]
out = "movies/rss.xml"
When I want to send an audiobook, music album, or movie to my phone, I copy its
files to the relevant subdirectory, ./books/, ./music/ or ./movies/.
When I run localhost-podcast in the directory with the config file, it will
start by reading localhost-podcast.toml for all of its settings. Then, for
each feed described in the config file, it will find any matching files and put
them into an rss.xml file.
Some nice high level features in building the feed:
It will order files using natural ordering. This means that numbers are treated as if they were a single character when sorting, so that
2nd-track.oggis sorted before10th-track.ogg, even though10would appear before2if they were naively sorted alphabetically.Audio files have their metadata scraped, so that the title is listed correctly, and the description is the artist and album. If it can't figure out the title, it will fall back to the filename.
After the RSS files are built, it will immediately start a web server using the same port listed in the config.
All together this means that I only need to drop the media files into right
directories and run localhost-podcast, and I'm ready to download the new
tracks onto my phone.
Other interesting implementation anecdotes
Audio file metadata is more complicated than I thought
I don't know why, but I had this expectation that reading the metadata from audio files would be relatively simple. After all, audio players, file explorers, and tag management software like Picard all makes it look like this is the same for every audio file. What I hadn't though about is that each audio container stores the metadata in its own way.
I ended up settling on a library called Symphonia for reading the audio metadata. It's primarily a library for reading the actual audio, not just the metadata, but it had the best support I found for many different audio formats in one crate. It also handles having any file thrown at it and it figures out if the file is in any of the formats that it supports.
Theoretically it would be nice to have something similar for video, but audio was my primary concern for now.
Natural ordering and Unicode
Another area that things go deep and I feel I only scratched the surface is in
the natural ordering of filenames. I basically only support the ASCII base 10
digits, 0-9. That is perfectly good for my personal use case.
The problem is that those aren't the only numeric characters. If you consider the full set of Unicode, there are many other numeric characters, for example Japanese numerals. Should these be taken into account when doing natural ordering? I don't know! Since I don't personally use those characters, I don't have a good intuition on how they should be sorted, so it's not simple for me to implement myself without a research dive.
SourceHut
I've been eyeing SourceHut for a while as a different forge to host my open source software. I like having my own Git server to control my own data, but it is limited. I don't want to let anyone I don't know have write access to anything on that server, which means that most collaboration tools like issue reports or pull requests can't work. Also, I don't have it linked to any CI server.
Many forges are kind of the same. For the most part, GitLab and Gitea look like they're trying to copy GitHub's homework. I also use Codeberg, which also looks just like GitHub but has the advantage that it is explicitly run as a non-profit to support free software, which avoids some of the sins of GitHub. SourceHut looks like it is trying to do something a bit different.
I'm not going to go into details on SourceHut here, except to say that this Localhost Podcast CLI was partly a way for me to try out SourceHut. Since I did it all alone I didn't use all of their collaboration features, but I was pleasantly surprised by the parts I did interact with.
I'd like to specifically call out their CI server. One fantastic feature that I would love to see from other CI products is that I can drop a potential config file into the web UI to trigger a build without committing. This meant that I could go through the typical trial and error of setting up CI without the many little meaningless Git commits. And when I got the config wrong and the build failed, it had a seamless option to SSH into the failed build environment to troubleshoot.
I'm considering using SourceHut further, while still mirroring any SourceHut repos to my own Git server so that it is included in my backups.
Wrapping up
At this point the localhost-podcast CLI matches my use case. I've called the
current version 1.0.0, and I'm actively using it myself and enjoying it.
I hope that it can bring values to others too!