initial commit

This commit is contained in:
milaq 2018-07-23 14:23:57 +02:00
commit bbc4e55e55
3 changed files with 193 additions and 0 deletions

78
README.md Normal file
View file

@ -0,0 +1,78 @@
# YCast
YCast is a self hosted replacement for the vTuner internet radio service which some Yamaha AVRs use.
It was developed for and tested with the __RX-Vx73__ series.
It _should_ also work for the following Yamaha AVR models:
* RX-Vx75
* RX-Vx77
* RX-Vx79
* RX-Vx81
YCast is for you if:
* You do not want to use a proprietary streaming service
* You are sick of loading and/or downtimes of the vRadio server
* You are unsure about the vTuner service's future
## Dependencies:
Python version: `3`
Python packages:
* `PyYAML`
## Usage
YCast really does not need much computing power nor bandwidth. It just serves the information to the AVR. The streaming
itself gets handled by the AVR directly, i.e. you can run it on a low-spec RISC machine like a Raspberry Pi.
* Create your initial `stations.yml`. The config follows a basic YAML structure (see below)
* Create a manual entry in your DNS server (read 'Router' for most home users) for:
`radioyamaha.vtuner.com`
to point to the local machine running YCast.
* Run `ycast.py` on the target machine.
### Station configuration
```
Category one name:
First awesome station name: first.awesome/station/URL
Second awesome station name: second.awesome/station/URL
Category two name:
Third awesome station name: third.awesome/station/URL
Fourth awesome station name: fourth.awesome/station/URL
```
You can also have a look at `stations.yml.example` for how it can be set up.
## Firewall rules
* The server running YCast does __not__ need internet access
* The Yamaha AVR needs access to the internet (i.e. to the station URLs you defined)
* The Yamaha AVR needs to reach port `80` of the machine running YCast
## Web redirects
You can (__and should__) change the `listen_port` and `listen_address` variables in `ycast.py` if you are already running a HTTP server on the target machine
and/or want to proxy or encase YCast access.
It is advised to use a proper webserver (e.g. Nginx) in front of YCast if you can.
Then, you also don't need to run YCast as root and can proxy the requests to YCast running on a higher port (>1024) listening only on `localhost`.
You _need_ to redirect the following URLs from your webserver to YCast (of course listening to requests to `radioyamaha.vtuner.com`):
* `/setupapp`
* `/ycast`
## Caveats
YCast was a quick and dirty project to lay the foundation for having a self hosted vTuner emulation.
It is a barebone service at the moment. It provides your AVR with the basic info it needs to play internet radio stations.
Maybe this will change in the future.
But for now just station names and URLs. No web-based management interface, no coverart, no fancy stuff.

22
stations.yml.example Normal file
View file

@ -0,0 +1,22 @@
Electronic:
Deep House Lounge: http://198.15.94.34:8006
Ibiza Sonica: http://s1.sonicabroadcast.com:7005/stream
Bassdrive: http://50.7.98.106:8200
SomaFM Fluid: http://ice1.somafm.com/fluid-128-mp3
Chillout:
Joint Radio: http://radio.jointil.net:9998
SomaFM DEF CON Radio: http://ice1.somafm.com/defcon-256-mp3
SomaFM Drone Zone: http://ice1.somafm.com/dronezone-256-mp3
SomaFM Mission Control: http://ice1.somafm.com/missioncontrol-128-mp3
The Jazz Groove: http://west-mp3-128.streamthejazzgroove.com
Radionomy Downbeat: http://streaming.radionomy.com/TempoOfTheDownbeat1
Casual:
76Radio: http://192.240.102.133:9566/stream
SomaFM Beat Blender: http://ice1.somafm.com/beatblender-128-mp3
Jazz Radio Electro Swing: http://jazz-wr04.ice.infomaniak.ch/jazz-wr04-128.mp3
SomaFM Groove Salad: http://ice1.somafm.com/groovesalad-256-mp3
SomaFM Lush: http://ice1.somafm.com/lush-128-mp3
Allzic Radio R&B: http://allzic10.ice.infomaniak.ch/allzic10.mp3
The UK 1940s Radio Station: http://91.121.134.23:8100/1

93
ycast.py Executable file
View file

@ -0,0 +1,93 @@
#!/usr/bin/env python3
import os
from http.server import BaseHTTPRequestHandler, HTTPServer
import xml.etree.cElementTree as etree
import yaml
listen_address = '0.0.0.0'
listen_port = 80
VTUNER_DNS = 'http://radioyamaha.vtuner.com'
VTUNER_INITURL = '/setupapp/Yamaha/asp/BrowseXML/loginXML.asp'
XMLHEADER = '<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>'
YCAST_LOCATION = 'ycast'
stations = {}
def get_stations():
global stations
ycast_dir = os.path.dirname(os.path.realpath(__file__))
with open(ycast_dir + '/stations.yml', 'r') as f:
stations = yaml.load(f)
def text_to_url(text):
return text.replace(' ', '%20')
def url_to_text(url):
return url.replace('%20', ' ')
class YCastServer(BaseHTTPRequestHandler):
def do_GET(self):
get_stations()
if self.path == '/' \
or self.path == '/' + YCAST_LOCATION \
or self.path == '/' + YCAST_LOCATION + '/'\
or self.path.startswith(VTUNER_INITURL):
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(bytes(XMLHEADER, 'utf-8'))
xml = self.create_root()
for category in sorted(stations, key=str.lower):
self.add_dir(xml, category,
VTUNER_DNS + ':' + str(listen_port) + '/' + YCAST_LOCATION + '/' + text_to_url(category))
self.wfile.write(bytes(etree.tostring(xml).decode('utf-8'), 'utf-8'))
elif self.path.startswith('/' + YCAST_LOCATION + '/'):
category = url_to_text(self.path[len(YCAST_LOCATION) + 2:].partition('?')[0])
if category not in stations:
self.send_error(404)
return
xml = self.create_root()
for station in sorted(stations[category], key=str.lower):
self.add_station(xml, station, stations[category][station])
self.send_response(200)
self.send_header('Content-type', 'text/html')
self.end_headers()
self.wfile.write(bytes(XMLHEADER, 'utf-8'))
self.wfile.write(bytes(etree.tostring(xml).decode('utf-8'), 'utf-8'))
else:
self.send_error(404)
def create_root(self):
return etree.Element('ListOfItems')
def add_dir(self, root, name, dest):
item = etree.SubElement(root, 'Item')
etree.SubElement(item, 'ItemType').text = 'Dir'
etree.SubElement(item, 'Title').text = name
etree.SubElement(item, 'UrlDir').text = dest
return item
def add_station(self, root, name, url):
item = etree.SubElement(root, 'Item')
etree.SubElement(item, 'ItemType').text = 'Station'
etree.SubElement(item, 'StationName').text = name
etree.SubElement(item, 'StationUrl').text = url
return item
get_stations()
server = HTTPServer((listen_address, listen_port), YCastServer)
print('YCast server listening on %s:%s' % (listen_address, listen_port))
try:
server.serve_forever()
except KeyboardInterrupt:
pass
print('YCast server shutting down')
server.server_close()