Introducing Beets-audible: a Beets plugion for managing audiobooks

I am a huge fan of audiobooks, and they are my favorite way of consuming fiction.

For the last two months, I have spent much of my spare time on developing Beets-audible, a Beets plugin for managing audiobook collections. This is my first side-project that has attracted interest from others in the form of bug reports or pull requests. In this post, I will talk about the motivation for this side-project, the technical challenges that I encountered and the hack that made it possible.

Motivation

Audible is the dominant player in the audiobook space. However, using the Audible app to access audiobooks has some significant downsides; primarily not having control over purchased content and content potentially disappearing due to licensing issues.

Hence, I started looking for solutions to selfhost my audiobook collection and stream them from my iPhone. I settled on selfhosting a Plex instance and using the Prologue app to access my audiobook collection. However, this setup requires audiobooks to be accurately tagged to show book metadata. This is also the case if using other solutions like Audiobookshelf or Booksonic. Here is an example of how ID3 tags map to book metadata.

ID3 Tag Book Data
TALB (ALBUM) Title
TIT3 (SUBTITLE) Subtitle
TPE1 (ARTIST) Author, Narrator
TPE2 (ALBUMARTIST) Author
TCOM (COMPOSER) Narrator
COMM or desc for m4b files (COMMENT) Publisher's Summary
ASIN (ASIN) Amazon Standard Identification Number

Previously, the best way of tagging audiobook files was manually importing each book into Mp3tag after configuring it to scrape book metadata from Audible. However, this relies on Mp3tag, an application that only works on Windows. Additionally, Mp3tag is also difficult to automate as it does not run on the command line.

Extending Beets to Support Audiobooks

Beets is an incredible cross-platform command-line tool for managing music collections. It scans imported music and looks up track metadata from various online sources. Beets uses this metadata to correct inconsistencies on imported files such as typos or filling in missing tags.

Beets can be extended by writing plugins. Since I was already using Beets to manage my music collection, I wondered if I could write a plugin that uses Audible as the source of book metadata.

This is how the plugin works:

  1. When importing audiobook files, use the album and artist ID3 tags to determine the title and author, respectively.
  2. Call Audible's search endpoint with the author and title.
  3. For each result, call a separate endpoint to get the list of chapters in that book.
  4. Return book results as album objects and populate the track list with the list of chapters. A chapter corresponds to one track. Beets uses these results to tag the imported files.

Beets is designed to work on music. Hence, it expects a one-to-one mapping between each track of an album and a file on disk.

The plugin worked well when an audiobook is chapterized, meaning that it consists of a folder where each file is one chapter. However, it did not work for the following scenarios where the number of files differs from the number of chapters:

  • The audiobook consists of a single m4b file with chapter markers. This is the preferred format for storing audiobooks as copying a single file is much faster than copying many smaller files.
  • The audiobook is ripped from physical disks.

My initial idea to solve this problem was to refactor Beets so that it does not assume a one-to-one correspondence between tracks and files. However, this would have been a time-consuming and complex undertaking.

I also tried not returning chapter info (i.e., returning an empty track list), but Beets considers albums with zero tracks to be an invalid result.

Telling White Lies

I realized that when Beets invokes the plugin, the function is passed info about the files being imported. This means that using the number of files being imported, the plugin can check if it is chapterized based on whether the number of files matches the number of chapters for a book result.

If the book being imported is not chapterized, ignore the chapter data for that book. Instead, tell a white lie. If the book being imported consists of a single file, then the plugin would return a single track. Likewise, if the imported audiobook consists of 10, 20 or 30 files.

This is a hack in every sense of the word. It means that for an m4b audiobook file with chapter markers, Beets would not be able to correct any inconsistencies in the chapter title. This is something I can live with because it allows Beets to work with non-chapterized audiobooks without a massive Beets refactor.

Try It Out!

I would highly recommend using Beets to manage your music collection if you're not already doing so.

If you need to manage an audiobook collection, I believe Beets-audible is the best way to do so. Its open-source, cross-platform and works well when bulk importing multiple books.