mp3tag
Version:
A library for reading/writing mp3 tag data
127 lines (126 loc) • 5.5 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.readHeader = void 0;
// External dependencies
const _ = __importStar(require("lodash"));
// Utilities
const encoding = __importStar(require("./encoding"));
// Classes
const file_1 = require("./file");
const tagdata_1 = require("./tagdata");
const frame_1 = require("./frame");
/** Reads the id3v2 tag data from the provided file.
* This is the only exported function as the root entry point into the library.
*
* @param path the file path to read the tagdata from
*
* @return a promise, which resolves to the parsed tag data
*/
function readHeader(path) {
return readID3v2(path);
}
exports.readHeader = readHeader;
/** Reads the id3v2 tag data from the provided file.
*
* @param {string} path the filepath to read the tag data from
*
* @return {Promise<TagData>} resolves to the parsed tag data
*/
async function readID3v2(path) {
const file = await file_1.File.open(path, "r");
const header = Buffer.alloc(tagdata_1.TagData.TAG_HEADER_SIZE);
const bytesRead = await file.read(header, 0, header.length); // Read 10 Byte header
if (bytesRead < tagdata_1.TagData.TAG_HEADER_SIZE) {
throw new Error("Can't read ID3 tag header!");
}
const marker = header.toString('ascii', 0, 3);
const majorVersion = header.readUInt8(3);
const minorVersion = header.readUInt8(4);
const flags = header.readUInt8(5);
// Add constant header size to given header size to make it comparable with the file's offset
let headerSize = encoding.decodeUInt7Bit(header.readUInt32BE(6)) + tagdata_1.TagData.TAG_HEADER_SIZE;
if (marker !== "ID3") { //No header at all
// Simply return an empty dummy header that won't be written unless a frame gets created
return tagdata_1.TagData.noHeader(file);
}
else {
// This library supports v2.3 and v2.4 (though 2.4 is not yet complete)
let hasFooter = false;
if (majorVersion === 4) {
hasFooter = (flags & 0x10) != 0;
if (hasFooter) {
headerSize += tagdata_1.TagData.TAG_FOOTER_SIZE; // footer isn't included in the headersize either
}
/** Meaning of the footer:
*
* 1. Having a footer means, the file cannot contain padding
* 2. The footer is (like the header) not included in the header's size field
* 3. The footer is 10 bytes long and has the structure:
* "3DI" <majorV:1><minorV:1><flags:1><size:4>
*/
}
else if (majorVersion !== 3) {
throw new Error(`Unsupported ID3 version: 2.${majorVersion}.${minorVersion}`);
}
const hasExtendedHeader = (flags & 0x40) != 0;
if (hasExtendedHeader) {
throw new Error("No support for extended header yet!");
}
let frames = await readFrames(file, headerSize);
frames = frames || [];
const padding = getPadding(frames, headerSize); // Filter out padding
const dataFrames = _.filter(frames, frame => !frame.isPadding());
return new tagdata_1.TagData(file, { 'major': majorVersion, 'minor': minorVersion }, flags, headerSize, dataFrames, padding);
}
}
/** This function determines the padding offset and size for the given frames.
*
* @param frames the frames which were returned by getFrames (ordered in that way)
*
* @return an object which represents the file's padding.
* Length will be zero if file isn't padded.
*/
function getPadding(frames, headerSize) {
// Last frame is the padding frame (if any)
// In case we read 0 frames, we generate an empty padding starting at the end of the header
const lastFrame = frames[frames.length - 1] ?? new frame_1.PaddingFrame(headerSize, 0);
return lastFrame.getPadding();
}
/** Reads the frames from the provided tag file into a frame array. The first frame is
* read from the current file position and frame reading stops at file position `tagSize`.
*
* @param file the file to read the frames from
* @param tagSize the parsed tag size (position in file where the frames end)
*
* @return {Promise<Frame[]>} resolves to all parsed frames including padding frames if present.
*/
async function readFrames(file, tagSize) {
const frames = [];
while (file.offset < tagSize) {
frames.push(await frame_1.Frame.read(file, tagSize));
}
return frames;
}