diff --git a/server/app/api/album.py b/server/app/api/album.py index 0b34a24a..11b4ccef 100644 --- a/server/app/api/album.py +++ b/server/app/api/album.py @@ -40,7 +40,6 @@ def get_albums(): def get_album(): """Returns all the tracks in the given album.""" data = request.get_json() - print(data) album, artist = data["album"], data["artist"] albumhash = helpers.create_album_hash(album, artist) diff --git a/server/app/db/mongodb/albums.py b/server/app/db/mongodb/albums.py index b9e124be..030c900c 100644 --- a/server/app/db/mongodb/albums.py +++ b/server/app/db/mongodb/albums.py @@ -2,7 +2,6 @@ This file contains the Album class for interacting with album documents in MongoDB. """ -from app import db from app.db.mongodb import convert_many from app.db.mongodb import convert_one from app.db.mongodb import MongoAlbums @@ -26,7 +25,8 @@ class Albums(MongoAlbums): upsert=True, ).upserted_id - def insert_many(self, albums: list): + def insert_many(self, albums: Album): + albums = [a.__dict__ for a in albums] """ Inserts multiple albums into the database. """ diff --git a/server/app/functions.py b/server/app/functions.py index 9c972192..7d5509bf 100644 --- a/server/app/functions.py +++ b/server/app/functions.py @@ -6,16 +6,14 @@ import time from io import BytesIO import requests -from app import api from app import helpers from app import settings from app.lib import watchdoge -from app.lib.populate import Populate +from app.lib.populate import Populate, CreateAlbums from PIL import Image from concurrent.futures import ThreadPoolExecutor from app.lib import trackslib -from app import instances, models @helpers.background @@ -27,7 +25,8 @@ def reindex_tracks(): while True: trackslib.validate_tracks() - populate() + Populate() + CreateAlbums() CheckArtistImages()() time.sleep(60) @@ -41,11 +40,6 @@ def start_watchdog(): watchdoge.watch.run() -def populate(): - pop = Populate() - pop.run() - - class getArtistImage: """ Returns an artist image url. diff --git a/server/app/helpers.py b/server/app/helpers.py index f2b38672..ea11a63b 100644 --- a/server/app/helpers.py +++ b/server/app/helpers.py @@ -73,17 +73,6 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]: return tracklist - -# def save_image(url: str, path: str) -> None: -# """ -# Saves an image from an url to a path. -# """ - -# response = requests.get(url) -# img = Image.open(BytesIO(response.content)) -# img.save(path, "JPEG") - - def is_valid_file(filename: str) -> bool: """ Checks if a file is valid. Returns True if it is, False if it isn't. @@ -120,7 +109,7 @@ def create_album_hash(title: str, artist: str) -> str: Creates a simple hash for an album """ lower = (title + artist).replace(" ", "").lower() - hash = lower.join([i for i in lower if i not in '/\\:*?"<>|&']) + hash = "".join([i for i in lower if i not in '/\\:*?"<>|&']) return hash diff --git a/server/app/lib/albumslib.py b/server/app/lib/albumslib.py index 7030cbff..5c389833 100644 --- a/server/app/lib/albumslib.py +++ b/server/app/lib/albumslib.py @@ -34,32 +34,6 @@ def validate() -> None: """ -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 - right = len(albums) - 1 - - while left <= right: - mid = (left + right) // 2 - - if albums[mid].hash == hash: - return mid - - if albums[mid].hash < hash: - left = mid + 1 - else: - right = mid - 1 - - return None - - def get_album_duration(album: List[models.Track]) -> int: """ Gets the duration of an album. @@ -81,31 +55,19 @@ def use_defaults() -> str: return path -def gen_random_path() -> str: - """ - Generates a random image file path for an album image. - """ - choices = "abcdefghijklmnopqrstuvwxyz0123456789" - path = "".join(random.choice(choices) for i in range(20)) - path += ".webp" - - return path - - -def get_album_image(album: list) -> str: +def get_album_image(track: models.Track) -> str: """ Gets the image of an album. """ - for track in album: - img_p = gen_random_path() + img_p = track.albumhash + ".webp" - exists = taglib.extract_thumb(track["filepath"], webp_path=img_p) + success = taglib.extract_thumb(track.filepath, webp_path=img_p) - if exists: - return img_p + if success: + return img_p - return use_defaults() + return None class GetAlbumTracks: @@ -122,14 +84,6 @@ class GetAlbumTracks: def __call__(self): tracks = helpers.UseBisection(self.tracks, "albumhash", [self.hash])() - pprint(tracks) - - # while index is not None: - # track = self.tracks[index] - # tracks.append(track) - # self.tracks.remove(track) - # index = helpers.UseBisection(self.tracks, "albumhash", [self.hash])() - return tracks @@ -137,23 +91,23 @@ def get_album_tracks(tracklist: List[models.Track], hash: str) -> List: return GetAlbumTracks(tracklist, hash)() -def create_album(track: dict, tracklist: list[models.Track]) -> dict: +def create_album(track: models.Track) -> dict: """ Generates and returns an album object from a track object. """ album = { - "title": track["album"], - "artist": track["albumartist"], + "title": track.album, + "artist": track.albumartist, + "hash": track.albumhash, } - albumhash = helpers.create_album_hash(album["title"], album["artist"]) - album_tracks = get_album_tracks(tracklist, albumhash) - if len(album_tracks) == 0: - return None + album["date"] = track.date - album["date"] = album_tracks[0]["date"] + img_p = get_album_image(track) - album["image"] = get_album_image(album_tracks) - # album["image"] = "".join(x for x in albumhash if x not in "\/:*?<>|") + if img_p is not None: + album["image"] = img_p + return album + album["image"] = None return album diff --git a/server/app/lib/populate.py b/server/app/lib/populate.py index d1bd63e2..5ca7d31c 100644 --- a/server/app/lib/populate.py +++ b/server/app/lib/populate.py @@ -1,18 +1,17 @@ +from dataclasses import dataclass +from pprint import pprint import time from concurrent.futures import ThreadPoolExecutor from typing import List from app import settings -from app.helpers import create_album_hash +from app.helpers import Get, UseBisection, create_album_hash from app.helpers import run_fast_scandir -from app.instances import album_instance from app.instances import tracks_instance from app.lib.albumslib import create_album -from app.lib.albumslib import find_album from app.lib.taglib import get_tags -from app.lib.trackslib import find_track from app.logger import Log -from app.models import Album +from app.models import Album, Track from tqdm import tqdm from app import instances @@ -28,36 +27,15 @@ class Populate: """ def __init__(self) -> None: - self.files = [] self.db_tracks = [] self.tagged_tracks = [] - self.folders = set() - self.pre_albums = [] - self.albums: List[Album] = [] self.files = run_fast_scandir(settings.HOME_DIR, full=True)[1] self.db_tracks = tracks_instance.get_all_tracks() - self.tag_count = 0 - self.exist_count = 0 - self.tracks = [] - def run(self): self.check_untagged() self.tag_untagged() - if len(self.tagged_tracks) == 0: - return - - # self.tagged_tracks.sort(key=lambda x: x["albumhash"]) - - self.pre_albums = self.create_pre_albums(self.tagged_tracks) - self.create_albums(self.pre_albums) - - self.albums.sort(key=lambda x: x.hash) - self.create_tracks() - - self.save_all() - def check_untagged(self): """ Loops through all the tracks in db tracks removing each @@ -88,97 +66,80 @@ class Populate: with ThreadPoolExecutor() as executor: executor.map(self.get_tags, self.files) - tracks_instance.insert_many(self.tagged_tracks) + if len(self.tagged_tracks) > 0: + tracks_instance.insert_many(self.tagged_tracks) + d = time.time() - s Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds") + +@dataclass +class PreAlbum: + title: str + artist: str + hash: str + + +class CreateAlbums: + def __init__(self) -> None: + self.db_tracks = Get.get_all_tracks() + self.db_albums = Get.get_all_albums() + + prealbums = self.create_pre_albums(self.db_tracks) + prealbums = self.filter_processed(self.db_albums, prealbums) + print(f"📌 {len(prealbums)}") + + albums = [] + for album in tqdm(prealbums, desc="Creating albums"): + a = self.create_album(album) + albums.append(a) + + if len(albums) > 0: + instances.album_instance.insert_many(albums) + @staticmethod - def create_pre_albums(tracks: List[dict]): - """ - Creates pre-albums for the all tagged tracks. - """ + def create_pre_albums(tracks: List[Track]) -> List[PreAlbum]: prealbums = [] - for track in tqdm(tracks, desc="Creating pre-albums"): - album = {"title": track["album"], "artist": track["albumartist"]} + for track in tqdm(tracks, desc="Creating prealbums"): + album = { + "title": track.album, + "artist": track.albumartist, + "hash": track.albumhash, + } + + album = PreAlbum(**album) if album not in prealbums: prealbums.append(album) - Log(f"Created {len(prealbums)} pre-albums") return prealbums - def create_album(self, album: dict): - albumhash = create_album_hash(album["title"], album["artist"]) - album = instances.album_instance.find_album_by_hash(albumhash) + @staticmethod + def filter_processed(albums: List[Album], prealbums: List[PreAlbum]) -> List[dict]: + to_process = [] - if album is not None: - self.albums.append(album) - self.exist_count += 1 - return + for p in tqdm(prealbums, desc="Filtering processed albums"): + album = UseBisection(albums, "hash", [p.hash])()[0] - index = find_track(self.tagged_tracks, albumhash) + if album is None: + to_process.append(p) - track = self.tagged_tracks[index] + return to_process - album = create_album(track, self.tagged_tracks) + def create_album(self, album: PreAlbum) -> Album: + hash = album.hash - if album is None: - print("album is none") - return + album = {"image": None} + + while album["image"] is None: + track = UseBisection(self.db_tracks, "albumhash", [hash])()[0] + + if track is not None: + album = create_album(track) + self.db_tracks.remove(track) + else: + album["image"] = hash album = Album(album) - - self.albums.append(album) - - def create_albums(self, albums: List[dict]): - """ - Uses the pre-albums to create new albums and add them to the database. - """ - for album in tqdm(albums, desc="Building albums"): - self.create_album(album) - - Log(f"{self.exist_count} of {len(albums)} albums were already in the database") - - def create_track(self, track: dict): - """ - Creates a single track object. - """ - - albumhash = track["albumhash"] - index = find_album(self.albums, albumhash) - - if index is None: - return - - try: - album: Album = self.albums[index] - except (TypeError): - """ - 😭😭😭 - """ - pass - - track["image"] = album.image - return track - - def create_tracks(self): - """ - Loops through all the tagged tracks creating complete track objects using the `models.Track` model. - """ - with ThreadPoolExecutor() as executor: - iterable = executor.map(self.create_track, self.tagged_tracks) - - self.tracks = [t for t in iterable if t is not None] - - Log( - f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" - ) - - def save_all(self): - """ - Saves the albums to the database. - """ - - album_instance.insert_many([a.__dict__ for a in self.albums]) - tracks_instance.insert_many(self.tracks) + return album diff --git a/server/app/lib/taglib.py b/server/app/lib/taglib.py index 0106b2c3..1292feba 100644 --- a/server/app/lib/taglib.py +++ b/server/app/lib/taglib.py @@ -135,8 +135,8 @@ def parse_track_number(tags): Parses the track number from an audio file. """ try: - track_number = tags["tracknumber"][0] - except (KeyError, IndexError): + track_number = int(tags["tracknumber"][0]) + except (KeyError, IndexError, ValueError): track_number = 1 return track_number @@ -147,8 +147,8 @@ def parse_disk_number(tags): Parses the disk number from an audio file. """ try: - disk_number = tags["disknumber"][0] - except (KeyError, IndexError): + disk_number = int(tags["disknumber"][0]) + except (KeyError, IndexError, ValueError): disk_number = 1 return disk_number diff --git a/server/app/lib/trackslib.py b/server/app/lib/trackslib.py index 3c73af39..34d037e2 100644 --- a/server/app/lib/trackslib.py +++ b/server/app/lib/trackslib.py @@ -44,7 +44,7 @@ def get_track_by_id(trackid: str) -> models.Track: print("AttributeError") -def find_track(tracks: list, hash: str) -> int or None: +def find_track(tracks: list, hash: str) -> int | None: """ Finds an album by album title and artist. """ diff --git a/server/app/models.py b/server/app/models.py index 2bc11fe6..1c461294 100644 --- a/server/app/models.py +++ b/server/app/models.py @@ -6,7 +6,6 @@ import random from typing import List from app import helpers -from app.exceptions import TrackExistsInPlaylist @dataclass(slots=True) @@ -25,20 +24,14 @@ class Track: length: int genre: str bitrate: int - image: str tracknumber: int disknumber: int albumhash: str + date: str + image: str def __init__(self, tags): - try: - self.trackid = tags["_id"]["$oid"] - except KeyError: - self.trackid = "".join( - random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for i in range(20) - ) - print("No id") - + self.trackid = tags["_id"]["$oid"] self.title = tags["title"] self.artists = tags["artists"].split(", ") self.albumartist = tags["albumartist"] @@ -50,16 +43,9 @@ class Track: self.length = int(tags["length"]) self.disknumber = int(tags["disknumber"]) self.albumhash = tags["albumhash"] - - try: - self.image = tags["image"] - except KeyError: - print(tags) - - try: - self.tracknumber = int(tags["tracknumber"]) - except ValueError: - self.tracknumber = 1 + self.date = tags["date"] + self.image = tags["albumhash"] + ".webp" + self.tracknumber = int(tags["tracknumber"]) @dataclass(slots=True) @@ -81,14 +67,14 @@ class Artist: @dataclass class Album: """ - Album class + Creates an album object """ title: str artist: str + hash: str date: int image: str - hash: str count: int = 0 duration: int = 0 is_soundtrack: bool = False @@ -100,11 +86,7 @@ class Album: self.artist = tags["artist"] self.date = tags["date"] self.image = tags["image"] - - try: - self.hash = tags["albumhash"] - except KeyError: - self.hash = helpers.create_album_hash(self.title, self.artist) + self.hash = tags["hash"] @property def is_soundtrack(self) -> bool: diff --git a/server/app/settings.py b/server/app/settings.py index fb840a26..9cce056e 100644 --- a/server/app/settings.py +++ b/server/app/settings.py @@ -12,7 +12,7 @@ HOME_DIR = os.path.expanduser("~") APP_DIR = os.path.join(HOME_DIR, CONFIG_FOLDER) THUMBS_PATH = os.path.join(APP_DIR, "images", "thumbnails") TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio" -HOME_DIR = TEST_DIR +# HOME_DIR = TEST_DIR # URL IMG_BASE_URI = "http://127.0.0.1:8900/images/" IMG_ARTIST_URI = IMG_BASE_URI + "artists/" diff --git a/src/interfaces.ts b/src/interfaces.ts index 78b99e44..03b96aa5 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -6,6 +6,7 @@ export interface Track { album?: string; artists: string[]; albumartist?: string; + albumhash?: string; folder?: string; filepath?: string; length?: number;