How I'm using the Presto screen for my media consumption
I am a Customer support manager who works from home. This means lots of reading and writing reports and emails whilst sat at home. So - to stem the silence I do what most people do and play music during the day. I tend to listen to random playlists or just let play what comes on. Since I play the music through my PC, but work on a laptop supplied by work - I can't see what's one the screen as I share it with my work machine.
So I purchased one of the new Presto displays from Pimoroni and set to work coming up with an idea. Originally I used the Spotify API to get the details to display on the screen. However, this does not always work and restricted me to Spotify. So I wanted to find a way to tap into the "MediaInfo" that is displayed on most widgets on Linux. I found a tool called "playerctrl" and set to work finding out to use this.
Using what I already knew and some help from Co-pilot I was able to get some basic code working within a few hours. So the project currently looks like
- PlayerCTRL installed on my Linux desktop
- Flask Python3 server running on my Linux Desktop
- Microphython running on the Pimoroni Presto showing the now playing details.
Now, whilst the basic code worked, it needed lots of work if i wanted to play media not from the desktop version of Spotify due the way Playerctrl gets the album art. From Spotify I got a resonable sized image in JPEG. But if I played media from YouTube in a browser window, this gave me a smaller PNG file. So I needed to add some Pillow magic to convert the PNG to a JPEG. The problem then was the different sizes meant that the Presto needed it's code changing to scale the two different sized imaged. So I added a new flag to be sent from Flask to the Presto and a new line of code to change the size used.
So - now I have a working prototype that I am happy to demonstrate.
I plan on putting a video together soon showing how to use this code, for now - here is an image to wet your apatite.
Microphython running on the Presto
from presto import Presto
from picovector import ANTIALIAS_BEST, PicoVector, Polygon, Transform
from machine import PWM, Pin
import datetime, time, re, math, ntptime, plasma
from touch import Button
import urequests, jpegdec, gc
def screen_backlight(i):
bl = i / BACKLIGHT_RANGE
print(f"Backlight: {bl}")
presto.set_backlight(bl)
# Setup for the Presto display
presto = Presto(ambient_light=False, full_res=True)
display = presto.display
WIDTH, HEIGHT = display.get_bounds()
CX, CY = WIDTH // 2, HEIGHT // 2
BACKLIGHT_RANGE = 20
screen_backlight(0)
presto.connect()
WHITE, RED, GREEN, BLACK = (
display.create_pen(r, g, b) for r, g, b in [
(255, 255, 255), (230, 60, 45), (9, 185, 120), (0, 0, 0)
]
)
touch = presto.touch
vector = PicoVector(display)
vector.set_antialiasing(ANTIALIAS_BEST)
vector.set_font("Roboto-Medium.af", 54)
vector.set_font_letter_spacing(100)
vector.set_font_word_spacing(100)
vector.set_font_size(32)
vector.set_transform(Transform())
# Buttons
start_button = Button(1, HEIGHT - 50, CX - 2, 49)
skip_button = Button(WIDTH - CX, HEIGHT - 50, CX - 2, 49)
start = Polygon().rectangle(*start_button.bounds, (5, 5, 5, 5))
skip = Polygon().rectangle(*skip_button.bounds, (5, 5, 5, 5))
outline = Polygon()
outline.rectangle(5, 20, WIDTH - 10, HEIGHT - 80, (5, 5, 5, 5), 2)
# Flask Endpoints
# Base URL
BASE_URL = "http://192.168.50.173:5000"
# Flask Endpoints
MEDIA_ENDPOINT = f"{BASE_URL}/now_playing"
IMAGE_ENDPOINT = f"{BASE_URL}/album_art"
TOGGLE_ENDPOINT = f"{BASE_URL}/toggle_play"
def truncate_string(s, max_length=35):
return s[:max_length] if len(s) > max_length else s
# Track previous song to avoid redundant image loads
last_title = None
# --- Main Loop ---
while True:
try:
response = urequests.get(MEDIA_ENDPOINT)
if response.status_code == 200:
data = response.json()
title = data.get("title", "")
artist = data.get("artist", "")
album = data.get("album", "")
status = data.get("status", "")
is_playing = True
art_source = data.get("art_source", "")
else:
title = ""
artist = ""
album = ""
is_playing = False
if status == "Paused":
screen_backlight(0)
time.sleep(1)
continue
# Backlight control
if not is_playing:
screen_backlight(0)
time.sleep(1)
continue
else:
screen_backlight(10)
display.set_pen(BLACK)
display.clear()
display.set_pen(GREEN)
vector.draw(start)
display.set_pen(RED)
vector.draw(skip)
display.set_pen(WHITE)
vector.draw(outline)
# Track + Album text
string1 = truncate_string(artist + " / " + title)
x, y, w, h = vector.measure_text(string1, x=0, y=0, angle=None)
text_x = int(CX - (w // 2))
text_y = int(CY + (h // 2))
text_x_offset = text_x + 2
text_y_offset = text_y + 2
#vector.text(artist_name, text_x, 50)
vector.text(string1, text_x, 45)
string2 = truncate_string(album)
x, y, w, h = vector.measure_text(string2, x=0, y=0, angle=None)
text_x = int(CX - (w // 2))
text_y = int(CY + (h // 2))
text_x_offset = text_x + 2
text_y_offset = text_y + 2
#vector.text(string2, 40, 80)
vector.text(string2, text_x, 80)
# Only decode album art if the track has changed
if is_playing:
try:
img_response = urequests.get(IMAGE_ENDPOINT)
if img_response.status_code == 200:
j = jpegdec.JPEG(display) # Recreate to free memory
j.open_file(img_response.content)
print(j)
img_x = 5 + (470 - 300) // 2
img_y = 20 + (380 - 240) // 2
if art_source == "url":
j.decode(img_x, img_y, jpegdec.JPEG_SCALE_HALF, dither=True)
else:
j.decode(img_x, img_y, jpegdec.JPEG_SCALE_FULL, dither=True)
del j
gc.collect()
img_response.close()
except Exception as e:
print("Image error:", e)
# Button labels
vector.text("Pause", start_button.bounds[0] + 83, start_button.bounds[1] + 33)
vector.text("Skip", skip_button.bounds[0] + 83, skip_button.bounds[1] + 33)
# Toggle playback
touch.poll()
if start_button.is_pressed():
print("toggled play/pause")
try:
urequests.post(TOGGLE_ENDPOINT)
except Exception as e:
print("Toggle error:", e)
presto.update()
gc.collect()
time.sleep(0.1)
except Exception as e:
print("Main loop error:", e)
gc.collect()
Python running on my PC
from flask import Flask, jsonify, send_file
import subprocess
import requests
import os
from urllib.parse import urlparse, unquote
from PIL import Image
import io
app = Flask(__name__)
def get_media_info():
try:
title = subprocess.check_output(['playerctl', 'metadata', 'xesam:title']).decode('utf-8').strip()
artist = subprocess.check_output(['playerctl', 'metadata', 'xesam:artist']).decode('utf-8').strip()
album = subprocess.check_output(['playerctl', 'metadata', 'xesam:album']).decode('utf-8').strip()
art_url = subprocess.check_output(['playerctl', 'metadata', 'mpris:artUrl']).decode('utf-8').strip()
status = subprocess.check_output(['playerctl','status']).decode('utf-8').strip()
art_path = None
art_source = None
if art_url.startswith("http://") or art_url.startswith("https://"):
response = requests.get(art_url)
if response.status_code == 200:
with open("/tmp/album_art.jpg", "wb") as f:
f.write(response.content)
art_path = "/tmp/album_art.jpg"
art_source = "url"
elif art_url.startswith("file:///"):
parsed = urlparse(art_url)
art_path = unquote(parsed.path)
art_source = "file"
return {
"title": title,
"artist": artist,
"album": album,
"art_path": art_path,
"status": status,
"art_source": art_source
}
except subprocess.CalledProcessError:
return None
@app.route('/now_playing')
def now_playing():
media_info = get_media_info()
if media_info:
return jsonify({
"title": media_info["title"],
"artist": media_info["artist"],
"album": media_info["album"],
"is_playing": True,
"status": media_info["status"],
"art_source": media_info["art_source"]
})
else:
return jsonify({"error": "No media playing"}), 404
@app.route('/album_art')
def album_art():
info = get_media_info()
art_path = info.get("art_path") if info else None
if art_path and os.path.exists(art_path):
try:
if art_path.lower().endswith((".jpg", ".jpeg")):
return send_file(art_path, mimetype='image/jpeg')
else:
with Image.open(art_path) as img:
img = img.convert("RGB")
buffer = io.BytesIO()
img.save(buffer, format="JPEG")
buffer.seek(0)
return send_file(buffer, mimetype='image/jpeg')
except Exception as e:
return f"Image processing error: {e}", 500
return "No album art", 404
@app.route('/toggle_play', methods=['POST'])
def toggle_play():
os.system("playerctl play-pause")
return jsonify({"status": "toggled"})
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)