Skip to content

Commit

Permalink
Merge pull request #111 from metabrainz/mode-ranges
Browse files Browse the repository at this point in the history
Improve tag radio, add troi.py, use similar tags, other tag improvements
  • Loading branch information
mayhem committed Sep 18, 2023
2 parents 3c424ea + 9d08aaf commit 661232b
Show file tree
Hide file tree
Showing 8 changed files with 178 additions and 149 deletions.
16 changes: 8 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ below.
virtualenv -p python3 .ve
source .ve/bin/activate
pip3 install -r requirements.txt -r requirements_test.txt
python3 -m troi.cli --help
python3 troi.py --help
```

**Windows**
Expand All @@ -48,22 +48,22 @@ python3 -m troi.cli --help
virtualenv -p python .ve
.ve\Scripts\activate.bat
pip install -r requirements.txt -r requirements_test.txt
python -m troi.cli --help
python troi.py --help
```

## Basic commands

List available patches:

python -m troi.cli list
python troi.py list

Generate a playlist using a patch:

python -m troi.cli playlist --print [patch-name]
python troi.py playlist --print [patch-name]

If the patch requires arguments, running it with no arguments will print a usage statement, e.g.

$ python -m troi.cli playlist --print area-random-recordings
$ python troi.py playlist --print area-random-recordings
Usage: area-random-recordings [OPTIONS] AREA START_YEAR END_YEAR

Generate a list of random recordings from a given area.
Expand All @@ -78,9 +78,9 @@ If the patch requires arguments, running it with no arguments will print a usage
## Running tests

```
python3 -m troi.cli test
python3 -m troi.cli test -v
python3 -m troi.cli test -v <file to test>
python3 troi.py test
python3 troi.py test -v
python3 troi.py test -v <file to test>
```

## Building Documentation
Expand Down
5 changes: 3 additions & 2 deletions docs/lb_radio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ All terms have the following options:
#. **medium**: Use medium mode for this term.
#. **hard**: Use hard mode for this term.

For artist terms, the following option applies:
For artist and tag terms, the following option applies:

#. **nosim**: Do not add similar artists, only output recordings from the given artist.
#. **nosim**: Do not add similar artists/tags, only output recordings from the given artist/tag.

For tag queries, the following options exist:

#. **and**: For a tag query, if "and" is specified (the default) recordings will be chosen if all the given tags are applied to that recording.
#. **or**: For a tag query, if "or" is specified, then recordings will be chosen if any of the tags are applied to the recording.
#. **nosim**: Tag queries on medium and hard mode may include similar tags. Specifying nosim for a tag query ensures that no similar tags are used.

For the stats term, the following options apply:

Expand Down
8 changes: 8 additions & 0 deletions troi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env python3

import sys
from troi.cli import cli

if __name__ == "__main__":
cli()
sys.exit(0)
13 changes: 12 additions & 1 deletion troi/patches/lb_radio.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from troi.patches.lb_radio_classes.stats import LBRadioStatsRecordingElement
from troi.patches.lb_radio_classes.recs import LBRadioRecommendationRecordingElement
from troi import TARGET_NUMBER_OF_RECORDINGS, Playlist
from troi.utils import interleave


class LBRadioPatch(troi.patch.Patch):
Expand Down Expand Up @@ -130,12 +131,22 @@ def create(self, inputs):
if "hard" in element["opts"]:
mode = "hard"

# Determine percent ranges based on mode -- this will likely need further tweaking
if mode == "easy":
start, stop = 0, 33
elif self.mode == "medium":
start, stop = 33, 66
else:
start, stop = 66, 100
self.local_storage["modes"] = {"easy": (0, 33), "medium": (33, 66), "hard": (66, 100)}

if element["entity"] == "artist":
include_sim = False if "nosim" in element["opts"] else True
source = LBRadioArtistRecordingElement(element["values"][0], mode=mode, include_similar_artists=include_sim)

if element["entity"] == "tag":
source = LBRadioTagRecordingElement(element["values"], mode=mode, operator="and")
include_sim = False if "nosim" in element["opts"] else True
source = LBRadioTagRecordingElement(element["values"], mode=mode, operator="and", include_similar_tags=include_sim)

if element["entity"] == "tag-or":
source = LBRadioTagRecordingElement(element["values"], mode=mode, operator="or")
Expand Down
54 changes: 30 additions & 24 deletions troi/patches/lb_radio_classes/artist.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from troi import Recording, Artist
from troi.splitter import plist
from troi import TARGET_NUMBER_OF_RECORDINGS
from troi.utils import interleave

OVERHYPED_SIMILAR_ARTISTS = [
"b10bbbfc-cf9e-42e0-be17-e2c3e1d2600d", # The Beatles
Expand All @@ -16,13 +17,10 @@
"5b11f4ce-a62d-471e-81fc-a69a8278c7da", # Nirvana
"f59c5520-5f46-4d2c-b2c4-822eabf53419", # Linkin Park
"cc0b7089-c08d-4c10-b6b0-873582c17fd6", # System of a Down
"ebfc1398-8d96-47e3-82c3-f782abcdb13d", # Beach boys
]


def interleave(lists):
return [val for tup in zip(*lists) for val in tup]


class LBRadioArtistRecordingElement(troi.Element):
"""
Given an artist, find its similar artists and their popular tracks and return one
Expand Down Expand Up @@ -109,22 +107,26 @@ def fetch_artist_names(self, artist_mbids):

def read(self, entities):

# Fetch our mode ranges
start, stop = self.local_storage["modes"][self.mode]

self.data_cache = self.local_storage["data_cache"]
artists = [{"mbid": self.artist_mbid}]

# First, fetch similar artists if the user didn't override that.
if self.include_similar_artists:
# Fetch similar artists for original artist
similar_artists = self.get_similar_artists(self.artist_mbid)
if len(similar_artists) == 0:
raise RuntimeError("Not enough similar artist data available for artist %s. Please choose a different artist." %
self.artist_name)
# if len(similar_artists) == 0:
# raise RuntimeError(f"Not enough similar artist data available for artist {self.artist_name}. Please choose a different artist.")

# Verify and lookup artist mbids
for artist in similar_artists[:self.MAX_NUM_SIMILAR_ARTISTS]:
# select artists
for artist in similar_artists[start:stop]:
artists.append({"mbid": artist["artist_mbid"]})
if len(artists) >= self.MAX_NUM_SIMILAR_ARTISTS:
break

# For all fetched artists, fetcht their names
# For all fetched artists, fetch their names
artist_names = self.fetch_artist_names([i["mbid"] for i in artists])
for artist in artists:
if artist["mbid"] not in artist_names:
Expand All @@ -136,23 +138,23 @@ def read(self, entities):
self.data_cache[artist["mbid"]] = artist["name"]

# start crafting user feedback messages
msg = "artist: using seed artist %s" % artists[0]["name"]
if self.include_similar_artists:
msg += " and similar artists: " + ", ".join([a["name"] for a in artists[1:]])
msgs = []
if self.include_similar_artists and len(artists) == 1:
msgs.append(f"Seed artist {artist_names[self.artist_mbid]} no similar artists.")
else:
msg += " only"

self.local_storage["user_feedback"].append(msg)
if self.include_similar_artists and len(artists) < 4:
msgs.append(f"Seed artist {artist_names[self.artist_mbid]} few similar artists.")
msg = "artist: using seed artist %s" % artists[0]["name"]
if self.include_similar_artists:
msg += " and similar artists: " + ", ".join([a["name"] for a in artists[1:]])
else:
msg += " only"
msgs.append(msg)

for msg in msgs:
self.local_storage["user_feedback"].append(msg)
self.data_cache["element-descriptions"].append("artist %s" % artists[0]["name"])

# Determine percent ranges based on mode -- this will likely need further tweaking
if self.mode == "easy":
start, stop = 0, 50
elif self.mode == "medium":
start, stop = 25, 75
else:
start, stop = 50, 100

# Now collect recordings from the artist and similar artists and return an interleaved
# strem of recordings.
for i, artist in enumerate(artists):
Expand All @@ -161,6 +163,10 @@ def read(self, entities):
continue

recs_plist = plist(self.fetch_top_recordings(artist["mbid"]))
if len(recs_plist) < 20:
self.local_storage["user_feedback"].append(
f"Artist {artist['name']} only has {'no' if len(recs_plist) == 0 else 'few'} top recordings.")

recordings = []
for recording in recs_plist.random_item(start, stop, self.max_top_recordings_per_artist):
recordings.append(recording)
Expand Down
Loading

0 comments on commit 661232b

Please sign in to comment.