Skip to content

matching

Classes and methods to easily compare the compatibility of a result with a song being matched. Uses fuzzy string comparison with the RapidFuzz package.

Matching is done individually on song name, title, primary artist, other artists, album name, and length - artist matches are calculated down to a single score value (scores go from 0 to 100). If the fields in one of the Song objects being compared are null, the corresponding match score will also be null.

The sum (i.e. total match score) is scaled based on how many match scores are null, so that it always falls on a range of 0 to 100.

MODULE DESCRIPTION
utils

Utility functions for the matching submodule.

CLASS DESCRIPTION
MatchQuality

Thresholds to consider when getting the quality of a match. Values are based on the sum of all matches - if

MatchResult

Holds match results and provides convenient property methods to get/calculate quality and match score.

MatchQuality

Bases: Enum

Thresholds to consider when getting the quality of a match. Values are based on the sum of all matches - if all are perfect, equals to 100.

PERFECT class-attribute instance-attribute

PERFECT = 100

Both songs are exactly the same.

GREAT class-attribute instance-attribute

GREAT = 90

Extremely likely songs are the same. Different platforms usually have small discrepancies in the matching value.

GOOD class-attribute instance-attribute

GOOD = 80

Likely a different version of the same song, like a live version for example.

MEDIOCRE class-attribute instance-attribute

MEDIOCRE = 60

Probably a cover from another artist or something else from the same artist.

BAD class-attribute instance-attribute

BAD = 0

Not the same song.

MatchResult dataclass

MatchResult(
    method: str,
    name_match: float | None,
    title_match: float | None,
    artists_match: list[Tuple[Artist, float]] | None,
    result_artists_matches: (
        list[Tuple[Artist, float]] | None
    ),
    album_match: float | None,
    length_match: float | None,
)

Holds match results and provides convenient property methods to get/calculate quality and match score.

ATTRIBUTE DESCRIPTION
method

Comparison method used, such as QRatio from RapidFuzz.

TYPE: str

name_match

Score for similarity in the song's names.

TYPE: float | None

title_match

Score for similarity in the song's titles, taken from the Song.title property.

TYPE: float | None

artists_match

Score for similarity in the original song's artists, compared against the result song's.

TYPE: list[Tuple[Artist, float]] | None

result_artists_matches

Score for similarity in the result song's artists, compared against the original song's.

TYPE: list[Tuple[Artist, float]] | None

album_match

Score for similarity in the song's albums.

TYPE: float | None

length_match

Score for similarity in the song's lengths. Score is scaled according to the equation \(y=1-(f*x^2)\), where \(f\) is an arbitrary "falloff" value.

TYPE: float | None

method instance-attribute

method: str

name_match instance-attribute

name_match: float | None

title_match instance-attribute

title_match: float | None

artists_match instance-attribute

artists_match: list[Tuple[Artist, float]] | None

result_artists_matches instance-attribute

result_artists_matches: list[Tuple[Artist, float]] | None

album_match instance-attribute

album_match: float | None

length_match instance-attribute

length_match: float | None

quality property

quality: MatchQuality

Returns the match quality from the enum MatchQuality based on the sum of points.

artists_match_avg property

artists_match_avg: float | None

Averages the match score of the list of artists. Returns zero if list is empty.

sum property

sum: float

Sums all matches (uses average artist match value). Result will be scaled to fall under 0 to 100.

all_above_threshold

all_above_threshold(threshold: float) -> bool

Checks if all the scores are above the threshold value given.

PARAMETER DESCRIPTION

threshold

Tha value that will be compared to all the values.

TYPE: float

RETURNS DESCRIPTION
bool

True if every match score is higher than the threshold, false otherwise.

Source code in src/downmixer/matching/__init__.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
@deprecated(
    "all_above_threshold is not supported anymore and will be removed in a future version"
)
def all_above_threshold(self, threshold: float) -> bool:
    """Checks if all the scores are above the threshold value given.

    Args:
        threshold (float): Tha value that will be compared to all the values.

    Returns:
        True if every match score is higher than the threshold, false otherwise.
    """
    name_test = self.name_match >= threshold
    artists_test = self.artists_match_avg >= threshold
    album_test = self.album_match >= threshold
    length_test = self.length_match >= threshold

    return name_test and artists_test and album_test and length_test

match

match(
    original_song: Song, result_song: Song
) -> MatchResult

Returns match values using RapidFuzz comparing the two given song objects.

PARAMETER DESCRIPTION

original_song

Song to be compared to. Should be slugified for better results.

TYPE: Song

result_song

Song being compared. Should be slugified for better results.

TYPE: Song

RETURNS DESCRIPTION
MatchResult

Match scores of the comparison between original and result song.

TYPE: MatchResult

Source code in src/downmixer/matching/__init__.py
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
def match(original_song: Song, result_song: Song) -> MatchResult:
    """Returns match values using RapidFuzz comparing the two given song objects.

    Args:
        original_song (Song): Song to be compared to. Should be slugified for better results.
        result_song (Song): Song being compared. Should be slugified for better results.

    Returns:
        MatchResult: Match scores of the comparison between original and result song.
    """
    song_slug = original_song.slug()
    result_slug = result_song.slug()

    name_match = _match_simple(song_slug.name, result_slug.name)
    title_match = _match_simple(song_slug.title, result_slug.title)
    artists_matches, result_artists_matches = _match_artist_list(song_slug, result_slug)
    if result_slug.album is not None and song_slug.album is not None:
        album_match = _match_simple(song_slug.album.name, result_slug.album.name)
    else:
        album_match = None
    length_match = _match_length(original_song.duration, result_song.duration)

    return MatchResult(
        method="QRatio",
        name_match=name_match,
        title_match=title_match,
        artists_match=artists_matches,
        result_artists_matches=result_artists_matches,
        album_match=album_match,
        length_match=length_match,
    )