import spotipy from spotipy.oauth2 import SpotifyOAuth import tkinter as ttk from tkinter import ttk as tk import sv_ttk import requests import os import syncedlyrics from PIL import Image, ImageTk, ImageDraw, ImageFilter from io import BytesIO import math from time import sleep, time import threading import queue import numpy from sys import exit import _canvas as SpotifyCanvas from _tkvideoplayer import TkinterVideo from gc import collect as gc # SpotifyGUI - Made by Brandon Brunson # import speech_recognition as sr if os.name == 'posix': client_id = "df61ecadf09941eb87e693d37f3ad178" client_secret = "ba97992d614e48d6b0d023998b2957cb" os.system("xset -display :0 s 86400") if os.path.isfile("updated.cfg"): print("NEW VER") os.remove("updated.cfg") sleep(5) else: client_id = "69b82a34d0fb40be80b020eae8e80f25" client_secret = "455575b0e3db44acbbfaa0c419bc3c10" q = queue.Queue() # import canvas # development client id and secret (SpotifyGUI) # client_id = "69b82a34d0fb40be80b020eae8e80f25" # client_secret = "455575b0e3db44acbbfaa0c419bc3c10" redirect_uri = "http://127.0.0.1:8888/callback" # embeeded client id and secret (SpotifyGUI 2) # client_id = "df61ecadf09941eb87e693d37f3ad178" # client_secret = "ba97992d614e48d6b0d023998b2957cb" # misc client id and secret (SpotifyGUI 3) # client_id = "21ca0dc87c9441ceaf875a05d0199756" # client_secret = "dd430e634ae94471aa70dfc22936be10" # Set the user's Spotify username username = "thebrandon45" password = "Mariposa2502$" # os.environ["DISPLAY"] = ":0" # def setup(): # checkDisplay() # def checkDisplay(): # while True: # if os.name == 'posix': # if "DISPLAY" in os.environ: # break # else: # sleep(0.25) # pass # else: # break # Get the user's Spotify authorization token scope = "user-read-playback-state,user-modify-playback-state,user-library-read,user-library-modify" oauth = SpotifyOAuth(client_id=client_id, client_secret=client_secret, redirect_uri=redirect_uri, scope=scope, requests_timeout=30) # Create a Spotify object with the user's authorization token spotify = spotipy.Spotify(auth_manager=oauth) # access_token = SpotifyOAuth.get_access_token(spotify) # print(SpotifyOAuth.refresh_access_token(refresh_token=access_token)) bg_color = "#000000" count = 0 # wait_time = 6500 # hotword = "magical" # sleep = False # Create the tkinter window root = ttk.Tk() root.title("Media Controller") root.geometry("1280x400") root.attributes("-topmost", True) if os.name == 'posix': root.overrideredirect(1) sv_ttk.use_dark_theme() # Function to call the Spotify API to play the current track def controlPlay(): spotify.start_playback() # Function to call the Spotify API to pause the current track def controlPause(): spotify.pause_playback() def controlNext(): spotify.next_track() def controlPrevious(): if track_progress < 5000: spotify.previous_track() else: spotify.seek_track(0) # def sleep(): # while not sleep: # sleep(900000) # kill(kill=sleep) # development purposes def kill(kill): if kill is sleep: exit(1) def likeSong(): if spotify.current_user_saved_tracks_contains(tracks=[(spotify.current_playback()["item"]["id"])])[0] is False: spotify.current_user_saved_tracks_add(tracks=[(spotify.current_playback()["item"]["id"])]) if isBright is True: play_button.config(image=play_heart_img_black) pause_button.config(image=pause_heart_img_black) else: play_button.config(image=play_heart_img) pause_button.config(image=pause_heart_img) else: spotify.current_user_saved_tracks_delete(tracks=[(spotify.current_playback()["item"]["id"])]) if isBright is True: play_button.config(image=play_img_black) pause_button.config(image=pause_img_black) else: play_button.config(image=play_img) pause_button.config(image=pause_img) def colorUI(track_id, dominant_color): if spotify.current_user_saved_tracks_contains(tracks=[track_id])[0] is True: liked_song = True else: liked_song = False if math.sqrt(0.299 * (dominant_color[0] ** 2) + 0.587 * (dominant_color[1] ** 2) + 0.114 * (dominant_color[2] ** 2)) > 170: is_bright = True if liked_song is True: play_button.config(image=play_heart_img_black) pause_button.config(image=pause_heart_img_black) else: play_button.config(image=play_img_black) pause_button.config(image=pause_img_black) song_label.config(foreground="black") artist_label.config(foreground="black") device_name_label.config(foreground="black") lyrics_label.config(foreground="black") next_button.config(image=next_img_black) previous_button.config(image=previous_img_black) else: is_bright = False if liked_song is True: play_button.config(image=play_heart_img) pause_button.config(image=pause_heart_img) else: play_button.config(image=play_img) pause_button.config(image=pause_img) song_label.config(foreground="white") artist_label.config(foreground="white") device_name_label.config(foreground="white") lyrics_label.config(foreground="white") next_button.config(image=next_img) previous_button.config(image=previous_img) return is_bright def start_playback_on_device(): # global count # global wait_time # count = 0 # wait_time = 6500 device_selections = devices_list.curselection() list_of_devices = spotify.devices() device_id = list_of_devices["devices"][device_selections[0]]["id"] spotify.transfer_playback(device_id=device_id) def get_devices(): global count # global count # global wait_time0 # count +=1 # if count < 69: # wait_time = 6500 # elif count >= 69: # wait_time = 3600000 # count += 1 list_of_devices = spotify.devices() # if list_of_devices == "{'devices': []}": # unloadDevices_list() # loadSearching_Devices() # root.after(1000, get_devices) # else: # if count > 210: # unloadDevices_list() # loadSleep() # root.after(3600, get_devices) if spotify.current_playback() != None: count = 0 # unloadSearching_Devices() unloadDevices_list() loadNow_playing() root.after(800, update_song_label) else: count += 1 if count > 420: kill(kill=sleep) # unloadSearching_Devices() # loadDevices_list() else: devices_list.delete(0, ttk.END) list_of_devices = spotify.devices() for num_of_device, garbage in enumerate(list_of_devices["devices"]): # exec(f'dev_{num_of_device} = tk.Button(root, text=list_of_devices["devices"][num_of_device]["name"], command=start_playback_on_device(device_id=num_of_device))') # exec(f'dev_{num_of_device}.grid(row={num_of_device}, column=1)') devices_list.insert(num_of_device, list_of_devices["devices"][num_of_device]["name"]) root.after(8500, get_devices) # def wakeup(): # global count # count = 0 # def wakeup(): # global count # global wait_time # count = 0 # wait_time = 6500 def addCorners(im, rad): circle = Image.new('L', (rad * 2, rad * 2), 0) draw = ImageDraw.Draw(circle) draw.ellipse((0, 0, rad * 2 - 1, rad * 2 - 1), fill=255) alpha = Image.new('L', im.size, 255) w, h = im.size alpha.paste(circle.crop((0, 0, rad, rad)), (0, 0)) alpha.paste(circle.crop((0, rad, rad, rad * 2)), (0, h - rad)) alpha.paste(circle.crop((rad, 0, rad * 2, rad)), (w - rad, 0)) alpha.paste(circle.crop((rad, rad, rad * 2, rad * 2)), (w - rad, h - rad)) im.putalpha(alpha) return im # def addDropShadow( image, offset=(5,5), background=0xffffff, shadow=0x444444, # border=8, iterations=3): # """ # Add a gaussian blur drop shadow to an image. # image - The image to overlay on top of the shadow. # offset - Offset of the shadow from the image as an (x,y) tuple. Can be # positive or negative. # background - Background colour behind the image. # shadow - Shadow colour (darkness). # border - Width of the border around the image. This must be wide # enough to account for the blurring of the shadow. # iterations - Number of times to apply the filter. More iterations # produce a more blurred shadow, but increase processing time. # """ # # Create the backdrop image -- a box in the background colour with a # # shadow on it. # totalWidth = image.size[0] + abs(offset[0]) + 2*border # totalHeight = image.size[1] + abs(offset[1]) + 2*border # back = Image.new(image.mode, (totalWidth, totalHeight), background) # # Place the shadow, taking into account the offset from the image # shadowLeft = border + max(offset[0], 0) # shadowTop = border + max(offset[1], 0) # back.paste(shadow, [shadowLeft, shadowTop, shadowLeft + image.size[0], # shadowTop + image.size[1]] ) # # Apply the filter to blur the edges of the shadow. Since a small kernel # # is used, the filter must be applied repeatedly to get a decent blur. # n = 0 # while n < iterations: # back = back.filter(ImageFilter.BLUR) # n += 1 # # Paste the input image onto the shadow backdrop # imageLeft = border - min(offset[0], 0) # imageTop = border - min(offset[1], 0) # back.paste(image, (imageLeft, imageTop)) # return back # def get_colors(image_file, resize=150): # # Resize image to speed up processing # image_file.thumbnail((resize, resize)) # max_count_index = numpy.argmax(numpy.unique(numpy.array(image_file).reshape(-1, numpy.array(image_file).shape[-1]), axis=0, return_counts=True)[1]) # # colors = list() # # for i in range(numcolors): # # palette_index = color_counts[i][1] # # dominant_color = palette[palette_index*3:palette_index*3+3] # # colors.append(tuple(dominant_color)) # return tuple(numpy.unique(numpy.array(image_file).reshape(-1, numpy.array(image_file).shape[-1]), axis=0, return_counts=True)[0][max_count_index]) def get_colors(image_file, resize=150): # Resize image to speed up processing image_file.thumbnail((resize, resize)) # Reduce to palette numpy_array = numpy.array(image_file) pixels = numpy_array.reshape(-1, numpy_array.shape[-1]) color_counts = numpy.unique(pixels, axis=0, return_counts=True) max_count_index = numpy.argmax(color_counts[1]) # colors = list() # for i in range(numcolors): # palette_index = color_counts[i][1] # dominant_color = palette[palette_index*3:palette_index*3+3] # colors.append(tuple(dominant_color)) return tuple(color_counts[0][max_count_index]) # def get_colors(image_file, numcolors=1, resize=150): # # Resize image to speed up processing # image_file.thumbnail((resize, resize)) # # Reduce to palette # paletted = image_file.convert('P', palette=Image.ADAPTIVE, colors=numcolors) # # Find dominant colors # palette = paletted.getpalette() # # color_counts = sorted(paletted.getcolors(), reverse=True) # dominant_color = (palette[0], palette[1], palette[2]) # # colors = list() # # for i in range(numcolors): # # palette_index = color_counts[i][1] # # dominant_color = palette[palette_index*3:palette_index*3+3] # # colors.append(tuple(dominant_color)) # return dominant_color def getLyrics(artist_name, track_name): global lrc lrc = syncedlyrics.search("[" + track_name + "] [" + artist_name + "]") # IT WONT DELETE def getCanvas(track_id): global videoplayer videoplayer.stop() videoplayer.grid_forget() videoplayer.destroy() gc() try: canvas_url = SpotifyCanvas.get_canvas_for_track(SpotifyCanvas.get_access_token(), track_id) except AttributeError: videoplayer = TkinterVideo(album_art_frame, scaled=True, background=bg_color, loop=True) album_art_label.grid(sticky="w") return None else: videoplayer = TkinterVideo(album_art_frame, scaled=True, background=bg_color, loop=True) album_art_label.grid_forget() videoplayer.load(canvas_url) videoplayer.grid(sticky="w") videoplayer.play() videoplayer.bind("", lambda e:likeSong()) # def loop(e): # videoplayer.play() # videoplayer.bind("<>", loop) # def upNext(): # print(spotify.queue()) # up_next_label.config(text="Placeholder") # def rgb_to_hex(r, g, b): # return ('{:X}{:X}{:X}').format(r, g, b) play_img = ttk.PhotoImage(file="icons/play.png") pause_img = ttk.PhotoImage(file="icons/pause.png") play_heart_img = ttk.PhotoImage(file="icons/play-heart.png") pause_heart_img = ttk.PhotoImage(file="icons/pause-heart.png") next_img = ttk.PhotoImage(file="icons/next.png") previous_img = ttk.PhotoImage(file="icons/previous.png") play_img_black = ttk.PhotoImage(file="icons/play-black.png") pause_img_black = ttk.PhotoImage(file="icons/pause-black.png") play_heart_img_black = ttk.PhotoImage(file="icons/play-heart-black.png") pause_heart_img_black = ttk.PhotoImage(file="icons/pause-heart-black.png") next_img_black = ttk.PhotoImage(file="icons/next-black.png") previous_img_black = ttk.PhotoImage(file="icons/previous-black.png") album_art_img = "" frame_artist_song = ttk.Frame(root, width=(1280/3), height=400, bg=bg_color) album_art_frame = ttk.Frame(root) lyrics_label_frame = ttk.Frame(root, width=(1280/3), height=400, bg=bg_color) lyrics_label_frame.grid_propagate(0) sleep_frame = ttk.Frame(root, width=(1280/3), height=400, bg=bg_color) root.grid_rowconfigure(0, weight=1) root.grid_rowconfigure(1, weight=1) root.grid_rowconfigure(2, weight=1) root.grid_columnconfigure(0, weight=1) root.grid_columnconfigure(1, weight=1) root.configure(background=bg_color) lyrics_label_frame.grid_rowconfigure(0, weight=1) lyrics_label_frame.grid_columnconfigure(0, weight=1) # Create the media control buttons and a text label play_button = ttk.Label(root, image=play_img, borderwidth=0) pause_button = ttk.Label(root, image=pause_img, borderwidth=0) next_button = ttk.Label(root, image=next_img, borderwidth=0) previous_button = ttk.Label(root, image=previous_img, borderwidth=0) artist_label = tk.Label(frame_artist_song, text="", font=("Helvetica", 24), wraplength=(1280/3), justify=ttk.CENTER, background=bg_color) song_label = tk.Label(frame_artist_song, text="", font=("Helvetica", 32), wraplength=(1280/3), justify=ttk.CENTER, background=bg_color) get_devices_button = tk.Button(root, text="Get Devices", command=get_devices) start_playback_on_device_button = tk.Button(root, text="Start Playback on Device", command=start_playback_on_device) devices_list = ttk.Listbox(root, selectmode=ttk.SINGLE, font=("Helvetica", 18)) progress_bar = tk.Progressbar(root, orient=ttk.HORIZONTAL, length=1280) searching_for_devices_label = tk.Label(root, text="Searching for Devices...", font=("Helvetica", 24)) device_name_label = tk.Label(frame_artist_song, text="", font=("Helvetica", 12)) lyrics_label = tk.Label(lyrics_label_frame, text="", font=("Helvetica", 32), wraplength=(1280/3), justify=ttk.CENTER, background=bg_color) album_art_label = tk.Label(album_art_frame, image=album_art_img) # up_next_label = tk.Label(frame_artist_song, text="", font=("Helvetica", 12), wraplength=(1280/3), justify=ttk.CENTER, background=bg_color) videoplayer = TkinterVideo(album_art_frame, scaled=True, background=bg_color, loop=True) play_button.bind("", lambda e:controlPlay()) pause_button.bind("", lambda e:controlPause()) next_button.bind("", lambda e:controlNext()) previous_button.bind("", lambda e:controlPrevious()) # previous_button.bind("", lambda e:controlPrevious(double=True)) album_art_label.bind("", lambda e:likeSong()) # development restart script (will push to update.py) song_label.bind("", lambda e:kill()) devices_list.bind("", lambda e:kill()) # sleep_frame.bind("", lambda e:wakeup()) # devices_list.bind("", lambda e:wakeup()) # Function to update the song label with the current track's name def update_song_label(): try: global lrc global album_art_img global isBright global track_progress # Get the current playback information current_playback = spotify.current_playback() # If there is no current playback, set the text of the song label to "No playback" if current_playback is None: unloadNow_playing() loadDevices_list() root.after(200, get_devices) # Update the song label every 1 second elif current_playback.get("item") is not None: track_name = current_playback["item"]["name"] track_progress = current_playback["progress_ms"] playing_status = current_playback["is_playing"] track_progress_min = track_progress//(1000*60)%60 track_progress_sec = (track_progress//1000)%60 if track_name == song_label.cget("text"): track_progress_formatted = ("{}:{:02d}".format(track_progress_min, track_progress_sec)) progress_bar.config(value=track_progress) for line in str(lrc).splitlines(): if track_progress_formatted in line: lyric = line.split("]")[1] lyrics_label.config(text=lyric) # if track_progress < 5000: # threading.Thread(target=upNext).start() root.after(800, update_song_label) else: artist_name = current_playback["item"]["artists"][0]["name"] threading.Thread(target=getLyrics, args=(artist_name, track_name)).start() track_id = current_playback["item"]["id"] threading.Thread(target=getCanvas, args=(track_id,)).start() track_duration = current_playback["item"]["duration_ms"] device_name = current_playback["device"]["name"] album_art_url = current_playback["item"]["album"]["images"][0]["url"] device_name_label.config(text=device_name) song_label.config(text=track_name) artist_label.config(text=artist_name) progress_bar.config(maximum=track_duration) lyrics_label.config(text="") album_art_img_data = requests.get(album_art_url).content album_art_img_open = Image.open(BytesIO(album_art_img_data)) # bg_color_img = album_art_img_open.resize((1,1), resample=0) # bg_color_img_pixel = bg_color_img.getpixel((0,0)) # bg_color_rgb = get_colors(album_art_img_open) dominant_color = get_colors(album_art_img_open) bg_color = "#" + '%02x%02x%02x' % (dominant_color) # print(bg_color) album_art_img_with_corners = addCorners(album_art_img_open, 15) # addDropShadow(album_art_img_with_corners) album_art_img = ImageTk.PhotoImage(album_art_img_with_corners.resize((300,300))) album_art_label.config(image=album_art_img, background=bg_color) root.config(background=bg_color) frame_artist_song.config(background=bg_color) device_name_label.config(background=bg_color) song_label.config(background=bg_color) artist_label.config(background=bg_color) play_button.config(background=bg_color) pause_button.config(background=bg_color) next_button.config(background=bg_color) previous_button.config(background=bg_color) lyrics_label_frame.config(background=bg_color) lyrics_label.config(background=bg_color) isBright = colorUI(track_id, dominant_color) # print(oauth.get_cached_token()["access_token"]) # print(current_playback["item"]["id"]) # canvas_url = canvas.get_canvas_for_track(access_token=oauth.get_cached_token()["access_token"], track_id=current_playback["item"]["id"]) # print(canvas_url) # lrc = q.get() root.after(500, update_song_label) if playing_status == True: play_button.grid_forget() pause_button.grid(row=3, column=1, pady=(0,30)) elif playing_status == False: pause_button.grid_forget() play_button.grid(row=3, column=1, pady=(0,30)) else: pass else: root.after(1000, get_devices) except Exception as e: print(e) root.after(5000, update_song_label) def loadNow_playing(): frame_artist_song.grid(row=0, column=1, rowspan=3, pady=(30,0), sticky="n") device_name_label.grid(row=0, column=1) artist_label.grid(row=2, column=1) song_label.grid(row=1, column=1) # up_next_label.grid(row=3, column=1) previous_button.grid(row=3, column=1, padx=(0,200), pady=(0,30)) play_button.grid(row=3, column=1, pady=(0,30)) next_button.grid(row=3, column=1, padx=(200,0), pady=(0,30)) progress_bar.grid(row=3, column=0, columnspan=3, sticky="wse") album_art_frame.grid(row=0, column=0, rowspan=4) album_art_label.grid(sticky="w") lyrics_label_frame.grid(row=0, column=2, rowspan=4) lyrics_label.grid() def unloadNow_playing(): device_name_label.grid_forget() artist_label.grid_forget() song_label.grid_forget() previous_button.grid_forget() play_button.grid_forget() next_button.grid_forget() progress_bar.grid_forget() lyrics_label.grid_forget() album_art_label.grid_forget() videoplayer.grid_forget() def loadDevices_list(): devices_list.grid(row=1, column=1, pady=10) start_playback_on_device_button.grid(row=0, column=1, ipadx=40, ipady=40) def unloadDevices_list(): devices_list.grid_forget() start_playback_on_device_button.grid_forget() def loadSearching_Devices(): searching_for_devices_label.grid() def unloadSearching_Devices(): searching_for_devices_label.grid_forget() def loadSleep(): sleep_frame.grid() # def recognize(recognizer, audio): # try: # words = r.recognize_sphinx(audio) # print(words) # if ("magical") in words: # if (" play") in words: # controlPlay() # elif (" pause") in words: # controlPause() # elif (" next") in words: # controlNext() # elif (" previous") in words: # controlPrevious() # else: # pass # else: # pass # except sr.RequestError as e: # print("Could not request results; {0}".format(e)) # except sr.UnknownValueError: # print("Speech not understood") # r = sr.Recognizer() # m = sr.Microphone() # with m as source: # r.adjust_for_ambient_noise(source) # print("Ambient Noise Calibration Complete") # r.listen_in_background(m, recognize) # Start updating the song label # setup() # Run the GUI loadNow_playing() update_song_label() root.mainloop()