Skip to content

Commit

Permalink
Switch to pure python library for dbus (#82)
Browse files Browse the repository at this point in the history
Using the CLI requires system-level dbus packages to be installed, which
vary by distro and can sometimes cause problems even when installed if
trying to run `spotifycli` in an isolated env (like installing with pipx
or uv). To sidestep this issue, switch to using `jeepney`, a pure python
implementation of the dbus interface. Since it is lower level, the
logic ends up a little bit more complex. This could be improved, but
since the changes are still quite small it didn't feel worthwhile at
this point.

Closes #53.

I have written a script to test the output of all the supported CLI
endpoints, but writing stable tests would require a bigger refactor,
so I won't add it to the commit, I'll just paste it in here (since it
doesn't rely on mocking the dbus response and instead requires exact
setup).

```python
import io
import sys
from contextlib import redirect_stdout
from unittest.mock import patch

from spotifycli.spotifycli import main

def test_main():

    cases = (
        ("--status", "Tindersticks - Travelling Light\n"),
        ("--statusshort", "Tindersticks - Travelling...\n"),
        ("--statusposition", "Tindersticks - Travelling Light (00:07/04:51)\n"),
        ("--song", "Travelling Light\n"),
        ("--songshort", "Travelling...\n"),
        ("--artist", "Tindersticks\n"),
        ("--artistshort", "Tindersticks\n"),
        ("--album", "Tindersticks\n"),
        ("--position", "(00:07/04:51)\n"),
        ("--playbackstatus", "▮▮\n"),
        ("--lyrics", "lyrics not found\n"),
        ("--arturl", "https://i.scdn.co/image/ab67616d0000b273cdd1aabdf50a42ca4556d31d\n"),
    )

    for flag, expected in cases:
        f = io.StringIO()
        with redirect_stdout(f), patch.object(sys, 'argv', ['spotifycli', '--client', 'spotify', flag]):
            main()
            result = f.getvalue()
            assert result == expected
```
  • Loading branch information
allhailwesttexas authored Dec 3, 2024
1 parent bd96705 commit 108c4d1
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 50 deletions.
16 changes: 0 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,22 +75,6 @@ if you don't use any parameters, you'll enter the shell mode, where you'll be ab
solving problems
----------------

### dbus

When you've seen the following error:

```
No module named dbus
```

Then try to install `python-dbus`! On Ubuntu you can do it as follows:

```
sudo apt-get install python-dbus
```

If you are using another distro, then try to install `python-dbus` with your package manager.

### lyricwikia

When, you're missing `lyricwikia` dependency, run the following command:
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
autopep8
jeepney
lyricwikia
pycodestyle
pylint
setuptools
twine
lyricwikia
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
url='https://github.com/pwittchen/spotify-cli-linux',
license='GPL 3.0',
packages=['spotifycli'],
install_requires=['lyricwikia'],
install_requires=['jeepney', 'lyricwikia'],
entry_points={
"console_scripts": ['spotifycli = spotifycli.spotifycli:main']
},
Expand Down
75 changes: 43 additions & 32 deletions spotifycli/spotifycli.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import datetime
from subprocess import Popen, PIPE

import dbus
import lyricwikia

from jeepney import DBusAddress, new_method_call
from jeepney.io.blocking import open_dbus_connection


def main():
if len(sys.argv) == 1:
Expand Down Expand Up @@ -115,9 +117,9 @@ def show_version():

def get_song():
metadata = get_spotify_property("Metadata")
artist = metadata['xesam:artist'][0]
title = metadata['xesam:title']
return (artist, title)
artist = ", ".join(metadata['xesam:artist'][1])
title = metadata['xesam:title'][1]
return artist, title


def show_status():
Expand All @@ -135,12 +137,11 @@ def show_status_position():
metadata = get_spotify_property("Metadata")
position_raw = get_spotify_property("Position")

artist = metadata['xesam:artist'][0]
title = metadata['xesam:title']
artist, title = get_song()

# Both values are in microseconds
position = datetime.timedelta(milliseconds=position_raw / 1000)
length = datetime.timedelta(milliseconds=metadata['mpris:length'] / 1000)
length = datetime.timedelta(milliseconds=metadata['mpris:length'][1] / 1000)

p_hours, p_minutes, p_seconds = convert_timedelta(position)
l_hours, l_minutes, l_seconds = convert_timedelta(length)
Expand Down Expand Up @@ -201,42 +202,52 @@ def show_playback_status():

def show_album():
metadata = get_spotify_property("Metadata")
album = metadata['xesam:album']
album = metadata['xesam:album'][1]
print(f'{album}')


def show_art_url():
metadata = get_spotify_property("Metadata")
print("%s" % metadata['mpris:artUrl'])
print("%s" % metadata['mpris:artUrl'][1])


def get_spotify_property(spotify_property):
try:
session_bus = dbus.SessionBus()
names = dbus.Interface(
session_bus.get_object(
"org.freedesktop.DBus",
"/org/freedesktop/DBus"),
"org.freedesktop.DBus").ListNames()
mpris_name = None

for name in names:
if name.startswith("org.mpris.MediaPlayer2.%s" % client):
mpris_name = name

dbus_addr = DBusAddress(
bus_name="org.freedesktop.DBus",
object_path="/org/freedesktop/DBus",
interface="org.freedesktop.DBus",
)
connection = open_dbus_connection(bus="SESSION")

list_names_call = new_method_call(
remote_obj=dbus_addr, method="ListNames", signature=""
)
reply = connection.send_and_get_reply(list_names_call)
names = reply.body[0]

client_name = f"org.mpris.MediaPlayer2.{client}"
mpris_name = next((name for name in names if name.startswith(client_name)), None)
if mpris_name is None:
sys.stderr.write("No mpris clients found for client %s\n" % client)
sys.stderr.write(f"No mpris clients found for client {client}\n")
sys.exit(1)

spotify_bus = session_bus.get_object(
mpris_name,
"/org/mpris/MediaPlayer2")
spotify_properties = dbus.Interface(
spotify_bus,
"org.freedesktop.DBus.Properties")
return spotify_properties.Get(
"org.mpris.MediaPlayer2.Player",
spotify_property)
spotify_dbus_addr = DBusAddress(
bus_name=mpris_name,
object_path="/org/mpris/MediaPlayer2",
interface="org.freedesktop.DBus.Properties"
)
get_property_call = new_method_call(
remote_obj=spotify_dbus_addr,
method="Get",
signature="ss",
body=("org.mpris.MediaPlayer2.Player", spotify_property)
)

reply = connection.send_and_get_reply(get_property_call)
body = reply.body[0]
return body[1]

except BaseException:
sys.stderr.write("Spotify is off\n")
sys.exit(1)
Expand All @@ -253,7 +264,7 @@ def show_position():
position_raw = get_spotify_property("Position")
# Both values are in microseconds
position = datetime.timedelta(milliseconds=position_raw / 1000)
length = datetime.timedelta(milliseconds=metadata['mpris:length'] / 1000)
length = datetime.timedelta(milliseconds=metadata['mpris:length'][1] / 1000)

p_hours, p_minutes, p_seconds = convert_timedelta(position)
l_hours, l_minutes, l_seconds = convert_timedelta(length)
Expand Down

0 comments on commit 108c4d1

Please sign in to comment.