185 lines
6.8 KiB
Python

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 spotiplayer_pi.lib import LCD_2inch
from spotiplayer_pi.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/GothamMedium.ttf", 13)
Font1 = ImageFont.truetype("Font/GothamMedium.ttf", 18)
Font1b = ImageFont.truetype("Font/GothamBold.ttf", 19)
Font2 = ImageFont.truetype("Font/GothamMedium.ttf", 20)
Font3 = ImageFont.truetype("Font/GothamMedium.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/logo64.jpg")
qr = Image.open("imgs/qr.jpg")
spoti_logo = spoti_logo.resize((64, 64))
not_playing_img = Image.new("RGB", (320, 240), (0, 0, 0))
not_playing_img.paste(spoti_logo, (25, 40))
not_playing_img.paste(qr, (120, 40))
draw = ImageDraw.Draw(not_playing_img)
draw.text(
(112, 15),
"Connect to speakers",
font=Font2,
fill=(255, 255, 255),
# fill=cfg["color_theme"]["text"],
)
del spoti_logo, qr, draw
last_api_call = 0
last_auth_refresh = 0
current_mode = None # 0: not-playing, 1: playing
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))
offset = 10 if current_time.hour < 9 else 0
draw.text(
(10 + offset, 120),
f"{current_time.hour}:{current_time.minute:02d}",
font=Font3,
fill=(255, 255, 255),
# fill=cfg["color_theme"]["text"],
)
draw.text(
(20, 152),
current_time.strftime("%a %d"),
font=Font2,
fill=(255, 255, 255),
# fill=cfg["color_theme"]["text"],
)
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(
(145, 33),
"\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)),
font=Font1,
fill=cfg["color_theme"]["text"],
)
draw.text(
(11, 164),
"\n".join(textwrap.wrap(data["track"], width=28)),
font=Font1b,
fill=cfg["color_theme"]["text"],
)
w, h = bg.size
w -= 95
progress_time = data["progress_ms"] + int(
(time.time() - last_api_call) * 1000
)
progress = min(1, 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(
[(231, 215), (320, 235)], fill=cfg["color_theme"]["background"]
)
draw.text(
(232, 220),
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)