Python实现mp3 ID3v2.3信息提取

Python实现mp3 ID3v2.3信息提取

使用python实现对mp3 ID3v2.3提取歌名,歌手,专辑,时长,图片。(附源码)

提取内容

  1. 歌曲名称;
  2. 歌手名称;
  3. 专辑名称;
  4. 歌曲时长;
  5. 歌曲图片;

结果返回为字典格式,这样可以方便的将其转换为json格式。 其中,歌曲图片为图片的保存路径。

实现原理

使用python读取mp3文件的16进制(可使用WinHex进行16进制分析),根据官网给出的解释,对照提取信息。

  1. 歌曲名称、歌手名称、专辑名称,为mp3对应的标签头数据;
  2. 歌曲时长 使用歌曲实际内容部分来进行计算,为了匹配更为准确,这里是从496e666f0000000f 也就是 Info… 处,我们将它作为开始计算的提取标记,当然和歌曲实际内容的起始位置是存在一定误差的,但不影响最终的 歌曲时长 输出结果。
    Info… 作为起始位置(并非歌曲实际起始位置),计算到文件末尾的字节长度,这就是歌曲的字节数,乘以8得到比特,再除以比特率(128kbps):128*1000,得到最终结果就是歌曲时长(单位/s)。
    公式:字节长度 * 8 / (128 * 1000)
  3. 歌曲图片 是从 ffd8 开始到 496e666f0000000f 结束,其中结束位置本应该是 ffd9 但为了匹配更准确这里也是按照 Info… 标记位置进行定位,所以也是存在一定误差的,但不影响最终的 歌曲图片 的输出结果。
    :代码只实现了jpg格式图片的提取,可能存在png图片信息,代码未实现)

★ 实现原理16进制对照分析图(请对照 id3v2.3.0官网 给出的标签格式进行理解)

16进制分析图1
…(此处有省略)
16进制分析图2
标签类型 TIT2:歌曲名称,TPE1:歌手名称,TALB:专辑名称

TIT2标签歌曲名称分析:

16进制:
5449543200000011000001FFFEE0563A4E604F20004062E54E1162

  1. 54495432(H) TIT2标签-歌曲名称
  2. 00000011(H)标签长度,转为十进制为 17(D),也就是标签长度为17个字节,包含编码格式1个字节,也就是说实际歌曲名称信息内容为16个字节。
    即:FFFEE0563A4E604F20004062E54E1162 (16进制两位一个字节哦)
  3. 0000(H)Flags,我也母鸡呀~
  4. 01(H)编码格式
    如下:
    0:表示帧内容字符用 ISO-8859-1 编码;
    1:表示帧内容字符用 UTF-16LE 编码;
    2:表示帧内容字符用 UTF-16BE 编码;
    3:表示帧内容字符用 UTF-8 编码(仅ID3V2.4才支持)
  5. FFFEE0563A4E604F20004062E54E1162(H)歌曲名称,需要根据第4点的编码格式进行转码,

其他标签同理可得。

文件数据

本段程序,只需要引用一个 re 模块,因为用到了正则表达式,代码拷下来可以直接跑。

程序将会产生一个 log.txt 文件用于收集日志。所在位置为 程序运行的文件夹目录 下。

程序提取的 图片的路径歌曲所在文件夹的路径
例如:
文件保存样式

实现代码

# 引用所需模块
import re  # 正则表达式

# mp3信息和路径提取
def mp3Info(input_file_url):
    # 读取mp3文件
    input_file_url = input_file_url
    with open(input_file_url, "rb") as input_file:
        mp3_data = input_file.read().hex()

    # 判断mp3文件类型是否是 ID3v2.3 格式
    if mp3_data[:6] == "494433" and mp3_data[6:8] == "03":
        print("歌曲是ID3v2.3版本,正在提取信息...")

        # 获取歌曲名称
        if re.search(r"\\", input_file_url):
            input_file_name = re.search("(.*).mp3", input_file_url.split("\\")[-1]).group(1)
        else:
            input_file_name = re.search("(.*).mp3", input_file_url).group(1)

        # 获取歌曲路径
        if re.search("(.*)" + input_file_name + ".mp3", input_file_url):
            input_file_path = re.search("(.*)" + input_file_name + ".mp3", input_file_url).group(1)
        else:
            input_file_path = ""

        return mp3_data, input_file_path, input_file_name  # 返回 mp3 16进制数据,输入文件路径,输入文件文件名
    else:
        return ""


# 提取标签内容函数
def tagInfo(tag_name, mp3_data):
    # 标签名称,mp3完整数据
    tag_name, mp3_data = tag_name, mp3_data

    # 标签列表
    tag_name_list = {"TIT2": "54495432", "TPE1": "54504531", "TALB": "54414c42"}
    if tag_name in tag_name_list:
        tag_hex = tag_name_list[tag_name]  # 标签的 16 进制数据

        # 标签长度
        tag_len = int(re.search(tag_hex + "(.{8})", mp3_data).group(1), 16)
        # print("%s 标签的长度是 %s 个字节" % (tag_name,tag_len))

        # 判断标签类型
        tag_index = mp3_data.find(tag_hex)
        tag_data_type = mp3_data[(tag_index + 2 * (4 + 4 + 2)):(tag_index + 2 * (4 + 4 + 2 + 1))]

        # 判断内容编码方式
        if tag_data_type == "00":
            encoding_type = 'iso8859-1'
            # print("采用 ISO-8859-1 编码 ")
        if tag_data_type == "01":
            encoding_type = 'utf-16-le'
            # print("采用 UTF-16LE 编码 ")
        elif tag_data_type == "02":
            encoding_type = 'utf-16-be'
            # print("采用 UTF-16BE 编码 ")
        # elif tag_data_type == "03":    # (仅ID3V2.4才支持)
        #     encoding_type = 'utf-8'
        #     print("采用 UTF-8 编码 ")

        # 提取标签内容
        tag_data_hex = mp3_data[(tag_index + 2 * (4 + 4 + 2 + 1)):(
                tag_index + 2 * (4 + 4 + 2 + 1) + tag_len * 2 - 2)]  # 取标签内容(16进制)
        tag_data_bytes = bytes.fromhex(tag_data_hex)  # 将字符串转换为字节流数据
        tag_info = tag_data_bytes.decode(encoding_type, 'ignore')  # 根据编码类型解码

        return tag_info  # 返回标签的内容
    else:
        return ""


# 提取所需标签信息
def tagsInfo():
    # 标签类型 TIT2:标题,TPE1:艺术家,TALB:专辑
    tags = {"TIT2": "歌名", "TPE1": "歌手", "TALB": "专辑"}

    # 标签内容提取
    tags_info = {}
    for i in tags:
        tag_name = i
        tag_info = tagInfo(tag_name, mp3_data)  # 标签数据
        tags_info[tags[i]] = tag_info.encode('utf-8').decode('utf-8-sig')  # 使用utf-8-sig编码,否则出现'\ufeff' BOM数据

    return tags_info  # 返回所有标签的内容


# 获取歌曲时长函数
def mp3Duration(mp3_data):
    music_index = mp3_data.find("496e666f0000000f")  # 定位歌曲实际的起始位置
    music_size = len(mp3_data[music_index:]) / 2  # 歌曲字节长度
    duration = music_size * 8 / (128 * 1000)  # 获取歌曲时长,单位 s
    duration_show = str(int(duration / 60)) + ":" + str(int(duration % 60))  # 格式化歌曲时长
    return duration_show  # 返回格式化的歌曲时长


# 提取图片函数
def imgTag(mp3_data, input_file_path, input_file_name):
    # 歌曲数据,输入文件的路径,输入文件的文件名
    mp3_data, input_file_path, input_file_name = mp3_data, input_file_path, input_file_name

    # 图片数据的提取
    img_data_hex = re.search(r"ffd8.+?496e666f0000000f", mp3_data)[0]  # 图片的 16 进制数据
    if img_data_hex:
        img_data_bytes = bytes.fromhex(img_data_hex)  # 将字符串转换为字节流数据
        out_file_name = input_file_path + input_file_name + '.jpg'
        with open(out_file_name, "wb") as out_file:
            out_file.write(img_data_bytes)

        return "%s%s.jpg" % (input_file_path, input_file_name)
    else:
        return ""


if __name__ == '__main__':
    try:
        # 提示信息
        print("##### 本程序为提取 mp3 ID3v2.3 格式的歌曲信息 #####")

        # mp3路径
        input_file_url = input("请输入需要提取的文件路径:")

        # 获取歌曲信息
        mp3_info = mp3Info(input_file_url)
        if mp3_info:
            # 获取文件路径,文件名
            mp3_data, input_file_path, input_file_name = mp3_info  # 返回 mp3 16进制数据,输入文件路径,输入文件文件名

            # 获取标签数据
            tags_info = tagsInfo()

            # 获取歌曲时长信息
            mp3_duration = mp3Duration(mp3_data)
            tags_info["时长"] = mp3_duration  # 添加时长字段

            # 获取图片数据
            imgInfo = imgTag(mp3_data, input_file_path, input_file_name)
            tags_info["图片路径"] = imgInfo  # 添加图片字段
            print(tags_info)

            # 日志信息
            log = "歌曲路径: " + input_file_path + input_file_name + ".mp3 \n" + "歌曲信息:" + str(
                tags_info) + "\n\n"
        else:
            # 日志信息
            log = "暂不支持此文件的提取,本程序仅支持 ID3v2.3 格式的 mp3 文件\n\n"
            print("暂不支持此文件的提取,本程序仅支持 ID3v2.3 格式的 mp3 文件")
    except:
        # 日志信息
        log = "意外错误\n\n"
        print("意外错误")
    finally:
        # 保存日志
        with open(r"log.txt", "a") as out_file:
            out_file.write(log)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值