Skip to content

Commit

Permalink
feat: ✨ filter support added
Browse files Browse the repository at this point in the history
  • Loading branch information
deep5050 committed Dec 21, 2023
1 parent d42cd53 commit 42ca986
Show file tree
Hide file tree
Showing 11 changed files with 273 additions and 44 deletions.
7 changes: 5 additions & 2 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
],
"files.autoSave": "off",
"editor.wordWrap": "wordWrapColumn",
"workbench.colorTheme": "GitHub Light High Contrast",
"workbench.colorTheme": "GitHub Dark",
"editor.minimap.autohide": true,
"editor.minimap.renderCharacters": false,
"editor.experimentalWhitespaceRendering": "font",
"editor.fontFamily": "'Fira Code', Consolas, 'Courier New', monospace",
"editor.codeLensFontFamily": "'Fira Code'",
"editor.fontLigatures": true,
"editor.defaultFormatter": "ms-python.black-formatter"
"editor.defaultFormatter": "ms-python.black-formatter",
"[python]": {
"editor.defaultFormatter": "ms-python.black-formatter"
}
}
37 changes: 34 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ I recommend installing it using `pipx install radio-active`
### External Dependency

It needs [FFmpeg](https://ffmpeg.org/download.html) to be installed on your
system in order to play the audio
system in order to record the audio

on Ubuntu-based system >= 20.04 Run

Expand Down Expand Up @@ -189,7 +189,7 @@ h/H/help/?: Show this help message
q/Q/quit: Quit radioactive
```

### sort parameters
### Sort Parameters

you can sort the result page with these parameters:
- `name` (default)
Expand All @@ -202,8 +202,38 @@ you can sort the result page with these parameters:
- `clicktrend` (currently trending stations)
- `random`

### Filter Parameters

### Default configs
Filter search results with `--filter`. Possible expressions are
- `--filter "name=shows"`
- `--filter "name=shows,talks,tv"`
- `--filter "name!=news,shows"`
- `--filter "country=in"`
- `--filter "language=bengali,nepali"`
- `--filter "bitrate>64"`
- `--filter "votes<500"`
- `--filter "codec=mp3"`
- `--filter "tags!=rock,pop"`

Allowed operators are:

- `=`
- `,`
- `!=`
- `>`
- `<`
- `&`

Allowed keys are: `name`, `country` (countrycode as value), `language`, `bitrate`, `votes`, `codec`, `tags`

Provide multiple filters at one go, use `&`

A complex filter example: `--filter "country!=CA&tags!=islamic,classical&votes>500"`

> NOTE: set `--limit` to a higher value while filtering results

### Default Configs

Default configuration file is added into your home directory as `.radio-active-configs.ini`

Expand All @@ -212,6 +242,7 @@ Default configuration file is added into your home directory as `.radio-active-c
loglevel = info
limit = 100
sort = votes
filter = none
volume = 80
filepath = /home/{user}/recordings/radioactive/
filetype = mp3
Expand Down
21 changes: 17 additions & 4 deletions radioactive/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,10 @@ def main():
# ----------- country ----------- #
if options["discover_country_code"]:
response = handler.discover_by_country(
options["discover_country_code"], options["limit"], options["sort_by"]
options["discover_country_code"],
options["limit"],
options["sort_by"],
options["filter_with"],
)
if response is not None:
(
Expand All @@ -172,7 +175,10 @@ def main():
# -------------- state ------------- #
if options["discover_state"]:
response = handler.discover_by_state(
options["discover_state"], options["limit"], options["sort_by"]
options["discover_state"],
options["limit"],
options["sort_by"],
options["filter_with"],
)
if response is not None:
(
Expand All @@ -186,7 +192,10 @@ def main():
# ----------- language ------------ #
if options["discover_language"]:
response = handler.discover_by_language(
options["discover_language"], options["limit"], options["sort_by"]
options["discover_language"],
options["limit"],
options["sort_by"],
options["filter_with"],
)
if response is not None:
(
Expand All @@ -200,7 +209,10 @@ def main():
# -------------- tag ------------- #
if options["discover_tag"]:
response = handler.discover_by_tag(
options["discover_tag"], options["limit"], options["sort_by"]
options["discover_tag"],
options["limit"],
options["sort_by"],
options["filter_with"],
)
if response is not None:
(
Expand Down Expand Up @@ -246,6 +258,7 @@ def main():
options["search_station_name"],
options["limit"],
options["sort_by"],
options["filter_with"],
)
if response is not None:
(
Expand Down
3 changes: 2 additions & 1 deletion radioactive/app.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""
Version of the current program, (in development mode it needs to be updated in every release)
Version of the current program, (in development mode
it needs to be updated in every release)
and to check if an updated version available for the app or not
"""
import json
Expand Down
8 changes: 8 additions & 0 deletions radioactive/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ def __init__(self):
help="Sort stations",
)

self.parser.add_argument(
"--filter",
action="store",
dest="stations_filter_with",
default=self.defaults["filter"],
help="Filter Results",
)

self.parser.add_argument(
"--add",
"-A",
Expand Down
6 changes: 4 additions & 2 deletions radioactive/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def write_a_sample_config_file():
"loglevel": "info",
"limit": "100",
"sort": "votes",
"filter": "none",
"volume": "80",
"filepath": "/home/{user}/recordings/radioactive/",
"filetype": "mp3",
Expand Down Expand Up @@ -55,6 +56,7 @@ def load(self):
options["volume"] = self.config.get("AppConfig", "volume")
options["loglevel"] = self.config.get("AppConfig", "loglevel")
options["sort"] = self.config.get("AppConfig", "sort")
options["filter"] = self.config.get("AppConfig", "filter")
options["limit"] = self.config.get("AppConfig", "limit")
options["filepath"] = self.config.get("AppConfig", "filepath")
# if filepath has any placeholder, replace
Expand All @@ -67,8 +69,8 @@ def load(self):

return options

except:
log.error("Something went wrong while parsing the config file")
except Exception as e:
log.error(f"Something went wrong while parsing the config file: {e}")
# write the example config file
write_a_sample_config_file()
log.info("Re-run radioative")
Expand Down
151 changes: 151 additions & 0 deletions radioactive/filter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import sys

from zenlog import log


# function to filter strings
def _filter_entries_by_key(data, filter_param, key):
log.debug(f"filter: {filter_param}")

filtered_entries = []

for entry in data:
value = entry.get(key)

if value is not None and value != "":
if "!=" in filter_param:
# Handle exclusion
exclusion_values = filter_param.split("!=")[1].split(",")

if all(
exclusion_value.lower() not in value.lower()
for exclusion_value in exclusion_values
):
filtered_entries.append(entry)

elif "=" in filter_param:
# Handle inclusion
inclusion_values = filter_param.split("=")[1].split(",")

if any(
inclusion_value.lower() in value.lower()
for inclusion_value in inclusion_values
):
filtered_entries.append(entry)

return filtered_entries


# function to filter numeric values
def _filter_entries_by_numeric_key(data, filter_param, key):
filtered_entries = []

filter_key = filter_param.split(key)[0] # most left hand of the expression
filter_param = filter_param.split(key)[1] # portion after the operator
filter_operator = filter_param[0] # operator part
filter_value = int(filter_param[1:]) # value part
# log.debug(f"filter: parameter:{filter_param}")

for entry in data:
value = int(entry.get(key))

if value is not None:
try:
if filter_operator not in [">", "<", "="]:
log.warning("Unsupported filter operator, not filtering !!")
return data
if filter_operator == "<" and value < filter_value:
filtered_entries.append(entry)
elif filter_operator == ">" and value > filter_value:
filtered_entries.append(entry)
elif filter_operator == "=" and value == filter_value:
filtered_entries.append(entry)

except ValueError:
log.error(f"Invalid filter value for {key}: {filter_param}")
sys.exit(1)

return filtered_entries


# allowed string string filters
def _filter_entries_by_name(data, filter_param):
return _filter_entries_by_key(data, filter_param, key="name")


def _filter_entries_by_language(data, filter_param):
return _filter_entries_by_key(data, filter_param, key="language")


def _filter_entries_by_country(data, filter_param):
return _filter_entries_by_key(data, filter_param, key="countrycode")


def _filter_entries_by_tags(data, filter_param):
return _filter_entries_by_key(data, filter_param, key="tags")


def _filter_entries_by_codec(data, filter_param):
return _filter_entries_by_key(data, filter_param, key="codec")


# allowed numeric filters
def _filter_entries_by_votes(data, filter_param):
return _filter_entries_by_numeric_key(data, filter_param, key="votes")


def _filter_entries_by_bitrate(data, filter_param):
return _filter_entries_by_numeric_key(data, filter_param, key="bitrate")


def _filter_entries_by_clickcount(data, filter_param):
return _filter_entries_by_numeric_key(data, filter_param, key="clickcount")


# top level filter function
def _filter_results(data, expression):
log.debug(f"Filter exp: {expression}")
if not data:
log.error("Empty results")
sys.exit(0)

if "name" in expression:
return _filter_entries_by_name(data, expression)
elif "language" in expression:
return _filter_entries_by_language(data, expression)
elif "country" in expression:
return _filter_entries_by_country(data, expression)
elif "tags" in expression:
return _filter_entries_by_tags(data, expression)
elif "codec" in expression:
return _filter_entries_by_codec(data, expression)
elif "bitrate" in expression:
return _filter_entries_by_bitrate(data, expression)
elif "clickcount" in expression:
return _filter_entries_by_clickcount(data, expression)
elif "votes" in expression:
return _filter_entries_by_votes(data, expression)
else:
log.warning("Unknown filter expression, not filtering!")
return data


# Top most function for multiple filtering expressions with '&'
# NOTE: it will filter maintaining the order you provided on the CLI


def filter_expressions(data, input_expression):
log.info(
"Setting a higher value for the --limit parameter is preferable when filtering stations."
)
if "&" in input_expression:
log.debug("filter: multiple expressions found")
expression_parts = input_expression.split("&")

for expression in expression_parts:
if data:
data = _filter_results(data, expression)
return data

else:
return _filter_results(data, input_expression)
Loading

0 comments on commit 42ca986

Please sign in to comment.