Skip to content

Commit

Permalink
- Fix album art on GNOME by caching in $HOME/.cache/beefweb_mpris/{al…
Browse files Browse the repository at this point in the history
…bum art}, directory is cleared when exiting

- Other fixes like volume control and shuffle/repeat
  • Loading branch information
Theron Tjapkes committed Aug 26, 2021
1 parent 4e5ba30 commit 8b6370e
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 44 deletions.
47 changes: 28 additions & 19 deletions beefweb_mpris/adapter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import urllib.request
from mimetypes import guess_type
from typing import Optional

from math import log
from mpris_server import MetadataObj, ValidMetadata
from mpris_server.adapters import MprisAdapter
from mpris_server.base import Microseconds, PlayState, DbusObj, DEFAULT_RATE, RateDecimal, VolumeDecimal, Track, \
DEFAULT_TRACK_ID
from mpris_server.events import EventAdapter
from mpris_server.mpris.compat import get_track_id
from pyfoobeef.models import PlayerState
from gi.repository import GLib

from beefweb_mpris.beefweb import Beefweb

Expand All @@ -21,19 +21,19 @@ def metadata(self) -> ValidMetadata:
try:
active_item = self.beefweb.active_item
columns = active_item.columns
self.beefweb.download_art()
return MetadataObj(
track_id=get_track_id(active_item.columns.title),
length=int(self.beefweb.active_item.duration * 1000000),
art_url=f'https://{self.beefweb.server}:{self.beefweb.port}/api'
f'/artwork/{active_item.playlist_id}/{active_item.index}',
art_url=f'file:https://{GLib.get_user_cache_dir()}/beefweb_mpris/{columns.album}',
title=columns.title,
artists=[columns.artists],
album=columns.album,
album_artists=[columns.album_artist],
disc_no=int(columns.disc_no),
track_no=int(columns.track_no)
)
except AttributeError:
except AttributeError as e:
return MetadataObj(
track_id=DEFAULT_TRACK_ID
)
Expand Down Expand Up @@ -85,7 +85,7 @@ def seek(

def open_uri(self, uri: str):
mimetype, _ = guess_type(uri)
self.beefweb.client.play
self.beefweb.client.play()

def is_repeating(self) -> bool:
try:
Expand All @@ -100,10 +100,18 @@ def is_playlist(self) -> bool:
return True

def set_repeating(self, val: bool):
self.beefweb.client.set_player_state(playback_mode=2)
if self.beefweb.state.playback_mode.number == 2:
self.beefweb.client.set_player_state(playback_mode=0)
else:
self.beefweb.client.set_player_state(playback_mode=2)

def set_loop_status(self, val: str):
pass
if val == "None":
self.beefweb.client.set_player_state(playback_mode=0)
elif val == "Track":
self.beefweb.client.set_player_state(playback_mode=2)
elif val == "Playlist":
self.beefweb.client.set_player_state(playback_mode=1)

def get_rate(self) -> RateDecimal:
return DEFAULT_RATE
Expand Down Expand Up @@ -133,20 +141,27 @@ def get_shuffle(self) -> bool:
return False

def set_shuffle(self, val: bool):
self.beefweb.client.set_player_state(playback_mode=4)
if self.beefweb.state.playback_mode.number == 4:
self.beefweb.client.set_player_state(playback_mode=0)
else:
self.beefweb.client.set_player_state(playback_mode=4)

def get_art_url(self, track: int) -> str:
return f'https://{self.beefweb.server}:{self.beefweb.port}' \
f'/artwork/{self.beefweb.active_item.playlist_id}/{self.beefweb.active_item.index}'
self.beefweb.download_art()
return f'file:https://{GLib.get_user_cache_dir()}/beefweb_mpris/{self.beefweb.active_item.columns.album}'

def get_volume(self) -> VolumeDecimal:
print("returning volume: ", self.beefweb.state.volume.value)
try:
return self.beefweb.state.volume.value + 100.0
except AttributeError:
return 100

def set_volume(self, val: VolumeDecimal):
return self.beefweb.client.set_player_state(volume=VolumeDecimal-100.0)
# I don't know what im doing but it works kinda
new_vol = 0 - (100 ** (1 - val))
print(new_vol)
return self.beefweb.client.set_player_state(volume=new_vol)

def is_mute(self) -> bool:
try:
Expand Down Expand Up @@ -183,9 +198,3 @@ def get_previous_track(self) -> Track:

def get_next_track(self) -> Track:
pass


class BeefwebEventHandler(EventAdapter):

def on_event(self, state: PlayerState):
self.emit_all()
37 changes: 32 additions & 5 deletions beefweb_mpris/beefweb.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import asyncio
import threading
import os
import requests
import pyfoobeef

from gi.repository import GLib
from pyfoobeef.models import PlayerState, ActiveItem, Columns


class Beefweb:
def __init__(self, server: str, port: int, username: str, password: str):
self.server = server
self.port = port
self.listener = pyfoobeef.EventListener(
self.event_listener = pyfoobeef.EventListener(
base_url=server,
port=port,
active_item_column_map={
Expand All @@ -31,7 +36,7 @@ def __init__(self, server: str, port: int, username: str, password: str):

@property
def state(self) -> PlayerState:
return self.listener.player_state
return self.event_listener.player_state

@property
def active_item(self) -> ActiveItem:
Expand All @@ -41,7 +46,29 @@ def active_item(self) -> ActiveItem:
def columns(self) -> Columns:
return self.active_item.columns

async def register_event_handler(self, event_handler):
self.listener.add_callback("player_state", event_handler.on_event)
await self.listener.connect(reconnect_time=1)
def download_art(self):
try:
r = requests.get(f'https://{self.server}:{self.port}/api'
f'/artwork/{self.active_item.playlist_id}/{self.active_item.index}')
cover_path = f'{GLib.get_user_cache_dir()}/beefweb_mpris/{self.active_item.columns.album}'
if not os.path.isfile(cover_path):
if not os.path.isdir(os.path.dirname(cover_path)):
os.makedirs(os.path.dirname(cover_path))
with open(cover_path, 'wb') as f:
f.write(r.content)
finally:
return

def listener(self, loop, event_listener):
self.event_listener.add_callback("player_state", event_listener.new_player_state)
asyncio.set_event_loop(loop)
loop.run_until_complete(self.listener_loop())

async def listener_loop(self):
await self.event_listener.connect(1)
while True:
await asyncio.sleep(10)

def register_event_handler(self, event_handler):
loop = asyncio.new_event_loop()
threading.Thread(target=self.listener, args=(loop, event_handler), daemon=True).start()
32 changes: 32 additions & 0 deletions beefweb_mpris/handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from typing import Optional

from mpris_server.adapters import MprisAdapter
from mpris_server.events import EventAdapter
from mpris_server.server import Server
from pyfoobeef.models import PlayerState
from beefweb_mpris.beefweb import Beefweb


class BeefwebEventHandler(EventAdapter):
def __init__(
self,
beefweb: Beefweb,
server: Server,
adapter: Optional[MprisAdapter] = None
):
self.beefweb = beefweb
self.server = server
self.adapter = adapter
super().__init__(self.server.player, self.server.root)

def new_player_state(self, state: PlayerState):
self.on_player_all()


def register_event_handler(
beefweb: Beefweb,
server: Server,
adapter: MprisAdapter
):
event_handler = BeefwebEventHandler(beefweb, server, adapter)
beefweb.register_event_handler(event_handler)
26 changes: 8 additions & 18 deletions beefweb_mpris/main.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
import asyncio
import threading
import os
import shutil
from typing import Dict

import yaml
from gi.repository import GLib
from subprocess import Popen
from mpris_server.server import Server

from beefweb_mpris.beefweb import Beefweb
from beefweb_mpris.adapter import BeefwebAdapter, BeefwebEventHandler


async def start(event_handler, beefweb):
await beefweb.register_event_handler(event_handler)


def start_event_loop(loop: asyncio.AbstractEventLoop) -> None:
asyncio.set_event_loop(loop)
loop.run_forever()
from beefweb_mpris.adapter import BeefwebAdapter
from beefweb_mpris.handler import register_event_handler


def main():
Expand All @@ -28,7 +23,6 @@ def main():
'host': 'localhost',
'port': 8880,
'foobar2000-command': 'foobar2000',
'timeout': 30.0,
'username': 'user',
'password': 'password'
}
Expand All @@ -42,21 +36,17 @@ def main():
print(e)

foobar2000 = Popen([config['foobar2000-command']])

beefweb = Beefweb(config['host'], config['port'], config['username'], config['password'])
adapter = BeefwebAdapter(beefweb)
mpris = Server('beefweb', adapter=adapter)
event_handler = BeefwebEventHandler(root=mpris.root, player=mpris.player)

event_loop = asyncio.new_event_loop()
event_thread = threading.Thread(target=start_event_loop, args=(event_loop,), daemon=True)
event_thread.start()
event_task = asyncio.run_coroutine_threadsafe(start(event_handler, beefweb), event_loop)

mpris_thread = threading.Thread(target=mpris.loop, daemon=True)
mpris_thread.start()

register_event_handler(beefweb, mpris, adapter)

foobar2000.wait()
shutil.rmtree(f'{GLib.get_user_cache_dir()}/beefweb_mpris')


if __name__ == '__main__':
Expand Down
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ mpris_server
pyfoobeef
pyyaml
pygobject
pygobject-stubs
requests
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name="beefweb_mpris",
version="1.0.0",
version="1.0.1",
author="Theron Tjapkes",
description="Adds MPRIS support to foobar2000 through beefweb",
url="https://github.com/ther0n/beefweb_mpris",
Expand All @@ -15,6 +15,7 @@
'mpris_server',
'pyfoobeef',
'pyyaml',
'pygobject'
'pygobject',
'requests'
]
)

0 comments on commit 8b6370e

Please sign in to comment.