Files
spotify-gui/spotifycontroller.py
Brandon4466 4467115135 added notifications
added freepbx integration as notification source
2023-08-11 02:17:41 -07:00

637 lines
24 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, 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)
try:
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)
except:
root.after(5000, 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("<Button-1>", lambda e:likeSong())
# def loop(e):
# videoplayer.play()
# videoplayer.bind("<<Ended>>", 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("<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())
# previous_button.bind("<Double-Button-1>", lambda e:controlPrevious(double=True))
album_art_label.bind("<Button-1>", lambda e:likeSong())
# development restart script (will push to update.py)
song_label.bind("<Double-Button-1>", lambda e:kill())
devices_list.bind("<Double-Button-1>", lambda e:kill())
# sleep_frame.bind("<Button-1>", lambda e:wakeup())
# devices_list.bind("<Button-1>", 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()
pause_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()