文章目录
- 概述
- mp3(一种音频编码方式)
- mp3的Tag
- 标签说明
- TAG_V1(ID3V1)
- TAG_V2(ID3V2)
- ID3V2 标签头
- ID3V2标签帧
- 常用标识符表
- Flags标志的说明
- 实例(ID3V1)
- 前提
- 实例代码
- 实例2(ID3V2)
- 实例代码
- 实例结果
概述
mp3(一种音频编码方式)
以下来自百度百科。
MP3是一种音频压缩技术,其全称是动态影像专家压缩标准音频层面3(Moving Picture Experts Group Audio Layer III),简称为MP3。它被设计用来大幅度地降低音频数据量。利用 MPEG Audio Layer 3 的技术,将音乐以1:10 甚至 1:12 的压缩率,压缩成容量较小的文件,而对于大多数用户来说重放的音质与最初的不压缩音频相比没有明显的下降。它是在1991年由位于德国埃尔朗根的研究组织Fraunhofer-Gesellschaft的一组工程师发明和标准化的。用MP3形式存储的音乐就叫作MP3音乐,能播放MP3音乐的机器就叫作MP3播放器。
mp3的Tag
mp3 的tag一般情况下,指的是mp3的元数据,一个规则mp3文件通常包含有3个部分:
- TAG_V2(ID3V2)
- FRAME
- TAG_V1(ID3V1)
标签说明
标签 | 说明 |
ID3V2 | 包含了作者、作曲、专辑等信息,长度不固定,拓展了ID3V1的信息量 |
FRAME | 一系列的帧,个数由文件大小和帧长决定 |
· | 每个FRAME的长度可能不固定,也可能是固定,由位率bitrate 决定 |
· | 每个FRAME又分为帧头和数据实体两部分 |
· | 帧头记录了mp3的位率,采样率,版本等信息,每个帧之间相互独立 |
· | |
ID3V1 | 包含了作者,作曲,专辑等信息,长度为128byte |
TAG_V1(ID3V1)
TAG_V1部分是mp3文件等最后128byte等内容,其中包含的信息如下:
- 标签头“TAG”3个byte
- 标题 30个byte
- 作者 30个byte
- 专辑 30个byte
- 出品年份 4个byte
- 备注信息 28个byte
- 保留 1个byte
- 音轨 1个byte
- 类型1字节
TAG_V2(ID3V2)
ID3V2与ID3V1的作用差不多,也是记录mp3的有关信息,但ID3V2的结构比ID3V要复杂得多,而且可以伸缩和拓展。ID3V2到现在一共有4个版本,但流行的播放软件一般只支持第3版,即ID3V2.3。由于ID3V1记录在mp3文件的末尾,ID3V2就只好记录在mp3文件的首部了。
每个ID3V2.3的标签都是由一个标签头和热干个标签帧或一个拓展标签头组成。歌曲的信息如标题、作者等都存放在不同的标签帧中,拓展标签头和标签并不是必须的,但每个标签至少要有一个标签帧。
ID3V2 标签头
一个mp3文件如果有ID3V2的话,那么ID3V2的标签头占用文件最前面的10个byte,其数据说明如下:
名称 | 字节 | 说明 |
Header | 3 | ID3V2标示符“ID3”的ascii编码,否则认为没有ID3V2 |
Ver | 1 | 版本号,=03 |
Reveision | 1 | 副版本号,=00 |
flag | 1 | 标志位,一般没意义,=00 |
Size | 4 | 标签内容长度,高位在前,不包括标签头的10个字节 |
表说明
- Size 字段的计算公式如下(从左至右):
size=字节1多值x&H200000 + 字节2多值x&H4000 + 字节3的值x&H80 + 字节4的值 - 如果所有标签帧的总长度 < 标签内容长度,则须用0填满
ID3V2标签帧
标签内容由若干个标签帧组成,每个标签帧都由一个10字节的枕头和至少1个字节的不固定长度的帧内容组成,她们顺序存放在文件中。每个帧都由帧头和帧内容组成,数据说明如下:
名称 | 字节 | 说明 |
FrameID | 4 | 帧标识符的ascii编码,常用标识符见【常见标识符表】说明 |
Size | 4 | 帧内容及编码方式的合计长度,高位在前 |
Flags | 2 | 标志位,只使用了6位,详细见【标志位表】,一般均=0 |
encode | 4 | 帧内容所用的编码方式,一般没有此项 |
帧内容 | 0 | 至少1个字节 |
表说明
- 标签帧之间没有特殊的分隔符,要得到一个完整的标签帧内容必须先从帧头中得到帧内容长度
- encode有4个可能值:
- 0:表示帧内容字符用ISO-8859-1编码
- 1:表示帧内容字符用UTF-16LE编码
- 2:表示帧内容字符用UTF-16BE编码
- 3:表示帧内容字符用UTF-8编码(仅ID3V2才支持)
- 帧内容均为字符串,常以00开头
常用标识符表
类型 | 说明 |
AENC | 音频加密技术 |
APIC | 附加描述 |
COMM | 注释,相当于ID3V1的Comment |
COMR | 广告 |
ENCR | 加密方法注册 |
ETC0 | 事件时间编码 |
GEOB | 常规压缩对象 |
GRID | 组识别注册 |
IPLS | 复杂类别列表 |
MCDI | 音乐CD标识符 |
MLLT | MPEG位置查找表格 |
OWNE | 所有权 |
PRIV | 私有 |
PCNT | 播放计数 |
POPM | 普通仪表 |
POSS | 位置同步 |
RBUF | 推荐缓冲区大小 |
RVAD | 音量调节器 |
RVRB | 混响 |
SYLT | 同步歌词或文本 |
SYTC | 同步节拍编码 |
TALB | 专辑,相当于ID3V1的Album |
TBPM | 每分钟节拍数 |
TCOM | 作曲家 |
TCON | 流派风 |
TCOP | 版权 |
TDAT | 日期 |
TDLY | 播放列表返录 |
TENC | 编码 |
TEXT | 歌词作者 |
TFLT | 文件类型 |
TIME | 时间 |
TIT1 | 内容组描述 |
TIT2 | 标题,相当于ID3V1的Title |
TIT3 | 副标题 |
TKEY | 最初关键字 |
TLAN | 语言 |
TLEN | 长度 |
TMED | 媒体类型 |
TOAL | 原唱片集 |
TOFN | 原文件名 |
TOLY | 原歌词作者 |
TOPE | 原艺术家 |
TORY | 最初发行年份 |
TOWM | 文件所有者 |
TPE1 | 艺术家,相当于ID3V1的Artist |
TPUB | 发行人 |
TACK | 音轨,相当于ID3V1的Track |
TRDA | 录制日期 |
TSIZ | 大小 |
TSSE | 编码使用的软件 |
TYER | 年代,相当于ID3V1的Year |
TXXX | 年度 |
UFID | 唯一文件标识符 |
USLT | 歌词 |
Flags标志的说明
位址 | 说明 |
0 | 标签保护标志,如设置表示此帧作废 |
1 | 文件保护标志,如设置表示此帧作废 |
2 | 只读标志,如设置表示此帧不能修改 |
3 | 压缩标志,如设置表示1个字节存放2个BCD码表示数字 |
4 | 加密标志 |
5 | 组标志,如设置表示此帧和其他的某帧是一组 |
实例(ID3V1)
下面举例说明python读取mp3元数据。
前提
python读取mp3的ID3V1(TAG V1)。
使用如下命令查看python是否已经安装chardet.
pip list
输出结果如下:
Package Version
---------- ---------
certifi 2020.12.5
chardet 4.0.0
idna 2.10
pip 20.2.2
pyexiv2 2.4.1
requests 2.25.1
setuptools 49.2.1
urllib3 1.26.2
wheel 0.34.2
实例代码
#coding: UTF-8
import os
import string
import base64
import chardet
'''
解析mp3,获取TAG_V1
'''
def parse(fileObj,version='v1'):
fileObj.seek(0,2)
if(fileObj.tell()<128):
return False
fileObj.seek(-128,2)
tag_data=fileObj.read()
if(tag_data[0:3] != b'TAG'):
return False
return getTag(tag_data)
def decodeData(bin_seq):
result=chardet.detect(bin_seq)
# print(result)
if(result['confidence']>0):
try:
return bin_seq.decode(result['encoding'])
except:
return 'Decode fail'
def getTag(tag_data):
STRIP_CHARS=b'\x00'
tags={}
tags['title']=tag_data[3:33].strip(STRIP_CHARS)
if(tags['title']):
tags['title'] = decodeData(tags['title'])
tags['artist']=tag_data[33:63].strip(STRIP_CHARS)
if(tags['artist']):
tags['artist']=decodeData(tags['artist'])
tags['genre'] = ord(tag_data[127:128])
return tags
f=open('/Users/michaelkoo/work/env/csdn/nanerzhi.mp3','rb')
t=parse(f)
print(t)
实例运行结果如下:
{'title': '男儿志(《少林足球》电影插曲)', 'artist': 'Decode fail', 'genre': 255}
实例2(ID3V2)
实例代码
如下:
"""
处理mp3解析,获取ID3V2信息
"""
import struct
encodings = ['GBK', 'UTF-16', 'UTF-16BE', 'UTF-8']
def parse_ID3V2_frames(frames_bin):
"""
解析帧数据
:param frames_bin:
:return:
"""
pointer = 0
frames_bin_size = len(frames_bin)
frames = {}
while pointer < frames_bin_size - 10:
frame_header_bin = frames_bin[pointer:pointer + 10]
frame_header = struct.unpack('>4sI2s', frame_header_bin)
frame_body_size = frame_header[1]
if frame_body_size == 0:
break
pointer += 10
frames[frame_header[0]] = frames_bin[pointer:pointer + frame_body_size]
pointer += frame_body_size
TIT2_bin = frames.get(b'TIT2', None)
TPE1_bin = frames.get(b'TPE1', None)
TALB_bin = frames.get(b'TALB', None)
if TALB_bin:
encoding = encodings[TALB_bin[0]]
frames[b'TALB'] = TALB_bin[1:].decode(encoding)
if TIT2_bin:
encoding = encodings[TIT2_bin[0]]
frames[b'TIT2'] = TIT2_bin[1:].decode(encoding)
if TPE1_bin:
encoding = encodings[TPE1_bin[0]]
frames[b'TPE1'] = TPE1_bin[1:].decode(encoding)
return frames
def parse_ID3V2_head(head_bin):
"""
获取数据头位置
:param head_bin:
:return:
"""
if head_bin[:3] != b'ID3':
return None
frames_bin_size = (head_bin[6] << 21 | head_bin[7] << 14 |
head_bin[8] << 7 | head_bin[9])
return frames_bin_size
def read_mp3_tag_v2():
"""
:return:
"""
f = open('/Users/michaelkoo/work/env/csdn/dream.mp3', 'rb')
frames_bin_size = parse_ID3V2_head(f.read(10))
if frames_bin_size is None:
f.close()
print('it not contain ID3V2')
return
frames_bin = f.read(frames_bin_size - 10)
f.close()
frames = parse_ID3V2_frames(frames_bin)
tit2 = frames[b'TIT2']#标题
print('标题:', tit2)
tpe1 = frames[b'TPE1']#作者
print('艺术家:',tpe1)
talb = frames[b'TALB']#专辑
print('专辑:', talb)
pass
实例结果
实例运行结果如下:
标题: 1-白日梦
艺术家: 1-白日梦
专辑: 配乐大师