UNPKG

mp3tag

Version:

A library for reading/writing mp3 tag data

127 lines (126 loc) 5.5 kB
"use strict"; 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; }