nice stats and stable

This commit is contained in:
Шурупов Илья Викторович 2026-01-06 02:34:04 +03:00
parent 11fb2816e3
commit 9c9ddb7a9e
4 changed files with 81 additions and 80 deletions

View file

@ -53,7 +53,7 @@ class Flow:
resolve_remote_tracks(client_id, client_secret, self.local_library, self.spotify_library, work_dir) resolve_remote_tracks(client_id, client_secret, self.local_library, self.spotify_library, work_dir)
def log_stats(self): def log_stats(self):
log_stats(work_dir / "remote_to_local_map.json", work_dir / "stats.json", self.local_library, self.spotify_library) log_stats(work_dir / "remote_to_local_map.json", work_dir / "stats", self.local_library, self.spotify_library)
def run(self, arg): def run(self, arg):

View file

@ -5,7 +5,7 @@ if __name__ == '__main__':
flow = Flow.Flow() flow = Flow.Flow()
# flow.fetch_spotify() # flow.fetch_spotify()
# flow.fetch_local() flow.fetch_local()
flow.parse_spotify_library() flow.parse_spotify_library()
flow.parse_local_library() flow.parse_local_library()

View file

@ -1,4 +1,5 @@
from src.Library import * from src.Library import *
from src.helpers import *
from pathlib import Path from pathlib import Path
import json import json
@ -52,20 +53,8 @@ class ArtistStat:
return (f"{str(int(self.resolved_precent))} % - " return (f"{str(int(self.resolved_precent))} % - "
f"{self.artist.title}") f"{self.artist.title}")
class LibraryStats:
class StatGenerator: def __init__(self, mapping_path : Path, local_lib: Library, remote_lib: Library):
def __init__(self):
self.out = {
"local_count": 0,
"resolved_count": 0,
"popular_artists": [],
"popular_albums": [],
"unresolved_albums": [],
"unresolved_tracks": [],
}
self.max_popular_albums = 100 self.max_popular_albums = 100
self.max_popular_artists = 100 self.max_popular_artists = 100
@ -79,17 +68,19 @@ class StatGenerator:
self.albums_map: dict[str, AlbumStat] = {} self.albums_map: dict[str, AlbumStat] = {}
self.artists_map: dict[str, ArtistStat] = {} self.artists_map: dict[str, ArtistStat] = {}
self.local_lib: Library = Library() self.local_lib: Library = local_lib
self.remote_lib: Library = Library() self.remote_lib: Library = remote_lib
self.mapping: dict[str, Any] = Any self.mapping: dict[str, Any] = get_mapping(mapping_path)
self.remote_id_map: dict[str, Track] = {} self.remote_id_map: dict[str, Track] = {}
self.local_id_map: dict[str, Track] = {} self.local_id_map: dict[str, Track] = {}
def find_add_local_paths(self, mapping_path: Path): self.find_add_local_paths()
self.mapping = get_mapping(mapping_path) self.score_artists()
self.score_albums()
def find_add_local_paths(self):
self.remote_id_map = {track.id: track for track in self.remote_lib.tracks} self.remote_id_map = {track.id: track for track in self.remote_lib.tracks}
self.local_id_map = {track.id: track for track in self.local_lib.tracks} self.local_id_map = {track.id: track for track in self.local_lib.tracks}
@ -134,13 +125,12 @@ class StatGenerator:
for artist in album_stat.album.artists: for artist in album_stat.album.artists:
self.artists_map[artist.id].albums_stats.append(album_stat) self.artists_map[artist.id].albums_stats.append(album_stat)
def stat_generic(self):
self.out["resolved_count"] = len(self.mapping)
def gen_album_songs(album_stat): def gen_album(self, album_stat, condition):
tracks = [] tracks = []
for track in album_stat.album.tracks: for track in album_stat.album.tracks:
if condition.should_add_track(track):
tracks.append(f"{"" if is_resolved(track) else "𐄂"} {track.title} -> {track.local_path}") tracks.append(f"{"" if is_resolved(track) else "𐄂"} {track.title} -> {track.local_path}")
return { return {
@ -148,61 +138,72 @@ class StatGenerator:
"tracks" : tracks "tracks" : tracks
} }
for artist_stat in self.artists: def gen_artist(self, artist_stat, condition):
artist_log = { albums = []
"artist": artist_stat.get_str(),
"albums": [gen_album_songs(album) for album in artist_stat.albums_stats] for album_stat in artist_stat.albums_stats:
if condition.should_add_album(album_stat):
albums.append(self.gen_album(album_stat, condition))
return {
"artist" : artist_stat.get_str(),
"albums" : albums,
} }
if len(self.out["popular_artists"]) < self.max_popular_artists: def generate_stat_tree(self, condition):
self.out["popular_artists"].append(artist_log) artists = []
for album_data in self.albums: for artist_stat in self.artists:
log = album_data.get_str() if condition.should_add_artist(artist_stat):
artists.append(self.gen_artist(artist_stat, condition))
if len(self.out["popular_albums"]) < self.max_popular_albums: return {
self.out["popular_albums"].append(log) "artists" : artists
}
def stat_unresolved_albums(self): def save_tree(self, output_path, condition):
for album_data in self.albums: save_data(self.generate_stat_tree(condition), output_path)
if album_data.resolved_precent > self.resolved_percentage_threshold:
continue
log = album_data.get_str() def save_all(self, output_path):
if len(self.out["unresolved_albums"]) < self.max_unresolved_albums: class Condition:
self.out["unresolved_albums"].append(log) def should_add_artist(self, item):
return True
def stat_unresolved_tracks(self): def should_add_album(self, item):
self.out["local_count"] = len(self.local_lib.tracks) return True
for track in self.remote_lib.top_tracks: def should_add_track(self, item):
if not track.local_path: return True
log = f"{track.title} - {track.artists[0].title if len(track.artists) else ""} - {track.album.title}"
if len(self.out["unresolved_tracks"]) < self.max_unresolved_tracks:
self.out["unresolved_tracks"].append(log)
def save_stats(self, output_path: Path):
with output_path.open("w", encoding="utf-8") as f:
json.dump(self.out, f, indent=2, ensure_ascii=False)
def log_stats(self, mapping_path: Path, output_path: Path, local_lib: Library, remote_lib: Library):
self.remote_lib = remote_lib
self.local_lib = local_lib
self.find_add_local_paths(mapping_path)
self.score_artists()
self.score_albums()
self.stat_generic()
self.stat_unresolved_tracks()
self.stat_unresolved_albums()
self.save_stats(output_path)
def log_stats(mapping_path: Path, output_path: Path, local_lib: Library, remote_lib: Library): self.save_tree(output_path, Condition())
stat_gen = StatGenerator()
stat_gen.log_stats(mapping_path, output_path, local_lib, remote_lib)
def save_threshold(self, output_path, threshold = 80):
class Condition:
def should_add_artist(self, item : ArtistStat):
for album in item.albums_stats:
if self.should_add_album(album):
return True
return False
def should_add_album(self, item: AlbumStat):
return item.resolved_precent < threshold and len(item.album.tracks) > 3
def should_add_track(self, item: Track):
return not is_resolved(item)
self.save_tree(output_path, Condition())
def log_stats(mapping_path: Path, output_dir: Path, local_lib: Library, remote_lib: Library):
set_workdir(output_dir)
stat = LibraryStats(mapping_path, local_lib, remote_lib)
stat.save_all("stats_all")
stat.save_threshold("stats_missing")

View file

@ -46,7 +46,7 @@ def save_data(data, name):
file_path = os.path.join(data_dir, f"{name}.json") file_path = os.path.join(data_dir, f"{name}.json")
with open(file_path, 'w') as file: with open(file_path, 'w') as file:
json.dump(data, file, indent=4) json.dump(data, file, indent=4, ensure_ascii=False)
print(f"Data saved to {file_path}.") print(f"Data saved to {file_path}.")