Vesion 019
[rox-musicbox.git] / player.py
blob0209478a2c3607e75b9c589bb2d514170007690e
1 """
2 player.py (play either ogg or mp3 files)
4 based on ogg123.py By Andrew Chatham Based on ogg123.c by Keneth Arnold.
5 also based on mpg123.py from the pymad module (no attribution in those sources)
7 Portions, Copyright 2004 Kenneth Hayber <khayber@socal.rr.com>
8 All rights reserved.
10 This program is free software; you can redistribute it and/or modify
11 it under the terms of the GNU General Public License as published by
12 the Free Software Foundation; either version 2 of the License.
14 This program is distributed in the hope that it will be useful
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 """
24 import os, time, string, sys, Queue
26 try:
27 import ogg.vorbis
28 HAVE_OGG = True
29 except:
30 HAVE_OGG = False
31 print 'No OGG support!'
33 try:
34 import mad
35 HAVE_MAD = True
36 except:
37 HAVE_MAD = False
38 print 'No MP3 support!'
40 try:
41 import ossaudiodev
42 HAVE_OSS = True
43 except:
44 print 'No OSS support!!'
45 HAVE_OSS = False
47 try:
48 import ao
49 HAVE_AO = True
50 except:
51 HAVE_AO = False
53 if not HAVE_AO and not HAVE_OSS:
54 print 'No OSS and no AO support Falling back to linuxaudiodev which sucks!!'
55 import linuxaudiodev
58 TYPE_OGG = 'application/ogg'
59 TYPE_MP3 = 'audio/x-mp3'
60 TYPE_LIST = [TYPE_OGG, TYPE_MP3]
63 class Player:
64 """
65 A class to playback MP3 and OGG sound files to an audio device.
66 Supports/depends on libmad, libvorbis, libogg, libao, pyao,
67 linuxaudiodev and/or ossaudiodev
68 """
69 state = 'stop'
70 remain = 0
71 elapse = 0
72 last_elapse = 0
73 total_time = 0
74 seek_val = 0
76 def __init__(self, id=None, buffersize=4096):
77 """Initialize the Player instance"""
78 if HAVE_AO:
79 if id is None:
80 self.id = ao.driver_id('esd') #also can be 'oss', 'alsa', 'alsa09', etc.
81 else:
82 self.id = id
84 self.buffersize = buffersize
85 self.dev = None
86 self.queue = Queue.Queue(5)
88 def open(self, rate=44100, channels=2):
89 """Open and configure the audio device driver"""
90 bits=16
91 #try 3 times to open the device, then give up.
92 for x in range(3):
93 try:
94 if HAVE_AO:
95 self.dev = ao.AudioDevice(self.id, bits, rate, channels)
96 elif HAVE_OSS:
97 self.dev = ossaudiodev.open('w')
98 self.dev.setparameters(ossaudiodev.AFMT_S16_LE, channels, rate)
99 else:
100 self.dev = linuxaudiodev.open('w')
101 self.dev.setparameters(rate, bits, channels, linuxaudiodev.AFMT_S16_NE)
102 break
103 except:
104 time.sleep(1)
106 def close(self):
107 """Close the current device if open and delete it"""
108 if self.dev:
109 if HAVE_AO:
110 self.dev = None
111 elif HAVE_OSS:
112 self.dev.close()
113 else:
114 self.dev = None
117 def write(self, buff, bytes):
118 """Write data to the audio device"""
119 if HAVE_AO:
120 self.dev.play(buff, bytes)
121 elif HAVE_OSS:
122 self.dev.write(buff)
123 else:
124 while self.dev.obuffree() < bytes:
125 time.sleep(0.2)
126 self.dev.write(buff[:bytes])
128 def play(self, name, type):
129 """Push the song info on the queue"""
130 if (type == TYPE_OGG and not HAVE_OGG):
131 raise TypeError, _('You must have OGG support to play ogg files (%s).') % name
132 if (type == TYPE_MP3 and not HAVE_MAD):
133 raise TypeError, _('You must have MAD support to play mp3 files (%s).') % name
134 self.queue.put((name, type))
136 def run(self):
137 """Check the filename and type and start the appropriate play-loop"""
138 while True:
139 (name, type) = self.queue.get()
140 if os.path.isfile(name):
141 if (type == TYPE_OGG and HAVE_OGG):
142 vf = ogg.vorbis.VorbisFile(name)
143 #self.info_ogg(vf)
144 self.start_ogg(vf)
146 elif (type == TYPE_MP3 and HAVE_MAD):
147 mf = mad.MadFile(name, self.buffersize)
148 #self.info_mad(mf)
149 self.start_mad(mf)
151 else:
152 raise ValueError, 'Unsupported file (%s).' % name
153 else:
154 raise SyntaxError, 'play takes a filename.'
156 def stop(self):
157 """Set a flag telling the current play-loop to exit and close the device"""
158 self.state = 'stop'
159 while True:
160 try: self.queue.get_nowait()
161 except Queue.Empty: break
163 def pause(self):
164 """Pause playback (works as a toggle between play and pause)"""
165 if self.state == 'play':
166 self.state = 'pause'
167 elif self.state == 'pause':
168 self.state = 'play'
170 def seek(self, percent):
171 """Jump to a specific point in the song by percent"""
172 self.seek_val = percent
174 def set_volume(self, volume):
175 """Set the PCM volume to a new value"""
176 vol = int(volume)
177 if HAVE_OSS:
178 mixer = ossaudiodev.openmixer()
179 if mixer != None:
180 mixer.set(ossaudiodev.SOUND_MIXER_PCM, (vol, vol))
181 else:
182 pass
184 def get_volume(self):
185 """Return the current volume setting"""
186 if HAVE_OSS:
187 mixer = ossaudiodev.openmixer()
188 if mixer != None:
189 vol = mixer.get(ossaudiodev.SOUND_MIXER_PCM)
190 return float(max(vol[0], vol[1]))
191 else:
192 return 0
195 #############################
196 # OGG-specific stuff
197 #############################
198 def info_ogg(self, vf):
199 """Display some OGG information"""
200 print vf.comment().as_dict()
202 def start_ogg(self, vf):
203 """Open the audio device and start playing an OGG file"""
204 self.state = 'play'
206 self.total_time = int(vf.time_total(0))
207 self.remain = self.total_time
208 self.elapse = 0
209 last_elapse = 0
211 try:
212 vi = vf.info()
213 self.open(vi.rate, vi.channels)
215 while self.state == 'play' or self.state == 'pause':
216 if self.state == 'pause':
217 time.sleep(1)
218 elif self.seek_val:
219 vf.time_seek(float(self.total_time * self.seek_val))
220 self.seek_val = 0
221 else:
222 #for some reason with ossaudiodev a buffer greater
223 #than 512 bytes causes problems with ogg, smaller seems better
224 # but I'm afraid performance will suck.
225 if not HAVE_AO and HAVE_OSS:
226 self.buffersize = 256
227 (buff, bytes, bit) = vf.read(self.buffersize)
228 if bytes == 0:
229 self.state = 'eof'
230 self.elapse = self.total_time
231 last_elapse = 0
232 self.remain = 0
233 else:
234 self.elapse = int(vf.time_tell())
235 self.remain = max(0, self.total_time - self.elapse)
236 self.write(buff, bytes)
237 if self.elapse != last_elapse or self.state == 'pause':
238 last_elapse = self.elapse
239 except:
240 self.state = 'stop'
242 self.close()
245 #############################
246 # MAD/MP3-specific stuff
247 #############################
248 def info_mad(self, mf):
249 """Display some MP3 information"""
250 if mf.layer() == mad.LAYER_I:
251 print "MPEG Layer I"
252 elif mf.layer() == mad.LAYER_II:
253 print "MPEG Layer II"
254 elif mf.layer() == mad.LAYER_III:
255 print "MPEG Layer III"
256 else:
257 print "unexpected layer value"
259 if mf.mode() == mad.MODE_SINGLE_CHANNEL:
260 print "single channel"
261 elif mf.mode() == mad.MODE_DUAL_CHANNEL:
262 print "dual channel"
263 elif mf.mode() == mad.MODE_JOINT_STEREO:
264 print "joint (MS/intensity) stereo"
265 elif mf.mode() == mad.MODE_STEREO:
266 print "normal L/R stereo"
267 else:
268 print "unexpected mode value"
270 if mf.emphasis() == mad.EMPHASIS_NONE:
271 print "no emphasis"
272 elif mf.emphasis() == mad.EMPHASIS_50_15_US:
273 print "50/15us emphasis"
274 elif mf.emphasis() == mad.EMPHASIS_CCITT_J_17:
275 print "CCITT J.17 emphasis"
276 else:
277 print "unexpected emphasis value"
279 print "bitrate %lu bps" % mf.bitrate()
280 print "samplerate %d Hz" % mf.samplerate()
281 millis = mf.total_time()
282 secs = millis / 1000
283 print "total time %d ms (%dm%2ds)" % (millis, secs / 60, secs % 60)
285 def start_mad(self, mf):
286 """Open the audio device and start playing an MP3 file"""
287 self.state = 'play'
289 self.total_time = mf.total_time()/1000
290 self.remain = self.total_time
291 self.elapse = 0
292 last_elapse = 0
294 if mf.mode() == mad.MODE_SINGLE_CHANNEL:
295 channels = 1
296 else:
297 channels = 2
299 try:
300 self.open(mf.samplerate(), channels)
302 while self.state == 'play' or self.state == 'pause':
303 if self.state == 'pause':
304 time.sleep(1)
305 elif self.seek_val:
306 mf.seek_time(long(self.total_time * self.seek_val * 1000))
307 self.seek_val = 0
308 else:
309 buff = mf.read()
310 if buff is None:
311 self.state = 'eof'
312 self.elapse = self.total_time
313 last_elapse = 0
314 remain = 0
315 else:
316 self.elapse = mf.current_time() / 1000
317 self.remain = max(0, self.total_time - self.elapse)
318 self.write(buff, len(buff))
319 if self.elapse != last_elapse or self.state == 'pause':
320 last_elapse = self.elapse
321 except:
322 self.state = 'stop'
324 self.close()