mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
implement playlist store
This commit is contained in:
@@ -61,3 +61,6 @@
|
|||||||
- Paginate the following endpoints:
|
- Paginate the following endpoints:
|
||||||
1. Folder tracks
|
1. Folder tracks
|
||||||
2. Playlist tracks
|
2. Playlist tracks
|
||||||
|
|
||||||
|
|
||||||
|
- When you update a playlist, update the store as well!
|
||||||
+71
-80
@@ -12,6 +12,7 @@ from flask_openapi3 import Tag
|
|||||||
from flask_openapi3 import APIBlueprint, FileStorage
|
from flask_openapi3 import APIBlueprint, FileStorage
|
||||||
|
|
||||||
from app import models
|
from app import models
|
||||||
|
from app.api.apischemas import GenericLimitSchema
|
||||||
from app.db.libdata import TrackTable
|
from app.db.libdata import TrackTable
|
||||||
from app.db.userdata import PlaylistTable
|
from app.db.userdata import PlaylistTable
|
||||||
from app.lib import playlistlib
|
from app.lib import playlistlib
|
||||||
@@ -20,6 +21,9 @@ from app.lib.home.recentlyadded import get_recently_added_playlist
|
|||||||
from app.lib.home.recentlyplayed import get_recently_played_playlist
|
from app.lib.home.recentlyplayed import get_recently_played_playlist
|
||||||
from app.models.playlist import Playlist
|
from app.models.playlist import Playlist
|
||||||
from app.serializers.playlist import serialize_for_card
|
from app.serializers.playlist import serialize_for_card
|
||||||
|
from app.serializers.track import serialize_tracks
|
||||||
|
from app.store.playlists import PlaylistStore
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
from app.utils.dates import create_new_date, date_string_to_time_passed
|
from app.utils.dates import create_new_date, date_string_to_time_passed
|
||||||
from app.utils.remove_duplicates import remove_duplicates
|
from app.utils.remove_duplicates import remove_duplicates
|
||||||
from app.settings import Paths
|
from app.settings import Paths
|
||||||
@@ -28,35 +32,6 @@ tag = Tag(name="Playlists", description="Get and manage playlists")
|
|||||||
api = APIBlueprint("playlists", __name__, url_prefix="/playlists", abp_tags=[tag])
|
api = APIBlueprint("playlists", __name__, url_prefix="/playlists", abp_tags=[tag])
|
||||||
|
|
||||||
|
|
||||||
class SendAllPlaylistsQuery(BaseModel):
|
|
||||||
no_images: bool = Field(False, description="Whether to include images")
|
|
||||||
|
|
||||||
|
|
||||||
@api.get("")
|
|
||||||
def send_all_playlists(query: SendAllPlaylistsQuery):
|
|
||||||
"""
|
|
||||||
Gets all the playlists.
|
|
||||||
"""
|
|
||||||
playlists = PlaylistTable.get_all()
|
|
||||||
playlists = list(playlists)
|
|
||||||
|
|
||||||
for playlist in playlists:
|
|
||||||
if not query.no_images:
|
|
||||||
playlist.images = playlistlib.get_first_4_images(
|
|
||||||
trackhashes=playlist.trackhashes
|
|
||||||
)
|
|
||||||
playlist.images = [img["image"] for img in playlist.images]
|
|
||||||
|
|
||||||
playlist.clear_lists()
|
|
||||||
|
|
||||||
playlists.sort(
|
|
||||||
key=lambda p: datetime.strptime(p.last_updated, "%Y-%m-%d %H:%M:%S"),
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {"data": playlists}
|
|
||||||
|
|
||||||
|
|
||||||
def insert_playlist(name: str, image: str = None):
|
def insert_playlist(name: str, image: str = None):
|
||||||
playlist = {
|
playlist = {
|
||||||
"image": image,
|
"image": image,
|
||||||
@@ -79,31 +54,6 @@ def insert_playlist(name: str, image: str = None):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
class CreatePlaylistBody(BaseModel):
|
|
||||||
name: str = Field(..., description="The name of the playlist")
|
|
||||||
|
|
||||||
|
|
||||||
@api.post("/new")
|
|
||||||
def create_playlist(body: CreatePlaylistBody):
|
|
||||||
"""
|
|
||||||
New playlist
|
|
||||||
|
|
||||||
Creates a new playlist. Accepts POST method with a JSON body.
|
|
||||||
"""
|
|
||||||
# existing_playlist_count = PL.count_playlist_by_name(body.name)
|
|
||||||
exists = PlaylistTable.check_exists_by_name(body.name)
|
|
||||||
|
|
||||||
if exists:
|
|
||||||
return {"error": "Playlist already exists"}, 409
|
|
||||||
|
|
||||||
playlist = insert_playlist(body.name)
|
|
||||||
|
|
||||||
if playlist is None:
|
|
||||||
return {"error": "Playlist could not be created"}, 500
|
|
||||||
|
|
||||||
return {"playlist": playlist}, 201
|
|
||||||
|
|
||||||
|
|
||||||
def get_path_trackhashes(path: str):
|
def get_path_trackhashes(path: str):
|
||||||
"""
|
"""
|
||||||
Returns a list of trackhashes in a folder.
|
Returns a list of trackhashes in a folder.
|
||||||
@@ -130,9 +80,63 @@ def get_artist_trackhashes(artisthash: str):
|
|||||||
return [t.trackhash for t in tracks]
|
return [t.trackhash for t in tracks]
|
||||||
|
|
||||||
|
|
||||||
|
def format_custom_playlist(playlist: models.Playlist, tracks: list[models.Track]):
|
||||||
|
duration = sum(t.duration for t in tracks)
|
||||||
|
|
||||||
|
playlist.duration = duration
|
||||||
|
|
||||||
|
return {
|
||||||
|
"info": serialize_for_card(playlist),
|
||||||
|
"tracks": tracks,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class SendAllPlaylistsQuery(BaseModel):
|
||||||
|
no_images: bool = Field(False, description="Whether to include images")
|
||||||
|
|
||||||
|
|
||||||
|
@api.get("")
|
||||||
|
def send_all_playlists(query: SendAllPlaylistsQuery):
|
||||||
|
"""
|
||||||
|
Gets all the playlists.
|
||||||
|
"""
|
||||||
|
playlists = PlaylistStore.get_flat_list()
|
||||||
|
playlists.sort(
|
||||||
|
key=lambda p: datetime.strptime(p.last_updated, "%Y-%m-%d %H:%M:%S"),
|
||||||
|
reverse=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return {"data": playlists}
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePlaylistBody(BaseModel):
|
||||||
|
name: str = Field(..., description="The name of the playlist")
|
||||||
|
|
||||||
|
|
||||||
|
@api.post("/new")
|
||||||
|
def create_playlist(body: CreatePlaylistBody):
|
||||||
|
"""
|
||||||
|
New playlist
|
||||||
|
|
||||||
|
Creates a new playlist. Accepts POST method with a JSON body.
|
||||||
|
"""
|
||||||
|
exists = PlaylistTable.check_exists_by_name(body.name)
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
return {"error": "Playlist already exists"}, 409
|
||||||
|
|
||||||
|
playlist = insert_playlist(body.name)
|
||||||
|
|
||||||
|
if playlist is None:
|
||||||
|
return {"error": "Playlist could not be created"}, 500
|
||||||
|
|
||||||
|
PlaylistStore.add_playlist(playlist)
|
||||||
|
return {"playlist": playlist}, 201
|
||||||
|
|
||||||
|
|
||||||
class PlaylistIDPath(BaseModel):
|
class PlaylistIDPath(BaseModel):
|
||||||
# INFO: playlistid string examples: "recentlyadded"
|
# INFO: playlistid string examples: "recentlyadded"
|
||||||
playlistid: int | str = Field(..., description="The ID of the playlist")
|
playlistid: str = Field(..., description="The ID of the playlist")
|
||||||
|
|
||||||
|
|
||||||
class AddItemToPlaylistBody(BaseModel):
|
class AddItemToPlaylistBody(BaseModel):
|
||||||
@@ -170,19 +174,9 @@ def add_item_to_playlist(path: PlaylistIDPath, body: AddItemToPlaylistBody):
|
|||||||
return {"msg": "Done"}, 200
|
return {"msg": "Done"}, 200
|
||||||
|
|
||||||
|
|
||||||
class GetPlaylistQuery(BaseModel):
|
class GetPlaylistQuery(GenericLimitSchema):
|
||||||
no_tracks: bool = Field(False, description="Whether to include tracks")
|
no_tracks: bool = Field(False, description="Whether to include tracks")
|
||||||
|
start: int = Field(0, description="The start index of the tracks")
|
||||||
|
|
||||||
def format_custom_playlist(playlist: models.Playlist, tracks: list[models.Track]):
|
|
||||||
duration = sum(t.duration for t in tracks)
|
|
||||||
|
|
||||||
playlist.duration = duration
|
|
||||||
|
|
||||||
return {
|
|
||||||
"info": serialize_for_card(playlist),
|
|
||||||
"tracks": tracks,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@api.get("/<playlistid>")
|
@api.get("/<playlistid>")
|
||||||
@@ -206,24 +200,22 @@ def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery):
|
|||||||
playlist, tracks = handler()
|
playlist, tracks = handler()
|
||||||
return format_custom_playlist(playlist, tracks)
|
return format_custom_playlist(playlist, tracks)
|
||||||
|
|
||||||
playlist = PlaylistTable.get_by_id(playlistid)
|
entry = PlaylistStore.playlistmap.get(playlistid)
|
||||||
|
|
||||||
if playlist is None:
|
if entry is None:
|
||||||
return {"msg": "Playlist not found"}, 404
|
return {"msg": "Playlist not found"}, 404
|
||||||
|
|
||||||
tracks = TrackTable.get_tracks_by_trackhashes(playlist.trackhashes)
|
playlist = entry.playlist
|
||||||
|
tracks = PlaylistStore.get_playlist_tracks(playlistid, query.start, query.limit)
|
||||||
|
|
||||||
duration = sum(t.duration for t in tracks)
|
duration = sum(t.duration for t in tracks)
|
||||||
playlist.last_updated = date_string_to_time_passed(playlist.last_updated)
|
playlist._last_updated = date_string_to_time_passed(playlist.last_updated)
|
||||||
|
|
||||||
playlist.duration = duration
|
playlist.duration = duration
|
||||||
|
|
||||||
if not playlist.has_image:
|
return {
|
||||||
playlist.images = playlistlib.get_first_4_images(tracks)
|
"info": playlist,
|
||||||
|
"tracks": serialize_tracks(tracks) if not no_tracks else [],
|
||||||
playlist.clear_lists()
|
}
|
||||||
|
|
||||||
return {"info": playlist, "tracks": tracks if not no_tracks else []}
|
|
||||||
|
|
||||||
|
|
||||||
class UpdatePlaylistForm(BaseModel):
|
class UpdatePlaylistForm(BaseModel):
|
||||||
@@ -340,7 +332,7 @@ def remove_playlist(path: PlaylistIDPath):
|
|||||||
Delete playlist
|
Delete playlist
|
||||||
"""
|
"""
|
||||||
PlaylistTable.remove_one(path.playlistid)
|
PlaylistTable.remove_one(path.playlistid)
|
||||||
|
PlaylistStore.playlistmap.pop(path.playlistid, None)
|
||||||
return {"msg": "Done"}, 200
|
return {"msg": "Done"}, 200
|
||||||
|
|
||||||
|
|
||||||
@@ -426,7 +418,6 @@ def save_item_as_playlist(body: SavePlaylistAsItemBody):
|
|||||||
img, str(playlist.id), "image/webp", filename=filename
|
img, str(playlist.id), "image/webp", filename=filename
|
||||||
)
|
)
|
||||||
|
|
||||||
# PL.add_tracks_to_playlist(playlist.id, trackhashes)
|
|
||||||
PlaylistTable.append_to_playlist(playlist.id, trackhashes)
|
PlaylistTable.append_to_playlist(playlist.id, trackhashes)
|
||||||
playlist.count = len(trackhashes)
|
playlist.count = len(trackhashes)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import dataclasses
|
import dataclasses
|
||||||
import json
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -20,6 +19,7 @@ class Playlist:
|
|||||||
trackhashes: list[str] = dataclasses.field(default_factory=list)
|
trackhashes: list[str] = dataclasses.field(default_factory=list)
|
||||||
extra: dict[str, Any] = dataclasses.field(default_factory=dict)
|
extra: dict[str, Any] = dataclasses.field(default_factory=dict)
|
||||||
|
|
||||||
|
_last_updated: str = ""
|
||||||
userid: int | None = None
|
userid: int | None = None
|
||||||
thumb: str = ""
|
thumb: str = ""
|
||||||
count: int = 0
|
count: int = 0
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from app.setup.sqlite import run_migrations, setup_sqlite
|
|||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
from app.store.folder import FolderStore
|
from app.store.folder import FolderStore
|
||||||
|
from app.store.playlists import PlaylistStore
|
||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
from app.utils.generators import get_random_str
|
from app.utils.generators import get_random_str
|
||||||
from app.config import UserConfig
|
from app.config import UserConfig
|
||||||
@@ -49,4 +50,5 @@ def load_into_mem():
|
|||||||
TrackStore.load_all_tracks(get_random_str())
|
TrackStore.load_all_tracks(get_random_str())
|
||||||
AlbumStore.load_albums('a')
|
AlbumStore.load_albums('a')
|
||||||
ArtistStore.load_artists('a')
|
ArtistStore.load_artists('a')
|
||||||
|
PlaylistStore.load_playlists()
|
||||||
FolderStore.load_filepaths()
|
FolderStore.load_filepaths()
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
from app.db.userdata import PlaylistTable
|
||||||
|
from app.lib.playlistlib import get_first_4_images
|
||||||
|
from app.models.playlist import Playlist
|
||||||
|
from app.store.tracks import TrackStore
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistEntry:
|
||||||
|
def __init__(self, playlist: Playlist) -> None:
|
||||||
|
self.playlist = playlist
|
||||||
|
self.trackhashes: list[str] = playlist.trackhashes
|
||||||
|
self.playlist.clear_lists()
|
||||||
|
|
||||||
|
if not playlist.has_image:
|
||||||
|
self.playlist.images = get_first_4_images(
|
||||||
|
TrackStore.get_tracks_by_trackhashes(self.trackhashes)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistStore:
|
||||||
|
playlistmap: dict[str, PlaylistEntry] = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def load_playlists(cls):
|
||||||
|
"""
|
||||||
|
Loads all playlists into the store.
|
||||||
|
"""
|
||||||
|
cls.playlistmap = {str(p.id): PlaylistEntry(p) for p in PlaylistTable.get_all()}
|
||||||
|
print(cls.playlistmap)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_playlist_tracks(cls, playlist_id: str, start: int, limit: int):
|
||||||
|
"""
|
||||||
|
Returns the trackhashes for a playlist.
|
||||||
|
"""
|
||||||
|
|
||||||
|
entry = cls.playlistmap.get(playlist_id)
|
||||||
|
if entry is None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return TrackStore.get_tracks_by_trackhashes(
|
||||||
|
entry.trackhashes[start : start + limit]
|
||||||
|
)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_flat_list(cls):
|
||||||
|
return [p.playlist for p in cls.playlistmap.values()]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_playlist(cls, playlist: Playlist):
|
||||||
|
cls.playlistmap[str(playlist.id)] = PlaylistEntry(playlist)
|
||||||
Reference in New Issue
Block a user