# Copyright (c) 2004-5 Marek Hnilica. All rights reserved. # Module that handles ID3v2 tags. # Distributed under GPL version 2, or (at your option) later # ''' Class of interest - ID3v2 Methods: GetTag() (automatically called at init), WriteTag(ListOfTags) Arguments to __init__: needed: file (filename, not file object!) optional: RemovePadding=x; x=1-yes(default), otherwise no - extracts padding from tags Extend=x; x=1-yes(default), otherwise no - Whether to create variables self.ARTIST, self.ALBUM, self.TITLE, self.GENRE, self.YEAR, self.TRACK, self.COMM, or not. ''' import sys,string,os.path,os class TagError: def __init__(self, err): self.err=err def __str__(self): return self.err class ID3v2: def __init__(self,file,RemovePadding=1,Extend=1): self.TagList={} self.filename=file if not os.path.exists(self.filename): raise TagError,'File %s does not exist.' % self.filename self.RemovePadding=RemovePadding self.WillExtend=Extend self.TagSize=0 try: self.GetTag() except: pass if self.WillExtend: self.Extend() def remove_sub(self,string): NewString='' for i in list(string): if ord(i) > 20: NewString+=i return(NewString) #This is probably not the best idea; other solution would be nice def remove_pads(self,pad): self.pad=string.replace(pad,'\x00','') return self.pad def GetTag(self): if not os.access(self.filename,4): raise TagError,'You don\'t have permissions to read %s' %self.filename if os.path.getsize(self.filename) < 10: raise TagError,'File %s is invalid (too small).' % self.filename self.ID3File=open(self.filename,'rb') self.isID3=self.ID3File.read(3) if not self.isID3=='ID3': raise TagError,'File %s doesn\'t contain valid ID3v2 information.' % self.filename self.ID3File.close() self.ID3File.seek(3,1) self.TagSizeByte1=int(ord(self.ID3File.read(1))) self.TagSizeByte2=int(ord(self.ID3File.read(1))) self.TagSizeByte3=int(ord(self.ID3File.read(1))) self.TagSizeByte4=int(ord(self.ID3File.read(1))) self.TagSize=((self.TagSizeByte1<< 21) + (self.TagSizeByte2 << 14) + (self.TagSizeByte3 << 7) + self.TagSizeByte4) while self.ID3File.tell()<=self.TagSize: self.TagName=self.ID3File.read(4) self.SizeByteOne=self.ID3File.read(1) self.SizeByteTwo=self.ID3File.read(1) self.SizeByteThree=self.ID3File.read(1) self.SizeByteFour=self.ID3File.read(1) # Uhhh, that's painful... self.ContentSize=((ord(self.SizeByteOne)*0x1000000)+(ord(self.SizeByteTwo)*0x10000)+(ord(self.SizeByteThree)*0x100)+(ord(self.SizeByteFour))) self.ID3File.seek(2,1) try: self.TagContent=self.ID3File.read(self.ContentSize) except OverflowError: raise TagError,'Too big/corrupted tag' if (self.RemovePadding == 1) and not (self.TagName=='APIC'): self.TagContent=self.remove_pads(self.TagContent) self.TagList[self.TagName]=self.TagContent self.ID3File.close() # If there is padding after tag, next two lines become handy if self.TagList.has_key('\x00\x00\x00\x00'): del self.TagList['\x00\x00\x00\x00'] def Extend(self): # Won't create a tuple as in ID3v1 - we already have # TagList variable, which is similar. Named variable are, # imho, more useful. self.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", "Alt. Rock", "Bass", "Soul", "Punk", "Space", "Meditative", "Instrum. Pop", "Instrum. Rock", "Ethnic", "Gothic", "Darkwave", "Techno-Indust.", "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", "Folk", "Folk/Rock", "National Folk", "Swing", "Fusion", "Bebob", "Latin", "Revival", "Celtic", "Bluegrass", "Avantgarde", "Gothic Rock", "Progress. Rock", "Psychadel. 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", "A Capella", "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", "Thrash Metal", "Anime", "Jpop", "Synthpop"] if self.TagList.has_key('TIT2'): self.TITLE=self.remove_sub(self.TagList['TIT2']) else: self.TITLE='' if self.TagList.has_key('TPE1'): self.ARTIST=self.remove_sub(self.TagList['TPE1']) else: self.ARTIST='' if self.TagList.has_key('TALB'): self.ALBUM=self.remove_sub(self.TagList['TALB']) else: self.ALBUM='' if self.TagList.has_key('TYER'): self.YEAR=self.remove_sub(self.TagList['TYER']) else: self.YEAR='' if self.TagList.has_key('TRCK'): self.TRACK=self.remove_sub(self.TagList['TRCK']) else: self.TRACK='' if self.TagList.has_key('TCON'): self.GENRE=self.remove_sub(self.TagList['TCON']) # If genres are in number format.. try: if (self.GENRE[:1]=='(') and (self.GENRE[-1:]==')'): if (int(self.GENRE[1:-1])) and (int(self.GENRE[1:-1]) < 256): self.GENRE=self.GENRE[1:-1] if int(self.GENRE)<148: try: self.GENRE=self.genres[int(self.GENRE)] except IndexError: self.GENRE='' if self.GENRE and self.GENRE[0]=='(': pos=string.find(self.GENRE,')') if pos>0: try: self.GENRE=self.genres(int(self.GENRE[1:pos])) print self.GENRE except: self.GENRE='' except ValueError: self.GENRE='' else: self.GENRE='' if self.TagList.has_key('COMM'): self.COMM=self.remove_sub(self.TagList['COMM']) else: self.COMM='' def DetectID3(self): if not os.access(self.filename,6): raise TagError('file %s is not writable & readable (needed chmod 600, at least)'%self.filename) self.ID3File=open(self.filename,'rb') if not self.ID3File.read(3)=='ID3': self.ID3File.seek(0) return 0 else: self.ID3File.seek(3,1) self.TagSizeByte1=int(ord(self.ID3File.read(1))) self.TagSizeByte2=int(ord(self.ID3File.read(1))) self.TagSizeByte3=int(ord(self.ID3File.read(1))) self.TagSizeByte4=int(ord(self.ID3File.read(1))) self.TagSize=((self.TagSizeByte1<< 21) + (self.TagSizeByte2 << 14) + (self.TagSizeByte3 << 7) + self.TagSizeByte4) self.ID3File.close() return 1 def WriteTag(self,TagsList): if not os.access(self.filename,os.W_OK): raise IOError('Unable to open file %s for writing'%self.filename) self.TagsList=TagsList self.BytesToWrite=0 self.NewTagList={} self.temp=0 for self.i in self.TagsList.keys(): self.BytesToWrite+=len(self.i) self.BytesToWrite+=len(self.TagsList[self.i]) self.BytesToWrite+=7 self.ByteFour=0 if self.BytesToWrite >= 1: (self.temp,self.ByteFour)=divmod(self.BytesToWrite,128) self.ByteThree=0 if self.temp >= 1: (self.temp,self.ByteThree)=divmod(self.temp,128) self.ByteTwo=0 if self.temp >=1: (self.temp,self.ByteTwo)=divmod(self.temp,128) self.ByteOne=0 if self.temp >=1: self.ByteOne=self.temp self.DataFile=open(self.filename,'rb') if self.DetectID3(): self.DataFile.seek(self.TagSize+10,0) self.data=self.DataFile.read() self.DataFile.close() self.NewFile=open(self.filename,'wb') self.NewFileHeader='ID3'+chr(3)+chr(0)+chr(0)+chr(self.ByteOne)+chr(self.ByteTwo)+chr(self.ByteThree)+chr(self.ByteFour) self.NewFileTag='' for self.i in self.TagsList.keys(): self.TagLength=len(TagsList[self.i]) self.TagLength+=1 if self.TagLength >= 1: (self.temp,self.FrameByteFour)=divmod(self.TagLength,256) else: self.FrameByteFour=0 self.FrameByteThree=0 if self.temp >= 1: (self.temp,self.FrameByteThree)=divmod(self.temp,256) else: self.FrameByteThree=0 self.FrameByteTwo=0 if self.temp >=1: (self.temp,self.FrameByteTwo)=divmod(self.temp,256) else: self.FrameByteTwo=0 if self.temp >=1: self.FrameByteOne=self.temp else: self.FrameByteOne=0 self.NewFileTag=self.NewFileTag+self.i+chr(self.FrameByteOne)+chr(self.FrameByteTwo)+chr(self.FrameByteThree)+chr(self.FrameByteFour)+'\x00'*3+self.TagsList[self.i] self.NewFile.write(self.NewFileHeader) self.NewFile.write(self.NewFileTag) self.NewFile.write(self.data) self.NewFile.close()