如何给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_final def 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 struct 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_final def 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()