diff --git a/.cache b/.cache index 1651c81..c2e3477 100644 --- a/.cache +++ b/.cache @@ -1 +1 @@ -{"access_token": "BQAlP-N225jrOKxkXWbI3uWIbprdAPRTdpgCbqYXPy4F1ZW5j_50Y5uheK72ndz8aHDMwW93zqIZj0w8lODitTO9pUVCN6OQaKlaPe5DZsLB2MSqi5bn6nXVAfELO76yEqqATSVSXNPqObjbbNzE1BFZJvkEM5pMie2prbadewOtAIPIqogNxsiQXLAvql_gJU6xGm077lIaTyh7pcgM", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "AQAXp9B05jv8qP5hp9cqwkz1nXQ9yTC1KUSJV_6V3LUr0KWA8SxhFHyZXN7tqjlRYQiQTP-43t22DQFDDTwLQRGvRWBcunA7ME_Rj5SnCQJOCpvmcyRRJm6JRwDAFnMkpnU", "scope": "user-library-modify user-library-read user-modify-playback-state user-read-playback-state", "expires_at": 1680996947} \ No newline at end of file +{"access_token": "BQAPv3RjiQEQ5b-JCwQelxSZraGjsil9xLAlyVJfTXqYAiRYpM6tXEbY2KgICsgoG0N8Cal51hGasiPpw4GGILK0f4YcXYzblcmDrWILRzOCfZ5ju0VjByNCEOjVgJWoAQoalmBuXRwNhC30TI7v9oKkB2kmmmTRdWFPvHgSsRdGqZ1UOaqntt-X3arhUFjm6BiJqFim9n8SYfBTJmc", "token_type": "Bearer", "expires_in": 3600, "scope": "user-library-modify user-library-read user-modify-playback-state user-read-playback-state", "expires_at": 1685062757, "refresh_token": "AQAby3zEGc-H8o8zciCRMZm-O6Gj3FAup6Vb0sRrbtiO48VyMTJMzU4DoJ_wrhk8LmOiN8Hvt0Fb_Ag-09XVEDgQe3VUBDD3HdoMk6aZA1n02VLygxQNMNUQgAGw6oUUdI0"} \ No newline at end of file diff --git a/handler.py b/handler.py deleted file mode 100644 index 392267d..0000000 --- a/handler.py +++ /dev/null @@ -1,6 +0,0 @@ -def handler(): - try: - exec(open('spotifycontroller.py').read()) - except: - print("An error has ocurred, reestablishing the application now.") - exec(open('spotifycontroller.py').read()) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index bb035a0..b9c5eb2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,6 @@ requests spotipy sv_ttk syncedlyrics -numpy \ No newline at end of file +numpy +flask +psutil \ No newline at end of file diff --git a/spotifycontroller.py b/spotifycontroller.py index 2c6c89f..f90458f 100644 --- a/spotifycontroller.py +++ b/spotifycontroller.py @@ -9,10 +9,11 @@ import syncedlyrics from PIL import Image, ImageTk, ImageDraw, ImageFilter from io import BytesIO import math -from time import sleep +from time import sleep, time import threading import queue import numpy +from sys import exit # SpotifyGUI - Made by Brandon Brunson @@ -20,7 +21,7 @@ import numpy if os.name == 'posix': client_id = "df61ecadf09941eb87e693d37f3ad178" client_secret = "ba97992d614e48d6b0d023998b2957cb" - os.system("xset -display :0 s 21600") + os.system("xset -display :0 s 1800") if os.path.isfile("updated.cfg"): print("NEW VER") os.remove("updated.cfg") @@ -42,6 +43,10 @@ redirect_uri = "http://127.0.0.1:8888/callback" # 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$" @@ -80,14 +85,15 @@ 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) -root.overrideredirect(1) +if os.name == 'posix': + root.overrideredirect(1) sv_ttk.use_dark_theme() # Function to call the Spotify API to play the current track @@ -102,7 +108,20 @@ def controlNext(): spotify.next_track() def controlPrevious(): - spotify.previous_track() + 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: @@ -171,13 +190,13 @@ def start_playback_on_device(): def get_devices(): global count # global count - # global wait_time + # global wait_time0 # count +=1 # if count < 69: # wait_time = 6500 # elif count >= 69: # wait_time = 3600000 - count += 1 + # count += 1 list_of_devices = spotify.devices() # if list_of_devices == "{'devices': []}": @@ -185,27 +204,32 @@ def get_devices(): # loadSearching_Devices() # root.after(1000, get_devices) # else: - if count > 210: - unloadDevices_list() - loadSleep() - root.after(3600, get_devices) + # 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(200, update_song_label) + root.after(800, update_song_label) else: + count += 1 + if count > 210: + kill(kill=sleep) # 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"]) + else: + 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(8500, get_devices) -def wakeup(): - global count - count = 0 +# def wakeup(): +# global count +# count = 0 # def wakeup(): # global count @@ -322,8 +346,12 @@ def get_colors(image_file, resize=150): def getLyrics(artist_name, track_name): + global lrc lrc = syncedlyrics.search("[" + track_name + "] [" + artist_name + "]") - q.put(lrc) + +# 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) @@ -375,107 +403,123 @@ searching_for_devices_label = tk.Label(root, text="Searching for Devices...", fo 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) 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()) -sleep_frame.bind("", lambda e:wakeup()) + +# 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(): - global lrc - global album_art_img - global isBright - # 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() + 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) - root.after(800, update_song_label) + 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: + start_time = time() + artist_name = current_playback["item"]["artists"][0]["name"] + threading.Thread(target=getLyrics, args=(artist_name, track_name)).start() + track_id = current_playback["item"]["id"] + 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() + print(time() - start_time) + 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: - artist_name = current_playback["item"]["artists"][0]["name"] - threading.Thread(target=getLyrics, args=(artist_name, track_name)).start() - track_id = current_playback["item"]["id"] - 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,20)) - elif playing_status == False: - pause_button.grid_forget() - play_button.grid(row=3, column=1, pady=(0,20)) - else: - pass - else: - root.after(1000, get_devices) + 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) - previous_button.grid(row=3, column=1, padx=(0,200), pady=(0,20)) - play_button.grid(row=3, column=1, pady=(0,20)) - next_button.grid(row=3, column=1, padx=(200,0), pady=(0,20)) + # 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") diff --git a/testing.py b/testing.py new file mode 100644 index 0000000..30b7a53 --- /dev/null +++ b/testing.py @@ -0,0 +1,10 @@ +from pynput import mouse +from functools import partial + +def click(x, y, button, pressed, foo): + return False + +bar = partial(click, foo="bar") + +with mouse.Listener(on_click=bar) as listener: + listener.join() \ No newline at end of file diff --git a/todo.txt b/todo.txt index a81e2ad..927e443 100644 --- a/todo.txt +++ b/todo.txt @@ -81,4 +81,21 @@ make it so the displays turns off automatically after some time (maybe depending make it so update.py that kills the main script after 6 hours and turn the display off, if the displays wakes up then it will restart the main script 04/08/2023: -background of artist image \ No newline at end of file +background of artist image +hold previous button to start song over + +04/10/2023: +if title is held for over 5 seconds then also shutdown the update.py by throwing an exception that the update.py will catch and then kill the update script +also, if you hold it for over 10 seconds then shutdown the pi entirely. all by sending certain sigterms with exit() + +04/13/2023: +update.py modified to handle click (untested), spotify.py functions created. TODO: tie get_devices into sleep & kill functions as those are setup to kill script and handoff to update.py + +04/28/2023: +periodically check the spotify api, if it is playing something then click a button to wake the display + +04/29/2023: +experiment with converting project to flask/js and browser based. + +05/07/2023: +detect previous song change by checking if song is at less than 5 seconds into song, if yes then go to previous song, if not then restart song. \ No newline at end of file diff --git a/update.py b/update.py index 538a0f0..4ad8f07 100644 --- a/update.py +++ b/update.py @@ -4,6 +4,9 @@ from zipfile import ZipFile from io import BytesIO from time import sleep import subprocess +from pynput import mouse +from functools import partial +from os import name, path, remove # while True: @@ -14,6 +17,9 @@ import subprocess # except: # pass +if name == 'nt': + print("Windows detected. Shutting down...") + exit() # try: # print("Checking for updates...") @@ -24,21 +30,38 @@ import subprocess # print("No update available.") # pass +def click(x, y, button, pressed, foo): + return False + +bar = partial(click, foo="bar", button=True, pressed=True) while True: try: urlopen('http://bbrunson.com', timeout=1) print("Connection to server established.") try: + if path.exists('web/update/update.zip'): + print("Update found. Installing...") + with ZipFile('web/update/update.zip') as zipObj: + zipObj.extractall() + remove('web/update/update.zip') + print("Uploaded update successfully installed.") print("Checking for updates...") with ZipFile(BytesIO((urlopen('http://files.bbrunson.com/spotify-gui/update.zip')).read())) as zipObj: zipObj.extractall() - print("Update successfully installed.") + print("Update successfully downloaded and installed.") except urllib.error.HTTPError: print("No update available.") pass + subprocess.Popen(['python3', 'web/web.py']) subprocess.check_call(['python3', 'spotifycontroller.py']) - except: - print("An error has ocurred, checking server connection, checking for update, and then reestablishing the application in 10 seconds.") - sleep(10) + except Exception as e: + if e.args[0] == 1: + print("Program has requested sleep mode.") + with mouse.Listener(on_move=bar, on_click=bar, on_scroll=bar) as listener: + listener.join() + else: + print(e) + print("Restart procedure initiated: Checking server connection, checking for update, and then reestablishing the application in 10 seconds.") + sleep(10) continue \ No newline at end of file diff --git a/web/web.py b/web/web.py new file mode 100644 index 0000000..fb4507a --- /dev/null +++ b/web/web.py @@ -0,0 +1,48 @@ +from flask import Flask, Blueprint, render_template, redirect, url_for, request, flash +from werkzeug.utils import secure_filename +import os +import signal +import psutil + +app = Flask(__name__) +app.config['SECRET_KEY'] = 'spotify-gui' +app.config['UPLOAD_FOLDER'] = 'update' + +@app.route('/', methods=['GET', 'POST']) +def update(): + if request.method == 'POST': + if 'file' not in request.files: + flash('No file part') + return redirect(request.url) + file = request.files['file'] + if file.filename == '': + flash('No selected file') + return redirect(request.url) + if file and file.filename.endswith('.zip'): + file.save('web/update/update.zip') + return redirect(request.url) + return ''' + + Upload new File +

Upload new File

+
+ + +

+
+ +
+ ''' + +@app.route('/restart', methods=['POST']) +def restart(): + for line in os.popen("ps ax | grep " + "spotifycontroller.py" + " | grep -v grep"): + fields = line.split() + pid = fields[0] + os.kill(int(pid), signal.SIGKILL) + exit() + return redirect(url_for('update')) + + +if __name__ == "__main__": + app.run(host='0.0.0.0', port=80) \ No newline at end of file