用python给MP3加封面图片,修改作者,专辑等信息

一个需求,用户上传的mp3需要给自动加上用户传的封面图,

研究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()

  • 5
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值