Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save pjosalgado/f8ee298ea113c40b9e9f497499c70aa9 to your computer and use it in GitHub Desktop.

Select an option

Save pjosalgado/f8ee298ea113c40b9e9f497499c70aa9 to your computer and use it in GitHub Desktop.
Media Playing for UsbPCMonitor 3.5" [Landscape version]
# Install dbus-python dependency
import os
import sys
import io
import time
import tempfile
import uuid
import atexit
import requests
import dbus
from PIL import Image, ImageDraw, ImageFont
from library.lcd.lcd_comm import Orientation
from library.lcd.lcd_comm_rev_a import LcdCommRevA
SCRIPT_DIR = os.path.dirname(os.path.abspath(sys.argv[0]))
os.chdir(SCRIPT_DIR)
DISPLAY_WIDTH = 480
DISPLAY_HEIGHT = 320
COM_PORT = "AUTO"
POLL_INTERVAL = 0.5
SCROLL_SPEED_PX = 36
lcd = LcdCommRevA(
com_port=COM_PORT,
display_width=DISPLAY_HEIGHT,
display_height=DISPLAY_WIDTH
)
lcd.Reset()
lcd.InitializeComm()
lcd.SetBrightness(level=25)
lcd.SetOrientation(Orientation.LANDSCAPE)
ROBOTO_FOLDER = os.path.join(SCRIPT_DIR, "res", "fonts", "roboto")
def find_font(names):
for n in names:
p = os.path.join(ROBOTO_FOLDER, n)
if os.path.exists(p):
return p
return None
FONT_BOLD = find_font(["Roboto-Bold.ttf","roboto-bold.ttf"])
FONT_REG = find_font(["Roboto-Regular.ttf","roboto.ttf"]) or FONT_BOLD
def load_font(path,size):
return ImageFont.truetype(path,size) if path and os.path.exists(path) else ImageFont.load_default()
_font_cache={}
def get_font(size,bold=False):
k=(size,bold)
if k not in _font_cache:
_font_cache[k]=load_font(FONT_BOLD if bold else FONT_REG,size)
return _font_cache[k]
def shutdown():
try:
lcd.SetBrightness(0)
lcd.ClearScreen()
except:
pass
atexit.register(shutdown)
session_bus=dbus.SessionBus()
def get_player():
for s in session_bus.list_names():
if s.startswith("org.mpris.MediaPlayer2."):
return s
return None
state={
"progress":0,
"ts":time.monotonic(),
"duration":0,
"playing":False
}
def fetch_now_playing():
name=get_player()
if not name:
return None
try:
obj=session_bus.get_object(name,"/org/mpris/MediaPlayer2")
prop=dbus.Interface(obj,"org.freedesktop.DBus.Properties")
meta=prop.Get("org.mpris.MediaPlayer2.Player","Metadata")
status=prop.Get("org.mpris.MediaPlayer2.Player","PlaybackStatus")
pos=prop.Get("org.mpris.MediaPlayer2.Player","Position")
duration=int(meta.get("mpris:length",0))//1000
progress=int(pos)//1000
state.update({
"progress":progress,
"ts":time.monotonic(),
"duration":duration,
"playing":status=="Playing"
})
art=None
url=meta.get("mpris:artUrl")
if url:
url=str(url)
if url.startswith("file://"):
p=url.replace("file://","")
if os.path.exists(p):
with open(p,"rb") as f: art=f.read()
else:
try: art=requests.get(url,timeout=5).content
except: pass
track={
"title":str(meta.get("xesam:title","Unknown")),
"artists":[str(a) for a in meta.get("xesam:artist",[])],
"album":str(meta.get("xesam:album","Unknown")),
"duration":duration
}
return track,status=="Playing",progress,duration,art
except:
return None
def interpolated_progress():
if not state["playing"]:
return state["progress"]
e=(time.monotonic()-state["ts"])*1000
if state["duration"]:
return min(state["progress"]+e,state["duration"])
return state["progress"]+e
def dominant_color(b):
try:
i=Image.open(io.BytesIO(b)).convert("RGB").resize((50,50))
c=max(i.getcolors(2500),key=lambda x:x[0])[1]
return tuple(int(v*0.4) for v in c)
except:
return (0,0,0)
def rounded_album(b,size=180,r=10):
try:
a=Image.open(io.BytesIO(b)).convert("RGB").resize((size,size),Image.LANCZOS)
m=Image.new("L",(size,size),0)
ImageDraw.Draw(m).rounded_rectangle([0,0,size,size],r,fill=255)
a.putalpha(m)
bg=Image.new("RGB",(size,size),(0,0,0))
bg.paste(a,(0,0),a)
return bg
except:
return None
def format_time(ms):
s=int(ms//1000)
h=s//3600
m=(s%3600)//60
sec=s%60
return f"{h}:{m:02d}:{sec:02d}" if h>0 else f"{m}:{sec:02d}"
def text_border(d,pos,t,f,c):
x,y=pos
for dx in(-1,0,1):
for dy in(-1,0,1):
if dx or dy:
d.text((x+dx,y+dy),t,font=f,fill=(0,0,0))
d.text(pos,t,font=f,fill=c)
def play_icon(d,x,y,s=18):
d.polygon([(x,y),(x,y+s),(x+s,y+s//2)],fill=(255,255,255))
def pause_icon(d,x,y,s=18):
b=s//3
d.rectangle([x,y,x+b,y+s],fill=(255,255,255))
d.rectangle([x+b*2,y,x+b*3,y+s],fill=(255,255,255))
scroll={}
def scroll_state(k,t):
if k not in scroll or scroll[k]["t"]!=t:
scroll[k]={"t":t,"o":0,"d":False}
return scroll[k]
def draw_scroll(base,d,pos,t,f,c,w,s):
tw=d.textlength(t,font=f)
if tw<=w:
text_border(d,pos,t,f,c)
return
if s["d"]:
tr=t
while d.textlength(tr+"…",font=f)>w:
tr=tr[:-1]
text_border(d,pos,tr+"…",f,c)
return
o=s["o"]
img=Image.new("RGBA",(int(tw+100),80),(0,0,0,0))
td=ImageDraw.Draw(img)
text_border(td,(0,0),t,f,c)
if o>=tw-w:
s["d"]=True
tr=t
while d.textlength(tr+"…",font=f)>w:
tr=tr[:-1]
text_border(d,pos,tr+"…",f,c)
return
crop=img.crop((o,0,o+w,80))
base.paste(crop,pos,crop)
s["o"]+=SCROLL_SPEED_PX
def render(track,playing,prog,dur,art,out):
bg=dominant_color(art) if art else (0,0,0)
img=Image.new("RGB",(DISPLAY_WIDTH,DISPLAY_HEIGHT),bg)
d=ImageDraw.Draw(img)
f_title=get_font(28,True)
f_artist=get_font(24)
f_album=get_font(22)
f_time=get_font(20)
alb=rounded_album(art)
if alb:
img.paste(alb,(20,(DISPLAY_HEIGHT-alb.height)//2))
tx=220
mw=DISPLAY_WIDTH-tx-20
st=scroll_state("t",track["title"])
sa=scroll_state("a",", ".join(track["artists"]))
sl=scroll_state("l",track["album"])
draw_scroll(img,d,(tx,50),track["title"],f_title,(255,255,255),mw,st)
draw_scroll(img,d,(tx,95),", ".join(track["artists"]),f_artist,(235,235,235),mw,sa)
draw_scroll(img,d,(tx,135),track["album"],f_album,(215,215,215),mw,sl)
bw=DISPLAY_WIDTH-tx-40
bx=tx
by=200
bh=12
d.rounded_rectangle([bx,by,bx+bw,by+bh],6,fill=(90,90,90),outline=(0,0,0),width=2)
if dur:
pct=max(0,min(1,prog/dur))
d.rounded_rectangle([bx,by,bx+int(bw*pct),by+bh],6,fill=(255,255,255),outline=(0,0,0),width=2)
time_txt=f"{format_time(prog)} / {format_time(dur)}"
tw=d.textlength(time_txt,font=f_time)
icon=18
gap=20
total=icon+gap+tw
cx=bx+(bw//2)
sx=cx-(total//2)
y=by+30
play_icon(d,sx,y,icon) if playing else pause_icon(d,sx,y,icon)
text_border(d,(sx+icon+gap,y-2),time_txt,f_time,(255,255,255))
img.save(out)
while True:
data=fetch_now_playing()
if not data:
time.sleep(POLL_INTERVAL)
continue
track,playing,prog,dur,art=data
prog=interpolated_progress()
tmp=os.path.join(tempfile.gettempdir(),f"turing_{uuid.uuid4().hex}.png")
try:
render(track,playing,prog,dur,art,tmp)
lcd.DisplayBitmap(tmp)
finally:
if os.path.exists(tmp):
os.remove(tmp)
time.sleep(POLL_INTERVAL)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment