Browse Source

Merge branch 'master' into master

Micha LaQua 4 years ago
parent
commit
e799b92ed6
4 changed files with 61 additions and 28 deletions
  1. 5 1
      README.md
  2. 11 9
      ycast/radiobrowser.py
  3. 44 17
      ycast/server.py
  4. 1 1
      ycast/vtuner.py

+ 5 - 1
README.md

@@ -35,6 +35,7 @@ Any reported device helps the community to see which AVRs work properly and whic
  * Denon CEOL piccolo N5
  * Denon CEOL piccolo N5
  * Denon CEOL N9
  * Denon CEOL N9
  * Denon DNP-720AE
  * Denon DNP-720AE
+ * Denon DNP-730AE
  * Denon DRA-100
  * Denon DRA-100
  * Marantz Melody Media M-CR610
  * Marantz Melody Media M-CR610
  * Marantz NR1506
  * Marantz NR1506
@@ -49,6 +50,7 @@ Any reported device helps the community to see which AVRs work properly and whic
  * Yamaha RX-A810
  * Yamaha RX-A810
  * Yamaha RX-A820
  * Yamaha RX-A820
  * Yamaha RX-A830
  * Yamaha RX-A830
+ * Yamaha CRX-N560/MCR-N560
  * Yamaha RX-V_71 series with network connectivity (RX-V671, RX-V771)
  * Yamaha RX-V_71 series with network connectivity (RX-V671, RX-V771)
  * Yamaha RX-V_73 series with network connectivity (RX-V473, RX-V573, RX-V673, RX-V773)
  * Yamaha RX-V_73 series with network connectivity (RX-V473, RX-V573, RX-V673, RX-V773)
  * Yamaha RX-V_75 series (RX-V375, RX-V475, RX-V575, RX-V675, RX-V775)
  * Yamaha RX-V_75 series (RX-V375, RX-V475, RX-V575, RX-V675, RX-V775)
@@ -85,11 +87,13 @@ YCast really does not need much computing power nor bandwidth, i.e. you can run
 
 
 ### DNS entries
 ### DNS entries
 
 
-You need to create a manual entry in your DNS server (read 'Router' for most home users). `vtuner.com` (more specifically `*.vtuner.com`) should point to the machine YCast is running on. Alternatively, in case you only want to forward specific vendors, the following entries may be configured:
+You need to create a manual entry in your DNS server (read 'Router' for most home users). The `*.vtuner.com` domain should point to the machine YCast is running on.
+Specifically the following entries may be configured instead of a wildcard entry:
 
 
   * Yamaha AVRs: `radioyamaha.vtuner.com` (and optionally `radioyamaha2.vtuner.com`)
   * Yamaha AVRs: `radioyamaha.vtuner.com` (and optionally `radioyamaha2.vtuner.com`)
   * Onkyo AVRs: `onkyo.vtuner.com` (and optionally `onkyo2.vtuner.com`)
   * Onkyo AVRs: `onkyo.vtuner.com` (and optionally `onkyo2.vtuner.com`)
   * Denon/Marantz AVRs: `denon.vtuner.com` (and optionally `denon2.vtuner.com`)
   * Denon/Marantz AVRs: `denon.vtuner.com` (and optionally `denon2.vtuner.com`)
+  * Grundig radios: `grundig.vtuner.com`, `grundig.radiosetup.com` (and optionally `grundig2.vtuner.com` and `grundig2.radiosetup.com`)
 
 
 
 
 ### Running the server
 ### Running the server

+ 11 - 9
ycast/radiobrowser.py

@@ -5,6 +5,7 @@ from ycast import __version__
 import ycast.vtuner as vtuner
 import ycast.vtuner as vtuner
 import ycast.generic as generic
 import ycast.generic as generic
 
 
+API_ENDPOINT = "http://all.api.radio-browser.info"
 MINIMUM_COUNT_GENRE = 5
 MINIMUM_COUNT_GENRE = 5
 MINIMUM_COUNT_COUNTRY = 5
 MINIMUM_COUNT_COUNTRY = 5
 MINIMUM_COUNT_LANGUAGE = 5
 MINIMUM_COUNT_LANGUAGE = 5
@@ -22,12 +23,12 @@ def get_json_attr(json, attr):
 
 
 class Station:
 class Station:
     def __init__(self, station_json):
     def __init__(self, station_json):
-        self.id = generic.generate_stationid_with_prefix(get_json_attr(station_json, 'id'), ID_PREFIX)
+        self.id = generic.generate_stationid_with_prefix(get_json_attr(station_json, 'stationuuid'), ID_PREFIX)
         self.name = get_json_attr(station_json, 'name')
         self.name = get_json_attr(station_json, 'name')
         self.url = get_json_attr(station_json, 'url')
         self.url = get_json_attr(station_json, 'url')
         self.icon = get_json_attr(station_json, 'favicon')
         self.icon = get_json_attr(station_json, 'favicon')
         self.tags = get_json_attr(station_json, 'tags').split(',')
         self.tags = get_json_attr(station_json, 'tags').split(',')
-        self.country = get_json_attr(station_json, 'country')
+        self.countrycode = get_json_attr(station_json, 'countrycode')
         self.language = get_json_attr(station_json, 'language')
         self.language = get_json_attr(station_json, 'language')
         self.votes = get_json_attr(station_json, 'votes')
         self.votes = get_json_attr(station_json, 'votes')
         self.codec = get_json_attr(station_json, 'codec')
         self.codec = get_json_attr(station_json, 'codec')
@@ -35,7 +36,7 @@ class Station:
 
 
     def to_vtuner(self):
     def to_vtuner(self):
         return vtuner.Station(self.id, self.name, ', '.join(self.tags), self.url, self.icon,
         return vtuner.Station(self.id, self.name, ', '.join(self.tags), self.url, self.icon,
-                              self.tags[0], self.country, self.codec, self.bitrate, None)
+                              self.tags[0], self.countrycode, self.codec, self.bitrate, None)
 
 
     def get_playable_url(self):
     def get_playable_url(self):
         try:
         try:
@@ -49,7 +50,7 @@ def request(url):
     logging.debug("Radiobrowser API request: %s", url)
     logging.debug("Radiobrowser API request: %s", url)
     headers = {'content-type': 'application/json', 'User-Agent': generic.USER_AGENT + '/' + __version__}
     headers = {'content-type': 'application/json', 'User-Agent': generic.USER_AGENT + '/' + __version__}
     try:
     try:
-        response = requests.get('http://www.radio-browser.info/webservice/json/' + url, headers=headers)
+        response = requests.get(API_ENDPOINT + '/json/' + url, headers=headers)
     except requests.exceptions.ConnectionError as err:
     except requests.exceptions.ConnectionError as err:
         logging.error("Connection to Radiobrowser API failed (%s)", err)
         logging.error("Connection to Radiobrowser API failed (%s)", err)
         return {}
         return {}
@@ -71,7 +72,7 @@ def search(name, limit=DEFAULT_STATION_LIMIT):
     stations = []
     stations = []
     stations_json = request('stations/search?order=name&reverse=false&limit=' + str(limit) + '&name=' + str(name))
     stations_json = request('stations/search?order=name&reverse=false&limit=' + str(limit) + '&name=' + str(name))
     for station_json in stations_json:
     for station_json in stations_json:
-        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == '1':
+        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
             stations.append(Station(station_json))
             stations.append(Station(station_json))
     return stations
     return stations
 
 
@@ -122,7 +123,7 @@ def get_stations_by_country(country):
     stations = []
     stations = []
     stations_json = request('stations/search?order=name&reverse=false&countryExact=true&country=' + str(country))
     stations_json = request('stations/search?order=name&reverse=false&countryExact=true&country=' + str(country))
     for station_json in stations_json:
     for station_json in stations_json:
-        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == '1':
+        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
             stations.append(Station(station_json))
             stations.append(Station(station_json))
     return stations
     return stations
 
 
@@ -131,7 +132,7 @@ def get_stations_by_language(language):
     stations = []
     stations = []
     stations_json = request('stations/search?order=name&reverse=false&languageExact=true&language=' + str(language))
     stations_json = request('stations/search?order=name&reverse=false&languageExact=true&language=' + str(language))
     for station_json in stations_json:
     for station_json in stations_json:
-        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == '1':
+        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
             stations.append(Station(station_json))
             stations.append(Station(station_json))
     return stations
     return stations
 
 
@@ -140,7 +141,7 @@ def get_stations_by_genre(genre):
     stations = []
     stations = []
     stations_json = request('stations/search?order=name&reverse=false&tagExact=true&tag=' + str(genre))
     stations_json = request('stations/search?order=name&reverse=false&tagExact=true&tag=' + str(genre))
     for station_json in stations_json:
     for station_json in stations_json:
-        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == '1':
+        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
             stations.append(Station(station_json))
             stations.append(Station(station_json))
     return stations
     return stations
 
 
@@ -149,6 +150,7 @@ def get_stations_by_votes(limit=DEFAULT_STATION_LIMIT):
     stations = []
     stations = []
     stations_json = request('stations?order=votes&reverse=true&limit=' + str(limit))
     stations_json = request('stations?order=votes&reverse=true&limit=' + str(limit))
     for station_json in stations_json:
     for station_json in stations_json:
-        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == '1':
+        print(station_json)
+        if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
             stations.append(Station(station_json))
             stations.append(Station(station_json))
     return stations
     return stations

+ 44 - 17
ycast/server.py

@@ -73,6 +73,8 @@ def get_stations_page(stations, request):
 def get_paged_elements(items, requestargs):
 def get_paged_elements(items, requestargs):
     if requestargs.get('startitems'):
     if requestargs.get('startitems'):
         offset = int(requestargs.get('startitems')) - 1
         offset = int(requestargs.get('startitems')) - 1
+    elif requestargs.get('startItems'):
+        offset = int(requestargs.get('startItems')) - 1
     elif requestargs.get('start'):
     elif requestargs.get('start'):
         offset = int(requestargs.get('start')) - 1
         offset = int(requestargs.get('start')) - 1
     else:
     else:
@@ -82,6 +84,8 @@ def get_paged_elements(items, requestargs):
         return []
         return []
     if requestargs.get('enditems'):
     if requestargs.get('enditems'):
         limit = int(requestargs.get('enditems'))
         limit = int(requestargs.get('enditems'))
+    elif requestargs.get('endItems'):
+        limit = int(requestargs.get('endItems'))
     elif requestargs.get('start') and requestargs.get('howmany'):
     elif requestargs.get('start') and requestargs.get('howmany'):
         limit = int(requestargs.get('start')) - 1 + int(requestargs.get('howmany'))
         limit = int(requestargs.get('start')) - 1 + int(requestargs.get('howmany'))
     else:
     else:
@@ -113,7 +117,8 @@ def vtuner_redirect(url):
     return redirect(url, code=302)
     return redirect(url, code=302)
 
 
 
 
-@app.route('/setupapp/<path:path>')
+@app.route('/setupapp/<path:path>',
+           methods=['GET', 'POST'])
 def upstream(path):
 def upstream(path):
     if request.args.get('token') == '0':
     if request.args.get('token') == '0':
         return vtuner.get_init_token()
         return vtuner.get_init_token()
@@ -121,14 +126,22 @@ def upstream(path):
         return station_search()
         return station_search()
     if 'statxml.asp' in path and request.args.get('id'):
     if 'statxml.asp' in path and request.args.get('id'):
         return get_station_info()
         return get_station_info()
+    if 'navXML.asp' in path:
+        return radiobrowser_landing()
+    if 'FavXML.asp' in path:
+        return my_stations_landing()
     if 'loginXML.asp' in path:
     if 'loginXML.asp' in path:
         return landing()
         return landing()
     logging.error("Unhandled upstream query (/setupapp/%s)", path)
     logging.error("Unhandled upstream query (/setupapp/%s)", path)
     abort(404)
     abort(404)
 
 
 
 
-@app.route('/', defaults={'path': ''})
-@app.route('/' + PATH_ROOT + '/', defaults={'path': ''})
+@app.route('/',
+           defaults={'path': ''},
+           methods=['GET', 'POST'])
+@app.route('/' + PATH_ROOT + '/',
+           defaults={'path': ''},
+           methods=['GET', 'POST'])
 def landing(path=''):
 def landing(path=''):
     page = vtuner.Page()
     page = vtuner.Page()
     page.add(vtuner.Directory('Radiobrowser', url_for('radiobrowser_landing', _external=True), 4))
     page.add(vtuner.Directory('Radiobrowser', url_for('radiobrowser_landing', _external=True), 4))
@@ -141,19 +154,22 @@ def landing(path=''):
     return page.to_string()
     return page.to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_MY_STATIONS + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_MY_STATIONS + '/',
+           methods=['GET', 'POST'])
 def my_stations_landing():
 def my_stations_landing():
     directories = my_stations.get_category_directories()
     directories = my_stations.get_category_directories()
     return get_directories_page('my_stations_category', directories, request).to_string()
     return get_directories_page('my_stations_category', directories, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_MY_STATIONS + '/<directory>')
+@app.route('/' + PATH_ROOT + '/' + PATH_MY_STATIONS + '/<directory>',
+           methods=['GET', 'POST'])
 def my_stations_category(directory):
 def my_stations_category(directory):
     stations = my_stations.get_stations_by_category(directory)
     stations = my_stations.get_stations_by_category(directory)
     return get_stations_page(stations, request).to_string()
     return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/',
+           methods=['GET', 'POST'])
 def radiobrowser_landing():
 def radiobrowser_landing():
     page = vtuner.Page()
     page = vtuner.Page()
     page.add(vtuner.Directory('Genres', url_for('radiobrowser_genres', _external=True),
     page.add(vtuner.Directory('Genres', url_for('radiobrowser_genres', _external=True),
@@ -168,49 +184,57 @@ def radiobrowser_landing():
     return page.to_string()
     return page.to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/',
+           methods=['GET', 'POST'])
 def radiobrowser_countries():
 def radiobrowser_countries():
     directories = radiobrowser.get_country_directories()
     directories = radiobrowser.get_country_directories()
     return get_directories_page('radiobrowser_country_stations', directories, request).to_string()
     return get_directories_page('radiobrowser_country_stations', directories, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/<directory>')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_COUNTRY + '/<directory>',
+           methods=['GET', 'POST'])
 def radiobrowser_country_stations(directory):
 def radiobrowser_country_stations(directory):
     stations = radiobrowser.get_stations_by_country(directory)
     stations = radiobrowser.get_stations_by_country(directory)
     return get_stations_page(stations, request).to_string()
     return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_LANGUAGE + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_LANGUAGE + '/',
+           methods=['GET', 'POST'])
 def radiobrowser_languages():
 def radiobrowser_languages():
     directories = radiobrowser.get_language_directories()
     directories = radiobrowser.get_language_directories()
     return get_directories_page('radiobrowser_language_stations', directories, request).to_string()
     return get_directories_page('radiobrowser_language_stations', directories, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_LANGUAGE + '/<directory>')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_LANGUAGE + '/<directory>',
+           methods=['GET', 'POST'])
 def radiobrowser_language_stations(directory):
 def radiobrowser_language_stations(directory):
     stations = radiobrowser.get_stations_by_language(directory)
     stations = radiobrowser.get_stations_by_language(directory)
     return get_stations_page(stations, request).to_string()
     return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/',
+           methods=['GET', 'POST'])
 def radiobrowser_genres():
 def radiobrowser_genres():
     directories = radiobrowser.get_genre_directories()
     directories = radiobrowser.get_genre_directories()
     return get_directories_page('radiobrowser_genre_stations', directories, request).to_string()
     return get_directories_page('radiobrowser_genre_stations', directories, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/<directory>')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_GENRE + '/<directory>',
+           methods=['GET', 'POST'])
 def radiobrowser_genre_stations(directory):
 def radiobrowser_genre_stations(directory):
     stations = radiobrowser.get_stations_by_genre(directory)
     stations = radiobrowser.get_stations_by_genre(directory)
     return get_stations_page(stations, request).to_string()
     return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_POPULAR + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_RADIOBROWSER + '/' + PATH_RADIOBROWSER_POPULAR + '/',
+           methods=['GET', 'POST'])
 def radiobrowser_popular():
 def radiobrowser_popular():
     stations = radiobrowser.get_stations_by_votes()
     stations = radiobrowser.get_stations_by_votes()
     return get_stations_page(stations, request).to_string()
     return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_SEARCH + '/')
+@app.route('/' + PATH_ROOT + '/' + PATH_SEARCH + '/',
+           methods=['GET', 'POST'])
 def station_search():
 def station_search():
     query = request.args.get('search')
     query = request.args.get('search')
     if not query or len(query) < 3:
     if not query or len(query) < 3:
@@ -224,7 +248,8 @@ def station_search():
         return get_stations_page(stations, request).to_string()
         return get_stations_page(stations, request).to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_PLAY)
+@app.route('/' + PATH_ROOT + '/' + PATH_PLAY,
+           methods=['GET', 'POST'])
 def get_stream_url():
 def get_stream_url():
     stationid = request.args.get('id')
     stationid = request.args.get('id')
     if not stationid:
     if not stationid:
@@ -238,7 +263,8 @@ def get_stream_url():
     return vtuner_redirect(station.url)
     return vtuner_redirect(station.url)
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_STATION)
+@app.route('/' + PATH_ROOT + '/' + PATH_STATION,
+           methods=['GET', 'POST'])
 def get_station_info():
 def get_station_info():
     stationid = request.args.get('id')
     stationid = request.args.get('id')
     if not stationid:
     if not stationid:
@@ -261,7 +287,8 @@ def get_station_info():
     return page.to_string()
     return page.to_string()
 
 
 
 
-@app.route('/' + PATH_ROOT + '/' + PATH_ICON)
+@app.route('/' + PATH_ROOT + '/' + PATH_ICON,
+           methods=['GET', 'POST'])
 def get_station_icon():
 def get_station_icon():
     stationid = request.args.get('id')
     stationid = request.args.get('id')
     if not stationid:
     if not stationid:

+ 1 - 1
ycast/vtuner.py

@@ -137,7 +137,7 @@ class Station:
         ET.SubElement(item, 'Logo').text = self.icon
         ET.SubElement(item, 'Logo').text = self.icon
         ET.SubElement(item, 'StationFormat').text = self.genre
         ET.SubElement(item, 'StationFormat').text = self.genre
         ET.SubElement(item, 'StationLocation').text = self.location
         ET.SubElement(item, 'StationLocation').text = self.location
-        ET.SubElement(item, 'StationBandWidth').text = self.bitrate
+        ET.SubElement(item, 'StationBandWidth').text = str(self.bitrate)
         ET.SubElement(item, 'StationMime').text = self.mime
         ET.SubElement(item, 'StationMime').text = self.mime
         ET.SubElement(item, 'Relia').text = '3'
         ET.SubElement(item, 'Relia').text = '3'
         ET.SubElement(item, 'Bookmark').text = self.bookmark
         ET.SubElement(item, 'Bookmark').text = self.bookmark