-
Notifications
You must be signed in to change notification settings - Fork 12
/
spotify.py
182 lines (143 loc) · 5.24 KB
/
spotify.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
import sys
import subprocess
from SwSpotify import SpotifyClosed, SpotifyPaused, SpotifyNotRunning
def get_info_windows():
"""
Reads the window titles to get the data.
Older Spotify versions simply use FindWindow for "SpotifyMainWindow",
the newer ones create an EnumHandler and flood the list with
Chrome_WidgetWin_0s
"""
import win32api
import win32gui
import win32process
import pywintypes
from pathlib import Path
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
windows = []
old_window = win32gui.FindWindow("SpotifyMainWindow", None)
old = win32gui.GetWindowText(old_window)
def find_spotify_uwp(hwnd, windows):
text = win32gui.GetWindowText(hwnd)
classname = win32gui.GetClassName(hwnd)
if classname == "Chrome_WidgetWin_0" and len(text) > 0:
# Retrieve the thread id and process id that created the Spotify window
_, proc_id = win32process.GetWindowThreadProcessId(hwnd)
# Retrieve a handle to the Spotify process
proc_h = win32api.OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, False, proc_id)
# Get the file path of the Spotify process
path = win32process.GetModuleFileNameEx(proc_h, None)
# Close the handle
win32api.CloseHandle(proc_h)
if Path(path).name == "Spotify.exe":
windows.append(text)
# Stop enumeration once we found it
# This will always throw a pywintypes.error by design
return False
if old:
windows.append(old)
else:
try:
win32gui.EnumWindows(find_spotify_uwp, windows)
except pywintypes.error:
pass
# If Spotify isn't running the list will be empty
if len(windows) == 0:
raise SpotifyClosed
# Local songs may only have a title field
try:
artist, track = windows[0].split(" - ", 1)
except ValueError:
artist = ""
track = windows[0]
# The window title is the default one when paused
if windows[0].startswith("Spotify"):
raise SpotifyPaused
return track, artist
def get_info_linux():
"""
Uses the dbus API to get the data.
"""
import dbus
if not hasattr(get_info_linux, "session_bus"):
get_info_linux.session_bus = dbus.SessionBus()
try:
spotify_bus = get_info_linux.session_bus.get_object("org.mpris.MediaPlayer2.spotify", "/org/mpris/MediaPlayer2")
spotify_properties = dbus.Interface(spotify_bus, "org.freedesktop.DBus.Properties")
metadata = spotify_properties.Get("org.mpris.MediaPlayer2.Player", "Metadata")
except dbus.exceptions.DBusException:
raise SpotifyClosed
track = str(metadata["xesam:title"])
# When this function is called and Spotify hasn't finished setting up the dbus properties, the artist field
# is empty. This counts as if it weren't open because no data is loaded.
try:
artist = str(metadata["xesam:artist"][0])
except IndexError:
raise SpotifyClosed from None
status = str(spotify_properties.Get("org.mpris.MediaPlayer2.Player", "PlaybackStatus"))
if status.lower() != "playing":
raise SpotifyPaused
return track, artist
def get_info_mac():
"""
Runs an AppleScript script to get the data.
Exceptions aren't thrown inside get_info_mac because it automatically
opens Spotify if it's closed.
"""
apple_script_code = """
# Check if Spotify is currently running
if application "Spotify" is running then
# If this executes it means Spotify is currently running
getCurrentlyPlayingTrack()
end if
on getCurrentlyPlayingTrack()
tell application "Spotify"
set isPlaying to player state as string
set currentArtist to artist of current track as string
set currentTrack to name of current track as string
return {currentArtist, currentTrack, isPlaying}
end tell
end getCurrentlyPlayingTrack
"""
result = subprocess.run(
["osascript", "-e", apple_script_code],
capture_output=True,
encoding="utf-8",
)
if not result.stdout:
raise SpotifyClosed from None
# output is of the form "song, artist, status"
info = list(map(str.strip, result.stdout.split(",")))
if info[2].lower() != "playing":
raise SpotifyPaused
return info[1], info[0]
def get_info_web():
import SwSpotify.web_server
import os
import contextlib
from SwSpotify import WebData
with open(os.devnull, "w") as f, contextlib.redirect_stdout(f):
SwSpotify.web_server.run()
if not WebData.track:
raise SpotifyClosed
elif WebData.playState == "Play":
raise SpotifyPaused
else:
return WebData.track, WebData.artist
def current():
try:
if sys.platform.startswith("win"):
return get_info_windows()
elif sys.platform.startswith("darwin"):
return get_info_mac()
else:
return get_info_linux()
except SpotifyNotRunning as e:
try:
return get_info_web()
except SpotifyClosed:
raise e from None
def artist():
return current()[1]
def song():
return current()[0]