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;
}
...