diff --git a/downloader.py b/downloader.py index 51a865a..5d98295 100644 --- a/downloader.py +++ b/downloader.py @@ -1,152 +1,212 @@ -from os import path as ospath -from os import remove,rename +from datetime import datetime +from os import path as ospath, remove, rename +from sys import exit +from threading import Thread +from tkinter import messagebox, Toplevel from urllib import request -from mutagen.mp4 import MP4,MP4Cover -from mutagen.id3 import ID3,TIT2,APIC,TALB,TPE1,TPE2,TYER,TRCK + +from mutagen.flac import FLAC, Picture +from mutagen.id3 import APIC, ID3, TALB, TIT2, TPE1, TPE2, TRCK, TYER +from mutagen.mp4 import MP4, MP4Cover from mutagen.wave import WAVE -from mutagen.flac import FLAC,Picture +from mysql.connector import connect +from pydub import AudioSegment from pytube import YouTube +from requests import get from spotipy import Spotify from spotipy.oauth2 import SpotifyClientCredentials from youtube_search import YoutubeSearch -from threading import Thread -from datetime import datetime -from pydub import AudioSegment -from tkinter import messagebox -from mysql.connector import connect from ytmusicapi import YTMusic -from base64 import b64encode -ytm=YTMusic() +__version__ = "v1.67" +__supported_filetypes__ = (".m4a", ".mp3", ".wav", ".flac") + +response = ( + get("https://api.github.com/repos/rickyrorton/spotify-downloader/releases/latest") +).json() +data = response["tag_name"] +if data.lower() != __version__.lower(): + ch = messagebox.askokcancel( + "UPDATE APP", + f"Press OK to update your app to the latest version {data} from the github repository", + ) + if ch: + from webbrowser import open_new_tab + + open_new_tab("https://github.com/rickyrorton/spotify-downloader/releases") + exit() + +ytm = YTMusic() + def checkdb(splink): - db=connect(host='',user='',passwd='',database='') - cur=db.cursor(buffered=True) - cur.execute('Select ytlink from songs where splink like"{}%"'.format(splink)) - data=cur.fetchone() + db = connect( + host="", + user="", + passwd="", + database="", + ) + cur = db.cursor(buffered=True) + cur.execute(f'Select ytlink from songs where splink like"{splink}%"') + data = cur.fetchone() cur.close() db.close() return data + def songnotfound(splink): - db=connect(host='',user='',passwd='',database='') - cur=db.cursor(buffered=True) - cur.execute('insert into notfound values("{}")'.format(splink)) + db = connect( + host="", + user="", + passwd="", + database="", + ) + cur = db.cursor(buffered=True) + cur.execute(f'insert into notfound values("{splink}")') db.commit() cur.close() db.close() -client_credentials_manager = SpotifyClientCredentials(client_id='', client_secret='') + +client_credentials_manager = SpotifyClientCredentials( + client_id="", + client_secret="", +) sp = Spotify(client_credentials_manager=client_credentials_manager) -def remove_sus_characters(name:str): - converted = "".join(i for i in name if i not in ("/", "\\", "?", "%", "*", ":", "|", '"', "<", ">", ".", ",", ";", "=")) + +def remove_sus_characters(name: str): + converted = "".join( + i + for i in name + if i + not in ("/", "\\", "?", "%", "*", ":", "|", '"', "<", ">", ".", ",", ";", "=") + ) return converted -def add_text(scrltxt_obj,text:str): - scrltxt_obj.config(state='normal') - scrltxt_obj.insert('insert',text) - scrltxt_obj.see('end') - scrltxt_obj.config(state='disabled') + +def add_text(scrltxt_obj, text: str): + scrltxt_obj.config(state="normal") + scrltxt_obj.insert("insert", text) + scrltxt_obj.see("end") + scrltxt_obj.config(state="disabled") + def logger(text): - with open('log.txt','a') as f: + with open("log.txt", "a") as f: f.write(text) -def accusearch(results,songlen): + +def accusearch(results, songlen): for i in results: try: - time = datetime.strptime(i['duration'], '%M:%S') - vid_length = time.minute*60+time.second + time = datetime.strptime(i["duration"], "%M:%S") + vid_length = time.minute * 60 + time.second except: - time = datetime.strptime(i['duration'], '%H:%M:%S') - vid_length = time.hour*3600+time.minute*60+time.second - if vid_length >= songlen+5 or vid_length <= songlen-5: + time = datetime.strptime(i["duration"], "%H:%M:%S") + vid_length = time.hour * 3600 + time.minute * 60 + time.second + if vid_length >= songlen + 5 or vid_length <= songlen - 5: pass else: - vid_url = 'http://youtu.be'+ i['url_suffix'].replace('watch?v=', '') + vid_url = "https://youtu.be" + i["url_suffix"].replace("watch?v=", "") break try: return YouTube(vid_url) except: return None -def m4atagger(mp4,m4a,song,path): - rename(mp4,m4a) - iconname=ospath.join(path,remove_sus_characters(song['artists'][0]['name']+'-'+song['name'])+'.jpg') - request.urlretrieve(song['album']['images'][0]['url'],iconname) - tags=MP4(m4a) + +def m4atagger(mp4, m4a, song, path): + rename(mp4, m4a) + iconname = ospath.join( + path, + remove_sus_characters(song["artists"][0]["name"] + "-" + song["name"]) + ".jpg", + ) + request.urlretrieve(song["album"]["images"][0]["url"], iconname) + tags = MP4(m4a) if not tags.tags: tags.add_tags() - tags[u'\xa9nam']=song['name'] - tags[u'\xa9alb']=song['album']['name'] - tags[u'\xa9ART']=', '.join([i['name'] for i in song['artists']]) - tags[u'aART']=', '.join([i['name'] for i in song['album']['artists']]) - tags[u'\xa9day']=song['album']['release_date'][0:4] - tags[u'trkn']=((int(song["track_number"]),int(song['album']['total_tracks'])),) - with open(iconname,'rb') as f: - tags['covr'] = [MP4Cover(f.read(),imageformat=MP4Cover.FORMAT_JPEG)] + tags["\xa9nam"] = song["name"] + tags["\xa9alb"] = song["album"]["name"] + tags["\xa9ART"] = ", ".join([i["name"] for i in song["artists"]]) + tags["aART"] = ", ".join([i["name"] for i in song["album"]["artists"]]) + tags["\xa9day"] = song["album"]["release_date"][0:4] + tags["trkn"] = ((int(song["track_number"]), int(song["album"]["total_tracks"])),) + with open(iconname, "rb") as f: + tags["covr"] = [MP4Cover(f.read(), imageformat=MP4Cover.FORMAT_JPEG)] tags.save() remove(iconname) -def mp3convtagger(mp4,mp3,song,path,bitrate): - iconname=ospath.join(path,remove_sus_characters(song['artists'][0]['name']+'-'+song['name'])+'.jpg') - request.urlretrieve(song['album']['images'][0]['url'],iconname) - convert=AudioSegment.from_file(mp4) - convert.export(mp3,format='mp3',bitrate=bitrate) - tags=ID3(mp3) - tags.add(TIT2(encoding=3, text=[song['name']])) - tags.add(TALB(encoding=3, text=[song['album']['name']])) - tags.add(TPE1(encoding=3, text=[i['name'] for i in song['artists']])) - tags.add(TPE2(encoding=3, text=[i['name'] for i in song['album']['artists']])) - tags.add(TYER(encoding=3, text=[song['album']['release_date'][0:4]])) - tags.add(TRCK(encoding=3, text=[song['track_number']])) + +def mp3convtagger(mp4, mp3, song, path, bitrate): + iconname = ospath.join( + path, + remove_sus_characters(song["artists"][0]["name"] + "-" + song["name"]) + ".jpg", + ) + request.urlretrieve(song["album"]["images"][0]["url"], iconname) + convert = AudioSegment.from_file(mp4) + convert.export(mp3, format="mp3", bitrate=bitrate) + tags = ID3(mp3) + tags.add(TIT2(encoding=3, text=[song["name"]])) + tags.add(TALB(encoding=3, text=[song["album"]["name"]])) + tags.add(TPE1(encoding=3, text=[i["name"] for i in song["artists"]])) + tags.add(TPE2(encoding=3, text=[i["name"] for i in song["album"]["artists"]])) + tags.add(TYER(encoding=3, text=[song["album"]["release_date"][0:4]])) + tags.add(TRCK(encoding=3, text=[song["track_number"]])) with open(iconname, "rb") as f: - tags.add(APIC(encoding=3,mime=u'image/jpeg',type=3, desc=u'Cover',data=f.read())) + tags.add( + APIC(encoding=3, mime="image/jpeg", type=3, desc="Cover", data=f.read()) + ) tags.save(v2_version=3) remove(mp4) remove(iconname) -def wavconvtagger(webm,wav,song,path,bitrate): - iconname=ospath.join(path,remove_sus_characters(song['artists'][0]['name']+'-'+song['name'])+'.jpg') - request.urlretrieve(song['album']['images'][0]['url'],iconname) - convert=AudioSegment.from_file(webm) - convert.export(wav,format='wav',bitrate=bitrate) - tags=WAVE(wav) + +def wavconvtagger(webm, wav, song, path, bitrate): + iconname = ospath.join( + path, + remove_sus_characters(song["artists"][0]["name"] + "-" + song["name"]) + ".jpg", + ) + request.urlretrieve(song["album"]["images"][0]["url"], iconname) + convert = AudioSegment.from_file(webm) + convert.export(wav, format="wav", bitrate=bitrate) + tags = WAVE(wav) tags.add_tags() - tags=tags.tags - tags.add(TIT2(encoding=3, text=[song['name']])) - tags.add(TALB(encoding=3, text=[song['album']['name']])) - tags.add(TPE1(encoding=3, text=[i['name'] for i in song['artists']])) - tags.add(TPE2(encoding=3, text=[i['name'] for i in song['album']['artists']])) - tags.add(TYER(encoding=3, text=[song['album']['release_date'][0:4]])) - tags.add(TRCK(encoding=3, text=[song['track_number']])) + tags = tags.tags + tags.add(TIT2(encoding=3, text=[song["name"]])) + tags.add(TALB(encoding=3, text=[song["album"]["name"]])) + tags.add(TPE1(encoding=3, text=[i["name"] for i in song["artists"]])) + tags.add(TPE2(encoding=3, text=[i["name"] for i in song["album"]["artists"]])) + tags.add(TYER(encoding=3, text=[song["album"]["release_date"][0:4]])) + tags.add(TRCK(encoding=3, text=[song["track_number"]])) with open(iconname, "rb") as f: - tags.add(APIC(encoding=3,mime=u'image/jpeg',type=3, desc=u'Cover',data=f.read())) - tags.save(wav,v2_version=3) + tags.add( + APIC(encoding=3, mime="image/jpeg", type=3, desc="Cover", data=f.read()) + ) + tags.save(wav, v2_version=3) remove(webm) remove(iconname) -def flacconvtagger(webm,flac,song,path,bitrate): - iconname=ospath.join(path,remove_sus_characters(song['artists'][0]['name']+'-'+song['name'])+'.jpg') - request.urlretrieve(song['album']['images'][0]['url'],iconname) - convert=AudioSegment.from_file(webm) - convert.export(flac,format='flac',bitrate=bitrate) - tags=FLAC(flac) - tags['TITLE']=song['name'] - tags['ARTIST']=', '.join([i['name'] for i in song['artists']]) - tags['ALBUMARTIST']=', '.join([i['name'] for i in song['album']['artists']]) - tags['ALBUM']=song['album']['name'] - tags['DATE']=song['album']['release_date'][0:4] - tags['TRACKNUMBER']=str(song['track_number']) + +def flacconvtagger(webm, flac, song, path, bitrate): + iconname = ospath.join( + path, + remove_sus_characters(song["artists"][0]["name"] + "-" + song["name"]) + ".jpg", + ) + request.urlretrieve(song["album"]["images"][0]["url"], iconname) + convert = AudioSegment.from_file(webm) + convert.export(flac, format="flac", bitrate=bitrate) + tags = FLAC(flac) + tags["TITLE"] = song["name"] + tags["ARTIST"] = ", ".join([i["name"] for i in song["artists"]]) + tags["ALBUMARTIST"] = ", ".join([i["name"] for i in song["album"]["artists"]]) + tags["ALBUM"] = song["album"]["name"] + tags["DATE"] = song["album"]["release_date"][0:4] + tags["TRACKNUMBER"] = str(song["track_number"]) image = Picture() image.type = 3 - if iconname.endswith('png'): - mime = 'image/png' - else: - mime = 'image/jpeg' - image.desc = 'front cover' - with open(iconname, 'rb') as f: + image.desc = "front cover" + with open(iconname, "rb") as f: image.data = f.read() tags.add_picture(image) tags.save() @@ -154,275 +214,331 @@ def flacconvtagger(webm,flac,song,path,bitrate): remove(iconname) -def start(dlbut,scrltxt,progress,link:str,path:str,threadno:int,filetype:str,bitrate:str): +def start( + dlbut, + scrltxt, + progress, + link: str, + path: str, + threadno: int, + filetype: str, + bitrate: str, +): global threads global leader - scrltxt.config(state='normal') - scrltxt.delete(1.0,'end') - scrltxt.config(state='disabled') + scrltxt.config(state="normal") + scrltxt.delete(1.0, "end") + scrltxt.config(state="disabled") try: - for t in threads: + for t in threads: t.join() for t in leader: t.join() except: pass - threads=[] - leader=[] + threads = [] + leader = [] - if link.startswith('https://open.spotify.com/track'): - dlbut['state']='disabled' - progress['maximum']=1 + if link.startswith("https://open.spotify.com/track"): + dlbut["state"] = "disabled" + progress["maximum"] = 1 for i in range(1): - t=Thread(target=download_song,args=(link,scrltxt,path,filetype,dlbut,progress,bitrate),daemon=False) + t = Thread( + target=download_song, + args=( + link, + scrltxt, + path, + filetype, + dlbut, + progress, + bitrate, + "Single", + ), + daemon=False, + ) t.start() threads.append(t) - elif link.startswith('https://open.spotify.com/playlist/'): - dlbut['state']='disabled' - playlist=sp.playlist_tracks(link) - name=sp.playlist(link)["name"] - tracks=playlist['items'] - while playlist['next']: - playlist=sp.next(playlist) - tracks.extend(playlist['items']) - progress['maximum']=len(tracks) - add_text(scrltxt,'Downloading playlist \"{}\" with {} songs\n'.format(name,len(tracks))) - lead=True + elif link.startswith("https://open.spotify.com/playlist/"): + dlbut["state"] = "disabled" + playlist = sp.playlist_tracks(link) + name = sp.playlist(link)["name"] + tracks = playlist["items"] + while playlist["next"]: + playlist = sp.next(playlist) + tracks.extend(playlist["items"]) + progress["maximum"] = len(tracks) + add_text(scrltxt, f'Downloading playlist "{name}" with {len(tracks)} songs\n') + lead = True for i in range(threadno): - t=Thread(target=download_playlist,args=(tracks[i::threadno],scrltxt,path,filetype,lead,dlbut,progress,bitrate),daemon=False) + t = Thread( + target=download_playlist, + args=( + tracks[i::threadno], + scrltxt, + path, + filetype, + lead, + dlbut, + progress, + bitrate, + ), + daemon=False, + ) t.start() if not lead: threads.append(t) else: - leader.append(t) - lead=False - - elif link.startswith('https://open.spotify.com/album/'): - dlbut['state']='disabled' - playlist=sp.album(link) - name=playlist['name'] - tracks=playlist['tracks']['items'] - progress['maximum']=len(tracks) + leader.append(t) + lead = False + + elif link.startswith("https://open.spotify.com/album/"): + dlbut["state"] = "disabled" + playlist = sp.album(link) + name = playlist["name"] + tracks = playlist["tracks"]["items"] + progress["maximum"] = len(tracks) for i in tracks: - tracks[tracks.index(i)]=sp.track(i['external_urls']['spotify']) - add_text(scrltxt,'Downloading album \"{}\" with {} songs\n'.format(name,len(tracks))) - lead=True + tracks[tracks.index(i)] = sp.track(i["external_urls"]["spotify"]) + add_text(scrltxt, f'Downloading album "{name}" with {len(tracks)} songs\n') + lead = True for i in range(threadno): - t=Thread(target=download_playlist,args=(tracks[i::threadno],scrltxt,path,filetype,lead,dlbut,progress,bitrate),daemon=False) + t = Thread( + target=download_playlist, + args=( + tracks[i::threadno], + scrltxt, + path, + filetype, + lead, + dlbut, + progress, + bitrate, + ), + daemon=False, + ) t.start() if not lead: threads.append(t) else: - leader.append(t) - lead=False - elif link==None: - messagebox.showerror('No link given','You have not given a valid link,try again but this time dont forget to give a link') + leader.append(t) + lead = False + + elif link == None: + messagebox.showerror( + "No link given", + "You have not given a link, try again but this time dont forget to give a link", + ) else: - messagebox.showerror('Invalid link','You have given an invalid link,try again this time but with a correct link') + messagebox.showerror( + "Invalid link", + "You have given an invalid link,try again this time but with a correct link", + ) -def download_song(link,scrltxt,path,filetype,button,progress,bitrate): - song=sp.track(link) - download_name=remove_sus_characters(song['artists'][0]['name']+'-'+song['name']) - if not (ospath.exists(ospath.join(path,download_name+'.m4a')) or ospath.exists(ospath.join(path,download_name+'.mp3')) or ospath.exists(ospath.join(path,download_name+'.wav')) or ospath.exists(ospath.join(path,download_name+'.flac'))): - try: - data=checkdb(song['external_urls']['spotify']) - except: - data=None - pass - - try: - if data==None: - if data==None: - try: - isrc_code=song['external_ids']['isrc'].replace('-','') - vid_id=ytm.search(isrc_code) - if len(vid_id)==0: - vid_id=ytm.search(song['artists'][0]['name']+' '+song['name'],filter='songs') - for i in vid_id: - spartists=[j['name'].lower() for j in song['artists']] - ytartists=[x['name'].lower() for x in i['artists']] - spname=''.join(i for i in song['name'].lower() if i not in ['-','(',')',' ','/','\\',',']) - ytname=''.join(i for i in i['title'].lower() if i not in ['-','(',')',' ','/','\\',',']) - if any(char in spartists for char in ytartists) and (spname in ytname or ytname in spname): - vid_url = 'http://youtu.be/'+ i['videoId'] - vid=YouTube(vid_url) - break - else: - vid=None - except: - vid==None - - if vid==None: - results = YoutubeSearch(song['artists'][0]['name']+' '+song['name'], max_results=10).to_dict() - spsonglen = int(song['duration_ms']/1000) - vid=accusearch(results=results,songlen=spsonglen) - else: - vid=YouTube(data[0]) - except: - vid=None - if vid != None: - download_name=remove_sus_characters(song['artists'][0]['name']+'-'+song['name']) - mp4path=ospath.join(path,download_name+'.mp4') - webmpath=ospath.join(path,download_name+'.webm') - if filetype=='.m4a': - try: - yt=vid.streams.get_audio_only() - yt.download(path,download_name+'.mp4') - m4apath=ospath.join(path,download_name+'.m4a') - m4atagger(mp4path,m4apath,song,path) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - if filetype=='.mp3': - try: - yt=vid.streams.get_audio_only() - yt.download(path,download_name+'.mp4') - mp3path=ospath.join(path,download_name+'.mp3') - mp3convtagger(mp4path,mp3path,song,path,bitrate) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - if filetype=='.wav': - try: - yt=vid.streams.filter(mime_type='audio/webm').order_by('abr').desc().first() - yt.download(path,download_name+'.webm') - wavpath=ospath.join(path,download_name+'.wav') - wavconvtagger(webmpath,wavpath,song,path,bitrate) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - if filetype=='.flac': - try: - yt=vid.streams.filter(mime_type='audio/webm').order_by('abr').desc().first() - yt.download(path,download_name+'.webm') - flacpath=ospath.join(path,download_name+'.flac') - flacconvtagger(webmpath,flacpath,song,path,bitrate) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - progress['value']=1 - button['state']='normal' - messagebox.showinfo("Song has finished downloading","The song has finished downloading") - progress['value']=0 - +def get_ytVid(song): + try: + data = checkdb(song["external_urls"]["spotify"]) + except: + data = None + + try: + if data == None: + try: + isrc_code = song["external_ids"]["isrc"].replace("-", "") + vid_id = ytm.search(isrc_code) + if len(vid_id) == 0: + vid_id = ytm.search( + song["artists"][0]["name"] + " " + song["name"], + filter="songs", + ) + for i in vid_id: + spartists = [j["name"].lower() for j in song["artists"]] + ytartists = [x["name"].lower() for x in i["artists"]] + spname = "".join( + i + for i in song["name"].lower() + if i not in ["-", "(", ")", " ", "/", "\\", ","] + ) + ytname = "".join( + i + for i in i["title"].lower() + if i not in ["-", "(", ")", " ", "/", "\\", ","] + ) + if any(char in spartists for char in ytartists) and ( + spname in ytname or ytname in spname + ): + vid_url = "https://youtu.be/" + i["videoId"] + vid = YouTube(vid_url) + break + else: + vid = None + except: + vid = None + + if vid == None: + results = YoutubeSearch( + song["artists"][0]["name"] + " " + song["name"], + max_results=10, + ).to_dict() + spsonglen = int(song["duration_ms"] / 1000) + vid = accusearch(results=results, songlen=spsonglen) else: - button['state']='normal' - messagebox.showinfo("Song couldn't be downloaded","The program was not able to find the matching song on youtube \nContact the developers and provide them links to the song on spotify and youtube") + vid = YouTube(data[0]) + return vid + except: + return None + + +def download_song(link, scrltxt, path, filetype, button, progress, bitrate, mode): + song = sp.track(link) if mode == "Single" else link + download_name = remove_sus_characters( + song["artists"][0]["name"] + "-" + song["name"] + ) + + if not ( + any( + ospath.exists(ospath.join(path, download_name + i)) + for i in __supported_filetypes__ + ) + ): + add_text( + scrltxt, + f'Starting download of song - {song["name"]}\n', + ) + vid = get_ytVid(song) + if vid: + mp4path = ospath.join(path, download_name + ".mp4") + webmpath = ospath.join(path, download_name + ".webm") try: + match filetype: + case ".m4a": + yt = vid.streams.get_audio_only() + yt.download(path, download_name + ".mp4") + m4apath = ospath.join(path, download_name + ".m4a") + m4atagger(mp4path, m4apath, song, path) + add_text( + scrltxt, + f"Finished downloading and converting {song['name']}\n", + ) + + case ".mp3": + yt = vid.streams.get_audio_only() + yt.download(path, download_name + ".mp4") + mp3path = ospath.join(path, download_name + ".mp3") + mp3convtagger(mp4path, mp3path, song, path, bitrate) + add_text( + scrltxt, + f"Finished downloading and converting {song['name']}\n", + ) + + case ".wav": + yt = ( + vid.streams.filter(mime_type="audio/webm") + .order_by("abr") + .desc() + .first() + ) + yt.download(path, download_name + ".webm") + wavpath = ospath.join(path, download_name + ".wav") + wavconvtagger(webmpath, wavpath, song, path, bitrate) + add_text( + scrltxt, + f"Finished downloading and converting {song['name']}\n", + ) + + case ".flac": + yt = ( + vid.streams.filter(mime_type="audio/webm") + .order_by("abr") + .desc() + .first() + ) + yt.download(path, download_name + ".webm") + flacpath = ospath.join(path, download_name + ".flac") + flacconvtagger(webmpath, flacpath, song, path, bitrate) + add_text( + scrltxt, + f"Finished downloading and converting {song['name']}\n", + ) + + progress["value"] += 1 + + if mode == "Single": + button["state"] = "normal" + messagebox.showinfo( + "Song has finished downloading", + "The song has finished downloading", + ) + progress["value"] = 0 + + except Exception as e: + messagebox.showerror( + "Error", + f"Oops program couldnt download {song['name']} because of {e}", + ) + else: + add_text( + scrltxt, + f"Couldn't find {song['name']} on youtube report problem to devs\n", + ) + progress["value"] += 1 + if mode == "Single": + messagebox.showinfo( + "Song couldn't be downloaded", + "The program was not able to find the matching song on youtube \nContact the developers and provide them links to the song on spotify and youtube", + ) + try: + logger(f"{song['name']}-{song['external_urls']['spotify']}") songnotfound(link) except: - pass + try: + logger(f"{song['name']}\n") + except: + pass + elif mode == "Single": + button["state"] = "normal" + messagebox.showinfo( + "Skipping download", + f"Skipping download as song - {song['name']} already exists", + ) else: - add_text(scrltxt,'Skipping download as {} already exists\n'.format(song['name'])) - progress['value']+=1 + add_text( + scrltxt, f"Skipping download as song - {song['name']} already exists\n" + ) + progress["value"] += 1 + -def download_playlist(tracks,scrltxt,path,filetype,leader,button,progress,bitrate): +def download_playlist( + tracks, scrltxt, path, filetype, leader, button, progress, bitrate +): for i in tracks: try: - song=i['track'] - except KeyError: - song=i - download_name=remove_sus_characters(song['artists'][0]['name']+'-'+song['name']) - if not (ospath.exists(ospath.join(path,download_name+'.m4a')) or ospath.exists(ospath.join(path,download_name+'.mp3')) or ospath.exists(ospath.join(path,download_name+'.wav')) or ospath.exists(ospath.join(path,download_name+'.flac'))): - try: - data=checkdb(song['external_urls']['spotify']) - except: - data=None - pass - - try: - if data==None: - try: - isrc_code=song['external_ids']['isrc'].replace('-','') - vid_id=ytm.search(isrc_code) - if len(vid_id)==0: - vid_id=ytm.search(song['artists'][0]['name']+' '+song['name'],filter='songs') - for i in vid_id: - spartists=[j['name'].lower() for j in song['artists']] - ytartists=[x['name'].lower() for x in i['artists']] - spname=''.join(i for i in song['name'].lower() if i not in ['-','(',')',' ','/','\\',',']) - ytname=''.join(i for i in i['title'].lower() if i not in ['-','(',')',' ','/','\\',',']) - if any(char in spartists for char in ytartists) and (spname in ytname or ytname in spname): - vid_url = 'http://youtu.be/'+ i['videoId'] - vid=YouTube(vid_url) - break - else: - vid=None - except: - vid=None - if vid==None: - results = YoutubeSearch(song['artists'][0]['name']+' '+song['name'], max_results=10).to_dict() - spsonglen = int(song['duration_ms']/1000) - vid=accusearch(results=results,songlen=spsonglen) - else: - vid=YouTube(data[0]) - except Exception as e: - vid=None - - if vid != None: - mp4path=ospath.join(path,download_name+'.mp4') - webmpath=ospath.join(path,download_name+'.webm') - if filetype=='.m4a': - try: - yt=vid.streams.get_audio_only() - yt.download(path,download_name+'.mp4') - m4apath=ospath.join(path,download_name+'.m4a') - m4atagger(mp4path,m4apath,song,path) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - if filetype=='.mp3': - try: - yt=vid.streams.get_audio_only() - yt.download(path,download_name+'.mp4') - mp3path=ospath.join(path,download_name+'.mp3') - mp3convtagger(mp4path,mp3path,song,path,bitrate) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - if filetype=='.wav': - try: - yt=vid.streams.filter(mime_type='audio/webm').order_by('abr').desc().first() - yt.download(path,download_name+'.webm') - wavpath=ospath.join(path,download_name+'.wav') - wavconvtagger(webmpath,wavpath,song,path,bitrate) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - if filetype=='.flac': - try: - yt=vid.streams.filter(mime_type='audio/webm').order_by('abr').desc().first() - yt.download(path,download_name+'.webm') - flacpath=ospath.join(path,download_name+'.flac') - flacconvtagger(webmpath,flacpath,song,path,bitrate) - add_text(scrltxt,'Finished downloading and converting {}\n'.format(song['name'])) - except Exception as e: - messagebox.showerror('Error','Oops program couldnt download {} because of {}'.format(song['name'],e)) - - progress['value']+=1 - else: - add_text(scrltxt,'Couldn\'t find {} on yt report problem to devs\n'.format(song['name'])) - try: - logger('{}-{}\n'.format(song['name'],song['external_urls']['spotify'])) - songnotfound(song['external_urls']['spotify']) - except: - try: - logger('{}\n'.format(song['name'])) - except: - pass - progress['value']+=1 - else: - add_text(scrltxt,'Skipping download as {} already exists\n'.format(song['name'])) - progress['value']+=1 + song = i["track"] + except KeyError: # Keyerror happens when albums are being download so this try except loop is necessary do not remove + song = i + download_song( + song, scrltxt, path, filetype, button, progress, bitrate, "Multiple" + ) + if leader: global threads for i in threads: i.join() - button['state']='normal' - messagebox.showinfo("Songs have finished downloading","All the songs have finished downloading") - progress['value']=0 + button["state"] = "normal" + progress["value"] = progress["maximum"] + messagebox.showinfo( + "Songs have finished downloading", "All the songs have finished downloading" + ) + progress["value"] = 0 + + +def downloadyt(link, filetype, res, location): + pass + + +if __name__ == "__main__": + # Checking for song (debugging?) + vid = get_ytVid(sp.track(input("Enter Spotify Song Link: "))) + print(f"Youtube Link: https://youtu.be/{vid.video_id}") diff --git a/gui.py b/gui.py index f123fc1..78ac8ae 100644 --- a/gui.py +++ b/gui.py @@ -1,175 +1,286 @@ -#importing necessary libraries +# importing necessary libraries +# fmt: off +import os +import sys +from pickle import dump, load from platform import system -from tkinter import Entry, StringVar, Tk,Button,Label,scrolledtext,LabelFrame,CENTER,OptionMenu,Scale, HORIZONTAL,LEFT#tkinter for the user interface -from PIL import Image,ImageTk #Python(PIL) image library for inserting images into the user interface +from tkinter import (CENTER, HORIZONTAL, # tkinter for the user interface + LEFT, Button, Entry, IntVar, Label, LabelFrame, + OptionMenu, Scale, StringVar, Tk, scrolledtext) from tkinter.filedialog import askdirectory from tkinter.ttk import Progressbar -import sys -from os import path as ospath -import downloader from webbrowser import open_new_tab -from pickle import load,dump - -#defining a window -global window -window=Tk() -window.geometry('600x600') -window.resizable(False,False) -window.configure(bg = '#333333') -window.title('Spotify Downloader') - -#Widgets in the window -title=Label(window,text=' SPOTIFY DOWNLOADER',font = ("Arial Bold",18),bg = '#333333', fg = 'white') -title.place(relx=0.5,rely=0.05 ,anchor=CENTER) - -entry_label=Label(window,text='Enter song/playlist link:',font = ("Arial Bold",10),bg = '#333333', fg = 'white') -entry_label.place(relx=0.15,rely=0.12,anchor=CENTER) - -output_label=Label(window,text='OUTPUT:',font = ("Arial Bold",12),bg = '#333333', fg = 'white') -output_label.place(relx=0.5,rely=0.17,anchor=CENTER) - -download_location=Label(window,text='Download location:',font = ("Arial Bold",10),bg = '#333333', fg = 'white',justify=LEFT) -download_location.place(relx=0.5,rely=0.84,anchor=CENTER) - -thread_number=Label(window,text='Thread count:',font = ("Arial Bold",10),bg = '#333333', fg = 'white') -thread_number.place(relx=0.12,rely=0.78,anchor=CENTER) - -filetype=Label(window,text='Filetype:',font = ("Arial Bold",10),bg = '#333333', fg = 'white') -filetype.place(relx=0.46,rely=0.78,anchor=CENTER) - -bitrate=Label(window,text='Bitrate:',font = ("Arial Bold",10),bg = '#333333', fg = 'white') -bitrate.place(relx=0.73,rely=0.78,anchor=CENTER) - -global progress -progress=Progressbar(window,orient = HORIZONTAL,mode = 'determinate',length=100) -progress.place(relx=0.5,rely=0.7,width=400,anchor=CENTER) - -scrolled_cont = LabelFrame(window, font=("Arial Bold", 15), background='#1DB954', foreground='white', borderwidth=5, labelanchor="n") -scrolled_cont.place(relx=0.5,rely=0.43,height=290,width=510,anchor=CENTER) - -global output_box -output_box=scrolledtext.ScrolledText(window, font = ("Arial",10),state='disabled',bg='#3d3d3d',fg='white') -output_box.place(relx=0.5,rely=0.43,height=280,width=500,anchor=CENTER) - -dl_location_button=Button(window,text='Change download folder',fg='#3d3d3d',bg='white',font = ("Arial",14),command=lambda:directrory()) -dl_location_button.place(relx=0.3,rely=0.91,anchor=CENTER) - -global download_button -download_button=Button(window,text='Download songs',fg='#3d3d3d',bg='white',font = ("Arial",14),command=lambda:start_downloader()) -download_button.place(relx=0.7,rely=0.91,anchor=CENTER) - -global filetype_default -filetypes=['.m4a','.mp3','.wav','.flac'] -filetype_default=StringVar() -filetype_default.set('.m4a') -filetype_dropdown=OptionMenu(window,filetype_default,*filetypes) -filetype_dropdown.place(relx=0.52,rely=0.755) - -global bitrate_default -bitrates=['96k','128k','192k','320k'] -bitrate_default=StringVar() -bitrate_default.set('192k') -bitrate_dropdown=OptionMenu(window,bitrate_default,*bitrates) -bitrate_dropdown.place(relx=0.78,rely=0.755) - -global thread_num_set -thread_num_set=Scale(window,from_=1,to=20,tickinterval=19,orient=HORIZONTAL,bg='#333333',fg='white',highlightthickness=0) -thread_num_set.set(4) -thread_num_set.place(relx=0.2,rely=0.73) - -entry_cont = LabelFrame(window, font=("Arial Bold", 15), background='#1DB954', foreground='white', borderwidth=5, labelanchor="n") -entry_cont.place(relx=0.62,rely=0.12,width=394,height=24,anchor=CENTER) - -global playlist_link -playlist_link=Entry(window,bg='#3d3d3d',fg='white') -playlist_link.place(relx=0.62,rely=0.12,width=390,height=20,anchor=CENTER) -playlist_link.bind('',lambda e:start_downloader()) - -def server_invite(): - open_new_tab('https://discord.gg/8pTQAfAAbm') -discord_link=Label(window,text='Click here to contact us on discord if you have any problems',font = ("Arial Bold",10),bg = '#333333', fg = 'white',cursor="hand2") -discord_link.place(relx=0.5,rely=0.97,anchor=CENTER) -discord_link.bind("", lambda e:server_invite()) - -#getting path to the directory -if getattr(sys, 'frozen', False): - application_path = ospath.dirname(sys.executable) -elif __file__: - application_path = ospath.dirname(__file__) - -#setting default download location -global location -if ospath.exists(ospath.join(application_path,'spdconfig.dat')): - f=open(ospath.join(application_path,'spdconfig.dat'),'rb') - dict=load(f) - location=dict['location'] - filetype_default.set(dict['filetype']) - bitrate_default.set(dict['bitrate']) - thread_num_set.set(dict['threads']) -elif system()=='Darwin': - location=ospath.join(ospath.expanduser("~/Music"), "Spotify Downloader/Downloads") -else: - location=ospath.join(application_path,'Downloads').replace('\\','/') -download_location.config(text='Download location:'+str(location)) - -#function for importing pictures into the gui -def image_import(filepath,height,width): - try: - base_path = sys._MEIPASS - except Exception: - base_path = ospath.abspath(".") - try: - image_path=ospath.join(base_path,filepath) - img=Image.open(image_path) - except: - image_path=ospath.join(application_path,filepath) - img=Image.open(image_path) - img=img.resize((height,width), Image.Resampling.BOX) - pic=ImageTk.PhotoImage(img) - return pic - -#The actual images being imported -logo=image_import('img/logo.png',48,48) -title.config(image=logo,compound=LEFT) - -download_logo=image_import('img/dl_logo.png',40,40) -download_button.config(image=download_logo,compound=LEFT) - -#download location related stuff -def directrory(): - global location - location=askdirectory() - if location: - dict={'location':location,'bitrate':bitrate_default.get(),'filetype':filetype_default.get(),'threads':thread_num_set.get()} - f=open(ospath.join(application_path,'spdconfig.dat'),'wb') - dump(dict,f) - f.close() - download_location.config(text='Download location:'+str(location)) + +from PIL import ( # Python Imaging Library(PIL) for inserting images into the user interface + Image, ImageTk) + +import downloader + +# fmt: on + + +class App(Tk): + def __init__(self): + super().__init__() + # Configuring Window + self.geometry("600x600") + self.resizable(False, False) + self.configure(bg="#333333") + self.title("Spotify Downloader") + + # Getting path to the directory + if getattr(sys, "frozen", False): + self.application_path = os.path.dirname(sys.executable) + elif __file__: + self.application_path = os.path.dirname(__file__) + + # Get previous configuration + self.config_data = None + if os.path.exists(os.path.join(self.application_path, "spdconfig.dat")): + with open(os.path.join(self.application_path, "spdconfig.dat"), "rb") as f: + self.config_data = load(f) + + # Setting download location + if self.config_data: + self.location = self.config_data["location"] + elif system() == "Darwin": + self.location = os.path.join( + os.path.expanduser("~/Music"), "Spotify Downloader/Downloads" + ) else: - pass - -def saveconf(*args): - global localtion - dict={'location':location,'bitrate':bitrate_default.get(),'filetype':filetype_default.get(),'threads':thread_num_set.get()} - - f=open(ospath.join(application_path,'spdconfig.dat'),'wb') - dump(dict,f) - f.close() - -filetype_default.trace('w',saveconf) -bitrate_default.trace('w',saveconf) -thread_num_set.config(command=saveconf) -def start_downloader(): - global location - global output_box - global download_button - global window - global progress - link=playlist_link.get() - playlist_link.delete(0,len(link)) - threads=thread_num_set.get() - filetype=filetype_default.get() - bitrate=bitrate_default.get() - downloader.start(dlbut=download_button,link=link,path=location,threadno=threads,filetype=filetype,scrltxt=output_box,progress=progress,bitrate=bitrate) - -window.mainloop() \ No newline at end of file + self.location = os.path.join(self.application_path, "Downloads").replace( + "\\", "/" + ) + + # Widgets in the window + self.logo = self.image_import("img/logo.png", 48, 48) + title = Label( + self, + text=" SPOTIFY DOWNLOADER", + font=("Arial Bold", 18), + image=self.logo, + compound=LEFT, + bg="#333333", + fg="white", + ) + title.place(relx=0.5, rely=0.05, anchor=CENTER) + + entry_label = Label( + self, + text="Enter song/playlist link:", + font=("Arial Bold", 10), + bg="#333333", + fg="white", + ) + entry_label.place(relx=0.15, rely=0.12, anchor=CENTER) + + output_label = Label( + self, text="OUTPUT:", font=("Arial Bold", 12), bg="#333333", fg="white" + ) + output_label.place(relx=0.5, rely=0.17, anchor=CENTER) + + self.location_label = Label( + self, + text="Download location:" + str(self.location), + font=("Arial Bold", 10), + bg="#333333", + fg="white", + justify=LEFT, + ) + self.location_label.place(relx=0.5, rely=0.84, anchor=CENTER) + self.location_label.config(text="Download location:" + str(self.location)) + + thread_number = Label( + self, + text="Thread count:", + font=("Arial Bold", 10), + bg="#333333", + fg="white", + ) + thread_number.place(relx=0.12, rely=0.78, anchor=CENTER) + + filetype = Label( + self, text="Filetype:", font=("Arial Bold", 10), bg="#333333", fg="white" + ) + filetype.place(relx=0.46, rely=0.78, anchor=CENTER) + + bitrate = Label( + self, text="Bitrate:", font=("Arial Bold", 10), bg="#333333", fg="white" + ) + bitrate.place(relx=0.73, rely=0.78, anchor=CENTER) + + self.progress = Progressbar( + self, orient=HORIZONTAL, mode="determinate", length=100 + ) + self.progress.place(relx=0.5, rely=0.7, width=400, anchor=CENTER) + + scrolled_cont = LabelFrame( + self, + font=("Arial Bold", 15), + background="#1DB954", + foreground="white", + borderwidth=5, + labelanchor="n", + ) + scrolled_cont.place(relx=0.5, rely=0.43, height=290, width=510, anchor=CENTER) + + self.output_box = scrolledtext.ScrolledText( + self, font=("Arial", 10), state="disabled", bg="#333333", fg="white" + ) + self.output_box.place(relx=0.5, rely=0.43, height=280, width=500, anchor=CENTER) + + self.download_logo = self.image_import("img/dl_logo.png", 40, 40) + self.download_button = Button( + self, + text="Download songs", + font=("Arial", 14), + image=self.download_logo, + compound=LEFT, + fg="#333333", + bg="white", + command=lambda: self.start_downloader(), + ) + self.download_button.place(relx=0.7, rely=0.91, anchor=CENTER) + + dl_location_button = Button( + self, + text="Change download folder", + font=("Arial", 14), + fg="#333333", + bg="white", + command=lambda: self.directory(), + ) + dl_location_button.place( + relx=0.3, + rely=0.91, + anchor=CENTER, + ) + + filetypes = [".m4a", ".mp3", ".wav", ".flac"] + self.filetype_default = StringVar(value=".m4a") + filetype_dropdown = OptionMenu(self, self.filetype_default, *filetypes) + filetype_dropdown.place(relx=0.52, rely=0.755) + # Save variable value when it is modified + self.filetype_default.trace_add("write", self.saveconf) + + bitrates = ["96k", "128k", "192k", "320k"] + self.bitrate_default = StringVar(value="192k") + bitrate_dropdown = OptionMenu(self, self.bitrate_default, *bitrates) + bitrate_dropdown.place(relx=0.78, rely=0.755) + # Save variable value when it is modified + self.bitrate_default.trace_add("write", self.saveconf) + + self.threads_default = IntVar(value=4) + thread_scale = Scale( + self, + variable=self.threads_default, + from_=1, + to=20, + tickinterval=19, + orient=HORIZONTAL, + bg="#333333", + fg="white", + highlightthickness=0, + ) + thread_scale.place(relx=0.2, rely=0.73) + # Save variable value when it is modified + self.threads_default.trace_add("write", self.saveconf) + + # Setting previous configuration if it exists + if self.config_data: + self.filetype_default.set(self.config_data["filetype"]) + self.bitrate_default.set(self.config_data["bitrate"]) + self.threads_default.set(self.config_data["threads"]) + + entry_cont = LabelFrame( + self, + font=("Arial Bold", 15), + background="#1DB954", + foreground="white", + borderwidth=5, + labelanchor="n", + ) + entry_cont.place(relx=0.62, rely=0.12, width=394, height=24, anchor=CENTER) + + self.playlist_link = Entry(self, bg="#333333", fg="white") + self.playlist_link.place( + relx=0.62, rely=0.12, width=390, height=20, anchor=CENTER + ) + self.playlist_link.bind("", lambda e: self.start_downloader()) + + discord_link = Label( + self, + text="Click here to contact us on discord if you have any problems", + font=("Arial Bold", 10), + bg="#333333", + fg="light blue", + cursor="hand2", + ) + discord_link.place(relx=0.5, rely=0.97, anchor=CENTER) + discord_link.bind("", lambda event: self.server_invite(event)) + + def server_invite(self, event): + open_new_tab("https://discord.gg/8pTQAfAAbm") + event.widget.config(fg="#5c67f6") + + # function for importing pictures into the gui + def image_import(self, filepath, height, width): + try: + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + try: + image_path = os.path.join(base_path, filepath) + img = Image.open(image_path) + except: + image_path = os.path.join(self.application_path, filepath) + img = Image.open(image_path) + img = img.resize((height, width), Image.Resampling.BOX) + pic = ImageTk.PhotoImage(img) + return pic + + # download location related stuff + def directory(self): + location = askdirectory() + if location: + self.location = location + config_data = { + "location": location, + "bitrate": self.bitrate_default.get(), + "filetype": self.filetype_default.get(), + "threads": self.threads_default.get(), + } + with open(os.path.join(self.application_path, "spdconfig.dat"), "wb") as f: + dump(config_data, f) + self.location_label.config(text="Download location:" + str(location)) + + def saveconf(self, *args): + config_data = { + "location": self.location, + "bitrate": self.bitrate_default.get(), + "filetype": self.filetype_default.get(), + "threads": self.threads_default.get(), + } + with open(os.path.join(self.application_path, "spdconfig.dat"), "wb") as f: + dump(config_data, f) + + def start_downloader(self): + link = self.playlist_link.get() + self.playlist_link.delete(0, len(link)) + threads = self.threads_default.get() + filetype = self.filetype_default.get() + bitrate = self.bitrate_default.get() + downloader.start( + dlbut=self.download_button, + link=link, + path=self.location, + threadno=threads, + filetype=filetype, + scrltxt=self.output_box, + progress=self.progress, + bitrate=bitrate, + ) + + +# Starting GUI +App().mainloop()