omniverse theirix's Thoughts About Research and Development

Scrobbling to Last.fm offline

Turntable with a vinyl record Image by M. Johnson under CC BY 2.0

Imagine: you are sitting in a comfy chair near your stereo system, smelling the scent of a vinyl record spinning on your turntable, looking at the artwork, and listening to music. Is there something you might have forgotten to do?

It is not a problem at all since Last.fm allows you to scrobble using its API. That’s how your scrobblers for desktops and mobile devices work. So, I wrote a utility called offline-scrobbler to scrobble music to Last.fm for a given artist and album.

How to set it up

Install the utility. The simplest way to do it, if you have Cargo installed, is to run:

cargo install offline-scrobbler

GitHub releases contain Linux binaries. They are automatically built for Linux using musl to avoid problems with glibc linkage. Building macOS and Windows executables is a little more challenging. If anyone wants them, please ping me.

You need to create a Last.fm application account. Since this scrobbler is not a public service and it just runs on your machine, you need to create an account for yourself. Head to Last.fm, and log in as usual. Then go to Create API account page. Here, you need to enter your “Contact e-mail”, an arbitrary name to “Application name” field, and nothing more.

account step 1

On the next screen, you will be presented with unique account keys for this application. Write down the “API Key” and “Shared secret”.

account step 2

Provide the keys to the utility with the following call:

offline-scrobbler auth --api-key API_KEY --secret-key SHARED_SECRET

You are all set!

Scrobbling music

That’s why we are here, right?

I supported three typical scrobbling scenarios.

First of all, scrobble one song. It’s pretty easy:

offline-scrobbler scrobble --artist=Hooverphonic --track=Eden

Then, you can scrobble the whole album:

offline-scrobbler scrobble --artist=Hooverphonic --album="A New Stereophonic Sound Spectacular"

Finally, if you have a Last.fm album page open, just grab the URL and feed it to the scrobbler:

offline-scrobbler scrobble-url --url "https://www.last.fm/music/Hooverphonic/Blue+Wonder+Power+Milk"

Internals

Did I mention that the scrobbler is blazingly fast 🚀 because it is written in Rust 🦀 ? :)

The scrobbler relies on Last.fm artist and album names. So, it’s advisable to first run scrobbler in preview mode (--dryrun argument) to verify you’re scrobbling precisely the tracks you want. If there are any scrobble problems or tracks are ignored, they will be reported to the console.

I was surprised that Rust is missing a good scrobbler library. Anyway, it is a common problem. I wrote mine in Objective C while developing a desktop scrobbler for OS X a decade ago. The reqwest and serde simplify writing API clients a lot. Unfortunately, last.fm / audioscrobbler API is XML-based, so you have to write some boilerplate.

The core types for scrobbling logic are pretty simple:

 #[derive(Debug)]
 pub struct Track {
     pub title: String,
     pub duration: i64,
 }

 #[derive(Debug)]
 pub struct Album {
     pub title: String,
     pub tracks: Vec<Track>,
     pub url: Option<String>,
 }

All the error handling logic is nicely supported by anyhow (for propagating errors in CLI application) and thiserror (to model library error enum).

Writing CLI applications in Rust is a breeze. Combining clap, serde and env_logger is one of the finest ways to make a complicated CLI with parameter groups, validation, colour handling and other nice things. The only unfortunate thing I’ve noticed is that authors of clap v4 recently decided to unconditionally drop colour support in favour of more generic ANSI underlining. Let’s check the state of this issue.

Date and time

One curious thing I’ve discovered is that std library doesn’t provide formatters for std::time::Duration. It has Debug trait implementation, but the formatting is excluded explicitly because: “there are a variety of ways to format periods for human readability”. So, one should use an implementation from chrono or time. The time crate has a totally different Duration type apart from std, which is not so convenient. Finally, there are ongoing debates which external datetime library to use. Maintainers of chrono returned to support the crate, and one more library called jiff just arrived from BurntSushi, so the choice became even harder. The main downside of a well-supported and lightweight time crate for me is the lack of strftime-style format. It is so ubiquitous to use %H-%M-%S instead of

time.format(format_description!("[hour]:[minute]:[second]"))

To mimic normal human behaviour, I added pauses between tracks in the duration of each track. What’s more, you can scrobble into the past. Remember, you had been playing that album 6 hours ago? That is no problem - just specify an offset in human-readable form.

offline-scrobbler scrobble --artist=Hooverphonic --album="A New Stereophonic Sound Spectacular" --start=6h

Kudos to the great humantime library for this functionality.

Conclusion

My life has many entertaining activities, and music-related ones make up a significant part. I’ve worked on different tools to ease managing music libraries, encoding files and synchronizing with services. While building such tools, always remember that it could also be fun and rewarding.