Theme changes, fix for unicode conversion errors, misc
[rox-musicbox.git] / plugins / _mp3.py
blob910d12a6f73c91b1a69c2a3e1f973355ae1a31a3
1 """
2 Copyright 2005 Kenneth Hayber <ken@hayber.us>, All rights reserved.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License.
8 This program is distributed in the hope that it will be useful
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16 """
18 try:
19 import mad
20 HAVE_MP3 = True
21 except:
22 HAVE_MP3 = False
23 print 'No MP3 support!'
25 try:
26 from pyid3lib import *
27 HAVE_ID3V2 = True
28 except:
29 from ID3 import *
30 HAVE_ID3V2 = False
31 print 'No id3v2 support'
32 import re
33 import genres
34 ID3V2_GENRE_RE = re.compile('\((?P<genre>\d+)\)')
36 def to_unicode(s, fallback_encoding='iso-8859-1'):
37 try:
38 return s.decode('utf-8')
39 except UnicodeDecodeError:
40 return s.decode(fallback_encoding, 'ignore')
42 def get_info(song):
43 if HAVE_ID3V2:
44 tag_info = tag(song.filename)
45 else: #ID3V1
46 try:
47 tag_info = ID3(song.filename)
48 except:
49 return
50 val = None
51 for key in ('title', 'track', 'album', 'artist', 'contenttype', 'genre'):
52 try:
53 if key == 'track':
54 # it is a tuple (x of y)
55 if HAVE_ID3V2:
56 val = int(tag_info.track[0])
57 else:
58 val = tag_info.track
59 elif key == 'genre':
60 val = tag_info.genre
61 else:
62 val = to_unicode(getattr(tag_info, key))
63 if key == 'contenttype':
64 key = 'genre'
65 # ID3v2 genres are either a string/tuple index
66 # e.g. '(17)' or the actual genre string.
67 x = ID3V2_GENRE_RE.match(val)
68 if x:
69 val = int(x.group('genre'))
70 if key == 'genre' and isinstance(val, int):
71 val = genres.genre_list[val]
72 if val != '':
73 setattr(song, key, val)
74 except (AttributeError, IndexError):
75 pass
76 # don't trust any length specs from a tag
77 song.length = 0
80 class MP3Decoder:
81 def __init__(self, filename, buffersize):
82 """Initialize the decoder"""
83 self.buffersize = buffersize
84 self.filename = filename
86 def open(self):
87 """Open the file and prepare for decoding"""
88 self.mf = mad.MadFile(self.filename) #, self.buffersize)
90 def close(self):
91 """Close the file and do any needed cleanup"""
92 pass
94 def length(self):
95 """Return the length of the file in seconds as an integer"""
96 return self.mf.total_time()/1000
98 def samplerate(self):
99 """Return the sample rate of the file in samples per second"""
100 return self.mf.samplerate()
102 def channels(self):
103 """Return the number of channels in the file"""
104 if self.mf.mode() == mad.MODE_SINGLE_CHANNEL:
105 return 1
106 else:
107 return 2
109 def read(self):
111 Read data from the file and decode to PCM data. Return a buffer
112 of data and length, or (None, 0) at EOF
114 buff = self.mf.read()
115 if buff:
116 return (buff, len(buff))
117 else:
118 return (None, 0)
120 def tell(self):
121 """Return the current playback position in seconds"""
122 return self.mf.current_time() / 1000
124 def seek(self, pos):
125 """Jump to pos as a percentage of the total length of the file"""
126 self.mf.seek_time(long(self.length() * pos * 1000))
128 def info(self):
129 """Display some MP3 information"""
130 if self.mf.layer() == mad.LAYER_I:
131 print "MPEG Layer I"
132 elif self.mf.layer() == mad.LAYER_II:
133 print "MPEG Layer II"
134 elif self.mf.layer() == mad.LAYER_III:
135 print "MPEG Layer III"
136 else:
137 print "unexpected layer value"
139 if self.mf.mode() == mad.MODE_SINGLE_CHANNEL:
140 print "single channel"
141 elif self.mf.mode() == mad.MODE_DUAL_CHANNEL:
142 print "dual channel"
143 elif self.mf.mode() == mad.MODE_JOINT_STEREO:
144 print "joint (MS/intensity) stereo"
145 elif self.mf.mode() == mad.MODE_STEREO:
146 print "normal L/R stereo"
147 else:
148 print "unexpected mode value"
150 if self.mf.emphasis() == mad.EMPHASIS_NONE:
151 print "no emphasis"
152 elif self.mf.emphasis() == mad.EMPHASIS_50_15_US:
153 print "50/15us emphasis"
154 elif self.mf.emphasis() == mad.EMPHASIS_CCITT_J_17:
155 print "CCITT J.17 emphasis"
156 else:
157 print "unexpected emphasis value"
159 print "bitrate %lu bps" % self.mf.bitrate()
160 print "samplerate %d Hz" % self.mf.samplerate()
161 millis = self.mf.total_time()
162 secs = millis / 1000
163 print "total time %d ms (%dm%2ds)" % (millis, secs / 60, secs % 60)