This commit is contained in:
Шурупов Илья Викторович 2026-02-07 15:17:59 +03:00
parent b6c59e81ad
commit 9cd2c9189b
6 changed files with 112 additions and 128 deletions

View file

@ -23,6 +23,7 @@ from src.StatGenerator import log_stats
class Flow: class Flow:
spotify_library : Library = None spotify_library : Library = None
local_library : Library = None local_library : Library = None
itunes_library : Library = None
@staticmethod @staticmethod
def fetch_spotify(self): def fetch_spotify(self):
@ -107,6 +108,7 @@ class Flow:
def load_libraries(self): def load_libraries(self):
self.spotify_library = LibraryLoader().load(work_dir / "spotify_library.json") self.spotify_library = LibraryLoader().load(work_dir / "spotify_library.json")
self.itunes_library = LibraryLoader().load(work_dir / "itunes_library.json")
self.local_library = LibraryLoader().load(work_dir / "local_library.json") self.local_library = LibraryLoader().load(work_dir / "local_library.json")
def save_libraries(self): def save_libraries(self):

38
Gui.py
View file

@ -6,34 +6,36 @@ import pandas as pd
import Flow import Flow
from src.Library import Library, Artist, Album, Track, Playlist from src.Library import *
def entity_to_row(entity: Entity):
return {
"id": entity.id,
"mbid": entity.mbid,
"Title": entity.title,
"PLay Count": entity.play_count,
"Resolved": int(entity.resolved_percentage * 100),
"AutoScore": entity.auto_score,
"Added": entity.date_added.strftime("%Y-%m-%d") if entity.date_added else None,
"Played": entity.date_last_play.strftime("%Y-%m-%d") if entity.date_last_play else None,
"Released": entity.date_released.strftime("%Y-%m-%d") if entity.date_released else None,
"LocalId": str(entity.local_id) if entity.local_id else None,
"Path": str(entity.local_path) if entity.local_path else None,
}
def track_to_row(track: Track): def track_to_row(track: Track):
return { return entity_to_row(track) | {
"Title": track.title,
"Album": track.album.title if track.album else "", "Album": track.album.title if track.album else "",
"Artists": ", ".join(a.title if a.title else "no_arist_name" for a in track.artists), "Artists": ", ".join(a.title if a.title else "no_arist_name" for a in track.artists),
"Lyrics": track.has_lyrics, "Lyrics": track.has_lyrics,
"AutoScore": track.auto_score,
"Added": track.added_at.strftime("%Y-%m-%d") if track.added_at else "",
"LocalId": str(track.local_id) if track.local_id else "",
"Path": str(track.local_path) if track.local_path else "",
} }
def artist_to_row(artist: Artist): def artist_to_row(artist: Artist):
return { return entity_to_row(artist) | {
"Title": artist.title,
"AutoScore": artist.auto_score,
"Resolved": int(artist.resolved_percentage * 100),
"LocalId": str(artist.local_id) if artist.local_id else "",
} }
def album_to_row(album: Album): def album_to_row(album: Album):
return { return entity_to_row(album) | {
"Title": album.title,
"AutoScore": album.auto_score,
"Resolved": int(album.resolved_percentage * 100),
"LocalId": str(album.local_id) if album.local_id else "",
} }
@ -200,7 +202,7 @@ def run():
# flow.log_stats() # flow.log_stats()
gui = LibraryView() gui = LibraryView()
gui.add_libraries([flow.spotify_library, flow.local_library]) gui.add_libraries([flow.spotify_library, flow.itunes_library, flow.local_library])
gui.render() gui.render()
run() run()

View file

@ -4,77 +4,58 @@ from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import List, Optional, Any from typing import List, Optional, Any
class Entity: class Entity:
def __init__(self): def __init__(self):
self.id: str = None self.id: str = None
self.mbid : str = None self.mbid: str = None
self.title: str = None self.title: str = None
self.duration_ms: int = None
self.fav: bool = False self.fav: bool = False
self.rating: int = 0 self.rating: int = 0
self.play_count : int = 0 self.play_count: int = 0
self.date_added : datetime = None self.date_added: datetime = None
self.date_released : datetime = None self.date_released: datetime = None
self.date_last_play : datetime = None self.date_last_play: datetime = None
self.auto_score: float = 0 self.auto_score: float = 0
self.local_id: str = None self.local_id: str = None
self.resolved_percentage : float = 0 self.local_path: Path = None
self.resolved_percentage: float = 0
class Artist:
class Artist(Entity):
def __init__(self): def __init__(self):
self.id: str = None super().__init__()
self.title: str = None
self.tracks: List[Track] = [] self.tracks: List[Track] = []
self.albums: List[Album] = [] self.albums: List[Album] = []
self.local_id: str = None
self.auto_score: float = 0
self.fav: bool = False
self.rating: int = 0
self.resolved_percentage : float = 0
class Track: class Track(Entity):
def __init__(self): def __init__(self):
self.id: str = None super().__init__()
self.title: str = None
self.artists: List[Artist] = [] self.artists: List[Artist] = []
self.album: Album = None self.album: Album = None
self.duration_ms: int = None
self.added_at: datetime = None
self.local_path: Path = None
self.has_lyrics: bool = False self.has_lyrics: bool = False
self.local_id: str = None
self.auto_score: float = 0
self.fav: bool = False
self.rating: int = 0
class Album: class Album(Entity):
def __init__(self): def __init__(self):
self.id: str = None super().__init__()
self.title: str = None
self.release_date: datetime = None
self.tracks: List[Track] = [] self.tracks: List[Track] = []
self.artists: List[Artist] = [] self.artists: List[Artist] = []
self.local_id: str = None
self.auto_score: float = 0
self.fav: bool = False
self.rating: int = 0
self.resolved_percentage : float = 0
class Playlist: class Playlist(Entity):
def __init__(self): def __init__(self):
self.title: str = None super().__init__()
self.tracks: List[Track] = [] self.tracks: List[Track] = []
self.created_at: datetime = None self.created_at: datetime = None
self.description: str = None self.description: str = None
self.auto_score: float = 0
self.fav: bool = False
self.rating: int = 0
self.resolved_percentage : float = 0
class Library: class Library:
@ -120,14 +101,8 @@ class Library:
artist.albums.append(album) artist.albums.append(album)
def auto_score(self): def auto_score(self):
self.score_tracks()
self.score_artists()
self.score_albums() self.score_albums()
self.score_artists()
def score_tracks(self):
for idx, track in enumerate(self.top_tracks):
score = len(self.top_tracks) - idx
track.auto_score = score
def score_artists(self): def score_artists(self):
for track in self.tracks: for track in self.tracks:
@ -150,4 +125,4 @@ class Library:
album.resolved_percentage = album_count_resolved / len(album.tracks) album.resolved_percentage = album_count_resolved / len(album.tracks)
artist_count_resolved += album_count_resolved artist_count_resolved += album_count_resolved
artist_count += len(album.tracks) artist_count += len(album.tracks)
artist.resolved_percentage = artist_count_resolved / artist_count artist.resolved_percentage = artist_count_resolved / artist_count

View file

@ -26,60 +26,51 @@ class LibrarySaver:
def __init__(self, library: Library): def __init__(self, library: Library):
self.library = library self.library = library
@staticmethod def _entity_to_dict(self, entity: Entity) -> dict[str, Any]:
def _artist_to_dict(artist: Artist) -> dict[str, Any]:
return { return {
"id": artist.id, "id": entity.id,
"title": artist.title, "mbid": entity.mbid,
"tracks": [track.id for track in artist.tracks],
"albums": [album.id for album in artist.albums], "title": entity.title,
"local_id": artist.local_id, "duration": entity.duration_ms,
"auto_score": artist.auto_score,
"fav" : artist.fav, "fav" : entity.fav,
"rating" : artist.rating, "rating" : entity.rating,
"play_count": entity.play_count,
"date_added": entity.date_added,
"date_released": entity.date_released,
"date_last_play": entity.date_last_play,
"auto_score": entity.auto_score,
"local_id": entity.local_id,
"local_path": entity.local_path,
"resolved_percentage": entity.resolved_percentage,
} }
@staticmethod def _artist_to_dict(self, artist: Artist) -> dict[str, Any]:
def _track_to_dict(track: Track) -> dict[str, Any]: return self._entity_to_dict(artist) | {
return { "tracks": [track.id for track in artist.tracks],
"id": track.id, "albums": [album.id for album in artist.albums],
"title": track.title, }
def _track_to_dict(self, track: Track) -> dict[str, Any]:
return self._entity_to_dict(track) | {
"artists": [artist.id for artist in track.artists], "artists": [artist.id for artist in track.artists],
"album": track.album.id, "album": track.album.id,
"duration_ms": track.duration_ms, "duration_ms": track.duration_ms,
"added_at": _dt_to_str(track.added_at),
"local_path": track.local_path,
"has_lyrics": track.has_lyrics, "has_lyrics": track.has_lyrics,
"local_id": track.local_id,
"auto_score": track.auto_score,
"fav" : track.fav,
"rating" : track.rating,
} }
@staticmethod def _playlist_to_dict(self, playlist: Playlist) -> dict[str, Any]:
def _playlist_to_dict(playlist: Playlist) -> dict[str, Any]: return self._entity_to_dict(playlist) | {
return {
"title": playlist.title,
"tracks": [track.id for track in playlist.tracks], "tracks": [track.id for track in playlist.tracks],
"created_at": _dt_to_str(playlist.created_at),
"description": playlist.description, "description": playlist.description,
"auto_score": playlist.auto_score,
"fav" : playlist.fav,
"rating" : playlist.rating,
} }
@staticmethod def _album_to_dict(self ,album: Album) -> dict[str, Any]:
def _album_to_dict(album: Album) -> dict[str, Any]: return self._entity_to_dict(album) | {
return {
"id": album.id,
"title": album.title,
"release_date": album.release_date,
"tracks": [track.id for track in album.tracks], "tracks": [track.id for track in album.tracks],
"artists": [artist.id for artist in album.artists], "artists": [artist.id for artist in album.artists],
"local_id": album.local_id,
"auto_score": album.auto_score,
"fav" : album.fav,
"rating" : album.rating,
} }
def save(self, path: Path) -> None: def save(self, path: Path) -> None:
@ -100,6 +91,7 @@ class LibrarySaver:
json.dump(data, f, indent=2, ensure_ascii=False) json.dump(data, f, indent=2, ensure_ascii=False)
class LibraryLoader: class LibraryLoader:
def __init__(self): def __init__(self):
self.track_map: dict[str, Track] = {} self.track_map: dict[str, Track] = {}
@ -127,48 +119,54 @@ class LibraryLoader:
self.track_map[id] = item self.track_map[id] = item
return item return item
def load_entity(self, entity : Entity, data: dict[str, Any]):
entity.id = data.get("id")
entity.mbid = data.get("mbid")
entity.title = data.get("title")
entity.duration_ms = data.get("duration_ms")
entity.fav = data.get("fav")
entity.rating = data.get("rating")
entity.play_count = data.get("play_count")
entity.date_added = _str_to_dt(data.get("date_added"))
entity.date_released = _str_to_dt(data.get("date_released"))
entity.date_last_play = _str_to_dt(data.get("date_last_play"))
entity.auto_score = data.get("auto_score")
entity.local_id = data.get("local_id")
entity.local_path = data.get("local_path")
entity.resolved_percentage = data.get("resolved_percentage")
def load_track(self, data: dict[str, Any]) -> Track: def load_track(self, data: dict[str, Any]) -> Track:
track = self.get_track(data.get("id")) track = self.get_track(data.get("id"))
track.id = data.get("id") self.load_entity(track, data)
track.title = data.get("title")
track.duration_ms = data.get("duration_ms") track.duration_ms = data.get("duration_ms")
track.added_at = _str_to_dt(data.get("added_at"))
track.local_path = data.get("local_path")
track.artists = [self.get_artist(artist_id) for artist_id in data.get("artists")] track.artists = [self.get_artist(artist_id) for artist_id in data.get("artists")]
track.album = self.get_album(data.get("album")) track.album = self.get_album(data.get("album"))
track.has_lyrics = data.get("has_lyrics") track.has_lyrics = data.get("has_lyrics")
track.local_id = data.get("local_id")
track.auto_score = data.get("auto_score")
track.fav = data.get("fav")
track.rating = data.get("rating")
self.track_map[track.id] = track self.track_map[track.id] = track
return track return track
def load_artist(self, data: dict[str, Any]): def load_artist(self, data: dict[str, Any]):
artist = self.get_artist(data.get("id")) artist = self.get_artist(data.get("id"))
artist.id = data.get("id") self.load_entity(artist, data)
artist.title = data.get("title")
artist.tracks = [self.get_track(track_id) for track_id in data.get("tracks")] artist.tracks = [self.get_track(track_id) for track_id in data.get("tracks")]
artist.albums = [self.get_album(album_id) for album_id in data.get("albums")] if data.get("albums") else [] artist.albums = [self.get_album(album_id) for album_id in data.get("albums")] if data.get("albums") else []
artist.local_id = data.get("local_id")
artist.auto_score = data.get("auto_score")
artist.fav = data.get("fav")
artist.rating = data.get("rating")
self.artist_map[artist.id] = artist self.artist_map[artist.id] = artist
return artist return artist
def load_album(self, data: dict[str, Any]): def load_album(self, data: dict[str, Any]):
album = self.get_album(data.get("id")) album = self.get_album(data.get("id"))
album.id = data.get("id") self.load_entity(album, data)
album.tracks = [self.get_track(track_id) for track_id in data.get("tracks")] album.tracks = [self.get_track(track_id) for track_id in data.get("tracks")]
album.title = data.get("title")
album.artists = [self.get_artist(artist_id) for artist_id in data.get("artists")] album.artists = [self.get_artist(artist_id) for artist_id in data.get("artists")]
album.local_id = data.get("local_id")
album.auto_score = data.get("auto_score")
album.fav = data.get("fav")
album.rating = data.get("rating")
self.album_map[album.id] = album self.album_map[album.id] = album
return album return album

View file

@ -167,6 +167,7 @@ class ParseItunesXml:
track.title = data.get("Name") track.title = data.get("Name")
track.artists = self.parse_artists([data.get("Artist")]) track.artists = self.parse_artists([data.get("Artist")])
track.album = self.parse_album(data.get("Album")) track.album = self.parse_album(data.get("Album"))
track.play_count = int(data.get("Play Count"))
if not track.album or not len(track.artists) or not track.title: if not track.album or not len(track.artists) or not track.title:
raise "error parsing" raise "error parsing"
@ -183,10 +184,9 @@ class ParseItunesXml:
self.library.name = "Itunes" self.library.name = "Itunes"
for track in self.library.tracks:
track.auto_score = track.play_count
return self.library return self.library
def parse_track_item(self, id, track: dict[str, Any]) -> Track:
return track

View file

@ -33,6 +33,8 @@ class Parser:
self.library.name = "Spotify" self.library.name = "Spotify"
self.library.auto_score() self.library.auto_score()
self.score_tracks()
return self.library return self.library
@ -122,3 +124,8 @@ class Parser:
self.library.artists.append(artist) self.library.artists.append(artist)
return artist return artist
def score_tracks(self):
for idx, track in enumerate(self.library.top_tracks):
score = len(self.library.top_tracks) - idx
track.auto_score = score