Skip to main content

Overview

The MusicBrainzClient class provides methods to search and retrieve music metadata from the MusicBrainz database. It includes automatic retry logic, rate limiting, and intelligent title matching.
All methods are class methods (@classmethod) and can be called directly without instantiating the class.

Configuration

The client is pre-configured with:
  • User Agent: “TagQt/1.0”
  • Rate Limiting: 1 request per second (respects MusicBrainz rate limits)
  • Auto-retry: Up to 3 attempts on network errors with exponential backoff

Methods

normalize_title()

Normalizes a title string for fuzzy matching by removing accents, punctuation, and extra formatting.
title
str
required
Title string to normalize
from tagqt.core.musicbrainz import MusicBrainzClient

normalized = MusicBrainzClient.normalize_title("Song (Remix) [2024]")
print(normalized)  # "song remix"
return
str
Normalized title with:
  • Accents removed (NFKD normalization)
  • Parentheses and brackets content removed
  • Punctuation and special characters removed
  • Lowercase
  • Extra whitespace collapsed
Normalization steps:
  1. Unicode NFKD normalization
  2. Remove trailing parentheses: (...) and brackets: [...]
  3. Remove quotes/apostrophes: ', ', `
  4. Remove all non-alphanumeric characters except spaces
  5. Convert to lowercase and collapse whitespace

titles_match()

Fuzzy title matching using normalization. Returns True if titles are considered equivalent.
title1
str
required
First title to compare
title2
str
required
Second title to compare
result = MusicBrainzClient.titles_match(
    "Song Title (2024 Remix)",
    "Song Title"
)
print(result)  # True

result = MusicBrainzClient.titles_match(
    "Complete Different Song",
    "Another Song"
)
print(result)  # False
return
bool
True if titles match exactly or one contains the other after normalization
Matching logic:
  1. Exact match after normalization
  2. Substring match (one normalized title contains the other)

search_release()

Searches for album releases matching the given artist and album name. Returns the best matching release with metadata.
artist
str
required
Artist name to search for
album
str
required
Album/release name to search for
track_title
str
default:"None"
Optional track title for future use (currently not used in search)
release = MusicBrainzClient.search_release(
    artist="Radiohead",
    album="OK Computer"
)

if release:
    print(f"Title: {release['title']}")
    print(f"Artist: {release['artist']}")
    print(f"Year: {release.get('year', 'N/A')}")
    print(f"Genres: {', '.join(release['genres'])}")
    print(f"Release ID: {release['id']}")
return
dict | None
Dictionary containing release metadata, or None if no results found or error occurred.Response fields:
  • id (str): MusicBrainz release ID (UUID)
  • title (str): Album title
  • artist (str): Primary artist name
  • artist_id (str): MusicBrainz artist ID
  • date (str): Full release date (YYYY-MM-DD)
  • year (str): Release year (first 4 characters of date)
  • country (str): Release country code
  • status (str): Release status (e.g., “Official”)
  • genres (list[str]): Up to 3 top genres (from release, release group, or artist)
  • release_group_id (str): MusicBrainz release group ID
  • disc_count (int): Number of discs (default: 1)
  • track_disc (None): Track disc number (only set by lookup_release())
  • track_position (None): Track position (only set by lookup_release())
  • track_count (None): Total tracks (only set by lookup_release())
Search behavior:
  • Searches up to 10 releases
  • Prioritizes “Official” releases over other statuses
  • Returns the first official release, or first result if no official releases found
  • Attempts to fetch genres from release tags, then release group, then artist

lookup_release()

Fetches detailed release information by MusicBrainz release ID, including track positions and genre data.
release_id
str
required
MusicBrainz release ID (UUID)
track_title
str
default:"None"
Optional track title to find disc/track position within the release
# Lookup release without track matching
details = MusicBrainzClient.lookup_release(
    release_id="6a09041b-0f79-3278-88d9-0552dd28d37f"
)

if details:
    print(f"Disc Count: {details['disc_count']}")
    print(f"Genres: {', '.join(details['genres'])}")

# Lookup with track title to find position
details = MusicBrainzClient.lookup_release(
    release_id="6a09041b-0f79-3278-88d9-0552dd28d37f",
    track_title="Paranoid Android"
)

if details:
    print(f"Track: {details['track_position']}/{details['track_count']}")
    print(f"Disc: {details['track_disc']}/{details['disc_count']}")
return
dict | None
Dictionary containing detailed release information, or None on error.Response fields:
  • genres (list[str]): Up to 3 top genres (prioritizes recording-level genres)
  • disc_count (int): Total number of discs/media in the release
  • release_group_id (str): MusicBrainz release group ID
  • track_disc (int | None): Disc number containing the track (if track_title provided and found)
  • track_position (int | None): Track position on disc (if found)
  • track_count (int | None): Total tracks on the disc (if found)
Track matching:
  • Uses fuzzy matching via titles_match() to find the track
  • Searches all discs sequentially until a match is found
  • Prioritizes recording-level genres over release-level genres when track is found

lookup_release_group()

Fetches genre tags for a MusicBrainz release group.
rg_id
str
required
MusicBrainz release group ID (UUID)
genres = MusicBrainzClient.lookup_release_group(
    rg_id="b1392450-e666-3926-a536-22c65f834433"
)

print(f"Genres: {', '.join(genres)}")
return
list[str]
List of up to 3 top genre names, sorted by tag count (most popular first). Returns empty list on error or if no tags found.

lookup_artist()

Fetches genre tags for a MusicBrainz artist.
artist_id
str
required
MusicBrainz artist ID (UUID)
genres = MusicBrainzClient.lookup_artist(
    artist_id="a74b1b7f-71a5-4011-9441-d0b5e4122711"
)

print(f"Artist Genres: {', '.join(genres)}")
return
list[str]
List of up to 3 top genre names, sorted by tag count. Returns empty list on error or if no tags found.

Complete Example

from tagqt.core.musicbrainz import MusicBrainzClient
from tagqt.core.tags import MetadataHandler

# Load audio file
handler = MetadataHandler("/music/song.mp3")

# Search for release
release = MusicBrainzClient.search_release(
    artist=handler.artist or "Radiohead",
    album=handler.album or "OK Computer"
)

if not release:
    print("No release found")
else:
    # Get detailed track information
    details = MusicBrainzClient.lookup_release(
        release_id=release['id'],
        track_title=handler.title
    )
    
    # Update metadata with MusicBrainz data
    handler.album = release['title']
    handler.artist = release['artist']
    handler.year = release.get('year', '')
    
    if release['genres']:
        handler.genre = release['genres'][0]  # Use top genre
    
    if details and details['track_position']:
        handler.track_number = str(details['track_position'])
        handler.track_total = str(details['track_count'])
        handler.disc_number = str(details['track_disc'])
    
    handler.save()
    print("Metadata updated from MusicBrainz!")

Error Handling

The client includes robust error handling:
  • Network Errors: Automatic retry with exponential backoff (2^attempt seconds)
  • API Errors: Caught and logged, returns None or empty list
  • Rate Limiting: Automatically enforced at 1 request/second
# Methods return None or [] on failure
release = MusicBrainzClient.search_release(
    artist="Invalid",
    album="NonexistentAlbum"
)

if release is None:
    print("Search failed or no results")

Best Practices

  1. Check return values: Always verify results are not None before accessing fields
  2. Use track matching: Pass track_title to lookup_release() for accurate track positions
  3. Genre fallback: The client automatically tries release → release group → artist for genres
  4. Respect rate limits: The client handles this automatically, but avoid rapid successive calls

API Dependencies

Requires the musicbrainzngs Python package:
pip install musicbrainzngs

Build docs developers (and LLMs) love