This repository has been archived by the owner on Nov 1, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
Copy pathairplay_class.py
297 lines (253 loc) · 7.75 KB
/
airplay_class.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
#!/usr/bin/env python
#
# Raspberry Pi Airplay receiver Class
# $Id: airplay_class.py,v 1.17 2017/07/02 08:51:38 bob Exp $
#
#
# Author : Bob Rathbone
# Site : http://www.bobrathbone.com
#
# This class uses shairport-sync from the following Github repository
# https://github.com/mikebrady/shairport-sync.git
#
# License: GNU V3, See https://www.gnu.org/copyleft/gpl.html
#
# Disclaimer: Software is provided as is and absolutly no warranties are implied or given.
# The authors shall not be liable for any loss or damage however caused.
#
import os
import sys
import pwd
import time
import ConfigParser
import socket
from log_class import Log
from config_class import Configuration
from translate_class import Translate
# Airplay (shairport-sync) pipe and files
AirplayDir = "/tmp/shairport"
AirplayInfo = AirplayDir + "/info"
AirplayMetadata = AirplayDir + "/metadata"
AirplayPipe = "/tmp/shairport-sync-metadata"
ShairportReader = "/usr/local/bin/shairport-sync-metadata-reader"
# Stored mixer volume
RadioLibDir = "/var/lib/radiod"
MixerVolumeFile = RadioLibDir + "/mixer_volume"
log = Log()
translate = Translate()
config = Configuration()
class AirplayReceiver:
mixerVolume = 100 # Mixer volume
saveMixerVolume = 0 # Saved Mixer volume for mute functions
mixerPreset = 100 # Mixer preset volume
mixerMuted = False # Mixer muted yes no
AirplayRunning = False
hostname = None
title = 'Uknown title'
interrupt = False
# Initialisation routine
def __init__(self):
if pwd.getpwuid(os.geteuid()).pw_uid > 0:
print "This program must be run with sudo or root permissions!"
sys.exit(1)
log.init('radio')
self.setupConfiguration()
return
# Set up configuration files
def setupConfiguration(self):
return
# Start Airlpay
def start(self):
log.message("Starting Airplay", log.DEBUG)
self.hostname = socket.gethostname()
if not os.path.isfile(MixerVolumeFile) or os.path.getsize(MixerVolumeFile) == 0:
self.execCommand ("echo " + str(100) + " > " + MixerVolumeFile)
self.execCommand("sudo rm -f " + AirplayPipe) # Delete old pipe
self.execCommand("sudo mkdir -p " + AirplayDir) # Make airplay info directory
self.execCommand("sudo chmod o+w " + AirplayDir)
self.execCommand("sudo touch " + AirplayMetadata)
self.execCommand("sudo chmod o+w " + AirplayMetadata)
cmd = "sudo service shairport-sync start"
log.message(cmd, log.DEBUG)
self.execCommand(cmd)
time.sleep(0.5) # Allow shairport-sync to start-up
self.setMixerVolume(self.getStoredMixerVolume())
self.execCommand( "sudo rm -f " + AirplayMetadata)
cmd = 'cat ' + AirplayPipe + ' | ' + ShairportReader + ' > ' + AirplayMetadata + ' &'
log.message(cmd, log.DEBUG)
self.execCommand(cmd)
self.airplay_scrolling = 0
self.AirplayRunning = True
log.message("Airplay running "+ str(self.AirplayRunning), log.DEBUG)
return self.AirplayRunning
# Stop Airlpay
def stop(self):
log.message("Stopping Airplay", log.DEBUG)
pid_shairport = 0
try:
pid_shairport = int(self.execCommand("pidof shairport-sync"))
except:
log.message("Airplay (shairport-sync) not running", log.DEBUG)
if pid_shairport > 0:
self.execCommand("sudo service shairport-sync stop")
time.sleep(1)
# Safety precaution to stop accidentally deleting complete file system
if len(AirplayDir) > 8:
self.execCommand("rm -f " + AirplayDir + "/*")
self.execCommand("sudo killall -q cat")
self.execCommand("sudo killall -q shairport-sync-metadata-reader")
self.AirplayRunning = False
return self.AirplayRunning
# Is Airplay running
def isRunning(self):
return self.AirplayRunning
# Airplay information scrolling - decides which line to control
def scroll(self):
scroll = self.airplay_scrolling
self.airplay_scrolling += 1
if self.airplay_scrolling > 2:
self.airplay_scrolling = 0
return scroll
# Get metadata info from reader string (called by info routine)
def extractData(self,data):
elements = data.split('"')
sInfo = elements[1]
return sInfo
# Get Airplay metadata information
def info(self):
artist = 'Unknown artist'
title = 'Unknown title'
album = 'Airplay:' + self.hostname
info = []
if os.path.isfile(AirplayMetadata):
cmd = "tail -8 " + AirplayMetadata + " > " + AirplayInfo
self.execCommand(cmd)
if os.path.isfile(AirplayInfo):
with open(AirplayInfo) as f:
for line in f:
if len(line) < 4:
next
if line.startswith("Title:"):
title = self.extractData(line)
elif line.startswith("Album Name:"):
album = self.extractData(line)
elif line.startswith("Artist:"):
artist = self.extractData(line)
info.append(translate.all(artist))
info.append(translate.all(title))
info.append(translate.all(album))
if title != self.title:
self.interrupt = True
self.title = title
else:
self.interrupt = False
return info
# Get interrupt (Title has changed)
def getInterrupt(self):
interrupt = self.interrupt
self.interrupt = False
return interrupt
# Get mixer volume
def getMixerVolume(self):
volume = self.mixerVolume
range = config.getVolumeRange()
return volume*range/100
# Increase mixer volume
def increaseMixerVolume(self):
volume = self.mixerVolume
range = config.getVolumeRange()
volume += int(100/range)
self.mixerVolume = self.setMixerVolume(volume)
return self.mixerVolume
# Decrease mixer volume
def decreaseMixerVolume(self):
volume = self.mixerVolume
range = config.getVolumeRange()
volume -= int(100/range)
self.mixerVolume = self.setMixerVolume(volume)
return self.mixerVolume
# Set mixer volume
def setMixerVolume(self,volume):
if volume > 100:
volume = 100
if volume < 0:
volume = 0
mixer_volume_id = config.getMixerVolumeID()
cmd = "amixer cset numid=" + str(mixer_volume_id) + " -- " + str(volume) + "%"
log.message(cmd,log.DEBUG)
self.execCommand(cmd)
if volume > 0:
self.mixerMuted = False
else:
self.mixerMuted = True
# Store Airplay mixer volume level
self.mixerVolume = volume
self.storeMixerVolume(self.mixerVolume)
return self.mixerVolume
# Is mixer muted
def mixerIsMuted(self):
return self.mixerMuted
# Mute the mixer
def muteMixer(self):
self.saveMixerVolume = self.mixerVolume
self.setMixerVolume(0)
self.mixerMuted = True
return self.mixerVolume
# unmute the mixer
def unmuteMixer(self):
self.setMixerVolume(self.saveMixerVolume)
self.mixerMuted = False
return self.mixerVolume
# Get the stored mixer volume
def getStoredMixerVolume(self):
volume = 100
if os.path.isfile(MixerVolumeFile):
try:
volume = int(self.execCommand("cat " + MixerVolumeFile) )
except ValueError:
volume = 100
else:
log.message("Error reading " + MixerVolumeFile, log.ERROR)
return volume
# Store mixer volume
def storeMixerVolume(self, volume):
if volume > 100:
volume = 100
if volume < 0:
volume = 0
try:
self.execCommand("echo " + str(volume) + " > " + MixerVolumeFile)
except:
log.message("Error writing " + MixerVolumeFile, log.ERROR)
return volume
# Get the mixer preset (Sound cards)
def setMixerPreset(self):
mixer_preset = config.getMixerPreset()
# If preset=0 (On-board audio) do not change mixer level as this is done by MPD
if mixer_preset != 0:
log.message("Set mixer preset volume " + str(mixer_preset), log.DEBUG)
self.setMixerVolume(mixer_preset)
return mixer_preset
# Execute system command
def execCommand(self,cmd):
p = os.popen(cmd)
return p.readline().rstrip('\n')
# End of class
### Test routine ###
if __name__ == "__main__":
print "Test airplay_class.py"
airplay = AirplayReceiver()
airplay.start()
airplay.setMixerVolume(70)
time.sleep(4)
airplay.setMixerVolume(90)
time.sleep(4)
airplay.setMixerVolume(80)
time.sleep(4)
airplay.muteMixer()
time.sleep(4)
airplay.unmuteMixer()
time.sleep(4)
airplay.stop()
# Exit
sys.exit(0)