当前位置:   article > 正文

python解析mp3,获取mp3的元数据_python mp3解码算法

python mp3解码算法

概述

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,其数据说明如下:

名称字节说明
Header3ID3V2标示符“ID3”的ascii编码,否则认为没有ID3V2
Ver1版本号,=03
Reveision1副版本号,=00
flag1标志位,一般没意义,=00
Size4标签内容长度,高位在前,不包括标签头的10个字节

表说明

  • Size 字段的计算公式如下(从左至右):
    size=字节1多值x&H200000 + 字节2多值x&H4000 + 字节3的值x&H80 + 字节4的值
  • 如果所有标签帧的总长度 < 标签内容长度,则须用0填满
ID3V2标签帧

标签内容由若干个标签帧组成,每个标签帧都由一个10字节的枕头和至少1个字节的不固定长度的帧内容组成,她们顺序存放在文件中。每个帧都由帧头和帧内容组成,数据说明如下:

名称字节说明
FrameID4帧标识符的ascii编码,常用标识符见【常见标识符表】说明
Size4帧内容及编码方式的合计长度,高位在前
Flags2标志位,只使用了6位,详细见【标志位表】,一般均=0
encode4帧内容所用的编码方式,一般没有此项
帧内容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标识符
MLLTMPEG位置查找表格
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
  • 1

输出结果如下:

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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
实例代码
#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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

实例运行结果如下:

{'title': '男儿志(《少林足球》电影插曲)', 'artist': 'Decode fail', 'genre': 255}
  • 1

实例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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
实例结果

实例运行结果如下:

标题: 1-白日梦
艺术家: 1-白日梦
专辑: 配乐大师
  • 1
  • 2
  • 3
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/674255
推荐阅读
相关标签
  

闽ICP备14008679号