-
Notifications
You must be signed in to change notification settings - Fork 7
/
mpd_client.py
405 lines (323 loc) · 10.8 KB
/
mpd_client.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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
from mpd import (MPDClient, CommandError)
import threading, time, math
class mpd_client:
def __init__(self, con_id, password):
# Create First MPD client(for status)
self.client = MPDClient()
# Create Second MPD client (for commands)
self.cmd_client = MPDClient()
# Connect to the MPD daemon (first client)
if (self.mpdConnect(self.client, con_id) == False):
print('Connection to MPD daemon (for status) failed!')
sys.exit(1)
# Connect to the MPD daemon (second client)
if (self.mpdConnect(self.cmd_client, con_id) == False):
print('Connection to MPD daemon (for commands) failed!')
sys.exit(1)
# If password is set, we have to authenticate
if password:
# First client
if (self.mpdAuth(self.client, password) == False):
print('MPD authentication (for status) failed!')
sys.exit(1)
# Second client
if (self.mpdAuth(self.cmd_client, password) == False):
print('MPD authentication (for commands) failed!')
sys.exit(1)
# Initialize data container
self.data = {
'artist': '', # Contains artist name or radio station name
'title': '', # Contains song title or when radio playing, "artist - title" format
'type': 0, # 0 - file, 1 - radio
'state': 0, # 0 - stopped, 1 - playing, 2 - paused
'volume': '', # Volume value
'shuffle': False, # True - ON, False - OFF
'repeat_all': False, # True - ON, False - OFF
'repeat_single': False, # True - ON, False - OFF
'elapsed_time': 0, # Song elapsed time
'total_time': 0, # Total song duration
'bitrate': 0, # Song/station bitrate (for example 320)
'playtime': 0, # Total playing time from last reboot
'uptime': 0 # Total uptime from last reboot
}
# Initialize LCD listener for changes
self.LCD_client = False
# Get first data update
self.updateData()
# Function for connecting to MPD daemon
def mpdConnect(self, client, con_id):
try:
client.connect(**con_id)
except SocketError:
return False
return True
# Function for authenticating to MPD if password is set
def mpdAuth(client, pwd):
try:
client.password(pwd)
except CommandError:
return False
return True
# Function for pinging cmd_client, we have to ping it or it will close connection
def mpdPing(self):
while True:
time.sleep(50) # We will ping it every 50 seconds
self.cmd_client.ping() # Ping it!
# Register LCD client listener
def register(self, lcd):
self.LCD_client = lcd
# Start MPD threads
def start(self):
# MPD Ping Thread - we have to ping cmd_client to prevent closing connection
self.mpdping_t = threading.Thread(target=self.mpdPing, args = ()) # Create thread for pinging MPD
self.mpdping_t.daemon = True # Yep, it's a daemon, when main thread finish, this one will finish too
self.mpdping_t.start() # Start it!
# Main Thread - Start main thread which waits for changes
self.mpd_t = threading.Thread(target=self.mpdMain, args = ()) # Create thread
self.mpd_t.daemon = True # Yep, it's a daemon, when main thread finish, this one will finish too
self.mpd_t.start() # Start it!
# Counter Thread - we have to count elapsed time, playtime and uptime
self.counter_t = threading.Thread(target=self.timeCounter, args = ()) # Create thread
self.counter_t.daemon = True # Yep, it's a daemon, when main thread finish, this one will finish too
self.counter_t.start() # Start it!
# Wait for all threads to finish
def join(self):
# Wait for MPD ping thread to finish
self.mpdping_t.join()
# Wait for main thread to finish
self.mpd_t.join()
# Wait for counter thread to finish
self.counter_t.join()
# Function for setting every first letter of word to uppercase
def toUpper(self, data):
#Declare list
lst = []
words = data.split(" ")
# Iterate through words
for word in words:
# Check if there's a word
if (len(word) == 0):
continue
# Check if first word is ( or -
if ((word[0] == '(' or word[0] == '-')):
if (len(word) >= 3):
lst.append(word[0] + word[1].upper() + word[2:]) # Add to list
elif (len(word) == 2):
lst.append(word[0] + word[1].upper()) # Add to list
else:
lst.append(word[0]) # Add to list
elif (len(word) >= 2):
lst.append(word[0].upper() + word[1:]) # Add to list
else:
lst.append(word[0].upper()) # Add to list
# Return joined list
return " ".join(lst)
# This function is called by buttons to give commands to MPD
def commands(self, command):
if (command == 'PLAY'):
if (self.data['state'] == 1):
self.cmd_client.pause(1)
else:
self.cmd_client.play()
elif (command == 'STOP'):
self.cmd_client.stop()
elif (command == 'NEXT'):
self.cmd_client.next()
elif (command == 'PREV'):
self.cmd_client.previous()
elif (command == 'VDN'):
# Get volume value
vol = self.data['volume'] - 5
if (vol < 0):
vol = 0
self.cmd_client.setvol(vol)
elif (command == 'VUP'):
# Get volume value
vol = self.data['volume'] + 5
if (vol > 100):
vol = 100
self.cmd_client.setvol(vol)
# Function for updating data
# Returns which option has been changed (shuffle - 0, repeat all - 1, repeat single - 2, nothing changed - -1)
def updateData(self):
# Nothing has changed so far
changed = -1
# Fetch volume
try:
self.data['volume'] = int(self.client.status()['volume'])
except KeyError:
self.data['volume'] = 0
# Get state
try:
state = self.client.status()['state']
except KeyError:
state = ''
# Get station
try:
station = self.client.currentsong()['name']
except KeyError:
station = ''
# Get title
try:
title = self.client.currentsong()['title']
except KeyError:
title = ''
# Get artist
try:
artist = self.client.currentsong()['artist']
except KeyError:
artist = ''
# Check whether the player is playing, paused or stopped
if (state == 'play'):
self.data['state'] = 1
elif (state == 'stop' or state == ''):
self.data['state'] = 0
elif (state == 'pause'):
self.data['state'] = 2
# Check if web radio is playing (radio station)
if(station != ''):
self.data['type'] = 1 # Set data type to radio
# Get radio station name in artist field, all first letters to uppercase
self.data['artist'] = self.toUpper(station)
# Check if there is no data
if (title == ''):
self.data['title'] = '[Unknown Song]'
# Else get artist - title in title field, all first letters to uppercase
else:
self.data['title'] = self.toUpper(title)
# Else, it's a file playing
else:
self.data['type'] = 0 # Set data type to file
# Check if there's no artist data
if (artist == ''):
self.data['artist'] = '[Unknown Artist]'
# Else, get artist name, all first letters to uppercase
else:
self.data['artist'] = self.toUpper(artist)
# Check if there's no song title data
if (title == ''):
self.data['title'] = '[Unknown Title]'
# Else get current song title, all first letters to uppercase
else:
self.data['title'] = self.toUpper(title)
# If player is playing or it's paused, get elapsed time, total track time and bitrate
if (self.data['state'] == 1 or self.data['state'] == 2):
# If file is playing, get total track time
if (self.data['type'] == 0):
try:
self.data['total_time'] = int(self.client.currentsong()['time'])
except KeyError:
self.data['total_time'] = 0
# Else, if radio is playing, there's no total track time
else:
self.data['total_time'] = 0
# Get elapsed time and convert it to seconds (int)
try:
self.data['elapsed_time'] = int(math.floor(float(self.client.status()['elapsed'])))
except KeyError:
self.data['elapsed_time'] = 0
# Get track/station bitrate
try:
self.data['bitrate'] = int(self.client.status()['bitrate'])
except KeyError:
self.data['bitrate'] = 0
# Else, put elapsed time to zero
else:
self.data['elapsed_time'] = 0
# Get total playtime and uptime from last reboot
try:
self.data['uptime'] = int(self.client.stats()['uptime'])
except KeyError:
self.data['uptime'] = 0
try:
self.data['playtime'] = int(self.client.stats()['playtime'])
except KeyError:
self.data['playtime'] = 0
# Get shuffle state
try:
temp = self.client.status()['random']
if (temp == '0'):
temp = False
elif (temp == '1'):
temp = True
# Check if shuffle has changed
if (temp != self.data['shuffle']):
changed = 0 # Shuffle has changed
self.data['shuffle'] = temp # Update data
except KeyError:
pass
# Get repeat all state
try:
temp = self.client.status()['repeat']
if (temp == '0'):
temp = False
elif (temp == '1'):
temp = True
# Check if repeat all has changed
if (temp != self.data['repeat_all']):
changed = 1 # Repeat all has changed
self.data['repeat_all'] = temp # Update data
except KeyError:
pass
# Get repeat single state
try:
temp = self.client.status()['single']
if (temp == '0'):
temp = False
elif (temp == '1'):
temp = True
# Check if repeat single has changed
if (temp != self.data['repeat_single']):
changed = 2 # Repeat single has changed
self.data['repeat_single'] = temp # Update data
except KeyError:
pass
# Return what has changed
return changed
# Function for counters (will be running in another thread)
def timeCounter(self):
while True:
time.sleep(1) # Wait one second
self.data['uptime'] += 1 # Increase uptime
# If player is playing
if (self.data['state'] == 1):
self.data['elapsed_time'] += 1 # Increase elapsed time
self.data['playtime'] += 1 # Increase total playtime
# Time is changed, notify LCD thread
self.LCD_client.time_change()
# Function which returns data for LCD display to get it
def getData(self):
return self.data
# Main function which is running in thread and waiting for changes
def mpdMain(self):
while True:
# Wait for any change from MPD
self.client.send_idle()
status = self.client.fetch_idle()
# Update data
changed = self.updateData()
# Check if some option changed
if (changed != -1):
temp = False
# Get changed option state
if (changed == 0):
# Shuffle changed
temp = self.data['shuffle']
elif (changed == 1):
# Repeat all changed
temp = self.data['repeat_all']
elif (changed == 2):
# Repeat single changed
temp = self.data['repeat_single']
self.LCD_client.play_mode_changed(changed, temp) # Notify LCD
# Check what has changed
try:
type = status[0]
except KeyError:
continue
# If volume has changed
if (type == 'mixer'):
self.LCD_client.volume_changed(self.data['volume'])
# Else, if song or something from player changed
elif (type == 'player'):
self.LCD_client.data_change()