On Sat, 11 Dec 2004 20:32:30 +0100, Ivo Woltring <[EMAIL PROTECTED]> wrote:
mmpython will help but not always.
Lets put it this way. I will ALWAYS read through the whole file. In that
order I don't mind evaluating each frame. The thing I don't seem to be able
to find is the timelength-constants for frames for the different mp3
versions, bitrates and layers. Can anybody help me?
From http://www.oreilly.com/catalog/mp3/chapter/ch02.html#71109
"In addition, the number of samples stored in an MP3 frame is constant, at 1,152 samples per frame."
So you only need the samplerate for each frame to calculate the duration of that frame.
1152 samples / 44100 samples per second ~ 0.026 seconds
I don't exactly know whether you need to include mono/stereo into the calculation, you would have to test that out.
Regards, Florian Schulze
This thread prompted me to dig up some old code I wrote in April 2003 parsing MPEG audio headers. Don't remember much about it except I had trouble finding reference material.
This is what I used for frame duration:
self.size = (144*self.bitrate) / self.samplerate + self.padding
if self.bitrate: self.duration = self.size*8.0/self.bitrate else: self.duration = 0.0
That is, using bitrate instead of samplerate. More complicated, if you don't need the frame size. However, remember there might be metaframes, so the naive samplerate method might be off. I think most encoders set bitrate to 0 for metaframes, but you should check the Xing/Info tag to be sure...
Ofcourse, the right way to do it is to parse and use the VBR tag...
I'm attaching my old as-is MPEG code. Most of that project was lost in a disk crash and abandoned, so I don't know what state it's in, but...
Erik
#!/usr/bin/env python # # dAMP # - Music Server # # $Id: mpeg.py,v 1.5 2003/04/28 23:35:11 eh Exp $ # # history # 2003-04-xx eh created # 2003-04-28 eh integrated mp3 streaming # # Copyright (c) 2003 by Erik Heneryd. #
import song class NoFrame(Exception): pass # MPEG tables MPEG_VERSIONS = (2.5, -1, 2.0, 1.0) MPEG_LAYERS = (-1, 3, 2, 1) MPEG_BITRATES = { (1, 1): (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448), (1, 2): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384), (1, 3): (0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320), (2, 1): (0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448), (2, 2): (0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384), (2, 3): (0, 8, 16, 24, 32, 64, 80, 56, 64, 128, 160, 112, 128, 256, 320)} MPEG_SAMPLERATES = { 1.0: (44100, 48000, 32000), 2.0: (22050, 24000, 16000), 2.5: (11025, 12000, 8000)} ID3_GENRES = [ 'Blues', 'Classic Rock', 'Country', 'Dance', 'Disco', 'Funk', 'Grunge', 'Hip-Hop', 'Jazz', 'Metal', 'New Age', 'Oldies', 'Other', 'Pop', 'R&B', 'Rap', 'Reggae', 'Rock', 'Techno', 'Industrial', 'Alternative', 'Ska', 'Death Metal', 'Pranks', 'Soundtrack', 'Euro-Techno', 'Ambient', 'Trip-Hop', 'Vocal', 'Jazz+Funk', 'Fusion', 'Trance', 'Classical', 'Instrumental', 'Acid', 'House', 'Game', 'Sound Clip', 'Gospel', 'Noise', 'AlternRock', 'Bass', 'Soul', 'Punk', 'Space', 'Meditative', 'Instrumental Pop', 'Instrumental Rock', 'Ethnic', 'Gothic', 'Darkwave', 'Techno-Industrial', 'Electronic', 'Pop-Folk', 'Eurodance', 'Dream', 'Southern Rock', 'Comedy', 'Cult', 'Gangsta', 'Top 40', 'Christian Rap', 'Pop/Funk', 'Jungle', 'Native American', 'Cabaret', 'New Wave', 'Psychadelic', 'Rave', 'Showtunes', 'Trailer', 'Lo-Fi', 'Tribal', 'Acid Punk', 'Acid Jazz', 'Polka', 'Retro', 'Musical', 'Rock & Roll', 'Hard Rock', # added by winamp 'Folk', 'Folk-Rock', 'National Folk', 'Swing', 'Fast Fusion', 'Bebob', 'Latin', 'Revival', 'Celtic', 'Bluegrass', 'Avantgarde', 'Gothic Rock', 'Progressive Rock', 'Psychedelic Rock', 'Symphonic Rock', 'Slow Rock', 'Big Band', 'Chorus', 'Easy Listening', 'Acoustic', 'Humour', 'Speech', 'Chanson', 'Opera', 'Chamber Music', 'Sonata', 'Symphony', 'Booty Bass', 'Primus', 'Porn Groove', 'Satire', 'Slow Jam', 'Club', 'Tango', 'Samba', 'Folklore', 'Ballad', 'Power Ballad', 'Rhythmic Soul', 'Freestyle', 'Duet', 'Punk Rock', 'Drum Solo', 'Acapella', 'Euro-House', 'Dance Hall', 'Goa', 'Drum & Bass', 'Club-House', 'Hardcore', 'Terror', 'Indie', 'BritPop', 'Negerpunk', 'Polsk Punk', 'Beat', 'Christian Gangsta Rap', 'Heavy Metal', 'Black Metal', 'Crossover', 'Contemporary Christian', 'Christian Rock', 'Merengue', 'Salsa'] STEREO, JOINT_STEREO, DUAL_CHANNEL, SINGLE_CHANNEL = range(4) def int32(s): return (ord(s[0]) << 24) + (ord(s[1]) << 16) + (ord(s[2]) << 8) + ord(s[3]) class MPEGFrame: def __init__(self, data, start=0): self.start = start while 1: self.start = ix = data.find("\xff", self.start) if not -1 < ix < len(data)-4: raise NoFrame # no valid MPEG frame (header) found in data head = int32(data[ix:ix+4]) try: if (head >> 21) & 0x7ff != 0x7ff: # sync bits raise IndexError self.version = MPEG_VERSIONS[(head >> 19) & 0x3] self.layer = MPEG_LAYERS[(head >> 17) & 0x3] self.protection = (head >> 16) & 0x1 self.bitrate = MPEG_BITRATES[int(self.version), self.layer][(head >> 12) & 0xf] * 1000 self.samplerate = MPEG_SAMPLERATES[self.version][(head >> 10) & 0x3] self.padding = (head >> 9) & 0x1 self.private = (head >> 8) & 0x1 self.chanmode = (head >> 6) & 0x3 self.modeext = (head >> 4) & 0x3 self.copyright = (head >> 3) & 0x1 self.original = (head >> 2) & 0x1 self.emphasis = head & 0x3 self.size = (144*self.bitrate) / self.samplerate + self.padding if self.bitrate: self.duration = self.size*8.0/self.bitrate else: self.duration = 0.0 if self.version == 1.0: if self.chanmode == SINGLE_CHANNEL: ix += 21 else: ix += 36 else: if self.chanmode == SINGLE_CHANNEL: ix += 13 else: ix += 21 # XING VBR self.totalframes = None self.totalsize = None if data[ix:ix+4] in ["Xing", "Info"]: self.vbr = True ix += 4 flags = int32(data[ix:ix+4]) if flags & 0x1: # FRAMES_FLAG ix += 4 self.totalframes = int32(data[ix:ix+4]) if flags & 0x2: # BYTES_FLAG ix += 4 self.totalsize = int32(data[ix:ix+4]) else: self.vbr = False break # header ok except (IndexError, KeyError): # found bad header, continue searching self.start += 1 class MPEGSong(song.Song): """MP3 song.""" type = "MPEG" playcmd = ("mpg123",) usecmd = False # stream seek seek = 0 data = None def getinfo(self): """Parse info from mp3 frame headers + get ID3 tag info.""" if self.data is None: f = file(self.filename) data = f.read() f.close() else: data = self.data try: frame = MPEGFrame(data, 0) except NoFrame: return if frame.vbr: self.vbr = True if frame.totalframes and frame.totalsize: br = float(frame.totalsize/frame.totalframes)*frame.samplerate if frame.version == 1.0: self.bitrate = br/144000 else: self.bitrate = br/72000 else: # FIXME: continue and search for a proper header? raise NotImplemented else: self.vbr = False self.bitrate = frame.bitrate self.version = frame.version self.layer = frame.layer self.chanmode = frame.chanmode self.samplerate = frame.samplerate if self.bitrate: self.duration = len(data)*0.008/self.bitrate else: self.duration = 0.0 # id3 tags f = lambda s: s.strip("\0").strip() id3 = data[-128:] if len(id3) == 128 and id3[:3] == "TAG": self.title = f(id3[3:33]) self.artist = f(id3[33:63]) self.album = f(id3[63:93]) if id3[93:97].isdigit(): self.year = int(id3[93:97]) self.comment = f(id3[97:127]) if ord(id3[126]): self.track = ord(id3[126]) self.genre = ord(id3[127]) def stop(self): self.data = None self.seek = 0 Song.stop(self) def getdata(self): if self.status() == PLAYING: if self.data is None: f = file(self.filename) self.data = f.read() f.close() try: frame = MPEGFrame(self.data, self.seek) self.seek = frame.start + max(1, frame.size) return self.data[frame.start:frame.start+frame.size], frame.duration except NoFrame: self.stop() return "", 0.0 else: # return silent MPEG frame return '\xff\xfb\xa0\x00\x00\x00\x00\x00\x00i\x06\x00\x00\x00\x00\x00\r [EMAIL PROTECTED] 80\xb0\x14\x0f(\xb1s\xf4\xaeE\xcf`\xe0\x1a\x07\x83A\xa0x\xa5\x8b\xda"\x7f\xf2\xf7\xef|\x10(d\x90eK\xbb\xdc\x0b\x9fp\x88\x95\xa2%Ib\xef\x02\xe2\xf7\t[\xdf\xff\xcf\xff\xbb\xbd\xfc\xbd\xc1\x02\x86H4\x0f)\xc5\xdf\xfe]\xe0\x81C$PQ7w\xd0\\\xfd\x12]\xf7}\x13\xf8{\x84\x92\xc5\xc5\xd8\\]\xe1\x05\x05\x11\xc3\x81Jw{\xfe]\xf2\x05\x05*A\x90\x00\x03\x04\x89\x85\xc0\x18\x03\x03d\xea\x02d\xe9\n\t1qZ8\xcfw\x7f\xbcc\xde\xb4G\x88\xcfw\xfb^\xb4s\xc9\xa6`\r6!\x87\x83\x85\x94\x03\x0bb\x0892z}\xf8\xf7w\xbf\xff\xff\xf7\x11\xee\xfbe\x90\x87\xbbc\x10r\x08=\xdbD9\x04\x1e\xef\xc4<C\xde\xc1\x04\x1c\x82\x0fw\xe2\x1e1\xefL A\xc8 \xf7lb\x19\x11\xefZ"/\xb4c\xde\xc14\xda!\xcfM\x89\xa6\xc6C\x93&[EMAIL PROTECTED]<\xa7\xd8r\x97\x82\x03\x03\t\xb7mc\xac"\x87\t\x07\xef\xabmO\xa8\xb0\x94\xbe\x93\x8f\x93\x92\xfb$}\x14\xf7\xefV\x13Q!\xd9s1i\xc5S\x8bN\x00\x96~\x1f\xe8\x9a\xe7\xbbq\'\x14X\x88q\x17Z\x80\x98K\xfc\xfea\xea\xec\xbf\x14\xaf\xfc\xb9\x9cB\xcc)e\xd7\x90\x1c\n??\xbf\xf8r\xea\x96C->\x10\xef\xdf\x97\xaex\xfa\xb5\xc5K\xd0^,\xf3\xfc?\x9c\xcf\xdf\xf8"W:\xe0A\xcem;\x8e\xd0a\xe2\xe3\xb1a\x02\x1c\xb4\xe6\x83\x8bq\x87\xf7\x0c9\xce\xfe\x18b\xd9Y}\xfaJ\xab2! p\x1fUjA\x1a\x01\'\x1a\n\x1cF\xb2\x8c\x80\xf0\xb6U\xf9\xcc\xfb\xfb\xee\x1f\x9fs\xef\xd8\xe2\xebf\x8e[\x8e\xbe\xdb\xea\xad2\x86<\xe2L\xb9k\xf1\xa5\xd19mU\x18', 0.0261 if __name__ == "__main__": import sys s = MPEGSong(sys.argv[1]) print "title:", s.title print "artist:", s.artist print "album:", s.album print "track:", s.track print "year:", s.year print "---" print "version:", s.version print "layer:", s.layer print "chanmode:", s.chanmode print "samplerate:", s.samplerate print "bitrate:", s.bitrate print "duration:", s.duration
-- http://mail.python.org/mailman/listinfo/python-list