got GPT to clean this up because i genuienly could not care

This commit is contained in:
WhatDidYouExpect 2025-08-03 16:22:50 +02:00
parent 4a42531632
commit 9ad86bc3eb

134
main.py
View file

@ -1,14 +1,17 @@
import os, json, base64, requests, time, subprocess, sys, concurrent.futures import os, json, base64, requests, time, subprocess, sys, concurrent.futures
from mutagen.easyid3 import EasyID3;from mutagen.mp3 import MP3 from mutagen.easyid3 import EasyID3
from mutagen.mp3 import MP3
from mutagen.id3 import ID3, APIC, error from mutagen.id3 import ID3, APIC, error
CONFIG_FILE="config.json";DOWNLOAD_DIR="downloads" CONFIG_FILE = "config.json"
DOWNLOAD_DIR = "downloads"
SPOTIFY_TRACK_API = "https://api.spotify.com/v1/tracks/{}" SPOTIFY_TRACK_API = "https://api.spotify.com/v1/tracks/{}"
SPOTIFY_PLAYLIST_API = "https://api.spotify.com/v1/playlists/{}/tracks" SPOTIFY_PLAYLIST_API = "https://api.spotify.com/v1/playlists/{}/tracks"
INVIDIOUS_API = None INVIDIOUS_API = None
def load_config(): # opens the config file def load_config(): # opens the config file
with open(CONFIG_FILE) as f:return json.load(f) with open(CONFIG_FILE) as f:
return json.load(f)
def ensure_valid_token(): def ensure_valid_token():
global INVIDIOUS_API global INVIDIOUS_API
@ -21,30 +24,46 @@ def ensure_valid_token():
return c["access_token"] return c["access_token"]
def get_liked_tracks(tok): # gets liked songs i guess def get_liked_tracks(tok): # gets liked songs i guess
l,r=[],0;h={"Authorization":f"Bearer {tok}"} l, r = [], 0
h = {"Authorization": f"Bearer {tok}"}
while 1: while 1:
u = f"https://api.spotify.com/v1/me/tracks?limit=50&offset={r}" u = f"https://api.spotify.com/v1/me/tracks?limit=50&offset={r}"
d = requests.get(u, headers=h) d = requests.get(u, headers=h)
if d.status_code==401:print("nah bro token's dead");break if d.status_code == 401:
j=d.json();i=j.get("items",[]) print("nah bro token's dead")
for it in i:l.append(it["track"]["id"]) break
if len(i)<50:break j = d.json()
r+=50;time.sleep(0.5) i = j.get("items", [])
for it in i:
l.append(it["track"]["id"])
if len(i) < 50:
break
r += 50
time.sleep(0.5)
return l return l
def get_playlist_tracks(tok, pid): # does same thing but like for playlists def get_playlist_tracks(tok, pid): # does same thing but like for playlists
l,r=[],0;h={"Authorization":f"Bearer {tok}"} l, r = [], 0
h = {"Authorization": f"Bearer {tok}"}
while 1: while 1:
u = f"{SPOTIFY_PLAYLIST_API.format(pid)}?limit=100&offset={r}" u = f"{SPOTIFY_PLAYLIST_API.format(pid)}?limit=100&offset={r}"
d = requests.get(u, headers=h) d = requests.get(u, headers=h)
if d.status_code==401:print("nope, bad token");break if d.status_code == 401:
if d.status_code==404:print("what playlist");break print("nope, bad token")
j=d.json();i=j.get("items",[]) break
if d.status_code == 404:
print("what playlist")
break
j = d.json()
i = j.get("items", [])
for t in i: for t in i:
tr = t.get("track") tr = t.get("track")
if tr and tr.get("id"):l.append(tr["id"]) if tr and tr.get("id"):
if len(i)<100:break l.append(tr["id"])
r+=100;time.sleep(0.5) if len(i) < 100:
break
r += 100
time.sleep(0.5)
return l return l
def fetch_metadata(tid, tok): # gets info about a track def fetch_metadata(tid, tok): # gets info about a track
@ -53,32 +72,46 @@ def fetch_metadata(tid,tok): # gets info about a track
def sanitize_filename(n): # removes stuff that windows would cry about def sanitize_filename(n): # removes stuff that windows would cry about
return "".join(c for c in n if c not in r'\/:*?"<>|').strip() return "".join(c for c in n if c not in r'\/:*?"<>|').strip()
def search_invidious(q): # looks up stuff on invidious, yeah def search_invidious(q): # looks up stuff on invidious
try: try:
r=requests.get(INVIDIOUS_API.format(query=q),timeout=10);r.raise_for_status() r = requests.get(INVIDIOUS_API.format(query=q), timeout=10)
r.raise_for_status()
for v in r.json(): for v in r.json():
if v.get("videoId"):return f"https://www.youtube.com/watch?v={v['videoId']}" if v.get("videoId"):
except:print("couldn't search :(");return None return f"https://www.youtube.com/watch?v={v['videoId']}"
except:
print("couldn't search :(")
return None
def download_from_youtube(url, f): # yoink an mp3 def download_from_youtube(url, f): # yoink an mp3
os.makedirs(DOWNLOAD_DIR, exist_ok=True) os.makedirs(DOWNLOAD_DIR, exist_ok=True)
p = os.path.join(DOWNLOAD_DIR, f) p = os.path.join(DOWNLOAD_DIR, f)
if os.path.exists(p):print(f"{f}? already here bro");return True if os.path.exists(p):
c=[];copt=["--cookies","cookies.txt"] if os.path.exists("cookies.txt") else [] print(f"{f}? already here bro")
return True
copt = ["--cookies", "cookies.txt"] if os.path.exists("cookies.txt") else []
cmd = ["yt-dlp", *copt, "-x", "--audio-format", "mp3", "--no-playlist", "--no-live-from-start", "-o", p, url] cmd = ["yt-dlp", *copt, "-x", "--audio-format", "mp3", "--no-playlist", "--no-live-from-start", "-o", p, url]
try: try:
subprocess.run(cmd,check=True);print(f"yanked {f}");return True subprocess.run(cmd, check=True)
except:print(f"nope, didn't work: {f}");return False print(f"yanked {f}")
return True
except:
print(f"nope, didn't work: {f}")
return False
def embed_album_art(path, img): # slap on some pic def embed_album_art(path, img): # slap on some pic
try: try:
i = requests.get(img).content i = requests.get(img).content
a = MP3(path, ID3=ID3) a = MP3(path, ID3=ID3)
try:a.add_tags() try:
except:error() a.add_tags()
except error:
pass
a.tags.add(APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=i)) a.tags.add(APIC(encoding=3, mime='image/jpeg', type=3, desc='Cover', data=i))
a.save();print(f"art slapped on {path}") a.save()
except Exception as e:print(f"eh: {e}") print(f"art slapped on {path}")
except Exception as e:
print(f"eh: {e}")
def tag_mp3_with_spotify_metadata(p, m): # add title n stuff def tag_mp3_with_spotify_metadata(p, m): # add title n stuff
try: try:
@ -87,32 +120,49 @@ def tag_mp3_with_spotify_metadata(p,m): # add title n stuff
a["artist"] = ", ".join(z["name"] for z in m.get("artists", [])) a["artist"] = ", ".join(z["name"] for z in m.get("artists", []))
a["album"] = m.get("album", {}).get("name", "") a["album"] = m.get("album", {}).get("name", "")
a["date"] = m.get("album", {}).get("release_date", "") a["date"] = m.get("album", {}).get("release_date", "")
a.save();print(f"tagged: {p}") a.save()
print(f"tagged: {p}")
i = m.get("album", {}).get("images", []) i = m.get("album", {}).get("images", [])
if i:embed_album_art(p,i[0]["url"]) if i:
except Exception as e:print(f"tag fail on {p}: {e}") embed_album_art(p, i[0]["url"])
except Exception as e:
print(f"tag fail on {p}: {e}")
def process_track(tid, tok): # the whole shabang def process_track(tid, tok): # the whole shabang
m = fetch_metadata(tid, tok) m = fetch_metadata(tid, tok)
if not m:print(f"nah nothing for {tid}");return False if not m:
a=m["artists"][0]["name"];t=m["name"] print(f"nah nothing for {tid}")
s=f"{a} - {t} audio";f=sanitize_filename(f"{a} - {t}.mp3") return False
a = m["artists"][0]["name"]
t = m["name"]
s = f"{a} - {t} audio"
f = sanitize_filename(f"{a} - {t}.mp3")
u = search_invidious(s) u = search_invidious(s)
if not u:print(f"nothing on invidious for {s}");return False if not u:
if not download_from_youtube(u,f):print(f"skipping {f}");return False print(f"nothing on invidious for {s}")
print(f"yoinked {f}");tag_mp3_with_spotify_metadata(os.path.join(DOWNLOAD_DIR,f),m) return False
if not download_from_youtube(u, f):
print(f"skipping {f}")
return False
print(f"yoinked {f}")
tag_mp3_with_spotify_metadata(os.path.join(DOWNLOAD_DIR, f), m)
return True return True
if __name__ == "__main__": if __name__ == "__main__":
p = sys.argv[1] if len(sys.argv) > 1 else None p = sys.argv[1] if len(sys.argv) > 1 else None
c=load_config();t=ensure_valid_token() c = load_config()
t = ensure_valid_token()
if p: if p:
print(f"playlist: {p}");ids=get_playlist_tracks(t,p) print(f"playlist: {p}")
ids = get_playlist_tracks(t, p)
else: else:
print("liked songs");ids=get_liked_tracks(t) print("liked songs")
ids = get_liked_tracks(t)
print(f"{len(ids)} songs found, whew") print(f"{len(ids)} songs found, whew")
with concurrent.futures.ThreadPoolExecutor(max_workers=8) as x: with concurrent.futures.ThreadPoolExecutor(max_workers=8) as x:
fut = {x.submit(process_track, i, t): i for i in ids} fut = {x.submit(process_track, i, t): i for i in ids}
for f in concurrent.futures.as_completed(fut): for f in concurrent.futures.as_completed(fut):
try:f.result() try:
except Exception as e:print(f"welp: {e}") f.result()
except Exception as e:
print(f"welp: {e}")