Last active
July 30, 2022 11:36
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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