class Config { static addFileReader(fileReader) { mediaFileReaders.push(fileReader); return Config; } static addTagReader(tagReader) { mediaTagReaders.push(tagReader); return Config; } static removeTagReader(tagReader) { var tagReaderIx = mediaTagReaders.indexOf(tagReader); if (tagReaderIx >= 0) { mediaTagReaders.splice(tagReaderIx, 1); } return Config; } static EXPERIMENTAL_avoidHeadRequests() { XhrFileReader.setConfig({ avoidHeadRequests: true }); } static setDisallowedXhrHeaders(disallowedXhrHeaders) { XhrFileReader.setConfig({ disallowedXhrHeaders: disallowedXhrHeaders }); } static setXhrTimeoutInSec(timeoutInSec) { XhrFileReader.setConfig({ timeoutInSec: timeoutInSec }); } }
n/a
class Reader { constructor(file) { this._file = file; } setTagsToRead(tagsToRead) { this._tagsToRead = tagsToRead; return this; } setFileReader(fileReader) { this._fileReader = fileReader; return this; } setTagReader(tagReader) { this._tagReader = tagReader; return this; } read(callbacks) { var FileReader = this._getFileReader(); var fileReader = new FileReader(this._file); var self = this; fileReader.init({ onSuccess: function () { self._getTagReader(fileReader, { onSuccess: function (TagReader) { new TagReader(fileReader).setTagsToRead(self._tagsToRead).read(callbacks); }, onError: callbacks.onError }); }, onError: callbacks.onError }); } _getFileReader() { if (this._fileReader) { return this._fileReader; } else { return this._findFileReader(); } } _findFileReader() { for (var i = 0; i < mediaFileReaders.length; i++) { if (mediaFileReaders[i].canReadFile(this._file)) { return mediaFileReaders[i]; } } throw new Error("No suitable file reader found for ", this._file); } _getTagReader(fileReader, callbacks) { if (this._tagReader) { var tagReader = this._tagReader; setTimeout(function () { callbacks.onSuccess(tagReader); }, 1); } else { this._findTagReader(fileReader, callbacks); } } _findTagReader(fileReader, callbacks) { // We don't want to make multiple fetches per tag reader to get the tag // identifier. The strategy here is to combine all the tag identifier // ranges into one and make a single fetch. This is particularly important // in file readers that have expensive loads like the XHR one. // However, with this strategy we run into the problem of loading the // entire file because tag identifiers might be at the start or end of // the file. // To get around this we divide the tag readers into two categories, the // ones that read their tag identifiers from the start of the file and the // ones that read from the end of the file. var tagReadersAtFileStart = []; var tagReadersAtFileEnd = []; var fileSize = fileReader.getSize(); for (var i = 0; i < mediaTagReaders.length; i++) { var range = mediaTagReaders[i].getTagIdentifierByteRange(); if (!isRangeValid(range, fileSize)) { continue; } if (range.offset >= 0 && range.offset < fileSize / 2 || range.offset < 0 && range.offset < -fileSize / 2) { tagReadersAtFileStart.push(mediaTagReaders[i]); } else { tagReadersAtFileEnd.push(mediaTagReaders[i]); } } var tagsLoaded = false; var loadTagIdentifiersCallbacks = { onSuccess: function () { if (!tagsLoaded) { // We're expecting to load two sets of tag identifiers. This flag // indicates when the first one has been loaded. tagsLoaded = true; return; } for (var i = 0; i < mediaTagReaders.length; i++) { var range = mediaTagReaders[i].getTagIdentifierByteRange(); if (!isRangeValid(range, fileSize)) { continue; } try { var tagIndentifier = fileReader.getBytesAt(range.offset >= 0 ? range.offset : range.offset + fileSize, range.length); } catch (ex) { if (callbacks.onError) { callbacks.onError({ "type": "fileReader", "info": ex.message }); return; } } if (mediaTagReaders[i].canReadTagFormat(tagIndentifier)) { callbacks.onSuccess(mediaTagReaders[i]); return; } } if (callbacks.onError) { callbacks.onError({ "type": "tagFormat", "info": "No suitable tag reader found" }); } }, onError: callbacks.onError }; this._loadTagIdentifierRanges(fileReader, tagReadersAtFileS ...
...
});
```
```javascript
// Advanced API
var jsmediatags = require("jsmediatags");
new jsmediatags.Reader("http://www.example.com/music-file.mp3")
.setTagsToRead(["title", "artist"])
.read({
onSuccess: function(tag) {
console.log(tag);
},
onError: function(error) {
console.log(':(', error.type, error.info);
...
function read(location, callbacks) { new Reader(location).read(callbacks); }
...
Run `npm install jsmediatags --save` to install.
```javascript
// Simple API - will fetch all tags
var jsmediatags = require("jsmediatags");
jsmediatags.read("./music-file.mp3", {
onSuccess: function(tag) {
console.log(tag);
},
onError: function(error) {
console.log(':(', error.type, error.info);
}
});
...
bin = function (string) { var binaryArray = new Array(string.length); for (var i = 0; i < string.length; i++) { binaryArray[i] = string.charCodeAt(i); } return binaryArray; }
n/a
getInteger24 = function (number) { return [number >> 16 & 0xff, number >> 8 & 0xff, number & 0xff]; }
n/a
getInteger32 = function (number) { return [number >> 24 & 0xff, number >> 16 & 0xff, number >> 8 & 0xff, number & 0xff]; }
n/a
getSynchsafeInteger32 = function (number) { // 0x7f = 0b01111111 return [number >> 21 & 0x7f, number >> 14 & 0x7f, number >> 7 & 0x7f, number & 0x7f]; }
n/a
pad = function (array, size) { for (var i = array.length; i < size; i++) { array.push(0); } return array; }
n/a
getFrameReaderFunction = function (frameId) { if (frameId in frameReaderFunctions) { return frameReaderFunctions[frameId]; } else if (frameId[0] === "T") { // All frame ids starting with T are text tags. return frameReaderFunctions["T*"]; } else if (frameId[0] === "W") { // All frame ids starting with W are url tags. return frameReaderFunctions["W*"]; } else { return null; } }
...
// (after unsynchronisation && encryption)
if (flags && flags.format.data_length_indicator) {
// var frameDataSize = frameData.getSynchsafeInteger32At(frameDataOffset);
frameDataOffset += 4;
frameSize -= 4;
}
var readFrameFunc = ID3v2FrameReader.getFrameReaderFunction(frameId);
var parsedData = readFrameFunc ? readFrameFunc(frameDataOffset, frameSize, frameData, flags) : null;
var desc = this._getFrameDescription(frameId);
var frame = {
id: frameId,
size: frameSize,
description: desc,
...
readNullTerminatedString = function (bytes, maxBytes) { var arr = []; maxBytes = maxBytes || bytes.length; for (var i = 0; i < maxBytes;) { var byte1 = bytes[i++]; if (byte1 == 0x00) { break; } arr[i - 1] = String.fromCharCode(byte1); } return new InternalDecodedString(arr.join(""), i); }
...
break;
case "utf-8":
string = StringUtils.readUTF8String(bytes);
break;
default:
string = StringUtils.readNullTerminatedString(bytes);
break;
}
return string;
}
getCharAt(offset) {
...
readUTF16String = function (bytes, bigEndian, maxBytes) { var ix = 0; var offset1 = 1, offset2 = 0; maxBytes = Math.min(maxBytes || bytes.length, bytes.length); if (bytes[0] == 0xFE && bytes[1] == 0xFF) { bigEndian = true; ix = 2; } else if (bytes[0] == 0xFF && bytes[1] == 0xFE) { bigEndian = false; ix = 2; } if (bigEndian) { offset1 = 0; offset2 = 1; } var arr = []; for (var j = 0; ix < maxBytes; j++) { var byte1 = bytes[ix + offset1]; var byte2 = bytes[ix + offset2]; var word1 = (byte1 << 8) + byte2; ix += 2; if (word1 == 0x0000) { break; } else if (byte1 < 0xD8 || byte1 >= 0xE0) { arr[j] = String.fromCharCode(word1); } else { var byte3 = bytes[ix + offset1]; var byte4 = bytes[ix + offset2]; var word2 = (byte3 << 8) + byte4; ix += 2; arr[j] = String.fromCharCode(word1, word2); } } return new InternalDecodedString(arr.join(""), ix); }
...
var bytes = this.getBytesAt(offset, length);
var string;
switch ((charset || '').toLowerCase()) {
case "utf-16":
case "utf-16le":
case "utf-16be":
string = StringUtils.readUTF16String(bytes, charset === "utf-16be");
break;
case "utf-8":
string = StringUtils.readUTF8String(bytes);
break;
default:
...
readUTF8String = function (bytes, maxBytes) { var ix = 0; maxBytes = Math.min(maxBytes || bytes.length, bytes.length); if (bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF) { ix = 3; } var arr = []; for (var j = 0; ix < maxBytes; j++) { var byte1 = bytes[ix++]; if (byte1 == 0x00) { break; } else if (byte1 < 0x80) { arr[j] = String.fromCharCode(byte1); } else if (byte1 >= 0xC2 && byte1 < 0xE0) { var byte2 = bytes[ix++]; arr[j] = String.fromCharCode(((byte1 & 0x1F) << 6) + (byte2 & 0x3F)); } else if (byte1 >= 0xE0 && byte1 < 0xF0) { var byte2 = bytes[ix++]; var byte3 = bytes[ix++]; arr[j] = String.fromCharCode(((byte1 & 0xFF) << 12) + ((byte2 & 0x3F) << 6) + (byte3 & 0x3F)); } else if (byte1 >= 0xF0 && byte1 < 0xF5) { var byte2 = bytes[ix++]; var byte3 = bytes[ix++]; var byte4 = bytes[ix++]; var codepoint = ((byte1 & 0x07) << 18) + ((byte2 & 0x3F) << 12) + ((byte3 & 0x3F) << 6) + (byte4 & 0x3F) - 0x10000; arr[j] = String.fromCharCode((codepoint >> 10) + 0xD800, (codepoint & 0x3FF) + 0xDC00); } } return new InternalDecodedString(arr.join(""), ix); }
...
case "utf-16":
case "utf-16le":
case "utf-16be":
string = StringUtils.readUTF16String(bytes, charset === "utf-16be");
break;
case "utf-8":
string = StringUtils.readUTF8String(bytes);
break;
default:
string = StringUtils.readNullTerminatedString(bytes);
break;
}
...