| 127 | | log.exception('mp3 tag parsing %s failed!' % file.name) |
| 128 | | except (KeyboardInterrupt, SystemExit): |
| 129 | | sys.exit(0) |
| 130 | | except: |
| 131 | | # The MP3 tag decoder crashed, assume the file is still |
| 132 | | # MP3 and try to play it anyway |
| 133 | | if log.level < 30: |
| 134 | | log.exception('mp3 tag parsing %s failed!' % file.name) |
| 135 | | |
| 136 | | if not id3: |
| 137 | | # let's take a look at the header |
| 138 | | s = file.read(4096) |
| 139 | | if not s[:3] == 'ID3': |
| 140 | | # no id3 tag header, not good |
| 141 | | if not re.compile(r'0*\xFF\xFB\xB0\x04$').search(s): |
| 142 | | # again, not good |
| 143 | | if not re.compile(r'0*\xFF\xFA\xB0\x04$').search(s): |
| 144 | | # that's it, it is no mp3 at all |
| 145 | | raise core.ParseError() |
| 146 | | |
| 147 | | try: |
| 148 | | if id3 and id3.tag: |
| 149 | | log.debug(id3.tag.frames) |
| 150 | | |
| 151 | | # Grip unicode bug workaround: Grip stores text data as UTF-8 |
| 152 | | # and flags it as latin-1. This workaround tries to decode |
| 153 | | # these strings as utf-8 instead. |
| 154 | | # http://sourceforge.net/tracker/index.php?func=detail&aid=1196919&group_id=3714&atid=103714 |
| 155 | | for frame in id3.tag.frames['COMM']: |
| 156 | | if "created by grip" not in frame.comment.lower(): |
| 157 | | continue |
| 158 | | for frame in id3.tag.frames: |
| 159 | | if hasattr(frame, "text") and isinstance(frame.text, unicode): |
| | 134 | log.exception('mp3 tag parsing %s failed!' % file.name) |
| | 135 | |
| | 136 | if not id3: |
| | 137 | # let's take a look at the header |
| | 138 | s = file.read(4096) |
| | 139 | if not s[:3] == 'ID3': |
| | 140 | # no id3 tag header, not good |
| | 141 | if not re.compile(r'0*\xFF\xFB\xB0\x04$').search(s): |
| | 142 | # again, not good |
| | 143 | if not re.compile(r'0*\xFF\xFA\xB0\x04$').search(s): |
| | 144 | # that's it, it is no mp3 at all |
| | 145 | raise core.ParseError() |
| | 146 | |
| | 147 | try: |
| | 148 | if id3 and id3.tag: |
| | 149 | log.debug(id3.tag.frames) |
| | 150 | |
| | 151 | # Grip unicode bug workaround: Grip stores text data as UTF-8 |
| | 152 | # and flags it as latin-1. This workaround tries to decode |
| | 153 | # these strings as utf-8 instead. |
| | 154 | # http://sourceforge.net/tracker/index.php?func=detail&aid=1196919&group_id=3714&atid=103714 |
| | 155 | for frame in id3.tag.frames['COMM']: |
| | 156 | if "created by grip" not in frame.comment.lower(): |
| | 157 | continue |
| | 158 | for frame in id3.tag.frames: |
| | 159 | if hasattr(frame, "text") and isinstance(frame.text, unicode): |
| | 160 | try: |
| | 161 | frame.text = frame.text.encode('latin-1').decode('utf-8') |
| | 162 | except UnicodeError: |
| | 163 | pass |
| | 164 | |
| | 165 | for k, var in MP3_INFO_TABLE.items(): |
| | 166 | if id3.tag.frames[k]: |
| | 167 | self._set(var,id3.tag.frames[k][0].text) |
| | 168 | if id3.tag.frames['APIC']: |
| | 169 | pic = id3.tag.frames['APIC'][0] |
| | 170 | if pic.imageData: |
| | 171 | self.thumbnail = pic.imageData |
| | 172 | if id3.tag.getYear(): |
| | 173 | self.userdate = id3.tag.getYear() |
| | 174 | tab = {} |
| | 175 | for f in id3.tag.frames: |
| | 176 | if f.__class__ is eyeD3_frames.TextFrame: |
| | 177 | tab[f.header.id] = f.text |
| | 178 | elif f.__class__ is eyeD3_frames.UserTextFrame: |
| | 179 | #userTextFrames : debug: id starts with _ |
| | 180 | self._set('_'+f.description,f.text) |
| | 181 | tab['_'+f.description] = f.text |
| | 182 | elif f.__class__ is eyeD3_frames.DateFrame: |
| | 183 | tab[f.header.id] = f.date_str |
| | 184 | elif f.__class__ is eyeD3_frames.CommentFrame: |
| | 185 | tab[f.header.id] = f.comment |
| | 186 | elif f.__class__ is eyeD3_frames.URLFrame: |
| | 187 | tab[f.header.id] = f.url |
| | 188 | elif f.__class__ is eyeD3_frames.UserURLFrame: |
| | 189 | tab[f.header.id] = f.url |
| | 190 | elif f.__class__ is eyeD3_frames.ImageFrame: |
| | 191 | tab[f.header.id] = f |
| | 192 | else: |
| | 193 | log.debug(f.__class__) |
| | 194 | self._appendtable('id3v2', tab) |
| | 195 | |
| | 196 | if id3.tag.frames['TCON']: |
| | 197 | genre = None |
| | 198 | tcon = id3.tag.frames['TCON'][0].text |
| | 199 | try: |
| | 200 | genre = int(tcon) |
| | 201 | except ValueError: |
| 161 | | frame.text = frame.text.encode('latin-1').decode('utf-8') |
| 162 | | except UnicodeError: |
| 163 | | pass |
| 164 | | |
| 165 | | for k, var in MP3_INFO_TABLE.items(): |
| 166 | | if id3.tag.frames[k]: |
| 167 | | self._set(var,id3.tag.frames[k][0].text) |
| 168 | | if id3.tag.frames['APIC']: |
| 169 | | pic = id3.tag.frames['APIC'][0] |
| 170 | | if pic.imageData: |
| 171 | | self.thumbnail = pic.imageData |
| 172 | | if id3.tag.getYear(): |
| 173 | | self.userdate = id3.tag.getYear() |
| 174 | | tab = {} |
| 175 | | for f in id3.tag.frames: |
| 176 | | if f.__class__ is eyeD3_frames.TextFrame: |
| 177 | | tab[f.header.id] = f.text |
| 178 | | elif f.__class__ is eyeD3_frames.UserTextFrame: |
| 179 | | #userTextFrames : debug: id starts with _ |
| 180 | | self._set('_'+f.description,f.text) |
| 181 | | tab['_'+f.description] = f.text |
| 182 | | elif f.__class__ is eyeD3_frames.DateFrame: |
| 183 | | tab[f.header.id] = f.date_str |
| 184 | | elif f.__class__ is eyeD3_frames.CommentFrame: |
| 185 | | tab[f.header.id] = f.comment |
| 186 | | elif f.__class__ is eyeD3_frames.URLFrame: |
| 187 | | tab[f.header.id] = f.url |
| 188 | | elif f.__class__ is eyeD3_frames.UserURLFrame: |
| 189 | | tab[f.header.id] = f.url |
| 190 | | elif f.__class__ is eyeD3_frames.ImageFrame: |
| 191 | | tab[f.header.id] = f |
| 192 | | else: |
| 193 | | log.debug(f.__class__) |
| 194 | | self._appendtable('id3v2', tab) |
| 195 | | |
| 196 | | if id3.tag.frames['TCON']: |
| 197 | | genre = None |
| 198 | | tcon = id3.tag.frames['TCON'][0].text |
| 199 | | try: |
| 200 | | genre = int(tcon) |
| 201 | | except ValueError: |
| 202 | | try: |
| 203 | | genre = int(tcon[1:tcon.find(')')]) |
| 204 | | except ValueError: |
| 205 | | genre = tcon |
| 206 | | if genre is not None: |
| 207 | | try: |
| 208 | | self.genre = ID3.GENRE_LIST[genre] |
| 209 | | except KeyError: |
| 210 | | try: |
| 211 | | self.genre = str(genre) |
| 212 | | except: |
| 213 | | self.genre = str_to_unicode(genre) |
| 214 | | # and some tools store it as trackno/trackof in TRCK |
| 215 | | if not self.trackof and self.trackno and \ |
| 216 | | self.trackno.find('/') > 0: |
| 217 | | self.trackof = self.trackno[self.trackno.find('/')+1:] |
| 218 | | self.trackno = self.trackno[:self.trackno.find('/')] |
| 219 | | if id3: |
| 220 | | self.length = id3.getPlayTime() |
| 221 | | except (KeyboardInterrupt, SystemExit): |
| 222 | | sys.exit(0) |
| 223 | | except: |
| 224 | | if log.level < 30: |
| 225 | | log.exception('parse error') |
| 226 | | |
| 227 | | offset, header = self._find_header(file) |
| 228 | | if offset == -1 or header is None: |
| 229 | | return |
| 230 | | |
| 231 | | self._parse_header(header) |
| 232 | | |
| 233 | | if id3: |
| 234 | | # Note: information about variable bitrate or not should |
| 235 | | # be handled somehow. |
| 236 | | (vbr, self.bitrate) = id3.getBitRate() |
| 237 | | |
| 238 | | def _find_header(self, file): |
| 239 | | file.seek(0, 0) |
| 240 | | amount_read = 0 |
| 241 | | |
| 242 | | # see if we get lucky with the first four bytes |
| 243 | | amt = 4 |
| 244 | | |
| 245 | | while amount_read < _MP3_HEADER_SEEK_LIMIT: |
| 246 | | header = file.read(amt) |
| 247 | | if len(header) < amt: |
| 248 | | # awfully short file. just give up. |
| 249 | | return -1, None |
| 250 | | |
| 251 | | amount_read = amount_read + len(header) |
| 252 | | |
| 253 | | # on the next read, grab a lot more |
| 254 | | amt = 500 |
| 255 | | |
| 256 | | # look for the sync byte |
| 257 | | offset = header.find(chr(255)) |
| 258 | | if offset == -1: |
| 259 | | continue |
| 260 | | |
| 261 | | # looks good, make sure we have the next 3 bytes after this |
| 262 | | # because the header is 4 bytes including sync |
| 263 | | if offset + 4 > len(header): |
| 264 | | more = file.read(4) |
| 265 | | if len(more) < 4: |
| 266 | | # end of file. can't find a header |
| 267 | | return -1, None |
| 268 | | amount_read = amount_read + 4 |
| 269 | | header = header + more |
| 270 | | |
| 271 | | # the sync flag is also in the next byte, the first 3 bits |
| 272 | | # must also be set |
| 273 | | if ord(header[offset+1]) >> 5 != 7: |
| 274 | | continue |
| 275 | | |
| 276 | | # ok, that's it, looks like we have the header |
| 277 | | return amount_read - len(header) + offset, header[offset:offset+4] |
| 278 | | |
| 279 | | # couldn't find the header |
| 280 | | return -1, None |
| 281 | | |
| 282 | | |
| 283 | | def _parse_header(self, header): |
| 284 | | # http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html |
| 285 | | bytes = struct.unpack('>i', header)[0] |
| 286 | | mpeg_version = (bytes >> 19) & 3 |
| 287 | | layer = (bytes >> 17) & 3 |
| 288 | | bitrate = (bytes >> 12) & 15 |
| 289 | | samplerate = (bytes >> 10) & 3 |
| 290 | | mode = (bytes >> 6) & 3 |
| 291 | | |
| 292 | | if mpeg_version == 0: |
| 293 | | self.version = 2.5 |
| 294 | | elif mpeg_version == 2: |
| 295 | | self.version = 2 |
| 296 | | elif mpeg_version == 3: |
| 297 | | self.version = 1 |
| 298 | | else: |
| 299 | | return |
| 300 | | |
| 301 | | if layer > 0: |
| 302 | | layer = 4 - layer |
| 303 | | else: |
| 304 | | return |
| 305 | | |
| 306 | | self.bitrate = _bitrates[mpeg_version & 1][layer - 1][bitrate] |
| 307 | | self.samplerate = _samplerates[mpeg_version][samplerate] |
| 308 | | |
| 309 | | if self.bitrate is None or self.samplerate is None: |
| 310 | | return |
| 311 | | |
| 312 | | self._set('mode', _modes[mode]) |
| | 203 | genre = int(tcon[1:tcon.find(')')]) |
| | 204 | except ValueError: |
| | 205 | genre = tcon |
| | 206 | if genre is not None: |
| | 207 | try: |
| | 208 | self.genre = ID3.GENRE_LIST[genre] |
| | 209 | except KeyError: |
| | 210 | try: |
| | 211 | self.genre = str(genre) |
| | 212 | except: |
| | 213 | self.genre = str_to_unicode(genre) |
| | 214 | # and some tools store it as trackno/trackof in TRCK |
| | 215 | if not self.trackof and self.trackno and \ |
| | 216 | self.trackno.find('/') > 0: |
| | 217 | self.trackof = self.trackno[self.trackno.find('/')+1:] |
| | 218 | self.trackno = self.trackno[:self.trackno.find('/')] |
| | 219 | if id3: |
| | 220 | self.length = id3.getPlayTime() |
| | 221 | except (KeyboardInterrupt, SystemExit): |
| | 222 | sys.exit(0) |
| | 223 | except: |
| | 224 | if log.level < 30: |
| | 225 | log.exception('parse error') |
| | 226 | |
| | 227 | offset, header = self._find_header(file) |
| | 228 | if offset == -1 or header is None: |
| | 229 | return |
| | 230 | |
| | 231 | self._parse_header(header) |
| | 232 | |
| | 233 | if id3: |
| | 234 | # Note: information about variable bitrate or not should |
| | 235 | # be handled somehow. |
| | 236 | (vbr, self.bitrate) = id3.getBitRate() |
| | 237 | |
| | 238 | def _find_header(self, file): |
| | 239 | file.seek(0, 0) |
| | 240 | amount_read = 0 |
| | 241 | |
| | 242 | # see if we get lucky with the first four bytes |
| | 243 | amt = 4 |
| | 244 | |
| | 245 | while amount_read < _MP3_HEADER_SEEK_LIMIT: |
| | 246 | header = file.read(amt) |
| | 247 | if len(header) < amt: |
| | 248 | # awfully short file. just give up. |
| | 249 | return -1, None |
| | 250 | |
| | 251 | amount_read = amount_read + len(header) |
| | 252 | |
| | 253 | # on the next read, grab a lot more |
| | 254 | amt = 500 |
| | 255 | |
| | 256 | # look for the sync byte |
| | 257 | offset = header.find(chr(255)) |
| | 258 | if offset == -1: |
| | 259 | continue |
| | 260 | |
| | 261 | # looks good, make sure we have the next 3 bytes after this |
| | 262 | # because the header is 4 bytes including sync |
| | 263 | if offset + 4 > len(header): |
| | 264 | more = file.read(4) |
| | 265 | if len(more) < 4: |
| | 266 | # end of file. can't find a header |
| | 267 | return -1, None |
| | 268 | amount_read = amount_read + 4 |
| | 269 | header = header + more |
| | 270 | |
| | 271 | # the sync flag is also in the next byte, the first 3 bits |
| | 272 | # must also be set |
| | 273 | if ord(header[offset+1]) >> 5 != 7: |
| | 274 | continue |
| | 275 | |
| | 276 | # ok, that's it, looks like we have the header |
| | 277 | return amount_read - len(header) + offset, header[offset:offset+4] |
| | 278 | |
| | 279 | # couldn't find the header |
| | 280 | return -1, None |
| | 281 | |
| | 282 | |
| | 283 | def _parse_header(self, header): |
| | 284 | # http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html |
| | 285 | bytes = struct.unpack('>i', header)[0] |
| | 286 | mpeg_version = (bytes >> 19) & 3 |
| | 287 | layer = (bytes >> 17) & 3 |
| | 288 | bitrate = (bytes >> 12) & 15 |
| | 289 | samplerate = (bytes >> 10) & 3 |
| | 290 | mode = (bytes >> 6) & 3 |
| | 291 | |
| | 292 | if mpeg_version == 0: |
| | 293 | self.version = 2.5 |
| | 294 | elif mpeg_version == 2: |
| | 295 | self.version = 2 |
| | 296 | elif mpeg_version == 3: |
| | 297 | self.version = 1 |
| | 298 | else: |
| | 299 | return |
| | 300 | |
| | 301 | if layer > 0: |
| | 302 | layer = 4 - layer |
| | 303 | else: |
| | 304 | return |
| | 305 | |
| | 306 | self.bitrate = _bitrates[mpeg_version & 1][layer - 1][bitrate] |
| | 307 | self.samplerate = _samplerates[mpeg_version][samplerate] |
| | 308 | |
| | 309 | if self.bitrate is None or self.samplerate is None: |
| | 310 | return |
| | 311 | |
| | 312 | self._set('mode', _modes[mode]) |