无需完整下载即可获取远程 MP3 文件的持续时间

sda*_*bet 7 node.js

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

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

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

boe*_*m_s 5

ID3元数据

mp3 文件(可选)带有称为“ID3 标签”的元数据:

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


ID3v1 增强型标签

  • ID3v1标签占用128字节,从字符串TAG开始,从文件末尾算起128字节。
  • Enhanced 标签长 227 字节,位于 ID3v1 标签之前。

增强标签包含start-timeend-time字段作为标签的最后 6 + 6 个字节,格式类似于mmm:ss。所以它给了你歌曲的持续时间。

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


ID3v2 特伦

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

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

您感兴趣的TLEN帧是帧,它代表歌曲的长度。

但不能保证任何 mp3 文件都会有 ID3v2 标签或TLEN帧将存储在其 ID3v2 标签中。


/!\ /!\ /!\
如果 mp3 文件不包含元数据,唯一的解决方案是您已经提出的解决方案:使用mp3-duration模块估计持续时间


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

范围请求

如果服务器接受范围请求,那么我们只需要告诉他我们想要的字节!

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});
Run Code Online (Sandbox Code Playgroud)

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

然后解析响应并检查是否tagBytes.includes('TAG+'),如果是,则您有持续时间。

ID3v2

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

检查 ID3v2 的 GET 请求

根据文档,

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
      }
  });
});
Run Code Online (Sandbox Code Playgroud)

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

将它包装在一个返回 Promise 的函数中会更清晰。