package org.farng.mp3.id3; import java.io.IOException; import java.io.RandomAccessFile; import org.farng.mp3.InvalidTagException; import org.farng.mp3.TagConstant; import org.farng.mp3.TagUtility; /** * <p class=t> The extended header contains information that is not vital to the correct parsing of the tag information, * hence the extended header is optional. </p> * <p/> * <p><center> <table border=0> <tr><td nowrap> Extended header size</td><td rowspan=3> </td><td width=100%>$xx xx * xx xx</td></tr> <tr><td>Extended Flags</td><td>$xx xx</td></tr> <tr><td>Size of padding</td><td>$xx xx xx xx</tr> * </table> </center> * <p/> * <p class=t> Where the 'Extended header size', currently 6 or 10 bytes, excludes itself. The 'Size of padding' is * simply the total tag size excluding the frames and the headers, in other words the padding. The extended header is * considered separate from the header proper, and as such is subject to unsynchronisation. </p> * <p/> * <p class=t> The extended flags are a secondary flag set which describes further attributes of the tag. These * attributes are currently defined as follows </p> * <p/> * <p class=ind> %x0000000 00000000 </p> * <p/> * <p class=t> x - CRC data present </p> * <p/> * <p class=ind> If this flag is set four bytes of CRC-32 data is appended to the extended header. The CRC should be * calculated before unsynchronisation on the data between the extended header and the padding, i.e. the frames and only * the frames. </p> * <p/> * <p><center> <table border=0> <tr><td nowrap> Total frame CRC</td><td> </td><td>$xx xx xx xx</td></tr> </table> * </center> * * @author Eric Farng * @version $Revision: 2374 $ */ public class ID3v2_3Frame extends ID3v2_2Frame { protected boolean compression = false; protected boolean encryption = false; protected boolean fileAlterPreservation = false; protected boolean groupingIdentity = false; protected boolean readOnly = false; // @todo implement this read only! // these are flags for each frame them selves protected boolean tagAlterPreservation = false; /** * Creates a new ID3v2_3Frame object. */ public ID3v2_3Frame() { setAlterPreservation(); } /** * Creates a new ID3v2_3Frame object. */ public ID3v2_3Frame(final AbstractID3v2FrameBody body) { super(body); setAlterPreservation(); } /** * Creates a new ID3v2_3Frame object. */ public ID3v2_3Frame(final ID3v2_3Frame copyObject) { super(copyObject); this.compression = copyObject.compression; this.encryption = copyObject.encryption; this.fileAlterPreservation = copyObject.fileAlterPreservation; this.groupingIdentity = copyObject.groupingIdentity; this.readOnly = copyObject.readOnly; this.tagAlterPreservation = copyObject.tagAlterPreservation; } /** * Creates a new ID3v2_3Frame object. */ public ID3v2_3Frame(final boolean readOnly, final boolean groupingIdentity, final boolean compression, final boolean encryption, final AbstractID3v2FrameBody body) { super(body); this.readOnly = readOnly; this.groupingIdentity = groupingIdentity; this.compression = compression; this.encryption = encryption; setAlterPreservation(); } /** * Creates a new ID3v2_4Frame object. */ public ID3v2_3Frame(final AbstractID3v2Frame frame) { if (frame instanceof ID3v2_3Frame) { final ID3v2_3Frame f = (ID3v2_3Frame) frame; this.tagAlterPreservation = f.tagAlterPreservation; this.fileAlterPreservation = f.fileAlterPreservation; this.readOnly = f.readOnly; this.groupingIdentity = f.groupingIdentity; this.compression = f.compression; this.encryption = f.encryption; } if (frame instanceof ID3v2_2Frame) { // no variables yet } if (frame.getBody() == null) { // do nothing } else if (TagUtility.isID3v2_3FrameIdentifier(frame.getIdentifier())) { this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); // } else if (TagUtility.isID3v2_4FrameIdentifier(frame.getIdentifier())) { // // @TODO correctly convert tags // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); // } else if (TagUtility.isID3v2_2FrameIdentifier(frame.getIdentifier())) { // // @TODO correctly convert tags // this.setBody((AbstractID3v2FrameBody) TagUtility.copyObject(frame.getBody())); } } /** * Creates a new ID3v2_3Frame object. */ public ID3v2_3Frame(final RandomAccessFile file) throws IOException, InvalidTagException { this.read(file); } public int getSize() { return this.getBody().getSize() + 4 + 2 + 4; } public boolean equals(final Object obj) { if ((obj instanceof ID3v2_3Frame) == false) { return false; } final ID3v2_3Frame id3v2_3Frame = (ID3v2_3Frame) obj; if (this.compression != id3v2_3Frame.compression) { return false; } if (this.encryption != id3v2_3Frame.encryption) { return false; } if (this.fileAlterPreservation != id3v2_3Frame.fileAlterPreservation) { return false; } if (this.groupingIdentity != id3v2_3Frame.groupingIdentity) { return false; } if (this.readOnly != id3v2_3Frame.readOnly) { return false; } if (this.tagAlterPreservation != id3v2_3Frame.tagAlterPreservation) { return false; } return super.equals(obj); } public void read(final RandomAccessFile file) throws IOException, InvalidTagException { byte b; long filePointer; final byte[] buffer = new byte[4]; // lets scan for a non-zero byte; do { filePointer = file.getFilePointer(); b = file.readByte(); org.farng.mp3.id3.AbstractID3v2.incrementPaddingCounter(); } while (b == 0); file.seek(filePointer); org.farng.mp3.id3.AbstractID3v2.decrementPaddingCounter(); // read the four character identifier file.read(buffer, 0, 4); final String identifier = new String(buffer, 0, 4); // is this a valid identifier? if (isValidID3v2FrameIdentifier(identifier) == false) { file.seek(file.getFilePointer() - 3); throw new InvalidTagException(identifier + " is not a valid ID3v2.30 frame"); } filePointer = file.getFilePointer(); // skip the 4 byte size file.skipBytes(4); // read the flag bytes file.read(buffer, 0, 2); this.tagAlterPreservation = (buffer[0] & TagConstant.MASK_V23_TAG_ALTER_PRESERVATION) != 0; this.fileAlterPreservation = (buffer[0] & TagConstant.MASK_V23_FILE_ALTER_PRESERVATION) != 0; this.readOnly = (buffer[0] & TagConstant.MASK_V23_READ_ONLY) != 0; this.compression = (buffer[1] & TagConstant.MASK_V23_COMPRESSION) != 0; this.encryption = (buffer[1] & TagConstant.MASK_V23_ENCRYPTION) != 0; this.groupingIdentity = (buffer[1] & TagConstant.MASK_V23_GROUPING_IDENTITY) != 0; file.seek(filePointer); this.setBody(readBody(identifier, file)); } public void write(final RandomAccessFile file) throws IOException { final long filePointer; final byte[] buffer = new byte[4]; final String str = TagUtility.truncate(getIdentifier(), 4); for (int i = 0; i < str.length(); i++) { buffer[i] = (byte) str.charAt(i); } file.write(buffer, 0, str.length()); filePointer = file.getFilePointer(); // skip the size bytes file.skipBytes(4); setAlterPreservation(); buffer[0] = 0; buffer[1] = 0; if (this.tagAlterPreservation) { buffer[0] |= TagConstant.MASK_V23_TAG_ALTER_PRESERVATION; } if (this.fileAlterPreservation) { buffer[0] |= TagConstant.MASK_V23_FILE_ALTER_PRESERVATION; } if (this.readOnly) { buffer[0] |= TagConstant.MASK_V23_READ_ONLY; } if (this.compression) { buffer[1] |= TagConstant.MASK_V23_COMPRESSION; } if (this.encryption) { buffer[1] |= TagConstant.MASK_V23_ENCRYPTION; } if (this.groupingIdentity) { buffer[1] |= TagConstant.MASK_V23_GROUPING_IDENTITY; } file.write(buffer, 0, 2); file.seek(filePointer); this.getBody().write(file); } protected void setAlterPreservation() { final String str = getIdentifier(); if (str.equals("ETCO") || str.equals("EQUA") || str.equals("MLLT") || str.equals("POSS") || str.equals("SYLT") || str.equals("SYTC") || str.equals("RVAD") || str.equals("TENC") || str.equals("TLEN") || str.equals("TSIZ")) { this.tagAlterPreservation = false; this.fileAlterPreservation = true; } else { this.tagAlterPreservation = false; this.fileAlterPreservation = true; } } }