Refactored library and radionet class

- Changes in radio.net API (added support for configurable apikey)
- Added better caching
- Added support for browsing additional categories
This commit is contained in:
Eric van Blokland
2021-09-08 00:48:20 +02:00
parent 2f659d8c60
commit 63da37c012
10 changed files with 708 additions and 242 deletions

View File

@@ -37,6 +37,7 @@ Mopidy-RadioNet to your Mopidy configuration file::
enabled = true enabled = true
language = pl # or net, de, at, fr, pt, es, dk, se, it language = pl # or net, de, at, fr, pt, es, dk, se, it
min_bitrate = 96 min_bitrate = 96
api_key = valid_api_key
favorite_stations = favorite_stations =
'bbcradio1' 'bbcradio1'
'bbcradio2' 'bbcradio2'
@@ -77,8 +78,8 @@ Mopidy-RadioNet to your Mopidy configuration file::
Project resources Project resources
================= =================
- `Source code <https://github.com/blackberrymamba/mopidy-radionet>`_ - `Source code <https://github.com/plintx/mopidy-radionet>`_
- `Issue tracker <https://github.com/blackberrymamba/mopidy-radionet/issues>`_ - `Issue tracker <https://github.com/plintx/mopidy-radionet/issues>`_
Changelog Changelog

View File

@@ -5,8 +5,7 @@ import os
from mopidy import config, ext from mopidy import config, ext
__version__ = "0.2.2"
__version__ = '0.2.2'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -14,21 +13,23 @@ logger = logging.getLogger(__name__)
class Extension(ext.Extension): class Extension(ext.Extension):
dist_name = 'Mopidy-RadioNet' dist_name = "Mopidy-RadioNet"
ext_name = 'radionet' ext_name = "radionet"
version = __version__ version = __version__
def get_default_config(self): def get_default_config(self):
conf_file = os.path.join(os.path.dirname(__file__), 'ext.conf') conf_file = os.path.join(os.path.dirname(__file__), "ext.conf")
return config.read(conf_file) return config.read(conf_file)
def get_config_schema(self): def get_config_schema(self):
schema = super(Extension, self).get_config_schema() schema = super(Extension, self).get_config_schema()
schema['language'] = config.String() schema["language"] = config.String()
schema['min_bitrate'] = config.String() schema["min_bitrate"] = config.String()
schema['favorite_stations'] = config.List() schema["api_key"] = config.String()
schema["favorite_stations"] = config.List()
return schema return schema
def setup(self, registry): def setup(self, registry):
from .backend import RadioNetBackend from .backend import RadioNetBackend
registry.add('backend', RadioNetBackend)
registry.add("backend", RadioNetBackend)

View File

@@ -1,11 +1,10 @@
from __future__ import unicode_literals from __future__ import unicode_literals
import re
import time import time
from mopidy import backend
import pykka import pykka
import re from mopidy import backend
import mopidy_radionet import mopidy_radionet
@@ -19,21 +18,24 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend):
def __init__(self, config, audio): def __init__(self, config, audio):
super(RadioNetBackend, self).__init__() super(RadioNetBackend, self).__init__()
self.radionet = RadioNetClient( self.radionet = RadioNetClient(
config['proxy'], config["proxy"],
"%s/%s" % ( "%s/%s"
mopidy_radionet.Extension.dist_name, % (mopidy_radionet.Extension.dist_name, mopidy_radionet.__version__),
mopidy_radionet.__version__))
self.library = RadioNetLibraryProvider(backend=self)
self.playback = RadioNetPlaybackProvider(
audio=audio, backend=self
) )
self.uri_schemes = ['radionet'] self.library = RadioNetLibraryProvider(backend=self)
self.playback = RadioNetPlaybackProvider(audio=audio, backend=self)
self.radionet.min_bitrate = int(config['radionet']['min_bitrate']) self.uri_schemes = ["radionet"]
self.radionet.set_lang(str(config['radionet']['language']))
self.radionet.set_favorites(tuple(file_ext.lower() for file_ext in config["radionet"]["favorite_stations"])) self.radionet.min_bitrate = int(config["radionet"]["min_bitrate"])
self.radionet.set_lang(str(config["radionet"]["language"]))
self.radionet.set_apikey(str(config["radionet"]["api_key"]))
self.radionet.set_favorites(
tuple(
file_ext.lower() for file_ext in config["radionet"]["favorite_stations"]
)
)
def set_update_timeout(self, minutes=2): def set_update_timeout(self, minutes=2):
self.update_timeout = time.time() + 60 * minutes self.update_timeout = time.time() + 60 * minutes
@@ -51,13 +53,13 @@ class RadioNetBackend(pykka.ThreadingActor, backend.Backend):
self.radionet.get_favorites() self.radionet.get_favorites()
self.set_update_timeout() self.set_update_timeout()
class RadioNetPlaybackProvider(backend.PlaybackProvider):
class RadioNetPlaybackProvider(backend.PlaybackProvider):
def is_live(self, uri): def is_live(self, uri):
return True return True
def translate_uri(self, uri): def translate_uri(self, uri):
identifier = re.findall(r'^radionet:track:?([a-z0-9]+|\d+)?$', uri) identifier = re.findall(r"^radionet:track:?([a-z0-9]+|\d+)?$", uri)
if identifier: if identifier:
radio_data = self.backend.radionet.get_station_by_id(identifier) radio_data = self.backend.radionet.get_station_by_id(identifier)
if radio_data: if radio_data:

View File

@@ -2,4 +2,5 @@
enabled = true enabled = true
language = pl language = pl
min_bitrate = 96 min_bitrate = 96
api_key = something
favorite_stations = favorite_stations =

View File

@@ -10,19 +10,19 @@ logger = logging.getLogger(__name__)
class RadioNetLibraryProvider(backend.LibraryProvider): class RadioNetLibraryProvider(backend.LibraryProvider):
root_directory = Ref.directory(uri='radionet:root', name='Radio.net') root_directory = Ref.directory(uri="radionet:root", name="Radio.net")
def __init__(self, backend): def __init__(self, backend):
super().__init__(backend) super().__init__(backend)
def lookup(self, uri): def lookup(self, uri):
if not uri.startswith('radionet:'): if not uri.startswith("radionet:"):
return None return None
variant, identifier = self.parse_uri(uri) variant, identifier, sorting, page = self.parse_uri(uri)
if variant == 'station' or variant == 'track': if variant == "station" or variant == "track":
identifier = int(identifier) identifier = int(identifier)
radio_data = self.backend.radionet.get_station_by_id(identifier) radio_data = self.backend.radionet.get_station_by_id(identifier)
@@ -30,9 +30,15 @@ class RadioNetLibraryProvider(backend.LibraryProvider):
album = Album( album = Album(
artists=[artist], artists=[artist],
name=radio_data.description + ' / ' + radio_data.continent + name=radio_data.description
' / ' + radio_data.country + ' - ' + radio_data.city, + " / "
uri='radionet:station:%s' % (identifier)) + radio_data.continent
+ " / "
+ radio_data.country
+ " - "
+ radio_data.city,
uri="radionet:station:%s" % (identifier),
)
track = Track( track = Track(
artists=[artist], artists=[artist],
@@ -40,81 +46,222 @@ class RadioNetLibraryProvider(backend.LibraryProvider):
name=radio_data.name, name=radio_data.name,
genre=radio_data.genres, genre=radio_data.genres,
comment=radio_data.description, comment=radio_data.description,
uri='radionet:track:%s' % (identifier)) uri="radionet:track:%s" % (identifier),
)
return [track] return [track]
return [] return []
def browse(self, uri): def browse(self, uri):
self.backend.refresh()
directories = [] category, page, value, sorting = self.parse_uri(uri)
tracks = []
variant, identifier = self.parse_uri(uri) logger.error("Uri %s", uri)
if variant == 'root':
if self.backend.radionet.local_stations: if category == "root":
directories.append( return self._browse_root()
self.ref_directory( elif category in ["favorites", "topstations", "localstations"]:
"radionet:category:localstations", "Local stations") return self._browse_category(category, page)
) elif category in ["genres", "topics", "languages", "cities", "countries"]:
if self.backend.radionet.top_stations: return self._browse_sorted_category(category, value, sorting, page)
directories.append(
self.ref_directory(
"radionet:category:top100", "Top 100")
)
if self.backend.radionet.favorite_stations:
directories.append(
self.ref_directory(
"radionet:category:favorites", "Favorites")
)
return directories
elif variant == 'category' and identifier:
if identifier == "localstations":
for station in self.backend.radionet.local_stations:
tracks.append(self.station_to_ref(station))
if identifier == "top100":
for station in self.backend.radionet.top_stations:
tracks.append(self.station_to_ref(station))
if identifier == "favorites":
for station in self.backend.radionet.favorite_stations:
tracks.append(self.station_to_ref(station))
tracks.sort(key=lambda ref: ref.name)
return tracks
else: else:
logger.debug('Unknown URI: %s', uri) logger.debug("Unknown URI: %s", uri)
return [] return []
def _browse_root(self):
directories = [
self.ref_directory("radionet:favorites", "Favorites"),
self.ref_directory("radionet:topstations", "Top stations"),
self.ref_directory("radionet:localstations", "Local stations"),
self.ref_directory("radionet:genres", "Genres"),
self.ref_directory("radionet:topics", "Topics"),
self.ref_directory("radionet:languages", "Languages"),
self.ref_directory("radionet:cities", "Cities"),
self.ref_directory("radionet:countries", "Countries"),
]
return directories
def _browse_category(self, category, page):
result = []
if category == "favorites":
items = self._get_favorites()
if items:
for item in items:
result.append(
self.ref_track(
"radionet:station:{0}".format(str(item.id)), item.name
)
)
elif category == "topstations":
items = self._get_topstations()
if items:
for item in items:
result.append(
self.ref_track(
"radionet:station:{0}".format(item["id"]),
item["name"]["value"],
)
)
elif not page:
pages = self._get_category_pages(category)
for index in range(pages):
result.append(
self.ref_directory(
"radionet:{0}:{1}".format(category, str(index + 1)),
str(index + 1),
)
)
else:
items = self._get_category(category, page)
if items:
for item in items:
result.append(
self.ref_track(
"radionet:station:{0}".format(item["id"]),
item["name"]["value"],
)
)
return result
def _browse_sorted_category(self, category, value, sorting, page):
result = []
if not value:
items = self.__getattribute__("_get_{0}".format(category))()
if items:
for item in items:
result.append(
self.ref_directory(
"radionet:{0}:{1}".format(category, item["systemEnglish"]),
item["localized"],
)
)
elif not sorting or sorting not in ["rank", "az"]:
result.append(
self.ref_directory(
"radionet:{0}:{1}:rank".format(category, value), "By rank"
)
)
result.append(
self.ref_directory(
"radionet:{0}:{1}:az".format(category, value), "Alphabetical"
)
)
elif not page:
pages = self._get_sorted_category_pages(category, value)
for index in range(pages):
result.append(
self.ref_directory(
"radionet:{0}:{1}:{2}:{3}".format(
category, value, sorting, str(index + 1)
),
str(index + 1),
)
)
else:
items = self._get_sorted_category(category, value, sorting, page)
if items:
for item in items:
result.append(
self.ref_track(
"radionet:station:{0}".format(item["id"]),
item["name"]["value"],
)
)
return result
def _get_genres(self):
return self.backend.radionet.get_genres()
def _get_topics(self):
return self.backend.radionet.get_topics()
def _get_languages(self):
return self.backend.radionet.get_languages()
def _get_cities(self):
return self.backend.radionet.get_cities()
def _get_countries(self):
return self.backend.radionet.get_countries()
def _get_topstations(self):
return self.backend.radionet.get_category("topstations", 1)
def _get_sorted_category(self, category, name, sorting, page):
return self.backend.radionet.get_sorted_category(category, name, sorting, page)
def _get_sorted_category_pages(self, category, name):
return self.backend.radionet.get_sorted_category_pages(category, name)
def _get_category(self, category, page):
return self.backend.radionet.get_category(category, page)
def _get_category_pages(self, category):
return self.backend.radionet.get_category_pages(category)
def _get_favorites(self):
return self.backend.radionet.get_favorites()
def search(self, query=None, uris=None, exact=False): def search(self, query=None, uris=None, exact=False):
if 'any' not in query: if "any" not in query:
return None return None
result = [] result = []
self.backend.radionet.do_search(' '.join(query['any'])) self.backend.radionet.do_search(" ".join(query["any"]))
for station in self.backend.radionet.search_results: for station in self.backend.radionet.search_results:
result.append(self.station_to_track(station)) result.append(self.station_to_track(station))
return SearchResult( return SearchResult(tracks=result)
tracks=result
)
def station_to_ref(self, station): def station_to_ref(self, station):
return Ref.track( return Ref.track(
uri='radionet:station:%s' % (station.id), uri="radionet:station:%s" % (station.id),
name=station.name, name=station.name,
) )
def station_to_track(self, station): def station_to_track(self, station):
ref = self.station_to_ref(station) ref = self.station_to_ref(station)
return Track(uri=ref.uri, name=ref.name, album=Album(uri=ref.uri, name=ref.name), return Track(
artists=[Artist(uri=ref.uri, name=ref.name)]) uri=ref.uri,
name=ref.name,
album=Album(uri=ref.uri, name=ref.name),
artists=[Artist(uri=ref.uri, name=ref.name)],
)
def ref_directory(self, uri, name): def ref_directory(self, uri, name):
return Ref.directory(uri=uri, name=name) return Ref.directory(uri=uri, name=name)
def ref_track(self, uri, name):
return Ref.track(uri=uri, name=name)
def parse_uri(self, uri): def parse_uri(self, uri):
result = re.findall(r'^radionet:([a-z]+):?([a-z0-9]+|\d+)?$', uri) category = None
value = None
page = None
sorting = None
result = re.findall(
r"^radionet:(genres|topics|languages|cities|countries)(:([^:]+)(:(rank|az)(:([0-9]+))?)?)?$",
uri,
)
if result: if result:
return result[0] category = result[0][0]
return None, None value = result[0][2]
sorting = result[0][4]
page = result[0][6]
else:
result = re.findall(
r"^radionet:(root|favorites|topstations|localstations|station|track)(:([0-9]+))?$",
uri,
)
if result:
category = result[0][0]
page = result[0][2]
return category, page, value, sorting

View File

@@ -6,9 +6,8 @@ import logging
import re import re
import time import time
from mopidy import httpclient
import requests import requests
from mopidy import httpclient
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -27,214 +26,324 @@ class Station(object):
class RadioNetClient(object): class RadioNetClient(object):
base_url = 'https://radio.net/' base_url = "https://radio.net/"
session = requests.Session() session = requests.Session()
api_prefix = None api_prefix = None
min_bitrate = 96 min_bitrate = 96
max_top_stations = 100 max_top_stations = 100
station_bookmarks = None station_bookmarks = None
api_key = None
stations_images = [] stations_images = []
top_stations = []
local_stations = []
search_results = [] search_results = []
favorites = []
cache = {}
category_param_map = {
"genres": "genre",
"topics": "topic",
"languages": "language",
"cities": "city",
"countries": "country",
}
def __init__(self, proxy_config=None, user_agent=None): def __init__(self, proxy_config=None, user_agent=None):
super(RadioNetClient, self).__init__() super(RadioNetClient, self).__init__()
self.session = requests.Session() self.session = requests.Session()
if proxy_config is not None: if proxy_config is not None:
proxy = httpclient.format_proxy(proxy_config) proxy = httpclient.format_proxy(proxy_config)
self.session.proxies.update({'http': proxy, 'https': proxy}) self.session.proxies.update({"http": proxy, "https": proxy})
if user_agent is None:
user_agent = "Mopidy Radio Client"
full_user_agent = httpclient.format_user_agent(user_agent) full_user_agent = httpclient.format_user_agent(user_agent)
self.session.headers.update({'user-agent': full_user_agent}) self.session.headers.update({"user-agent": full_user_agent})
self.session.headers.update({'cache-control': 'no-cache'}) self.session.headers.update({"cache-control": "no-cache"})
self.update_prefix()
def __del__(self):
self.session.close()
def set_lang(self, lang): def set_lang(self, lang):
langs = ['net', 'de', 'at', 'fr', 'pt', 'es', 'dk', 'se', 'it', 'pl'] langs = ["net", "de", "at", "fr", "pt", "es", "dk", "se", "it", "pl"]
if lang in langs: if lang in langs:
self.base_url = self.base_url.replace(".net", "." + lang) self.base_url = self.base_url.replace(".net", "." + lang)
else: else:
logging.error("Radio.net not supported language: %s", str(lang)) logging.error("Radio.net not supported language: %s", str(lang))
self.update_prefix()
def flush(self): def update_prefix(self):
self.top_stations = []
self.local_stations = []
self.search_results = []
def set_api_prefix(self):
if self.api_prefix is not None:
return
tmp_str = self.session.get(self.base_url) tmp_str = self.session.get(self.base_url)
lang = self.base_url.split('.')[-1].replace('/', '') lang = self.base_url.split(".")[-1].replace("/", "")
self.api_prefix = "https://api.radio." + lang + "/info/v2" self.api_prefix = "https://api.radio." + lang + "/info/v2"
def do_get(self, api_sufix, url_params=None): def set_apikey(self, api_key):
self.set_api_prefix() self.api_key = api_key
response = self.session.get(self.api_prefix + api_sufix,
params=url_params) def do_get(self, api_suffix, url_params=None):
if self.api_prefix is None:
return None
if url_params is None:
url_params = {}
url_params["apikey"] = self.api_key
response = self.session.get(self.api_prefix + api_suffix, params=url_params)
return response return response
def get_station_by_id(self, station_id): def get_cache(self, key):
if self.cache.get(key) is not None and self.cache[key].expired() is False:
return self.cache[key].value()
return None
api_suffix = '/search/station' def set_cache(self, key, value, expires):
self.cache[key] = CacheItem(value, expires)
return value
def get_station_by_id(self, station_id):
cache_key = "station/" + str(station_id)
cache = self.get_cache(cache_key)
if cache is not None:
return cache
api_suffix = "/search/station"
url_params = { url_params = {
'station': station_id, "station": station_id,
} }
response = self.do_get(api_suffix, url_params) response = self.do_get(api_suffix, url_params)
if response.status_code is not 200: if response.status_code != 200:
logger.error('Radio.net: Error on get station by id ' + logger.error(
str(station_id) + ". Error: " + response.text) "Radio.net: Error on get station by id "
+ str(station_id)
+ ". Error: "
+ response.text
)
return False return False
else:
logger.debug('Radio.net: Done get top stations list') logger.debug("Radio.net: Done get top stations list")
json = response.json() json = response.json()
station = Station() station = Station()
station.id = json['id'] station.id = json["id"]
station.continent = json['continent'] station.continent = json["continent"]
station.country = json['country'] station.country = json["country"]
station.city = json['city'] station.city = json["city"]
station.genres = ', '.join(json["genres"]) station.genres = ", ".join(json["genres"])
station.name = json['name'] station.name = json["name"]
station.stream_url = self.get_stream_url( station.stream_url = self.get_stream_url(json["streamUrls"], self.min_bitrate)
json['streamUrls'], self.min_bitrate) station.image = json["logo100x100"]
station.image = json['logo100x100'] station.description = json["shortDescription"]
station.description = json['shortDescription'] if json["playable"] == "PLAYABLE":
if json['playable'] == 'PLAYABLE':
station.playable = True station.playable = True
return station return self.set_cache(cache_key, station, 1440)
def get_local_stations(self): def get_genres(self):
self.local_stations = [] return self._get_items("genres")
api_suffix = '/search/localstations' def get_topics(self):
return self._get_items("topics")
def get_languages(self):
return self._get_items("languages")
def get_cities(self):
return self._get_items("cities")
def get_countries(self):
return self._get_items("countries")
def _get_items(self, key):
cached = self.get_cache(key)
if cached is not None:
return cached
api_suffix = "/search/get" + key
response = self.do_get(api_suffix)
if response.status_code != 200:
logger.error(
"Radio.net: Error on get item list "
+ str(api_suffix)
+ ". Error: "
+ response.text
)
return False
return self.set_cache(key, response.json(), 1440)
def get_sorted_category(self, category, name, sorting, page):
if sorting == "az":
sorting = "STATION_NAME"
else:
sorting = "RANK"
cache_key = category + "/" + name + "/" + sorting + "/" + str(page)
cache = self.get_cache(cache_key)
if cache is not None:
return cache
api_suffix = "/search/stationsby" + self.category_param_map[category]
url_params = { url_params = {
'pageindex': 1, self.category_param_map[category]: name,
'sizeperpage': 100, "sorttype": sorting,
"sizeperpage": 50,
"pageindex": page,
} }
response = self.do_get(api_suffix, url_params) response = self.do_get(api_suffix, url_params)
if response.status_code is not 200: if response.status_code != 200:
logger.error('Radio.net: Get local stations error. ' + logger.error(
response.text) "Radio.net: Error on get station by "
else: + str(category)
logger.debug('Radio.net: Done get local stations list') + ". Error: "
json = response.json() + response.text
for match in json['categories'][0]['matches']: )
station = self.get_station_by_id(match['id']) return False
if station:
if station.playable:
self.local_stations.append(station)
logger.info('Radio.net: Loaded ' + str(len(self.local_stations)) + json = response.json()
' local stations.') self.set_cache(category + "/" + name, int(json["numberPages"]), 10)
return self.set_cache(cache_key, json["categories"][0]["matches"], 10)
def get_category(self, category, page):
cache_key = category + "/" + str(page)
cache = self.get_cache(cache_key)
if cache is not None:
return cache
api_suffix = "/search/" + category
url_params = {"sizeperpage": 50, "pageindex": page}
response = self.do_get(api_suffix, url_params)
logger.error(response.text)
if response.status_code != 200:
logger.error(
"Radio.net: Error on get station by "
+ str(category)
+ ". Error: "
+ response.text
)
return False
json = response.json()
self.set_cache(category, int(json["numberPages"]), 10)
return self.set_cache(cache_key, json["categories"][0]["matches"], 10)
def get_sorted_category_pages(self, category, name):
cache_key = category + "/" + name
cache = self.get_cache(cache_key)
if cache is not None:
return cache
self.get_sorted_category(category, name, "rank", 1)
return self.get_cache(cache_key)
def get_category_pages(self, category):
cache_key = category
cache = self.get_cache(cache_key)
if cache is not None:
return cache
self.get_category(category, 1)
return self.get_cache(cache_key)
def set_favorites(self, favorites): def set_favorites(self, favorites):
self.favortes = favorites self.favorites = favorites
def get_favorites(self): def get_favorites(self):
self.favorite_stations = [] logger.error(str(self.favorites))
cache_key = "favorites"
cache = self.get_cache(cache_key)
if cache is not None:
return cache
for station in self.favortes: favorite_stations = []
api_suffix = '/search/stationsonly' for station in self.favorites:
logger.error(str(station))
api_suffix = "/search/stationsonly"
url_params = { url_params = {
'query': station, "query": station,
'pageindex': 1, "pageindex": 1,
} }
response = self.do_get(api_suffix, url_params) response = self.do_get(api_suffix, url_params)
if response.status_code is not 200: if response.status_code != 200:
logger.error('Radio.net: Search error ' + response.text) logger.error("Radio.net: Search error " + response.text)
else: else:
logger.debug('Radio.net: Done search') logger.debug("Radio.net: Done search")
json = response.json() json = response.json()
# take only the first match! # take only the first match!
station = self.get_station_by_id(json['categories'][0]['matches'][0]['id']) station = self.get_station_by_id(
json["categories"][0]["matches"][0]["id"]
)
if station and station.playable: if station and station.playable:
self.favorite_stations.append(station) favorite_stations.append(station)
logger.info('Radio.net: Loaded ' + str(len(self.favorite_stations)) + logger.info(
' favorite stations.') "Radio.net: Loaded " + str(len(favorite_stations)) + " favorite stations."
)
return self.set_cache(cache_key, favorite_stations, 1440)
def get_top_stations(self): def do_search(self, query_string, page_index=1, search_results=[]):
self.top_stations = []
api_suffix = '/search/topstations'
api_suffix = "/search/stationsonly"
url_params = { url_params = {
'pageindex': 1, "query": query_string,
'sizeperpage': 100, "sizeperpage": 50,
"pageindex": page_index,
} }
response = self.do_get(api_suffix, url_params) response = self.do_get(api_suffix, url_params)
if response.status_code is not 200: if response.status_code != 200:
logger.error('Radio.net: Get top stations error. ' + response.text) logger.error("Radio.net: Search error " + response.text)
else: else:
logger.debug('Radio.net: Done get top stations list') logger.debug("Radio.net: Done search")
json = response.json() json = response.json()
for match in json['categories'][0]['matches']: for match in json["categories"][0]["matches"]:
station = self.get_station_by_id(match['id']) station = self.get_station_by_id(match["id"])
if station:
if station.playable:
self.top_stations.append(station)
logger.info('Radio.net: Loaded ' + str(len(self.top_stations)) +
' top stations.')
def do_search(self, query_string, page_index=1):
if page_index == 1:
self.search_results = []
api_suffix = '/search/stationsonly'
url_params = {
'query': query_string,
'pageindex': page_index,
}
response = self.do_get(api_suffix, url_params)
if response.status_code is not 200:
logger.error('Radio.net: Search error ' + response.text)
else:
logger.debug('Radio.net: Done search')
json = response.json()
for match in json['categories'][0]['matches']:
station = self.get_station_by_id(match['id'])
if station and station.playable: if station and station.playable:
self.search_results.append(station) search_results.append(station)
number_pages = int(json['numberPages']) number_pages = int(json["numberPages"])
if number_pages >= page_index: if number_pages >= page_index:
self.do_search(query_string, page_index + 1) self.do_search(query_string, page_index + 1, search_results)
else: else:
logger.info('Radio.net: Found ' + logger.info(
str(len(self.search_results)) + ' stations.') "Radio.net: Found " + str(len(search_results)) + " stations."
)
return search_results
def get_stream_url(self, stream_json, bit_rate): def get_stream_url(self, stream_json, bit_rate):
stream_url = None stream_url = None
for stream in stream_json: for stream in stream_json:
if int(stream['bitRate']) >= bit_rate and \ if int(stream["bitRate"]) >= bit_rate and stream["streamStatus"] == "VALID":
stream['streamStatus'] == 'VALID': stream_url = stream["streamUrl"]
stream_url = stream['streamUrl']
break break
if stream_url is None and len(stream_json) > 0: if stream_url is None and len(stream_json) > 0:
stream_url = stream_json[0]['streamUrl'] stream_url = stream_json[0]["streamUrl"]
return stream_url return stream_url
class CacheItem(object):
def __init__(self, value, expires=10):
self._value = value
self._expires = expires = time.time() + expires * 60
def expired(self):
return self._expires < time.time()
def value(self):
return self._value

View File

@@ -1,3 +1,52 @@
[metadata]
name = Mopidy-RadioNet
version = 0.2.2
url = https://github.com/plintx/mopidy-radionet
license = Apache License, Version 2.0
license_file = LICENSE
description = Mopidy extension for playing music from Radio.net
long_description = file: README.rst
classifiers =
Environment :: No Input/Output (Daemon)
Intended Audience :: End Users/Desktop
License :: OSI Approved :: Apache Software License
Operating System :: OS Independent
Programming Language :: Python :: 3
Programming Language :: Python :: 3.7
Programming Language :: Python :: 3.8
Programming Language :: Python :: 3.9
Topic :: Multimedia :: Sound/Audio :: Players
[options]
zip_safe = False
include_package_data = True
packages = find:
python_requires = >= 3.7
install_requires =
Mopidy >= 3.0.0
Pykka >= 2.0.1
pyspotify >= 2.0.5
requests >= 2.20.0
setuptools
[options.extras_require]
lint =
black
check-manifest
flake8
flake8-black
flake8-bugbear
flake8-import-order
isort
test =
pytest
pytest-cov
responses
dev =
%(lint)s
%(test)s
[flake8] [flake8]
application-import-names = mopidy_radionet,tests application-import-names = mopidy_radionet,tests
exclude = .git,.tox exclude = .git,.tox

24
tests/conftest.py Normal file
View File

@@ -0,0 +1,24 @@
from unittest import mock
import pytest
from mopidy_radionet import backend
from mopidy_radionet.radionet import RadioNetClient
from mopidy_radionet.library import RadioNetLibraryProvider
@pytest.fixture
def backend_mock():
backend_mock = mock.Mock(spec=backend.RadioNetBackend)
backend_mock.radionet = RadioNetClient(proxy_config=None)
backend_mock.library = RadioNetLibraryProvider(backend=backend_mock)
backend_mock.radionet.set_apikey('test')
backend_mock.radionet.set_favorites({'lush'})
return backend_mock
@pytest.fixture
def library(backend_mock):
return backend_mock.library
@pytest.fixture
def radionet(backend_mock):
return backend_mock.radionet

142
tests/test_library.py Normal file
View File

@@ -0,0 +1,142 @@
from unittest import mock
def test_browse_root(library):
results = library.browse('radionet:root');
assert 8 == len(results)
def test_browse_localstations(library):
results = library.browse('radionet:localstations');
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_topstations(library):
results = library.browse('radionet:topstations');
assert len(results) > 0
def test_browse_genres(library):
results = library.browse('radionet:genres');
assert len(results) > 0
cat_uri = results[0].uri if results is not None else None
assert cat_uri is not None
results = library.browse(cat_uri)
assert len(results) == 2
sort_uri = results[0].uri if results is not None else None
assert sort_uri is not None
results = library.browse(sort_uri)
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_topics(library):
results = library.browse('radionet:topics');
assert len(results) > 0
cat_uri = results[0].uri if results is not None else None
assert cat_uri is not None
results = library.browse(cat_uri)
assert len(results) == 2
sort_uri = results[0].uri if results is not None else None
assert sort_uri is not None
results = library.browse(sort_uri)
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_languages(library):
results = library.browse('radionet:languages');
assert len(results) > 0
cat_uri = results[0].uri if results is not None else None
assert cat_uri is not None
results = library.browse(cat_uri)
assert len(results) == 2
sort_uri = results[0].uri if results is not None else None
assert sort_uri is not None
results = library.browse(sort_uri)
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_cities(library):
results = library.browse('radionet:cities');
assert len(results) > 0
cat_uri = results[0].uri if results is not None else None
assert cat_uri is not None
results = library.browse(cat_uri)
assert len(results) == 2
sort_uri = results[0].uri if results is not None else None
assert sort_uri is not None
results = library.browse(sort_uri)
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_countries(library):
results = library.browse('radionet:countries');
assert len(results) > 0
cat_uri = results[0].uri if results is not None else None
assert cat_uri is not None
results = library.browse(cat_uri)
assert len(results) == 2
sort_uri = results[0].uri if results is not None else None
assert sort_uri is not None
results = library.browse(sort_uri)
assert len(results) > 0
page_uri = results[0].uri if results is not None else None
assert page_uri is not None
results = library.browse(page_uri)
assert len(results) > 0
def test_browse_favorites(library):
results = library.browse('radionet:favorites');
assert 1 == len(results)

View File

@@ -1,38 +1,28 @@
import unittest from unittest import mock
from mopidy_radionet.radionet import RadioNetClient
class RadioNetClientTest(unittest.TestCase): def test_get_genres(radionet):
genres = radionet.get_genres();
assert len(genres) > 0
def test_get_api_key(self):
radionet = RadioNetClient()
radionet.get_api_key()
self.assertIsNotNone(radionet.api_key) def test_get_top_stations(radionet):
result = radionet.get_category('topstations', 1)
assert len(result) > 0
def test_get_top_stations(self):
radionet = RadioNetClient()
radionet.get_top_stations()
self.assertGreater(len(radionet.top_stations), 0)
def test_get_local_stations(self): def test_get_local_stations(radionet):
radionet = RadioNetClient() result = radionet.get_category('localstations', 1)
radionet.get_local_stations() assert len(result) > 0
self.assertGreater(len(radionet.local_stations), 0)
def test_do_search(self):
radionet = RadioNetClient()
radionet.do_search("radio ram")
self.assertGreater(len(radionet.search_results), 0)
def test_get_favorites(self): def test_do_search(radionet):
result = radionet.do_search("radio ram")
assert len(result) > 0
def test_get_favorites(radionet):
test_favorites = ("Rock Antenne", "radio ram") test_favorites = ("Rock Antenne", "radio ram")
radionet = RadioNetClient()
radionet.set_favorites(test_favorites) radionet.set_favorites(test_favorites)
radionet.get_favorites() result = radionet.get_favorites()
self.assertEqual(len(radionet.favorite_stations), len(test_favorites)) assert len(result) == len(test_favorites)
if __name__ == "__main__":
unittest.main()