refactor most things to use the database directly

This commit is contained in:
geoffrey45
2022-06-13 14:45:18 +03:00
parent f1ec6309ba
commit 030ab8a379
18 changed files with 237 additions and 365 deletions
-18
View File
@@ -3,25 +3,10 @@ This module contains all the Flask Blueprints and API routes. It also contains a
that are used through-out the app. It handles the initialization of the watchdog, that are used through-out the app. It handles the initialization of the watchdog,
checking and creating config dirs and starting the re-indexing process using a background thread. checking and creating config dirs and starting the re-indexing process using a background thread.
""" """
from typing import List
from typing import Set
from app import functions from app import functions
from app import helpers from app import helpers
from app import instances
from app import models
from app import prep from app import prep
from app.lib import albumslib
from app.lib import folderslib
from app.lib import playlistlib
DB_TRACKS = instances.tracks_instance.get_all_tracks()
VALID_FOLDERS: Set[str] = set()
ALBUMS: List[models.Album] = []
TRACKS: List[models.Track] = []
PLAYLISTS: List[models.Playlist] = []
FOLDERS: List[models.Folder] = List
@helpers.background @helpers.background
@@ -31,9 +16,6 @@ def initialize() -> None:
""" """
functions.start_watchdog() functions.start_watchdog()
prep.create_config_dir() prep.create_config_dir()
albumslib.create_everything()
folderslib.run_scandir()
playlistlib.create_all_playlists()
functions.reindex_tracks() functions.reindex_tracks()
+20 -22
View File
@@ -1,16 +1,17 @@
""" """
Contains all the album routes. Contains all the album routes.
""" """
from pprint import pprint
from typing import List from typing import List
from app import api from app import api
from app import helpers from app import helpers
from app import models from app import models
from app.lib import albumslib from app.lib import albumslib
from app.lib import trackslib
from flask import Blueprint from flask import Blueprint
from flask import request from flask import request
from app.functions import FetchAlbumBio from app.functions import FetchAlbumBio
from app import instances
album_bp = Blueprint("album", __name__, url_prefix="") album_bp = Blueprint("album", __name__, url_prefix="")
@@ -35,31 +36,31 @@ def get_albums():
return {"albums": albums} return {"albums": albums}
@album_bp.route("/album/tracks", methods=["POST"]) @album_bp.route("/album", methods=["POST"])
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()
album, artist = data["album"], data["artist"]
album = data["album"]
artist = data["artist"]
songs = trackslib.get_album_tracks(album, artist)
albumhash = helpers.create_album_hash(album, artist) albumhash = helpers.create_album_hash(album, artist)
index = albumslib.find_album(api.ALBUMS, albumhash)
album: models.Album = api.ALBUMS[index]
album.count = len(songs) tracks = instances.tracks_instance.find_tracks_by_hash(albumhash)
album.duration = albumslib.get_album_duration(songs) tracks = [models.Track(t) for t in tracks]
album = instances.album_instance.find_album_by_hash(albumhash)
album = models.Album(album)
album.count = len(tracks)
album.duration = albumslib.get_album_duration(tracks)
if ( if (
album.count == 1 album.count == 1
and songs[0].title == album.title and tracks[0].title == album.title
and songs[0].tracknumber == 1 and tracks[0].tracknumber == 1
and songs[0].disknumber == 1 and tracks[0].disknumber == 1
): ):
album.is_single = True album.is_single = True
return {"songs": songs, "info": album} return {"tracks": tracks, "info": album}
@album_bp.route("/album/bio", methods=["POST"]) @album_bp.route("/album/bio", methods=["POST"])
@@ -80,14 +81,11 @@ def get_albumartists():
"""Returns a list of artists featured in a given album.""" """Returns a list of artists featured in a given album."""
data = request.get_json() data = request.get_json()
album = data["album"] album, artist = data["album"], data["artist"]
artist = data["artist"] albumhash = helpers.create_album_hash(album, artist)
tracks: List[models.Track] = [] tracks = instances.tracks_instance.find_tracks_by_hash(albumhash)
tracks = [models.Track(t) for t in tracks]
for track in api.TRACKS:
if track.album == album and track.albumartist == artist:
tracks.append(track)
artists = [] artists = []
+21 -22
View File
@@ -22,8 +22,12 @@ TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist
@playlist_bp.route("/playlists", methods=["GET"]) @playlist_bp.route("/playlists", methods=["GET"])
def get_all_playlists(): def get_all_playlists():
"""Returns all the playlists."""
dbplaylists = instances.playlist_instance.get_all_playlists()
dbplaylists = [models.Playlist(p) for p in dbplaylists]
playlists = [ playlists = [
serializer.Playlist(p, construct_last_updated=False) for p in api.PLAYLISTS serializer.Playlist(p, construct_last_updated=False) for p in dbplaylists
] ]
playlists.sort( playlists.sort(
key=lambda p: datetime.strptime(p.lastUpdated, "%Y-%m-%d %H:%M:%S"), key=lambda p: datetime.strptime(p.lastUpdated, "%Y-%m-%d %H:%M:%S"),
@@ -36,7 +40,7 @@ def get_all_playlists():
def create_playlist(): def create_playlist():
data = request.get_json() data = request.get_json()
playlist = { data = {
"name": data["name"], "name": data["name"],
"description": "", "description": "",
"pre_tracks": [], "pre_tracks": [],
@@ -45,21 +49,16 @@ def create_playlist():
"thumb": "", "thumb": "",
} }
try: dbp = instances.playlist_instance.get_playlist_by_name(data["name"])
for pl in api.PLAYLISTS:
if pl.name == playlist["name"]:
raise PlaylistExists("Playlist already exists.")
except PlaylistExists as e: if dbp is not None:
return {"error": str(e)}, 409 return {"message": "Playlist already exists."}, 409
upsert_id = instances.playlist_instance.insert_playlist(playlist) upsert_id = instances.playlist_instance.insert_playlist(data)
p = instances.playlist_instance.get_playlist_by_id(upsert_id) p = instances.playlist_instance.get_playlist_by_id(upsert_id)
pp = models.Playlist(p) playlist = models.Playlist(p)
api.PLAYLISTS.append(pp) return {"playlist": playlist}, 201
return {"playlist": pp}, 201
@playlist_bp.route("/playlist/<playlist_id>/add", methods=["POST"]) @playlist_bp.route("/playlist/<playlist_id>/add", methods=["POST"])
@@ -70,23 +69,23 @@ def add_track_to_playlist(playlist_id: str):
try: try:
playlistlib.add_track(playlist_id, trackid) playlistlib.add_track(playlist_id, trackid)
except TrackExistsInPlaylist as e: except TrackExistsInPlaylist:
return {"error": "Track already exists in playlist"}, 409 return {"error": "Track already exists in playlist"}, 409
return {"msg": "I think It's done"}, 200 return {"msg": "I think It's done"}, 200
@playlist_bp.route("/playlist/<playlistid>") @playlist_bp.route("/playlist/<playlistid>")
def get_single_p_info(playlistid: str): def get_playlist(playlistid: str):
p = UseBisection(api.PLAYLISTS, "playlistid", [playlistid])() p = instances.playlist_instance.get_playlist_by_id(playlistid)
playlist: models.Playlist = p[0] if p is None:
if playlist is not None:
tracks = playlist.get_tracks()
return {"info": serializer.Playlist(playlist), "tracks": tracks}
return {"info": {}, "tracks": []} return {"info": {}, "tracks": []}
playlist = models.Playlist(p)
tracks = playlistlib.create_playlist_tracks(playlist.pretracks)
return {"info": serializer.Playlist(playlist), "tracks": tracks}
@playlist_bp.route("/playlist/<playlistid>/update", methods=["PUT"]) @playlist_bp.route("/playlist/<playlistid>/update", methods=["PUT"])
def update_playlist(playlistid: str): def update_playlist(playlistid: str):
+6 -15
View File
@@ -95,18 +95,9 @@ def search():
return { return {
"data": [ "data": [
{ {"tracks": tracks[:5], "more": len(tracks) > 5},
"tracks": tracks[:5], {"albums": albums[:6], "more": len(albums) > 6},
"more": len(tracks) > 5 {"artists": artists_dicts[:6], "more": len(artists_dicts) > 6},
},
{
"albums": albums[:6],
"more": len(albums) > 6
},
{
"artists": artists_dicts[:6],
"more": len(artists_dicts) > 6
},
] ]
} }
@@ -121,18 +112,18 @@ def search_load_more():
if type == "tracks": if type == "tracks":
return { return {
"tracks": SEARCH_RESULTS["tracks"][index:index + 5], "tracks": SEARCH_RESULTS["tracks"][index : index + 5],
"more": len(SEARCH_RESULTS["tracks"]) > index + 5, "more": len(SEARCH_RESULTS["tracks"]) > index + 5,
} }
elif type == "albums": elif type == "albums":
return { return {
"albums": SEARCH_RESULTS["albums"][index:index + 6], "albums": SEARCH_RESULTS["albums"][index : index + 6],
"more": len(SEARCH_RESULTS["albums"]) > index + 6, "more": len(SEARCH_RESULTS["albums"]) > index + 6,
} }
elif type == "artists": elif type == "artists":
return { return {
"artists": SEARCH_RESULTS["artists"][index:index + 6], "artists": SEARCH_RESULTS["artists"][index : index + 6],
"more": len(SEARCH_RESULTS["artists"]) > index + 6, "more": len(SEARCH_RESULTS["artists"]) > index + 6,
} }
+8 -12
View File
@@ -6,6 +6,8 @@ from app import instances
from flask import Blueprint from flask import Blueprint
from flask import send_file from flask import send_file
from app import models
track_bp = Blueprint("track", __name__, url_prefix="/") track_bp = Blueprint("track", __name__, url_prefix="/")
@@ -14,21 +16,15 @@ def send_track_file(trackid):
""" """
Returns an audio file that matches the passed id to the client. Returns an audio file that matches the passed id to the client.
""" """
try: track = instances.tracks_instance.get_track_by_id(trackid)
files = []
for f in api.DB_TRACKS:
try:
if f["_id"]["$oid"] == trackid:
files.append(f["filepath"])
except KeyError:
# 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.
pass
filepath = files[0] if track is None:
except IndexError:
return "File not found", 404 return "File not found", 404
return send_file(filepath, mimetype="audio/mp3") track = models.Track(track)
type = track.filepath.split(".")[-1]
return send_file(track.filepath, mimetype=f"audio/{type}")
@track_bp.route("/sample") @track_bp.route("/sample")
+12
View File
@@ -200,3 +200,15 @@ class TrackMethods:
Removes a track from the database. Returns a boolean indicating success or failure of the operation. Removes a track from the database. Returns a boolean indicating success or failure of the operation.
""" """
pass pass
def find_tracks_by_hash():
"""
Returns all the tracks matching the passed hash.
"""
pass
def find_tracks_inside_path_regex():
"""
Returns a list of all the tracks matching the path in the query params.
"""
pass
+11 -10
View File
@@ -21,16 +21,17 @@ class Albums(MongoAlbums):
""" """
album = album.__dict__ album = album.__dict__
return self.collection.update_one( return self.collection.update_one(
{ {"album": album["title"], "artist": album["artist"]},
"album": album["title"], {"$set": album},
"artist": album["artist"]
},
{
"$set": album
},
upsert=True, upsert=True,
).upserted_id ).upserted_id
def insert_many(self, albums: list):
"""
Inserts multiple albums into the database.
"""
return self.collection.insert_many(albums)
def get_all_albums(self) -> list: def get_all_albums(self) -> list:
""" """
Returns all the albums in the database. Returns all the albums in the database.
@@ -52,9 +53,9 @@ class Albums(MongoAlbums):
album = self.collection.find_one({"album": name, "artist": artist}) album = self.collection.find_one({"album": name, "artist": artist})
return convert_one(album) return convert_one(album)
def get_album_by_artist(self, name: str) -> dict: def find_album_by_hash(self, hash: str) -> dict:
""" """
Returns a single album matching the artist in the query params. Returns a single album matching the hash in the query params.
""" """
album = self.collection.find_one({"albumartist": name}) album = self.collection.find_one({"hash": hash})
return convert_one(album) return convert_one(album)
+15 -16
View File
@@ -18,12 +18,8 @@ class Playlists(MongoPlaylists):
Inserts a new playlist object into the database. Inserts a new playlist object into the database.
""" """
return self.collection.update_one( return self.collection.update_one(
{ {"name": playlist["name"]},
"name": playlist["name"] {"$set": playlist},
},
{
"$set": playlist
},
upsert=True, upsert=True,
).upserted_id ).upserted_id
@@ -41,25 +37,28 @@ class Playlists(MongoPlaylists):
playlist = self.collection.find_one({"_id": ObjectId(id)}) playlist = self.collection.find_one({"_id": ObjectId(id)})
return convert_one(playlist) return convert_one(playlist)
def add_track_to_playlist(self, playlistid: str, track: dict) -> None: def set_last_updated(self, playlistid: str) -> None:
""" """
Adds a track to a playlist. Sets the lastUpdated field to the current date.
""" """
date = create_new_date() date = create_new_date()
return self.collection.update_one( return self.collection.update_one(
{"_id": ObjectId(playlistid)},
{"$set": {"lastUpdated": date}},
)
def add_track_to_playlist(self, playlistid: str, track: dict) -> None:
"""
Adds a track to a playlist.
"""
self.collection.update_one(
{ {
"_id": ObjectId(playlistid), "_id": ObjectId(playlistid),
}, },
{ {"$push": {"pre_tracks": track}},
"$push": {
"pre_tracks": track
},
"$set": {
"lastUpdated": date
}
},
) )
self.set_last_updated(playlistid)
def get_playlist_by_name(self, name: str) -> dict: def get_playlist_by_name(self, name: str) -> dict:
""" """
+37 -6
View File
@@ -2,9 +2,7 @@
This file contains the AllSongs class for interacting with track documents in MongoDB. This file contains the AllSongs class for interacting with track documents in MongoDB.
""" """
import pymongo import pymongo
from app.db.mongodb import convert_many from app.db.mongodb import MongoTracks, convert_many, convert_one
from app.db.mongodb import convert_one
from app.db.mongodb import MongoTracks
from bson import ObjectId from bson import ObjectId
@@ -24,17 +22,23 @@ class Tracks(MongoTracks):
{"filepath": song_obj["filepath"]}, {"$set": song_obj}, upsert=True {"filepath": song_obj["filepath"]}, {"$set": song_obj}, upsert=True
).upserted_id ).upserted_id
def insert_many(self, songs: list):
"""
Inserts multiple songs into the database.
"""
return self.collection.insert_many(songs)
def get_all_tracks(self) -> list: def get_all_tracks(self) -> list:
""" """
Returns all tracks in the database. Returns all tracks in the database.
""" """
return convert_many(self.collection.find()) return convert_many(self.collection.find())
def get_song_by_id(self, file_id: str) -> dict: def get_track_by_id(self, id: str) -> dict:
""" """
Returns a track object by its mongodb id. Returns a track object by its mongodb id.
""" """
song = self.collection.find_one({"_id": ObjectId(file_id)}) song = self.collection.find_one({"_id": ObjectId(id)})
return convert_one(song) return convert_one(song)
def get_song_by_album(self, name: str, artist: str) -> dict: def get_song_by_album(self, name: str, artist: str) -> dict:
@@ -81,11 +85,20 @@ class Tracks(MongoTracks):
def find_songs_by_folder_og(self, query: str) -> list: def find_songs_by_folder_og(self, query: str) -> list:
""" """
Returns an unsorted list of all the tracks exactly matching the folder in the query params Returns an unsorted list of all the track matching the folder in the query params
""" """
songs = self.collection.find({"folder": query}) songs = self.collection.find({"folder": query})
return convert_many(songs) return convert_many(songs)
def find_tracks_inside_path_regex(self, path: str) -> list:
"""
Returns a list of all the tracks matching the path in the query params.
"""
songs = self.collection.find(
{"filepath": {"$regex": f"^{path}", "$options": "i"}}
)
return convert_many(songs)
def find_songs_by_artist(self, query: str) -> list: def find_songs_by_artist(self, query: str) -> list:
""" """
Returns a list of all the tracks exactly matching the artists in the query params. Returns a list of all the tracks exactly matching the artists in the query params.
@@ -128,3 +141,21 @@ class Tracks(MongoTracks):
return True return True
except: except:
return False return False
def find_tracks_by_hash(self, hash: str) -> list:
"""
Returns a list of all the tracks matching the hash in the query params.
"""
songs = self.collection.find({"albumhash": hash})
return convert_many(songs)
def find_track_by_title_artists_album(
self, title: str, artist: str, album: str
) -> dict:
"""
Returns a single track matching the title, artist, and album in the query params.
"""
song = self.collection.find_one(
{"title": title, "artists": artist, "album": album}
)
return convert_one(song)
+9 -9
View File
@@ -14,7 +14,9 @@ from app.lib.populate import Populate
from PIL import Image from PIL import Image
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from app.lib.trackslib import create_all_tracks from app.lib.trackslib import validate_tracks
from app.lib import trackslib
from server.app import instances, models
@helpers.background @helpers.background
@@ -24,6 +26,8 @@ def reindex_tracks():
""" """
while True: while True:
trackslib.validate_tracks()
populate() populate()
CheckArtistImages()() CheckArtistImages()()
@@ -42,10 +46,6 @@ def populate():
pop = Populate() pop = Populate()
pop.run() pop.run()
tracks = create_all_tracks()
api.TRACKS.clear()
api.TRACKS.extend(tracks)
class getArtistImage: class getArtistImage:
""" """
@@ -104,11 +104,11 @@ class CheckArtistImages:
""" """
Loops through all the tracks and gathers all the artists. Loops through all the tracks and gathers all the artists.
""" """
ar = instances.tracks_instance.get_all_tracks()
tracks = [models.Track(t) for t in ar]
for song in api.DB_TRACKS: for t in tracks:
this_artists: list = song["artists"].split(", ") for artist in t.artists:
for artist in this_artists:
if artist not in self.artists: if artist not in self.artists:
self.artists.append(artist) self.artists.append(artist)
+3 -18
View File
@@ -4,12 +4,8 @@ This library contains all the functions related to albums.
import random import random
from typing import List from typing import List
from app import api from app import api, helpers, instances, models
from app import helpers from app.lib import taglib, trackslib
from app import instances
from app import models
from app.lib import taglib
from app.lib import trackslib
from tqdm import tqdm from tqdm import tqdm
@@ -30,21 +26,12 @@ def get_all_albums() -> List[models.Album]:
return albums return albums
def create_everything() -> List[models.Track]: def validate() -> None:
""" """
Creates album objects for all albums and returns Creates album objects for all albums and returns
a list of track objects a list of track objects
""" """
albums: list[models.Album] = get_all_albums()
api.ALBUMS = albums
api.ALBUMS.sort(key=lambda x: x.hash)
tracks = trackslib.create_all_tracks()
api.TRACKS.clear()
api.TRACKS.extend(tracks)
api.TRACKS.sort(key=lambda x: x.title)
def find_album(albums: List[models.Album], hash: str) -> int | None: def find_album(albums: List[models.Album], hash: str) -> int | None:
@@ -142,8 +129,6 @@ class GetAlbumTracks:
self.tracks.remove(track) self.tracks.remove(track)
index = trackslib.find_track(self.tracks, self.hash) index = trackslib.find_track(self.tracks, self.hash)
# self.tracks.extend(tracks)
# self.tracks.sort(key=lambda x: x["albumhash"])
return tracks return tracks
+7 -71
View File
@@ -1,15 +1,11 @@
from dataclasses import dataclass from dataclasses import dataclass
from os import scandir from os import scandir
from time import time
from typing import List
from typing import Set
from typing import Tuple from typing import Tuple
from app import api
from app import helpers
from app.models import Folder from app.models import Folder
from app.models import Track from app.models import Track
from tqdm import tqdm
from app import instances
@dataclass @dataclass
@@ -18,20 +14,12 @@ class Dir:
is_sym: bool is_sym: bool
def get_valid_folders() -> None:
for track in api.TRACKS:
api.VALID_FOLDERS.add(track.folder)
def get_folder_track_count(foldername: str) -> int: def get_folder_track_count(foldername: str) -> int:
""" """
Returns the number of files associated with a folder. Returns the number of files associated with a folder.
""" """
count = 0 tracks = instances.tracks_instance.find_tracks_inside_path_regex(foldername)
for track in api.TRACKS: return len(tracks)
if foldername in track.folder:
count += 1
return count
def create_folder(dir: Dir) -> Folder: def create_folder(dir: Dir) -> Folder:
@@ -46,51 +34,6 @@ def create_folder(dir: Dir) -> Folder:
return Folder(folder) return Folder(folder)
def create_all_folders() -> Set[Folder]:
folders: List[Folder] = []
for foldername in tqdm(api.VALID_FOLDERS, desc="Creating folders"):
folder = create_folder(foldername)
folders.append(folder)
return folders
def get_subdirs(foldername: str) -> List[Folder]:
"""
Finds and Creates Folder objects for each sub-directory string in the foldername passed.
"""
subdirs = set()
for folder in api.VALID_FOLDERS:
if foldername in folder:
str0 = folder.replace(foldername, "")
try:
str1 = str0.split("/")[1]
except IndexError:
str1 = None
if str1 is not None:
subdirs.add(foldername + "/" + str1)
return [create_folder(dir) for dir in subdirs]
@helpers.background
def run_scandir():
"""
Initiates the creation of all folder objects for each folder with a track in it.
Runs in a background thread after every 5 minutes.
It calls the
"""
get_valid_folders()
# folders_ = create_all_folders()
"""Create all the folder objects before clearing api.FOLDERS"""
# api.FOLDERS = folders_
class getFnF: class getFnF:
""" """
Get files and folders from a directory. Get files and folders from a directory.
@@ -99,15 +42,6 @@ class getFnF:
def __init__(self, path: str) -> None: def __init__(self, path: str) -> None:
self.path = path self.path = path
@classmethod
def get_tracks(cls, files: List[str]) -> List[Track]:
"""
Returns a list of Track objects for each file in the given list.
"""
tracks = helpers.UseBisection(api.TRACKS, "filepath", files)()
tracks = filter(lambda t: t is not None, tracks)
return list(tracks)
def __call__(self) -> Tuple[Track, Folder]: def __call__(self) -> Tuple[Track, Folder]:
try: try:
all = scandir(self.path) all = scandir(self.path)
@@ -125,9 +59,11 @@ class getFnF:
dirs.append(Dir(**dir)) dirs.append(Dir(**dir))
elif entry.is_file() and entry.name.endswith((".mp3", ".flac")): elif entry.is_file() and entry.name.endswith((".mp3", ".flac")):
files.append(entry.path) files.append(entry.path)
tracks = self.get_tracks(files) tracks = instances.tracks_instance.find_songs_by_folder(self.path)
tracks = [Track(track) for track in tracks]
folders = [create_folder(dir) for dir in dirs] folders = [create_folder(dir) for dir in dirs]
folders = filter(lambda f: f.trackcount > 0, folders) folders = filter(lambda f: f.trackcount > 0, folders)
return tracks, folders return tracks, folders
+39 -42
View File
@@ -5,6 +5,7 @@ import os
import random import random
import string import string
from datetime import datetime from datetime import datetime
from typing import List
from tqdm import tqdm from tqdm import tqdm
@@ -13,55 +14,37 @@ from app import exceptions
from app import instances from app import instances
from app import models from app import models
from app import settings from app import settings
from app.lib import trackslib
from PIL import Image from PIL import Image
from PIL import ImageSequence from PIL import ImageSequence
from progress.bar import Bar
from werkzeug import datastructures from werkzeug import datastructures
from app.lib import trackslib
TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist TrackExistsInPlaylist = exceptions.TrackExistsInPlaylist
def add_track(playlistid: str, trackid: str): def add_track(playlistid: str, trackid: str):
""" """
Adds a track to a playlist in the api.PLAYLISTS dict and to the database. Adds a track to a playlist to the database.
""" """
for playlist in api.PLAYLISTS: tt = instances.tracks_instance.get_track_by_id(trackid)
if playlist.playlistid == playlistid:
tt = trackslib.get_track_by_id(trackid) if tt is None:
return
track = models.Track(tt)
playlist = instances.playlist_instance.get_playlist_by_id(playlistid)
track = { track = {
"title": tt.title, "title": track.title,
"artists": tt.artists, "artists": tt["artists"],
"album": tt.album, "album": track.album,
} }
if track in playlist["pre_tracks"]:
raise TrackExistsInPlaylist
try: instances.playlist_instance.add_track_to_playlist(playlistid, track)
playlist.add_track(track)
instances.playlist_instance.add_track_to_playlist(
playlistid, track)
return
except TrackExistsInPlaylist as error:
raise error
def get_playlist_tracks(pid: str):
for p in api.PLAYLISTS:
if p.playlistid == pid:
return p.tracks
def create_all_playlists():
"""
Gets all playlists from the database.
"""
playlists = instances.playlist_instance.get_all_playlists()
for playlist in tqdm(playlists, desc="Creating playlists"):
api.PLAYLISTS.append(models.Playlist(playlist))
validate_images()
def create_thumbnail(image: any, img_path: str) -> str: def create_thumbnail(image: any, img_path: str) -> str:
@@ -69,8 +52,7 @@ def create_thumbnail(image: any, img_path: str) -> str:
Creates a 250 x 250 thumbnail from a playlist image Creates a 250 x 250 thumbnail from a playlist image
""" """
thumb_path = "thumb_" + img_path thumb_path = "thumb_" + img_path
full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists", full_thumb_path = os.path.join(settings.APP_DIR, "images", "playlists", thumb_path)
thumb_path)
aspect_ratio = image.width / image.height aspect_ratio = image.width / image.height
@@ -88,13 +70,11 @@ def save_p_image(file: datastructures.FileStorage, pid: str):
""" """
img = Image.open(file) img = Image.open(file)
random_str = "".join( random_str = "".join(random.choices(string.ascii_letters + string.digits, k=5))
random.choices(string.ascii_letters + string.digits, k=5))
img_path = pid + str(random_str) + ".webp" img_path = pid + str(random_str) + ".webp"
full_img_path = os.path.join(settings.APP_DIR, "images", "playlists", full_img_path = os.path.join(settings.APP_DIR, "images", "playlists", img_path)
img_path)
if file.content_type == "image/gif": if file.content_type == "image/gif":
frames = [] frames = []
@@ -118,8 +98,10 @@ def validate_images():
Removes all unused images in the images/playlists folder. Removes all unused images in the images/playlists folder.
""" """
images = [] images = []
p = instances.playlist_instance.get_all_playlists()
playlists = [models.Playlist(p) for p in p]
for playlist in api.PLAYLISTS: for playlist in playlists:
if playlist.image: if playlist.image:
img_path = playlist.image.split("/")[-1] img_path = playlist.image.split("/")[-1]
thumb_path = playlist.thumb.split("/")[-1] thumb_path = playlist.thumb.split("/")[-1]
@@ -136,3 +118,18 @@ def validate_images():
def create_new_date(): def create_new_date():
return datetime.now() return datetime.now()
def create_playlist_tracks(playlist_tracks: List) -> List[models.Track]:
"""
Creates a list of model.Track objects from a list of playlist track dicts.
"""
tracks: List[models.Track] = []
for t in playlist_tracks:
track = trackslib.get_p_track(t)
if track is not None:
tracks.append(models.Track(track))
return tracks
+15 -42
View File
@@ -1,4 +1,5 @@
import os import os
from pprint import pprint
import time import time
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy from copy import deepcopy
@@ -12,7 +13,6 @@ from app.helpers import 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 album_instance
from app.instances import tracks_instance from app.instances import tracks_instance
from app.lib import folderslib
from app.lib.albumslib import create_album 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
@@ -44,6 +44,7 @@ class Populate:
self.db_tracks = tracks_instance.get_all_tracks() self.db_tracks = tracks_instance.get_all_tracks()
self.tag_count = 0 self.tag_count = 0
self.exist_count = 0 self.exist_count = 0
self.tracks = []
def run(self): def run(self):
self.check_untagged() self.check_untagged()
@@ -53,17 +54,14 @@ class Populate:
return return
self.tagged_tracks.sort(key=lambda x: x["albumhash"]) self.tagged_tracks.sort(key=lambda x: x["albumhash"])
self.tracks = deepcopy(self.tagged_tracks)
self.pre_albums = self.create_pre_albums(self.tagged_tracks) self.pre_albums = self.create_pre_albums(self.tagged_tracks)
self.create_albums(self.pre_albums) self.create_albums(self.pre_albums)
self.albums.sort(key=lambda x: x.hash) self.albums.sort(key=lambda x: x.hash)
api.ALBUMS.sort(key=lambda x: x.hash)
self.save_albums()
self.create_tracks() self.create_tracks()
# self.create_folders()
self.save_all()
def check_untagged(self): def check_untagged(self):
""" """
@@ -84,7 +82,6 @@ class Populate:
t["albumhash"] = create_album_hash(t["album"], t["albumartist"]) t["albumhash"] = create_album_hash(t["album"], t["albumartist"])
self.tagged_tracks.append(t) self.tagged_tracks.append(t)
api.DB_TRACKS.append(t)
self.folders.add(t["folder"]) self.folders.add(t["folder"])
@@ -95,8 +92,7 @@ class Populate:
folder = tags["folder"] folder = tags["folder"]
self.folders.add(folder) self.folders.add(folder)
tags["albumhash"] = create_album_hash(tags["album"], tags["albumhash"] = create_album_hash(tags["album"], tags["albumartist"])
tags["albumartist"])
self.tagged_tracks.append(tags) self.tagged_tracks.append(tags)
api.DB_TRACKS.append(tags) api.DB_TRACKS.append(tags)
@@ -110,13 +106,6 @@ class Populate:
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
executor.map(self.get_tags, self.files) executor.map(self.get_tags, self.files)
# with Pool(maxtasksperchild=10, processes=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 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")
@@ -147,7 +136,6 @@ class Populate:
self.exist_count += 1 self.exist_count += 1
return return
self.albums.sort(key=lambda x: x.hash)
index = find_track(self.tagged_tracks, albumhash) index = find_track(self.tagged_tracks, albumhash)
if index is None: if index is None:
@@ -163,7 +151,6 @@ class Populate:
album = Album(album) album = Album(album)
api.ALBUMS.append(album)
self.albums.append(album) self.albums.append(album)
def create_albums(self, albums: List[dict]): def create_albums(self, albums: List[dict]):
@@ -173,8 +160,7 @@ class Populate:
for album in tqdm(albums, desc="Building albums"): for album in tqdm(albums, desc="Building albums"):
self.create_album(album) self.create_album(album)
Log(f"{self.exist_count} of {len(albums)} albums were already in the database" Log(f"{self.exist_count} of {len(albums)} albums were already in the database")
)
def create_track(self, track: dict): def create_track(self, track: dict):
""" """
@@ -196,38 +182,25 @@ class Populate:
pass pass
track["image"] = album.image track["image"] = album.image
return track
upsert_id = tracks_instance.insert_song(track)
track["_id"] = {"$oid": str(upsert_id)}
api.TRACKS.append(Track(track))
def create_tracks(self): def create_tracks(self):
""" """
Loops through all the tagged tracks creating complete track objects using the `models.Track` model. Loops through all the tagged tracks creating complete track objects using the `models.Track` model.
""" """
with ThreadPoolExecutor() as executor: with ThreadPoolExecutor() as executor:
executor.map(self.create_track, self.tagged_tracks) iterable = executor.map(self.create_track, self.tagged_tracks)
Log(f"Added {len(self.tagged_tracks)} new tracks and {len(self.albums)} new albums" 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_albums(self): def save_all(self):
""" """
Saves the albums to the database. Saves the albums to the database.
""" """
with ThreadPoolExecutor() as executor: album_instance.insert_many([a.__dict__ for a in self.albums])
executor.map(album_instance.insert_album, self.albums) tracks_instance.insert_many(self.tracks)
# def create_folders(self):
# """
# Creates the folder objects for all the tracks.
# """
# for folder in tqdm(self.folders, desc="Creating folders"):
# api.VALID_FOLDERS.add(folder)
# fff = folderslib.create_folder(folder)
# api.FOLDERS.append(fff)
# Log(f"Created {len(self.folders)} new folders")
+11 -12
View File
@@ -2,31 +2,25 @@
This library contains all the functions related to tracks. This library contains all the functions related to tracks.
""" """
import os import os
from pprint import pprint
from typing import List from typing import List
from app import api from app import api, instances, models
from app import instances
from app import models
from app.helpers import remove_duplicates from app.helpers import remove_duplicates
from tqdm import tqdm from tqdm import tqdm
def create_all_tracks() -> List[models.Track]: def validate_tracks() -> None:
""" """
Gets all songs under the ~/ directory. Gets all songs under the ~/ directory.
""" """
tracks: list[models.Track] = [] entries = instances.tracks_instance.get_all_tracks()
for track in tqdm(api.DB_TRACKS, desc="Creating tracks"): for track in tqdm(entries, desc="Validating tracks"):
try: try:
os.chmod(track["filepath"], 0o755) os.chmod(track["filepath"], 0o755)
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)
tracks.append(models.Track(track))
return tracks
def get_album_tracks(albumname, artist): def get_album_tracks(albumname, artist):
@@ -48,7 +42,6 @@ def get_track_by_id(trackid: str) -> models.Track:
return track return track
except AttributeError: except AttributeError:
print("AttributeError") print("AttributeError")
print(track)
def find_track(tracks: list, hash: str) -> int or None: def find_track(tracks: list, hash: str) -> int or None:
@@ -73,3 +66,9 @@ def find_track(tracks: list, hash: str) -> int or None:
right = mid - 1 right = mid - 1
return None return None
def get_p_track(ptrack):
return instances.tracks_instance.find_track_by_title_artists_album(
ptrack["title"], ptrack["artists"], ptrack["album"]
)
+7 -36
View File
@@ -4,7 +4,7 @@ Contains all the models for objects generation and typing.
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import List from typing import List
from app import api, helpers from app import helpers
from app.exceptions import TrackExistsInPlaylist from app.exceptions import TrackExistsInPlaylist
@@ -117,30 +117,6 @@ class Album:
return self.artist.lower() == "various artists" return self.artist.lower() == "various artists"
def get_p_track(ptrack):
for track in api.TRACKS:
if (
track.title == ptrack["title"]
and track.artists == ptrack["artists"]
and ptrack["album"] == track.album
):
return track
def create_playlist_tracks(playlist_tracks: List) -> List[Track]:
"""
Creates a list of model.Track objects from a list of playlist track dicts.
"""
tracks: List[Track] = []
for t in playlist_tracks:
track = get_p_track(t)
if track is not None:
tracks.append(track)
return tracks
@dataclass @dataclass
class Playlist: class Playlist:
"""Creates playlist objects""" """Creates playlist objects"""
@@ -148,7 +124,7 @@ class Playlist:
playlistid: str playlistid: str
name: str name: str
tracks: List[Track] tracks: List[Track]
_pre_tracks: list = field(init=False, repr=False) pretracks: list = field(init=False, repr=False)
lastUpdated: int lastUpdated: int
image: str image: str
thumb: str thumb: str
@@ -162,16 +138,11 @@ class Playlist:
self.description = data["description"] self.description = data["description"]
self.image = self.create_img_link(data["image"]) self.image = self.create_img_link(data["image"])
self.thumb = self.create_img_link(data["thumb"]) self.thumb = self.create_img_link(data["thumb"])
self._pre_tracks = data["pre_tracks"] self.pretracks = data["pre_tracks"]
self.tracks = [] self.tracks = []
self.lastUpdated = data["lastUpdated"] self.lastUpdated = data["lastUpdated"]
self.count = len(self._pre_tracks) self.count = len(self.pretracks)
def get_tracks(self) -> List[Track]:
"""
Generates and returns Track objects from pre_tracks
"""
return create_playlist_tracks(self._pre_tracks)
def create_img_link(self, image: str): def create_img_link(self, image: str):
if image: if image:
@@ -180,11 +151,11 @@ class Playlist:
return "default.webp" return "default.webp"
def update_count(self): def update_count(self):
self.count = len(self._pre_tracks) self.count = len(self.pretracks)
def add_track(self, track): def add_track(self, track):
if track not in self._pre_tracks: if track not in self.pretracks:
self._pre_tracks.append(track) self.pretracks.append(track)
self.update_count() self.update_count()
self.lastUpdated = helpers.create_new_date() self.lastUpdated = helpers.create_new_date()
else: else:
+3 -1
View File
@@ -11,7 +11,8 @@ CONFIG_FOLDER = ".alice"
HOME_DIR = os.path.expanduser("~") 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" TEST_DIR = "/home/cwilvx/Music/Link to Music/Chill/Wolftyla Radio"
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/"
@@ -38,6 +39,7 @@ P_COLORS = [
CPU_COUNT = multiprocessing.cpu_count() CPU_COUNT = multiprocessing.cpu_count()
class logger: class logger:
enable = True enable = True
+2 -2
View File
@@ -9,13 +9,13 @@ const getAlbumTracks = async (album: string, artist: string) => {
}; };
await axios await axios
.post(state.settings.uri + "/album/tracks", { .post(state.settings.uri + "/album", {
album: album, album: album,
artist: artist, artist: artist,
}) })
.then((res) => { .then((res) => {
data.info = res.data.info; data.info = res.data.info;
data.tracks = res.data.songs; data.tracks = res.data.tracks;
}) })
.catch((err: AxiosError) => { .catch((err: AxiosError) => {
console.error(err); console.error(err);