support mix items not in store

This commit is contained in:
cwilvx
2024-12-26 20:55:59 +03:00
parent 24aa34807b
commit 94591daa1e
9 changed files with 202 additions and 319 deletions
+1
View File
@@ -31,3 +31,4 @@ logs.txt
testdata.py testdata.py
test.py test.py
nohup.out nohup.out
*s.json
+1 -2
View File
@@ -78,8 +78,7 @@ def get_mix(query: MixQuery):
return {"msg": "Mix not found"}, 404 return {"msg": "Mix not found"}, 404
if mixtype == "custom_mixes": if mixtype == "custom_mixes":
plugin = MixesPlugin() mix = MixesPlugin.get_track_mix(mix)
mix = plugin.get_track_mix(mix)
if not mix: if not mix:
return {"msg": "Mix not found"}, 404 return {"msg": "Mix not found"}, 404
+2
View File
@@ -72,6 +72,8 @@ def log_track(body: LogTrackBody):
return {"msg": "Track not found."}, 404 return {"msg": "Track not found."}, 404
scrobble_data = dict(body) scrobble_data = dict(body)
# REVIEW: Do we need to store the extra info in the database?
# OR .... can we just write it to the backup file on demand?
scrobble_data["extra"] = get_extra_info(body.trackhash, "track") scrobble_data["extra"] = get_extra_info(body.trackhash, "track")
ScrobbleTable.add(scrobble_data) ScrobbleTable.add(scrobble_data)
+21 -294
View File
@@ -1,303 +1,30 @@
from app.store.albums import AlbumStore from app.db.userdata import MixTable
from app.store.artists import ArtistStore from app.plugins.mixes import MixesPlugin
from app.store.folder import FolderStore
from app.store.tracks import TrackStore
from app.models.logger import TrackLog
from app.lib.playlistlib import get_first_4_images
from app.utils.dates import timestamp_to_time_passed
from app.lib.home.recentlyadded import get_recently_added_playlist
from app.db.userdata import FavoritesTable, MixTable, PlaylistTable
from app.lib.home.recentlyplayed import get_recently_played_playlist
from app.serializers.track import serialize_track
from app.serializers.album import album_serializer
from app.serializers.artist import serialize_for_card
from app.serializers.playlist import serialize_for_card as serialize_playlist
def create_items(entries: list[TrackLog], limit: int): def find_mix(mixid: str, sourcehash: str):
custom_playlists = [ """
{"name": "recentlyadded", "handler": get_recently_added_playlist}, Find a mix in the homepage store or the db.
{"name": "recentlyplayed", "handler": get_recently_played_playlist}, """
] from app.store.homepage import HomepageStore
items = [] mixtype = "custom_mixes" if mixid[0] == "t" else "artist_mixes"
added = set()
for entry in entries: # INFO: Try getting the mix from the homepage store
if len(items) >= limit: mix = HomepageStore.get_mix(mixtype, mixid)
break if mix and mix["sourcehash"] == sourcehash:
return mix
if entry.source in added: # INFO: Get the mix from the db
continue mix = MixTable.get_by_sourcehash(sourcehash)
added.add(entry.source) if not mix:
return None
if entry.type == "mix": if mixtype == "custom_mixes":
if not entry.type_src: mix = MixesPlugin.get_track_mix(mix)
continue
# INFO: Get mix from homepage store if not mix:
from app.store.homepage import HomepageStore return None
mix = HomepageStore.find_mix(entry.type_src)
if not mix and entry.type_src.startswith("t"): return mix.to_dict()
# mix is a track mix (not saved in the db)
continue
if not mix:
# INFO: Get mix from db
mix = MixTable.get_by_mixid(entry.type_src)
if not mix:
continue
items.append(
{
"type": "mix",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
)
continue
if entry.type == "album":
album = AlbumStore.albummap.get(entry.type_src)
if album is None:
continue
item = {
"type": "album",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
items.append(item)
continue
if entry.type == "artist":
artist = ArtistStore.artistmap.get(entry.type_src)
if artist is None:
continue
items.append(
{
"type": "artist",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
)
continue
if entry.type == "folder":
folder = entry.type_src
if not folder:
continue
if not folder.endswith("/"):
folder += "/"
is_home_dir = entry.type_src == "$home"
if is_home_dir:
folder = os.path.expanduser("~")
item = {
"type": "folder",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
items.append(item)
continue
if entry.type == "playlist":
is_custom = entry.type_src in [i["name"] for i in custom_playlists]
if is_custom:
items.append(
{
"type": "playlist",
"hash": entry.type_src,
"timestamp": entry.timestamp,
"is_custom": True,
}
)
continue
playlist = PlaylistTable.get_by_id(entry.type_src)
if playlist is None:
continue
item = {
"type": "playlist",
"hash": entry.type_src,
"timestamp": entry.timestamp,
}
items.append(item)
continue
if entry.type == "favorite":
items.append(
{
"type": "favorite",
"timestamp": entry.timestamp,
}
)
continue
t = TrackStore.trackhashmap.get(entry.trackhash)
if t is None:
continue
item = {
"type": "track",
"hash": entry.trackhash,
"timestamp": entry.timestamp,
}
items.append(item)
return items
def recover_items(items: list[dict]):
custom_playlists = [
{"name": "recentlyadded", "handler": get_recently_added_playlist},
{"name": "recentlyplayed", "handler": get_recently_played_playlist},
]
recovered = []
for item in items:
recovered_item = None
if item["type"] == "album":
album = AlbumStore.get_album_by_hash(item["hash"])
if album is None:
continue
album = album_serializer(
album,
to_remove={
"genres",
"date",
"count",
"duration",
"albumartists_hashes",
"og_title",
},
)
recovered_item = {
"type": "album",
"item": album,
}
elif item["type"] == "artist":
artist = ArtistStore.get_artist_by_hash(item["hash"])
if artist is None:
continue
recovered_item = {
"type": "artist",
"item": serialize_for_card(artist),
}
elif item["type"] == "folder":
count = FolderStore.count_tracks_containing_paths([item["hash"]])
recovered_item = {
"type": "folder",
"item": {
"path": item["hash"],
"count": count[0]["trackcount"],
},
}
elif item["type"] == "playlist":
if item.get("is_custom"):
playlist, _ = next(
i["handler"]()
for i in custom_playlists
if i["name"] == item["hash"]
)
playlist.images = [i["image"] for i in playlist.images]
playlist = serialize_playlist(
playlist, to_remove={"settings", "duration"}
)
recovered_item = {
"type": "playlist",
"item": playlist,
}
else:
playlist = PlaylistTable.get_by_id(item["hash"])
if playlist is None:
continue
tracks = TrackStore.get_tracks_by_trackhashes(playlist.trackhashes)
playlist.clear_lists()
if not playlist.has_image:
images = get_first_4_images(tracks)
images = [i["image"] for i in images]
playlist.images = images
recovered_item = {
"type": "playlist",
"item": serialize_playlist(playlist),
}
elif item["type"] == "favorite":
recovered_item = {
"type": "favorite",
"item": {
"count": FavoritesTable.count(),
},
}
elif item["type"] == "track":
track = TrackStore.trackhashmap.get(item["hash"])
if track is None:
continue
recovered_item = {
"type": "track",
"item": serialize_track(track.get_best()),
}
elif item["type"] == "mix":
from app.store.homepage import HomepageStore
mix = HomepageStore.find_mix(item["hash"])
if mix is None:
mix = MixTable.get_by_mixid(item["hash"])
if mix is None:
continue
mix = mix.to_dict()
recovered_item = {
"type": "mix",
"item": mix,
}
if recovered_item is not None:
helptext = item.get("help_text") or item.get("type")
secondary_text = item.get("secondary_text")
if "secondary_text" in item:
secondary_text = item["secondary_text"]
elif "timestamp" in item:
secondary_text = timestamp_to_time_passed(item["timestamp"])
if helptext:
recovered_item["item"]["help_text"] = helptext
if secondary_text:
recovered_item["item"]["time"] = secondary_text
recovered.append(recovered_item)
return recovered
+11 -11
View File
@@ -1,10 +1,11 @@
from app.db.userdata import MixTable, PlaylistTable import os
from app.db.userdata import PlaylistTable
from app.lib.home import find_mix
from app.lib.home.recentlyadded import get_recently_added_playlist 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.logger import TrackLog from app.models.logger import TrackLog
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.homepage import HomepageStore
from app.store.tracks import TrackStore from app.store.tracks import TrackStore
@@ -30,17 +31,16 @@ def create_items(entries: list[TrackLog], limit: int):
if not entry.type_src: if not entry.type_src:
continue continue
# INFO: Get mix from homepage store splits = entry.type_src.split(".")
mix = HomepageStore.find_mix(entry.type_src)
if not mix and entry.type_src.startswith("t"): try:
# mix is a track mix (not saved in the db) mixid = splits[0]
sourcehash = splits[1]
except IndexError:
continue continue
if not mix: # INFO: Get mix from homepage store
# INFO: Get mix from db mix = find_mix(mixid, sourcehash)
mix = MixTable.get_by_mixid(entry.type_src)
if not mix: if not mix:
continue continue
@@ -155,4 +155,4 @@ def create_items(entries: list[TrackLog], limit: int):
} }
items.append(item) items.append(item)
return items return items
+151
View File
@@ -0,0 +1,151 @@
from app.db.userdata import FavoritesTable, MixTable, PlaylistTable
from app.lib.home import find_mix
from app.lib.home.recentlyadded import get_recently_added_playlist
from app.lib.home.recentlyplayed import get_recently_played_playlist
from app.lib.playlistlib import get_first_4_images
from app.serializers.album import album_serializer
from app.serializers.artist import serialize_for_card
from app.serializers.playlist import serialize_for_card as serialize_playlist
from app.serializers.track import serialize_track
from app.store.albums import AlbumStore
from app.store.artists import ArtistStore
from app.store.folder import FolderStore
from app.store.tracks import TrackStore
from app.utils.dates import timestamp_to_time_passed
def recover_items(items: list[dict]):
custom_playlists = [
{"name": "recentlyadded", "handler": get_recently_added_playlist},
{"name": "recentlyplayed", "handler": get_recently_played_playlist},
]
recovered = []
for item in items:
recovered_item = None
if item["type"] == "album":
album = AlbumStore.get_album_by_hash(item["hash"])
if album is None:
continue
album = album_serializer(
album,
to_remove={
"genres",
"date",
"count",
"duration",
"albumartists_hashes",
"og_title",
},
)
recovered_item = {
"type": "album",
"item": album,
}
elif item["type"] == "artist":
artist = ArtistStore.get_artist_by_hash(item["hash"])
if artist is None:
continue
recovered_item = {
"type": "artist",
"item": serialize_for_card(artist),
}
elif item["type"] == "folder":
count = FolderStore.count_tracks_containing_paths([item["hash"]])
recovered_item = {
"type": "folder",
"item": {
"path": item["hash"],
"count": count[0]["trackcount"],
},
}
elif item["type"] == "playlist":
if item.get("is_custom"):
playlist, _ = next(
i["handler"]()
for i in custom_playlists
if i["name"] == item["hash"]
)
playlist.images = [i["image"] for i in playlist.images]
playlist = serialize_playlist(
playlist, to_remove={"settings", "duration"}
)
recovered_item = {
"type": "playlist",
"item": playlist,
}
else:
playlist = PlaylistTable.get_by_id(item["hash"])
if playlist is None:
continue
tracks = TrackStore.get_tracks_by_trackhashes(playlist.trackhashes)
playlist.clear_lists()
if not playlist.has_image:
images = get_first_4_images(tracks)
images = [i["image"] for i in images]
playlist.images = images
recovered_item = {
"type": "playlist",
"item": serialize_playlist(playlist),
}
elif item["type"] == "favorite":
recovered_item = {
"type": "favorite",
"item": {
"count": FavoritesTable.count(),
},
}
elif item["type"] == "track":
track = TrackStore.trackhashmap.get(item["hash"])
if track is None:
continue
recovered_item = {
"type": "track",
"item": serialize_track(track.get_best()),
}
elif item["type"] == "mix":
try:
splits = item["hash"].split(".")
mixid = splits[0]
sourcehash = splits[1]
except IndexError:
continue
mix = find_mix(mixid, sourcehash)
if mix is None:
continue
recovered_item = {
"type": "mix",
"item": mix,
}
if recovered_item is not None:
helptext = item.get("help_text") or item.get("type")
secondary_text = item.get("secondary_text")
if "secondary_text" in item:
secondary_text = item["secondary_text"]
elif "timestamp" in item:
secondary_text = timestamp_to_time_passed(item["timestamp"])
if helptext:
recovered_item["item"]["help_text"] = helptext
if secondary_text:
recovered_item["item"]["time"] = secondary_text
recovered.append(recovered_item)
return recovered
+1 -1
View File
@@ -25,7 +25,7 @@ class ArtistMixes(HomepageRoutine):
custom_mixes = [] custom_mixes = []
for _mix in mixes: for _mix in mixes:
custom_mix = mix.get_track_mix(_mix) custom_mix = MixesPlugin.get_track_mix(_mix)
if custom_mix: if custom_mix:
custom_mixes.append(custom_mix) custom_mixes.append(custom_mix)
+13 -10
View File
@@ -237,7 +237,8 @@ class MixesPlugin(Plugin):
print([m.title for m in mixes]) print([m.title for m in mixes])
return mixes return mixes
def get_mix_description(self, tracks: list[Track], artishash: str): @classmethod
def get_mix_description(cls, tracks: list[Track], artishash: str):
""" """
Constructs a description for a mix by putting together the first n=4 Constructs a description for a mix by putting together the first n=4
artists in the mix tracklist. artists in the mix tracklist.
@@ -434,17 +435,18 @@ class MixesPlugin(Plugin):
The resulting mix is definitely expected to be of low quality. The resulting mix is definitely expected to be of low quality.
TODO: Implement this! TODO: Maybe implement this!
""" """
pass pass
def get_track_mix(self, mix: Mix): @classmethod
def get_track_mix(cls, mix: Mix):
""" """
Given a mix, returns the excess tracks as a custom mix. Given a mix, returns the excess tracks as a custom mix.
""" """
# INFO: If the mix can't have more than 20 tracks, return None # INFO: If the mix can't have more than 20 tracks, return None
if len(mix.tracks) <= self.MIX_TRACKS_LENGTH + 20: if len(mix.tracks) <= cls.MIX_TRACKS_LENGTH + 20:
return None return None
og_track = TrackStore.trackhashmap.get(mix.tracks[0]) og_track = TrackStore.trackhashmap.get(mix.tracks[0])
@@ -454,20 +456,20 @@ class MixesPlugin(Plugin):
og_track = og_track.get_best() og_track = og_track.get_best()
tracks = [og_track] + TrackStore.get_tracks_by_trackhashes( tracks = [og_track] + TrackStore.get_tracks_by_trackhashes(
mix.tracks[self.MIX_TRACKS_LENGTH :] mix.tracks[cls.MIX_TRACKS_LENGTH :]
) )
trackmix = Mix( trackmix = Mix(
id=f"t{mix.userid}{mix.extra['artisthash']}", id=f"t{mix.userid}{mix.extra['artisthash']}",
title=og_track.title, title=og_track.title,
description=self.get_mix_description(tracks, mix.extra["artisthash"]), description=cls.get_mix_description(tracks, mix.extra["artisthash"]),
tracks=[t.trackhash for t in tracks], tracks=[t.trackhash for t in tracks],
sourcehash=create_hash(*[t.trackhash for t in tracks]), sourcehash=create_hash(*[t.trackhash for t in tracks]),
userid=mix.userid, userid=mix.userid,
extra={ extra={
"type": "track", "type": "track",
"og_sourcehash": mix.sourcehash, "og_sourcehash": mix.sourcehash,
"images": self.get_custom_mix_images(tracks), "images": cls.get_custom_mix_images(tracks),
"artists": None, "artists": None,
"albums": None, "albums": None,
}, },
@@ -479,8 +481,8 @@ class MixesPlugin(Plugin):
return trackmix return trackmix
def get_custom_mix_images(self, tracks: list[Track]): @classmethod
def get_custom_mix_images(cls, tracks: list[Track]):
first_album = tracks[0].albumhash first_album = tracks[0].albumhash
first_img = { first_img = {
"image": first_album + ".webp", "image": first_album + ".webp",
@@ -517,7 +519,8 @@ class MixesPlugin(Plugin):
return images return images
def get_because_items(self, mixes: list[Mix]): @staticmethod
def get_because_items(mixes: list[Mix]):
""" """
Given a list of mixes, returns a list of artists that are similar to the Given a list of mixes, returns a list of artists that are similar to the
artists in the mixes. artists in the mixes.
+1 -1
View File
@@ -1,7 +1,7 @@
from abc import ABC from abc import ABC
from typing import Any from typing import Any
from app.lib.home import recover_items from app.lib.home.recover_items import recover_items
from app.models.mix import Mix from app.models.mix import Mix
class HomepageEntry(ABC): class HomepageEntry(ABC):