447 lines
17 KiB
Python
447 lines
17 KiB
Python
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
|
|
import threading
|
|
import queue
|
|
|
|
# SpotifyGUI - Made by Brandon Brunson
|
|
|
|
# import speech_recognition as sr
|
|
|
|
if os.path.isfile("updated.cfg"):
|
|
print("A new update was downloaded and installed. Starting in 5 seconds...")
|
|
os.remove("updated.cfg")
|
|
sleep(5)
|
|
|
|
q = queue.Queue()
|
|
|
|
# import canvas
|
|
|
|
# Set the Spotify app's client ID and client secret
|
|
client_id = "69b82a34d0fb40be80b020eae8e80f25"
|
|
client_secret = "455575b0e3db44acbbfaa0c419bc3c10"
|
|
redirect_uri = "http://127.0.0.1:8888/callback"
|
|
|
|
# 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"
|
|
|
|
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"
|
|
|
|
if os.name == 'posix':
|
|
os.system("xset -display :0 s 21600")
|
|
|
|
# Create the tkinter window
|
|
root = ttk.Tk()
|
|
root.title("Media Controller")
|
|
root.geometry("1280x400")
|
|
root.attributes("-topmost", True)
|
|
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():
|
|
spotify.previous_track()
|
|
|
|
|
|
def start_playback_on_device():
|
|
wakeup()
|
|
device_selections = devices_list.curselection()
|
|
list_of_devices = spotify.devices()
|
|
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 wait_time
|
|
count +=1
|
|
if count < 69:
|
|
wait_time = 6500
|
|
elif count >= 69:
|
|
wait_time = 300000
|
|
elif count >= 138:
|
|
wait_time = 600000
|
|
elif count >= 277:
|
|
wait_time = 3600000
|
|
list_of_devices = spotify.devices()
|
|
unloadNow_playing()
|
|
if list_of_devices == "{'devices': []}":
|
|
unloadDevices_list()
|
|
loadSearching_Devices()
|
|
root.after(1000, get_devices)
|
|
else:
|
|
current_playback = spotify.current_playback()
|
|
if current_playback != None:
|
|
unloadSearching_Devices()
|
|
unloadDevices_list()
|
|
loadNow_playing()
|
|
update_song_label()
|
|
else:
|
|
unloadSearching_Devices()
|
|
loadDevices_list()
|
|
devices_list.delete(0, ttk.END)
|
|
list_of_devices = spotify.devices()
|
|
for num_of_device, garbage in enumerate(list_of_devices["devices"]):
|
|
devices_list.insert(num_of_device, list_of_devices["devices"][num_of_device]["name"])
|
|
root.after(wait_time, get_devices)
|
|
|
|
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, 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):
|
|
lrc = syncedlyrics.search("[" + track_name + "] [" + artist_name + "]")
|
|
q.put(lrc)
|
|
|
|
# def rgb_to_hex(r, g, b):
|
|
# return ('{:X}{:X}{:X}').format(r, g, b)
|
|
|
|
play_img = ttk.PhotoImage(file="icons/play-circle-x2.png")
|
|
pause_img = ttk.PhotoImage(file="icons/pause-circle-x2.png")
|
|
next_img = ttk.PhotoImage(file="icons/skip-next-x2.png")
|
|
previous_img = ttk.PhotoImage(file="icons/skip-previous-x2.png")
|
|
play_img_black = ttk.PhotoImage(file="icons/play-circle-x2-black.png")
|
|
pause_img_black = ttk.PhotoImage(file="icons/pause-circle-x2-black.png")
|
|
next_img_black = ttk.PhotoImage(file="icons/skip-next-x2-black.png")
|
|
previous_img_black = ttk.PhotoImage(file="icons/skip-previous-x2-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, bg=bg_color)
|
|
lyrics_label_frame = ttk.Frame(root, width=(1280/3), height=400, bg=bg_color)
|
|
lyrics_label_frame.grid_propagate(0)
|
|
|
|
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(frame_artist_song, image=play_img, borderwidth=0)
|
|
pause_button = ttk.Label(frame_artist_song, image=pause_img, borderwidth=0)
|
|
next_button = ttk.Label(frame_artist_song, image=next_img, borderwidth=0)
|
|
previous_button = ttk.Label(frame_artist_song, 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)
|
|
|
|
|
|
play_button.bind("<Button-1>", lambda e:controlPlay())
|
|
pause_button.bind("<Button-1>", lambda e:controlPause())
|
|
next_button.bind("<Button-1>", lambda e:controlNext())
|
|
previous_button.bind("<Button-1>", lambda e:controlPrevious())
|
|
devices_list.bind("<Button-1>", lambda e:wakeup())
|
|
|
|
# Function to update the song label with the current track's name
|
|
def update_song_label():
|
|
global lrc
|
|
global album_art_img
|
|
# 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:
|
|
get_devices()
|
|
# Update the song label every 1 second
|
|
else:
|
|
track_name = current_playback["item"]["name"]
|
|
artist_name = current_playback["item"]["artists"][0]["name"]
|
|
track_duration = current_playback["item"]["duration_ms"]
|
|
track_progress = current_playback["progress_ms"]
|
|
playing_status = current_playback["is_playing"]
|
|
device_name = current_playback["device"]["name"]
|
|
album_art_url = current_playback["item"]["album"]["images"][0]["url"]
|
|
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(maximum=track_duration)
|
|
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)
|
|
root.after(800, update_song_label)
|
|
else:
|
|
threading.Thread(target=getLyrics, args=(artist_name, track_name)).start()
|
|
device_name_label.config(text=device_name)
|
|
song_label.config(text=track_name)
|
|
artist_label.config(text=artist_name)
|
|
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)
|
|
bg_color = "#" + '%02x%02x%02x' % (get_colors(album_art_img_open))
|
|
# 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)
|
|
if math.sqrt(0.299 * (get_colors(album_art_img_open)[0] ** 2) + 0.587 * (get_colors(album_art_img_open)[1] ** 2) + 0.114 * (get_colors(album_art_img_open)[2] ** 2)) > 170:
|
|
song_label.config(foreground="black")
|
|
artist_label.config(foreground="black")
|
|
device_name_label.config(foreground="black")
|
|
lyrics_label.config(foreground="black")
|
|
play_button.config(image=play_img_black)
|
|
pause_button.config(image=pause_img_black)
|
|
next_button.config(image=next_img_black)
|
|
previous_button.config(image=previous_img_black)
|
|
else:
|
|
song_label.config(foreground="white")
|
|
artist_label.config(foreground="white")
|
|
device_name_label.config(foreground="white")
|
|
lyrics_label.config(foreground="white")
|
|
play_button.config(image=play_img)
|
|
pause_button.config(image=pause_img)
|
|
next_button.config(image=next_img)
|
|
previous_button.config(image=previous_img)
|
|
# 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=(100,0))
|
|
elif playing_status == False:
|
|
pause_button.grid_forget()
|
|
play_button.grid(row=3, column=1, pady=(100,0))
|
|
else:
|
|
pass
|
|
|
|
def loadNow_playing():
|
|
frame_artist_song.grid(row=0, column=1, rowspan=3, pady=(20,0))
|
|
device_name_label.grid(row=0, column=1)
|
|
artist_label.grid(row=2, column=1)
|
|
song_label.grid(row=1, column=1)
|
|
previous_button.grid(row=3, column=1, padx=(0,200), pady=(100,0))
|
|
play_button.grid(row=3, column=1, pady=(100,0))
|
|
next_button.grid(row=3, column=1, padx=(200,0), pady=(100,0))
|
|
progress_bar.grid(row=3, column=0, columnspan=3)
|
|
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()
|
|
|
|
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 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()
|
|
loadNow_playing()
|
|
update_song_label()
|
|
|
|
# Run the GUI
|
|
root.mainloop() |