Building My Music Library Database with Python and Mutagen

Introduction

Managing a large music library can be challenging, especially when files are scattered across folders and lack consistent metadata. I wanted a tool that could scan my music collection, extract metadata (artist, album, title, year, genre), and store it in a database for easy searching and categorization. This blog post documents my journey from idea to working solution using Python, Mutagen, and SQLite.

The Idea

I started with a simple goal: automate the process of cataloging my music files. I wanted to:

  • Recursively scan a directory for music files (MP3, FLAC, M4A)
  • Extract metadata from each file
  • Store the metadata in a SQLite database
  • Update the database if files change

A caveat is I am using plexmedia server to manage and play my music, so this script is primarily for my own organizational purposes, to learn, and to determine if I really want to pay for the plex pass for simply having a “smart” playlist generator. Why can’t I just do this myself?

Choosing the Tools

I gravitated to using python since there are so many tools available for processing files and that maybe someday I could access via a webapp. For metadata extraction, I found Mutagen, a powerful library that supports many audio formats. SQLite provided a lightweight, file-based database solution.

Writing the Code

1. Scanning Directories

I used Python’s os.walk to recursively scan the target directory and collect all files with allowed extensions:

for root, dirs, files in os.walk(music_dir):
    for f in files:
        if os.path.splitext(f)[1].lower() in ALLOWED_EXTENSIONS:
            music_files.append(os.path.join(root, f))

2. Extracting Metadata

Mutagen makes it easy to extract metadata. For MP3 files, I used mutagen.mp3.MP3, and for FLAC files, mutagen.flac.FLAC. I wrote a helper function to select the right class based on file extension:

def get_mutagen_class(file_path):
    ext = os.path.splitext(file_path)[1].lower()
    if ext == ".mp3":
        return MP3(file_path)
    elif ext == ".flac":
        return flac.FLAC(file_path)
    return None

Then, I extracted fields like artist, album, title, date, and genre:

def extract_metadata(file_path):
    audio = get_mutagen_class(file_path)
    if audio is None:
        return None, None, None, None, None
    artist = audio.get('artist', [None])[0] or None
    album = audio.get('album', [None])[0] or None
    title = audio.get('title', [None])[0] or None
    date = audio.get('date', [None])[0] or None
    genre = audio.get('genre', [None])[0] or None
    return artist, album, title, date, genre

3. Storing Metadata in SQLite

I created a songs table in music.db to store the metadata. For each file, I checked if it already existed in the database and updated or inserted as needed:

cursor.execute("SELECT * FROM songs WHERE file_path = ?", (file,))
existing_entry = cursor.fetchone()
if existing_entry:
    cursor.execute("UPDATE songs SET artist = ?, album = ?, title = ?, date = ?, genre = ? WHERE file_path = ?",
                   (metadata[0], metadata[1], metadata[2], metadata[3], metadata[4], file))
    db_conn.commit()
else:
    cursor.execute("INSERT INTO songs (artist, album, title, date, genre, file_path) VALUES (?, ?, ?, ?, ?, ?)",
                   (metadata[0], metadata[1], metadata[2], metadata[3], metadata[4], file))
    db_conn.commit()

4. Database Example

After running the script, my database was populated with entries like this:

4. Handling Edge Cases

I learned that not all files have complete metadata. For example, the year is often stored as the “date” tag, not “year”. I also added error handling to skip files that couldn’t be read.

Another edge case—which I haven’t addressed yet—was that the directories in the folders where I store all my music have system specific folders used for backup and recycle. So I will need to add code to skip those folders in the future.

Lessons Learned

  • Mutagen is flexible but metadata tags can vary between formats and files.
  • Always check for missing or unexpected tags.
  • SQLite is perfect for small, local databases.
  • Commenting code and writing helper functions makes future maintenance easier.

Conclusion and Future work

This project helped me organize my music library and taught me a lot about audio metadata and Python scripting. If you have a large collection of music files, consider automating your cataloging process—you’ll save time and discover hidden gems in your library!

For future improvements, I plan to:

  • Add support for more audio formats (e.g., OGG, WAV)
  • Assign genres and tags using my own ollama server
  • Create a simple web interface to browse and search the database
  • Create playlists based on metadata (e.g., all songs from a specific year or genre) where I can just say “Play me programming music” and it will play songs it considers good for programming based on the metadata where that metadata was assigned by my ollama server.



Posted

in

,

by

Tags: