From bbcacf81bd10fd63904c7e697849cafa64087cda Mon Sep 17 00:00:00 2001 From: cwilvx Date: Thu, 23 May 2024 12:42:36 +0300 Subject: [PATCH] add recently played playlist --- .github/changelog.md | 4 +++ TODO.md | 3 +- app/api/home/__init__.py | 4 +-- app/api/playlist.py | 38 ++++++++++++++++-------- app/db/sqlite/logger/tracks.py | 19 ++++++++++-- app/lib/home/recentlyadded.py | 38 ++++++++++++++++++++---- app/lib/home/recentlyplayed.py | 53 ++++++++++++++++++++++++++++++---- app/lib/playlistlib.py | 31 -------------------- app/models/logger.py | 2 +- app/store/tracks.py | 12 ++++++-- 10 files changed, 142 insertions(+), 62 deletions(-) diff --git a/.github/changelog.md b/.github/changelog.md index 4c1511df..316a8390 100644 --- a/.github/changelog.md +++ b/.github/changelog.md @@ -3,4 +3,8 @@ - Auth +## Improvements +- The context menu now doesn't take forever to open up +- Merged "Save as Playlist" with "Add to Playlist" > "New Playlist" + ## Development diff --git a/TODO.md b/TODO.md index 0258f1a6..60085e3d 100644 --- a/TODO.md +++ b/TODO.md @@ -1,11 +1,12 @@ # TODO - Migrations: 1. Move userdata to new hashing algorithm -- Store on the correct user account: +- Store (and read) from the correct user account: 1. Playlists 2. Favorites - Package jsoni and publish on PyPi # DONE +- Add recently played playlist - Move user track logs to user zero - Move future logs to appropriate user id \ No newline at end of file diff --git a/app/api/home/__init__.py b/app/api/home/__init__.py index 3bac00ca..5fcfd744 100644 --- a/app/api/home/__init__.py +++ b/app/api/home/__init__.py @@ -2,7 +2,7 @@ from flask_openapi3 import Tag from flask_openapi3 import APIBlueprint from app.api.apischemas import GenericLimitSchema -from app.lib.home.recentlyadded import get_recent_items +from app.lib.home.recentlyadded import get_recently_added_items from app.lib.home.recentlyplayed import get_recently_played bp_tag = Tag(name="Home", description="Homepage items") @@ -14,7 +14,7 @@ def get_recently_added(query: GenericLimitSchema): """ Get recently added """ - return {"items": get_recent_items(query.limit)} + return {"items": get_recently_added_items(query.limit)} @api.get("/recents/played") diff --git a/app/api/playlist.py b/app/api/playlist.py index 8ccd1582..1f2720d0 100644 --- a/app/api/playlist.py +++ b/app/api/playlist.py @@ -15,6 +15,8 @@ from app import models from app.db.sqlite.playlists import SQLitePlaylistMethods from app.lib import playlistlib from app.lib.albumslib import sort_by_track_no +from app.lib.home.recentlyadded import get_recently_added_playlist +from app.lib.home.recentlyplayed import get_recently_played_playlist from app.serializers.playlist import serialize_for_card from app.store.tracks import TrackStore from app.utils.dates import create_new_date, date_string_to_time_passed @@ -174,6 +176,18 @@ class GetPlaylistQuery(BaseModel): no_tracks: bool = Field(False, description="Whether to include tracks") +def format_custom_playlist(playlist: models.Playlist, tracks: list[models.Track]): + duration = sum(t.duration for t in tracks) + + playlist.set_duration(duration) + playlist = serialize_for_card(playlist) + + return { + "info": playlist, + "tracks": tracks, + } + + @api.get("/") def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery): """ @@ -182,18 +196,18 @@ def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery): no_tracks = query.no_tracks playlistid = path.playlistid - is_recently_added = playlistid == "recentlyadded" + custom_playlists = [ + {"name": "recentlyadded", "handler": get_recently_added_playlist}, + {"name": "recentlyplayed", "handler": get_recently_played_playlist}, + ] + is_custom = playlistid in {p["name"] for p in custom_playlists} - if is_recently_added: - playlist, tracks = playlistlib.get_recently_added_playlist() - - tracks = remove_duplicates(tracks) - duration = sum(t.duration for t in tracks) - - playlist.set_duration(duration) - playlist = serialize_for_card(playlist) - - return {"info": playlist, "tracks": tracks} + if is_custom: + handler = next( + p["handler"] for p in custom_playlists if p["name"] == playlistid + ) + playlist, tracks = handler() + return format_custom_playlist(playlist, tracks) playlist = PL.get_playlist_by_id(int(playlistid)) @@ -247,7 +261,7 @@ def update_playlist_info(path: PlaylistIDPath, form: UpdatePlaylistForm): settings["has_gif"] = False playlist = { - "id": playlistid, + "id": int(playlistid), "image": db_playlist.image, "last_updated": create_new_date(), "name": str(form.name).strip(), diff --git a/app/db/sqlite/logger/tracks.py b/app/db/sqlite/logger/tracks.py index 36db63db..832cbefb 100644 --- a/app/db/sqlite/logger/tracks.py +++ b/app/db/sqlite/logger/tracks.py @@ -1,11 +1,12 @@ from app.db.sqlite.utils import SQLiteManager +from app.models.logger import TrackLog as TrackLog class SQLiteTrackLogger: @classmethod def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int, userid: int): """ - Inserts a track into the database + Inserts a track play record into the database """ with SQLiteManager(userdata_db=True) as cur: @@ -26,7 +27,7 @@ class SQLiteTrackLogger: @classmethod def get_all(cls): """ - Returns all tracks from the database + Returns all track play records from the database """ with SQLiteManager(userdata_db=True) as cur: @@ -36,3 +37,17 @@ class SQLiteTrackLogger: rows = cur.fetchall() return rows + + @classmethod + def get_recently_played(cls, start: int = 0, limit: int = 100): + """ + Returns a list of recently played tracks + """ + + with SQLiteManager(userdata_db=True) as cur: + sql = """SELECT * FROM track_logger ORDER BY timestamp DESC LIMIT ?,?""" + + cur.execute(sql, (start, limit)) + rows = cur.fetchall() + + return [TrackLog(*row) for row in rows] \ No newline at end of file diff --git a/app/lib/home/recentlyadded.py b/app/lib/home/recentlyadded.py index 4e7b5e39..ce9c86cf 100644 --- a/app/lib/home/recentlyadded.py +++ b/app/lib/home/recentlyadded.py @@ -1,6 +1,7 @@ -import os +from datetime import datetime -from flask import g +from app.lib.playlistlib import get_first_4_images +from app.models.playlist import Playlist from app.models.track import Track from app.store.tracks import TrackStore from app.store.albums import AlbumStore @@ -12,7 +13,7 @@ from app.serializers.artist import serialize_for_card from itertools import groupby -from app.utils.dates import timestamp_to_time_passed +from app.utils.dates import create_new_date, date_string_to_time_passed, timestamp_to_time_passed older_albums = set() older_artists = set() @@ -191,7 +192,7 @@ def group_track_by_folders(tracks: Track): return sorted(groups, key=lambda group: group["time"], reverse=True) -def get_recent_items(limit: int = 7): +def get_recently_added_items(limit: int = 7): tracks = sorted(TrackStore.tracks, key=lambda t: t.created_date) groups = group_track_by_folders(tracks) @@ -216,6 +217,33 @@ def get_recent_items(limit: int = 7): return recent_items -def get_recent_tracks(limit: int): + +def get_recently_added_playlist(limit: int = 100): + playlist = Playlist( + id="recentlyadded", + name="Recently Added", + image=None, + last_updated="Now", + settings={}, + trackhashes=[], + ) + + tracks = get_recently_added_tracks(limit=limit) + + try: + # Create date to show as last updated + date = datetime.fromtimestamp(tracks[0].created_date) + except IndexError: + return playlist, [] + + playlist.last_updated = date_string_to_time_passed(create_new_date(date)) + + images = get_first_4_images(tracks=tracks) + playlist.images = images + playlist.set_count(len(tracks)) + + return playlist, tracks + +def get_recently_added_tracks(limit: int): tracks = sorted(TrackStore.tracks, key=lambda t: t.created_date, reverse=True) return tracks[:limit] \ No newline at end of file diff --git a/app/lib/home/recentlyplayed.py b/app/lib/home/recentlyplayed.py index 99fd1268..7dc5066e 100644 --- a/app/lib/home/recentlyplayed.py +++ b/app/lib/home/recentlyplayed.py @@ -1,27 +1,37 @@ +from datetime import datetime import os -from app.models.logger import Track as TrackLog +from app.models.logger import TrackLog from app.db.sqlite.logger.tracks import SQLiteTrackLogger as db from app.db.sqlite.playlists import SQLitePlaylistMethods as pdb from app.db.sqlite.favorite import SQLiteFavoriteMethods as fdb +from app.models.playlist import Playlist from app.serializers.track import serialize_track from app.serializers.album import album_serializer -from app.utils.dates import timestamp_to_time_passed +from app.lib.playlistlib import get_first_4_images +from app.utils.dates import create_new_date, date_string_to_time_passed, timestamp_to_time_passed from app.serializers.artist import serialize_for_card from app.serializers.playlist import serialize_for_card as serialize_playlist -from app.lib.playlistlib import get_first_4_images, get_recently_added_playlist +from app.lib.home.recentlyadded import get_recently_added_playlist from app.store.albums import AlbumStore from app.store.tracks import TrackStore from app.store.artists import ArtistStore + def get_recently_played(limit=7): + # TODO: Paginate this entries = db.get_all() items = [] added = set() + custom_playlists = [ + {"name": "recentlyadded", "handler": get_recently_added_playlist}, + {"name": "recentlyplayed", "handler": get_recently_played_playlist}, + ] + for entry in entries: if len(items) >= limit: break @@ -112,10 +122,13 @@ def get_recently_played(limit=7): continue if entry.type == "playlist": - is_recently_added = entry.type_src == "recentlyadded" + is_custom = entry.type_src in [i["name"] for i in custom_playlists] + # is_recently_added = entry.type_src == "recentlyadded" - if is_recently_added: - playlist, _ = get_recently_added_playlist() + if is_custom: + playlist, _ = next( + i["handler"]() for i in custom_playlists if i["name"] == entry.type_src + ) playlist.images = [i["image"] for i in playlist.images] playlist = serialize_playlist( @@ -186,3 +199,31 @@ def get_recently_played(limit=7): ) return items + + +def get_recently_played_tracks(limit: int): + records = db.get_recently_played(start=0, limit=limit) + last_updated = records[0].timestamp + tracks = TrackStore.get_tracks_by_trackhashes([r.trackhash for r in records]) + return tracks, last_updated + +def get_recently_played_playlist(limit: int = 100): + playlist = Playlist( + id="recentlyplayed", + name="Recently Played", + image=None, + last_updated="Now", + settings={}, + trackhashes=[], + ) + + tracks, timestamp = get_recently_played_tracks(limit) + + date = datetime.fromtimestamp(timestamp) + playlist.last_updated = date_string_to_time_passed(create_new_date(date)) + + images = get_first_4_images(tracks=tracks) + playlist.images = images + playlist.set_count(len(tracks)) + + return playlist, tracks \ No newline at end of file diff --git a/app/lib/playlistlib.py b/app/lib/playlistlib.py index fc08e76e..f9512fe1 100644 --- a/app/lib/playlistlib.py +++ b/app/lib/playlistlib.py @@ -5,18 +5,14 @@ This library contains all the functions related to playlists. import os import random import string -from datetime import datetime from typing import Any from PIL import Image, ImageSequence from app import settings -from app.lib.home.recentlyadded import get_recent_tracks -from app.models.playlist import Playlist from app.models.track import Track from app.store.albums import AlbumStore from app.store.tracks import TrackStore -from app.utils.dates import create_new_date, date_string_to_time_passed def create_thumbnail(image: Any, img_path: str) -> str: @@ -133,30 +129,3 @@ def get_first_4_images( return images return duplicate_images(images) - - -def get_recently_added_playlist(limit: int = 100): - playlist = Playlist( - id="recentlyadded", - name="Recently Added", - image=None, - last_updated="Now", - settings={}, - trackhashes=[], - ) - - tracks = get_recent_tracks(limit=limit) - - try: - # Create date to show as last updated - date = datetime.fromtimestamp(tracks[0].created_date) - except IndexError: - return playlist, [] - - playlist.last_updated = date_string_to_time_passed(create_new_date(date)) - - images = get_first_4_images(tracks=tracks) - playlist.images = images - playlist.set_count(len(tracks)) - - return playlist, tracks diff --git a/app/models/logger.py b/app/models/logger.py index 7c630741..0f1e92d8 100644 --- a/app/models/logger.py +++ b/app/models/logger.py @@ -3,7 +3,7 @@ from typing import Literal @dataclass -class Track: +class TrackLog: """ Track play logger model """ diff --git a/app/store/tracks.py b/app/store/tracks.py index ae699e01..dd7981a4 100644 --- a/app/store/tracks.py +++ b/app/store/tracks.py @@ -129,10 +129,18 @@ class TrackStore: """ Returns a list of tracks by their hashes. """ + hash_set = set(trackhashes) + set_len = len(hash_set) - trackhashes = " ".join(trackhashes) - tracks = [track for track in cls.tracks if track.trackhash in trackhashes] + tracks = [] + for track in cls.tracks: + if track.trackhash in hash_set: + tracks.append(track) + if len(tracks) == set_len: + break + + # sort the tracks in the order of the given trackhashes tracks.sort(key=lambda t: trackhashes.index(t.trackhash)) return tracks