如何給MP3加封面呢,當然用iturns,千千靜聽當然是可以的,但是如果用程式來自動加封面呢,研究linux的ffmpeg,發現用這個加專輯資訊還是容易的,但是封面始終加不上,無法,去研究mp3檔案的編碼吧
用來描述MP3資訊的head有兩個大版本,分別是idv2和idv3,idv2放在檔案尾部,只能描述一些簡單的資訊,idv3就厲害了,可以添加圖片,和其它各種資訊,包括自訂資訊
我們只研究 idv3,它放在檔案頭
idv3也有幾個常用的版本,一個是idv3.3一個是idv3.4兩者區別不大,就是frame中的長度idv3.4 改成了sync safe integer ,其它相同,但是idv3.4隻有iturns和一些比較先進的播放器能識別出來,比如windows 8 的mediaplay 就無法識別出來,這樣在windows的檔案夾中,那張封面圖就木有了,所以,我們主要研究idv3.3
idv3.3分 header和frame ,header描述 整個idv3.3的長度啊,和一些常見資訊 共10bytes,frame可以有多個,比如專輯名,作者名,就是兩個frame
每個frame也有它的頭,也是10個位元組,下面具體描述
header 共10個位元組
1-3 位元組 字串
ID3
4 位元組 整數
表示版本號碼 正常是03 或者 04,03就是idv3.3 ,04就是idv3.4
5 位元組 整數
小版本號碼 不管它
6 一個flags
不管它 用0即可
7-10 位元組 一個不帶正負號的整數
表示整個id3頭的長度,這裡的長度是個synchsafe integer,具體這個是啥,你可以去百度搜,我這兒只提供個演算法,將該數字轉義成真正的長度(不包括這個頭的長度)
def decode(x): #如果按照正常演算法得到的是synchsafe integer,解析成 真正的整數大小 a = x & 0xff; b = (x >> 8) & 0xff; c = (x >> 16) & 0xff; d = (x >> 24) & 0xff; x_final = 0x0; x_final = x_final | a; x_final = x_final | (b << 7); x_final = x_final | (c << 14); x_final = x_final | (d << 21); return x_finaldef encode(x): #和上邊相反 a = x & 0x7f; b = (x >> 7) & 0x7f; c = (x >> 14) & 0x7f; d = (x >> 21) & 0x7f; x_final = 0x0; x_final = x_final | a; x_final = x_final | (b << 8); x_final = x_final | (c << 16); x_final = x_final | (d << 24); return x_final
首先將那個4個位元組的無符號整形轉成整數n,這個整數並不是真正的長度,然後調decode(n)
如果你要將一個整數轉化成syncsafe integer 那麼調encode()函數即可
一個header的例子 ID3| 0x03| 0x00 | 0x00 | 0x00000013
那麼意思是idv3的版本,decode(0x00000013)的長度,全部是大端編碼 big-endian
到這兒id3的頭就說完啦
下面講frame,每個frame也有一個固定的格式,每個frame 都有一個頭
也是10個位元組
1-4 位元組 字串
TPE1 ,TIT2 ,TALB 具體去查http://id3.org/id3v2.3.0
5-8位元組 一個無符號整形 大端編碼
表示這個frame的長度,不包括這個10個位元組的頭(v3.4的版本這兒也是sync safe integer 需要decode,v3.3就不用啦)
9-10位元組 兩個 0 不管他
每個frame還有一個體,體也是有格式滴複雜的咱不說,只說最常用的,第一個位元組表示編碼,0就是普通編碼,在win上就是gbk,在linux系列就是utf8
所以如果是在win上的能正確解析的到 linux上就是亂碼咧,如果你預設用utf8,win上是解析不出來滴,
那如果我們選擇1呢,1就是unicode,unicode是啥編碼,說是ucs-2 這個是神馬東東,其實就是utf16,所以,第一個位元組,咱們用1,然後內容用utf16編碼,兩個平台就相容啦
例子
TPE1|0x00000012|0x0000
0x01|content
長度12的content編碼是utf16
普通的frame是這個樣子,還有我們的關鍵 frame,圖片
圖片的的frame頭和上邊一樣,也是10個位元組 ,但是體 稍微不同
第一個位元組還是編碼,選0就成,然後是 mime type 就是圖片格式比如 image/jpeg 或者是image/png 然後跟一個0x00 表示格式結束
然後再來個一個位元組表示圖片用途,比如封面是03,但是用03 有問題,不知道為啥,所以都是用0
然後一個是描述,沒用 用0就行
然後就是圖片資料開始啦,將圖片開啟,然後read資料到這兒就成了
例子
APIC|0x00001234|0x0000 頭
0x00|image/jpeg0x00|0x00|0x00 content(比如一個jpg的圖片 是0xFFD8打頭)
噢了,下邊是我寫的一個讀寫mp3 idv3資訊的小python代碼
# -*- coding: utf8 -*-import structdef decode(x): #如果按照正常演算法得到的synchsafe integer,解析成 真正的整數大小 a = x & 0xff; b = (x >> 8) & 0xff; c = (x >> 16) & 0xff; d = (x >> 24) & 0xff; x_final = 0x0; x_final = x_final | a; x_final = x_final | (b << 7); x_final = x_final | (c << 14); x_final = x_final | (d << 21); return x_finaldef encode(x): #和上邊相反 a = x & 0x7f; b = (x >> 7) & 0x7f; c = (x >> 14) & 0x7f; d = (x >> 21) & 0x7f; x_final = 0x0; x_final = x_final | a; x_final = x_final | (b << 8); x_final = x_final | (c << 16); x_final = x_final | (d << 24); return x_final
class MP3: def __init__(self,path): self.path = path pass def getInfo(self): fp = open( self.path,'rb'); head = fp.read(10) id3,ver,revision,flag,length = struct.unpack("!3sBBBI",head); length = decode(length) data = [] while True: frame = fp.read(10) fid,size,flag,flag2 = struct.unpack("!4sI2B",frame) if size==0: #有時候會留1024的白 不知道為啥 break if ver==4: #就是這一點 4和3的不同之處,4的這兒也採用synchsafe integer 了,注意啊 size = decode(size) content = fp.read(size) data.append((fid,content)) length-= (size+10) print length if length<=0: break fp.close() return data def buildItem(self,flag,content): content = content.decode('utf8').encode("utf16") content = struct.pack('!B',1)+content length = len(content) head = struct.pack('!4sI2B',flag,length,0,0); return head + content def addImage(self,image,data): fp = open( self.path,'rb'); head = fp.read(10) try: id3,ver,revision,flag,length = struct.unpack("!3sBBBI",head); except: return False; if id3 != 'ID3': return False #建立立個檔案 fpNew = open(self.path+'.bak',"wb"); fpImage = open(image,"rb") imageData = fpImage.read() #待用 originLength = decode(length) #真實長度 length = 0 imageDataPre = struct.pack("!B10s2BB",0,'image/jpeg',0,0,0) imageData = imageDataPre+imageData apicLen = len(imageData) #圖片資料區域長度 imageDataHead = struct.pack("!4sI2B",'APIC',apicLen,0,0) imageData = imageDataHead+imageData TPE1 = self.buildItem('TPE1', data[u'Artist'].encode("utf8")) TIT2 = self.buildItem('TIT2', data[u'Title'].encode("utf8")) TALB = self.buildItem('TALB', data[u'Album'].encode("utf8")) #新長度 length += len(imageData) length += len(TPE1) length += len(TIT2) length += len(TALB) header = head[0:3] header += struct.pack('!B',3) header += struct.pack('!H',0) #1位元組留白 header += struct.pack("!I",encode(length+1)) fpNew.write(header) fpNew.write(TPE1) fpNew.write(TIT2) fpNew.write(TALB) fpNew.write(imageData) fpNew.write(struct.pack('!B',0)) fp.seek(originLength,1) #跳 fpNew.write(fp.read()) fpNew.close() fp.close() fpImage.close()