aboutsummaryrefslogtreecommitdiffstats
path: root/mpris_to_text.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpris_to_text.py')
-rwxr-xr-xmpris_to_text.py301
1 files changed, 301 insertions, 0 deletions
diff --git a/mpris_to_text.py b/mpris_to_text.py
new file mode 100755
index 0000000..1284300
--- /dev/null
+++ b/mpris_to_text.py
@@ -0,0 +1,301 @@
1#!/usr/bin/python
2
3import sys
4import asyncio
5import re
6import threading
7import argparse
8import signal
9
10from blessed import Terminal
11
12import ravel
13
14
15loop = asyncio.get_event_loop()
16name_regex = re.compile("^org\.mpris\.MediaPlayer2\.")
17bus = None
18term = Terminal()
19
20refresh_cond = threading.Condition()
21refresh_flag = True
22exit_flag = False
23exit_task = asyncio.Future()
24
25players = []
26player_id_to_index = {}
27active_player = ""
28current_output = ""
29
30filename = ""
31meta_format = ""
32meta_format_artist = ""
33meta_format_title = ""
34meta_format_album = ""
35
36
37def track_string(metadata):
38 artist = metadata["xesam:artist"][1][0] if "xesam:artist" in metadata else ""
39 title = metadata["xesam:title"][1] if "xesam:title" in metadata else ""
40 album = metadata["xesam:album"][1] if "xesam:album" in metadata else ""
41
42 return meta_format.format(
43 artist = meta_format_artist.format(artist) if artist != "" else "",
44 title = meta_format_title.format(title) if title != "" else "",
45 album = meta_format_album.format(album) if title != "" else ""
46 )
47
48
49def write_track(track):
50 global filename
51 global current_output
52
53 current_output = track
54 f = open(filename, "w")
55 f.write(track)
56 f.close()
57
58
59@ravel.signal(name = "PropertiesChanged", in_signature = "sa{sv}as", args_keyword = "args")
60def playing_song_changed(args):
61 global refresh_cond
62 global refresh_flag
63
64 [ player, changed_properties, invalidated_properties ] = args
65 if "Metadata" in changed_properties:
66 write_track(track_string(changed_properties["Metadata"][1]))
67 with refresh_cond:
68 refresh_flag = True
69 refresh_cond.notify()
70
71
72@ravel.signal(name = "NameOwnerChanged", in_signature = "sss", args_keyword = "args")
73def dbus_name_owner_changed(args):
74 global refresh_cond
75 global refresh_flag
76
77 [ name, old_owner, new_owner ] = args
78 if name_regex.match(name):
79 get_players()
80 with refresh_cond:
81 refresh_flag = True
82 refresh_cond.notify()
83
84
85def set_active_player(player_id):
86 global bus
87 global active_player
88
89 if player_id in player_id_to_index:
90 active_player = player_id
91 elif len(player_id_to_index) != 0:
92 active_player = next(iter(player_id_to_index))
93 else:
94 active_player = ""
95
96 for (name, player_id) in players:
97 bus.unlisten_propchanged(
98 path = "/org/mpris/MediaPlayer2",
99 interface = name,
100 func = playing_song_changed,
101 fallback = True
102 )
103
104 if active_player != "":
105 bus.listen_propchanged(
106 path = "/org/mpris/MediaPlayer2",
107 interface = active_player,
108 func = playing_song_changed,
109 fallback = True
110 )
111
112 player_path = bus[active_player]["/org/mpris/MediaPlayer2"]
113 player_props = player_path.get_interface("org.freedesktop.DBus.Properties")
114
115 write_track(track_string(player_props.Get("org.mpris.MediaPlayer2.Player", "Metadata")[0][1]))
116 else:
117 write_track("")
118
119
120def get_players():
121 global players
122 global player_id_to_index
123
124 players = []
125 player_id_to_index = {}
126 bus_proxy = bus["org.freedesktop.DBus"]["/org/freedesktop/DBus"].get_interface("org.freedesktop.DBus")
127 names = bus_proxy.ListNames()
128
129 for name in names[0]:
130 if name_regex.match(name):
131 split_name = name.split(".")
132 id_start_index = len(split_name[0]) + len(split_name[1]) + len(split_name[2]) + 3
133
134 player_path = bus[name]["/org/mpris/MediaPlayer2"]
135 player_proxy = player_path.get_interface("org.mpris.MediaPlayer2")
136 player_id = name[id_start_index:]
137 try:
138 player_id = player_proxy.Identity
139 except AttributeError:
140 pass
141
142 players.append((name, player_id))
143 player_id_to_index[name] = len(players) - 1
144
145 set_active_player(active_player)
146
147
148def draw_menu():
149 global term
150 global players
151 global refresh_cond
152 global refresh_flag
153 global exit_flag
154 global current_output
155 global filename
156
157 while not exit_flag:
158 with refresh_cond:
159 while not refresh_flag and not exit_flag:
160 refresh_cond.wait()
161
162 refresh_flag = False
163
164 with term.fullscreen():
165 print(term.move(0, 0) + term.bold_bright_white_on_bright_black(("{0:<{width}}").format("MPRIS To Text", width=term.width)) + "\n")
166 print(term.move_x(2) + term.bold("Player: ") + term.move_up())
167
168 for i in range(len(players)):
169 player = players[i]
170 output = "%d: %s" % (i, player[1])
171
172 if players[player_id_to_index[active_player]][0] == player[0]:
173 print(term.move_x(10) + term.standout(output))
174 else:
175 print(term.move_x(10) + output)
176
177 print(term.move_x(2) + term.bold("File: ") + filename)
178 print(term.move_x(2) + term.bold("Output: ") + "\n".join(term.wrap(current_output, width=term.width - 10, subsequent_indent=" " * 10)))
179
180 print(term.move_x(0) + "\nEnter number to select player or q to exit." + term.move_up())
181
182 with term.fullscreen():
183 print(term.move(0, 0) + "Exiting...")
184
185
186def on_resize(*args):
187 global refresh_cond
188 global refresh_flag
189
190 with refresh_cond:
191 refresh_flag = True
192 refresh_cond.notify()
193
194
195def init_dbus():
196 global bus
197 global loop
198
199 bus = ravel.session_bus()
200 bus.attach_asyncio(loop)
201 bus.listen_signal(
202 path = "/org/freedesktop/DBus",
203 interface = "org.freedesktop.DBus",
204 name = "NameOwnerChanged",
205 func = dbus_name_owner_changed,
206 fallback = True
207 )
208
209 get_players()
210
211
212def init_blessed():
213 global bus
214 global term
215 global refresh_cond
216 global refresh_flag
217 global exit_flag
218
219 with term.cbreak():
220 val = None
221
222 while val not in (u'q', u'Q',):
223 val = term.inkey(timeout=5)
224
225 if not val or val.is_sequence or not val.isnumeric():
226 continue
227
228 if int(val) < len(players):
229 set_active_player(players[int(val)][0])
230 with refresh_cond:
231 refresh_flag = True
232 refresh_cond.notify()
233
234 with refresh_cond:
235 exit_flag = True
236 refresh_cond.notify()
237
238 exit_task.set_result(True)
239
240
241def read_args():
242 global filename
243 global meta_format
244 global meta_format_artist
245 global meta_format_title
246 global meta_format_album
247
248 parser = argparse.ArgumentParser(description="Write metadata from MPRIS-compliant media players into a text file.")
249 parser.add_argument("--file",
250 type = str,
251 dest = "filename",
252 default = "/tmp/mpris_info.txt",
253 help = "Full path to file (default: \"/tmp/mpris_info.txt\")"
254 )
255 parser.add_argument("--format-artist",
256 type = str,
257 dest = "format_artist",
258 default = "{} ",
259 help = "Format string for the artist part (default: \"{} \")"
260 )
261 parser.add_argument("--format-title",
262 type = str,
263 dest = "format_title",
264 default = "\"{}\"",
265 help = "Format string for the title part (default: \"\"{}\"\")"
266 )
267 parser.add_argument("--format-album",
268 type = str,
269 dest = "format_album",
270 default = " from \"{}\"",
271 help = "Format string for the album part (default: \" from \"{}\"\")"
272 )
273 parser.add_argument("--format",
274 type = str,
275 dest = "format",
276 default = "{artist}{title}{album} ",
277 help = "Full format string (default: \"{artist}{title}{album} \")"
278 )
279
280 args = parser.parse_args()
281 filename = args.filename
282 meta_format = args.format
283 meta_format_artist = args.format_artist
284 meta_format_title = args.format_title
285 meta_format_album = args.format_album
286
287
288
289read_args()
290
291signal.signal(signal.SIGWINCH, on_resize)
292
293init_dbus()
294
295blessed_thread = threading.Thread(target=init_blessed)
296blessed_thread.start()
297
298menu_thread = threading.Thread(target=draw_menu)
299menu_thread.start()
300
301loop.run_until_complete(exit_task)