/* * MusicTag Copyright (C)2003,2004 * * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser * General Public License as published by the Free Software Foundation; either version 2.1 of the License, * or (at your option) any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this library; if not, * you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.jaudiotagger.tag.id3; import org.jaudiotagger.FileConstants; import org.jaudiotagger.audio.generic.Utils; import org.jaudiotagger.audio.mp3.MP3File; import org.jaudiotagger.logging.Hex; import org.jaudiotagger.tag.EmptyFrameException; import org.jaudiotagger.tag.InvalidDataTypeException; import org.jaudiotagger.tag.InvalidFrameException; import org.jaudiotagger.tag.InvalidFrameIdentifierException; import org.jaudiotagger.tag.id3.framebody.*; import org.jaudiotagger.tag.id3.valuepair.TextEncoding; import org.jaudiotagger.utils.EqualsUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.DataFormatException; import java.util.zip.Inflater; /** * Represents an ID3v2.3 frame. * * @author : Paul Taylor * @author : Eric Farng * @version $Id: ID3v23Frame.java 925 2010-10-21 15:22:41Z paultaylor $ */ public class ID3v23Frame extends AbstractID3v2Frame { private static Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{3}"); protected static final int FRAME_ID_SIZE = 4; protected static final int FRAME_FLAGS_SIZE = 2; protected static final int FRAME_SIZE_SIZE = 4; protected static final int FRAME_COMPRESSION_UNCOMPRESSED_SIZE = 4; protected static final int FRAME_ENCRYPTION_INDICATOR_SIZE = 1; protected static final int FRAME_GROUPING_INDICATOR_SIZE = 1; protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE + FRAME_FLAGS_SIZE; /** * If the frame is encrypted then the encryption method is stored in this byte */ private int encryptionMethod; /** * If the frame belongs in a group with other frames then the group identifier byte is stored */ private int groupIdentifier; protected int getFrameIdSize() { return FRAME_ID_SIZE; } protected int getFrameSizeSize() { return FRAME_SIZE_SIZE; } protected int getFrameHeaderSize() { return FRAME_HEADER_SIZE; } /** * Creates a new ID3v23 Frame */ public ID3v23Frame() { } /** * Creates a new ID3v23 Frame of type identifier. * <p/> * <p>An empty body of the correct type will be automatically created. * This constructor should be used when wish to create a new * frame from scratch using user data. * * @param identifier */ public ID3v23Frame(String identifier) { super(identifier); statusFlags = new StatusFlags(); encodingFlags = new EncodingFlags(); } /** * Copy Constructor * <p/> * Creates a new v23 frame based on another v23 frame * * @param frame */ public ID3v23Frame(ID3v23Frame frame) { super(frame); statusFlags = new StatusFlags(frame.getStatusFlags().getOriginalFlags()); encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags()); } /** * Creates a new ID3v23Frame based on another frame of a different version. * * @param frame * @throws org.jaudiotagger.tag.InvalidFrameException * */ public ID3v23Frame(AbstractID3v2Frame frame) throws InvalidFrameException { //logger.finer("Creating frame from a frame of a different version"); if (frame instanceof ID3v23Frame) { throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument"); } if (frame instanceof ID3v24Frame) { statusFlags = new StatusFlags((ID3v24Frame.StatusFlags) frame.getStatusFlags()); encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags()); } if (frame instanceof ID3v24Frame) { //Unknown Frame e.g NCON, also protects when known id but has unsupported frame body if (frame.getBody() instanceof FrameBodyUnsupported) { this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody()); this.frameBody.setHeader(this); identifier = frame.getIdentifier(); //logger.info("UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); return; } // Deprecated frame for v24 else if (frame.getBody() instanceof FrameBodyDeprecated) { //Was it valid for this tag version, if so try and reconstruct if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier())) { this.frameBody = ((FrameBodyDeprecated) frame.getBody()).getOriginalFrameBody(); this.frameBody.setHeader(this); this.frameBody.setTextEncoding(ID3TextEncodingConversion.getTextEncoding(this, this.frameBody.getTextEncoding())); identifier = frame.getIdentifier(); //logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); } //or was it still deprecated, if so leave as is else { this.frameBody = new FrameBodyDeprecated((FrameBodyDeprecated) frame.getBody()); this.frameBody.setHeader(this); this.frameBody.setTextEncoding(ID3TextEncodingConversion.getTextEncoding(this, this.frameBody.getTextEncoding())); identifier = frame.getIdentifier(); //logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); return; } } else if (ID3Tags.isID3v24FrameIdentifier(frame.getIdentifier())) { //logger.finer("isID3v24FrameIdentifier"); //Version between v4 and v3 identifier = ID3Tags.convertFrameID24To23(frame.getIdentifier()); if (identifier != null) { //logger.finer("V4:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody()); this.frameBody.setHeader(this); this.frameBody.setTextEncoding(ID3TextEncodingConversion.getTextEncoding(this, this.frameBody.getTextEncoding())); return; } else { //Is it a known v4 frame which needs forcing to v3 frame e.g. TDRC - TYER,TDAT identifier = ID3Tags.forceFrameID24To23(frame.getIdentifier()); if (identifier != null) { //logger.finer("V4:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody()); this.frameBody.setHeader(this); this.frameBody.setTextEncoding(ID3TextEncodingConversion.getTextEncoding(this, this.frameBody.getTextEncoding())); return; } //It is a v24 frame that is not known and cannot be forced in v23 e.g TDRL,in which case //we convert to a frameBody unsupported by writing contents as a byte array and feeding //it into FrameBodyUnsupported else { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ((AbstractID3v2FrameBody) frame.getBody()).write(baos); identifier = frame.getIdentifier(); this.frameBody = new FrameBodyUnsupported(identifier, baos.toByteArray()); this.frameBody.setHeader(this); //logger.finer("V4:Orig id is:" + frame.getIdentifier() + ":New Id Unsupported is:" + identifier); return; } } } // Unable to find a suitable frameBody, this should not happen else { logger.severe("Orig id is:" + frame.getIdentifier() + "Unable to create Frame Body"); throw new InvalidFrameException("Orig id is:" + frame.getIdentifier() + "Unable to create Frame Body"); } } else if (frame instanceof ID3v22Frame) { if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier())) { identifier = ID3Tags.convertFrameID22To23(frame.getIdentifier()); if (identifier != null) { //logger.info("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody()); this.frameBody.setHeader(this); return; } //Is it a known v2 frame which needs forcing to v23 frame e.g PIC - APIC else if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier())) { //Force v2 to v3 identifier = ID3Tags.forceFrameID22To23(frame.getIdentifier()); if (identifier != null) { //logger.info("V22Orig id is:" + frame.getIdentifier() + "New id is:" + identifier); this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody()); this.frameBody.setHeader(this); return; } //No mechanism exists to convert it to a v23 frame else { this.frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frame.getBody()); this.frameBody.setHeader(this); identifier = frame.getIdentifier(); //logger.info("Deprecated:V22:orig id id is:" + frame.getIdentifier() + ":New id is:" + identifier); return; } } } // Unknown Frame e.g NCON else { this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody()); this.frameBody.setHeader(this); identifier = frame.getIdentifier(); //logger.info("UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier); return; } } //logger.warning("Frame is unknown version:" + frame.getClass()); } /** * Creates a new ID3v23Frame dataType by reading from byteBuffer. * * @param byteBuffer to read from * @param loggingFilename * @throws org.jaudiotagger.tag.InvalidFrameException * */ public ID3v23Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException, InvalidDataTypeException { setLoggingFilename(loggingFilename); read(byteBuffer); } /** * Creates a new ID3v23Frame dataType by reading from byteBuffer. * * @param byteBuffer to read from * @throws org.jaudiotagger.tag.InvalidFrameException * * @deprecated use {@link #ID3v23Frame(ByteBuffer, String)} instead */ public ID3v23Frame(ByteBuffer byteBuffer) throws InvalidFrameException, InvalidDataTypeException { this(byteBuffer, ""); } /** * Return size of frame * * @return int frame size */ public int getSize() { return frameBody.getSize() + ID3v23Frame.FRAME_HEADER_SIZE; } /** * Compare for equality * To be deemed equal obj must be a IDv23Frame with the same identifier * and the same flags. * containing the same body,dataType list ectera. * equals() method is made up from all the various components * * @param obj * @return if true if this object is equivalent to obj */ public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof ID3v23Frame)) { return false; } ID3v23Frame that = (ID3v23Frame) obj; return EqualsUtil.areEqual(this.statusFlags, that.statusFlags) && EqualsUtil.areEqual(this.encodingFlags, that.encodingFlags) && super.equals(that); } /** * Read the frame from a byteBuffer * * @param byteBuffer buffer to read from */ public void read(ByteBuffer byteBuffer) throws InvalidFrameException, InvalidDataTypeException { String identifier = readIdentifier(byteBuffer); if (!isValidID3v2FrameIdentifier(identifier)) { //logger.info(getLoggingFilename() + ":Invalid identifier:" + identifier); byteBuffer.position(byteBuffer.position() - (getFrameIdSize() - 1)); throw new InvalidFrameIdentifierException(getLoggingFilename() + ":" + identifier + ":is not a valid ID3v2.30 frame"); } //Read the size field (as Big Endian Int - byte buffers always initialised to Big Endian order) frameSize = byteBuffer.getInt(); if (frameSize < 0) { //logger.warning(getLoggingFilename() + ":Invalid Frame Size:" + identifier); throw new InvalidFrameException(identifier + " is invalid frame"); } else if (frameSize == 0) { //logger.warning(getLoggingFilename() + ":Empty Frame Size:" + identifier); //We don't process this frame or add to frameMap because contains no useful information //Skip the two flag bytes so in correct position for subsequent frames byteBuffer.get(); byteBuffer.get(); throw new EmptyFrameException(identifier + " is empty frame"); } else if (frameSize > byteBuffer.remaining()) { //logger.warning(getLoggingFilename() + ":Invalid Frame size of " + frameSize + " larger than size of" + byteBuffer.remaining() + " before mp3 audio:" + identifier); throw new InvalidFrameException(identifier + " is invalid frame"); } //Read the flag bytes statusFlags = new StatusFlags(byteBuffer.get()); encodingFlags = new EncodingFlags(byteBuffer.get()); String id; //If this identifier is a valid v24 identifier or easily converted to v24 id = ID3Tags.convertFrameID23To24(identifier); // Cant easily be converted to v24 but is it a valid v23 identifier if (id == null) { // It is a valid v23 identifier so should be able to find a // frame body for it. if (ID3Tags.isID3v23FrameIdentifier(identifier)) { id = identifier; } // Unknown so will be created as FrameBodyUnsupported else { id = UNSUPPORTED_ID; } } //logger.fine(getLoggingFilename() + ":Identifier was:" + identifier + " reading using:" + id + "with frame size:" + frameSize); //Read extra bits appended to frame header for various encodings //These are not included in header size but are included in frame size but won't be read when we actually //try to read the frame body data int extraHeaderBytesCount = 0; int decompressedFrameSize = -1; if (((EncodingFlags) encodingFlags).isCompression()) { //Read the Decompressed Size decompressedFrameSize = byteBuffer.getInt(); extraHeaderBytesCount = FRAME_COMPRESSION_UNCOMPRESSED_SIZE; //logger.fine(getLoggingFilename() + ":Decompressed frame size is:" + decompressedFrameSize); } if (((EncodingFlags) encodingFlags).isEncryption()) { //Consume the encryption byte extraHeaderBytesCount += FRAME_ENCRYPTION_INDICATOR_SIZE; encryptionMethod = byteBuffer.get(); } if (((EncodingFlags) encodingFlags).isGrouping()) { //Read the Grouping byte, but do nothing with it extraHeaderBytesCount += FRAME_GROUPING_INDICATOR_SIZE; groupIdentifier = byteBuffer.get(); } //Work out the real size of the frameBody data int realFrameSize = frameSize - extraHeaderBytesCount; ByteBuffer frameBodyBuffer; //Read the body data try { if (((EncodingFlags) encodingFlags).isCompression()) { frameBodyBuffer = ID3Compression.uncompress(identifier, getLoggingFilename(), byteBuffer, decompressedFrameSize, realFrameSize); frameBody = readBody(id, frameBodyBuffer, decompressedFrameSize); } else if (((EncodingFlags) encodingFlags).isEncryption()) { frameBodyBuffer = byteBuffer.slice(); frameBodyBuffer.limit(realFrameSize); frameBody = readEncryptedBody(identifier, byteBuffer, frameSize); } else { //Create Buffer that only contains the body of this frame rather than the remainder of tag frameBodyBuffer = byteBuffer.slice(); frameBodyBuffer.limit(realFrameSize); frameBody = readBody(id, frameBodyBuffer, realFrameSize); } //TODO code seems to assume that if the frame created is not a v23FrameBody //it should be deprecated, but what about if somehow a V24Frame has been put into a V23 Tag, shouldn't //it then be created as FrameBodyUnsupported if (!(frameBody instanceof ID3v23FrameBody)) { //logger.info(getLoggingFilename() + ":Converted frameBody with:" + identifier + " to deprecated frameBody"); frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frameBody); } } finally { //Update position of main buffer, so no attempt is made to reread these bytes byteBuffer.position(byteBuffer.position() + realFrameSize); } } /** * Write the frame to bufferOutputStream * * @throws IOException */ public void write(ByteArrayOutputStream tagBuffer) { //logger.info("Writing frame to buffer:" + getIdentifier()); //This is where we will write header, move position to where we can //write body ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE); //Write Frame Body Data ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream(); ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream); //Write Frame Header write Frame ID if (getIdentifier().length() == 3) { identifier = identifier + ' '; } headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE); //Write Frame Size int size = frameBody.getSize(); //logger.fine("Frame Size Is:" + size); headerBuffer.putInt(frameBody.getSize()); //Write the Flags //Status Flags:leave as they were when we read headerBuffer.put(statusFlags.getWriteFlags()); //Remove any non standard flags ((EncodingFlags) encodingFlags).unsetNonStandardFlags(); //Unset Compression flag if previously set because we uncompress previously compressed frames on write. ((EncodingFlags) encodingFlags).unsetCompression(); headerBuffer.put(encodingFlags.getFlags()); try { //Add header to the Byte Array Output Stream tagBuffer.write(headerBuffer.array()); if (((EncodingFlags) encodingFlags).isEncryption()) { tagBuffer.write(encryptionMethod); } if (((EncodingFlags) encodingFlags).isGrouping()) { tagBuffer.write(groupIdentifier); } //Add body to the Byte Array Output Stream tagBuffer.write(bodyOutputStream.toByteArray()); } catch (IOException ioe) { //This could never happen coz not writing to file, so convert to RuntimeException throw new RuntimeException(ioe); } } public AbstractID3v2Frame.StatusFlags getStatusFlags() { return statusFlags; } public AbstractID3v2Frame.EncodingFlags getEncodingFlags() { return encodingFlags; } public int getEncryptionMethod() { return encryptionMethod; } public int getGroupIdentifier() { return groupIdentifier; } /** * This represents a frame headers Status Flags * Make adjustments if necessary based on frame type and specification. */ class StatusFlags extends AbstractID3v2Frame.StatusFlags { public static final String TYPE_TAGALTERPRESERVATION = "typeTagAlterPreservation"; public static final String TYPE_FILEALTERPRESERVATION = "typeFileAlterPreservation"; public static final String TYPE_READONLY = "typeReadOnly"; /** * Discard frame if tag altered */ public static final int MASK_TAG_ALTER_PRESERVATION = FileConstants.BIT7; /** * Discard frame if audio file part altered */ public static final int MASK_FILE_ALTER_PRESERVATION = FileConstants.BIT6; /** * Frame tagged as read only */ public static final int MASK_READ_ONLY = FileConstants.BIT5; public StatusFlags() { originalFlags = (byte) 0; writeFlags = (byte) 0; } StatusFlags(byte flags) { originalFlags = flags; writeFlags = flags; modifyFlags(); } /** * Use this constructor when convert a v24 frame * * @param statusFlags */ StatusFlags(ID3v24Frame.StatusFlags statusFlags) { originalFlags = convertV4ToV3Flags(statusFlags.getOriginalFlags()); writeFlags = originalFlags; modifyFlags(); } private byte convertV4ToV3Flags(byte v4Flag) { byte v3Flag = (byte) 0; if ((v4Flag & ID3v24Frame.StatusFlags.MASK_FILE_ALTER_PRESERVATION) != 0) { v3Flag |= (byte) MASK_FILE_ALTER_PRESERVATION; } if ((v4Flag & ID3v24Frame.StatusFlags.MASK_TAG_ALTER_PRESERVATION) != 0) { v3Flag |= (byte) MASK_TAG_ALTER_PRESERVATION; } return v3Flag; } protected void modifyFlags() { String str = getIdentifier(); if (ID3v23Frames.getInstanceOf().isDiscardIfFileAltered(str)) { writeFlags |= (byte) MASK_FILE_ALTER_PRESERVATION; writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION; } else { writeFlags &= (byte) ~MASK_FILE_ALTER_PRESERVATION; writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION; } } public void createStructure() { MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, ""); MP3File.getStructureFormatter().addElement(TYPE_TAGALTERPRESERVATION, originalFlags & MASK_TAG_ALTER_PRESERVATION); MP3File.getStructureFormatter().addElement(TYPE_FILEALTERPRESERVATION, originalFlags & MASK_FILE_ALTER_PRESERVATION); MP3File.getStructureFormatter().addElement(TYPE_READONLY, originalFlags & MASK_READ_ONLY); MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS); } } /** * This represents a frame headers Encoding Flags */ class EncodingFlags extends AbstractID3v2Frame.EncodingFlags { public static final String TYPE_COMPRESSION = "compression"; public static final String TYPE_ENCRYPTION = "encryption"; public static final String TYPE_GROUPIDENTITY = "groupidentity"; /** * Frame is compressed */ public static final int MASK_COMPRESSION = FileConstants.BIT7; /** * Frame is encrypted */ public static final int MASK_ENCRYPTION = FileConstants.BIT6; /** * Frame is part of a group */ public static final int MASK_GROUPING_IDENTITY = FileConstants.BIT5; public EncodingFlags() { super(); } public EncodingFlags(byte flags) { super(flags); logEnabledFlags(); } public void setCompression() { flags |= MASK_COMPRESSION; } public void setEncryption() { flags |= MASK_ENCRYPTION; } public void setGrouping() { flags |= MASK_GROUPING_IDENTITY; } public void unsetCompression() { flags &= (byte) ~MASK_COMPRESSION; } public void unsetEncryption() { flags &= (byte) ~MASK_ENCRYPTION; } public void unsetGrouping() { flags &= (byte) ~MASK_GROUPING_IDENTITY; } public boolean isNonStandardFlags() { return ((flags & FileConstants.BIT4) > 0) || ((flags & FileConstants.BIT3) > 0) || ((flags & FileConstants.BIT2) > 0) || ((flags & FileConstants.BIT1) > 0) || ((flags & FileConstants.BIT0) > 0); } public void unsetNonStandardFlags() { if (isNonStandardFlags()) { //logger.warning(getLoggingFilename() + ":" + getIdentifier() + ":Unsetting Unknown Encoding Flags:" + Hex.asHex(flags)); flags &= (byte) ~FileConstants.BIT4; flags &= (byte) ~FileConstants.BIT3; flags &= (byte) ~FileConstants.BIT2; flags &= (byte) ~FileConstants.BIT1; flags &= (byte) ~FileConstants.BIT0; } } public void logEnabledFlags() { if (isNonStandardFlags()) { //logger.warning(getLoggingFilename() + ":" + identifier + ":Unknown Encoding Flags:" + Hex.asHex(flags)); } if (isCompression()) { //logger.warning(getLoggingFilename() + ":" + identifier + " is compressed"); } if (isEncryption()) { //logger.warning(getLoggingFilename() + ":" + identifier + " is encrypted"); } if (isGrouping()) { //logger.warning(getLoggingFilename() + ":" + identifier + " is grouped"); } } public boolean isCompression() { return (flags & MASK_COMPRESSION) > 0; } public boolean isEncryption() { return (flags & MASK_ENCRYPTION) > 0; } public boolean isGrouping() { return (flags & MASK_GROUPING_IDENTITY) > 0; } public void createStructure() { MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, ""); MP3File.getStructureFormatter().addElement(TYPE_COMPRESSION, flags & MASK_COMPRESSION); MP3File.getStructureFormatter().addElement(TYPE_ENCRYPTION, flags & MASK_ENCRYPTION); MP3File.getStructureFormatter().addElement(TYPE_GROUPIDENTITY, flags & MASK_GROUPING_IDENTITY); MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS); } } /** * Does the frame identifier meet the syntax for a idv3v2 frame identifier. * must start with a capital letter and only contain capital letters and numbers * * @param identifier to be checked * @return whether the identifier is valid */ public boolean isValidID3v2FrameIdentifier(String identifier) { Matcher m = ID3v23Frame.validFrameIdentifier.matcher(identifier); return m.matches(); } /** * Return String Representation of body */ public void createStructure() { MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier()); MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize); statusFlags.createStructure(); encodingFlags.createStructure(); frameBody.createStructure(); MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME); } /** * @return true if considered a common frame */ public boolean isCommon() { return ID3v23Frames.getInstanceOf().isCommon(getId()); } /** * @return true if considered a common frame */ public boolean isBinary() { return ID3v23Frames.getInstanceOf().isBinary(getId()); } /** * Sets the charset encoding used by the field. * * @param encoding charset. */ public void setEncoding(String encoding) { Integer encodingId = TextEncoding.getInstanceOf().getIdForValue(encoding); if (encoding != null) { if (encodingId < 2) { this.getBody().setTextEncoding(encodingId.byteValue()); } } } }