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) | ||
