Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Get duration of remote MP3 file without full download

Tags:

node.js

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 ?

like image 388
sdabet Avatar asked May 24 '18 07:05

sdabet


People also ask

How do I find the length of an audio file?

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.


1 Answers

ID3 Metadatas

mp3 files comes (optionnaly) with metadata called "ID3 tags" :

ID3 is a de facto standard for metadata in MP3 files


ID3v1 enhanced tag

  • 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.


ID3v2 TLEN

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.


How to get these metadata without downloading the entire file ?

Range requests

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.

GET request to check for ID3v2

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.

like image 160
boehm_s Avatar answered Oct 17 '22 08:10

boehm_s