v0.20.1: one fingerprint, six artists
trav's "Dionne Farris - I Know" kept identifying as Jay-Z, against ID3 tags that plainly said otherwise. AcoustID was right; the ranking threw the answer away. The fingerprint matched one AcoustID result at 0.97, and six recordings hang off it: Dionne Farris twice, plus Jay-Z, Marisela, New Atlantic and David Essex, all of whom recorded a song called "I Know". A result's score belongs to the *audio*, so every linked recording carries it however wrong the link is. With the scores tied, ranking fell through to the duration bucket, where Jay-Z's 222.7 s beat Dionne's 227.3 s against a 224 s file. The tags never got a vote: the hint sat below duration in the sort key. So the lookup now asks who submitted each link. `sources` joins LOOKUP_META — 475 people linked that audio to Dionne Farris, 6 to Jay-Z, 1 each to the rest — and _link_tier sinks anything under a tenth of the strongest link in the same result. The share is relative, never an absolute count, and a missing count ranks as real: an obscure song's true link may have two submissions against a stray's one, and rounds 45-46's payloads rank unchanged. And it asks what the file already says. artist_hint_for gathers the artist tag, the album artist and the artist in the filename; _artist_agreement counts the words shared with a candidate's credit, placeholders dropped. Like every hint since round 46 it only chooses among what AcoustID returned. New key order: stray tier, artist agreement, duration bucket, hint overlap, release rank — who, which take, which release. Artist above duration is the whole fix; duration still separates two takes by one artist. Verified live against the reported file: the proposal is now I Know — Dionne Farris — Wild Seed - Wild Flower (1994), track 1, with all five mis-tagged artists off the dropdown. The real response is pinned in tests/test_round52.py. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SKXUgsBBwe3qaHEjeV8ubP
This commit is contained in:
@@ -0,0 +1,227 @@
|
||||
"""Round 52 — Identify Track stops calling Dionne Farris "Jay-Z".
|
||||
|
||||
trav's "Dionne Farris - I Know [fqng9NDqKB8].mp3" kept being identified as
|
||||
Jay-Z's "I Know" from American Gangster, and AcoustID was not at fault. The
|
||||
fingerprint matched one AcoustID result at 0.97, and *six* recordings hang off
|
||||
that one result — Dionne Farris twice, plus Jay-Z, Marisela, New Atlantic and
|
||||
David Essex, all of whom recorded a song called "I Know". The score belongs to
|
||||
the audio, so all six carried 0.97, and ranking fell through to the duration
|
||||
bucket, where Jay-Z's 222.7 s beat Dionne's 227.3 s against a 224 s file. The
|
||||
tags said "dionne farris" the whole time.
|
||||
|
||||
Two things were missing. The lookup never asked for ``sources`` — how many
|
||||
people submitted each link, which is 475 for Dionne against 6 for Jay-Z and 1
|
||||
for the rest — and nothing compared the candidates' artist against the file's
|
||||
own. So the ranking now goes: strays below real links, then the artist the file
|
||||
names, then the take whose length matches, then the release the hint echoes.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from lintunes import fingerprint
|
||||
from lintunes.fingerprint import (
|
||||
artist_hint_for, lookup_fingerprint, parse_lookup,
|
||||
)
|
||||
|
||||
|
||||
class _Track:
|
||||
def __init__(self, name="", artist="", album="", album_artist="",
|
||||
location=""):
|
||||
self.track_id = 1
|
||||
self.name = name
|
||||
self.artist = artist
|
||||
self.album = album
|
||||
self.album_artist = album_artist
|
||||
self.location = location
|
||||
|
||||
|
||||
def _group(title, year, type_="Album", secondary=None):
|
||||
return {"title": title, "type": type_,
|
||||
"secondarytypes": list(secondary or []),
|
||||
"releases": [{"date": {"year": year} if year else {}}]}
|
||||
|
||||
|
||||
def _recording(title, artist, duration=None, sources=None, groups=()):
|
||||
rec = {"title": title, "artists": [{"name": artist}],
|
||||
"releasegroups": list(groups)}
|
||||
if duration is not None:
|
||||
rec["duration"] = duration
|
||||
if sources is not None:
|
||||
rec["sources"] = sources
|
||||
return rec
|
||||
|
||||
|
||||
def _payload(*recordings, score=0.9695):
|
||||
return {"status": "ok",
|
||||
"results": [{"score": score, "recordings": list(recordings)}]}
|
||||
|
||||
|
||||
# The real response, trimmed to the release groups that matter. Durations and
|
||||
# source counts are exactly what AcoustID returned for trav's file; the file
|
||||
# itself is 224 s, which is why Jay-Z used to win.
|
||||
def _i_know_payload():
|
||||
return _payload(
|
||||
_recording("I Know", "Marisela", duration=227.8, sources=1,
|
||||
groups=[_group("Salsa: Original Motion Picture Soundtrack",
|
||||
1988)]),
|
||||
_recording("I Know (NY reprise mix)", "Dionne Farris", duration=229.4,
|
||||
sources=11, groups=[_group("I Know", 1994, type_="Single")]),
|
||||
_recording("I Know", "Jay‐Z", duration=222.706, sources=6,
|
||||
groups=[_group("American Gangster", 2007)]),
|
||||
_recording("I Know", "New Atlantic", duration=238.986, sources=1,
|
||||
groups=[_group("Very Best of Back to the Old Skool", 2001,
|
||||
secondary=["Compilation"])]),
|
||||
_recording("I Know", "David Essex", duration=214.0, sources=1,
|
||||
groups=[_group("David Essex", 1974)]),
|
||||
_recording("I Know", "Dionne Farris", duration=227.266, sources=475,
|
||||
groups=[_group("Wild Seed - Wild Flower", 1994),
|
||||
_group("The Hangover Cure: Time to Chill", 1996,
|
||||
secondary=["Compilation"])]),
|
||||
)
|
||||
|
||||
|
||||
DIONNE_TRACK = _Track(
|
||||
name="I Know", artist="dionne farris", album="Wild Seed – Wild Flower",
|
||||
location="/m/dionne farris/Wild Seed – Wild Flower/"
|
||||
"Dionne Farris - I Know [fqng9NDqKB8].mp3")
|
||||
|
||||
|
||||
class TestTheDionneFarrisCase:
|
||||
def test_the_file_is_no_longer_told_it_is_jay_z(self):
|
||||
top = parse_lookup(_i_know_payload(), duration=224,
|
||||
hint=fingerprint.hint_for(DIONNE_TRACK),
|
||||
artist_hint=artist_hint_for(DIONNE_TRACK))[0]
|
||||
assert top.artist == "Dionne Farris"
|
||||
assert top.album == "Wild Seed - Wild Flower"
|
||||
assert top.year == 1994
|
||||
|
||||
def test_submissions_alone_settle_it_when_the_file_says_nothing(self):
|
||||
"""An untagged rip has no artist to agree with, and 475 links against
|
||||
6 still say whose song this is."""
|
||||
top = parse_lookup(_i_know_payload(), duration=224)[0]
|
||||
assert top.artist == "Dionne Farris"
|
||||
|
||||
def test_a_stray_link_cannot_confirm_a_wrong_tag(self):
|
||||
"""Tagged Jay-Z, so agreement points at the mis-tag — but 6 links
|
||||
against 475 is a stray, and strays rank below real links."""
|
||||
wrong = _Track(name="I Know", artist="Jay-Z", location="/m/x/y/z.mp3")
|
||||
top = parse_lookup(_i_know_payload(), duration=224,
|
||||
hint=fingerprint.hint_for(wrong),
|
||||
artist_hint=artist_hint_for(wrong))[0]
|
||||
assert top.artist == "Dionne Farris"
|
||||
|
||||
def test_a_closer_duration_no_longer_outranks_the_artist(self):
|
||||
"""The whole bug in one line: Jay-Z's take is the closest match by
|
||||
length, and it still must not win."""
|
||||
best_length = min(
|
||||
_i_know_payload()["results"][0]["recordings"],
|
||||
key=lambda r: abs(r["duration"] - 224))
|
||||
assert best_length["artists"][0]["name"] == "Jay‐Z"
|
||||
top = parse_lookup(_i_know_payload(), duration=224,
|
||||
artist_hint="Dionne Farris")[0]
|
||||
assert top.artist == "Dionne Farris"
|
||||
|
||||
|
||||
class TestContestedLinks:
|
||||
"""Two links people genuinely submitted, rather than one and a stray."""
|
||||
|
||||
def _contested(self):
|
||||
return _payload(
|
||||
_recording("Shadow", "The Loud Ones", duration=180.0, sources=20,
|
||||
groups=[_group("Loud Album", 2001)]),
|
||||
_recording("Shadow", "Quiet Hour", duration=180.0, sources=3,
|
||||
groups=[_group("Quiet Album", 2001)]),
|
||||
)
|
||||
|
||||
def test_the_files_own_tags_decide(self):
|
||||
top = parse_lookup(self._contested(), duration=180,
|
||||
artist_hint="Quiet Hour")[0]
|
||||
assert top.artist == "Quiet Hour"
|
||||
|
||||
def test_without_tags_the_stronger_link_leads(self):
|
||||
assert parse_lookup(self._contested(), duration=180)[0].artist == \
|
||||
"The Loud Ones"
|
||||
|
||||
def test_a_tenth_is_the_line_between_a_rival_and_a_stray(self):
|
||||
assert fingerprint._link_tier({"sources": 3}, 20) == 0
|
||||
assert fingerprint._link_tier({"sources": 1}, 20) == 1
|
||||
|
||||
|
||||
class TestNoEvidenceChangesNothing:
|
||||
def test_a_payload_without_sources_ranks_as_before(self):
|
||||
"""Older responses (and every canned payload in rounds 45–46) carry no
|
||||
counts, so nothing may be demoted for lacking them."""
|
||||
payload = _payload(
|
||||
_recording("Snowfall", "The Band", duration=155.0,
|
||||
groups=[_group("The Long Take", 2000)]),
|
||||
_recording("Snowfall", "The Band", duration=150.3,
|
||||
groups=[_group("The Short Take", 2000)]),
|
||||
)
|
||||
assert parse_lookup(payload, duration=150)[0].album == "The Short Take"
|
||||
|
||||
def test_a_result_where_nobody_reported_counts_demotes_nothing(self):
|
||||
assert fingerprint._link_tier({}, 0) == 0
|
||||
assert fingerprint._link_tier({"sources": 1}, 0) == 0
|
||||
|
||||
def test_a_placeholder_artist_agrees_with_nothing(self):
|
||||
track = _Track(artist="Unknown Artist",
|
||||
location="/m/Unknown Artist/Unknown Album/blurf.mp3")
|
||||
tokens = fingerprint._artist_tokens(artist_hint_for(track))
|
||||
assert tokens == set()
|
||||
|
||||
def test_duration_still_separates_takes_by_one_artist(self):
|
||||
"""Artist agreement ties between two takes by the same person, so
|
||||
length decides — the round 46 rule, intact."""
|
||||
payload = _payload(
|
||||
_recording("Snowfall", "Ahmad Jamal", duration=155.0, sources=50,
|
||||
groups=[_group("The Long Take", 2000)]),
|
||||
_recording("Snowfall", "Ahmad Jamal", duration=150.3, sources=50,
|
||||
groups=[_group("The Short Take", 2000)]),
|
||||
)
|
||||
assert parse_lookup(payload, duration=150,
|
||||
artist_hint="Ahmad Jamal")[0].album == \
|
||||
"The Short Take"
|
||||
|
||||
|
||||
class TestArtistHint:
|
||||
def test_it_gathers_tags_and_the_filename(self):
|
||||
track = _Track(artist="dionne farris", album_artist="Dionne Farris",
|
||||
location="/m/x/y/Dionne Farris - I Know [fqng9NDqKB8].mp3")
|
||||
assert artist_hint_for(track).lower().count("dionne") == 3
|
||||
|
||||
def test_a_track_with_no_artist_anywhere_hints_nothing(self):
|
||||
assert artist_hint_for(_Track()) == ""
|
||||
|
||||
def test_the_title_is_not_part_of_it(self):
|
||||
"""Only the artist: every candidate here is titled "I Know", so title
|
||||
words separate nothing and would just dilute the agreement count."""
|
||||
assert "know" not in artist_hint_for(DIONNE_TRACK).lower()
|
||||
|
||||
|
||||
class TestTheLookupAsksForSources:
|
||||
def test_meta_requests_submission_counts(self, monkeypatch):
|
||||
sent = {}
|
||||
|
||||
class FakeResponse:
|
||||
def raise_for_status(self): pass
|
||||
def json(self): return _i_know_payload()
|
||||
|
||||
import requests
|
||||
monkeypatch.setattr(requests, "post",
|
||||
lambda *a, **k: sent.update(k["data"])
|
||||
or FakeResponse())
|
||||
candidates = lookup_fingerprint("key", 224, "fp",
|
||||
hint="I Know dionne farris",
|
||||
artist_hint="dionne farris")
|
||||
assert "sources" in sent["meta"]
|
||||
assert candidates[0].artist == "Dionne Farris"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("artist, expected", [
|
||||
("Dionne Farris", 2),
|
||||
("Jay-Z", 0),
|
||||
("", 0),
|
||||
])
|
||||
def test_agreement_counts_shared_artist_words(artist, expected):
|
||||
candidate = fingerprint.IdentifyCandidate(score=1.0, artist="Dionne Farris")
|
||||
tokens = fingerprint._artist_tokens(artist)
|
||||
assert fingerprint._artist_agreement(tokens, candidate) == expected
|
||||
Reference in New Issue
Block a user