feat: let a mood exclude tags, and widen the eighties one to the canon
Build and publish container / build (pull_request) Successful in 2m52s
Build and publish container / build (pull_request) Successful in 2m52s
The eighties mood selected on synth-specific tags only, on the grounds that the bare `80s` tag drags in Def Leppard and Bon Jovi. Checking the canon against live Last.fm pages shows what that costs. Eurythmics is tagged 80s, new wave, pop, female vocalists, synth pop -- four of those five are tags no mood may use. Frankie Goes to Hollywood is tagged 80s, new wave, pop, british, dance, and survives only on new wave. An act tagged purely 80s, pop and dance, which is a great deal of eighties pop, was missed outright. Two spellings also matter more than the census suggested. Of Depeche Mode, Duran Duran, Eurythmics and Frankie Goes to Hollywood, three carry "synth pop" with a space and only one carries "synthpop" without. The census could not show this because it only sees artists already in the library; the spelling that was kept was the rarer one. Add an `exclude` list to a mood. An excluded tag drops the artist outright rather than docking their score, which is the only construction that expresses "the eighties, but not the stadium rock" -- weighting cannot do it, because the tag it would weight is the one both share. The synth acts do not carry hard rock or hair metal, and that is the whole of the difference. A mood may not both select on and exclude the same tag; that is rejected when the definitions are read rather than silently producing nothing.
This commit is contained in:
@@ -1226,9 +1226,66 @@ def test_mood_names_are_unique():
|
||||
assert len(names) == len(set(names))
|
||||
|
||||
|
||||
def test_the_eighties_mood_does_not_lean_on_the_bare_decade_tag():
|
||||
"""In this library `80s` sits on Def Leppard and Bon Jovi, not on
|
||||
synthesisers. The hair-metal mood is where those belong."""
|
||||
ROCK_TAGS = {
|
||||
"classic rock", "hard rock", "blues rock", "southern rock", "arena rock",
|
||||
"glam rock", "hair metal", "glam metal", "heavy metal", "metal", "art rock",
|
||||
"psychedelic rock", "progressive rock", "rock and roll", "rock n roll",
|
||||
}
|
||||
|
||||
|
||||
def test_a_decade_tag_in_a_non_rock_mood_must_exclude_the_rock():
|
||||
"""`80s` sits on Def Leppard and Bon Jovi as heavily as on Eurythmics, so a
|
||||
mood that reaches for a decade without wanting rock has to say so. A mood
|
||||
that does want it -- classic-rock reaching for 70s -- is exempt."""
|
||||
for vibe in music_curator.DEFAULT_VIBES:
|
||||
tags = {tag.casefold() for tag in vibe["tags"]}
|
||||
decades = {"60s", "70s", "80s", "90s"} & tags
|
||||
if not decades or tags & ROCK_TAGS:
|
||||
continue
|
||||
excluded = {tag.casefold() for tag in vibe.get("exclude", [])}
|
||||
missing = {"hard rock", "hair metal"} - excluded
|
||||
assert not missing, f"{vibe['name']} selects on {decades} without excluding {missing}"
|
||||
|
||||
|
||||
def test_the_eighties_mood_covers_the_canon():
|
||||
"""Depeche Mode, Duran Duran, Eurythmics and Frankie Goes to Hollywood --
|
||||
checked against their live Last.fm tags. Three of the four carry "synth pop"
|
||||
with a space; only one carries it without."""
|
||||
synths = next(v for v in music_curator.DEFAULT_VIBES if v["name"] == "80s-synths")
|
||||
assert "80s" not in synths["tags"]
|
||||
tags = {t.casefold() for t in synths["tags"]}
|
||||
for artist_tags in (
|
||||
{"80s", "new wave", "pop", "female vocalists", "synth pop"}, # Eurythmics
|
||||
{"80s", "new wave", "pop", "british", "dance"}, # Frankie
|
||||
{"electronic", "synthpop", "new wave", "80s", "synth pop"}, # Depeche Mode
|
||||
{"new wave", "80s", "pop", "synth pop", "rock"}, # Duran Duran
|
||||
):
|
||||
assert tags & artist_tags, artist_tags
|
||||
assert synths["years"] == [1975, 1992]
|
||||
|
||||
|
||||
def test_an_excluded_tag_drops_the_artist(tmp_path):
|
||||
"""Weighting cannot separate the eighties synth acts from the eighties
|
||||
stadium rock, because the tag they would be weighted on is the one they
|
||||
share."""
|
||||
store, source, mirror = tagged_store(
|
||||
tmp_path,
|
||||
{
|
||||
"Played Band": [{"name": "80s", "count": 100}, {"name": "hard rock", "count": 90}],
|
||||
"Silent Band": [{"name": "80s", "count": 100}, {"name": "synth pop", "count": 90}],
|
||||
},
|
||||
)
|
||||
vibes = [{"name": "eighties", "tags": ["80s", "synth pop"], "exclude": ["hard rock"]}]
|
||||
|
||||
music_curator.build_vibe_playlists(store, vibes, mirror, str(source), 100, NOW)
|
||||
|
||||
written = (mirror / "_playlists" / "eighties.m3u").read_text()
|
||||
assert "Silent Band" in written
|
||||
assert "Played Band" not in written
|
||||
|
||||
|
||||
def test_a_vibe_cannot_both_select_and_exclude_a_tag(tmp_path):
|
||||
path = tmp_path / "vibes.json"
|
||||
path.write_text(json.dumps([{"name": "x", "tags": ["80s"], "exclude": ["80s"]}]))
|
||||
|
||||
with pytest.raises(ValueError, match="selects on and excludes"):
|
||||
music_curator.load_vibes(str(path))
|
||||
|
||||
Reference in New Issue
Block a user