212 lines
9.1 KiB
Python
212 lines
9.1 KiB
Python
import os
|
|
import ignis
|
|
import asyncio
|
|
from ignis.widgets import Widget
|
|
from ignis.services.mpris import MprisService, MprisPlayer
|
|
from ignis.utils import Utils
|
|
from services.material import MaterialService
|
|
from ignis.app import IgnisApp
|
|
from ignis.exceptions import StylePathNotFoundError
|
|
|
|
|
|
mpris = MprisService.get_default()
|
|
app = IgnisApp.get_default()
|
|
material = MaterialService.get_default()
|
|
|
|
MEDIA_TEMPLATE = Utils.get_current_dir() + "/media.scss"
|
|
MEDIA_SCSS_CACHE_DIR = ignis.CACHE_DIR + "/media" # type: ignore
|
|
MEDIA_ART_FALLBACK = Utils.get_current_dir() + "/../../../misc/media-art-fallback.png"
|
|
os.makedirs(MEDIA_SCSS_CACHE_DIR, exist_ok=True)
|
|
|
|
|
|
PLAYER_ICONS = {
|
|
"spotify": "spotify-symbolic",
|
|
"firefox": "firefox-browser-symbolic",
|
|
"chrome": "chrome-symbolic",
|
|
None: "folder-music-symbolic",
|
|
}
|
|
|
|
|
|
class Player(Widget.Revealer):
|
|
def __init__(self, player: MprisPlayer) -> None:
|
|
self._player = player
|
|
self._colors_path = f"{MEDIA_SCSS_CACHE_DIR}/{self._player.desktop_entry}.scss"
|
|
player.connect("closed", lambda x: self.destroy())
|
|
player.connect("notify::art-url", lambda x, y: self.load_colors())
|
|
self.load_colors()
|
|
|
|
super().__init__(
|
|
transition_type="slide_down",
|
|
reveal_child=False,
|
|
css_classes=[self.get_css("media")],
|
|
child=Widget.Overlay(
|
|
child=Widget.Box(css_classes=[self.get_css("media-image")]),
|
|
overlays=[
|
|
Widget.Box(
|
|
hexpand=True,
|
|
vexpand=True,
|
|
css_classes=[self.get_css("media-image-gradient")],
|
|
),
|
|
Widget.Icon(
|
|
icon_name=self.get_player_icon(),
|
|
pixel_size=22,
|
|
halign="start",
|
|
valign="start",
|
|
css_classes=[self.get_css("media-player-icon")],
|
|
),
|
|
Widget.Box(
|
|
vertical=True,
|
|
hexpand=True,
|
|
css_classes=[self.get_css("media-content")],
|
|
child=[
|
|
Widget.Box(
|
|
vexpand=True,
|
|
valign="center",
|
|
child=[
|
|
Widget.Box(
|
|
hexpand=True,
|
|
vertical=True,
|
|
child=[
|
|
Widget.Label(
|
|
ellipsize="end",
|
|
label=player.bind("title"),
|
|
max_width_chars=30,
|
|
halign="start",
|
|
css_classes=[
|
|
self.get_css("media-title")
|
|
],
|
|
),
|
|
Widget.Label(
|
|
label=player.bind("artist"),
|
|
max_width_chars=30,
|
|
ellipsize="end",
|
|
halign="start",
|
|
css_classes=[
|
|
self.get_css("media-artist")
|
|
],
|
|
),
|
|
],
|
|
),
|
|
Widget.Button(
|
|
child=Widget.Icon(
|
|
image=player.bind(
|
|
"playback_status",
|
|
lambda value: "media-playback-pause-symbolic"
|
|
if value == "Playing"
|
|
else "media-playback-start-symbolic",
|
|
),
|
|
pixel_size=18,
|
|
),
|
|
on_click=lambda x: asyncio.create_task(player.play_pause_async()),
|
|
visible=player.bind("can_play"),
|
|
css_classes=player.bind(
|
|
"playback_status",
|
|
lambda value: [
|
|
self.get_css("media-playback-button"),
|
|
"playing",
|
|
]
|
|
if value == "Playing"
|
|
else [
|
|
self.get_css("media-playback-button"),
|
|
"paused",
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
Widget.Box(
|
|
vexpand=True,
|
|
valign="end",
|
|
style="padding: 1rem;",
|
|
child=[
|
|
Widget.Scale(
|
|
value=player.bind("position"),
|
|
max=player.bind("length"),
|
|
hexpand=True,
|
|
css_classes=[self.get_css("media-scale")],
|
|
on_change=lambda x: asyncio.create_task(self._player.set_position_async(x.value)),
|
|
visible=player.bind(
|
|
"position", lambda value: value != -1
|
|
),
|
|
),
|
|
Widget.Button(
|
|
child=Widget.Icon(
|
|
image="media-skip-backward-symbolic",
|
|
pixel_size=20,
|
|
),
|
|
css_classes=[self.get_css("media-skip-button")],
|
|
on_click=lambda x: asyncio.create_task(player.previous_async()),
|
|
visible=player.bind("can_go_previous"),
|
|
style="margin-left: 1rem;",
|
|
),
|
|
Widget.Button(
|
|
child=Widget.Icon(
|
|
image="media-skip-forward-symbolic",
|
|
pixel_size=20,
|
|
),
|
|
css_classes=[self.get_css("media-skip-button")],
|
|
on_click=lambda x: asyncio.create_task(player.next_async()),
|
|
visible=player.bind("can_go_next"),
|
|
style="margin-left: 1rem;",
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
)
|
|
|
|
def get_player_icon(self) -> str:
|
|
if self._player.desktop_entry == "firefox":
|
|
return PLAYER_ICONS["firefox"]
|
|
elif self._player.desktop_entry == "spotify":
|
|
return PLAYER_ICONS["spotify"]
|
|
elif self._player.track_id is not None:
|
|
if "chromium" in self._player.track_id or "chrome" in self._player.track_id:
|
|
return PLAYER_ICONS["chrome"]
|
|
|
|
return PLAYER_ICONS[None]
|
|
|
|
def destroy(self) -> None:
|
|
self.set_reveal_child(False)
|
|
Utils.Timeout(self.transition_duration, super().unparent)
|
|
|
|
def get_css(self, class_name: str) -> str:
|
|
return f"{class_name}-{self._player.desktop_entry}"
|
|
|
|
def load_colors(self) -> None:
|
|
if not self._player.art_url:
|
|
art_url = MEDIA_ART_FALLBACK
|
|
else:
|
|
art_url = self._player.art_url
|
|
|
|
try:
|
|
app.remove_css(self._colors_path)
|
|
except StylePathNotFoundError:
|
|
pass
|
|
|
|
colors = material.get_colors_from_img(art_url, True)
|
|
colors["art_url"] = art_url
|
|
colors["desktop_entry"] = self._player.desktop_entry
|
|
material.render_template(
|
|
colors, input_file=MEDIA_TEMPLATE, output_file=self._colors_path
|
|
)
|
|
app.apply_css(self._colors_path)
|
|
|
|
|
|
class Media(Widget.Box):
|
|
def __init__(self):
|
|
super().__init__(
|
|
vertical=True,
|
|
setup=lambda self: mpris.connect(
|
|
"player_added", lambda x, player: self.__add_player(player)
|
|
),
|
|
css_classes=["rec-unset"],
|
|
)
|
|
|
|
def __add_player(self, obj: MprisPlayer) -> None:
|
|
player = Player(obj)
|
|
self.append(player)
|
|
player.set_reveal_child(True)
|