在 node.js 脚本中,我需要获取远程 MP3 文件的持续时间。
目前我使用下载和mp3-duration模块来下载文件,然后计算持续时间。它可以工作,但下载阶段很长。
我注意到媒体播放器几乎可以立即显示持续时间。是否有可能在不先下载整个文件的情况下获得持续时间?
mp3 文件(可选)带有称为“ID3 标签”的元数据:
ID3 是 MP3 文件中元数据的事实标准
- ID3v1标签占用128字节,从字符串TAG开始,从文件末尾算起128字节。
- Enhanced 标签长 227 字节,位于 ID3v1 标签之前。
增强标签包含start-time和end-time字段作为标签的最后 6 + 6 个字节,格式类似于mmm:ss。所以它给了你歌曲的持续时间。
解决方案是下载文件的最后 355 个字节并检查是否存在增强标记 ( TAG+),然后查找增强标记的最后 12 个字节。
现在,我们主要使用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标签头[...]应该是文件中的第一个信息,是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 的函数中会更清晰。