aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rwxr-xr-xmpris_to_text.py412
1 files changed, 205 insertions, 207 deletions
diff --git a/mpris_to_text.py b/mpris_to_text.py
index 16ee1e0..24baabf 100755
--- a/mpris_to_text.py
+++ b/mpris_to_text.py
@@ -16,230 +16,227 @@ from blessed import Terminal
16dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) 16dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
17 17
18 18
19name_regex = re.compile("^org\.mpris\.MediaPlayer2\.") 19class MetaWriter:
20bus = dbus.SessionBus() 20 filename = ""
21term = Terminal() 21 output_format = ""
22 22 output_format_artist = ""
23refresh_cond = threading.Condition() 23 output_format_title = ""
24refresh_flag = True 24 output_format_album = ""
25exit_flag = False 25 last_output = ""
26 26
27players = [] 27 def __init__(self, filename, output_format, output_format_artist, output_format_title, output_format_album):
28player_id_to_index = {} 28 self.filename = filename
29active_player = "" 29 self.output_format = output_format
30current_output = "" 30 self.output_format_album = output_format_album
31playing_song_changed_signal_receiver = None 31 self.output_format_artist = output_format_artist
32 32 self.output_format_title = output_format_title
33filename = "" 33
34meta_format = "" 34 def write_meta(self, metadata):
35meta_format_artist = "" 35 artist = metadata["xesam:artist"][0] if "xesam:artist" in metadata else ""
36meta_format_title = "" 36 title = metadata["xesam:title"] if "xesam:title" in metadata else ""
37meta_format_album = "" 37 album = metadata["xesam:album"] if "xesam:album" in metadata else ""
38 38
39 39 self.write(self.output_format.format(
40def track_string(metadata): 40 artist = self.output_format_artist.format(artist) if artist != "" else "",
41 artist = metadata["xesam:artist"][0] if "xesam:artist" in metadata else "" 41 title = self.output_format_title.format(title) if title != "" else "",
42 title = metadata["xesam:title"] if "xesam:title" in metadata else "" 42 album = self.output_format_album.format(album) if album != "" else ""
43 album = metadata["xesam:album"] if "xesam:album" in metadata else "" 43 ))
44 44
45 return meta_format.format( 45 def write(self, text):
46 artist = meta_format_artist.format(artist) if artist != "" else "", 46 self.last_output = text
47 title = meta_format_title.format(title) if title != "" else "", 47 f = open(self.filename, "w")
48 album = meta_format_album.format(album) if album != "" else "" 48 f.write(text)
49 ) 49 f.close()
50 50
51 51
52def write_track(track): 52class PlayerSelector(threading.Thread):
53 global filename 53 service_regex = re.compile("^org\.mpris\.MediaPlayer2\.")
54 global current_output 54 bus = None
55 loop = None
56 signal_receiver = None
57 players = {}
58 players_indexes = []
59 active_player = ""
60 menu = None
61 meta_writer = None
62
63 def __init__(self, meta_writer, menu=None):
64 super().__init__()
65
66 self.bus = dbus.SessionBus()
67 self.meta_writer = meta_writer
68 self.menu = menu
69
70 def set_menu(self, menu):
71 self.menu = menu
55 72
56 current_output = track 73 def get_players(self):
57 f = open(filename, "w") 74 self.players = {}
58 f.write(track) 75 self.players_indexes = []
59 f.close() 76
60 77 bus_path = self.bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
61 78 bus_proxy = dbus.Interface(bus_path, "org.freedesktop.DBus")
62def playing_song_changed(player, changed_properties, invalidated_properties): 79
63 global refresh_cond 80 for service in bus_proxy.ListNames():
64 global refresh_flag 81 if self.service_regex.match(service):
65 82 split_service = service.split(".")
66 if "Metadata" in changed_properties: 83 name_start_index = len(split_service[0]) + len(split_service[1]) + len(split_service[2]) + 3
67 write_track(track_string(changed_properties["Metadata"])) 84
68 with refresh_cond: 85 player_path = self.bus.get_object(service, "/org/mpris/MediaPlayer2")
69 refresh_flag = True 86 player_props = dbus.Interface(player_path, "org.freedesktop.DBus.Properties")
70 refresh_cond.notify() 87 player_name = service[name_start_index:]
71 88 try:
72 89 player_name = player_props.Get("org.mpris.MediaPlayer2.Player", "Identity")
73def dbus_name_owner_changed(name, old_owner, new_owner): 90 except dbus.exceptions.DBusException:
74 global refresh_cond 91 pass
75 global refresh_flag 92
76 93 self.players[service] = player_name
77 if name_regex.match(name): 94 self.players_indexes.append(service)
78 get_players() 95
79 with refresh_cond: 96 self.set_active_player(self.active_player)
80 refresh_flag = True 97
81 refresh_cond.notify() 98 def set_active_player_index(self, player_index):
82 99 if player_index < len(self.players_indexes):
83 100 self.set_active_player(self.players_indexes[player_index])
84def set_active_player(player_id): 101
85 global bus 102 def set_active_player(self, player_id):
86 global players 103 if player_id in self.players:
87 global player_id_to_index 104 self.active_player = player_id
88 global active_player 105 elif len(self.players) != 0:
89 global playing_song_changed_signal_receiver 106 self.active_player = next(iter(self.players.keys()))
90 107 else:
91 if player_id in player_id_to_index: 108 self.active_player = ""
92 active_player = player_id 109
93 elif len(player_id_to_index) != 0: 110 if self.signal_receiver is not None:
94 active_player = next(iter(player_id_to_index)) 111 self.signal_receiver.remove()
95 else: 112
96 active_player = "" 113 if self.active_player != "":
97 114 self.signal_receiver = self.bus.add_signal_receiver(
98 if playing_song_changed_signal_receiver is not None: 115 handler_function = self.playing_song_changed,
99 playing_song_changed_signal_receiver.remove() 116 bus_name = self.active_player,
100 117 dbus_interface = "org.freedesktop.DBus.Properties",
101 if active_player != "": 118 signal_name = "PropertiesChanged",
102 playing_song_changed_signal_receiver = bus.add_signal_receiver( 119 path = "/org/mpris/MediaPlayer2"
103 handler_function = playing_song_changed, 120 )
104 bus_name = active_player, 121
105 dbus_interface = "org.freedesktop.DBus.Properties", 122 player_path = self.bus.get_object(self.active_player, "/org/mpris/MediaPlayer2")
106 signal_name = "PropertiesChanged",
107 path = "/org/mpris/MediaPlayer2"
108 )
109
110 player_path = bus.get_object(active_player, "/org/mpris/MediaPlayer2")
111 player_props = dbus.Interface(player_path, "org.freedesktop.DBus.Properties")
112
113 write_track(track_string(player_props.Get("org.mpris.MediaPlayer2.Player", "Metadata")))
114 else:
115 write_track("")
116
117
118def get_players():
119 global players
120 global player_id_to_index
121 global active_player
122
123 players = []
124 player_id_to_index = {}
125 bus_path = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
126 bus_proxy = dbus.Interface(bus_path, "org.freedesktop.DBus")
127 names = bus_proxy.ListNames()
128
129 for name in names:
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.get_object(name, "/org/mpris/MediaPlayer2")
135 player_props = dbus.Interface(player_path, "org.freedesktop.DBus.Properties") 123 player_props = dbus.Interface(player_path, "org.freedesktop.DBus.Properties")
136 player_id = name[id_start_index:]
137 try:
138 player_id = player_props.Get("org.mpris.MediaPlayer2.Player", "Identity")
139 except dbus.exceptions.DBusException:
140 pass
141 124
142 players.append((name, player_id)) 125 self.meta_writer.write_meta(player_props.Get("org.mpris.MediaPlayer2.Player", "Metadata"))
143 player_id_to_index[name] = len(players) - 1 126 else:
127 self.meta_writer.write("")
144 128
145 set_active_player(active_player)
146 129
130 def run(self):
131 bus_path = self.bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus")
132 bus_proxy = dbus.Interface(bus_path, "org.freedesktop.DBus")
133 bus_proxy.connect_to_signal("NameOwnerChanged", self.dbus_name_owner_changed)
147 134
148def draw_menu(): 135 self.get_players()
149 global term 136 self.menu.refresh()
150 global players
151 global refresh_cond
152 global refresh_flag
153 global exit_flag
154 global current_output
155 global filename
156 137
157 while not exit_flag: 138 self.loop = GLib.MainLoop()
158 with refresh_cond: 139 self.loop.run()
159 while not refresh_flag and not exit_flag: 140
160 refresh_cond.wait() 141 def quit(self):
142 self.loop.quit()
143
144 def dbus_name_owner_changed(self, name, old_owner, new_owner):
145 if self.service_regex.match(name):
146 self.get_players()
147 self.menu.refresh()
161 148
162 refresh_flag = False 149 def playing_song_changed(self, player, changed_properties, invalidated_properties):
150 if "Metadata" in changed_properties:
151 self.meta_writer.write_meta(changed_properties["Metadata"])
152 self.menu.refresh()
163 153
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 154
172 if players[player_id_to_index[active_player]][0] == player[0]: 155class Menu(threading.Thread):
173 print(term.move_x(10) + term.standout(output)) 156 refresh_cond = threading.Condition()
174 else: 157 refresh_flag = True
175 print(term.move_x(10) + output) 158 exit_flag = False
159 player_selector = None
160 meta_writer = None
176 161
177 print(term.move_x(2) + term.bold("File: ") + filename) 162 def __init__(self, term, player_selector, meta_writer):
178 print(term.move_x(2) + term.bold("Output: ") + "\n".join(term.wrap(current_output, width=term.width - 10, subsequent_indent=" " * 10))) 163 super().__init__()
179 164
180 print(term.move_x(0) + "\nEnter number to select player or q to exit." + term.move_up()) 165 self.player_selector = player_selector
166 self.meta_writer = meta_writer
181 167
182 with term.fullscreen(): 168 def refresh(self, exit_flag=False):
183 print(term.move(0, 0) + "Exiting...") 169 with self.refresh_cond:
170 self.refresh_flag = True
171 self.exit_flag = self.exit_flag or exit_flag
172 self.refresh_cond.notify()
184 173
174 def run(self):
175 while not self.exit_flag:
176 with self.refresh_cond:
177 while not self.refresh_flag and not self.exit_flag:
178 self.refresh_cond.wait()
185 179
186def on_resize(*args): 180 self.refresh_flag = False
187 global refresh_cond 181
188 global refresh_flag 182 with term.fullscreen():
183 print(term.move(0, 0) + term.bold_bright_white_on_bright_black(("{0:<{width}}").format("MPRIS To Text", width=term.width)) + "\n")
184 print(term.move_x(2) + term.bold("Player: ") + term.move_up())
185
186 for i, (id, name) in enumerate(self.player_selector.players.items()):
187 output = "%d: %s" % (i, name)
189 188
190 with refresh_cond: 189 if id == self.player_selector.active_player:
191 refresh_flag = True 190 print(term.move_x(10) + term.standout(output))
192 refresh_cond.notify() 191 else:
192 print(term.move_x(10) + output)
193 193
194 print(term.move_x(2) + term.bold("File: ") + self.meta_writer.filename)
195 print(term.move_x(2) + term.bold("Output: ") + "\n".join(term.wrap(self.meta_writer.last_output, width=term.width - 10, subsequent_indent=" " * 10)))
194 196
195def init_dbus(): 197 print(term.move_x(0) + "\nEnter number to select player or q to exit." + term.move_up())
196 global bus
197 198
198 bus_path = bus.get_object("org.freedesktop.DBus", "/org/freedesktop/DBus") 199 with term.fullscreen():
199 bus_proxy = dbus.Interface(bus_path, "org.freedesktop.DBus") 200 print(term.move(0, 0) + "Exiting...")
200 201
201 bus_proxy.connect_to_signal("NameOwnerChanged", dbus_name_owner_changed)
202 202
203 get_players() 203class Input(threading.Thread):
204 player_selector = None
205 meta_writer = None
206 menu = None
204 207
208 def __init__(self, term, player_selector, meta_writer, menu):
209 super().__init__()
205 210
206def init_blessed(): 211 self.player_selector = player_selector
207 global bus 212 self.meta_writer = meta_writer
208 global term 213 self.menu = menu
209 global refresh_cond
210 global refresh_flag
211 global exit_flag
212 global loop
213 214
214 with term.cbreak(): 215 def run(self):
215 val = None 216 with term.cbreak():
217 val = None
216 218
217 while val not in (u'q', u'Q',): 219 while val not in (u'q', u'Q',):
218 val = term.inkey(timeout=5) 220 val = term.inkey(timeout=5)
219 221
220 if not val or val.is_sequence or not val.isnumeric(): 222 if not val or val.is_sequence or not val.isnumeric():
221 continue 223 continue
222 224
223 if int(val) < len(players): 225 if int(val) < len(self.player_selector.players):
224 set_active_player(players[int(val)][0]) 226 self.player_selector.set_active_player_index(int(val))
225 with refresh_cond: 227 self.menu.refresh()
226 refresh_flag = True
227 refresh_cond.notify()
228 228
229 with refresh_cond: 229 self.menu.refresh(True)
230 exit_flag = True 230 self.player_selector.quit()
231 refresh_cond.notify()
232 231
233 loop.quit()
234 232
233def on_resize(*args):
234 global menu_thread
235
236 menu_thread.refresh()
235 237
236def read_args():
237 global filename
238 global meta_format
239 global meta_format_artist
240 global meta_format_title
241 global meta_format_album
242 238
239def create_writer():
243 parser = argparse.ArgumentParser(description="Write metadata from MPRIS-compliant media players into a text file.") 240 parser = argparse.ArgumentParser(description="Write metadata from MPRIS-compliant media players into a text file.")
244 parser.add_argument("--file", 241 parser.add_argument("--file",
245 type = str, 242 type = str,
@@ -272,26 +269,27 @@ def read_args():
272 help = "Full format string (default: \"{artist}{title}{album} \")" 269 help = "Full format string (default: \"{artist}{title}{album} \")"
273 ) 270 )
274 271
275 args = parser.parse_args() 272 args = parser.parse_args()
276 filename = args.filename
277 meta_format = args.format
278 meta_format_artist = args.format_artist
279 meta_format_title = args.format_title
280 meta_format_album = args.format_album
281 273
274 return MetaWriter(
275 filename = args.filename,
276 output_format = args.format,
277 output_format_artist = args.format_artist,
278 output_format_title = args.format_title,
279 output_format_album = args.format_album
280 )
282 281
283 282
284read_args() 283term = Terminal()
284meta_writer = create_writer()
285 285
286signal.signal(signal.SIGWINCH, on_resize) 286signal.signal(signal.SIGWINCH, on_resize)
287 287
288init_dbus() 288player_selector = PlayerSelector(meta_writer)
289menu_thread = Menu(term, player_selector, meta_writer)
290player_selector.set_menu(menu_thread)
291input_thread = Input(term, player_selector, meta_writer, menu_thread)
289 292
290blessed_thread = threading.Thread(target=init_blessed) 293player_selector.start()
291blessed_thread.start()
292
293menu_thread = threading.Thread(target=draw_menu)
294menu_thread.start() 294menu_thread.start()
295 295input_thread.start()
296loop = GLib.MainLoop()
297loop.run()