Presto for the Presto Music P10

Working on a visualizer for the Presto Music P10 streamer. The Presto seemed like an ideal fit, in more ways than one.

Couple of questions:

  1. Presto provides 512X512 jpg’s, which I have to center by placing the origin at -16,-16, which crops the edges. Is there ANY way to shrink the jpg’s to 480x480 within the limits of the Presto’s CPU and memory?
  2. I want to display date/time when music isn’t playing. Does anyone know of a simple way to do TZ in micropython?
  3. Is there a way to create a semi-transparent gray background box behind the text?

1 Use a graphics/photo editor to change the image size to 480x480 - Gimp or Photoshop.
2 Time

import time

# Get current time
year, month, day, h, m, s, wd, _ = time.localtime()

days = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]ode here

Thanks, but:

  1. The images are being pulled dynamically from Presto Music’s servers, can’t be manually edited. e.g. https://d1iiivw74516uk.cloudfront.net/eyJidWNrZXQiOiJwcmVzdG8tY292ZXItaW1hZ2VzIiwia2V5IjoiODQ2OTM1Mi4xLmpwZyIsImVkaXRzIjp7InJlc2l6ZSI6eyJ3aWR0aCI6NTEyfSwianBlZyI6eyJxdWFsaXR5Ijo2NX0sInRvRm9ybWF0IjoianBlZyJ9LCJ0aW1lc3RhbXAiOjE1NTI1NjA5MzV9
  2. Without a TZ, time.localtime() returns GMT (or UCT, or whatever it’s called these days), unless running in Thonny. I set the Presto’s clock using ntptime, which gets GMT.

You should pull your time from worldtimeapi.org. No more hazzle with timezones, daylight saving time and so on.

Worldtimeapi looks interesting, though they explicitly state that they shouldn’t be relied on. For now, I’m just going to add a TZ to secrets.py, with the offset in hours (currently -8 in my TZ), and stick with ntptime. Perhaps will write an on-screen selection for TZ later.

It appears that the Presto doesn’t support png transparency. The gray box shown below is almost transparent, but renders opaque on the Presto. Maybe an update at some point will fix this.

How about fetching it via an external resizing proxy such as https://wsrv.nl/ ?
I do that for converting webp to png for my Radio Now Playing plugin for LMS.

1 Like

I wasn’t aware of that service. Thanks! You are Mr. Cool ;)

…have you already implemented the wsrv.nl service to your program?
Would you please be so kind as to share the code snippet in micropython on how to pass the image url to the service and retrieve it for further processing in your app same time?

I am considering to retrieve weather icons and conveniently scale it either up or down for several purposes of highlighting certain weather conditions on a dashboard…

…so many possibilities: I am also thinking of implementing this to badger & inky frame, as I am also currently manually resizing images… this service would be absolutely more handy without the hassle of a manual process in between…

Sure, still very much in debug mode, but here are the relevant snippets:

# Initialize JPEG and PNG Decoders
jpd = jpegdec.JPEG(display)
pnd = pngdec.PNG(display)

,,,

def fetch_album_art(art_url):
    global album_art
    global firstFetch
    
    gc.collect()
    print("Start requests.get")
    print(art_url)
    proxy_url = "https://wsrv.nl/?url="+art_url+"&w=480&h=480"
    print(proxy_url)
    response = requests.get(proxy_url, timeout=TIMEOUT)
    print("Finish requests.get:", response.status_code)
    
    if response.status_code == 200:
        print("Set album_art")
        album_art = response.content
        print(len(album_art))
        firstFetch = False
        try:
            print("Decode Album Art")
            jpd.open_RAM(memoryview(album_art))
            print("Done")
        except Exception as e:
            printtext(5,450,"Fetch Art Error: "+ str(e))
            firstFetch = True
        finally:
            response.close()
            gc.collect()
    else:
        printtext(5,450,"Failed to fetch album art")
        album_art = None
        firstFetch = True
        return None

def update_display(title, artist):
    global album_art

    #display.set_pen(BLACK)
    #display.clear()
    print("Update display")
    if album_art:
        try:
            print("Update album art")
            jpd.decode(0, 0, jpegdec.JPEG_SCALE_FULL)
            presto.update()
            print("Art updated")
        except Exception as e:
            print("Update art error:",e)
    else:
        try:
            jpd.open_file("default_album.jpg")
            jpd.decode(0,0, jpegdec.JPEG_SCALE_FULL)
        except Exception as e:
            print("Default album error",e)
      
    try:
        
        print("Get background.png")
        pnd.open_file("background.png")
        print("Decode bg")
        pnd.decode(0, 380)
        print("Update bg")
        presto.update()
    except OSError as e:
        print("background failed ", e)
        display.set_pen(BLACK)
        display.clear()
    
    display.set_pen(WHITE)

    vector.set_font_size(30)
    vector.text(title[1:], 10, CY + 170)
    vector.set_font_size(20)
    vector.text(artist, 10, CY + 190) #, WIDTH, 1, 1)
    presto.update()


1 Like

nah, so obvious…! I must have been blocked in thinking…

This is a really nice solution and this gives so many opportunities playing around with any kind of image (source, format, size, etc.) and makes it appropriate for any kind of device and screen size (Presto, Explorer, Inky Frames, badger, Tufty, etc.)…

A brief device specific sizing information in the variable declaration header on top of the code and the rest is reusing the exact same code for all devices…brilliant!

…just thinking out loud: If you would use Pimoroni’s library system variables WIDTH & HEIGHT (rather than 480 & 400) it always should be set properly for any device - even without a dedicated declaration block…

Code once and (always) re-use…

all credit goes to @PaulWebster

Note though - it does have rate limits for free usage (although I don’t think I have ever reached them in my usage).

I’ve pretty much given up on the Presto Music P10 for now, as it just isn’t stable enough, stops responding even to its own app frequently. Shame, it could be a nice system if stable.

Decided to move on to the super stable WiiM devices. The WiiM Mini sits nicely on the Presto, with a bit of double backed tape to secure it. Their https API is simple enough. Interesting that urequests has no issue with that invalid SSL certificate on the WiiM devices…


2 Likes

Attempting to compensate for the lack of the ability to render transparent images, I draw the text twice, offset by 10 pixels, in white and dark gray, to show up on different album covers. Kinda works, but not 100% satisfied.

Is there a fast way to determine whether the bottom of the image is dark or light?

Any suggestions appreciated.


OK, the individual pixels can be accessed and manipulated using memoryview(display).

I have done a crude transparent overlay by simply subtracting 64 from each byte. This obviously will color shift, so I’ll need to figure out the RGB565 pattern, extract the 5,6,5 bits into individual bytes, equally dim each one, then mash them back into the 2-byte pixel, and write them back as a darker pixel. Then the text can always be white.

This is very fast. Possibly an RGB565 to RGBA converter, then the reverse, would be the most logical way to dim this strip…

    data = memoryview(display)
    l = len(data)
    for i in range(l-55680,l,2):
        data[i] = data[i]- 64
        data[i+1] = data[i+1] - 64
        
    display.set_pen(WHITE)
        
    vector.set_font_size(30)
    vector.text(artist, 10, CY + 205)
    vector.set_font_size(20)
    vector.text(title, 10, CY + 225)
    
    presto.update()

Here’s the result of this crude hack, hopefully will get it fixed tomorrow. The bottom part of the album cover image shows behind the text, just color shifted for now.

1 Like

…looks really awesome as of now and I vote for a mention in the Raspberry Pi Magazine as they are always looking for creative projects by the community members…

So that idea turned out to be too much work for now.

Switching gears, decided to go for a slightly smaller image, with a nice neutral colored background, round the corners of the album cover, and call it a day.

1 Like