代码之家  ›  专栏  ›  技术社区  ›  sdabet

获取不完全下载的远程MP3文件的持续时间

  •  3
  • sdabet  · 技术社区  · 6 年前

    在node.js脚本中,我需要获取远程MP3文件的持续时间。

    当前我使用 download mp3-duration 模块下载文件,然后计算持续时间。它可以工作,但下载阶段很长。

    我注意到媒体播放器几乎可以立即显示持续时间。是否有可能在不先下载整个文件的情况下获得持续时间?

    1 回复  |  直到 6 年前
        1
  •  3
  •   boehm_s    6 年前

    ID3元数据

    MP3文件(可选)带有名为“ID3标记”的元数据:

    ID3是MP3文件中元数据的事实标准


    ID3v1增强型标签

    • id3v1标记占据128个字节,从文件末尾开始的字符串标记128个字节。
    • 增强标记的长度为227字节,并放在ID3v1标记之前。

    增强标记包含 start-time end-time 字段作为标记的最后6+6字节,格式如下 MSM:SS . 所以它给出了这首歌的持续时间。

    解决方案是下载文件的最后355个字节,并检查增强标记( TAG+ )存在,然后查找增强标记的最后12个字节。


    ID3V2电缆

    现在,我们主要使用 ID3V2 它允许我们嵌入高达256MB的信息:

    ID3V2 标签由框架组成。每个帧代表一个信息。最大帧大小为16MB。最大标签大小为256MB。

    你感兴趣的框架是 TLEN 帧,代表歌曲的长度。

    但不能保证任何MP3文件都有id3v2标签,或者 泰伦 帧将存储在其ID3v2标记中。


    !\!\!\
    如果MP3文件不包含元数据,则唯一的解决方案是您已经提出的解决方案:使用 MP3持续时间 模块。


    如何在不下载整个文件的情况下获取这些元数据?

    范围请求

    如果服务器接受 Range Request ,那么我们只需要告诉他我们想要的字节!

    ID3V1

    const request = require('request-promise');
    const NodeID3 = require('node-id3');
    const mp3FileURL = "http://musicSite/yourmp3File.mp3";
    
    const mp3FileHEAD = await request.head(mp3FileURL);
    const serverAcceptRangeReq = mp3FileHEAD.headers['Accept-Ranges'] && mp3FileHEAD.headers['Accept-Ranges'].toLowerCase() != "none";
    
    // here we assume that the server accepts range requests
    
    const mp3FileSize = mp3FileHEAD.headers['Content-Length'];
    const tagBytesHeader = {'Range': `${mp3FileSize - 355}-${mp3FileSize}`};
    const tagBytes = await request.get(mp3FileURL, {headers: tagBytesHeader});
    

    !\ 我没有测试代码,它只是作为一个演示 !\

    然后分析响应并检查 tagBytes.includes('TAG+') 如果有,你就有你的持续时间。

    ID3V2

    您可以使用相同的方法检查id3v2标记是否在文件的开头,然后使用类似的模块 node-id3 分析标签。

    获取检查ID3v2的请求

    根据文件,

    id3v2标记头[…]应该是文件中的第一个信息,为10个字节。

    因此,即使服务器不接受范围请求,您也可以使用简单的GET请求提出解决方案,并在收到超过10个字节时“停止”它:

    const request = require('request');
    let buff = new Buffer(0);
    
    const r = request
      .get(mp3FileURL)
      .on('data', chunk => {
          buff = Buffer.concat([buff, chunk]);
          if (buff.length > 10) {
              r.abort();
              // deal with your buff
          }
      });
    });
    

    !\ 我没有测试代码,它只是作为一个演示 !\

    将它包装在一个返回承诺的函数中会更干净。