fix messed up bisection search

This commit is contained in:
geoffrey45
2022-05-07 14:45:16 +03:00
parent d6a01cd35e
commit fb67f568ad
8 changed files with 137 additions and 59 deletions
+15 -8
View File
@@ -1,5 +1,5 @@
""" """
This module contains mimi functions for the server. This module contains mini functions for the server.
""" """
import datetime import datetime
import os import os
@@ -27,9 +27,7 @@ def background(func):
return background_func return background_func
def run_fast_scandir(__dir: str, def run_fast_scandir(__dir: str, ext: list, full=False) -> Dict[List[str], List[str]]:
ext: list,
full=False) -> Dict[List[str], List[str]]:
""" """
Scans a directory for files with a specific extension. Returns a list of files and folders in the directory. Scans a directory for files with a specific extension. Returns a list of files and folders in the directory.
""" """
@@ -62,10 +60,12 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]:
while song_num < len(tracklist) - 1: while song_num < len(tracklist) - 1:
for index, song in enumerate(tracklist): for index, song in enumerate(tracklist):
if (tracklist[song_num].title == song.title if (
tracklist[song_num].title == song.title
and tracklist[song_num].album == song.album and tracklist[song_num].album == song.album
and tracklist[song_num].artists == song.artists and tracklist[song_num].artists == song.artists
and index != song_num): and index != song_num
):
tracklist.remove(song) tracklist.remove(song)
song_num += 1 song_num += 1
@@ -108,8 +108,7 @@ def check_artist_image(image: str) -> str:
""" """
img_name = image.replace("/", "::") + ".webp" img_name = image.replace("/", "::") + ".webp"
if not os.path.exists(os.path.join(app_dir, "images", "artists", if not os.path.exists(os.path.join(app_dir, "images", "artists", img_name)):
img_name)):
return use_memoji() return use_memoji()
else: else:
return img_name return img_name
@@ -125,3 +124,11 @@ class Timer:
def stop(self): def stop(self):
self.end = time.time() self.end = time.time()
print(str(datetime.timedelta(seconds=round(self.end - self.begin)))) print(str(datetime.timedelta(seconds=round(self.end - self.begin))))
def create_album_hash(title: str, artist: str) -> str:
"""
Creates a simple hash for an album
"""
return (title + artist).replace(" ", "").lower()
-1
View File
@@ -37,7 +37,6 @@ def send_thumbnail(imgpath: str):
@app.route("/a/<imgpath>") @app.route("/a/<imgpath>")
def send_artist_image(imgpath: str): def send_artist_image(imgpath: str):
print(ARTIST_PATH)
fpath = join(ARTIST_PATH, imgpath) fpath = join(ARTIST_PATH, imgpath)
exists = path.exists(fpath) exists = path.exists(fpath)
+39 -18
View File
@@ -1,20 +1,20 @@
""" """
This library contains all the functions related to albums. This library contains all the functions related to albums.
""" """
from copy import deepcopy
import random import random
import urllib import urllib
from pprint import pprint
from typing import List from typing import List
from app import api from app import api
from app import functions
from app import instances from app import instances
from app import models from app import models
from app import settings
from app.lib import taglib from app.lib import taglib
from app.lib import trackslib from app.lib import trackslib
from progress.bar import Bar from progress.bar import Bar
from app import helpers
def get_all_albums() -> List[models.Album]: def get_all_albums() -> List[models.Album]:
""" """
@@ -23,6 +23,7 @@ def get_all_albums() -> List[models.Album]:
print("Getting all albums...") print("Getting all albums...")
albums: List[models.Album] = [] albums: List[models.Album] = []
db_albums = instances.album_instance.get_all_albums() db_albums = instances.album_instance.get_all_albums()
_bar = Bar("Creating albums", max=len(db_albums)) _bar = Bar("Creating albums", max=len(db_albums))
@@ -44,7 +45,7 @@ def create_everything() -> List[models.Track]:
albums: list[models.Album] = get_all_albums() albums: list[models.Album] = get_all_albums()
api.ALBUMS = albums api.ALBUMS = albums
api.ALBUMS.sort(key=lambda x: x.title) api.ALBUMS.sort(key=lambda x: x.hash)
tracks = trackslib.create_all_tracks() tracks = trackslib.create_all_tracks()
@@ -57,6 +58,7 @@ def find_album(albumtitle: str, artist: str) -> int or None:
""" """
Finds an album by album title and artist. Finds an album by album title and artist.
""" """
left = 0 left = 0
right = len(api.ALBUMS) - 1 right = len(api.ALBUMS) - 1
iter = 0 iter = 0
@@ -64,12 +66,15 @@ def find_album(albumtitle: str, artist: str) -> int or None:
while left <= right: while left <= right:
iter += 1 iter += 1
mid = (left + right) // 2 mid = (left + right) // 2
hash = helpers.create_album_hash(albumtitle, artist)
if api.ALBUMS[mid].title == albumtitle and api.ALBUMS[ try:
mid].artist == artist: if api.ALBUMS[mid].hash == hash:
return mid return mid
except:
print(api.ALBUMS[mid])
if api.ALBUMS[mid].title < albumtitle: if api.ALBUMS[mid].hash < hash:
left = mid + 1 left = mid + 1
else: else:
right = mid - 1 right = mid - 1
@@ -125,39 +130,55 @@ def get_album_image(album: list) -> str:
return use_defaults() return use_defaults()
def get_album_tracks(album: str, artist: str) -> List: class GetAlbumTracks:
"""
Finds all the tracks that match a specific album, given the album title
and album artist.
"""
def __init__(self, album: str, artist: str) -> None:
self.hash = helpers.create_album_hash(album, artist)
self.tracks = api.DB_TRACKS
self.tracks.sort(key=lambda x: x["albumhash"])
def find_tracks(self):
tracks = [] tracks = []
index = trackslib.find_track(self.tracks, self.hash)
for track in api.DB_TRACKS: while index is not None:
try: track = self.tracks[index]
if track["album"] == album and track["albumartist"] == artist:
tracks.append(track) tracks.append(track)
except TypeError: self.tracks.remove(track)
pprint(track, indent=4) index = trackslib.find_track(self.tracks, self.hash)
print(album, artist)
api.DB_TRACKS.extend(tracks)
return tracks return tracks
def get_album_tracks(album: str, artist: str) -> List:
return GetAlbumTracks(album, artist).find_tracks()
def create_album(track) -> models.Album: def create_album(track) -> models.Album:
""" """
Generates and returns an album object from a track object. Generates and returns an album object from a track object.
""" """
album = { album = {
"album": track["album"], "title": track["album"],
"artist": track["albumartist"], "artist": track["albumartist"],
} }
album_tracks = get_album_tracks(album["album"], album["artist"]) album_tracks = get_album_tracks(album["title"], album["artist"])
album["date"] = album_tracks[0]["date"] album["date"] = album_tracks[0]["date"]
album["artistimage"] = urllib.parse.quote_plus( album["artistimage"] = urllib.parse.quote_plus(
album_tracks[0]["albumartist"] + ".webp") album_tracks[0]["albumartist"] + ".webp"
)
album["image"] = get_album_image(album_tracks) album["image"] = get_album_image(album_tracks)
return models.Album(album) return album
def search_albums_by_name(query: str) -> List[models.Album]: def search_albums_by_name(query: str) -> List[models.Album]:
+19 -8
View File
@@ -3,7 +3,7 @@ from os import path
from app import api from app import api
from app import settings from app import settings
from app.helpers import run_fast_scandir from app.helpers import create_album_hash, run_fast_scandir
from app.instances import album_instance from app.instances import album_instance
from app.instances import tracks_instance from app.instances import tracks_instance
from app.lib import folderslib from app.lib import folderslib
@@ -11,7 +11,7 @@ from app.lib.albumslib import create_album
from app.lib.albumslib import find_album from app.lib.albumslib import find_album
from app.lib.taglib import get_tags from app.lib.taglib import get_tags
from app.logger import Log from app.logger import Log
from app.models import Track from app.models import Album, Track
from progress.bar import Bar from progress.bar import Bar
@@ -44,6 +44,7 @@ class Populate:
self.tag_files() self.tag_files()
self.create_pre_albums() self.create_pre_albums()
self.create_albums() self.create_albums()
api.ALBUMS.sort(key=lambda x: x.hash)
self.create_tracks() self.create_tracks()
self.create_folders() self.create_folders()
@@ -73,6 +74,9 @@ class Populate:
self.folders.add(folder) self.folders.add(folder)
if tags is not None: if tags is not None:
tags["albumhash"] = create_album_hash(
tags["album"], tags["albumartist"]
)
self.tagged_tracks.append(tags) self.tagged_tracks.append(tags)
api.DB_TRACKS.append(tags) api.DB_TRACKS.append(tags)
@@ -108,16 +112,17 @@ class Populate:
if index is None: if index is None:
try: try:
track = [ track = [
track for track in self.tagged_tracks track
for track in self.tagged_tracks
if track["album"] == album["title"] if track["album"] == album["title"]
and track["albumartist"] == album["artist"] and track["albumartist"] == album["artist"]
][0] ][0]
album = create_album(track) album = create_album(track)
api.ALBUMS.append(album) api.ALBUMS.append(Album(album))
self.albums.append(album) self.albums.append(album)
album_instance.insert_album(asdict(album)) album_instance.insert_album(album)
except IndexError: except IndexError:
print("😠\n") print("😠\n")
@@ -128,7 +133,8 @@ class Populate:
bar.next() bar.next()
bar.finish() bar.finish()
Log(f"{exist_count} of {len(self.pre_albums)} albums were already in the database" Log(
f"{exist_count} of {len(self.pre_albums)} albums were already in the database"
) )
def create_tracks(self): def create_tracks(self):
@@ -137,13 +143,16 @@ class Populate:
""" """
bar = Bar("Creating tracks", max=len(self.tagged_tracks)) bar = Bar("Creating tracks", max=len(self.tagged_tracks))
failed_count = 0 failed_count = 0
for track in self.tagged_tracks: for track in self.tagged_tracks:
try: try:
album_index = find_album(track["album"], track["albumartist"]) album_index = find_album(track["album"], track["albumartist"])
album = api.ALBUMS[album_index] album = api.ALBUMS[album_index]
track["image"] = album.image track["image"] = album.image
upsert_id = tracks_instance.insert_song(track) upsert_id = tracks_instance.insert_song(track)
track["_id"] = {"$oid": str(upsert_id)} track["_id"] = {"$oid": str(upsert_id)}
api.TRACKS.append(Track(track)) api.TRACKS.append(Track(track))
except: except:
# Bug: some albums are not found although they exist in `api.ALBUMS`. It has something to do with the bisection method used or sorting. Not sure yet. # Bug: some albums are not found although they exist in `api.ALBUMS`. It has something to do with the bisection method used or sorting. Not sure yet.
@@ -151,7 +160,8 @@ class Populate:
bar.next() bar.next()
bar.finish() bar.finish()
Log(f"Added {len(self.tagged_tracks) - failed_count} of {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" Log(
f"Added {len(self.tagged_tracks) - failed_count} of {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums"
) )
def create_folders(self): def create_folders(self):
@@ -159,11 +169,12 @@ class Populate:
Creates the folder objects for all the tracks. Creates the folder objects for all the tracks.
""" """
bar = Bar("Creating folders", max=len(self.folders)) bar = Bar("Creating folders", max=len(self.folders))
old_f_count = len(api.FOLDERS)
for folder in self.folders: for folder in self.folders:
api.VALID_FOLDERS.add(folder) api.VALID_FOLDERS.add(folder)
fff = folderslib.create_folder(folder) fff = folderslib.create_folder(folder)
api.FOLDERS.append(fff) api.FOLDERS.append(fff)
bar.next() bar.next()
bar.finish() bar.finish()
+25
View File
@@ -26,6 +26,7 @@ def create_all_tracks() -> List[models.Track]:
except FileNotFoundError: except FileNotFoundError:
instances.tracks_instance.remove_song_by_id(track["_id"]["$oid"]) instances.tracks_instance.remove_song_by_id(track["_id"]["$oid"])
api.DB_TRACKS.remove(track) api.DB_TRACKS.remove(track)
try: try:
tracks.append(models.Track(track)) tracks.append(models.Track(track))
except KeyError: except KeyError:
@@ -54,3 +55,27 @@ def get_track_by_id(trackid: str) -> models.Track:
for track in api.TRACKS: for track in api.TRACKS:
if track.trackid == trackid: if track.trackid == trackid:
return track return track
def find_track(tracks: list, hash: str) -> int or None:
"""
Finds an album by album title and artist.
"""
left = 0
right = len(tracks) - 1
iter = 0
while left <= right:
iter += 1
mid = (left + right) // 2
if tracks[mid]["albumhash"] == hash:
return mid
if tracks[mid]["albumhash"] < hash:
left = mid + 1
else:
right = mid - 1
return None
+18 -7
View File
@@ -8,11 +8,13 @@ from app import api
from app import instances from app import instances
from app import models from app import models
from app.lib import folderslib from app.lib import folderslib
from app.lib.albumslib import create_album from app.lib.albumslib import create_album, find_album
from app.lib.taglib import get_tags from app.lib.taglib import get_tags
from watchdog.events import PatternMatchingEventHandler from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer from watchdog.observers import Observer
from app.helpers import create_album_hash
class OnMyWatch: class OnMyWatch:
""" """
@@ -48,14 +50,24 @@ def add_track(filepath: str) -> None:
tags = get_tags(filepath) tags = get_tags(filepath)
if tags is not None: if tags is not None:
instances.tracks_instance.insert_song(tags) tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
tags = instances.tracks_instance.get_song_by_path(tags["filepath"])
api.DB_TRACKS.append(tags) api.DB_TRACKS.append(tags)
album = create_album(tags)
albumindex = find_album(tags["album"], tags["albumartist"])
if albumindex is not None:
album = api.ALBUMS[albumindex]
else:
album_data = create_album(tags)
instances.album_instance.insert_album(album_data)
album = models.Album(album_data)
api.ALBUMS.append(album) api.ALBUMS.append(album)
tags["image"] = album.image tags["image"] = album.image
upsert_id = instances.tracks_instance.insert_song(tags)
tags["_id"] = {"$oid": str(upsert_id)}
api.TRACKS.append(models.Track(tags)) api.TRACKS.append(models.Track(tags))
folder = tags["folder"] folder = tags["folder"]
@@ -74,8 +86,7 @@ def remove_track(filepath: str) -> None:
fpath = filepath.replace(fname, "") fpath = filepath.replace(fname, "")
try: try:
trackid = instances.tracks_instance.get_song_by_path( trackid = instances.tracks_instance.get_song_by_path(filepath)["_id"]["$oid"]
filepath)["_id"]["$oid"]
except TypeError: except TypeError:
print(f"💙 Watchdog Error: Error removing track {filepath} TypeError") print(f"💙 Watchdog Error: Error removing track {filepath} TypeError")
return return
+10 -5
View File
@@ -3,12 +3,11 @@ Contains all the models for objects generation and typing.
""" """
from dataclasses import dataclass from dataclasses import dataclass
from dataclasses import field from dataclasses import field
from datetime import date
from typing import List from typing import List
from app import api from app import api
from app import settings
from app.exceptions import TrackExistsInPlaylist from app.exceptions import TrackExistsInPlaylist
from app import helpers
@dataclass @dataclass
@@ -30,6 +29,7 @@ class Track:
image: str image: str
tracknumber: int tracknumber: int
discnumber: int discnumber: int
albumhash: str
def __init__(self, tags): def __init__(self, tags):
@@ -46,6 +46,7 @@ class Track:
self.image = tags["image"] self.image = tags["image"]
self.tracknumber = tags["tracknumber"] self.tracknumber = tags["tracknumber"]
self.discnumber = tags["discnumber"] self.discnumber = tags["discnumber"]
self.albumhash = tags['albumhash']
@dataclass @dataclass
@@ -59,22 +60,26 @@ class Album:
date: int date: int
artistimage: str artistimage: str
image: str image: str
hash: str
count: int = 0 count: int = 0
duration: int = 0 duration: int = 0
def __init__(self, tags): def __init__(self, tags):
self.title = tags["album"] self.title = tags["title"]
self.artist = tags["artist"] self.artist = tags["artist"]
self.date = tags["date"] self.date = tags["date"]
self.artistimage = tags["artistimage"] self.artistimage = tags["artistimage"]
self.image = tags["image"] self.image = tags["image"]
self.hash = helpers.create_album_hash(self.title, self.artist)
def get_p_track(ptrack): def get_p_track(ptrack):
for track in api.TRACKS: for track in api.TRACKS:
if (track.title == ptrack["title"] if (
track.title == ptrack["title"]
and track.artists == ptrack["artists"] and track.artists == ptrack["artists"]
and ptrack["album"] == track.album): and ptrack["album"] == track.album
):
return track return track
-1
View File
@@ -13,7 +13,6 @@ def create_config_dir() -> None:
_home_dir = os.path.expanduser("~") _home_dir = os.path.expanduser("~")
config_folder = os.path.join(_home_dir, settings.CONFIG_FOLDER) config_folder = os.path.join(_home_dir, settings.CONFIG_FOLDER)
print(config_folder)
dirs = [ dirs = [
"", "",