Compare commits
10 Commits
7ee49b9cf6
...
347c87a154
Author | SHA1 | Date | |
---|---|---|---|
347c87a154 | |||
9128e8ce87 | |||
a9e3bfcf98 | |||
e3a1680c90 | |||
7100dcea8b | |||
a9ea1e4d2a | |||
5bcaa84bfb | |||
3cc831ba72 | |||
0627a480f3 | |||
9765756d7b |
14
spotiplayer.service
Normal file
14
spotiplayer.service
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=Spotiplayer
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/path/to/this/repository
|
||||||
|
ExecStart=/usr/bin/python3 -m spotiplayer_pi.main
|
||||||
|
Restart=on-failure
|
||||||
|
User=root
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
|
@ -39,7 +39,7 @@ class Api:
|
|||||||
return self._format_req(req.json())
|
return self._format_req(req.json())
|
||||||
|
|
||||||
def _format_req(self, r):
|
def _format_req(self, r):
|
||||||
if not r["is_playing"]:
|
if not r["is_playing"] or r["currently_playing_type"] != "track":
|
||||||
return "not-playing"
|
return "not-playing"
|
||||||
item, album = r["item"], r["item"]["album"]
|
item, album = r["item"], r["item"]["album"]
|
||||||
res = {
|
res = {
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
refresh_token: "AQBGNOL-iocPZ_zxtr_sNXx4BZRaxPmg2Ea0cJGorcEu2IW2wSnLdkYDJpVDOuQQHWIQ-LjuNpBcY9PurPtdfPoT5ljXnGpso9CqX6n2alBXaYc767oKya_52qxqWPE0o7k"
|
refresh_token: "AQBGNOL-iocPZ_zxtr_sNXx4BZRaxPmg2Ea0cJGorcEu2IW2wSnLdkYDJpVDOuQQHWIQ-LjuNpBcY9PurPtdfPoT5ljXnGpso9CqX6n2alBXaYc767oKya_52qxqWPE0o7k"
|
||||||
base_64: "ZTI5Mzg3NWI3YWQ5NDQzZDg1NDMxYzkwOTEyZTdiMWQ6MjlmNDEzZTJiOTRlNGU0NDlkOWRiYmU0NmM2OTY5MTY="
|
base_64: "ZTI5Mzg3NWI3YWQ5NDQzZDg1NDMxYzkwOTEyZTdiMWQ6MjlmNDEzZTJiOTRlNGU0NDlkOWRiYmU0NmM2OTY5MTY="
|
||||||
|
|
||||||
api_interval: 5 # (float) number of seconds in-between API requests
|
api_interval: 3 # (float) number of seconds in-between API requests
|
||||||
refresh_interval: 0.4 # (float) number of seconds in-between progress bar refreshes
|
refresh_interval: 0.4 # (float) number of seconds in-between progress bar refreshes
|
||||||
|
|
||||||
# Set Color Theme
|
# Set Color Theme
|
||||||
color_theme:
|
color_theme:
|
||||||
text: [253, 238, 216] # [R, G, B] All text components
|
text: [253, 238, 216] # [R, G, B] All text components and bar outline
|
||||||
bar_outline: [16, 81, 86] # [R, G, B] Bar in-fill
|
bar_inside: [30, 215, 96] # [R, G, B] Bar in-fill
|
||||||
bar_inside: [148, 191, 136] # [R, G, B] Bar in-fill
|
|
||||||
background: [13, 19, 33] # [R, G, B] Background
|
background: [13, 19, 33] # [R, G, B] Background
|
||||||
|
@ -7,11 +7,28 @@ import warnings
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import yaml
|
import yaml
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
import subprocess
|
||||||
|
|
||||||
from spotiplayer_pi.lib import LCD_2inch
|
from spotiplayer_pi.lib import LCD_2inch
|
||||||
from spotiplayer_pi.api import Api
|
from spotiplayer_pi.api import Api
|
||||||
|
|
||||||
|
|
||||||
|
def get_cpu_temp():
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
["vcgencmd", "measure_temp"], capture_output=True, text=True, check=True
|
||||||
|
)
|
||||||
|
temp_str = result.stdout.strip()
|
||||||
|
temp_value = temp_str.split("=")[1].replace("'C", "")
|
||||||
|
return float(temp_value)
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
warnings.warn(f"Error executing vcgencmd: {e}")
|
||||||
|
return "None"
|
||||||
|
except (IndexError, ValueError) as e:
|
||||||
|
warnings.warn(f"Error parsing temperature: {e}")
|
||||||
|
return "None"
|
||||||
|
|
||||||
|
|
||||||
def parse_config(path="spotiplayer_pi/config.yaml"):
|
def parse_config(path="spotiplayer_pi/config.yaml"):
|
||||||
with open(path, "r") as file:
|
with open(path, "r") as file:
|
||||||
data = yaml.safe_load(file)
|
data = yaml.safe_load(file)
|
||||||
@ -28,7 +45,7 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
bg = Image.new("RGB", (d.height, d.width), (0, 0, 0))
|
bg = Image.new("RGB", (d.height, d.width), (0, 0, 0))
|
||||||
d.ShowImage(bg)
|
d.ShowImage(bg)
|
||||||
|
|
||||||
Font0 = ImageFont.truetype("Font/GothamMedium.ttf", 13)
|
Font0 = ImageFont.truetype("Font/GothamBold.ttf", 14)
|
||||||
Font1 = ImageFont.truetype("Font/GothamMedium.ttf", 18)
|
Font1 = ImageFont.truetype("Font/GothamMedium.ttf", 18)
|
||||||
Font1b = ImageFont.truetype("Font/GothamBold.ttf", 19)
|
Font1b = ImageFont.truetype("Font/GothamBold.ttf", 19)
|
||||||
Font2 = ImageFont.truetype("Font/GothamMedium.ttf", 20)
|
Font2 = ImageFont.truetype("Font/GothamMedium.ttf", 20)
|
||||||
@ -52,7 +69,6 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
"Connect to speakers",
|
"Connect to speakers",
|
||||||
font=Font2,
|
font=Font2,
|
||||||
fill=(255, 255, 255),
|
fill=(255, 255, 255),
|
||||||
# fill=cfg["color_theme"]["text"],
|
|
||||||
)
|
)
|
||||||
del spoti_logo, qr, draw
|
del spoti_logo, qr, draw
|
||||||
|
|
||||||
@ -78,22 +94,26 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
|
|
||||||
elif data == "not-playing":
|
elif data == "not-playing":
|
||||||
draw = ImageDraw.Draw(not_playing_img)
|
draw = ImageDraw.Draw(not_playing_img)
|
||||||
current_time = datetime.now().time()
|
current_time = datetime.now()
|
||||||
draw.rectangle([(00, 120), (119, 170)], fill=(0, 0, 0))
|
draw.rectangle([(00, 120), (119, 190)], fill=(0, 0, 0))
|
||||||
offset = 10 if current_time.hour < 9 else 0
|
offset = 10 if current_time.hour < 9 else 0
|
||||||
draw.text(
|
draw.text(
|
||||||
(10 + offset, 120),
|
(10 + offset, 120),
|
||||||
f"{current_time.hour}:{current_time.minute:02d}",
|
f"{current_time.hour}:{current_time.minute:02d}",
|
||||||
font=Font3,
|
font=Font3,
|
||||||
fill=(255, 255, 255),
|
fill=(255, 255, 255),
|
||||||
# fill=cfg["color_theme"]["text"],
|
|
||||||
)
|
)
|
||||||
draw.text(
|
draw.text(
|
||||||
(20, 152),
|
(25, 152),
|
||||||
current_time.strftime("%a %d"),
|
current_time.strftime("%a %d"),
|
||||||
font=Font2,
|
font=Font2,
|
||||||
fill=(255, 255, 255),
|
fill=(255, 255, 255),
|
||||||
# fill=cfg["color_theme"]["text"],
|
)
|
||||||
|
draw.text(
|
||||||
|
(20, 175),
|
||||||
|
f"CPU: {get_cpu_temp():.2f}°",
|
||||||
|
font=Font0,
|
||||||
|
fill=(255, 255, 255),
|
||||||
)
|
)
|
||||||
if current_mode != 0:
|
if current_mode != 0:
|
||||||
current_mode = 0
|
current_mode = 0
|
||||||
@ -105,7 +125,7 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
current_mode = 1
|
current_mode = 1
|
||||||
current_track = data["track"] + data["artists"][0] + data["album"]
|
current_track = data["track"] + data["artists"][0] + data["album"]
|
||||||
if current_track != last_track:
|
if current_track != last_track:
|
||||||
print("updating track")
|
print(f"Updating track to : {data['track']} - {data['artists'][0]}")
|
||||||
last_track = current_track
|
last_track = current_track
|
||||||
img = None
|
img = None
|
||||||
try:
|
try:
|
||||||
@ -124,7 +144,7 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
bg.paste(img, (10, 30))
|
bg.paste(img, (10, 30))
|
||||||
draw = ImageDraw.Draw(bg)
|
draw = ImageDraw.Draw(bg)
|
||||||
draw.text(
|
draw.text(
|
||||||
(145, 33),
|
(145, 34),
|
||||||
"\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)),
|
"\n".join(textwrap.wrap(", ".join(data["artists"]), width=16)),
|
||||||
font=Font1,
|
font=Font1,
|
||||||
fill=cfg["color_theme"]["text"],
|
fill=cfg["color_theme"]["text"],
|
||||||
@ -137,41 +157,41 @@ def display_loop(api: Api, cfg: Dict):
|
|||||||
)
|
)
|
||||||
|
|
||||||
w, h = bg.size
|
w, h = bg.size
|
||||||
w -= 95
|
w -= 90
|
||||||
|
h += 4
|
||||||
progress_time = min(
|
progress_time = min(
|
||||||
data["duration_ms"],
|
data["duration_ms"],
|
||||||
data["progress_ms"] + int((time.time() - last_api_call) * 1000),
|
data["progress_ms"] + int((time.time() - last_api_call) * 1000),
|
||||||
)
|
)
|
||||||
progress = min(1, progress_time / data["duration_ms"])
|
progress = min(1, progress_time / data["duration_ms"])
|
||||||
bar_width = int(w * progress)
|
bar_width = int((w - 10) * progress)
|
||||||
draw.rectangle(
|
draw.rectangle(
|
||||||
[(7, h - 23), (w + 3, h - 7)],
|
[(9, h - 23), (w + 3, h - 7)],
|
||||||
# outline=cfg["color_theme"]["text"],
|
outline=cfg["color_theme"]["text"],
|
||||||
outline=cfg["color_theme"]["bar_outline"],
|
)
|
||||||
|
|
||||||
|
draw.rectangle(
|
||||||
|
[(10, h - 22), (w + 2, h - 8)],
|
||||||
|
outline=cfg["color_theme"]["text"],
|
||||||
)
|
)
|
||||||
draw.rectangle(
|
draw.rectangle(
|
||||||
[(8, h - 22), (w + 2, h - 8)],
|
[(12, h - 20), (w, h - 10)], fill=cfg["color_theme"]["background"]
|
||||||
# outline=cfg["color_theme"]["text"],
|
|
||||||
outline=cfg["color_theme"]["bar_outline"],
|
|
||||||
)
|
)
|
||||||
draw.rectangle(
|
draw.rectangle(
|
||||||
[(10, h - 20), (w, h - 10)], fill=cfg["color_theme"]["background"]
|
[(12, h - 20), (12 + bar_width, h - 10)],
|
||||||
)
|
|
||||||
draw.rectangle(
|
|
||||||
[(10, h - 20), (bar_width, h - 10)],
|
|
||||||
fill=cfg["color_theme"]["bar_inside"],
|
fill=cfg["color_theme"]["bar_inside"],
|
||||||
)
|
)
|
||||||
f_current_time = "{:02.0f}:{:02.0f}".format(
|
f_current_time = "{}:{:02.0f}".format(
|
||||||
*divmod(progress_time // 1000, 60)
|
*divmod(progress_time // 1000, 60)
|
||||||
)
|
)
|
||||||
f_total_time = "{:02d}:{:02d}".format(
|
f_total_time = "{}:{:02d}".format(
|
||||||
*divmod(data["duration_ms"] // 1000, 60)
|
*divmod(data["duration_ms"] // 1000, 60)
|
||||||
)
|
)
|
||||||
draw.rectangle(
|
draw.rectangle(
|
||||||
[(231, 215), (320, 235)], fill=cfg["color_theme"]["background"]
|
[(237, 218), (320, 238)], fill=cfg["color_theme"]["background"]
|
||||||
)
|
)
|
||||||
draw.text(
|
draw.text(
|
||||||
(232, 220),
|
(238, 223),
|
||||||
f"{f_current_time}/{f_total_time}",
|
f"{f_current_time}/{f_total_time}",
|
||||||
font=Font0,
|
font=Font0,
|
||||||
fill=cfg["color_theme"]["text"],
|
fill=cfg["color_theme"]["text"],
|
||||||
|
@ -19,7 +19,7 @@ class TestApp(unittest.TestCase):
|
|||||||
# Check color_theme structure
|
# Check color_theme structure
|
||||||
color_theme = self.cfg["color_theme"]
|
color_theme = self.cfg["color_theme"]
|
||||||
self.assertIsInstance(color_theme, dict)
|
self.assertIsInstance(color_theme, dict)
|
||||||
for key in ["text", "bar_outline", "bar_inside", "background"]:
|
for key in ["text", "bar_inside", "background"]:
|
||||||
self.assertIn(key, color_theme)
|
self.assertIn(key, color_theme)
|
||||||
self.assertIsInstance(color_theme[key], tuple)
|
self.assertIsInstance(color_theme[key], tuple)
|
||||||
self.assertEqual(len(color_theme[key]), 3)
|
self.assertEqual(len(color_theme[key]), 3)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user