package com.frostwire.mp3; import java.util.Arrays; public class ID3v2Frame { private static final int HEADER_LENGTH = 10; private static final int ID_OFFSET = 0; private static final int ID_LENGTH = 4; protected static final int DATA_LENGTH_OFFSET = 4; private static final int FLAGS1_OFFSET = 8; private static final int FLAGS2_OFFSET = 9; private static final int PRESERVE_TAG_BIT = 6; private static final int PRESERVE_FILE_BIT = 5; private static final int READ_ONLY_BIT = 4; private static final int GROUP_BIT = 6; private static final int COMPRESSION_BIT = 3; private static final int ENCRYPTION_BIT = 2; private static final int UNSYNCHRONISATION_BIT = 1; private static final int DATA_LENGTH_INDICATOR_BIT = 0; protected String id; protected int dataLength = 0; protected byte[] data = null; private boolean preserveTag = false; private boolean preserveFile = false; private boolean readOnly = false; private boolean group = false; private boolean compression = false; private boolean encryption = false; private boolean unsynchronisation = false; private boolean dataLengthIndicator = false; public ID3v2Frame(byte[] buffer, int offset) throws InvalidDataException { unpackFrame(buffer, offset); } public ID3v2Frame(String id, byte[] data) { this.id = id; this.data = data; dataLength = data.length; } protected void unpackFrame(byte[] buffer, int offset) throws InvalidDataException { int dataOffset = unpackHeader(buffer, offset); sanityCheckUnpackedHeader(); data = BufferTools.copyBuffer(buffer, dataOffset, dataLength); } protected int unpackHeader(byte[] buffer, int offset) { id = BufferTools.byteBufferToString(buffer, offset + ID_OFFSET, ID_LENGTH); unpackDataLength(buffer, offset); unpackFlags(buffer, offset); return offset + HEADER_LENGTH; } protected void unpackDataLength(byte[] buffer, int offset) { dataLength = BufferTools.unpackInteger(buffer[offset + DATA_LENGTH_OFFSET], buffer[offset + DATA_LENGTH_OFFSET + 1], buffer[offset + DATA_LENGTH_OFFSET + 2], buffer[offset + DATA_LENGTH_OFFSET + 3]); } private void unpackFlags(byte[] buffer, int offset) { preserveTag = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], PRESERVE_TAG_BIT); preserveFile = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], PRESERVE_FILE_BIT); readOnly = BufferTools.checkBit(buffer[offset + FLAGS1_OFFSET], READ_ONLY_BIT); group = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], GROUP_BIT); compression = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], COMPRESSION_BIT); encryption = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], ENCRYPTION_BIT); unsynchronisation = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], UNSYNCHRONISATION_BIT); dataLengthIndicator = BufferTools.checkBit(buffer[offset + FLAGS2_OFFSET], DATA_LENGTH_INDICATOR_BIT); } protected void sanityCheckUnpackedHeader() throws InvalidDataException { for (int i = 0; i < id.length(); i++) { if (! ((id.charAt(i) >= 'A' && id.charAt(i) <= 'Z') || (id.charAt(i) >= '0' && id.charAt(i) <= '9'))) { throw new InvalidDataException("Not a valid frame - invalid tag " + id); } } } public byte[] toBytes() throws NotSupportedException { byte[] bytes = new byte[getLength()]; packFrame(bytes, 0); return bytes; } public void toBytes(byte[] bytes, int offset) throws NotSupportedException { packFrame(bytes, offset); } public void packFrame(byte[] bytes, int offset) throws NotSupportedException { packHeader(bytes, offset); BufferTools.copyIntoByteBuffer(data, 0, data.length, bytes, offset + HEADER_LENGTH); } private void packHeader(byte[] bytes, int i) { BufferTools.stringIntoByteBuffer(id, 0, id.length(), bytes, 0); BufferTools.copyIntoByteBuffer(packDataLength(), 0, 4, bytes, 4); BufferTools.copyIntoByteBuffer(packFlags(), 0, 2, bytes, 8); } protected byte[] packDataLength() { return BufferTools.packInteger(dataLength); } private byte[] packFlags() { byte[] bytes = new byte[2]; bytes[0] = BufferTools.setBit(bytes[0], PRESERVE_TAG_BIT, preserveTag); bytes[0] = BufferTools.setBit(bytes[0], PRESERVE_FILE_BIT, preserveFile); bytes[0] = BufferTools.setBit(bytes[0], READ_ONLY_BIT, readOnly); bytes[1] = BufferTools.setBit(bytes[1], GROUP_BIT, group); bytes[1] = BufferTools.setBit(bytes[1], COMPRESSION_BIT, compression); bytes[1] = BufferTools.setBit(bytes[1], ENCRYPTION_BIT, encryption); bytes[1] = BufferTools.setBit(bytes[1], UNSYNCHRONISATION_BIT, unsynchronisation); bytes[1] = BufferTools.setBit(bytes[1], DATA_LENGTH_INDICATOR_BIT, dataLengthIndicator); return bytes; } public String getId() { return id; } public int getDataLength() { return dataLength; } public int getLength() { return dataLength + HEADER_LENGTH; } public byte[] getData() { return data; } public void setData(byte[] data) { this.data = data; if (data == null) dataLength = 0; else dataLength = data.length; } public boolean hasDataLengthIndicator() { return dataLengthIndicator; } public boolean hasCompression() { return compression; } public boolean hasEncryption() { return encryption; } public boolean hasGroup() { return group; } public boolean hasPreserveFile() { return preserveFile; } public boolean hasPreserveTag() { return preserveTag; } public boolean isReadOnly() { return readOnly; } public boolean hasUnsynchronisation() { return unsynchronisation; } public boolean equals(Object obj) { if (! (obj instanceof ID3v2Frame)) return false; if (super.equals(obj)) return true; ID3v2Frame other = (ID3v2Frame) obj; if (dataLength != other.dataLength) return false; if (preserveTag != other.preserveTag) return false; if (preserveFile != other.preserveFile) return false; if (readOnly != other.readOnly) return false; if (group != other.group) return false; if (compression != other.compression) return false; if (encryption != other.encryption) return false; if (unsynchronisation != other.encryption) return false; if (dataLengthIndicator != other.dataLengthIndicator) return false; if (id == null) { if (other.id != null) return false; } else if (other.id == null) return false; else if (! id.equals(other.id)) return false; if (data == null) { if (other.data != null) return false; } else if (other.data == null) return false; else if (! Arrays.equals(data, other.data)) return false; return true; } }