From 2824bd1a0e196848a8fa1c7a48d5d44cc0e0562c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A8=D1=83=D1=80=D1=83=D0=BF=D0=BE=D0=B2=20=D0=98=D0=BB?= =?UTF-8?q?=D1=8C=D1=8F=20=D0=92=D0=B8=D0=BA=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B8=D1=87?= Date: Sun, 4 Jan 2026 01:37:47 +0300 Subject: [PATCH] stable - generate local playlists & add default options --- LinkerPatternGenerator.py | 28 ++++++++++----- LocalPLaylistGenerator.py | 50 +++++++++++++++++++++++++++ TODO | 7 ++++ main.py | 73 +++++++++++++++++++++++++++++---------- 4 files changed, 132 insertions(+), 26 deletions(-) create mode 100644 LocalPLaylistGenerator.py create mode 100644 TODO diff --git a/LinkerPatternGenerator.py b/LinkerPatternGenerator.py index 2f6eba2..8576da8 100644 --- a/LinkerPatternGenerator.py +++ b/LinkerPatternGenerator.py @@ -3,7 +3,7 @@ from SpotifyWebAPI import find_song, update_access_token from rapidfuzz import fuzz from tqdm import tqdm - +import requests def spotify_pattern_generator(): update_access_token() @@ -11,15 +11,29 @@ def spotify_pattern_generator(): local_library = get_data("local_library") spotify_library = get_data("tracks") + def find_local_songs_on_spotify(local_lib): + search_log = [] + total = len(local_library) found_tracks_map = {} with tqdm(total=total, desc='Searching local songs on spotify') as pbar: for track_id, track in local_lib.items(): - search_pattern = f"{track['name']} {track['artist']} {track['album']}" - found_tracks = find_song(search_pattern) - found_tracks_map[track_id] = found_tracks[0:1] + parts = [track["name"], track["artist"], track.get("album")] + search_pattern = " ".join(p for p in parts if p) + + if search_pattern == "": + print(f"cannot generate search pattern for the track - {track["path"]}") + pbar.update(1) + continue + + found_tracks = find_song(search_pattern)[0:1] + found_tracks_map[track_id] = found_tracks + search_log.append({"path" : track["path"], "search_pattern": search_pattern, "result" : found_tracks_map[track_id]}) pbar.update(1) + + save_data(search_log, "spotify_search_log") + return found_tracks_map spotify_found_tracks = find_local_songs_on_spotify(local_library) @@ -127,10 +141,8 @@ def fuzzy_tag_pattern_generator(): save_data(mappings, "link_pattern_tags") -def run_pattern_generators(): - #fuzzy_pattern_generator() - #spotify_pattern_generator() - fuzzy_tag_pattern_generator() +def run_pattern_generators(type_id = 0): + spotify_pattern_generator() if __name__ == "__main__": diff --git a/LocalPLaylistGenerator.py b/LocalPLaylistGenerator.py new file mode 100644 index 0000000..0299498 --- /dev/null +++ b/LocalPLaylistGenerator.py @@ -0,0 +1,50 @@ + +from pathlib import Path + +def generate_playlists(local_library, library_path, remote_playlists, link_pattern, output_dir): + output_dir = Path(output_dir) + output_playlists = [] + + remote_to_local_map = {} + + for local_id, links in link_pattern.items(): + if len(links['items']): + remote_to_local_map[links['items'][0]] = local_id + + for name, tracks in remote_playlists.items(): + local_tracks = [] + + for track in tracks: + track = track["track"] + path = "error" + error = "" + if track is None: + error = f"error: track is none" + elif track["id"] in remote_to_local_map: + path = local_library[remote_to_local_map[track["id"]]]["path"] + else: + error = f"error: not_found - {track["name"]}" + + if error != "": + local_tracks.append("# " + error) + else: + path = (output_dir / ".." ).resolve() / Path(path) + local_tracks.append(path) + + pl = { + "name": name, + "tracks": local_tracks, + } + + output_playlists.append(pl) + + + for pl in output_playlists: + output_dir.mkdir(parents=True, exist_ok=True) + + playlist_path = Path(output_dir) / f"{pl['name']}.m3u" + + with (playlist_path.open("w", encoding="utf-8") as f): + for track in pl["tracks"]: + f.write(str(track)) + f.write("\n") \ No newline at end of file diff --git a/TODO b/TODO new file mode 100644 index 0000000..f285a22 --- /dev/null +++ b/TODO @@ -0,0 +1,7 @@ +skip playlists that are not created by user + +create one big playlist with the order of the spotify and dont skip missing + +generate percentage like hierarchy of the missing items + +generate detailed log of the missing items and search results diff --git a/main.py b/main.py index 9709e93..10d378a 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,19 @@ from DataBase import * +from LocalLibraryIndexer import update_local_library +from LinkerPatternGenerator import run_pattern_generators +from LocalPLaylistGenerator import generate_playlists + from SpotifyWebAPI import fetch_data, update_access_token import cmd +local_library_path = "/home/auser/Music/" +rel_autogen_dir = "0autogen" + playlists = get_data('playlistTracks') top_artists = get_data('top_artists') top_tracks = get_data('top_tracks') user_tracks = get_data('tracks') -link_patterns = [get_data('link_pattern_tags'), get_data('link_pattern_tags'), get_data('link_pattern_tags')] +link_pattern = get_data('link_pattern_spotify') local_library = get_data("local_library") user_tracks_by_id = {track['track']['id']: track['track'] for track in user_tracks} @@ -109,33 +116,20 @@ def print_link_stats(link_pattern): print(f"'{remote_track['name']}' by '{get_artists_str(remote_track['artists'])}'") index += 1 - return - print(f"\nUnresolved tracks from the local: {len(unresolved_local)}") - index = 0 - for link in unresolved_local: - local_track = local_library[link] - print(f" {index}: '{local_track['name']}' by '{local_track['artist']}'") - index += 1 - class Interpreter(cmd.Cmd): intro = "Welcome to the interactive command loop. Type help or ? to list commands.\n" prompt = "(spotify) " - def do_link_patterns(self, arg): + def do_list_stat_vs_types(self, arg): """prints available linking patterns""" print(f"0 - fuzzy tags") print(f"1 - spotify") print(f"2 - fuzzy") - def do_link_pattern(self, arg): - if not len(arg.split()): - print("expected name of the pattern") - return - - """prints available linking patterns""" - pattern_id = int(arg.split()[0]) - print_link_stats(link_patterns[pattern_id]) + def do_stat_local_library_vs_spotify(self, arg): + """compares local library and remote with the link pattern generated by the generate_pattern_command""" + print_link_stats(link_pattern) def do_playlists(self, arg): """prints user playlists""" @@ -168,6 +162,24 @@ class Interpreter(cmd.Cmd): """Print tracks""" print_tracks() + def do_generate_local_playlists(self, arg): + """generates local playlists based on the vs comparison type""" + generate_playlists(local_library, local_library_path, playlists, link_pattern, local_library_path + rel_autogen_dir) + + + def do_index_local_library(self, arg): + """find local tracks and fetches the data from them for further analysis""" + global local_library + update_local_library(local_library_path) + local_library = get_data("local_library") + + def do_generate_vs_stat(self, arg): + """compares local and remote library and generates the vs stats""" + + global link_pattern + run_pattern_generators() + link_pattern = get_data('link_pattern_spotify') + def do_fetch(self, arg): """Fetch all spotify data""" try: @@ -184,6 +196,31 @@ class Interpreter(cmd.Cmd): except ValueError: print("Can not fetch the data.") + def do_generate_todo(self, arg): + """generates todos for the pirates""" + print("sorry not implemented yet.") + + def do_run_all_default(self, arg): + """runs general flow""" + + local_lib_path = "/home/auser/Music/" + print("Fetching spotify data...") + self.do_fetch([]) + + print("Fetching local data...") + self.do_index_local_library(f"{local_lib_path}/organized") + + print("Generating vs patterns...") + self.do_generate_vs_stat("0") + + print("Generating todos...") + self.do_generate_todo(f"{local_lib_path}") + + print("Generating local playlists...") + self.do_generate_local_playlists(f"{local_lib_path}") + + return True + def do_exit(self, arg): """Exit the command loop: exit""" print("Goodbye!")