stable - generate local playlists & add default options

This commit is contained in:
Шурупов Илья Викторович 2026-01-04 01:37:47 +03:00
parent 11e3b06824
commit 892568d668
4 changed files with 132 additions and 26 deletions

View file

@ -3,7 +3,7 @@ from SpotifyWebAPI import find_song, update_access_token
from rapidfuzz import fuzz from rapidfuzz import fuzz
from tqdm import tqdm from tqdm import tqdm
import requests
def spotify_pattern_generator(): def spotify_pattern_generator():
update_access_token() update_access_token()
@ -11,15 +11,29 @@ def spotify_pattern_generator():
local_library = get_data("local_library") local_library = get_data("local_library")
spotify_library = get_data("tracks") spotify_library = get_data("tracks")
def find_local_songs_on_spotify(local_lib): def find_local_songs_on_spotify(local_lib):
search_log = []
total = len(local_library) total = len(local_library)
found_tracks_map = {} found_tracks_map = {}
with tqdm(total=total, desc='Searching local songs on spotify') as pbar: with tqdm(total=total, desc='Searching local songs on spotify') as pbar:
for track_id, track in local_lib.items(): for track_id, track in local_lib.items():
search_pattern = f"{track['name']} {track['artist']} {track['album']}" parts = [track["name"], track["artist"], track.get("album")]
found_tracks = find_song(search_pattern) search_pattern = " ".join(p for p in parts if p)
found_tracks_map[track_id] = found_tracks[0:1]
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) pbar.update(1)
save_data(search_log, "spotify_search_log")
return found_tracks_map return found_tracks_map
spotify_found_tracks = find_local_songs_on_spotify(local_library) 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") save_data(mappings, "link_pattern_tags")
def run_pattern_generators(): def run_pattern_generators(type_id = 0):
#fuzzy_pattern_generator() spotify_pattern_generator()
#spotify_pattern_generator()
fuzzy_tag_pattern_generator()
if __name__ == "__main__": if __name__ == "__main__":

50
LocalPLaylistGenerator.py Normal file
View file

@ -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")

7
TODO Normal file
View file

@ -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

73
main.py
View file

@ -1,12 +1,19 @@
from DataBase import * 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 from SpotifyWebAPI import fetch_data, update_access_token
import cmd import cmd
local_library_path = "/home/auser/Music/"
rel_autogen_dir = "0autogen"
playlists = get_data('playlistTracks') playlists = get_data('playlistTracks')
top_artists = get_data('top_artists') top_artists = get_data('top_artists')
top_tracks = get_data('top_tracks') top_tracks = get_data('top_tracks')
user_tracks = get_data('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") local_library = get_data("local_library")
user_tracks_by_id = {track['track']['id']: track['track'] for track in user_tracks} 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'])}'") print(f"'{remote_track['name']}' by '{get_artists_str(remote_track['artists'])}'")
index += 1 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): class Interpreter(cmd.Cmd):
intro = "Welcome to the interactive command loop. Type help or ? to list commands.\n" intro = "Welcome to the interactive command loop. Type help or ? to list commands.\n"
prompt = "(spotify) " prompt = "(spotify) "
def do_link_patterns(self, arg): def do_list_stat_vs_types(self, arg):
"""prints available linking patterns""" """prints available linking patterns"""
print(f"0 - fuzzy tags") print(f"0 - fuzzy tags")
print(f"1 - spotify") print(f"1 - spotify")
print(f"2 - fuzzy") print(f"2 - fuzzy")
def do_link_pattern(self, arg): def do_stat_local_library_vs_spotify(self, arg):
if not len(arg.split()): """compares local library and remote with the link pattern generated by the generate_pattern_command"""
print("expected name of the pattern") print_link_stats(link_pattern)
return
"""prints available linking patterns"""
pattern_id = int(arg.split()[0])
print_link_stats(link_patterns[pattern_id])
def do_playlists(self, arg): def do_playlists(self, arg):
"""prints user playlists""" """prints user playlists"""
@ -168,6 +162,24 @@ class Interpreter(cmd.Cmd):
"""Print tracks""" """Print tracks"""
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): def do_fetch(self, arg):
"""Fetch all spotify data""" """Fetch all spotify data"""
try: try:
@ -184,6 +196,31 @@ class Interpreter(cmd.Cmd):
except ValueError: except ValueError:
print("Can not fetch the data.") 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): def do_exit(self, arg):
"""Exit the command loop: exit""" """Exit the command loop: exit"""
print("Goodbye!") print("Goodbye!")