mp3tag
Version:
A library for reading/writing mp3 tag data
156 lines (155 loc) • 5.78 kB
JavaScript
;
/** Frame class module
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.PaddingFrame = exports.Frame = void 0;
const data_1 = require("./data");
/**
* This class represents a single id3 frame in the mp3 file.
*/
class Frame {
id;
pos;
size;
data;
flags;
// Constant size of frame header
static HEADER_SIZE = 10;
/**
* @param id the frame id (no necesarily unique)
* @param pos byte position in the file
* @param size the size of the frame
* @param data the data object for the frame content buffer. If no data is passed, the frame is interpreted as padding frame
* @param flags the frame flags
*/
constructor(id, pos, size, data, flags = 0) {
this.id = id;
this.pos = pos;
this.size = size;
this.data = data;
this.flags = flags;
}
/** Used to check whether this frame is a padding frame
* Returns false for all regular frames
*/
isPadding() { return false; }
/** Returns a padding structure {offset,size} for the
* padding, which this frame represents. For regular non-padding
* frames, this returns an empty padding after this frame.
*/
getPadding() {
return {
offset: this.pos + this.size,
size: 0
};
}
/** Replaces the internal data by a new data and changes the
* size respectively.
*
* @param buffer the new buffer to set as the frame's content
*/
setBuffer(buffer) {
this.data = new data_1.Data(buffer);
this.size = buffer.length;
}
/** Writes the header into the provided file
*
* @param file the file to write into
*
* @return a promise, resolves to the number of bytes written into the file
*/
async write(file) {
if (!this.data) {
throw new Error('Cannot write a padding frame');
}
// Set correct write position for frame header
file.seek(this.pos - Frame.HEADER_SIZE, 'start');
// Build frame header
const headerBuffer = Buffer.alloc(Frame.HEADER_SIZE);
headerBuffer.write(this.id, 0, 4, 'ascii');
headerBuffer.writeUInt32BE(this.size, 4);
headerBuffer.writeUInt16BE(this.flags, 8);
// Write frame header
await file.write(headerBuffer);
// Write frame data
return file.write(this.data.toBuffer());
}
/** Returns a frame with the given id, containing the
* passed buffer, but with a valid position. Used for
* insertion of new frames.
*
* @param id the frame id of the allocated frame
* @param buffer the frame content buffer to use
*/
static allocate(id, buffer) {
return new Frame(id, 0, buffer.length, new data_1.Data(buffer));
}
/** Reads a frame from the provided file at the current position and returns the parsed frame.
* Format: [ID(4)] [size(4)] [flags(2)] [data(size)]
* The file position is moved past this frame by this call. This function will return a padding
* frame if the frame starts with a NULL byte.
*
* @param file the file object to read from
* @param mediaStart the offset at which the audio stream starts(=`tagSize` from ID3Tag Header)
*
* @return a promise, which resolves to the parsed frame at the current file position.
*/
static async read(file, mediaStart) {
const buffer = Buffer.alloc(Frame.HEADER_SIZE);
// Read first byte to check for padding
let bytesRead = await file.read(buffer, 0, 1);
if (bytesRead !== 1) {
throw new Error("Can't read initial byte of frame header");
}
if (buffer[0] == 0) { // Encountered NULL-Byte (padding starts here)
file.seek(-1, 'pos');
const offset = file.offset;
const paddingSize = mediaStart - file.offset;
file.seek(mediaStart, 'start'); // Move file pos to where audio starts
// Return padding frame
return new PaddingFrame(offset, paddingSize);
}
else {
// Now that we know, that we don't have padding, we can read in the remaining frame header
bytesRead = await file.read(buffer, 1, buffer.length - 1);
if (bytesRead < buffer.length - 1) {
throw new Error("Can't read ID3 frame header!");
}
const id = buffer.toString('ascii', 0, 4);
const size = buffer.readUInt32BE(4);
const flags = buffer.readUInt16BE(8); //TODO: what about the compression and encryption flags!?
const pos = file.offset;
file.seek(size);
// Read the whole frame into a buffer
const frameBuffer = await file.readSlice(pos, size);
return new Frame(id, pos, size, new data_1.Data(frameBuffer, 0, size), flags);
}
}
}
exports.Frame = Frame;
/** There are no such thing as a padding frame in ID3, but we return this structure as a placeholder
* for the padding area when Frame.read() is called at the start of the padding area.
*/
class PaddingFrame {
offset;
size;
constructor(offset, size) {
this.offset = offset;
this.size = size;
}
/** Used to check whether this frame is a padding frame
* Returns true for all padding frames
*/
isPadding() { return true; }
/** Returns a padding structure {offset,size} for the
* padding, which this frame represents. If this frame
* isn't a padding frame, then an empty padding after this
* frame is returned.
*/
getPadding() {
return this;
}
}
exports.PaddingFrame = PaddingFrame;
//Export class
module.exports = Frame;