diff --git a/troi/listenbrainz/recs.py b/troi/listenbrainz/recs.py index 9ff8a242..945d4f51 100644 --- a/troi/listenbrainz/recs.py +++ b/troi/listenbrainz/recs.py @@ -22,8 +22,6 @@ class UserRecordingRecommendationsElement(Element): def __init__(self, user_name, artist_type, count=25, offset=0): super().__init__() self.client = liblistenbrainz.ListenBrainz() - if auth_token: - self.client.set_auth_token(auth_token) self.user_name = user_name self.count = count self.offset = offset diff --git a/troi/recording_search_service.py b/troi/recording_search_service.py new file mode 100644 index 00000000..52db1154 --- /dev/null +++ b/troi/recording_search_service.py @@ -0,0 +1,88 @@ +from abc import abstractmethod + +import requests + +from troi import Recording, Artist +from troi.service import Service +from troi.splitter import plist + +# NOTES FOR LB API improvements: +# Tags: +# - flatten tag data for simplicity +# - use series of tighter random spots to speed up or searches for popular tags +# Artist: +# - Support percent based popular track lookups and move logic to server + + +class RecordingSearchByTagService(Service): + + SLUG = "recording-search-by-tag" + + def __init__(self): + super().__init__(self.SLUG) + + def search(self, tags, operator, begin_percent, end_percent, num_recordings): + """ + Fetch the tag data from the LB API and return it as a dict. + """ + + data = { + "condition": operator, + "count": num_recordings, + "begin_percent": begin_percent, + "end_percent": end_percent, + "tag": tags, + "min_tag_count": 1 + } + r = requests.get("https://api.listenbrainz.org/1/lb-radio/tags", params=data) + if r.status_code != 200: + raise RuntimeError(f"Cannot fetch recordings for tags. {r.text}") + + recordings = [] + for rec in self.flatten_tag_data(dict(r.json())): + recordings.append(Recording(mbid=rec["recording_mbid"])) + + return plist(recordings) + + def flatten_tag_data(self, tag_data): + + flat_data = list(tag_data["recording"]) + flat_data.extend(list(tag_data["release-group"])) + flat_data.extend(list(tag_data["artist"])) + + return sorted(flat_data, key=lambda f: f["percent"], reverse=True) + + +class RecordingSearchByArtistService(Service): + + SLUG = "recording-search-by-artist" + + def __init__(self): + super().__init__(self.SLUG) + + def search(self, artists, begin_percent, end_percent, num_recordings): + """ + Fetch the artist data from the LB API and return it as a dict. + + NOTE: This search is poor -- it should span all recordings by an artist not, just the top ones! + """ + + artists_recordings = {} + for artist_mbid in artists: + params={"artist_mbid": artist_mbid} + r = requests.get("https://api.listenbrainz.org/1/popularity/top-recordings-for-artist", params={"artist_mbid": artist_mbid}) + if r.status_code != 200: + raise RuntimeError(f"Cannot fetch top artist recordings: {r.status_code} ({r.text})") + + recordings = plist() + for recording in r.json(): + artist = Artist(mbids=recording["artist_mbids"], name=recording["artist_name"]) + recordings.append( + Recording(mbid=recording["recording_mbid"], + name=recording["recording_name"], + duration=recording["length"], + artist=artist)) + + artists_recordings[artist_mbid] = recordings.random_item(begin_percent, end_percent, num_recordings) + + return artists_recordings diff --git a/troi/service.py b/troi/service.py new file mode 100755 index 00000000..097f1058 --- /dev/null +++ b/troi/service.py @@ -0,0 +1,18 @@ +class Service: + """ + Services allow troi users to provide local versions of key search functions + so that both global (e.g. MusicBrainz) and local search contexts can be used. + + Downstream users will be able to provide localized version of search functions + to provide lists of Recordings that meet certain requirements for playlist inclusion. + + Only one service can be registered for any given service slug at a time. The most + recently registered service will be use for the next playlist generation. + """ + + def __init__(self, slug): + self._slug = slug + + @property + def slug(self): + return self._slug