Comments (3)
And it turns out to be "complicated"...
The good news: It's not the ID.
The bad news: It's everything else
This device breaks on the station list if:
- the
Bookmark
tag is empty - there is any additional tag in the station list besides
ItemType
,StationId
,StationUrl
andBookmark
Even when stripped down, it fails playing the station, because it does not call StationUrl
by itself but instead issues a search on the submitted ID to get more details. It's the same when playing "the last tuned station" on startup:
/setupapp/grundig/asp/BrowseXML/Search.asp?sSearchtype=3&Search=RB_f91aa755-2979-451e-a4fe-1393f&mac=c497f96ed9e56dad9aa7d69a827c1d6a&dlang=eng&fver=1&ven=grn6
And it expects a return of this kind with all the additional values that need to be missing in the first list:
GET /setupapp/grundig/asp/BrowseXML/Search.asp?sSearchtype=3&Search=5952&mac=7e1234567871a47e5c907f5e6a9164c2&dlang=eng&fver=1&ven=grn6
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<ListOfItems>
<ItemCount>1</ItemCount>
<Item>
<ItemType>Previous</ItemType>
<UrlPrevious>http://grundig.vtuner.com/setupapp/grundig/asp/browseXML/loginXML.asp?gofile=</UrlPrevious>
<UrlPreviousBackUp>http://grundig2.vtuner.com/setupapp/grundig/asp/browseXML/loginXML.asp?gofile=</UrlPreviousBackUp>
</Item>
<Item>
<ItemType>Station</ItemType>
<StationId>5952</StationId>
<StationName>Radio Swiss Classic</StationName>
<StationUrl>http://grundig.vtuner.com/setupapp/grundig/asp/func/dynamODFS.asp?ex45v=&id=5952</StationUrl>
<StationDesc>Das Klassikradio zum Entspannen</StationDesc>
<StationFormat>Classical</StationFormat>
<StationLocation>Basel Switzerland</StationLocation>
<StationBandWidth>128</StationBandWidth>
<StationMime>MP3</StationMime>
<StationProto>HTTP</StationProto>
<Relia>5</Relia>
<Bookmark>http://grundig.vtuner.com/setupapp/grundig/asp/browseXML/AddFav.asp?empty=&stationid=5952</Bookmark>
</Item>
</ListOfItems>
Since this "search" is not implemented yet, it fails with a 404 and results in "network error, please retry".
As soon as this reply is there, it issues ANOTHER call get the final channel URL within a special Content-Type. It seems weird, that this call is NOT protected by any token, so one could try to iterate through the station numbers.
I have yet to try, if this dynamic URL, that is submitted by the previous call can be exchanged against the true station URL, successfully skipping the last step:
GET /setupapp/grundig/asp/func/dynamODFS.asp?ex45v=&id=5952
HTTP/1.1 200 OK
Cache-Control: private
Content-Type: audio/x-mpegurl
Server: Microsoft-IIS/10.0
Set-Cookie: ASPSESSIONIDCAQABSSS=FIMGIPPCMLPKDPAPBLOKEPHH; path=/
X-Powered-By: ASP.NET
Date: Fri, 29 Dec 2023 10:08:37 GMT
Connection: close
Content-Length: 41
http://stream.srg-ssr.ch/m/rsc_de/mp3_128
Given this, it does not wonder that encoded characters within the station name are not decoded for the display either. So a station
<StationName>Retro FM (M érida) - 103.1 FM - XHPYM-FM - Cadena RASA - M érida, Yucat án</StationName>
displays as
Retro FM (M érida) - 103.1 FM - XHPYM-FM - Cadena RASA - M érida, Yucat án
Since the reply is marked as UTF-8, the character encoding would be necessary for just " <, &, >" and might be superfluous by XML definition.
I guess this would need a big addition that is likely to break things for all others. :-(
Btw. What was the idea about prefixing the UID with additional characters? Aren't GUIDs meant to be "globally unique" by definition? Any current GUID creation routine is guarranteed to not create any duplicate.
from ycast.
And more complication, but with some brute force I got it working.
I could have noted in the previous post, that the ID is truncated to 32 characters by my device before it is placed into the search. Formatted UUIDs are too long, adding a prefix does not make it better.
If I am forced to use UUIDs and I'm forced to use a prefix, then I have to shorten the URL.
I did this, by converting the UUID string into a real hex number (32 byte as string, 16 byte as binary) and then base64 encode that -> 24 characters for the ID + 3 for the prefix -> 27 characters in total. That easily fits into the 32-character limit of my device. This requires importing the base64 library.
To encode and decode the UUID on the fly, I had to pimp two functions in generic.py
in a way that should not break the current function:
import base64
def generate_stationid_with_prefix(uid, prefix):
if not prefix or len(prefix) != 2:
logging.error("Invalid station prefix length (must be 2)")
return None
if not uid:
logging.error("Missing station id for full station id generation")
return None
#if UUID formatted string, compress by base64 encoding
if len(uid) == 36 and uid[8] == "-":
uid = base64.b64encode(bytes.fromhex(uid.replace("-",""))).decode('ascii')
return str(prefix) + '_' + str(uid)
and
def get_stationid_without_prefix(uid):
if len(uid) < 4:
logging.error("Could not extract stationid (Invalid station id length)")
return None
if uid[2] == "_":
_uid = uid[3:]
#check for String in UUID format: RB_c86d26e1-0fdd-48e8-b362-67145770d981
if len(_uid) == 36 and _uid[8] == "-":
return _uid
#check for string in HEX() format --> convert to UUID string: RB_c86d26e10fdd48e8b36267145770d981
if len(_uid) == 32 and all(c in "0123456789abcdefABCDEF" for c in _uid):
return "-".join([_uid[:8],_uid[8:12],_uid[12:16],_uid[16:20],_uid[20:32]])
#check for base64 encoded UUID: RB_yG0m4Q/dSOizYmcUV3DZgQ==
if len(_uid) == 24 and all(c in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/= " for c in _uid):
#account for dumb devices not URLENCODING IDs in parameters by default ...
_uid = _uid.replace(" ","+")
_uid = bytes(base64.b64decode(_uid)).hex()
return "-".join([_uid[:8],_uid[8:12],_uid[12:16],_uid[16:20],_uid[20:32]])
#if nothing else, return entire remainder: RB_67145770d981
return _uid
# > 3 characters but no prefix? Return original: 67145770d981
return uid
I added a new function in radiobrowser.py
that delivers a station-list when queried with one or more UUIDs.
def search_uid(uid):
stations = []
stations_json = request('stations/byuuid/' + str(uid))
for station_json in stations_json:
if SHOW_BROKEN_STATIONS or get_json_attr(station_json, 'lastcheckok') == 1:
stations.append(Station(station_json))
return stations
and I added a case to get this called within server.py
when the URL contains "Search". This most likely specific to my device.
def upstream(path):
if request.args.get('token') == '0':
return vtuner.get_init_token()
if request.args.get('search'):
return station_search()
if 'statxml.asp' in path and request.args.get('id'):
return get_station_info()
if 'Search.asp' in path and request.args.get('Search'):
return station_by_id()
if 'navXML.asp' in path:
return radiobrowser_landing()
if 'FavXML.asp' in path:
return my_stations_landing()
if 'loginXML.asp' in path:
return landing()
logging.error("Unhandled upstream query (/setupapp/%s)", path)
abort(404)
def station_by_id():
query = request.args.get('Search')
if not query or len(query) < 3:
page = vtuner.Page()
page.add(vtuner.Display("Search query too short"))
page.set_count(1)
return page.to_string()
else:
uuid = generic.get_stationid_without_prefix(query) # should take care to explode compressed UUids
stations = radiobrowser.search_uid(uuid)
return get_stations_page(stations, request, details=True).to_string()
You might note the additional parameter details=True
. I invented that to either produce a reduced set of tags in the station list vs. a full set when queried by ID. This is definitely specific to my device and would have to get properly masked to "only when Grundig / and or specific URL parameters". Since all of the classes in vtuner.py
are looped with their specific to_xml
, I had to invent this dummy "details" to all of them, but I just used it in the Station.to_xml
class Station:
def __init__(self, uid, name, description, url, icon, genre, location, mime, bitrate, bookmark):
self.uid = uid
self.name = name
self.description = description
self.url = strip_https(url)
self.trackurl = None
self.icon = icon
self.genre = genre
self.location = location
self.mime = mime
self.bitrate = bitrate
self.bookmark = bookmark
def set_trackurl(self, url):
self.trackurl = url
def to_xml(self, details=False):
item = ET.Element('Item')
logging.debug("Station.to_xml() details: %s", details)
ET.SubElement(item, 'ItemType').text = 'Station'
ET.SubElement(item, 'StationId').text = self.uid
ET.SubElement(item, 'StationName').text = self.name
if self.trackurl:
ET.SubElement(item, 'StationUrl').text = self.trackurl
else:
ET.SubElement(item, 'StationUrl').text = self.url
if details:
ET.SubElement(item, 'StationDesc').text = self.description
ET.SubElement(item, 'Logo').text = self.icon
ET.SubElement(item, 'StationFormat').text = self.genre
ET.SubElement(item, 'StationLocation').text = self.location
ET.SubElement(item, 'StationBandWidth').text = str(self.bitrate)
ET.SubElement(item, 'StationMime').text = self.mime
ET.SubElement(item, 'Relia').text = '3'
#ET.SubElement(item, 'Bookmark').text = self.bookmark
ET.SubElement(item, 'Bookmark').text = self.url
return item
Ah, yes - I faked the Bookmark
entry. Does not "work", but I could not leave it empty for my device...
This made my code work for the "Grundig SonoClock 890A", at least with the RadioBrowser stations. I'm still thinking about how to handle my_stations
.
This certainly does not make a good pull request by now, but eventually someone can still comment about it if/how this eventually could enhance the current codebase.
Btw: I stumbled over a possible bug in get_stations_page
that replaces the URL to the station Icons, overwriting the RB supplied URL. Might be intended, but maybe this should be done only in case of station_tracking
(indentation problem). But even then, parameters in URLs should get URLENCODED. You never know for sure what the value contains. A base64 encoded UUID for instance :-)
The "search" functions use unencoded search arguments too. Might break easily.
from ycast.
Some final tweaks:
The entitiy decoding in vtuner.py Class Page:
did not work out. That one does it right. This does not guarantee that the characters are correct in the display - most foreign (Unicode) characters are still "?" but at least the Ӓ
are gone now and at least some umlauts and accented characters do show correct:
return XML_HEADER + ET.tostring(self.to_xml(), encoding='unicode')
After setting some defaults in case a value was not defined, my device now accepts the bookmarked entries too. It definitely does NOT like empty tags:
if details:
ET.SubElement(item, 'StationDesc').text = self.description
ET.SubElement(item, 'Logo').text = self.icon
ET.SubElement(item, 'StationFormat').text = self.genre
ET.SubElement(item, 'StationLocation').text = self.location if self.location != None else "XX"
ET.SubElement(item, 'StationBandWidth').text = str(self.bitrate) if self.bitrate != None else "128"
ET.SubElement(item, 'StationMime').text = self.mime if self.mime != None else "mp3"
ET.SubElement(item, 'Relia').text = '3'
ET.SubElement(item, 'Bookmark').text = self.bookmark if self.bookmark != None else self.url
I still could need some queries from other devices to confine all those changes to my model and not disturb others...
from ycast.
Related Issues (20)
- Confirmed Technics Ottava SC-C70 HOT 3
- What is roughly needed to implement the bookmark functionality? HOT 2
- Ycast
- How to create a manual entry in your DNS server? HOT 2
- Ycast Failure With Aluratek AIRMM01F Internet Clock/Radio HOT 2
- Problem with install ycast on router keenetic HOT 2
- Confirmed working on Marantz NA7004
- Ycast with Cabasse wireless speaker not working HOT 2
- Marantz NR-1504 is also supported
- Stream station image not loaded
- installation help HOT 4
- Always a 404 result HOT 4
- Noxon 2 Audio, not working
- Pi-Hole setup for Yamaha ycast HOT 1
- No retry if the randomly picked server all.api.radio-browser.info fails. HOT 7
- Confirmed working Marantz NR-1604 and NR-1607
- Confirmed working Denon AVR-X1100W
- Working but no radio station images HOT 2
- YCast in a virtual environment HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from ycast.