Skip to content

Instantly share code, notes, and snippets.

@P403n1x87
Last active July 30, 2022 11:36
Show Gist options
  • Save P403n1x87/0429f4f0102dd6862112a7eb2d030236 to your computer and use it in GitHub Desktop.
Save P403n1x87/0429f4f0102dd6862112a7eb2d030236 to your computer and use it in GitHub Desktop.
A simple Spotify widget written in Python for Blighty to celebrate the 1k download mark.
#!/usr/bin/env python
"""
This file is part of "blighty" which is released under GPL.
See file LICENCE or go to http://www.gnu.org/licenses/ for full license
details.
blighty is a desktop widget creation and management library for Python 3.
Copyright (c) 2018 Gabriele N. Tornetta <[email protected]>.
All rights reserved.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
import shutil
import cairo
import requests
from blighty import CanvasGravity, TextAlign
from blighty.x11 import Canvas
from gi.repository import GLib
from PIL import Image
from pydbus import SessionBus
from attrdict import AttrDict
class Palette(type):
WHITE = (1, 1, 1, 1)
SHADOW = (0, 0, 0, .15)
GREEN = (0, .67, 0, 1)
BLUE = (.1, .2, 1, 1)
MAGENTA = (.8, 0, .5, 1)
class Fonts(type):
MICHROMA_NORMAL = "Michroma", cairo.FontSlant.NORMAL, cairo.FontWeight.NORMAL
class SpotifyDBus:
def __init__(self):
self.bus = SessionBus()
self.proxy = self.bus.get(
'org.mpris.MediaPlayer2.spotify',
'/org/mpris/MediaPlayer2'
)
def get_metadata(self):
return {k.split(':')[1]: v for k, v in self.proxy.Metadata.items()}
def toggle_play(self):
self.proxy.PlayPause()
def is_paused(self):
return self.proxy.PlaybackStatus == "Paused"
def close(self):
del self.bus
class Spotify(Canvas):
ART = AttrDict({"w": 128, "h": 128, "x": 0, "y": 0})
ARTIST = AttrDict({"h": 22, "bg": Palette.GREEN})
TITLE = AttrDict({"h": 36, "bg": Palette.BLUE})
ALBUM = AttrDict({"h": 22, "bg": Palette.MAGENTA})
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.init_dbus()
self.last_paused = None
@staticmethod
def build():
return Spotify(
x = 0,
y = 0,
width = 800,
height = Spotify.ART.h,
gravity = CanvasGravity.SOUTH_WEST,
interval = 1000
)
def init_dbus(self):
try:
self.spotify = SpotifyDBus()
except GLib.Error:
self.spotify = None
self.last_art_url = ""
def on_button_pressed(self, button, state, x, y):
if button == 1: # Left button
self.spotify.toggle_play()
elif button == 3: # Right button
self.dispose()
def draw_art(ctx, url):
temp_file = "/tmp/spotify_art"
temp_img = temp_file + ".png"
if ctx.canvas.last_art_url != url:
response = requests.get(url, stream=True)
with open(temp_file, 'wb') as out_file:
shutil.copyfileobj(response.raw, out_file)
del response
ctx.canvas.last_art_url = url
size = Spotify.ART.w
with Image.open(temp_file) as image:
image.thumbnail((size, size), Image.ANTIALIAS)
image.save(temp_img)
ctx.set_source_rgba(*Palette.SHADOW)
for i in range(1, 5):
ctx.rectangle(Spotify.ART.w, i, i, Spotify.ART.w - i)
ctx.fill()
surface = cairo.ImageSurface.create_from_png(temp_img)
ctx.set_source_surface(surface, Spotify.ART.x, Spotify.ART.x)
ctx.paint()
def draw_pause(ctx):
size = Spotify.ART.w
ctx.set_source_rgba(0, 0, 0, 0.75)
ctx.rectangle(0, 0, size, size)
ctx.fill()
ctx.set_source_rgba(.8, 0.8, 0.8, 0.5)
size2 = size >> 1
size4 = size2 >> 1
size5 = size // 5
ctx.rectangle(size4, size4, size5, size2)
ctx.rectangle(size - size4 - size5, size4, size5, size2)
ctx.fill()
def draw_ribbon(ctx, x, y, label, height, fg_color, bg_color):
def draw_shape(x, y, width, height, color):
ctx.save()
ctx.move_to(x, y)
ctx.line_to(x + width + height / 2, y)
ctx.line_to(x + width, y + height)
ctx.line_to(x, y + height)
ctx.line_to(x, y)
ctx.set_source_rgba(*color)
ctx.fill()
ctx.restore()
ex = ctx.text_extents(label)
pad = 8
width = ex.width + (pad << 1)
for i in range(1, 5):
draw_shape(x + i, y + i, width, height, Palette.SHADOW) # Shadow
draw_shape(x, y, width, height, bg_color)
ctx.save()
ctx.set_source_rgba(*fg_color)
ctx.write_text(x + pad, y + height / 2, label, TextAlign.CENTER_LEFT)
ctx.restore()
return width, height
def draw_metadata(ctx, metadata):
ctx.select_font_face(*Fonts.MICHROMA_NORMAL)
ctx.set_font_size(16)
ctx.draw_ribbon(
x = Spotify.ART.x + Spotify.ART.w,
y = ctx.canvas.height - Spotify.ALBUM.h - Spotify.TITLE.h - 2,
label = metadata["title"],
height = Spotify.TITLE.h,
fg_color = Palette.WHITE,
bg_color = Spotify.TITLE.bg
)
ctx.set_font_size(12)
ctx.draw_ribbon(
x = Spotify.ART.x + Spotify.ART.w,
y = ctx.canvas.height - Spotify.ALBUM.h - Spotify.TITLE.h - Spotify.ARTIST.h,
label = ", ".join(metadata["artist"]),
height = Spotify.ARTIST.h,
fg_color = Palette.WHITE,
bg_color = Spotify.ARTIST.bg
)
ctx.draw_ribbon(
x = Spotify.ART.x + Spotify.ART.w,
y = ctx.canvas.height - Spotify.ALBUM.h - 4,
label = metadata["album"],
height = Spotify.ALBUM.h,
fg_color = Palette.WHITE,
bg_color = Spotify.ALBUM.bg
)
def on_draw(self, ctx):
if self.spotify is None:
self.init_dbus()
return
try:
metadata = self.spotify.get_metadata()
except GLib.Error:
self.spotify = None
return
url = metadata["artUrl"]
is_paused = self.spotify.is_paused()
if self.last_art_url == url and self.last_paused == is_paused:
return True
self.last_paused = is_paused
ctx.draw_metadata(metadata)
if "http" in url:
ctx.draw_art(url)
if is_paused:
ctx.draw_pause()
if __name__ == "__main__":
from blighty.x11 import start_event_loop
Spotify.build().show()
start_event_loop()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment