Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
9d903a1
Allow favicons in favourites
ThHanika Jan 23, 2022
107ce5c
add recently stations in favourites
ThHanika Jan 24, 2022
a0cba14
refactor: add recently stations in favourites
ThHanika Jan 24, 2022
bda5f15
bug
ThHanika Jan 24, 2022
53d88a4
init jonnieZG/YCast
ThHanika Jan 24, 2022
b0bb8c5
correcturen
ThHanika Jan 25, 2022
36b7f69
only station with FAVICON
ThHanika Jan 25, 2022
a490e13
refactor
ThHanika Jan 25, 2022
7f76a50
refactor
ThHanika Jan 25, 2022
e50c93e
refactor
ThHanika Jan 25, 2022
4bc3bed
filter.yml get global filter on station_lists
ThHanika Jan 26, 2022
4d2a8b2
using bas64 coded uuid, favicon refernced as favicon-url
ThHanika Jan 27, 2022
2179c48
used url_resolved for yamaha app
ThHanika Jan 28, 2022
838d2f3
manual
ThHanika Jan 28, 2022
cdb1c93
5 recently stations added in first page (experimental)
ThHanika Jan 28, 2022
f38b1f5
5 recently stations added in first page (experimental) bugfix missing…
ThHanika Jan 28, 2022
a80d6f1
refactor my_recentlystation
ThHanika Jan 29, 2022
415562c
display max 5 of my voted stations by clicks in landing page
ThHanika Jan 29, 2022
478f783
refactor filter modul
ThHanika Jan 29, 2022
3601528
logging reduced
ThHanika Jan 29, 2022
c818934
filter languages country directories and reduce genre by higher level…
ThHanika Jan 29, 2022
8766143
explanations in README.md
ThHanika Jan 29, 2022
f9c1c7e
radiobrowser limits configurable in filter.yml
ThHanika Jan 30, 2022
9eefaa0
radiobrowser limits configurable in filter.yml
ThHanika Jan 30, 2022
1338430
refaktor to use workingdirectory
ThHanika Jan 31, 2022
48072f9
optimize for using as root-service with confitured workingdirectory
ThHanika Feb 1, 2022
eea9f87
optimize for using as root-service with confitured workingdirectory
ThHanika Feb 1, 2022
9b4a5f4
refactor and add a spacer (undefined type) for items (workaround for …
ThHanika Feb 2, 2022
3222c2d
zwischenstand
ThHanika Feb 7, 2022
ac36745
zwischenstand
ThHanika Feb 8, 2022
c18ebca
FE prototyping
ThHanika Feb 9, 2022
d4a221b
FE prototyping
ThHanika Feb 10, 2022
8f28a1a
FE prototyping
ThHanika Feb 10, 2022
221db3c
FE save stations.yml
ThHanika Feb 10, 2022
cc24f07
FE save imediately stations.yml after setting
ThHanika Feb 11, 2022
61ef773
FE save imediately stations.yml after setting
ThHanika Feb 11, 2022
ef2bc83
setup files
ThHanika Feb 11, 2022
d9b44f6
refactor web
ThHanika Feb 12, 2022
696817a
refactor web
ThHanika Feb 12, 2022
3a94ea1
fix: bug with empty icon-url
ThHanika Feb 13, 2022
972db53
Filter/limits loaded at startup from config file. File is not reread …
Jan 18, 2023
09dd363
Added Dockerfile.
Jan 19, 2023
d6dfe30
Add files via upload
ThHanika Jan 19, 2023
333b465
Update README.md
ThHanika Jan 19, 2023
e4b2e24
Update README.md
ThHanika Jan 19, 2023
01a2a26
Merge pull request #2 from superclass/master
ThHanika Jan 19, 2023
a54bdf0
Merge pull request #1 from ThHanika/master
superclass Jan 20, 2023
f716d8e
Overhaul of filter/limits.
Jan 26, 2023
7f91239
Update README.md
superclass Jan 27, 2023
cabcb71
Merge pull request #3 from superclass/master
ThHanika Feb 24, 2023
ade87b5
Update Dockerfile
panosnl Jan 2, 2024
c81706c
Update Dockerfile
panosnl Jan 2, 2024
0d4f082
Update Dockerfile
panosnl Jan 2, 2024
5f4e18d
Merge pull request #4 from panosnl/panosnl-patch-Docker
ThHanika Jan 9, 2024
487d00c
Add entry_points in setup.py to enable installation via pipx
mriedel87 Jan 13, 2024
ec9bf51
Merge pull request #5 from mriedel87/setup
ThHanika Jan 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ build
dist
*.egg-info
.idea
.vscode
*.iml
*.pyc
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
include README.md
include LICENCE.txt
recursive-include ycast/templates *
recursive-include ycast/static *
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
<img src="https://image.ibb.co/iBY6hq/yamaha.png" width="600">

# YCast
# YCast (advanced)

[![PyPI latest version](https://img.shields.io/pypi/v/ycast?color=success)](https://pypi.org/project/ycast/) [![GitHub latest version](https://img.shields.io/github/v/release/milaq/YCast?color=success&label=github&sort=semver)](https://github.com/milaq/YCast/releases) [![Python version](https://img.shields.io/pypi/pyversions/ycast)](https://www.python.org/downloads/) [![License](https://img.shields.io/pypi/l/ycast)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![GitHub issues](https://img.shields.io/github/issues/milaq/ycast)](https://github.com/milaq/YCast/issues)

[Get it via PyPI](https://pypi.org/project/ycast/)
[Download from GitHub](https://github.com/THanika/YCast/releases)

[Download from GitHub](https://github.com/milaq/YCast/releases)
[Issue tracker](https://github.com/THanika/YCast/issues)

[Issue tracker](https://github.com/milaq/YCast/issues)
#### pip3 install git+https://github.com/ThHanika/YCast

### The advanced feature:
* Icons in my favorites list 'stations.yml' (the icon URL can be appended after the pipe character '|')
* recently visited radio stations are stored in /.yast/resently.yml (compatible with stations.yml, for easy editing of your favorites and pasting into stations.yml)
* global filter/limits configurable file ./ycast/filter.yml (with this you can globally reduce the radio stations according to your interests). The filter can be modified at runtime useing a REST API (/control/filter...), see below.
* 5 frequently used radio stations can be selected on the target page (self-learning algorithm based on frequency of station selection)
* web frontend to setup your favorites

<img src="https://github.com/ThHanika/YCast/blob/master/webFrontEnd.png" width="400">

YCast is a self hosted replacement for the vTuner internet radio service which many AVRs use.
It emulates a vTuner backend to provide your AVR with the necessary information to play self defined categorized internet radio stations and listen to Radio stations listed in the [Community Radio Browser index](http://www.radio-browser.info).
Expand Down Expand Up @@ -117,7 +126,7 @@ You can redirect all traffic destined for the original request URL (e.g. `radioy
__Attention__: Do not rewrite the requests transparently. YCast expects the complete URL (i.e. including `/ycast` or `/setupapp`). It also need an intact `Host` header; so if you're proxying YCast you need to pass the original header on. For Nginx, this can be accomplished with `proxy_set_header Host $host;`.

In case you are using (or plan on using) Nginx to proxy requests, have a look at [this example](examples/nginx-ycast.conf.example).
This can be used together with [this systemd service example](examples/ycast.service.example) for a fully functional deployment.
This can be used together with [this systemd service example](examples/ycast.service.example_ycast) for a fully functional deployment.

#### With WSGI

Expand All @@ -139,6 +148,20 @@ Category two name:

You can also have a look at the provided [example](examples/stations.yml.example) to better understand the configuration.

### Filter/limits
As the amount of stations can be overwhelming on a AV receiver interface Ycast allows for filtering. The filter configuration file .ycast/filter.yml allows to filter stations based on a whitelist / blacklist. The contents of this list specifies which attributes to filter on. Look at the provided [example](examples/filter.yml.example) for the details.

The limits allow to filter out genres, countries and languages that fail to have a certain amount of items. It also sets the default station limit for search and votes and allows to show or hide broken stations. Defaults are as follows:
* MINIMUM_COUNT_GENRE : 40
* MINIMUM_COUNT_COUNTRY : 5
* MINIMUM_COUNT_LANGUAGE : 5
* DEFAULT_STATION_LIMIT : 200
* SHOW_BROKEN_STATIONS : False

You can set your own values in filter.xml by adding these attributes and values in the limits list. The filter file is not reread automatically when modified while the server is running. Send a HUP signal to trigger but it's preferred to use the api (see below) to modify the lists.

The current filters/limits can be queried through a REST API by calling the GET method on /control/filter/whitelist, /control/filter/blacklist and /control/filter/limits. They can be modified by using the POST method an posting a JSON with the items to modify. Specifying a null value for an item will delete it from the list or, in the case of the limits, reset it to its default.

## Firewall rules

* Your AVR needs access to the internet.
Expand Down
69 changes: 69 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#
# Docker Buildfile for the ycast-docker container based on alpine linux - about 41.4MB
# put dockerfile and bootstrap.sh in same directory and build or enter
# docker build https://github.com/ThHanika/YCast.git#:docker
#
FROM alpine:latest

#
# Variables
# YC_VERSION version of ycast software
# YC_STATIONS path an name of the indiviudual stations.yml e.g. /ycast/stations/stations.yml
# YC_DEBUG turn ON or OFF debug output of ycast server else only start /bin/sh
# YC_PORT port ycast server listens to, e.g. 80
#
ENV YC_VERSION master
ENV YC_STATIONS /opt/ycast/stations.yml
ENV YC_DEBUG OFF
ENV YC_PORT 80

#
# Upgrade alpine Linux, install python3 and dependencies for pillow - alpine does not use glibc
# pip install needed modules for ycast
#
RUN apk --no-cache update && \
apk --no-cache upgrade && \
apk add --no-cache python3 && \
apk add --no-cache py3-pip && \
apk add --no-cache zlib-dev && \
apk add --no-cache jpeg-dev && \
apk add --no-cache build-base && \
apk add --no-cache python3-dev && \
pip3 install --no-cache-dir requests --break-system-packages && \
pip3 install --no-cache-dir flask --break-system-packages && \
pip3 install --no-cache-dir PyYAML --break-system-packages && \
pip3 install --no-cache-dir Pillow --break-system-packages && \
pip3 install --no-cache-dir olefile --break-system-packages && \
mkdir -p /opt/ycast/YCast-master && \
apk del --no-cache python3-dev && \
apk del --no-cache build-base && \
apk del --no-cache zlib-dev && \
apk add --no-cache curl
# download ycast tar.gz and extract it in ycast Directory
RUN curl -L https://codeload.github.com/superclass/YCast/tar.gz/master \
| tar xvzC /opt/ycast
# delete unneeded stuff
RUN apk del --no-cache curl && \
find /usr/lib -name \*.pyc -exec rm -f {} \; && \
find /usr/lib -type f -name \*.exe -exec rm -f {} \;

#
# Set Workdirectory on ycast folder
#
WORKDIR /opt/ycast/YCast-${YC_VERSION}

#
# Copy bootstrap.sh to /opt
#
COPY bootstrap.sh /opt

#
# Docker Container should be listening for AVR on port 80
#
EXPOSE ${YC_PORT}/tcp

#
# Start bootstrap on Container start
#
RUN ["chmod", "+x", "/opt/bootstrap.sh"]
ENTRYPOINT ["/opt/bootstrap.sh"]
24 changes: 24 additions & 0 deletions docker/bootstrap.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/sh

#Bootstrap File für ycast docker container
#Variables
#YC_VERSION version of ycast software
#YC_STATIONS path an name of the indiviudual stations.yml e.g. /ycast/stations/stations.yml
#YC_DEBUG turn ON or OFF debug output of ycast server else only start /bin/sh
#YC_PORT port ycast server listens to, e.g. 80

if [ "$YC_DEBUG" = "OFF" ]; then
/usr/bin/python3 -m ycast -c $YC_STATIONS -p $YC_PORT

elif [ "$YC_DEBUG" = "ON" ]; then
/usr/bin/python3 -m ycast -c $YC_STATIONS -p $YC_PORT -d

else
/bin/sh

fi





24 changes: 24 additions & 0 deletions examples/apicalls.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
ENDPOINT=127.0.0.1
# API
# Get recently played stations
curl "http://${ENDPOINT}/api/stations?category=recently"
# Get highest rated stations
curl "http://${ENDPOINT}/api/stations?category=voted"
# Get stations by language, specify by language paramter (default=german)
curl "http://${ENDPOINT}/api/stations?category=language&language=dutch"
# Get stations by country, specify by country paramter (default=Germany)
curl "http://${ENDPOINT}/api/stations?category=country&country=The%20Netherlands"

# Ycast XML calls
curl "http://${ENDPOINT}/setupapp"
# Search by name
curl "http://${ENDPOINT}/ycast/search/?search=Pinguin"
# List top directories (Genres, Countries, Languages, Most Popular).
curl "http://${ENDPOINT}/ycast/radiobrowser/"
# Play station
curl "http://${ENDPOINT}/ycast/play?id=stationid"
# Get station info
curl "http://${ENDPOINT}/ycast/station?id=stationid"
curl "http://${ENDPOINT}/ycast/icon?id=stationid"
# Get station icon

53 changes: 53 additions & 0 deletions examples/filter.yml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Filters can be applied to the search results coming back from
# api.radio-browser.info. Results can either be whitelisted or blacklisted. The
# attributes in the whitelist are actually the attributes of the Station Struct
# defined here: https://de1.api.radio-browser.info/#Struct_station. The most
# useful ones to filter on are: codec, bitrate, language, languagecode. Ycast
# has a default whitelist for the lastcheckok attribute to be set (1) which
# indicates the stream is currently operational, as it does not make sense to
# return stations to the AV receiver that are broken. There are a few
# attributes that have a multi value string, the values are separated by a
# comma (,) (imo this should be a json list so clients don't have to parse the
# string, but it is as it is). The filter code will split these strings into a
# list first and then will try to match the the value from the whitelist or
# blacklist on any of the values in the list. The most interesting multi value
# attribute is the tags attribute which carries the genre(s) of the station.
# Unfortunately the values are rather free format, so there is no fixed list of
# genres to filter on and most stations indicate multiple genres. Attribute
# filter values can be either a single or multi-value. Multi-values
# should be entered in the filter file as a json list, either using the bracket
# ([]) or list (-) syntax. See the examples below.
#
# For the directory listings by Genre, Language, Country the following filter
# attributes will be applied: Genre: tags; Language: languagecodes; Country:
# country.

whitelist:
#Filter on the full country name:
#country: Germany
#Filter on any of the country codes specified in this [] list:
#countrycode: [ "DE","US","NO","GB" ]
#Filter on any of the language codes specified in this - list:
#languagecodes:
# - "en"
# - "no"
# Filter on bitrate:
#bitrate: 192
#To override the lastcheckok default (1) use this:
#lastcheckok: [ 0,1 ]
blacklist:
# Filter out stations with no favicon:
favicon: ''
# Filter on codecs:
#codec: ["AAC", "AAC+"]
# Limits can be applied, below are the hardcoded defautls, which can be overridden in this file.
#limits:
# The following limits will be applied to the directory listing of genre, country and language. Each item should contain this minium amount of entries to be returned.
#MINIMUM_COUNT_GENRE: 40
#MINIMUM_COUNT_COUNTRY: 5
#MINIMUM_COUNT_LANGUAGE: 5
# The default maximum amount of entries to return from search and search by
# votes.
#DEFAULT_STATION_LIMIT: 200
# Include broken stations in the result.
#SHOW_BROKEN_STATIONS: False
12 changes: 0 additions & 12 deletions examples/ycast.service.example

This file was deleted.

21 changes: 21 additions & 0 deletions examples/ycast.service.example_root
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[Unit]
Description=YCast internet radio service
After=network.target

[Service]
Type=simple
WorkingDirectory=/var/www/ycast

# StandardOutput=file:/var/www/ycast/service.log
# StandardError=file:/var/www/ycast/ycast.log

StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=ycast

Restart=always
RestartSec=130
ExecStart=/usr/bin/python3 -m ycast -c /var/www/ycast/stations.yml -d

[Install]
WantedBy=multi-user.target
15 changes: 15 additions & 0 deletions examples/ycast.service.example_ycast
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[Unit]
Description=YCast internet radio service
After=network.target

[Service]
Type=simple
User=ycast
Group=ycast
WorkingDirectory=/home/ycast
StandardOutput=file:/home/ycast/service.log
StandardError=file:/home/ycast/ycast.log
ExecStart=/usr/bin/python3 -m ycast -l 127.0.0.1 -p 8010 -d -c /home/ycast/.ycast/stations.yml

[Install]
WantedBy=multi-user.target
2 changes: 2 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
description='Self hosted vTuner internet radio service emulation',
long_description=long_description,
long_description_content_type="text/markdown",
include_package_data=True,
url='https://github.com/milaq/YCast',
entry_points={"console_scripts": ["ycast=ycast.__main__:launch_server"]},
license='GPLv3',
classifiers=[
'Development Status :: 4 - Beta',
Expand Down
Binary file added webFrontEnd.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion ycast/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.1.0'
__version__ = '1.3.0'
13 changes: 13 additions & 0 deletions ycast/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,18 @@
import argparse
import logging
import sys
import signal

from ycast import __version__
from ycast import server

logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S', level=logging.INFO)

def handler(signum, frame):
logging.info('Signal received: rereading filter config')
from ycast.my_filter import init_filter_file
init_filter_file()
signal.signal(signal.SIGHUP, handler)

def launch_server():
parser = argparse.ArgumentParser(description='vTuner API emulation')
Expand All @@ -23,6 +29,13 @@ def launch_server():
logging.debug("Debug logging enabled")
else:
logging.getLogger('werkzeug').setLevel(logging.WARNING)

# initialize important ycast parameters
from ycast.generic import init_base_dir
init_base_dir('/.ycast')
from ycast.my_filter import init_filter_file
init_filter_file()

server.run(arguments.config, arguments.address, arguments.port)


Expand Down
Loading