mirror of
https://github.com/Dvorinka/swingmusic-extended.git
synced 2026-06-03 20:13:02 +00:00
add recently played playlist
This commit is contained in:
@@ -3,4 +3,8 @@
|
|||||||
<!-- TODO: ELABORATE -->
|
<!-- TODO: ELABORATE -->
|
||||||
- Auth
|
- Auth
|
||||||
|
|
||||||
|
## Improvements
|
||||||
|
- The context menu now doesn't take forever to open up
|
||||||
|
- Merged "Save as Playlist" with "Add to Playlist" > "New Playlist"
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
# TODO
|
# TODO
|
||||||
- Migrations:
|
- Migrations:
|
||||||
1. Move userdata to new hashing algorithm
|
1. Move userdata to new hashing algorithm
|
||||||
- Store on the correct user account:
|
- Store (and read) from the correct user account:
|
||||||
1. Playlists
|
1. Playlists
|
||||||
2. Favorites
|
2. Favorites
|
||||||
- Package jsoni and publish on PyPi
|
- Package jsoni and publish on PyPi
|
||||||
|
|
||||||
# DONE
|
# DONE
|
||||||
|
- Add recently played playlist
|
||||||
- Move user track logs to user zero
|
- Move user track logs to user zero
|
||||||
- Move future logs to appropriate user id
|
- Move future logs to appropriate user id
|
||||||
@@ -2,7 +2,7 @@ from flask_openapi3 import Tag
|
|||||||
from flask_openapi3 import APIBlueprint
|
from flask_openapi3 import APIBlueprint
|
||||||
|
|
||||||
from app.api.apischemas import GenericLimitSchema
|
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
|
from app.lib.home.recentlyplayed import get_recently_played
|
||||||
|
|
||||||
bp_tag = Tag(name="Home", description="Homepage items")
|
bp_tag = Tag(name="Home", description="Homepage items")
|
||||||
@@ -14,7 +14,7 @@ def get_recently_added(query: GenericLimitSchema):
|
|||||||
"""
|
"""
|
||||||
Get recently added
|
Get recently added
|
||||||
"""
|
"""
|
||||||
return {"items": get_recent_items(query.limit)}
|
return {"items": get_recently_added_items(query.limit)}
|
||||||
|
|
||||||
|
|
||||||
@api.get("/recents/played")
|
@api.get("/recents/played")
|
||||||
|
|||||||
+26
-12
@@ -15,6 +15,8 @@ from app import models
|
|||||||
from app.db.sqlite.playlists import SQLitePlaylistMethods
|
from app.db.sqlite.playlists import SQLitePlaylistMethods
|
||||||
from app.lib import playlistlib
|
from app.lib import playlistlib
|
||||||
from app.lib.albumslib import sort_by_track_no
|
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.serializers.playlist import serialize_for_card
|
||||||
from app.store.tracks import TrackStore
|
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
|
||||||
@@ -174,6 +176,18 @@ class GetPlaylistQuery(BaseModel):
|
|||||||
no_tracks: bool = Field(False, description="Whether to include tracks")
|
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("/<playlistid>")
|
@api.get("/<playlistid>")
|
||||||
def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery):
|
def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery):
|
||||||
"""
|
"""
|
||||||
@@ -182,18 +196,18 @@ def get_playlist(path: PlaylistIDPath, query: GetPlaylistQuery):
|
|||||||
no_tracks = query.no_tracks
|
no_tracks = query.no_tracks
|
||||||
playlistid = path.playlistid
|
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:
|
if is_custom:
|
||||||
playlist, tracks = playlistlib.get_recently_added_playlist()
|
handler = next(
|
||||||
|
p["handler"] for p in custom_playlists if p["name"] == playlistid
|
||||||
tracks = remove_duplicates(tracks)
|
)
|
||||||
duration = sum(t.duration for t in tracks)
|
playlist, tracks = handler()
|
||||||
|
return format_custom_playlist(playlist, tracks)
|
||||||
playlist.set_duration(duration)
|
|
||||||
playlist = serialize_for_card(playlist)
|
|
||||||
|
|
||||||
return {"info": playlist, "tracks": tracks}
|
|
||||||
|
|
||||||
playlist = PL.get_playlist_by_id(int(playlistid))
|
playlist = PL.get_playlist_by_id(int(playlistid))
|
||||||
|
|
||||||
@@ -247,7 +261,7 @@ def update_playlist_info(path: PlaylistIDPath, form: UpdatePlaylistForm):
|
|||||||
settings["has_gif"] = False
|
settings["has_gif"] = False
|
||||||
|
|
||||||
playlist = {
|
playlist = {
|
||||||
"id": playlistid,
|
"id": int(playlistid),
|
||||||
"image": db_playlist.image,
|
"image": db_playlist.image,
|
||||||
"last_updated": create_new_date(),
|
"last_updated": create_new_date(),
|
||||||
"name": str(form.name).strip(),
|
"name": str(form.name).strip(),
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
from app.db.sqlite.utils import SQLiteManager
|
from app.db.sqlite.utils import SQLiteManager
|
||||||
|
from app.models.logger import TrackLog as TrackLog
|
||||||
|
|
||||||
|
|
||||||
class SQLiteTrackLogger:
|
class SQLiteTrackLogger:
|
||||||
@classmethod
|
@classmethod
|
||||||
def insert_track(cls, trackhash: str, duration: int, source: str, timestamp: int, userid: int):
|
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:
|
with SQLiteManager(userdata_db=True) as cur:
|
||||||
@@ -26,7 +27,7 @@ class SQLiteTrackLogger:
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_all(cls):
|
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:
|
with SQLiteManager(userdata_db=True) as cur:
|
||||||
@@ -36,3 +37,17 @@ class SQLiteTrackLogger:
|
|||||||
rows = cur.fetchall()
|
rows = cur.fetchall()
|
||||||
|
|
||||||
return rows
|
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]
|
||||||
@@ -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.models.track import Track
|
||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
@@ -12,7 +13,7 @@ from app.serializers.artist import serialize_for_card
|
|||||||
|
|
||||||
from itertools import groupby
|
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_albums = set()
|
||||||
older_artists = 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)
|
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)
|
tracks = sorted(TrackStore.tracks, key=lambda t: t.created_date)
|
||||||
groups = group_track_by_folders(tracks)
|
groups = group_track_by_folders(tracks)
|
||||||
|
|
||||||
@@ -216,6 +217,33 @@ def get_recent_items(limit: int = 7):
|
|||||||
return recent_items
|
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)
|
tracks = sorted(TrackStore.tracks, key=lambda t: t.created_date, reverse=True)
|
||||||
return tracks[:limit]
|
return tracks[:limit]
|
||||||
@@ -1,27 +1,37 @@
|
|||||||
|
from datetime import datetime
|
||||||
import os
|
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.logger.tracks import SQLiteTrackLogger as db
|
||||||
from app.db.sqlite.playlists import SQLitePlaylistMethods as pdb
|
from app.db.sqlite.playlists import SQLitePlaylistMethods as pdb
|
||||||
from app.db.sqlite.favorite import SQLiteFavoriteMethods as fdb
|
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.track import serialize_track
|
||||||
from app.serializers.album import album_serializer
|
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.artist import serialize_for_card
|
||||||
from app.serializers.playlist import serialize_for_card as serialize_playlist
|
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.albums import AlbumStore
|
||||||
from app.store.tracks import TrackStore
|
from app.store.tracks import TrackStore
|
||||||
from app.store.artists import ArtistStore
|
from app.store.artists import ArtistStore
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_recently_played(limit=7):
|
def get_recently_played(limit=7):
|
||||||
|
# TODO: Paginate this
|
||||||
entries = db.get_all()
|
entries = db.get_all()
|
||||||
items = []
|
items = []
|
||||||
added = set()
|
added = set()
|
||||||
|
|
||||||
|
custom_playlists = [
|
||||||
|
{"name": "recentlyadded", "handler": get_recently_added_playlist},
|
||||||
|
{"name": "recentlyplayed", "handler": get_recently_played_playlist},
|
||||||
|
]
|
||||||
|
|
||||||
for entry in entries:
|
for entry in entries:
|
||||||
if len(items) >= limit:
|
if len(items) >= limit:
|
||||||
break
|
break
|
||||||
@@ -112,10 +122,13 @@ def get_recently_played(limit=7):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if entry.type == "playlist":
|
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:
|
if is_custom:
|
||||||
playlist, _ = get_recently_added_playlist()
|
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.images = [i["image"] for i in playlist.images]
|
||||||
|
|
||||||
playlist = serialize_playlist(
|
playlist = serialize_playlist(
|
||||||
@@ -186,3 +199,31 @@ def get_recently_played(limit=7):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return items
|
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
|
||||||
@@ -5,18 +5,14 @@ This library contains all the functions related to playlists.
|
|||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from PIL import Image, ImageSequence
|
from PIL import Image, ImageSequence
|
||||||
|
|
||||||
from app import settings
|
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.models.track import Track
|
||||||
from app.store.albums import AlbumStore
|
from app.store.albums import AlbumStore
|
||||||
from app.store.tracks import TrackStore
|
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:
|
def create_thumbnail(image: Any, img_path: str) -> str:
|
||||||
@@ -133,30 +129,3 @@ def get_first_4_images(
|
|||||||
return images
|
return images
|
||||||
|
|
||||||
return duplicate_images(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
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from typing import Literal
|
|||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Track:
|
class TrackLog:
|
||||||
"""
|
"""
|
||||||
Track play logger model
|
Track play logger model
|
||||||
"""
|
"""
|
||||||
|
|||||||
+10
-2
@@ -129,10 +129,18 @@ class TrackStore:
|
|||||||
"""
|
"""
|
||||||
Returns a list of tracks by their hashes.
|
Returns a list of tracks by their hashes.
|
||||||
"""
|
"""
|
||||||
|
hash_set = set(trackhashes)
|
||||||
|
set_len = len(hash_set)
|
||||||
|
|
||||||
trackhashes = " ".join(trackhashes)
|
tracks = []
|
||||||
tracks = [track for track in cls.tracks if track.trackhash in trackhashes]
|
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))
|
tracks.sort(key=lambda t: trackhashes.index(t.trackhash))
|
||||||
return tracks
|
return tracks
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user