diff --git a/server/app/api/search.py b/server/app/api/search.py index 18da8342..34374490 100644 --- a/server/app/api/search.py +++ b/server/app/api/search.py @@ -40,7 +40,7 @@ def search(): if artist_obj not in artists_dicts: artists_dicts.append(artist_obj) - _tracks = searchlib.get_tracks(query) + _tracks = searchlib.SearchTracks(query)() tracks = [*_tracks, *artist_tracks] SEARCH_RESULTS.clear() diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py index 67193893..aa59a86a 100644 --- a/server/app/lib/albumslib.py +++ b/server/app/lib/albumslib.py @@ -52,6 +52,10 @@ def create_everything() -> List[models.Track]: def find_album(albums: List[models.Album], hash: str) -> int | None: """ Finds an album by album title and artist. + + :param `albums`: List of album objects. + :param `hash`: Hash of album. + :return: Index of album in list. """ left = 0 @@ -62,8 +66,11 @@ def find_album(albums: List[models.Album], hash: str) -> int | None: iter += 1 mid = (left + right) // 2 - if albums[mid].hash == hash: - return mid + try: + if albums[mid].hash == hash: + return mid + except AttributeError: + print(albums) if albums[mid].hash < hash: left = mid + 1 @@ -151,7 +158,7 @@ def get_album_tracks(album: str, artist: str) -> List: return GetAlbumTracks(album, artist).find_tracks() -def create_album(track: dict, tracklist: list) -> models.Album: +def create_album(track: dict, tracklist: list) -> dict: """ Generates and returns an album object from a track object. """ diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py index df4c0be3..364f3910 100644 --- a/server/app/lib/populate.py +++ b/server/app/lib/populate.py @@ -1,6 +1,7 @@ from concurrent.futures import ThreadPoolExecutor +from multiprocessing import Pool from copy import deepcopy - +import os from os import path import time from typing import List @@ -46,7 +47,7 @@ class Populate: def run(self): self.check_untagged() - self.tag_all_files() + self.get_all_tags() if len(self.tagged_tracks) == 0: return @@ -76,6 +77,17 @@ class Populate: Log(f"Found {len(self.files)} untagged tracks") + def process_tags(self, tags: dict): + for t in tags: + if t is None: + continue + + t["albumhash"] = create_album_hash(t["album"], t["albumartist"]) + self.tagged_tracks.append(t) + api.DB_TRACKS.append(t) + + self.folders.add(t["folder"]) + def get_tags(self, file: str): tags = get_tags(file) @@ -87,15 +99,22 @@ class Populate: self.tagged_tracks.append(tags) api.DB_TRACKS.append(tags) - def tag_all_files(self): + def get_all_tags(self): """ Loops through all the untagged files and tags them. """ s = time.time() - print(f"Started tagging files") - with ThreadPoolExecutor() as executor: - executor.map(self.get_tags, self.files) + # print(f"Started tagging files") + # with ThreadPoolExecutor() as executor: + # executor.map(self.get_tags, self.files) + + with Pool(maxtasksperchild=10) as p: + tags = p.map(get_tags, tqdm(self.files)) + self.process_tags(tags) + + # for t in tqdm(self.files): + # self.get_tags(t) d = time.time() - s Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds") diff --git a/server/app/lib/searchlib.py b/server/app/lib/searchlib.py index ae7225c2..cf7438e0 100644 --- a/server/app/lib/searchlib.py +++ b/server/app/lib/searchlib.py @@ -3,17 +3,100 @@ This library contains all the functions related to the search functionality. """ from typing import List -from app import models, helpers + +from app import api, helpers, models from app.lib import albumslib -from app import api +from rapidfuzz import fuzz, process -def get_tracks(query: str) -> List[models.Track]: - """ - Gets all songs with a given title. - """ - tracks = [track for track in api.TRACKS if query.lower() in track.title.lower()] - return helpers.remove_duplicates(tracks) +class SearchTracks: + def __init__(self, query) -> None: + self.query = query + + def __call__(self) -> List[models.Track]: + """ + Gets all songs with a given title. + """ + + tracks = [track.title for track in api.TRACKS] + results = process.extract( + self.query, tracks, scorer=fuzz.token_set_ratio, score_cutoff=50 + ) + print(results) + return [api.TRACKS[i[2]] for i in results] + + +class SearchArtists: + def __init__(self, query) -> None: + self.query = query + + @staticmethod + def get_all_artist_names() -> List[str]: + """ + Gets all artist names. + """ + + artists = [track.artists for track in api.TRACKS] + + f_artists = [] + for artist in artists: + aa = artist.split(",") + f_artists.extend(aa) + + return f_artists + + @staticmethod + def get_valid_name(name: str) -> str: + """ + returns a valid artist name + """ + + return "".join([i for i in name if i not in '/\\:*?"<>|']) + + def __call__(self) -> list: + """ + Gets all artists with a given name. + """ + + artists = self.get_all_artist_names() + results = process.extract(self.query, artists) + + f_artists = [] + for artist in results: + aa = { + "name": artist[0], + "image": self.get_valid_name(artist[0]) + ".webp", + } + f_artists.append(aa) + + return f_artists + + +class SearchAlbums: + def __init__(self, query) -> None: + self.query = query + + def get_albums_by_name(self) -> List[models.Album]: + """ + Gets all albums with a given title. + """ + + albums = [album.title for album in api.ALBUMS] + results = process.extract(self.query, albums) + return [api.ALBUMS[i[2]] for i in results] + + def __call__(self) -> List[models.Album]: + """ + Gets all albums with a given title. + """ + + artists = SearchArtists(self.query)() + a_albums = [] + + # get all artists that matched the query + # for get all albums from the artists + # get all albums that matched the query + # return [**artist_albums **albums] def get_search_albums(query: str) -> List[models.Album]: @@ -27,4 +110,6 @@ def get_artists(artist: str) -> List[models.Track]: """ Gets all songs with a given artist. """ - return [track for track in api.TRACKS if artist.lower() in str(track.artists).lower()] + return [ + track for track in api.TRACKS if artist.lower() in str(track.artists).lower() + ] diff --git a/server/app/lib/taglib.py b/server/app/lib/taglib.py index 3a695a51..bee344ff 100644 --- a/server/app/lib/taglib.py +++ b/server/app/lib/taglib.py @@ -154,7 +154,7 @@ def parse_disk_number(audio): return disk_number -def get_tags(fullpath: str) -> dict: +def get_tags(fullpath: str) -> dict | None: """ Returns a dictionary of tags for a given file. """ diff --git a/server/app/lib/watchdoge.py b/server/app/lib/watchdoge.py index e094d849..2b98bd25 100644 --- a/server/app/lib/watchdoge.py +++ b/server/app/lib/watchdoge.py @@ -50,18 +50,19 @@ def add_track(filepath: str) -> None: tags = get_tags(filepath) if tags is not None: - tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"]) + hash = create_album_hash(tags["album"], tags["albumartist"]) + tags["albumhash"] = hash api.DB_TRACKS.append(tags) - albumindex = find_album(tags["album"], tags["albumartist"]) + albumindex = find_album(api.ALBUMS, hash) if albumindex is not None: album = api.ALBUMS[albumindex] else: album_data = create_album(tags, api.DB_TRACKS) - instances.album_instance.insert_album(album_data) - album = models.Album(album_data) + + instances.album_instance.insert_album(album) api.ALBUMS.append(album) tags["image"] = album.image @@ -152,7 +153,14 @@ class Handler(PatternMatchingEventHandler): Fired when a created file is closed. """ print("⚫ closed ~~~") - self.files_to_process.remove(event.src_path) + try: + self.files_to_process.remove(event.src_path) + except ValueError: + """ + The file was already removed from the list, or it was not in the list to begin with. + """ + pass + add_track(event.src_path) diff --git a/server/app/models.py b/server/app/models.py index d3d3a35f..5cc7d112 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -32,8 +32,11 @@ class Track: albumhash: str def __init__(self, tags): + try: + self.trackid = tags["_id"]["$oid"] + except KeyError: + print(tags) - self.trackid = tags["_id"]["$oid"] self.title = tags["title"] self.artists = tags["artists"].split(", ") self.albumartist = tags["albumartist"] @@ -49,6 +52,22 @@ class Track: self.albumhash = tags["albumhash"] +@dataclass(slots=True) +class Artist: + """ + Artist class + """ + + artistid: str + name: str + image: str + + def __init__(self, tags): + self.artistid = tags["_id"]["$oid"] + self.name = tags["name"] + self.image = tags["image"] + + @dataclass class Album: """ diff --git a/src/components/nav/Search.vue b/src/components/nav/Search.vue index ad60eaab..7640bbb6 100644 --- a/src/components/nav/Search.vue +++ b/src/components/nav/Search.vue @@ -5,7 +5,7 @@ type="search" name="" id="" - placeholder="Search this playlist" + placeholder="Search here" class="rounded" /> diff --git a/src/contexts/track_context.ts b/src/contexts/track_context.ts index 99a35a0f..f9f33fde 100644 --- a/src/contexts/track_context.ts +++ b/src/contexts/track_context.ts @@ -137,14 +137,14 @@ export default async ( add_to_playlist, play_next, add_to_q, - add_to_fav, + // add_to_fav, separator, go_to_folder, - go_to_artist, - go_to_alb_artist, + // go_to_artist, + // go_to_alb_artist, go_to_album, - separator, - del_track, + // separator, + // del_track, ]; return options;