ID3v24Framepublic class ID3v24Frame extends AbstractID3v2Frame Represents an ID3v2.4 frame. |
Fields Summary |
---|
private static Pattern | validFrameIdentifier | protected static final int | FRAME_DATA_LENGTH_SIZE | protected static final int | FRAME_ID_SIZE | protected static final int | FRAME_FLAGS_SIZE | protected static final int | FRAME_SIZE_SIZE | protected static final int | FRAME_ENCRYPTION_INDICATOR_SIZE | protected static final int | FRAME_GROUPING_INDICATOR_SIZE | protected static final int | FRAME_HEADER_SIZE | private int | encryptionMethodIf the frame is encrypted then the encryption method is stored in this byte | private int | groupIdentifierIf the frame belongs in a group with other frames then the group identifier byte is stored |
Constructors Summary |
---|
public ID3v24Frame(Lyrics3v2Field field)Creates a new ID3v2_4Frame datatype based on Lyrics3.
String id = field.getIdentifier();
String value;
if (id.equals("IND"))
{
throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 indications field.");
}
else if (id.equals("LYR"))
{
FieldFrameBodyLYR lyric = (FieldFrameBodyLYR) field.getBody();
Lyrics3Line line;
Iterator<Lyrics3Line> iterator = lyric.iterator();
FrameBodySYLT sync;
FrameBodyUSLT unsync;
boolean hasTimeStamp = lyric.hasTimeStamp();
// we'll create only one frame here.
// if there is any timestamp at all, we will create a sync'ed frame.
sync = new FrameBodySYLT((byte) 0, "ENG", (byte) 2, (byte) 1, "", new byte[0]);
unsync = new FrameBodyUSLT((byte) 0, "ENG", "", "");
while (iterator.hasNext())
{
line = iterator.next();
if (hasTimeStamp)
{
// sync.addLyric(line);
}
else
{
unsync.addLyric(line);
}
}
if (hasTimeStamp)
{
this.frameBody = sync;
this.frameBody.setHeader(this);
}
else
{
this.frameBody = unsync;
this.frameBody.setHeader(this);
}
}
else if (id.equals("INF"))
{
value = ((FieldFrameBodyINF) field.getBody()).getAdditionalInformation();
this.frameBody = new FrameBodyCOMM((byte) 0, "ENG", "", value);
this.frameBody.setHeader(this);
}
else if (id.equals("AUT"))
{
value = ((FieldFrameBodyAUT) field.getBody()).getAuthor();
this.frameBody = new FrameBodyTCOM((byte) 0, value);
this.frameBody.setHeader(this);
}
else if (id.equals("EAL"))
{
value = ((FieldFrameBodyEAL) field.getBody()).getAlbum();
this.frameBody = new FrameBodyTALB((byte) 0, value);
this.frameBody.setHeader(this);
}
else if (id.equals("EAR"))
{
value = ((FieldFrameBodyEAR) field.getBody()).getArtist();
this.frameBody = new FrameBodyTPE1((byte) 0, value);
this.frameBody.setHeader(this);
}
else if (id.equals("ETT"))
{
value = ((FieldFrameBodyETT) field.getBody()).getTitle();
this.frameBody = new FrameBodyTIT2((byte) 0, value);
this.frameBody.setHeader(this);
}
else if (id.equals("IMG"))
{
throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 image field.");
}
else
{
throw new InvalidTagException("Cannot caret ID3v2.40 frame from " + id + " Lyrics3 field");
}
| public ID3v24Frame(ByteBuffer byteBuffer, String loggingFilename)Creates a new ID3v24Frame datatype by reading from byteBuffer.
setLoggingFilename(loggingFilename);
read(byteBuffer);
| public ID3v24Frame(ByteBuffer byteBuffer)Creates a new ID3v24Frame datatype by reading from byteBuffer.
this(byteBuffer, "");
| public ID3v24Frame()
| public ID3v24Frame(String identifier)Creates a new ID3v2_4Frame of type identifier. 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 input
//Super Constructor creates a frame with empty body of type specified
super(identifier);
statusFlags = new StatusFlags();
encodingFlags = new EncodingFlags();
| public ID3v24Frame(ID3v24Frame frame)Copy Constructor:Creates a new ID3v2_4Frame datatype based on another frame.
super(frame);
statusFlags = new StatusFlags(frame.getStatusFlags().getOriginalFlags());
encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
| public ID3v24Frame(AbstractID3v2Frame frame)Creates a new ID3v2_4Frame datatype based on another frame of different version
Converts the framebody to the equivalent v24 framebody or to UnsupportedFrameBody if identifier
is unknown.
//Should not be called
if ((frame instanceof ID3v24Frame))
{
throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
}
//Flags
if (frame instanceof ID3v23Frame)
{
statusFlags = new StatusFlags((ID3v23Frame.StatusFlags) frame.getStatusFlags());
encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
}
else
{
statusFlags = new StatusFlags();
encodingFlags = new EncodingFlags();
}
// Convert Identifier. If the id was a known id for the original
// version we should be able to convert it to an v24 frame, although it may mean minor
// modification to the data. If it was not recognised originally it should remain
// unknown.
if (frame instanceof ID3v23Frame)
{
createV24FrameFromV23Frame((ID3v23Frame) frame);
}
else if (frame instanceof ID3v22Frame)
{
ID3v23Frame v23Frame = new ID3v23Frame(frame);
createV24FrameFromV23Frame(v23Frame);
}
this.frameBody.setHeader(this);
|
Methods Summary |
---|
private void | checkIfFrameSizeThatIsNotSyncSafe(java.nio.ByteBuffer byteBuffer)If frame is greater than certain size it will be decoded differently if unsynchronized to if synchronized
Frames with certain byte sequences should be unsynchronized but sometimes editors do not
unsynchronize them so this method checks both cases and goes with the option that fits best with the data
if (frameSize > ID3SyncSafeInteger.MAX_SAFE_SIZE)
{
//Set Just after size field this is where we want to be when we leave this if statement
int currentPosition = byteBuffer.position();
//Read as nonsync safe integer
byteBuffer.position(currentPosition - getFrameIdSize());
int nonSyncSafeFrameSize = byteBuffer.getInt();
//Is the frame size syncsafe, should always be BUT some encoders such as Itunes do not do it properly
//so do an easy check now.
byteBuffer.position(currentPosition - getFrameIdSize());
boolean isNotSyncSafe = ID3SyncSafeInteger.isBufferNotSyncSafe(byteBuffer);
//not relative so need to move position
byteBuffer.position(currentPosition);
if (isNotSyncSafe)
{
logger.warning(getLoggingFilename() + ":" + "Frame size is NOT stored as a sync safe integer:" + identifier);
//This will return a larger frame size so need to check against buffer size if too large then we are
//buggered , give up
if (nonSyncSafeFrameSize > (byteBuffer.remaining() - -getFrameFlagsSize()))
{
logger.warning(getLoggingFilename() + ":" + "Invalid Frame size larger than size before mp3 audio:" + identifier);
throw new InvalidFrameException(identifier + " is invalid frame");
}
else
{
frameSize = nonSyncSafeFrameSize;
}
}
else
{
//appears to be sync safe but lets look at the bytes just after the reported end of this
//frame to see if find a valid frame header
//Read the Frame Identifier
byte[] readAheadbuffer = new byte[getFrameIdSize()];
byteBuffer.position(currentPosition + frameSize + getFrameFlagsSize());
if (byteBuffer.remaining() < getFrameIdSize())
{
//There is no padding or framedata we are at end so assume syncsafe
//reset position to just after framesize
byteBuffer.position(currentPosition);
}
else
{
byteBuffer.get(readAheadbuffer, 0, getFrameIdSize());
//reset position to just after framesize
byteBuffer.position(currentPosition);
String readAheadIdentifier = new String(readAheadbuffer);
if (isValidID3v2FrameIdentifier(readAheadIdentifier))
{
//Everything ok, so continue
}
else if (ID3SyncSafeInteger.isBufferEmpty(readAheadbuffer))
{
//no data found so assume entered padding in which case assume it is last
//frame and we are ok
}
//haven't found identifier so maybe not syncsafe or maybe there are no more frames, just padding
else
{
//Ok lets try using a non-syncsafe integer
//size returned will be larger so is it valid
if (nonSyncSafeFrameSize > byteBuffer.remaining() - getFrameFlagsSize())
{
//invalid so assume syncsafe
byteBuffer.position(currentPosition);
}
else
{
readAheadbuffer = new byte[getFrameIdSize()];
byteBuffer.position(currentPosition + nonSyncSafeFrameSize + getFrameFlagsSize());
if (byteBuffer.remaining() >= getFrameIdSize())
{
byteBuffer.get(readAheadbuffer, 0, getFrameIdSize());
readAheadIdentifier = new String(readAheadbuffer);
//reset position to just after framesize
byteBuffer.position(currentPosition);
//ok found a valid identifier using non-syncsafe so assume non-syncsafe size
//and continue
if (isValidID3v2FrameIdentifier(readAheadIdentifier))
{
frameSize = nonSyncSafeFrameSize;
logger.warning(getLoggingFilename() + ":" + "Assuming frame size is NOT stored as a sync safe integer:" + identifier);
}
//no data found so assume entered padding in which case assume it is last
//frame and we are ok whereas we didn't hit padding when using syncsafe integer
//or we wouldn't have got to this point. So assume syncsafe integer ended within
//the frame data whereas this has reached end of frames.
else if (ID3SyncSafeInteger.isBufferEmpty(readAheadbuffer))
{
frameSize = nonSyncSafeFrameSize;
logger.warning(getLoggingFilename() + ":" + "Assuming frame size is NOT stored as a sync safe integer:" + identifier);
}
//invalid so assume syncsafe as that is is the standard
else
{
}
}
else
{
//reset position to just after framesize
byteBuffer.position(currentPosition);
//If the unsync framesize matches exactly the remaining bytes then assume it has the
//correct size for the last frame
if (byteBuffer.remaining() == 0)
{
frameSize = nonSyncSafeFrameSize;
}
//Inconclusive stick with syncsafe
else
{
}
}
}
}
}
}
}
| public void | createStructure()Return String Representation of body
MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
statusFlags.createStructure();
encodingFlags.createStructure();
frameBody.createStructure();
MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
| private void | createV24FrameFromV23Frame(ID3v23Frame frame)
// Is it a straight conversion e.g TALB - TALB
identifier = ID3Tags.convertFrameID23To24(frame.getIdentifier());
logger.finer("Creating V24frame from v23:" + frame.getIdentifier() + ":" + identifier);
//We cant convert unsupported bodies properly
if (frame.getBody() instanceof FrameBodyUnsupported)
{
this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
this.frameBody.setHeader(this);
identifier = frame.getIdentifier();
logger.finer("V3:UnsupportedBody:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
}//Simple Copy
else if (identifier != null)
{
//Special Case
if ((frame.getIdentifier().equals(ID3v23Frames.FRAME_ID_V3_USER_DEFINED_INFO)) && (((FrameBodyTXXX) frame.getBody()).getDescription().equals(FrameBodyTXXX.MOOD)))
{
this.frameBody = new FrameBodyTMOO((FrameBodyTXXX) frame.getBody());
this.frameBody.setHeader(this);
identifier = frameBody.getIdentifier();
}
else
{
logger.finer("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody());
this.frameBody.setHeader(this);
}
}
// Is it a known v3 frame which needs forcing to v4 frame e.g. TYER - TDRC
else if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
{
identifier = ID3Tags.forceFrameID23To24(frame.getIdentifier());
if (identifier != null)
{
logger.config("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
this.frameBody.setHeader(this);
}
// No mechanism exists to convert it to a v24 frame, e.g deprecated frame e.g TSIZ, so hold
// as a deprecated frame consisting of an array of bytes*/
else
{
this.frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frame.getBody());
this.frameBody.setHeader(this);
identifier = frame.getIdentifier();
logger.finer("V3:Deprecated:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
}
}
// Unknown Frame e.g NCON or TDRL (because TDRL unknown to V23)
else
{
this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
this.frameBody.setHeader(this);
identifier = frame.getIdentifier();
logger.finer("V3:Unknown:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
}
| public boolean | equals(java.lang.Object obj)
if (this == obj) return true;
if (!(obj instanceof ID3v24Frame))
{
return false;
}
ID3v24Frame that = (ID3v24Frame) obj;
return EqualsUtil.areEqual(this.statusFlags, that.statusFlags) && EqualsUtil.areEqual(this.encodingFlags, that.encodingFlags) && super.equals(that);
| public AbstractID3v2Frame.EncodingFlags | getEncodingFlags()Get Encoding Flags Object
return encodingFlags;
| public int | getEncryptionMethod()
return encryptionMethod;
| protected int | getFrameFlagsSize()
return FRAME_FLAGS_SIZE;
| protected int | getFrameHeaderSize()
return FRAME_HEADER_SIZE;
| protected int | getFrameIdSize()
return FRAME_ID_SIZE;
| private void | getFrameSize(java.nio.ByteBuffer byteBuffer)Read the frame size form the header, check okay , if not try to fix
or just throw exception
//Read frame size as syncsafe integer
frameSize = ID3SyncSafeInteger.bufferToValue(byteBuffer);
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:" + identifier);
//We dont process this frame or add to framemap becuase 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() - FRAME_FLAGS_SIZE))
{
logger.warning(getLoggingFilename() + ":" + "Invalid Frame size larger than size before mp3 audio:" + identifier);
throw new InvalidFrameException(identifier + " is invalid frame");
}
checkIfFrameSizeThatIsNotSyncSafe(byteBuffer);
| protected int | getFrameSizeSize()
return FRAME_SIZE_SIZE;
| public int | getGroupIdentifier()
return groupIdentifier;
| public int | getSize()Return size of frame
return frameBody.getSize() + ID3v24Frame.FRAME_HEADER_SIZE;
| public AbstractID3v2Frame.StatusFlags | getStatusFlags()Get Status Flags Object
return statusFlags;
| public boolean | isBinary()
return ID3v24Frames.getInstanceOf().isBinary(getId());
| public boolean | isCommon()
return ID3v24Frames.getInstanceOf().isCommon(getId());
| public boolean | isValidID3v2FrameIdentifier(java.lang.String identifier)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
Matcher m = ID3v24Frame.validFrameIdentifier.matcher(identifier);
return m.matches();
| public void | read(java.nio.ByteBuffer byteBuffer)Read the frame from the specified file.
Read the frame header then delegate reading of data to frame body.
String identifier = readIdentifier(byteBuffer);
//Is this a valid identifier?
if (!isValidID3v2FrameIdentifier(identifier))
{
//If not valid move file pointer back to one byte after
//the original check so can try again.
logger.config(getLoggingFilename() + ":" + "Invalid identifier:" + identifier);
byteBuffer.position(byteBuffer.position() - (getFrameIdSize() - 1));
throw new InvalidFrameIdentifierException(getLoggingFilename() + ":" + identifier + ":is not a valid ID3v2.30 frame");
}
//Get the frame size, adjusted as necessary
getFrameSize(byteBuffer);
//Read the flag bytes
statusFlags = new StatusFlags(byteBuffer.get());
encodingFlags = new EncodingFlags(byteBuffer.get());
//Read extra bits appended to frame header for various encodings
//These are not included in header size but are included in frame size but wont be read when we actually
//try to read the frame body data
int extraHeaderBytesCount = 0;
int dataLengthSize = -1;
if (((EncodingFlags) encodingFlags).isGrouping())
{
extraHeaderBytesCount = ID3v24Frame.FRAME_GROUPING_INDICATOR_SIZE;
groupIdentifier=byteBuffer.get();
}
if (((EncodingFlags) encodingFlags).isEncryption())
{
//Read the Encryption byte, but do nothing with it
extraHeaderBytesCount += ID3v24Frame.FRAME_ENCRYPTION_INDICATOR_SIZE;
encryptionMethod=byteBuffer.get();
}
if (((EncodingFlags) encodingFlags).isDataLengthIndicator())
{
//Read the sync safe size field
dataLengthSize = ID3SyncSafeInteger.bufferToValue(byteBuffer);
extraHeaderBytesCount += FRAME_DATA_LENGTH_SIZE;
logger.config(getLoggingFilename() + ":" + "Frame Size Is:" + frameSize + " Data Length Size:" + dataLengthSize);
}
//Work out the real size of the frameBody data
int realFrameSize = frameSize - extraHeaderBytesCount;
//Create Buffer that only contains the body of this frame rather than the remainder of tag
ByteBuffer frameBodyBuffer = byteBuffer.slice();
frameBodyBuffer.limit(realFrameSize);
//Do we need to synchronize the frame body
int syncSize = realFrameSize;
if (((EncodingFlags) encodingFlags).isUnsynchronised())
{
//We only want to synchronize the buffer up to the end of this frame (remember this
//buffer contains the remainder of this tag not just this frame), and we cannot just
//create a new buffer because when this method returns the position of the buffer is used
//to look for the next frame, so we need to modify the buffer. The action of synchronizing causes
//bytes to be dropped so the existing buffer is large enough to hold the modifications
frameBodyBuffer = ID3Unsynchronization.synchronize(frameBodyBuffer);
syncSize = frameBodyBuffer.limit();
logger.config(getLoggingFilename() + ":" + "Frame Size After Syncing is:" + syncSize);
}
//Read the body data
try
{
if (((EncodingFlags) encodingFlags).isCompression())
{
frameBodyBuffer = ID3Compression.uncompress(identifier, getLoggingFilename(), byteBuffer, dataLengthSize, realFrameSize);
frameBody = readBody(identifier, frameBodyBuffer, dataLengthSize);
}
else if (((EncodingFlags) encodingFlags).isEncryption())
{
frameBodyBuffer = byteBuffer.slice();
frameBodyBuffer.limit(realFrameSize);
frameBody = readEncryptedBody(identifier, byteBuffer,frameSize);
}
else
{
frameBody = readBody(identifier, frameBodyBuffer, syncSize);
}
if (!(frameBody instanceof ID3v24FrameBody))
{
logger.config(getLoggingFilename() + ":" + "Converted frame body 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);
}
| public void | setEncoding(java.lang.String encoding)Sets the charset encoding used by the field.
Integer encodingId = TextEncoding.getInstanceOf().getIdForValue(encoding);
if(encoding!=null)
{
if(encodingId <4)
{
this.getBody().setTextEncoding(encodingId.byteValue());
}
}
| public void | write(java.io.ByteArrayOutputStream tagBuffer)Write the frame. Writes the frame header but writing the data is delegated to the
frame body.
boolean unsynchronization;
logger.config("Writing frame to file:" + getIdentifier());
//This is where we will write header, move position to where we can
//write bodybuffer
ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE);
//Write Frame Body Data to a new stream
ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
//Does it need unsynchronizing, and are we allowing unsychronizing
byte[] bodyBuffer = bodyOutputStream.toByteArray();
unsynchronization = TagOptionSingleton.getInstance().isUnsyncTags() && ID3Unsynchronization.requiresUnsynchronization(bodyBuffer);
if (unsynchronization)
{
bodyBuffer = ID3Unsynchronization.unsynchronize(bodyBuffer);
logger.config("bodybytebuffer:sizeafterunsynchronisation:" + bodyBuffer.length);
}
//Write Frame Header
//Write Frame ID, the identifier must be 4 bytes bytes long it may not be
//because converted an unknown v2.2 id (only 3 bytes long)
if (getIdentifier().length() == 3)
{
identifier = identifier + ' ";
}
headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE);
//Write Frame Size based on size of body buffer (if it has been unsynced then it size
//will have increased accordingly
int size = bodyBuffer.length;
logger.fine("Frame Size Is:" + size);
headerBuffer.put(ID3SyncSafeInteger.valueToBuffer(size));
//Write the Flags
//Status Flags:leave as they were when we read
headerBuffer.put(statusFlags.getWriteFlags());
//Remove any non standard flags
((ID3v24Frame.EncodingFlags) encodingFlags).unsetNonStandardFlags();
//Encoding we only support unsynchronization
if (unsynchronization)
{
((ID3v24Frame.EncodingFlags) encodingFlags).setUnsynchronised();
}
else
{
((ID3v24Frame.EncodingFlags) encodingFlags).unsetUnsynchronised();
}
//These are not currently supported on write
((ID3v24Frame.EncodingFlags) encodingFlags).unsetCompression();
((ID3v24Frame.EncodingFlags) encodingFlags).unsetDataLengthIndicator();
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 bodybuffer to the Byte Array Output Stream
tagBuffer.write(bodyBuffer);
}
catch (IOException ioe)
{
//This could never happen coz not writing to file, so convert to RuntimeException
throw new RuntimeException(ioe);
}
|
|