My music library. see library.tsv and any folder for playlists. synced from spotify via ugly autohotkey scripts
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 179 lines 5.3 kB view raw
1# /// script 2# requires-python = ">=3.12" 3# dependencies = [ 4# "rich", 5# "spotipy", 6# "PyYAML", 7# ] 8# /// 9 10#!/usr/bin/env python3 11 12from typing import Literal 13from spotipy import Spotify, SpotifyOAuth, MemoryCacheHandler 14from subprocess import run 15from pathlib import Path 16import re 17import sys 18import json 19import yaml 20from rich import print 21from rich.console import Console 22from rich.table import Table 23 24here = Path(__file__).parent 25 26tokens = json.loads((here / "secrets.json").read_text()) 27 28gitignore = Path(".gitignore") 29 30# ensure secrets.json is gitignored 31if not gitignore.exists() or "\nsecrets.json\n" not in gitignore.read_text(): 32 gitignore.write_text( 33 (gitignore.read_text() if gitignore.exists() else "") + "\nsecrets.json\n", 34 encoding="utf8", 35 ) 36 37 38# Initial setup 39spotify = Spotify( 40 auth_manager=SpotifyOAuth( 41 scope=["user-follow-modify", "user-library-read"], 42 client_id=tokens["id"], 43 client_secret=tokens["secret"], 44 redirect_uri="http://localhost:8080", 45 cache_handler=MemoryCacheHandler() 46 ) 47) 48 49 50def sync_tsv_file(results: dict[Literal["items"], list], target: Path): 51 # Fix quoting 52 def fix_quoting(tracks): 53 return {re.sub(r'"([^"]+)"', r"\1”", track) for track in tracks} 54 55 if not target.exists(): 56 print(f"⋆𐙚₊˚⊹♡ Creating [bold][magenta]{target}[reset] ⋆౨ৎ˚⟡˖ ࣪") 57 target.write_text("Artist\tTitle\n", encoding="utf8") 58 59 # Get whole library 60 lib = list(target.read_text("utf8").splitlines()) 61 tracks, header = set(lib[1:]), lib[0] 62 tracks = fix_quoting(tracks) 63 64 # Boil them down to (artists, title, album) 65 new_tracks = ( 66 fix_quoting( 67 { 68 "\t".join( 69 [ 70 ", ".join(a["name"] for a in t["track"].get("artists", [])), 71 t["track"].get("name", None), 72 # t["track"]["album"]["name"], 73 ] 74 ) 75 for t in results["items"] 76 } 77 ) 78 - tracks 79 ) 80 81 if new_tracks: 82 print( 83 f"⋆𐙚₊˚⊹♡ I got [bold][cyan]{len(new_tracks)}[reset] new tracks for ya in [bold][magenta]{target}[reset] 💖 ⋆౨ৎ˚⟡˖ ࣪" 84 ) 85 86 table = Table.grid(padding=(0, 2)) 87 table.add_column(style="bold dim") 88 table.add_column() 89 for new_track in new_tracks: 90 artist, title = new_track.split("\t") 91 table.add_row(artist, title) 92 Console().print(table) 93 else: 94 print( 95 f"⋆𐙚₊˚⊹♡ Nyathing new to add to [magenta][bold]{target}[reset]. Go listen to sum new music :3 ⋆౨ৎ˚⟡˖ ࣪" 96 ) 97 return 98 99 print("") 100 101 # Add our tracks 102 tracks |= new_tracks 103 # Sort 104 tracks = list(tracks) 105 tracks.sort() 106 # Write back library 107 target.write_text("\n".join([header] + tracks), encoding="utf8") 108 109 run(["git", "add", target], capture_output=True) 110 111 112# Get playlists defined on Spotify by user 113playlists_resp = spotify.current_user_playlists() 114playlists = playlists_resp["items"] 115while playlists_resp["next"]: 116 playlists_resp = spotify.next(playlists_resp) 117 playlists.extend(playlists_resp["items"]) 118 119# Store IDs of playlists we have to autocreate 120autocreate_playlists = set( 121 [ 122 playlist["external_urls"]["spotify"] 123 for playlist in playlists 124 if playlist["owner"]["id"] == spotify.current_user()["id"] 125 ] 126) 127 128 129# Get tracks from API 130results = spotify.current_user_saved_tracks() 131 132sync_tsv_file(results, here / "library.tsv") 133 134for playlist_definition_file in here.glob("**/autofill.yaml"): 135 definition = yaml.safe_load(playlist_definition_file.read_text()) 136 if not definition.get("from", "").startswith("https://open.spotify.com/playlist/"): 137 continue 138 139 autocreate_playlists.discard(definition["from"]) 140 141 results = spotify.playlist_tracks( 142 definition["from"], 143 limit=100, 144 ) 145 tracks = results["items"] 146 get_all = not Path("tracklist.tsv").exists() 147 get_all = True 148 while get_all and results["next"]: 149 results = spotify.next(results) 150 tracks.extend(results["items"]) 151 152 sync_tsv_file( 153 {"items": tracks}, 154 playlist_definition_file.parent / "tracklist.tsv", 155 ) 156 157# Create playlists we have to create 158for spotifyurl in autocreate_playlists: 159 name = next( 160 playlist["name"] 161 for playlist in playlists 162 if playlist["external_urls"]["spotify"] == spotifyurl 163 ) 164 print(f"⋆𐙚₊˚⊹♡ Creating playlist [bold][magenta]{name}[reset] ⋆౨ৎ˚⟡˖ ࣪") 165 try: 166 Path(here, name).mkdir(exist_ok=True, parents=True) 167 Path(here, name, "autofill.yaml").write_text( 168 f"from: {spotifyurl}", encoding="utf8" 169 ) 170 run(["git", "add", str(Path(here, name))], capture_output=True) 171 except Exception as e: 172 print(f"\tCouldn't create playlist: {e}") 173 174 175# Git add commti and push 176print("⋆𐙚₊˚⊹♡ Beaming up to github ⋆౨ৎ˚⟡˖ ࣪") 177run(["git", "commit", "-m", "update"], capture_output=True) 178run(["git", "pull", "--autostash"], capture_output=True) 179run(["git", "push"], capture_output=True)