process albums seperate from tracks

- break populate function into 2
This commit is contained in:
geoffrey45
2022-06-19 14:45:25 +03:00
parent 06ed41d869
commit 3cf44759b5
11 changed files with 100 additions and 220 deletions
-1
View File
@@ -40,7 +40,6 @@ def get_albums():
def get_album(): def get_album():
"""Returns all the tracks in the given album.""" """Returns all the tracks in the given album."""
data = request.get_json() data = request.get_json()
print(data)
album, artist = data["album"], data["artist"] album, artist = data["album"], data["artist"]
albumhash = helpers.create_album_hash(album, artist) albumhash = helpers.create_album_hash(album, artist)
+2 -2
View File
@@ -2,7 +2,6 @@
This file contains the Album class for interacting with This file contains the Album class for interacting with
album documents in MongoDB. album documents in MongoDB.
""" """
from app import db
from app.db.mongodb import convert_many from app.db.mongodb import convert_many
from app.db.mongodb import convert_one from app.db.mongodb import convert_one
from app.db.mongodb import MongoAlbums from app.db.mongodb import MongoAlbums
@@ -26,7 +25,8 @@ class Albums(MongoAlbums):
upsert=True, upsert=True,
).upserted_id ).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. Inserts multiple albums into the database.
""" """
+3 -9
View File
@@ -6,16 +6,14 @@ import time
from io import BytesIO from io import BytesIO
import requests import requests
from app import api
from app import helpers from app import helpers
from app import settings from app import settings
from app.lib import watchdoge from app.lib import watchdoge
from app.lib.populate import Populate from app.lib.populate import Populate, CreateAlbums
from PIL import Image from PIL import Image
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from app.lib import trackslib from app.lib import trackslib
from app import instances, models
@helpers.background @helpers.background
@@ -27,7 +25,8 @@ def reindex_tracks():
while True: while True:
trackslib.validate_tracks() trackslib.validate_tracks()
populate() Populate()
CreateAlbums()
CheckArtistImages()() CheckArtistImages()()
time.sleep(60) time.sleep(60)
@@ -41,11 +40,6 @@ def start_watchdog():
watchdoge.watch.run() watchdoge.watch.run()
def populate():
pop = Populate()
pop.run()
class getArtistImage: class getArtistImage:
""" """
Returns an artist image url. Returns an artist image url.
+1 -12
View File
@@ -73,17 +73,6 @@ def remove_duplicates(tracklist: List[models.Track]) -> List[models.Track]:
return tracklist 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: def is_valid_file(filename: str) -> bool:
""" """
Checks if a file is valid. Returns True if it is, False if it isn't. 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 Creates a simple hash for an album
""" """
lower = (title + artist).replace(" ", "").lower() 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 return hash
+16 -62
View File
@@ -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: def get_album_duration(album: List[models.Track]) -> int:
""" """
Gets the duration of an album. Gets the duration of an album.
@@ -81,31 +55,19 @@ def use_defaults() -> str:
return path return path
def gen_random_path() -> str: def get_album_image(track: models.Track) -> 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:
""" """
Gets the image of an album. Gets the image of an album.
""" """
for track in album: img_p = track.albumhash + ".webp"
img_p = gen_random_path()
exists = taglib.extract_thumb(track["filepath"], webp_path=img_p) success = taglib.extract_thumb(track.filepath, webp_path=img_p)
if exists: if success:
return img_p return img_p
return use_defaults() return None
class GetAlbumTracks: class GetAlbumTracks:
@@ -122,14 +84,6 @@ class GetAlbumTracks:
def __call__(self): def __call__(self):
tracks = helpers.UseBisection(self.tracks, "albumhash", [self.hash])() 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 return tracks
@@ -137,23 +91,23 @@ def get_album_tracks(tracklist: List[models.Track], hash: str) -> List:
return GetAlbumTracks(tracklist, hash)() 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. Generates and returns an album object from a track object.
""" """
album = { album = {
"title": track["album"], "title": track.album,
"artist": track["albumartist"], "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: album["date"] = track.date
return None
album["date"] = album_tracks[0]["date"] img_p = get_album_image(track)
album["image"] = get_album_image(album_tracks) if img_p is not None:
# album["image"] = "".join(x for x in albumhash if x not in "\/:*?<>|") album["image"] = img_p
return album
album["image"] = None
return album return album
+62 -101
View File
@@ -1,18 +1,17 @@
from dataclasses import dataclass
from pprint import pprint
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from typing import List from typing import List
from app import settings 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.helpers import run_fast_scandir
from app.instances import album_instance
from app.instances import tracks_instance from app.instances import tracks_instance
from app.lib.albumslib import create_album from app.lib.albumslib import create_album
from app.lib.albumslib import find_album
from app.lib.taglib import get_tags from app.lib.taglib import get_tags
from app.lib.trackslib import find_track
from app.logger import Log from app.logger import Log
from app.models import Album from app.models import Album, Track
from tqdm import tqdm from tqdm import tqdm
from app import instances from app import instances
@@ -28,36 +27,15 @@ class Populate:
""" """
def __init__(self) -> None: def __init__(self) -> None:
self.files = []
self.db_tracks = [] self.db_tracks = []
self.tagged_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.files = run_fast_scandir(settings.HOME_DIR, full=True)[1]
self.db_tracks = tracks_instance.get_all_tracks() 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.check_untagged()
self.tag_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): def check_untagged(self):
""" """
Loops through all the tracks in db tracks removing each Loops through all the tracks in db tracks removing each
@@ -88,97 +66,80 @@ class Populate:
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
executor.map(self.get_tags, self.files) 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 d = time.time() - s
Log(f"Tagged {len(self.tagged_tracks)} files in {d} seconds") 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 @staticmethod
def create_pre_albums(tracks: List[dict]): def create_pre_albums(tracks: List[Track]) -> List[PreAlbum]:
"""
Creates pre-albums for the all tagged tracks.
"""
prealbums = [] prealbums = []
for track in tqdm(tracks, desc="Creating pre-albums"): for track in tqdm(tracks, desc="Creating prealbums"):
album = {"title": track["album"], "artist": track["albumartist"]} album = {
"title": track.album,
"artist": track.albumartist,
"hash": track.albumhash,
}
album = PreAlbum(**album)
if album not in prealbums: if album not in prealbums:
prealbums.append(album) prealbums.append(album)
Log(f"Created {len(prealbums)} pre-albums")
return prealbums return prealbums
def create_album(self, album: dict): @staticmethod
albumhash = create_album_hash(album["title"], album["artist"]) def filter_processed(albums: List[Album], prealbums: List[PreAlbum]) -> List[dict]:
album = instances.album_instance.find_album_by_hash(albumhash) to_process = []
if album is not None: for p in tqdm(prealbums, desc="Filtering processed albums"):
self.albums.append(album) album = UseBisection(albums, "hash", [p.hash])()[0]
self.exist_count += 1
return
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: album = {"image": None}
print("album is none")
return 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) album = Album(album)
return 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)
+4 -4
View File
@@ -135,8 +135,8 @@ def parse_track_number(tags):
Parses the track number from an audio file. Parses the track number from an audio file.
""" """
try: try:
track_number = tags["tracknumber"][0] track_number = int(tags["tracknumber"][0])
except (KeyError, IndexError): except (KeyError, IndexError, ValueError):
track_number = 1 track_number = 1
return track_number return track_number
@@ -147,8 +147,8 @@ def parse_disk_number(tags):
Parses the disk number from an audio file. Parses the disk number from an audio file.
""" """
try: try:
disk_number = tags["disknumber"][0] disk_number = int(tags["disknumber"][0])
except (KeyError, IndexError): except (KeyError, IndexError, ValueError):
disk_number = 1 disk_number = 1
return disk_number return disk_number
+1 -1
View File
@@ -44,7 +44,7 @@ def get_track_by_id(trackid: str) -> models.Track:
print("AttributeError") 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. Finds an album by album title and artist.
""" """
+9 -27
View File
@@ -6,7 +6,6 @@ import random
from typing import List from typing import List
from app import helpers from app import helpers
from app.exceptions import TrackExistsInPlaylist
@dataclass(slots=True) @dataclass(slots=True)
@@ -25,20 +24,14 @@ class Track:
length: int length: int
genre: str genre: str
bitrate: int bitrate: int
image: str
tracknumber: int tracknumber: int
disknumber: int disknumber: int
albumhash: str albumhash: str
date: str
image: str
def __init__(self, tags): def __init__(self, tags):
try: self.trackid = tags["_id"]["$oid"]
self.trackid = tags["_id"]["$oid"]
except KeyError:
self.trackid = "".join(
random.choice("abcdefghijklmnopqrstuvwxyz0123456789") for i in range(20)
)
print("No id")
self.title = tags["title"] self.title = tags["title"]
self.artists = tags["artists"].split(", ") self.artists = tags["artists"].split(", ")
self.albumartist = tags["albumartist"] self.albumartist = tags["albumartist"]
@@ -50,16 +43,9 @@ class Track:
self.length = int(tags["length"]) self.length = int(tags["length"])
self.disknumber = int(tags["disknumber"]) self.disknumber = int(tags["disknumber"])
self.albumhash = tags["albumhash"] self.albumhash = tags["albumhash"]
self.date = tags["date"]
try: self.image = tags["albumhash"] + ".webp"
self.image = tags["image"] self.tracknumber = int(tags["tracknumber"])
except KeyError:
print(tags)
try:
self.tracknumber = int(tags["tracknumber"])
except ValueError:
self.tracknumber = 1
@dataclass(slots=True) @dataclass(slots=True)
@@ -81,14 +67,14 @@ class Artist:
@dataclass @dataclass
class Album: class Album:
""" """
Album class Creates an album object
""" """
title: str title: str
artist: str artist: str
hash: str
date: int date: int
image: str image: str
hash: str
count: int = 0 count: int = 0
duration: int = 0 duration: int = 0
is_soundtrack: bool = False is_soundtrack: bool = False
@@ -100,11 +86,7 @@ class Album:
self.artist = tags["artist"] self.artist = tags["artist"]
self.date = tags["date"] self.date = tags["date"]
self.image = tags["image"] self.image = tags["image"]
self.hash = tags["hash"]
try:
self.hash = tags["albumhash"]
except KeyError:
self.hash = helpers.create_album_hash(self.title, self.artist)
@property @property
def is_soundtrack(self) -> bool: def is_soundtrack(self) -> bool:
+1 -1
View File
@@ -12,7 +12,7 @@ HOME_DIR = os.path.expanduser("~")
APP_DIR = os.path.join(HOME_DIR, CONFIG_FOLDER) APP_DIR = os.path.join(HOME_DIR, CONFIG_FOLDER)
THUMBS_PATH = os.path.join(APP_DIR, "images", "thumbnails") THUMBS_PATH = os.path.join(APP_DIR, "images", "thumbnails")
TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio" TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio"
HOME_DIR = TEST_DIR # HOME_DIR = TEST_DIR
# URL # URL
IMG_BASE_URI = "http://127.0.0.1:8900/images/" IMG_BASE_URI = "http://127.0.0.1:8900/images/"
IMG_ARTIST_URI = IMG_BASE_URI + "artists/" IMG_ARTIST_URI = IMG_BASE_URI + "artists/"
+1
View File
@@ -6,6 +6,7 @@ export interface Track {
album?: string; album?: string;
artists: string[]; artists: string[];
albumartist?: string; albumartist?: string;
albumhash?: string;
folder?: string; folder?: string;
filepath?: string; filepath?: string;
length?: number; length?: number;