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 @@

- +


GitHub PyPI @@ -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. +

Buy Me A Coffee

### Acknowledgements
Icons made by Freepik from www.flaticon.com
+
- +

Happy Listening

- -
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