#!/usr/bin/env python #-*- coding: UTF-8 -*- from tagger import * from ApeTag import * import os,re,time ############## for Lyrics3v* ############### def seekToLyricsEnd(f): f.seek(-128-9,os.SEEK_END) buf=f.read(9) if(buf != "LYRICS200" and buf != "LYRICSEND"): f.seek(-9,os.SEEK_END) buf=f.read(9) if(buf == "LYRICS200" or buf == "LYRICSEND"): #f.seek(-9,os.SEEK_CUR) if(buf == "LYRICS200"): return 2 elif(buf == "LYRICSEND"): return 1 return 0 def readV1(f): if(seekToLyricsEnd(f)==1): f.seek(-5100-9-11,os.SEEK_CUR) buf=f.read(5100+11) start=buf.find("LYRICSBEGIN") return buf[start+11:] return None def readV2(f): if(seekToLyricsEnd(f)==2): f.seek(-9-6,os.SEEK_CUR) size=int(f.read(6)) f.seek(-size-6,os.SEEK_CUR) buf=f.read(11) if(buf == "LYRICSBEGIN"): buf=f.read(size-11) tags=[] while buf!= '': tag=buf[:3] length=int(buf[3:8]) content=buf[8:8+length] buf=buf[8+length:] tags.append({'id':tag,'value':content}) return tags #to get the lyrics use [tag['value'] for tag in tags if tag['id']=='LYR'][0] return None def modify(filename,lrc=None,_type=2): f=open(filename,"rb+")#file must be opened with r+ mode f.seek(-128,os.SEEK_END) buf=f.read(3) id3="" if buf == "TAG":#exist id3 id3="TAG"+f.read(125) ty=seekToLyricsEnd(f) #move the cursor to the right position if ty==1:#Lyrics3v1 f.seek(-5100-9-11,os.SEEK_CUR) buf=f.read(5100+11) start=buf.find("LYRICSBEGIN") f.seek(-5111+start,os.SEEK_CUR) elif ty==2:#Lyrics3v2 f.seek(-9-6,os.SEEK_CUR) size=int(f.read(6)) f.seek(-size-6,os.SEEK_CUR) elif len(id3)>0: f.seek(-128,os.SEEK_END) else: f.seek(0,os.SEEK_END) if lrc != None:#if write the lyrics tag, else write nothing f.write("LYRICSBEGIN") if( _type==1):#Lyrics3v1 f.write(lrc) f.write("LYRICSEND") else: f.write("IND00003110")#Something like flags length="00000%d" % len(lrc) f.write("LYR%s" % (length[len(length)-5:])) f.write(lrc) length="000000%d" % (11+11+8+len(lrc)) f.write("%sLYRICS200" % (length[len(length)-6:])) if len(id3)>0:#rewrite id3 f.write(id3) f.truncate() f.close() ############## for ID3v2 ############### def extractUslt(filename): tag=getUslt(ID3v2(filename)) if tag: enc='\x03' lang=tag.language desc=tag.shortcomment lrc=tag.longcomment return {"enc":enc,"lang":lang,"desc":desc,"text":lrc} return None def addUslt(filename,uslt): mp3=ID3v2(filename) tag=getUslt(mp3) if tag: mp3.frames.remove(tag) tag=mp3.new_frame("USLT") tag.encoding='utf_8'#'utf-8' doesn't work it's a pytagger problem tag.language=uslt['lang'] tag.shortcomment=uslt['desc'] tag.longcomment=uslt['text'] mp3.frames.append(tag) mp3.commit() def getUslt(id3): for tag in id3.frames: if tag.fid == "USLT": return tag return None ############## for ApeTag ############### def getApeLyrics(filename): try: lrc=getapefields(filename)['Lyrics'][0] except: return None else: return lrc def addApeLyrics(filename,lrc): createape(filename,{'Lyrics':lrc}) def removeApeLyrics(filename): fields=getapefields(filename) if fields.has_key('Lyrics'): del fields['Lyrics'] replaceape(filename,fields) ############# for SYLT ###################### def endOfString(string,utf16=False): if(utf16): pos=0 while True: pos+=string[pos:].find('\x00\x00')+1 if pos % 2 == 1: return pos-1 else: return string.find('\x00') def ms2timestamp(ms,utf16=False): mins="0%s" % int(ms/1000/60) sec="0%s" % int((ms/1000)%60) msec="0%s" % int((ms%1000)/10) timestamp="[%s:%s.%s]" % (mins[-2:],sec[-2:],msec[-2:]) return timestamp def sylt2lrc(sylt): utf16=bool(sylt['enc'].find('16') != -1) if sylt['desc'].startswith('\xff\xfe'): code=u'\xff\xfe' else: code=u'\xfe\xff' strip=lambda x: x.replace(code,'') lrc='\r\n'.join(["%s%s" % (ms2timestamp(ms,utf16),strip(text)) for ms,text in sylt['pieces']]) return lrc.encode(sylt['enc']) def extractSYLT(filename): tag=getSYLT(ID3v2(filename)) if tag: enc=['latin_1','utf_16','utf_16_be','utf_8'][ord(tag.rawdata[0])] lang=tag.rawdata[1:4] format=tag.rawdata[4] ctype=tag.rawdata[5] raw=tag.rawdata[6:] utf16=bool(enc.find('16') != -1) pos=endOfString(raw,utf16) desc=raw[:pos] if utf16: pos+=1 content=raw[pos+1:] del raw pcs=[] while content != "": pos=endOfString(content,utf16) text=content[:pos].decode(enc) if utf16: pos+=1 time=content[pos+1:pos+5] timems=0 for x in range(4): timems+=(256)**(3-x) * ord(time[x]) pcs.append([timems,text.replace('\n','').replace('\r','').strip()]) content=content[pos+5:] return {"enc":enc,"lang":lang,"ctype":ctype,"desc":desc,"pieces":pcs} return None def getSYLT(id3): if id3.version == 2.2: sylt="SLT" else: sylt="SYLT" for tag in id3.frames: if tag.fid == sylt: return tag return None def removeSYLT(filename): mp3=ID3v2(filename) sylt=getSYLT(mp3) if sylt: mp3.frames.remove(sylt) mp3.commit() def stripUtf16(text): return ''.join([text[i] for i in range(len(text)) if i%2==0]) def lrc2list(lrc,utf16=False): if ord(lrc[0])==0xfe and ord(lrc[1])==0xff or ord(lrc[0])==0xff and ord(lrc[1])==0xfe: lrc=lrc[2:] if utf16: lrcstrip=stripUtf16(lrc) else: lrcstrip=lrc offset=re.findall("\[offset:([0-9]+)\]",lrcstrip) if offset: offset=int(offset[0]) else: offset=0 lines=lrcstrip.split('\r\n') gap=2 if len(lines) == 1: lines=lrcstrip.split('\n') gap=1 if len(lines) == 1: lines=lrcstrip.split('\r') ret=[] tot=0 rightpos=lambda x: (x+tot)*{True:2,False:1}[utf16] for i in range(len(lines)): if lines[i].rfind(']') != -1: line=lines[i] text=line[line.rfind(']')+1:] text=lrc[rightpos(line.rfind("]")+1):rightpos(len(line))] timestamps=re.findall("\[([0-9]+):([0-9]+)[\:\.]{0,1}([0-9]*)\]",line) for t in timestamps: time=int(t[0])*1000*60 time+=int(t[1])*1000 if t[2] != '': time+=int(t[2]+'0'*(3-len(t[2]))) time+=offset ret.append([time,text]) tot+=len(lines[i])+gap ret.sort() return ret def setSYLT(filename,lrc,lrc_enc='utf-8'): utf16=bool(lrc_enc.lower().startswith('utf') and lrc_enc.find('16') != -1) if not utf16: lrc=lrc.decode(lrc_enc).encode('utf-16') utf16=True lis=lrc2list(lrc,utf16) raw="%seng\x02\x01" % ({True:'\x01',False:'\x03'}[utf16]) if utf16: raw+="Sylt written by lrcShow-II\x00".encode('utf-16') else: raw+="Sylt written by lrcShow-II\x00" for t,text in lis: if len(text)%2 != 0: print [ms2timestamp(t),text] tstr=chr(t/256/256/256) tstr+=chr(t/256/256%256) tstr+=chr(t/256%256) tstr+=chr(t%256) if utf16: raw+="%s\x00\x00%s" % (text,tstr) else: raw+="%s\x00%s" % (text.encode('utf-8',lrc_enc),tstr) mp3 = ID3v2(filename) tag=getSYLT(mp3) if tag: mp3.frames.remove(tag) if mp3.version == 2.2: tag=mp3.new_frame("SLT") else: tag=mp3.new_frame("SYLT") tag.rawdata=raw mp3.frames.append(tag) mp3.commit() class embedLrc: def __init__(self,fileName): self.fileName=fileName def detectLrc(self): #detect order: apetag>ID3v2(uslt)>lyrics3v2>lyrics3v1>ID3v2(sylt) lrc=getApeLyrics(self.fileName) if lrc: return lrc.encode('utf8') lrc=extractUslt(self.fileName) if lrc: return lrc['text'] f=file(self.fileName,"r") tags=readV2(f) if tags: lrc=[tag['value'] for tag in tags if tag['id']=='LYR'][0] f.close() return lrc lrc=readV1(f) f.close() if lrc: return lrc sylt=extractSYLT(self.fileName) if sylt: lrc=sylt2lrc(sylt) if lrc: return lrc else: return None else: return None def insertLrc(self,writeType,lrc): if(writeType=='id3v2-uslt'): lrc={"enc":'\x03',"lang":'chi',"desc":'',"text":lrc} addUslt(self.fileName,lrc) elif(writeType=='lyrics3v2'): modify(self.fileName,lrc,2) elif(writeType=='lyrics3v1'): modify(self.fileName,lrc,1) elif(writeType=='apetag'): lrc=unicode(lrc,'utf8') addApeLyrics(self.fileName,lrc) elif(writeType=='id3v2-sylt'): setSYLT(self.fileName,lrc,'utf-8') def deleteLrc(self,writeType): if(writeType=='id3v2-uslt'): mp3=ID3v2(self.fileName) uslt=getUslt(mp3) if uslt: mp3.frames.remove(uslt) mp3.commit() return True else: return False elif(writeType=='lyrics3v2'): f=file(self.fileName,"r") tags=readV2(f) f.close() if tags: modify(self.fileName,None,2) return True else: return False elif(writeType=='lyrics3v1'): f=file(self.fileName,"r") tags=readV1(f) f.close() if tags: modify(self.fileName,None,1) return True else: return False elif(writeType=='apetag'): tags=getApeLyrics(self.fileName) if tags: removeApeLyrics(self.fileName) return True else: return False elif(writeType=='id3v2-sylt'): mp3=ID3v2(self.fileName) sylt=getSYLT(mp3) if sylt: mp3.frames.remove(sylt) mp3.commit() return True else: return False