feat: add --no-feat flag to disable extracting featured artists

+ support square brackets when extracting featured artists
+ remove feat artists from track title
+ fix dir browser for linux
This commit is contained in:
geoffrey45 2023-02-01 13:34:53 +03:00
parent 7e15680f26
commit 95c1524b68
8 changed files with 95 additions and 44 deletions

View File

@ -72,12 +72,9 @@ def get_album():
except AttributeError: except AttributeError:
album.duration = 0 album.duration = 0
if ( album.check_is_single(tracks)
album.count == 1
and tracks[0].title == album.title if album.is_single:
# and tracks[0].track == 1
# and tracks[0].disc == 1
):
album.is_single = True album.is_single = True
else: else:
album.check_type() album.check_type()
@ -129,7 +126,6 @@ def get_artist_albums():
return {"data": albums} return {"data": albums}
# @album_bp.route("/album/bio", methods=["POST"]) # @album_bp.route("/album/bio", methods=["POST"])
# def get_album_bio(): # def get_album_bio():
# """Returns the album bio for the given album.""" # """Returns the album bio for the given album."""

View File

@ -54,6 +54,13 @@ def get_folder_tree():
"tracks": [], "tracks": [],
} }
if is_windows():
req_dir = req_dir + "/"
# TODO: Test this on Windows
else:
req_dir = "/" + req_dir + "/" if not req_dir.startswith("/") else req_dir + "/"
print(req_dir)
tracks, folders = GetFilesAndDirs(req_dir)() tracks, folders = GetFilesAndDirs(req_dir)()
return { return {
@ -62,12 +69,20 @@ def get_folder_tree():
} }
def get_all_drives(): def get_all_drives(is_win: bool = False):
""" """
Returns a list of all the drives on a windows machine. Returns a list of all the drives on a Windows machine.
""" """
drives = psutil.disk_partitions() drives = psutil.disk_partitions(all=False)
return [d.mountpoint for d in drives] drives = [d.mountpoint for d in drives]
if is_win:
drives = [win_replace_slash(d) for d in drives]
else:
remove = ["/boot", "/boot/efi", "/tmp"]
drives = [d for d in drives if d not in remove]
return drives
@api.route("/folder/dir-browser", methods=["POST"]) @api.route("/folder/dir-browser", methods=["POST"])
@ -85,15 +100,19 @@ def list_folders():
if req_dir == "$home": if req_dir == "$home":
# req_dir = settings.USER_HOME_DIR # req_dir = settings.USER_HOME_DIR
if is_win: # if is_win:
return { return {
"folders": [ "folders": [
{"name": win_replace_slash(d), "path": win_replace_slash(d)} {"name": d, "path": d}
for d in get_all_drives() for d in get_all_drives(is_win=is_win)
] ]
} }
req_dir = req_dir + "/" if is_win:
req_dir = req_dir + "/"
else:
req_dir = "/" + req_dir + "/"
req_dir = str(Path(req_dir).resolve())
try: try:
entries = os.scandir(req_dir) entries = os.scandir(req_dir)
@ -109,7 +128,6 @@ def list_folders():
"folders": sorted(dirs, key=lambda i: i["name"]), "folders": sorted(dirs, key=lambda i: i["name"]),
} }
# todo: # todo:
# - handle showing windows disks in root_dir configuration # - handle showing windows disks in root_dir configuration

View File

@ -43,7 +43,7 @@ class SearchTracks:
Gets all songs with a given title. Gets all songs with a given title.
""" """
tracks = [track.title for track in self.tracks] tracks = [track.og_title for track in self.tracks]
results = process.extract( results = process.extract(
self.query, self.query,
tracks, tracks,

View File

@ -5,7 +5,7 @@ import dataclasses
import json import json
from dataclasses import dataclass from dataclasses import dataclass
from app import utils from app import utils, settings
@dataclass(slots=True) @dataclass(slots=True)
@ -55,20 +55,28 @@ class Track:
image: str = "" image: str = ""
artist_hashes: list[str] = dataclasses.field(default_factory=list) artist_hashes: list[str] = dataclasses.field(default_factory=list)
is_favorite: bool = False is_favorite: bool = False
og_title: str = ""
def __post_init__(self): def __post_init__(self):
self.og_title = self.title
if self.artist is not None: if self.artist is not None:
artists = utils.split_artists(self.artist) artists = utils.split_artists(self.artist)
featured = utils.parse_feat_from_title(self.title) if settings.EXTRACT_FEAT:
original_lower = "-".join([a.lower() for a in artists]) featured, new_title = utils.parse_feat_from_title(self.title)
artists.extend([a for a in featured if a.lower() not in original_lower]) original_lower = "-".join([a.lower() for a in artists])
artists.extend([a for a in featured if a.lower() not in original_lower])
self.title = new_title
if self.og_title == self.album:
self.album = new_title
self.artist_hashes = [utils.create_hash(a, decode=True) for a in artists] self.artist_hashes = [utils.create_hash(a, decode=True) for a in artists]
self.artist = [Artist(a) for a in artists] self.artist = [Artist(a) for a in artists]
albumartists = str(self.albumartist).split(", ") albumartists = utils.split_artists(self.albumartist)
self.albumartist = [Artist(a) for a in albumartists] self.albumartist = [Artist(a) for a in albumartists]
self.filetype = self.filepath.rsplit(".", maxsplit=1)[-1] self.filetype = self.filepath.rsplit(".", maxsplit=1)[-1]
@ -157,8 +165,10 @@ class Album:
if ( if (
len(tracks) == 1 len(tracks) == 1
and tracks[0].title == self.title and tracks[0].title == self.title
and tracks[0].track == 1
and tracks[0].disc == 1 # and tracks[0].track == 1
# and tracks[0].disc == 1
# Todo: Are the above commented checks necessary?
): ):
self.is_single = True self.is_single = True

View File

@ -48,7 +48,6 @@ SM_THUMB_PATH = os.path.join(THUMBS_PATH, "small")
LG_THUMBS_PATH = os.path.join(THUMBS_PATH, "large") LG_THUMBS_PATH = os.path.join(THUMBS_PATH, "large")
MUSIC_DIR = os.path.join(USER_HOME_DIR, "Music") MUSIC_DIR = os.path.join(USER_HOME_DIR, "Music")
# TEST_DIR = "/home/cwilvx/Downloads/Telegram Desktop" # TEST_DIR = "/home/cwilvx/Downloads/Telegram Desktop"
# TEST_DIR = "/mnt/dfc48e0f-103b-426e-9bf9-f25d3743bc96/Music/Chill/Wolftyla Radio" # TEST_DIR = "/mnt/dfc48e0f-103b-426e-9bf9-f25d3743bc96/Music/Chill/Wolftyla Radio"
# HOME_DIR = TEST_DIR # HOME_DIR = TEST_DIR
@ -80,10 +79,6 @@ USER_DATA_DB_NAME = "userdata.db"
APP_DB_PATH = os.path.join(APP_DIR, APP_DB_NAME) APP_DB_PATH = os.path.join(APP_DIR, APP_DB_NAME)
USERDATA_DB_PATH = os.path.join(APP_DIR, USER_DATA_DB_NAME) USERDATA_DB_PATH = os.path.join(APP_DIR, USER_DATA_DB_NAME)
# ===== Store =====
USE_STORE = True
HELP_MESSAGE = """ HELP_MESSAGE = """
Usage: swingmusic [options] Usage: swingmusic [options]
@ -91,10 +86,17 @@ Options:
--build: Build the application --build: Build the application
--host: Set the host --host: Set the host
--port: Set the port --port: Set the port
--no-feat: Do not extract featured artists from the song title
--help, -h: Show this help message --help, -h: Show this help message
--version, -v: Show the version --version, -v: Show the version
""" """
EXTRACT_FEAT = True
"""
Whether to extract the featured artists from the song title
Changed using the --no-feat flag
"""
class TCOLOR: class TCOLOR:
""" """

View File

@ -191,7 +191,7 @@ def get_albumartists(albums: list[models.Album]) -> set[str]:
def get_all_artists( def get_all_artists(
tracks: list[models.Track], albums: list[models.Album] tracks: list[models.Track], albums: list[models.Album]
) -> list[models.Artist]: ) -> list[models.Artist]:
artists_from_tracks = get_artists_from_tracks(tracks) artists_from_tracks = get_artists_from_tracks(tracks)
artist_from_albums = get_albumartists(albums) artist_from_albums = get_albumartists(albums)
@ -260,19 +260,29 @@ def is_windows():
return platform.system() == "Windows" return platform.system() == "Windows"
def parse_feat_from_title(title: str) -> list[str]: def parse_feat_from_title(title: str) -> tuple[list[str], str]:
""" """
Extracts featured artists from a song title using regex. Extracts featured artists from a song title using regex.
""" """
regex = r"\((?:feat|ft|featuring|with)\.?\s+(.+?)\)" regex = r"\((?:feat|ft|featuring|with)\.?\s+(.+?)\)"
# regex for square brackets 👇
sqr_regex = r"\[(?:feat|ft|featuring|with)\.?\s+(.+?)\]"
match = re.search(regex, title, re.IGNORECASE) match = re.search(regex, title, re.IGNORECASE)
if not match: if not match:
return [] match = re.search(sqr_regex, title, re.IGNORECASE)
regex = sqr_regex
if not match:
return [], title
artists = match.group(1) artists = match.group(1)
artists = split_artists(artists, with_and=True) artists = split_artists(artists, with_and=True)
return artists
# remove "feat" group from title
new_title = re.sub(regex, "", title, flags=re.IGNORECASE)
return artists, new_title
def get_random_str(length=5): def get_random_str(length=5):
@ -295,6 +305,7 @@ def split_artists(src: str, with_and: bool = False):
artists = re.split(exp, src) artists = re.split(exp, src)
return [a.strip() for a in artists] return [a.strip() for a in artists]
def parse_artist_from_filename(title: str): def parse_artist_from_filename(title: str):
""" """
Extracts artist names from a song title using regex. Extracts artist names from a song title using regex.
@ -327,7 +338,6 @@ def parse_title_from_filename(title: str):
res = re.sub(r"\s*\([^)]*official[^)]*\)", "", res, flags=re.IGNORECASE) res = re.sub(r"\s*\([^)]*official[^)]*\)", "", res, flags=re.IGNORECASE)
return res.strip() return res.strip()
# for title in sample_titles: # for title in sample_titles:
# print(parse_artist_from_filename(title)) # print(parse_artist_from_filename(title))
# print(parse_title_from_filename(title)) # print(parse_title_from_filename(title))

View File

@ -8,6 +8,7 @@ from configparser import ConfigParser
import PyInstaller.__main__ as bundler import PyInstaller.__main__ as bundler
from app import settings
from app.api import create_api from app.api import create_api
from app.functions import run_periodic_checks from app.functions import run_periodic_checks
from app.lib.watchdogg import Watcher as WatchDog from app.lib.watchdogg import Watcher as WatchDog
@ -18,6 +19,7 @@ from app.utils import background, get_home_res_path, get_ip, is_windows
werkzeug = logging.getLogger("werkzeug") werkzeug = logging.getLogger("werkzeug")
werkzeug.setLevel(logging.ERROR) werkzeug.setLevel(logging.ERROR)
class Variables: class Variables:
FLASK_PORT = 1970 FLASK_PORT = 1970
FLASK_HOST = "localhost" FLASK_HOST = "localhost"
@ -57,6 +59,7 @@ class ArgsEnum:
build = "--build" build = "--build"
port = "--port" port = "--port"
host = "--host" host = "--host"
no_feat = "--no-feat"
help = ["--help", "-h"] help = ["--help", "-h"]
version = ["--version", "-v"] version = ["--version", "-v"]
@ -66,6 +69,7 @@ class HandleArgs:
self.handle_build() self.handle_build()
self.handle_host() self.handle_host()
self.handle_port() self.handle_port()
self.handle_no_feat()
self.handle_help() self.handle_help()
self.handle_version() self.handle_version()
@ -130,6 +134,11 @@ class HandleArgs:
Variables.FLASK_HOST = host # type: ignore Variables.FLASK_HOST = host # type: ignore
@staticmethod
def handle_no_feat():
if ArgsEnum.no_feat in ARGS:
settings.EXTRACT_FEAT = False
@staticmethod @staticmethod
def handle_help(): def handle_help():
if any((a in ARGS for a in ArgsEnum.help)): if any((a in ARGS for a in ArgsEnum.help)):
@ -154,11 +163,17 @@ def start_watchdog():
WatchDog().run() WatchDog().run()
def log_info(): def log_startup_info():
lines = " ---------------------------------------" lines = "---------------------------------------"
# clears terminal 👇
os.system("cls" if os.name == "nt" else "echo -e \\\\033c") os.system("cls" if os.name == "nt" else "echo -e \\\\033c")
# TODO: Check whether the line above breaks Windows terminal's CTRL D
print(lines) print(lines)
print(f" {TCOLOR.HEADER}{APP_VERSION} {TCOLOR.ENDC}") print(f"{TCOLOR.HEADER}{APP_VERSION} {TCOLOR.ENDC}")
if not settings.EXTRACT_FEAT:
print(f"{TCOLOR.OKBLUE}Extracting featured artists from track titles: {TCOLOR.FAIL}DISABLED!{TCOLOR.ENDC}")
adresses = [Variables.FLASK_HOST] adresses = [Variables.FLASK_HOST]
@ -167,7 +182,7 @@ def log_info():
for address in adresses: for address in adresses:
print( print(
f" Started app on: {TCOLOR.OKGREEN}http://{address}:{Variables.FLASK_PORT}{TCOLOR.ENDC}" f"Started app on: {TCOLOR.OKGREEN}http://{address}:{Variables.FLASK_PORT}{TCOLOR.ENDC}"
) )
print(lines) print(lines)
@ -176,7 +191,7 @@ def log_info():
if __name__ == "__main__": if __name__ == "__main__":
HandleArgs() HandleArgs()
log_info() log_startup_info()
run_bg_checks() run_bg_checks()
start_watchdog() start_watchdog()

View File

@ -8,7 +8,7 @@ a = Analysis(
['manage.py'], ['manage.py'],
pathex=[], pathex=[],
binaries=[], binaries=[],
datas=[('assets', 'assets'), ('client', 'client'), ('app/migrations', 'app/migrations'), ('pyinstaller.config.ini', '.')], datas=[('assets', 'assets'), ('client', 'client'), ('pyinstaller.config.ini', '.')],
hiddenimports=[], hiddenimports=[],
hookspath=[], hookspath=[],
hooksconfig={}, hooksconfig={},