diff --git a/.gitignore b/.gitignore
index 5f32c75..1395d28 100644
--- a/.gitignore
+++ b/.gitignore
@@ -130,7 +130,6 @@ dmypy.json
last_station.json
build.sh
deploy.sh
-Makefile
pylint.txt
.gitpod.yml
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f674e41..ff0d719 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,11 @@
+## 2.5.2
+
+1. Added `--kill` option to stop background radios if any.
+2. Project restructured.
+3. Fixed saving empty last station information.
+
+
+
## 2.5.1
1. Fixed RuntimeError with empty selection menu on no options provided to radio.
diff --git a/LICENSE b/LICENSE
index 1e46125..a9ab609 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2021 Dipankar Pal
+Copyright (c) 2023 Dipankar Pal
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..c33659b
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,5 @@
+include *.py
+include README.md
+include LICENSE
+include requirements.txt
+include requirements-dev.txt
\ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3cfc5ec
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,60 @@
+SHELL := /bin/bash
+PYTHON = python3
+TEST_PATH = ./tests/
+FLAKE8_EXCLUDE = venv,.venv,.eggs,.tox,.git,__pycache__,*.pyc
+SRC_DIR = "radioactive"
+TEST_DIR = "test"
+
+all: clean install-dev test
+
+check:
+ ${PYTHON} -m flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --exclude ${FLAKE8_EXCLUDE}
+ ${PYTHON} -m flake8 . --count --exit-zero --max-complexity=10 --max-line-length=79 --statistics --exclude ${FLAKE8_EXCLUDE}
+
+clean:
+ @find . -name '*.pyc' -exec rm --force {} +
+ @find . -name '*.pyo' -exec rm --force {} +
+ @find . -name '*~' -exec rm --force {} +
+ rm -rf build
+ rm -rf dist
+ rm -rf *.egg-info
+ rm -f *.sqlite
+ rm -rf .cache
+
+dist: clean
+ ${PYTHON} setup.py sdist bdist_wheel
+
+deploy:
+ twine upload --repository-url https://upload.pypi.org/legacy/ dist/*
+
+test-deploy: dist
+ @echo "-------------------- sending to testpypi server ------------------------"
+ @twine upload -r testpypi dist/*
+
+help:
+ @echo "---------------------------- help --------------------------------------"
+ @echo " clean"
+ @echo " Remove python artifacts and build artifacts."
+ @echo " isort"
+ @echo " Sort import statements."
+ @echo " check"
+ @echo " Check style with flake8."
+ @echo " test"
+ @echo " Run pytest"
+
+isort:
+ @echo "Sorting imports..."
+ isort $(SRC_DIR) $(TEST_DIR)
+
+build:
+ python3 setup.py build
+
+install:
+ pip install -e .
+
+install-dev: install
+ pip install --upgrade pip
+ pip install -e .[dev]
+
+test:
+ ${PYTHON} -m pytest ${TEST_PATH}
diff --git a/README.md b/README.md
index 52634f5..f0c3515 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
RADIO-ACTIVE
Play any radios around the globe right from your terminal
@@ -11,7 +11,7 @@
-
+
@@ -37,7 +37,7 @@
- [x] Discovers stations by genre
- [x] Discovers stations by language
- [ ] I'm feeling lucky ! Play Random stations
-- [ ] No external media player dependency!
+
> See my progress [here](https://github.com/deep5050/radio-active/projects/1)
@@ -83,7 +83,7 @@ Run with `radioactive --station [STATION_NAME]` or as simply `radio -U [UUID] `
### Demo
-
+
@@ -92,22 +92,22 @@ Run with `radioactive --station [STATION_NAME]` or as simply `radio -U [UUID] `
### Options
-| Argument | Note | Description | Default |
-| ---------------------------- | ------------------------------------ | -------------------------------------------- | ------- |
+| Argument | Note | Description | Default |
+| ---------------------------- | ----------------------------------- | -------------------------------------------- | ------- |
| `--station`, `-S` | Required (Optional from second run) | Station name | None |
-| `--uuid`, `-U` | Optional | ID of the station | None |
-| `--log-level`, `-L` | Optional | Log level of the program | Info |
-| `--add-station` , `-A` | Optional | Add an entry to fav list | False |
-| `--show-favorite-list`,`-W` | Optional | Show fav list | False |
-| `--add-to-favorite`,`-F` | Optional | Add current station to fav list | False |
-| `--flush` | Optional | Remove all the entries from fav list | False |
-| `--discover-by-country`,`-D` | Optional | Discover stations by country code | False |
-| `--discover-by-state` | Optional | Discover stations by country state | False |
-| `--discover-by-tag` | Optional | Discover stations by tags/genre | False |
-| `--discover-by-language` | optional | Discover stations by | False |
-| `--limit` | Optional | Limit the # of results in the Discover table | 100 |
-| `--volume` | Optional | Change the volume passed into ffplay | 50 |
-
+| `--uuid`, `-U` | Optional | ID of the station | None |
+| `--log-level`, `-L` | Optional | Log level of the program | Info |
+| `--add-station` , `-A` | Optional | Add an entry to fav list | False |
+| `--show-favorite-list`,`-W` | Optional | Show fav list | False |
+| `--add-to-favorite`,`-F` | Optional | Add current station to fav list | False |
+| `--flush` | Optional | Remove all the entries from fav list | False |
+| `--discover-by-country`,`-D` | Optional | Discover stations by country code | False |
+| `--discover-by-state` | Optional | Discover stations by country state | False |
+| `--discover-by-tag` | Optional | Discover stations by tags/genre | False |
+| `--discover-by-language` | optional | Discover stations by | False |
+| `--limit` | Optional | Limit the # of results in the Discover table | 100 |
+| `--volume` | Optional | Change the volume passed into ffplay | 50 |
+| `--kill` | Optional | Kill background radios. | False |
@@ -143,17 +143,20 @@ If you ever face a situation where radio-active quits but the audio (ffplay) run
### Support
+
+
+Visit my contribution page for more payment options.
+
### Acknowledgements
+
diff --git a/radioactive/__main__.py b/radioactive/__main__.py
index 8c0e2e9..ce8e142 100755
--- a/radioactive/__main__.py
+++ b/radioactive/__main__.py
@@ -18,7 +18,7 @@
from radioactive.handler import Handler
from radioactive.help import show_help
from radioactive.last_station import Last_station
-from radioactive.player import Player
+from radioactive.player import Player, kill_background_ffplays
# using sentry to gather unhandled errors at production and will be removed on next major update.
@@ -27,9 +27,6 @@
sentry_sdk.init(
dsn="https://e3c430f3b03f49b6bd9e9d61e7b3dc37@o615507.ingest.sentry.io/5749950",
- # Set traces_sample_rate to 1.0 to capture 100%
- # of transactions for performance monitoring.
- # We recommend adjusting this value in production.
traces_sample_rate=1.0,
)
@@ -63,6 +60,7 @@ def main():
add_to_favorite = args.add_to_favorite
show_favorite_list = args.show_favorite_list
flush_fav_list = args.flush
+ kill_ffplays = args.kill_ffplays
########################################
VERSION = app.get_version()
@@ -123,10 +121,20 @@ def main():
else:
log.debug("Update not available")
+ # flush ?
if flush_fav_list:
# exit radio after deleting fav stations
sys.exit(alias.flush())
+ # -------------- kill background ffplay PIDs --------------------#
+ # sometimes radio exits while ffplay is still running.
+ # actively trying to prevent these scenarios. until then use this
+
+ if kill_ffplays:
+ kill_background_ffplays()
+ sys.exit(0)
+
+ # ----------------- favorite list ---------------- #
if show_favorite_list:
log.info("Your favorite station list is below")
table = Table(show_header=True, header_style="bold magenta")
@@ -140,6 +148,7 @@ def main():
log.info("You have no favorite station list")
sys.exit(0)
+ # --------------------------- add a station --------------------------#
if add_station:
left = input("Enter station name:")
right = input("Enter station stream-url or radio-browser uuid:")
@@ -172,8 +181,7 @@ def main():
if station_name is None and station_uuid is None:
# Add a selection list here. first entry must be the last played station
# try to fetch the last played station's information
- # log.warn(
- # "No station information provided, trying to play the last station")
+
try:
last_station_info = last_station.get_info()
except:
@@ -209,7 +217,7 @@ def main():
)
sys.exit(0)
- _ , index = pick(options, title, indicator="-->")
+ _, index = pick(options, title, indicator="-->")
# check if there is direct URL or just UUID
station_option_url = station_selection_urls[index]
@@ -326,7 +334,9 @@ def main():
last_played_station["alias"] = True
if not skip_saving_current_station:
- last_station.save_info(last_played_station)
+ log.debug(f"Saving the current station: {last_played_station}")
+ if last_played_station:
+ last_station.save_info(last_played_station)
# TODO: handle error when favouring last played (aliased) station (BUG) (LOW PRIORITY)
if add_to_favorite:
diff --git a/radioactive/app.py b/radioactive/app.py
index 3dc81e5..2e0fb8d 100644
--- a/radioactive/app.py
+++ b/radioactive/app.py
@@ -10,7 +10,7 @@
class App:
def __init__(self):
- self.__VERSION__ = "2.5.1" # change this on every update #
+ self.__VERSION__ = "2.5.2" # change this on every update #
self.pypi_api = "https://pypi.org/pypi/radio-active/json"
self.remote_version = ""
diff --git a/radioactive/args.py b/radioactive/args.py
index 7ae970a..1930bb5 100644
--- a/radioactive/args.py
+++ b/radioactive/args.py
@@ -137,12 +137,18 @@ def __init__(self):
"--volume",
action="store",
dest="volume",
- default=50,
+ default=80,
type=int,
choices=range(0,101,10),
help="Volume to pass down to ffplay",
)
-
+ self.parser.add_argument(
+ "--kill",
+ action="store_true",
+ dest="kill_ffplays",
+ default=False,
+ help="kill all the ffplay process initiated by radioactive",
+ )
def parse(self):
self.result = self.parser.parse_args()
diff --git a/radioactive/help.py b/radioactive/help.py
index 5233763..1a775da 100644
--- a/radioactive/help.py
+++ b/radioactive/help.py
@@ -117,10 +117,11 @@ def show_help():
"--volume",
"yes",
"Volume of the radio between 0 and 100",
- "50",
+ "80",
"Optional",
)
table.add_row("--flush", "no", "Clear your favorite list", "False", "Optional")
+ table.add_row("--kill", "no", "Stop background radios", "False", "Optional")
console.print(table)
diff --git a/radioactive/player.py b/radioactive/player.py
index 3e4a8b5..c6e95d5 100644
--- a/radioactive/player.py
+++ b/radioactive/player.py
@@ -10,6 +10,28 @@
from zenlog import log
+def kill_background_ffplays():
+ all_processes = psutil.process_iter(attrs=["pid", "name"])
+ count = 0
+ # Iterate through the processes and terminate those named "ffplay"
+ for process in all_processes:
+ try:
+ if process.info["name"] == "ffplay":
+ pid = process.info["pid"]
+ p = psutil.Process(pid)
+ p.terminate()
+ count += 1
+ log.info(f"Terminated ffplay process with PID {pid}")
+ if p.is_running():
+ p.kill()
+ log.debug(f"Forcefully killing ffplay process with PID {pid}")
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
+ # Handle exceptions, such as processes that no longer exist or access denied
+ log.debug("Could not terminate a ffplay processes!")
+ if count == 0:
+ log.info("No background radios are running!")
+
+
class Player:
"""FFPlayer handler, it holds all the attributes to properly execute ffplay
@@ -35,21 +57,32 @@ def __init__(self, URL, volume):
try:
self.process = subprocess.Popen(
- [self.exe_path, "-nodisp", "-nostats", "-loglevel", "0", "-volume", f"{self.volume}", self.url],
+ [
+ self.exe_path,
+ "-nodisp",
+ "-nostats",
+ "-loglevel",
+ "0",
+ "-volume",
+ f"{self.volume}",
+ self.url,
+ ],
shell=False,
)
log.debug("player: ffplay => PID {} initiated".format(self.process.pid))
- #sleep(3) # sleeping for 3 seconds waiting for ffplay to start properly
+ # sleep(3) # sleeping for 3 seconds waiting for ffplay to start properly
if self.is_active():
self.is_playing = True
log.info("Radio started successfully")
else:
- log.error("Radio could not be stared, may be a dead station. please try again")
+ log.error(
+ "Radio could not be stared, may be a dead station. please try again"
+ )
sys.exit(1)
-
+
except subprocess.CalledProcessError as e:
log.error("Error while starting radio: {}".format(e))
@@ -82,14 +115,13 @@ def is_active(self):
log.error("Error while checking process status: {}".format(e))
return False
-
def play(self):
"""Play a station"""
if not self.is_playing:
pass # call the init function again ?
def stop(self):
- """stop the ffplayer """
+ """stop the ffplayer"""
if self.is_playing:
try:
@@ -106,4 +138,4 @@ def stop(self):
self.is_playing = False
self.process = None
else:
- log.warning("Radio is not currently playing")
\ No newline at end of file
+ log.warning("Radio is not currently playing")
diff --git a/requirements-dev.txt b/requirements-dev.txt
new file mode 100644
index 0000000..9dc5883
--- /dev/null
+++ b/requirements-dev.txt
@@ -0,0 +1,3 @@
+flake8>=4.0.1
+twine
+
diff --git a/requirements b/requirements.txt
similarity index 100%
rename from requirements
rename to requirements.txt
diff --git a/setup.cfg b/setup.cfg
index b88034e..08aedd7 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,2 +1,2 @@
[metadata]
-description-file = README.md
+description_file = README.md
diff --git a/setup.py b/setup.py
index 52f3327..7a4f2a6 100644
--- a/setup.py
+++ b/setup.py
@@ -2,6 +2,7 @@
from setuptools import setup
from radioactive.app import App
+import io
app = App()
@@ -10,12 +11,12 @@
def readme():
- with open("README.md", "r", encoding="utf-8") as f:
+ with io.open("README.md", "r", encoding="utf-8") as f:
return f.read()
-def required():
- with open("requirements", "r", encoding="utf-8") as f:
+def required(sfx=''):
+ with io.open(f"requirements{sfx}.txt",encoding="utf-8") as f:
return f.read().splitlines()
@@ -36,8 +37,9 @@ def required():
"radio = radioactive.__main__:main",
]
},
- packages=find_packages(),
+ packages=find_packages(exclude=["test*"]),
install_requires=required(),
+ extras_require={'dev': required('-dev')},
classifiers=[
"License :: OSI Approved :: MIT License",
"Development Status :: 5 - Production/Stable",
@@ -52,4 +54,4 @@ def required():
"Source": "https://github.com/deep5050/radio-active/",
"Upstream": "https://api.radio-browser.info/",
},
-)
+)
\ No newline at end of file