diff options
-rw-r--r-- | README | 8 | ||||
-rwxr-xr-x | music_update.py | 230 |
2 files changed, 238 insertions, 0 deletions
@@ -0,0 +1,8 @@ | |||
1 | # MPRIS To Text | ||
2 | |||
3 | A python script that fetches data from MPRIS-compliant media players and saves it into a text file. | ||
4 | |||
5 | ## Required packages | ||
6 | |||
7 | - [blessed](https://github.com/jquast/blessed) | ||
8 | - [dbussy](https://github.com/ldo/dbussy) | ||
diff --git a/music_update.py b/music_update.py new file mode 100755 index 0000000..da2635f --- /dev/null +++ b/music_update.py | |||
@@ -0,0 +1,230 @@ | |||
1 | #!/usr/bin/python | ||
2 | |||
3 | import sys | ||
4 | import asyncio | ||
5 | import re | ||
6 | import threading | ||
7 | |||
8 | from blessed import Terminal | ||
9 | |||
10 | import dbussy as dbus | ||
11 | import ravel | ||
12 | |||
13 | |||
14 | loop = asyncio.get_event_loop() | ||
15 | name_regex = re.compile("^org\.mpris\.MediaPlayer2\.") | ||
16 | |||
17 | bus = None | ||
18 | term = Terminal() | ||
19 | refresh_cond = threading.Condition() | ||
20 | refresh_flag = True | ||
21 | exit_flag = False | ||
22 | exit_task = asyncio.Future() | ||
23 | |||
24 | players = [] | ||
25 | player_id_to_index = {} | ||
26 | active_player = "" | ||
27 | current_output = "" | ||
28 | |||
29 | |||
30 | def track_string(metadata): | ||
31 | artist = metadata["xesam:artist"][1][0] if "xesam:artist" in metadata else "" | ||
32 | title = metadata["xesam:title"][1] if "xesam:title" in metadata else "" | ||
33 | album = metadata["xesam:album"][1] if "xesam:album" in metadata else "" | ||
34 | |||
35 | track = "" | ||
36 | if artist != "": track = artist + " " | ||
37 | track = track + "\"" + title + "\"" | ||
38 | if album != "": track = track + " from \"" + album + "\"" | ||
39 | |||
40 | return track + " " | ||
41 | |||
42 | |||
43 | def write_track(track): | ||
44 | global current_output | ||
45 | |||
46 | current_output = track | ||
47 | f = open("/mnt/Data/stream_info.txt", "w") | ||
48 | f.write(track) | ||
49 | f.close() | ||
50 | |||
51 | |||
52 | @ravel.signal(name = "PropertiesChanged", in_signature = "sa{sv}as", args_keyword = "args") | ||
53 | def playing_song_changed(args): | ||
54 | global refresh_cond | ||
55 | global refresh_flag | ||
56 | |||
57 | [ player, changed_properties, invalidated_properties ] = args | ||
58 | if "Metadata" in changed_properties: | ||
59 | write_track(track_string(changed_properties["Metadata"][1])) | ||
60 | with refresh_cond: | ||
61 | refresh_flag = True | ||
62 | refresh_cond.notify() | ||
63 | |||
64 | |||
65 | @ravel.signal(name = "NameOwnerChanged", in_signature = "sss", args_keyword = "args") | ||
66 | def dbus_name_owner_changed(args): | ||
67 | global refresh_cond | ||
68 | global refresh_flag | ||
69 | |||
70 | [ name, old_owner, new_owner ] = args | ||
71 | if name_regex.match(name): | ||
72 | get_players() | ||
73 | with refresh_cond: | ||
74 | refresh_flag = True | ||
75 | refresh_cond.notify() | ||
76 | |||
77 | |||
78 | def set_active_player(player_id): | ||
79 | global bus | ||
80 | global active_player | ||
81 | |||
82 | if player_id in player_id_to_index: | ||
83 | active_player = player_id | ||
84 | elif len(player_id_to_index) != 0: | ||
85 | active_player = next(iter(player_id_to_index)) | ||
86 | else: | ||
87 | active_player = "" | ||
88 | |||
89 | for (name, player_id) in players: | ||
90 | bus.unlisten_propchanged( | ||
91 | path = "/org/mpris/MediaPlayer2", | ||
92 | interface = name, | ||
93 | func = playing_song_changed, | ||
94 | fallback = True | ||
95 | ) | ||
96 | |||
97 | if active_player != "": | ||
98 | bus.listen_propchanged( | ||
99 | path = "/org/mpris/MediaPlayer2", | ||
100 | interface = active_player, | ||
101 | func = playing_song_changed, | ||
102 | fallback = True | ||
103 | ) | ||
104 | |||
105 | player_path = bus[active_player]["/org/mpris/MediaPlayer2"] | ||
106 | player_props = player_path.get_interface("org.freedesktop.DBus.Properties") | ||
107 | |||
108 | write_track(track_string(player_props.Get("org.mpris.MediaPlayer2.Player", "Metadata")[0][1])) | ||
109 | else: | ||
110 | write_track("") | ||
111 | |||
112 | |||
113 | def get_players(): | ||
114 | global players | ||
115 | global player_id_to_index | ||
116 | |||
117 | players = [] | ||
118 | player_id_to_index = {} | ||
119 | bus_proxy = bus["org.freedesktop.DBus"]["/org/freedesktop/DBus"].get_interface("org.freedesktop.DBus") | ||
120 | names = bus_proxy.ListNames() | ||
121 | |||
122 | for name in names[0]: | ||
123 | if name_regex.match(name): | ||
124 | split_name = name.split(".") | ||
125 | id_start_index = len(split_name[0]) + len(split_name[1]) + len(split_name[2]) + 3 | ||
126 | |||
127 | player_path = bus[name]["/org/mpris/MediaPlayer2"] | ||
128 | player_proxy = player_path.get_interface("org.mpris.MediaPlayer2") | ||
129 | player_id = name[id_start_index:] | ||
130 | try: | ||
131 | player_id = player_proxy.Identity | ||
132 | except AttributeError: | ||
133 | pass | ||
134 | |||
135 | players.append((name, player_id)) | ||
136 | player_id_to_index[name] = len(players) - 1 | ||
137 | |||
138 | set_active_player(active_player) | ||
139 | |||
140 | |||
141 | def draw_menu(): | ||
142 | global term | ||
143 | global players | ||
144 | global refresh_cond | ||
145 | global refresh_flag | ||
146 | global exit_flag | ||
147 | global current_output | ||
148 | |||
149 | while not exit_flag: | ||
150 | with refresh_cond: | ||
151 | while not refresh_flag and not exit_flag: | ||
152 | refresh_cond.wait() | ||
153 | |||
154 | refresh_flag = False | ||
155 | |||
156 | with term.fullscreen(): | ||
157 | print(term.move(0, 0) + term.bold("MPRIS to Text") + "\n") | ||
158 | |||
159 | for i in range(len(players)): | ||
160 | player = players[i] | ||
161 | output = "%d: %s" % (i, player[1]) | ||
162 | |||
163 | if players[player_id_to_index[active_player]][0] == player[0]: | ||
164 | print(term.move_x(8) + term.standout_bold(output)) | ||
165 | else: | ||
166 | print(term.move_x(8) + output) | ||
167 | |||
168 | print(term.move_x(0) + "\n" + term.bold("Current output:") + " " + current_output) | ||
169 | |||
170 | print(term.move_x(0) + "\n\nEnter number to select player or q to exit." + term.move_up()) | ||
171 | |||
172 | with term.fullscreen(): | ||
173 | print(term.move(0, 0) + "Exiting...") | ||
174 | |||
175 | |||
176 | def init_dbus(): | ||
177 | global bus | ||
178 | global loop | ||
179 | |||
180 | bus = ravel.session_bus() | ||
181 | bus.attach_asyncio(loop) | ||
182 | bus.listen_signal( | ||
183 | path = "/org/freedesktop/DBus", | ||
184 | interface = "org.freedesktop.DBus", | ||
185 | name = "NameOwnerChanged", | ||
186 | func = dbus_name_owner_changed, | ||
187 | fallback = True | ||
188 | ) | ||
189 | |||
190 | get_players() | ||
191 | |||
192 | |||
193 | def init_blessed(): | ||
194 | global bus | ||
195 | global term | ||
196 | global refresh_cond | ||
197 | global refresh_flag | ||
198 | global exit_flag | ||
199 | |||
200 | with term.cbreak(): | ||
201 | val = None | ||
202 | |||
203 | while val not in (u'q', u'Q',): | ||
204 | val = term.inkey(timeout=5) | ||
205 | |||
206 | if not val or val.is_sequence or not val.isnumeric(): | ||
207 | continue | ||
208 | |||
209 | if int(val) < len(players): | ||
210 | set_active_player(players[int(val)][0]) | ||
211 | with refresh_cond: | ||
212 | refresh_flag = True | ||
213 | refresh_cond.notify() | ||
214 | |||
215 | with refresh_cond: | ||
216 | exit_flag = True | ||
217 | refresh_cond.notify() | ||
218 | |||
219 | exit_task.set_result(True) | ||
220 | |||
221 | |||
222 | init_dbus() | ||
223 | |||
224 | blessed_thread = threading.Thread(target=init_blessed) | ||
225 | blessed_thread.start() | ||
226 | |||
227 | menu_thread = threading.Thread(target=draw_menu) | ||
228 | menu_thread.start() | ||
229 | |||
230 | loop.run_until_complete(exit_task) | ||