In a node.js script I need to get the duration of a remote MP3 file.
Currently I use download and mp3-duration modules to download the file and then compute the duration. It works but the download phase is very long.
I noticed that media players can display the duration almost instantly. Would it be somehow possible to get the duration without downloading the entire file first ?
To determine the file size of an audio file, we have to multiply the bit rate of the audio by its duration in seconds. As a result, we get file size values in terms of kilobits and megabits.
mp3 files comes (optionnaly) with metadata called "ID3 tags" :
ID3 is a de facto standard for metadata in MP3 files
- The ID3v1 tag occupies 128 bytes, beginning with the string TAG 128 bytes from the end of the file.
- The Enhanced tag is 227 bytes long, and placed before the ID3v1 tag.
The enhanced tag contains the start-time
and end-time
fields as the last 6 + 6 bytes of the tag formated like mmm:ss. So it gives you the duration of the song.
The solution would be to download the 355 last bytes of the file and check if the enhanced tag (TAG+
) is present, then look for the last 12 bytes of the enhanced tag.
Nowdays, we're mostly using ID3v2 which allows us to embed up to 256MB of informations :
ID3v2 tag is composed of frames. Each frame represents an information. Max frame size is 16MB. Max tag size is 256MB.
The frame you're interested in is the TLEN
frame, which represent the length of the song.
But there is no guarantee that any mp3 file will have an ID3v2 tag or that the TLEN
frame will be stored in its ID3v2 tag.
/!\ /!\ /!\
If the mp3 file does not contains metadata, the only solution is the one you already came up with : estimating the duration with the mp3-duration module.
If the server accepts Range Request, then we just have to tell him the bytes we want !
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});
/!\ I didn't test the code, it just serves as a demonstration /!\
Then parse the response and check if tagBytes.includes('TAG+')
, and if it does you've got your duration.
ID3v2
You can use the same method to check if the ID3v2 tags are at the beginning of the file and then use a module like node-id3
to parse the tags.
According to the documentation,
The ID3v2 tag header [...] should be the first information in the file, is 10 bytes
So even if the server does not accept Range Requests, you could come up with a solution using a simple GET request and "stopping" it when you receive more than 10 bytes :
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
}
});
});
/!\ I didn't test the code, it just serves as a demonstration /!\
Wrapping it in a function that returns a Promise would be cleaner.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With