from PIL import Image, ImageDraw, ImageFont import textwrap import time import requests from io import BytesIO import warnings from datetime import datetime import yaml from typing import Dict from lib import LCD_2inch from api import Api def parse_config(path="spotiplayer_pi/config.yaml"): with open(path, "r") as file: data = yaml.safe_load(file) for key, value in data["color_theme"].items(): data["color_theme"][key] = tuple(value) return data def display_loop(api: Api, cfg: Dict): try: d = LCD_2inch() d.Init() d.clear() bg = Image.new("RGB", (d.height, d.width), (0, 0, 0)) d.ShowImage(bg) #Font0 = ImageFont.truetype("Font/Font00.ttf", 14) #Font1 = ImageFont.truetype("Font/Font00.ttf", 18) #Font2 = ImageFont.truetype("Font/Font00.ttf", 36) Font0 = ImageFont.truetype("Font/GothamLight.ttf", 14) Font1 = ImageFont.truetype("Font/GothamLight.ttf", 18) Font2 = ImageFont.truetype("Font/GothamLight.ttf", 36) unavailable_img = Image.open("imgs/unavailable.jpg") error_img = Image.new("RGB", (240, 320), color=(255, 0, 0)) draw = ImageDraw.Draw(error_img) draw.text((150, 150), "Error", font=Font1, fill=cfg["color_theme"]["text"]) del draw spoti_logo = Image.open("imgs/logo128.jpg") qr = Image.open("imgs/qr.jpg") spoti_logo = spoti_logo.resize((128, 128)) not_playing_img = Image.new("RGB", (320, 240), (0, 0, 0)) not_playing_img.paste(spoti_logo, (0, 10)) not_playing_img.paste(qr, (120, 40)) draw = ImageDraw.Draw(not_playing_img) draw.text( (124, 10), "Connect to speakers", font=Font1, fill=cfg["color_theme"]["text"], ) del spoti_logo, qr, draw last_api_call = 0 last_auth_refresh = 0 current_mode = None last_track = None data = None auth_interval = 0 while True: if time.time() - last_auth_refresh >= auth_interval: auth_interval = api.refreshAuth() - 120 last_auth_refresh = time.time() print(f"Refreshed auth at {datetime.now().strftime('%d-%m %H:%M:%S')}") if time.time() - last_api_call >= cfg["api_interval"]: data = api.getPlaying() last_api_call = time.time() if data == None: warnings.warn("No data found") d.ShowImage(error_img) elif data == "not-playing": draw = ImageDraw.Draw(not_playing_img) current_time = datetime.now().time() draw.rectangle([(00, 120), (119, 170)], fill=(0, 0, 0)) draw.text( (20, 120), f"{current_time.hour}:{current_time.minute:02d}", font=Font2, angle=0, ) if current_mode != 0: current_mode = 0 print("Standby mode") d.ShowImage(not_playing_img) time.sleep(cfg["api_interval"]) elif type(data) == dict: current_mode = 1 current_track = data["track"] + data["artists"][0] + data["album"] if current_track != last_track: print("updating track") last_track = current_track img = None try: img = requests.get( data["img_url"], timeout=cfg["refresh_interval"] / 2 ) img = Image.open(BytesIO(img.content)) except Exception as e: warnings.warn( f"Failed to fetch album cover at: {data['img_url']}\n{e}" ) if img == None: img = unavailable_img img = img.resize((128, 128)) bg = Image.new("RGB", (320, 240), cfg["color_theme"]["background"]) bg.paste(img, (10, 30)) draw = ImageDraw.Draw(bg) draw.text( (150, 28), "\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)), font=Font1, fill=cfg["color_theme"]["text"], ) draw.text( (10, 160), "\n".join(textwrap.wrap(data["track"], width=32)), font=Font1, fill=cfg["color_theme"]["text"], ) w, h = bg.size w -= 92 progress_time = data["progress_ms"] + int( (time.time() - last_api_call) * 1000 ) progress = progress_time / data["duration_ms"] bar_width = int(w * progress) draw.rectangle( [(8, h - 22), (w + 2, h - 8)], outline=cfg["color_theme"]["bar_outline"], ) draw.rectangle( [(10, h - 20), (w, h - 10)], fill=cfg["color_theme"]["background"] ) draw.rectangle( [(10, h - 20), (bar_width, h - 10)], fill=cfg["color_theme"]["bar_inside"], ) f_current_time = "{:02.0f}:{:02.0f}".format( *divmod(progress_time // 1000, 60) ) f_total_time = "{:02d}:{:02d}".format( *divmod(data["duration_ms"] // 1000, 60) ) draw.rectangle( [(234, 215), (310, 235)], fill=cfg["color_theme"]["background"] ) draw.text( (235, 215), f"{f_current_time}/{f_total_time}", font=Font0, fill=cfg["color_theme"]["text"], ) d.ShowImage(bg) time.sleep(cfg["refresh_interval"]) except IOError as e: raise e except KeyboardInterrupt: d.module_exit() if __name__ == "__main__": cfg = parse_config() api = Api(cfg["refresh_token"], cfg["base_64"]) display_loop(api, cfg)