From 655fd8bc22c68479507f67cbe12d2d896b67325e Mon Sep 17 00:00:00 2001 From: mungai-njoroge Date: Fri, 4 Aug 2023 13:40:48 +0300 Subject: [PATCH] add route to get all tracks in path + add routes to save album and artist as a playlist --- app/api/album.py | 4 +- app/api/artist.py | 17 +++-- app/api/folder.py | 26 ++++++-- app/api/playlist.py | 127 +++++++++++++++++++++++++++++++++---- app/db/sqlite/playlists.py | 6 +- app/serializers/track.py | 25 +++++--- app/store/tracks.py | 2 +- 7 files changed, 169 insertions(+), 38 deletions(-) diff --git a/app/api/album.py b/app/api/album.py index cd752c0..1387139 100644 --- a/app/api/album.py +++ b/app/api/album.py @@ -83,7 +83,7 @@ def get_album_tracks_and_info(): album.is_favorite = check_is_fav(albumhash, FavType.album) return { - "tracks": [track_serializer(t, retain_disc=True) for t in tracks], + "tracks": [track_serializer(t, remove_disc=False) for t in tracks], "info": album, } @@ -210,3 +210,5 @@ def get_similar_albums(): pass return {"albums": [serialize_for_card(a) for a in albums[:limit]]} + + diff --git a/app/api/artist.py b/app/api/artist.py index 3cd9af5..f8bd2f6 100644 --- a/app/api/artist.py +++ b/app/api/artist.py @@ -1,22 +1,21 @@ """ Contains all the artist(s) routes. """ -from collections import deque import random +from collections import deque from flask import Blueprint, request from app.db.sqlite.favorite import SQLiteFavoriteMethods as favdb -from app.db.sqlite.lastfm.similar_artists import SQLiteLastFMSimilarArtists as fmdb - +from app.db.sqlite.lastfm.similar_artists import \ + SQLiteLastFMSimilarArtists as fmdb from app.models import Album, FavType, Track from app.serializers.album import serialize_for_card_many from app.serializers.track import serialize_tracks -from app.utils.remove_duplicates import remove_duplicates - from app.store.albums import AlbumStore -from app.store.tracks import TrackStore from app.store.artists import ArtistStore +from app.store.tracks import TrackStore +from app.utils.remove_duplicates import remove_duplicates api = Blueprint("artist", __name__, url_prefix="/") @@ -159,7 +158,7 @@ def add_albums_to_cache(artisthash: str): """ Fetches albums and adds them to the cache. """ - tracks = TrackStore.get_tracks_by_artist(artisthash) + tracks = TrackStore.get_tracks_by_artisthash(artisthash) if len(tracks) == 0: return False @@ -197,7 +196,7 @@ def get_artist(artisthash: str): if tracks_cached: tracks = ArtistsCache.get_tracks(artisthash) else: - tracks = TrackStore.get_tracks_by_artist(artisthash) + tracks = TrackStore.get_tracks_by_artisthash(artisthash) albumhashes = set(t.albumhash for t in tracks) hashes_from_albums = set( a.albumhash for a in AlbumStore.get_albums_by_artisthash(artisthash) @@ -291,7 +290,7 @@ def get_all_artist_tracks(artisthash: str): """ Returns all artists by a given artist. """ - tracks = TrackStore.get_tracks_by_artist(artisthash) + tracks = TrackStore.get_tracks_by_artisthash(artisthash) return {"tracks": serialize_tracks(tracks)} diff --git a/app/api/folder.py b/app/api/folder.py index 3a4fba0..5626e81 100644 --- a/app/api/folder.py +++ b/app/api/folder.py @@ -2,16 +2,18 @@ Contains all the folder routes. """ import os -import psutil - from pathlib import Path + +import psutil from flask import Blueprint, request from showinfm import show_in_file_manager from app import settings -from app.lib.folderslib import GetFilesAndDirs, get_folders from app.db.sqlite.settings import SettingsSQLMethods as db -from app.utils.wintools import win_replace_slash, is_windows +from app.lib.folderslib import GetFilesAndDirs, get_folders +from app.serializers.track import track_serializer +from app.store.tracks import TrackStore as store +from app.utils.wintools import is_windows, win_replace_slash api = Blueprint("folder", __name__, url_prefix="") @@ -129,3 +131,19 @@ def open_in_file_manager(): show_in_file_manager(path) return {"success": True} + + +@api.route("/folder/tracks/all") +def get_tracks_in_path(): + path = request.args.get("path") + + if path is None: + return {"error": "No path provided."}, 400 + + tracks = store.get_tracks_in_path(path) + tracks = sorted(tracks, key=lambda i: i.last_mod) + tracks = (track_serializer(t) for t in tracks if Path(t.filepath).exists()) + + return { + "tracks": list(tracks)[:300], + } diff --git a/app/api/playlist.py b/app/api/playlist.py index a604250..bdb255b 100644 --- a/app/api/playlist.py +++ b/app/api/playlist.py @@ -135,6 +135,31 @@ def create_playlist(): return {"playlist": playlist}, 201 +def get_path_trackhashes(path: str): + """ + Returns a list of trackhashes in a folder. + """ + tracks = TrackStore.get_tracks_in_path(path) + tracks = sorted(tracks, key=lambda t: t.last_mod) + return [t.trackhash for t in tracks] + + +def get_album_trackhashes(albumhash: str): + """ + Returns a list of trackhashes in an album. + """ + tracks = TrackStore.get_tracks_by_albumhash(albumhash) + return [t.trackhash for t in tracks] + + +def get_artist_trackhashes(artisthash: str): + """ + Returns a list of trackhashes for an artist. + """ + tracks = TrackStore.get_tracks_by_artisthash(artisthash) + return [t.trackhash for t in tracks] + + @api.route("/playlist//add", methods=["POST"]) def add_track_to_playlist(playlist_id: str): """ @@ -145,9 +170,28 @@ def add_track_to_playlist(playlist_id: str): if data is None: return {"error": "Track hash not provided"}, 400 - trackhash = data["track"] + try: + itemtype = data["itemtype"] + except KeyError: + itemtype = None - insert_count = tracks_to_playlist(int(playlist_id), [trackhash]) + try: + itemhash = data["itemhash"] + except KeyError: + itemhash = None + + if itemtype == "track": + trackhashes = [itemhash] + elif itemtype == "folder": + trackhashes = get_path_trackhashes(itemhash) + elif itemtype == "album": + trackhashes = get_album_trackhashes(itemhash) + elif itemtype == "artist": + trackhashes = get_artist_trackhashes(itemhash) + else: + trackhashes = [] + + insert_count = tracks_to_playlist(int(playlist_id), trackhashes) if insert_count == 0: return {"error": "Track already exists in playlist"}, 409 @@ -328,6 +372,10 @@ def remove_tracks_from_playlist(pid: int): return {"msg": "Done"}, 200 +def playlist_exists(name: str) -> bool: + return count_playlist_by_name(name) > 0 + + @api.route("/playlist/save-folder", methods=["POST"]) def save_folder_as_folder(): data = request.get_json() @@ -342,17 +390,10 @@ def save_folder_as_folder(): if path is None or name is None: return msg - p_count = count_playlist_by_name(name) - - if p_count > 0: + if playlist_exists(name): return {"error": "Playlist already exists"}, 409 - tracks = TrackStore.get_tracks_in_path(path) - - # sort tracks by last_mod - tracks = sorted(tracks, key=lambda t: t.last_mod) - trackhashes = [t.trackhash for t in tracks] - + trackhashes = get_path_trackhashes(path) if len(trackhashes) == 0: return {"error": "No tracks found in folder"}, 404 @@ -365,3 +406,67 @@ def save_folder_as_folder(): PL.update_last_updated(playlist.id) return {"playlist_id": playlist.id}, 201 + + +@api.route("/playlist/save-album", methods=["POST"]) +def save_album_as_playlist(): + data = request.get_json() + msg = {"error": "'albumhash' and 'playlist_name' not provided"}, 400 + + if data is None: + return msg + + albumhash = data.get("albumhash") + name = data.get("playlist_name") + + if albumhash is None or name is None: + return msg + + if playlist_exists(name): + return {"error": "Playlist already exists"}, 409 + + trackhashes = get_album_trackhashes(albumhash) + if len(trackhashes) == 0: + return {"error": "No tracks found in album"}, 404 + + playlist = insert_playlist(name) + + if playlist is None: + return {"error": "Playlist could not be created"}, 500 + + tracks_to_playlist(playlist.id, trackhashes) + PL.update_last_updated(playlist.id) + + return {"playlist_id": playlist.id}, 201 + + +@api.route("/playlist/save-artist", methods=["POST"]) +def save_artist_as_playlist(): + data = request.get_json() + msg = {"error": "'artisthash' and 'playlist_name' not provided"}, 400 + + if data is None: + return msg + + artisthash = data.get("artisthash") + name = data.get("playlist_name") + + if artisthash is None or name is None: + return msg + + if playlist_exists(name): + return {"error": "Playlist already exists"}, 409 + + trackhashes = get_artist_trackhashes(artisthash) + if len(trackhashes) == 0: + return {"error": "No tracks found in artist"}, 404 + + playlist = insert_playlist(name) + + if playlist is None: + return {"error": "Playlist could not be created"}, 500 + + tracks_to_playlist(playlist.id, trackhashes) + PL.update_last_updated(playlist.id) + + return {"playlist_id": playlist.id}, 201 diff --git a/app/db/sqlite/playlists.py b/app/db/sqlite/playlists.py index 0838727..0777ad3 100644 --- a/app/db/sqlite/playlists.py +++ b/app/db/sqlite/playlists.py @@ -96,8 +96,7 @@ class SQLitePlaylistMethods: def add_item_to_json_list(playlist_id: int, field: str, items: set[str]): """ Adds a string item to a json dumped list using a playlist id and field name. - Takes the playlist ID, a field name, - an item to add to the field, and an error to raise if the item is already in the field. + Takes the playlist ID, a field name, an item to add to the field. """ sql = f"SELECT {field} FROM playlists WHERE id = ?" @@ -121,6 +120,9 @@ class SQLitePlaylistMethods: @classmethod def add_tracks_to_playlist(cls, playlist_id: int, trackhashes: list[str]): + """ + Adds trackhashes to a playlist + """ return cls.add_item_to_json_list(playlist_id, "trackhashes", trackhashes) @staticmethod diff --git a/app/serializers/track.py b/app/serializers/track.py index 40a7b84..8610105 100644 --- a/app/serializers/track.py +++ b/app/serializers/track.py @@ -1,30 +1,35 @@ from dataclasses import asdict + from app.models.track import Track -def track_serializer(track: Track, _remove: set = {}, retain_disc=False) -> dict: +def track_serializer(track: Track, to_remove: set = {}, remove_disc=True) -> dict: album_dict = asdict(track) - to_remove = { + props = { "date", "genre", "last_mod", "og_title", "og_album", - }.union(_remove) + "copyright", + "disc", + "track", + }.union(to_remove) - if not retain_disc: - to_remove.union("disc", "track") + if not remove_disc: + props.remove("disc") + props.remove("track") - to_remove.update(key for key in album_dict.keys() if key.startswith("is_")) - to_remove.remove('is_favorite') + props.update(key for key in album_dict.keys() if key.startswith("is_")) + props.remove("is_favorite") - for key in to_remove: + for key in props: album_dict.pop(key, None) return album_dict def serialize_tracks( - tracks: list[Track], _remove: set = {}, retain_disc=False + tracks: list[Track], _remove: set = {}, remove_disc=True ) -> list[dict]: - return [track_serializer(t, _remove, retain_disc) for t in tracks] + return [track_serializer(t, _remove, remove_disc) for t in tracks] diff --git a/app/store/tracks.py b/app/store/tracks.py index 9b1918a..1b303a3 100644 --- a/app/store/tracks.py +++ b/app/store/tracks.py @@ -160,7 +160,7 @@ class TrackStore: return remove_duplicates(tracks) @classmethod - def get_tracks_by_artist(cls, artisthash: str) -> list[Track]: + def get_tracks_by_artisthash(cls, artisthash: str) -> list[Track]: """ Returns all tracks matching the given artist. Duplicate tracks are removed. """