massive refactoring, and pylint cleanup
All checks were successful
Pylint / build (3.12) (push) Successful in 7s
All checks were successful
Pylint / build (3.12) (push) Successful in 7s
This commit is contained in:
parent
520569ab48
commit
0d48269790
15
Pipfile
15
Pipfile
@ -1,15 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
name = "pypi"
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
requests = "*"
|
|
||||||
py3dns = "*"
|
|
||||||
python-console-menu = "*"
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
pytest = "*"
|
|
||||||
|
|
||||||
[requires]
|
|
||||||
python_version = "3.8"
|
|
35
README.md
35
README.md
@ -1,6 +1,7 @@
|
|||||||
# pystations python script
|
# pystations python script
|
||||||
|
|
||||||
Simple python tool to grab radio stations from radio-browser.info, put them into a menu, and then use your local installation of mpg123 to play the station for you.
|
Simple python tool to grab radio stations from radio-browser.info, put them into a menu, and then use your local
|
||||||
|
installation of mpg123 to play the station for you.
|
||||||
|
|
||||||
### Requirements:
|
### Requirements:
|
||||||
|
|
||||||
@ -9,13 +10,14 @@ Simple python tool to grab radio stations from radio-browser.info, put them into
|
|||||||
* Pipenv
|
* Pipenv
|
||||||
* mpv (or mpg123, or mplayer, if you want to swap it in yourself)
|
* mpv (or mpg123, or mplayer, if you want to swap it in yourself)
|
||||||
|
|
||||||
|
|
||||||
### Install
|
### Install
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pipenv install
|
pipenv install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Execute
|
### Execute
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
pipenv run python ./stations.py -h
|
pipenv run python ./stations.py -h
|
||||||
|
|
||||||
@ -33,6 +35,7 @@ optional arguments:
|
|||||||
```
|
```
|
||||||
|
|
||||||
Example (using mpg123):
|
Example (using mpg123):
|
||||||
|
|
||||||
```
|
```
|
||||||
> pipenv run python ./stations.py -n "wfmt"
|
> pipenv run python ./stations.py -n "wfmt"
|
||||||
|
|
||||||
@ -54,6 +57,7 @@ A: 00:00:27 / 00:00:51 (53%) Cache: 23s/833KB
|
|||||||
```
|
```
|
||||||
|
|
||||||
When I hit `Q`, mpv exits, and sends me back to my menu:
|
When I hit `Q`, mpv exits, and sends me back to my menu:
|
||||||
|
|
||||||
```
|
```
|
||||||
Exiting... (Quit)
|
Exiting... (Quit)
|
||||||
|
|
||||||
@ -68,11 +72,22 @@ Select Option:
|
|||||||
|
|
||||||
### CAVEATS
|
### CAVEATS
|
||||||
|
|
||||||
1. This is a VERY ROUGH script. If your search criteria are too broad, you'll get hundreds and hundres of menu items. I set the limit to 9999, and it seems to work ok. But don't push your luck; try to be as precise with your searches as possible.
|
1. This is a VERY ROUGH script. If your search criteria are too broad, you'll get hundreds and hundres of menu items. I
|
||||||
2. ~~mpg123 and mplayer are primitive at best.~~ `mpv` is a better player than mpg123, and more streams will work out of the box. What's more, via homebrew, `mpv` is available on mac or linux. However, it's still the case that not all the stations on the list will play correctly. This is partly due to the player, but mostly due to problems with the streams themselves.
|
set the limit to 9999, and it seems to work ok. But don't push your luck; try to be as precise with your searches as
|
||||||
3. if you supply multiple tags in a comma-separated list, you may unintentionally filter out results. Unfortunately, the api at radio-info is such that the tag list you search for, has to be in precisely the order it is returned from the host. So, for example, if you search for "classical,chicago", your search will filter out WFMT, because their tags are "chicago,classical". So, best to keep your tags to a minimum (meaning 1 lol)
|
possible.
|
||||||
4. The country search is by country NAME, not CODE. So, "United States" will work, but "US" will not. Likewise for the United Kingdom.
|
2. ~~mpg123 and mplayer are primitive at best.~~ `mpv` is a better player than mpg123, and more streams will work out of
|
||||||
5. It seems many of the stations put their city in the tag list. So, you can reduce the size of your results by doing something like this: `-c "United States" -t "atlanta"`, which makes more sense for radio stations anyway. Eg:
|
the box. What's more, via homebrew, `mpv` is available on mac or linux. However, it's still the case that not all the
|
||||||
|
stations on the list will play correctly. This is partly due to the player, but mostly due to problems with the
|
||||||
|
streams themselves.
|
||||||
|
3. if you supply multiple tags in a comma-separated list, you may unintentionally filter out results. Unfortunately, the
|
||||||
|
api at radio-info is such that the tag list you search for, has to be in precisely the order it is returned from the
|
||||||
|
host. So, for example, if you search for "classical,chicago", your search will filter out WFMT, because their tags
|
||||||
|
are "chicago,classical". So, best to keep your tags to a minimum (meaning 1 lol)
|
||||||
|
4. The country search is by country NAME, not CODE. So, "United States" will work, but "US" will not. Likewise for the
|
||||||
|
United Kingdom.
|
||||||
|
5. It seems many of the stations put their city in the tag list. So, you can reduce the size of your results by doing
|
||||||
|
something like this: `-c "United States" -t "atlanta"`, which makes more sense for radio stations anyway. Eg:
|
||||||
|
|
||||||
```
|
```
|
||||||
> pipenv run python ./stations.py -c "United States" -t "atlanta"
|
> pipenv run python ./stations.py -c "United States" -t "atlanta"
|
||||||
|
|
||||||
@ -89,6 +104,10 @@ Radio Player Menu
|
|||||||
Select Option:
|
Select Option:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Potential next steps:
|
### Potential next steps:
|
||||||
|
|
||||||
* Searching by tags would be powerful, if it didn't matter how many or which order they were in. So, one approach might be to do some sort of preprocessing on them, after capture. But this requires getting an unfiltered list to begin with. The server code itself is open source on github. So, one could potentially suggest an improvement to the tag searching, via pull request. You can find it here: https://github.com/segler-alex/radiobrowser-api-rust
|
* Searching by tags would be powerful, if it didn't matter how many or which order they were in. So, one approach might
|
||||||
|
be to do some sort of preprocessing on them, after capture. But this requires getting an unfiltered list to begin
|
||||||
|
with. The server code itself is open source on github. So, one could potentially suggest an improvement to the tag
|
||||||
|
searching, via pull request. You can find it here: https://github.com/segler-alex/radiobrowser-api-rust
|
||||||
|
@ -18,4 +18,3 @@ def options():
|
|||||||
|
|
||||||
def maxitems():
|
def maxitems():
|
||||||
return int(config.get('DEFAULT', 'menu_items.max'))
|
return int(config.get('DEFAULT', 'menu_items.max'))
|
||||||
|
|
||||||
|
23
radiomenu.py
23
radiomenu.py
@ -1,6 +1,7 @@
|
|||||||
from python_console_menu import AbstractMenu, MenuItem
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from python_console_menu import AbstractMenu, MenuItem
|
||||||
|
|
||||||
from config import player, options, maxitems
|
from config import player, options, maxitems
|
||||||
|
|
||||||
|
|
||||||
@ -10,20 +11,22 @@ class RadioMenu(AbstractMenu):
|
|||||||
if station_list is None:
|
if station_list is None:
|
||||||
station_list = []
|
station_list = []
|
||||||
|
|
||||||
for i in range(len(station_list)):
|
for i, station in enumerate(station_list, start=1):
|
||||||
if i < maxitems(): # The very first item is the exit option, so not "<=".
|
|
||||||
self.add_menu_item(
|
self.add_menu_item(
|
||||||
MenuItem(
|
MenuItem(
|
||||||
i,
|
i,
|
||||||
"{:<30}".format(station_list[i]["name"][:30]) + " " + # force 35 character fixed length
|
(
|
||||||
"{:<5}".format(station_list[i]["codec"][:5]) + " " + # force 5 character fixed length
|
f"{station['name'][:30]:<30} "
|
||||||
"{:<5}".format(station_list[i]["bitrate"][:5]) + " " +
|
f"{station['codec'][:5]:<5} "
|
||||||
station_list[i]["url"],
|
f"{station['bitrate'][:5]:<5} "
|
||||||
lambda url=station_list[i]["url"]: subprocess.run([player(), options(), url])
|
f"{station['url']}"
|
||||||
|
),
|
||||||
|
lambda url=station["url"]: subprocess.run(
|
||||||
|
[player(), options(), url],
|
||||||
|
capture_output=True, text=True, check=True
|
||||||
|
).stdout
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
break
|
|
||||||
|
|
||||||
def initialise(self):
|
def initialise(self):
|
||||||
self.add_menu_item(MenuItem(maxitems(), "Exit menu").set_as_exit_option())
|
self.add_menu_item(MenuItem(maxitems(), "Exit menu").set_as_exit_option())
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
radio_browser.api=all.api.radio-browser.info
|
radio_browser.api = all.api.radio-browser.info
|
||||||
player.command=mpv
|
player.command = mpv
|
||||||
player.options=--no-video
|
player.options = --no-video
|
||||||
menu_items.max=9999
|
menu_items.max = 9999
|
||||||
|
82
stations.py
82
stations.py
@ -1,9 +1,10 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import DNS
|
|
||||||
import json
|
import json
|
||||||
import requests
|
|
||||||
import secrets
|
import secrets
|
||||||
|
|
||||||
|
import DNS
|
||||||
|
import requests
|
||||||
|
|
||||||
from config import api
|
from config import api
|
||||||
from radiomenu import RadioMenu
|
from radiomenu import RadioMenu
|
||||||
|
|
||||||
@ -23,56 +24,48 @@ def get_host():
|
|||||||
|
|
||||||
|
|
||||||
def get_stations(qstring, host):
|
def get_stations(qstring, host):
|
||||||
resp = requests.get(f"https://{host}/json/stations/search?{qstring}&limit=100000")
|
resp = requests.get(
|
||||||
|
f"https://{host}/json/stations/search?{qstring}&limit=100000", timeout=10
|
||||||
|
)
|
||||||
if resp.status_code == 200:
|
if resp.status_code == 200:
|
||||||
return json.loads(resp.text)
|
return json.loads(resp.text)
|
||||||
else:
|
|
||||||
return [{"response_code": resp.status_code, "reason": resp.reason}]
|
return [{"response_code": resp.status_code, "reason": resp.reason}]
|
||||||
|
|
||||||
|
|
||||||
def search_stations(name=None, country=None, state=None, tags=None, status="up"):
|
def search_stations(query_dict=None):
|
||||||
if tags is None:
|
if query_dict is None:
|
||||||
tags = []
|
query_dict = {}
|
||||||
query = ""
|
|
||||||
|
|
||||||
if name:
|
query_string = ""
|
||||||
if query != "":
|
for key, value in query_dict.items():
|
||||||
query = query + "&"
|
if key == "tags":
|
||||||
query = query + "name=" + name
|
query_string += f"{key}={','.join(value)}&"
|
||||||
if country:
|
query_string += f"{key}={value}&"
|
||||||
if query != "":
|
|
||||||
query = query + "&"
|
|
||||||
query = query + "country=" + country
|
|
||||||
if state:
|
|
||||||
if query != "":
|
|
||||||
query = query + "&"
|
|
||||||
query = query + "state=" + state
|
|
||||||
if len(tags) > 0:
|
|
||||||
if query != "":
|
|
||||||
query = query + "&"
|
|
||||||
tag_string = ','.join(tags) # Be careful here! the tag list is order-dependent in the http call :(
|
|
||||||
query = query + "tag=" + tag_string
|
|
||||||
|
|
||||||
stations = get_stations(query, get_host())
|
stations = get_stations(query_string, get_host())
|
||||||
filtered_list = []
|
filtered_list = []
|
||||||
for station in stations:
|
|
||||||
station_stat = "down"
|
|
||||||
if str(station["lastchecktime"]) == str(station["lastcheckoktime"]):
|
|
||||||
station_stat = "up"
|
|
||||||
|
|
||||||
if len(tags) > 1 and station["tags"] == "": # If searching with tags, but no tags, don't include the station.
|
for station in stations:
|
||||||
|
station["status"] = "down" # assume down
|
||||||
|
# if last check and lastcheckok match, the station is up
|
||||||
|
if str(station["lastchecktime"]) == str(station["lastcheckoktime"]):
|
||||||
|
station["status"] = "up"
|
||||||
|
|
||||||
|
if len(query_dict["tags"]) > 1 and station["tags"] == "":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if station_stat == status: # only add the entry if it matches the up/down status specified.
|
if station["status"] == "up":
|
||||||
station_entry = {
|
station_entry = {
|
||||||
"name": str(station["name"]).replace('"', "'").replace(",", " - "),
|
"name": str(station["name"]).replace('"', "'").replace(",", " - "),
|
||||||
"url": str(station["url"].replace(",", "%2C")),
|
"url": str(station["url"].replace(",", "%2C")),
|
||||||
"codec": str(station["codec"]),
|
"codec": str(station["codec"]),
|
||||||
"bitrate": str(station["bitrate"]),
|
"bitrate": str(station["bitrate"]),
|
||||||
"countrycode": str(station["countrycode"]),
|
"countrycode": str(station["countrycode"]),
|
||||||
"favicon": str(station["favicon"]).replace(" ", "%20").replace("(", "%28").replace(")", "%29"),
|
"favicon": str(station["favicon"])\
|
||||||
|
.replace(" ", "%20").replace("(", "%28").replace(")", "%29"),
|
||||||
"tags": str(station["tags"].split(",")).replace(",", ";"),
|
"tags": str(station["tags"].split(",")).replace(",", ";"),
|
||||||
"status": str(station_stat)
|
"status": str(station["status"])
|
||||||
}
|
}
|
||||||
filtered_list.append(station_entry)
|
filtered_list.append(station_entry)
|
||||||
return filtered_list
|
return filtered_list
|
||||||
@ -80,13 +73,24 @@ def search_stations(name=None, country=None, state=None, tags=None, status="up")
|
|||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument("-n", "--name", type=str, help="Name of station", default=None)
|
parser.add_argument("-n", "--name", type=str,
|
||||||
parser.add_argument("-c", "--country", type=str, help="Station country", default=None)
|
help="Name of station", default=None)
|
||||||
parser.add_argument("-s", "--state", type=str, help="Station state (if in US)", default=None)
|
parser.add_argument("-c", "--country", type=str,
|
||||||
parser.add_argument("-t", "--tags", type=str, help="search tag or tags (if more than one, comma-separated", default="")
|
help="Station country", default=None)
|
||||||
|
parser.add_argument("-s", "--state", type=str,
|
||||||
|
help="Station state (if in US)", default=None)
|
||||||
|
parser.add_argument("-t", "--tags", type=str,
|
||||||
|
help="search tag or tags (if more than one, comma-separated", default="")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
station_list = search_stations(name=args.name, country=args.country, state=args.state, tags=args.tags.split(","))
|
query = {
|
||||||
|
"name": args.name,
|
||||||
|
"country": args.country,
|
||||||
|
"state": args.state,
|
||||||
|
"tags": args.tags.split(","),
|
||||||
|
}
|
||||||
|
|
||||||
|
station_list = search_stations(query)
|
||||||
|
|
||||||
mainMenu = RadioMenu(station_list)
|
mainMenu = RadioMenu(station_list)
|
||||||
mainMenu.display()
|
mainMenu.display()
|
||||||
|
Loading…
Reference in New Issue
Block a user