文章目录

  • 概述
  • 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-白日梦
专辑: 配乐大师