FileDocCategorySizeDatePackage
ID3V2Frame.javaAPI Docjid3 0.4625897Wed May 11 04:22:19 BST 2005org.blinkenlights.jid3.v2

ID3V2Frame

public abstract class ID3V2Frame extends Object implements ID3Visitable, ID3Subject
author
paul The base class for all ID3 V2 frames.

Fields Summary
private Set
m_oID3ObserverSet
private boolean
m_bTagAlterPreservationFlag
private boolean
m_bFileAlterPreservationFlag
private boolean
m_bReadOnlyFlag
private boolean
m_bCompressionFlag
private boolean
m_bEncryptionFlag
private boolean
m_bGroupingIdentityFlag
private byte
m_byEncryptionMethod
private ICryptoAgent
m_oCryptoAgent
private byte[]
m_abyEncryptionData
Constructors Summary
public ID3V2Frame()

    
     
    
    
Methods Summary
public voidaddID3Observer(ID3Observer oID3Observer)

        m_oID3ObserverSet.add(oID3Observer);
    
private intgetActualLength()
Returns the length in bytes that the body of the frame will require when actually written to a file. This may be shorter than the default length, if the frame is compressed.

return
the length of the frame when written
throws
IOException if an error occurs while determining the compressed length

        ByteArrayOutputStream oBAOS = new ByteArrayOutputStream();
        ID3DataOutputStream oIDOS = new ID3DataOutputStream(oBAOS);
        writeBody(oIDOS);
        byte[] abyBody = oBAOS.toByteArray();

        if (m_bCompressionFlag)
        {
            ByteArrayOutputStream oCompressedBAOS = new ByteArrayOutputStream();
            DeflaterOutputStream oDeflaterOS = new DeflaterOutputStream(oCompressedBAOS);
            oDeflaterOS.write(abyBody);
            oDeflaterOS.finish();
            abyBody = oCompressedBAOS.toByteArray();
        }
        
        if (m_bEncryptionFlag)
        {
            if (m_oCryptoAgent == null)
            {
                throw new ID3Exception("Crypto agent for method " + m_byEncryptionMethod + " not registered.  Cannot write frame.");
            }
            
            abyBody = m_oCryptoAgent.encrypt(abyBody, m_abyEncryptionData);
        }
        
        return abyBody.length + (m_bCompressionFlag ? 4 : 0) + (m_bEncryptionFlag ? 1 : 0);
    
public bytegetEncryptionMethod()
Get the encryption method symbol used to encrypt this frame.

return
the encryption method symbol used
throws
ID3Exception if this frame is not encrypted

        if (! isEncrypted())
        {
            throw new ID3Exception("This frame is not encrypted.");
        }
        
        return m_byEncryptionMethod;
    
protected abstract byte[]getFrameId()
Get the four bytes which uniquely specify of which type this frame is.

private intgetLength()
Return number of bytes required to store the body of this frame.

return
the number of bytes

        ByteArrayOutputStream oBAOS = new ByteArrayOutputStream();
        ID3DataOutputStream oLengthIDOS = new ID3DataOutputStream(oBAOS);
        writeBody(oLengthIDOS);
        
        return oBAOS.size();
    
public booleanisEncrypted()
Check whether this frame is encrypted.

return
true if the frame is encrypted, false otherwise

        return m_bEncryptionFlag;
    
public voidnotifyID3Observers()

        Iterator oIter = m_oID3ObserverSet.iterator();
        
        while (oIter.hasNext())
        {
            ID3Observer oID3Observer = (ID3Observer)oIter.next();
            
            oID3Observer.update(this);
        }
    
static org.blinkenlights.jid3.v2.ID3V2Frameread(ID3DataInputStream oID3DIS)
Read an ID3 v2 frame from an ID3DataInputStream.

param
oID3DIS input stream from which a frame can directly be read
return
an ID3V2Frame which was read from the input stream
throws
ID3Exception if an error while reading occurs

        return read(oID3DIS, new ENCRID3V2Frame[0]);
    
static org.blinkenlights.jid3.v2.ID3V2Frameread(ID3DataInputStream oID3DIS, ENCRID3V2Frame[] aoENCRID3V2Frame)
Read an ID3 v2 frame from an ID3DataInputStream, providing the possibility for decryption.

param
oID3DIS input stream from which a frame can directly be read
param
aoENCRID3V2Frame the array of ENCR frames which were read in, which describe encryption details
return
an ID3V2Frame which was read from the input stream
throws
ID3Exception if an error while reading occurs

        String sFrameId = null;
        try
        {
            // read frame id
            byte[] abyFrameId = new byte[4];
            oID3DIS.readFully(abyFrameId);
            if (abyFrameId[0] == 0) // we're reading into the padding past the frames
            {
                return null;
            }
            sFrameId = new String(abyFrameId);
            
            //HACK: This is a work-around for a bug in the MP3ext Windows explorer extension.  It repeatedly
            //      writes the string "MP3ext V3.3.18(unicode)" into the padding area after the frames in a v2 tag.
            //      This is invalid, and the resulting frames are corrupt, according to the ID3 specification, which
            //      requires that padding contain only nulls.
            if (sFrameId.equals("MP3e"))
            {
                return null;
            }
            
            if (( ! sFrameId.matches("[A-Z0-9]+")) && ID3V2Tag.usingStrict())
            {
                throw new InvalidFrameID3Exception("Invalid frame id [" + sFrameId + "].");
            }
            
            // read size
            int iFrameSize = oID3DIS.readBE32();
            
            // read first flags byte
            int iFirstFlags = oID3DIS.readUnsignedByte();
            boolean bTagAlterPreservationFlag = ((iFirstFlags & 0x80) != 0);
            boolean bFileAlterPreservationFlag = ((iFirstFlags & 0x40) != 0);
            boolean bReadOnlyFlag = ((iFirstFlags & 0x20) != 0);
            boolean bUnknownFirstByteFlags = ((iFirstFlags & 0x1f) != 0);
            
            // read second flags byte
            int iSecondFlags = oID3DIS.readUnsignedByte();
            boolean bCompressionFlag = ((iSecondFlags & 0x80) != 0);
            boolean bEncryptionFlag = ((iSecondFlags & 0x40) != 0);
            boolean bGroupingIdentityFlag = ((iSecondFlags & 0x20) != 0);
            boolean bUnknownSecondByteFlags = ((iSecondFlags & 0x1f) != 0);
            
            // get length of uncompressed frame if compression set
            int iUncompressedSize = iFrameSize;
            if (bCompressionFlag)
            {
                iUncompressedSize = oID3DIS.readBE32();
                iFrameSize -= 4;    // FIX: four bytes read for frame size
            }
            
            // read encryption method byte, if used
            int iEncryptionMethodSymbol = 0;
            ICryptoAgent oCryptoAgent = null;
            byte[] abyEncryptionData = null;
            if (bEncryptionFlag)
            {
                iEncryptionMethodSymbol = oID3DIS.readUnsignedByte();
                iFrameSize -= 1;    // FIX: one byte for encryption method
                
                // this frame is encrypted.. do we have a means of decrypting it?
                for (int i=0; i < aoENCRID3V2Frame.length; i++)
                {
                    if ((aoENCRID3V2Frame[i].getEncryptionMethodSymbol() & 0xff) == iEncryptionMethodSymbol)
                    {
                        // we can decrypt this frame now
                        oCryptoAgent = ID3Encryption.getInstance().lookupCryptoAgent(aoENCRID3V2Frame[i].getOwnerIdentifier());
                        abyEncryptionData = aoENCRID3V2Frame[i].getEncryptionData();
                        break;
                    }
                }
                
                if (oCryptoAgent == null)
                {
                    ByteArrayOutputStream oEncryptedBAOS = new ByteArrayOutputStream();
                    ID3DataOutputStream oEncryptedIDOS = new ID3DataOutputStream(oEncryptedBAOS);
                    oEncryptedIDOS.write(abyFrameId);
                    oEncryptedIDOS.writeBE32(iFrameSize + (bEncryptionFlag ? 1 : 0));
                    oEncryptedIDOS.writeUnsignedByte(iFirstFlags);
                    oEncryptedIDOS.writeUnsignedByte(iSecondFlags);
                    if (bCompressionFlag)
                    {
                        oEncryptedIDOS.writeID3Four(iUncompressedSize);
                    }
                    oEncryptedIDOS.writeUnsignedByte(iEncryptionMethodSymbol);
                    
                    // determine the length of the compressed/encrypted data to be read in (minus the after header bytes we've already read)
                    int iFrameDataLength = iFrameSize;  // initial length of frame data before data we have already read

                    // read compressed/encrypted data
                    byte[] abyEncryptedFrameData = new byte[iFrameDataLength];
                    oID3DIS.readFully(abyEncryptedFrameData);
                    oEncryptedIDOS.write(abyEncryptedFrameData);
                    
                    // we cannot decrypt this frame at this time, so return it, as we read it, as a special encrypted frame object
                    EncryptedID3V2Frame oEncryptedFrame = new EncryptedID3V2Frame(sFrameId, oEncryptedBAOS.toByteArray());
                    
                    return oEncryptedFrame;
                }
            }
            
            // read frame data
            byte[] abyFrameData = null;
            if (bCompressionFlag)
            {
                // read compressed data
                byte[] abyCompressedFrameData = new byte[iFrameSize];
                oID3DIS.readFully(abyCompressedFrameData);
                
                // decrypt compressed data first, if encrypted
                if (bEncryptionFlag)
                {
                    abyCompressedFrameData = oCryptoAgent.decrypt(abyCompressedFrameData, abyEncryptionData);
                }
                
                // deflate data
                ByteArrayInputStream oBAIS = new ByteArrayInputStream(abyCompressedFrameData);
                InflaterInputStream oInflaterIS = new InflaterInputStream(oBAIS);
                ID3DataInputStream oInflaterID3DIS = new ID3DataInputStream(oInflaterIS);
                abyFrameData = new byte[iUncompressedSize];
                oInflaterID3DIS.readFully(abyFrameData);
            }
            else
            {
                abyFrameData = new byte[iFrameSize];
                oID3DIS.readFully(abyFrameData);
                
                // decrypt data, if encrypted
                if (bEncryptionFlag)
                {
                    abyFrameData = oCryptoAgent.decrypt(abyFrameData, abyEncryptionData);
                }
            }
            
            // create a frame object here based on what we've read
            ID3V2Frame oID3V2Frame;
            if (sFrameId.startsWith("T"))
            {
                // text information frame
                String sClassName = "org.blinkenlights.jid3.v2." + sFrameId + "TextInformationID3V2Frame";
                
                // if this class exists, then create such an object
                try
                {
                    Class oID3V2FrameClass = Class.forName(sClassName);
                    Class[] aoArgClassTypes = { InputStream.class };
                    Constructor oConstructor = oID3V2FrameClass.getConstructor(aoArgClassTypes);
                    Object[] aoConstructorArgs = { new ByteArrayInputStream(abyFrameData) };
                    oID3V2Frame = (ID3V2Frame)oConstructor.newInstance(aoConstructorArgs);
                }
                catch (ClassNotFoundException e)
                {
                    // unknown frame type
                    oID3V2Frame = new UnknownTextInformationID3V2Frame(sFrameId, new ByteArrayInputStream(abyFrameData));
                }
                catch (NoSuchMethodException e)
                {
                    // unknown frame type
                    oID3V2Frame = new UnknownTextInformationID3V2Frame(sFrameId, new ByteArrayInputStream(abyFrameData));
                }
                catch (InvocationTargetException e)
                {
                    // constructor threw an exception
                    if (e.getCause() instanceof Exception)
                    {
                        throw (Exception)e.getCause();
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
            else if (sFrameId.startsWith("W"))
            {
                // URL link frame
                String sClassName = "org.blinkenlights.jid3.v2." + sFrameId + "UrlLinkID3V2Frame";
                
                // if this class exists, then create such an object
                try
                {
                    Class oID3V2FrameClass = Class.forName(sClassName);
                    Class[] aoArgClassTypes = { InputStream.class };
                    Constructor oConstructor = oID3V2FrameClass.getConstructor(aoArgClassTypes);
                    Object[] aoConstructorArgs = { new ByteArrayInputStream(abyFrameData) };
                    oID3V2Frame = (ID3V2Frame)oConstructor.newInstance(aoConstructorArgs);
                }
                catch (ClassNotFoundException e)
                {
                    // unknown frame type
                    oID3V2Frame = new UnknownUrlLinkID3V2Frame(sFrameId, new ByteArrayInputStream(abyFrameData));
                }
                catch (NoSuchMethodException e)
                {
                    // unknown frame type
                    oID3V2Frame = new UnknownUrlLinkID3V2Frame(sFrameId, new ByteArrayInputStream(abyFrameData));
                }
                catch (InvocationTargetException e)
                {
                    // constructor threw an exception
                    if (e.getCause() instanceof Exception)
                    {
                        throw (Exception)e.getCause();
                    }
                    else
                    {
                        throw e;
                    }
                }
            }
            else
            {
                // unique frame
                String sClassName = "org.blinkenlights.jid3.v2." + sFrameId + "ID3V2Frame";
                
                // if this class exists, then create such an object
                try
                {
                    Class oID3V2FrameClass = Class.forName(sClassName);
                    Class[] aoArgClassTypes = { InputStream.class };
                    Constructor oConstructor = oID3V2FrameClass.getConstructor(aoArgClassTypes);
                    Object[] aoConstructorArgs = { new ByteArrayInputStream(abyFrameData) };
                    oID3V2Frame = (ID3V2Frame)oConstructor.newInstance(aoConstructorArgs);
                }
                catch (ClassNotFoundException e)
                {
                    // unknown frame
                    oID3V2Frame = new UnknownID3V2Frame(sFrameId, abyFrameData);
                }
                catch (NoSuchMethodException e)
                {
                    // unknown frame type
                    oID3V2Frame = new UnknownID3V2Frame(sFrameId, abyFrameData);
                }
            }

            // set flags applicable to all v2 frames
            oID3V2Frame.setTagAlterPreservationFlag(bTagAlterPreservationFlag);
            oID3V2Frame.setFileAlterPreservationFlag(bFileAlterPreservationFlag);
            oID3V2Frame.setReadOnlyFlag(bReadOnlyFlag);
            oID3V2Frame.setCompressionFlag(bCompressionFlag);
            if (bEncryptionFlag)
            {
                oID3V2Frame.setEncryption((byte)iEncryptionMethodSymbol);
            }
            oID3V2Frame.setGroupingIdentityFlag(bGroupingIdentityFlag);
            
            return oID3V2Frame;
        }
        catch (ID3Exception e)
        {
            throw e;
        }
        catch (Exception e)
        {
            if (sFrameId == null)
            {
                throw new ID3Exception("Error reading v2 frame.", e);
            }
            else
            {
                throw new ID3Exception("Error reading " + sFrameId + " v2 frame.", e);
            }
        }
    
public voidremoveID3Observer(ID3Observer oID3Observer)

        m_oID3ObserverSet.remove(oID3Observer);
    
public voidsetCompressionFlag(boolean bCompressionFlagValue)
Specify whether this frame is compressed or not. The compression method used is zlib.

param
bCompressionFlagValue the state this flag should be set to

        m_bCompressionFlag = bCompressionFlagValue;
    
voidsetCryptoAgent(ICryptoAgent oCryptoAgent, byte[] abyEncryptionData)
Set the crypto agent to be used in this frame.

param
oCryptoAgent the crypto agent to be used for encrypting this frame
param
abyEncryptionData the encryption data to be used when encrypting/decrypting with this agent

        m_oCryptoAgent = oCryptoAgent;
        m_abyEncryptionData = abyEncryptionData;
    
public voidsetEncryption(byte byEncryptionMethod)
Set encryption method for this frame. When an encryption method is specified, the frame will be encrypted before being written to a file.

param
byEncryptionMethod the encryption method value to use (this value must match a method specified in an ENCR frame in this tag)

        m_bEncryptionFlag = true;
        m_byEncryptionMethod = byEncryptionMethod;
        
        notifyID3Observers();
    
public voidsetFileAlterPreservationFlag(boolean bFileAlterPreservationFlagValue)
Specify what should happen to this frame, if this file (other than the tag) is modified by a program which does not recognize it. If set to true, then this frame should be discarded. If set to false, then the frame should be preserved as is.

param
bFileAlterPreservationFlagValue the state this flag should be set to

        m_bFileAlterPreservationFlag = bFileAlterPreservationFlagValue;
    
public voidsetGroupingIdentityFlag(boolean bGroupingIdentityFlagValue)
Specify whether this frame belongs to a group of other frames or not. If set, the group identifier must be specified.

param
bGroupingIdentityFlagValue the state this flag should be set to

        m_bGroupingIdentityFlag = bGroupingIdentityFlagValue;
    
public voidsetReadOnlyFlag(boolean bReadOnlyFlagValue)
Specify whether this frame should be considered read only or not.

param
bReadOnlyFlagValue the state this flag should be set to

        m_bReadOnlyFlag = bReadOnlyFlagValue;
    
public voidsetTagAlterPreservationFlag(boolean bTagAlterPreservationFlagValue)
Specify what should happen to this frame, if the tag it is in is modified by another program which does not recognize it. If set to true, then this frame should be discarded by a program which does not recognize it. If set to false, it should be left as is.

param
bTagAlterPreservationFlagValue the state this flag should be set to

        m_bTagAlterPreservationFlag = bTagAlterPreservationFlagValue;
    
public abstract java.lang.StringtoString()
Represent the contents of this frame as a string. For debugging purposes.

return
a string representing this frame

public voidwrite(java.io.OutputStream oOS)
Write this frame to an output stream.

param
oOS the output stream to write to
throws
ID3Exception if an error occurs while writing the frame
throws
IOException if an error occurs while writing the frame

        ID3DataOutputStream oIDOS = new ID3DataOutputStream(oOS);
        
        // write header
        writeHeader(oIDOS);
        
        // write body
        byte[] abyBody = null;
        
        // put original body bytes in abyBody
        ByteArrayOutputStream oBodyBAOS = new ByteArrayOutputStream();
        ID3DataOutputStream oBodyIDOS = new ID3DataOutputStream(oBodyBAOS);
        writeBody(oBodyIDOS);
        abyBody = oBodyBAOS.toByteArray();
        
        // if compression used, compress body byte array
        if (m_bCompressionFlag)
        {
            ByteArrayOutputStream oCompressedBAOS = new ByteArrayOutputStream();
            DeflaterOutputStream oDeflaterOS = new DeflaterOutputStream(oCompressedBAOS);
            oDeflaterOS.write(abyBody);
            oDeflaterOS.finish();
            abyBody = oCompressedBAOS.toByteArray();
        }
        
        // if encryption used, encrypt body byte array
        if (m_bEncryptionFlag)
        {
            if (m_oCryptoAgent == null)
            {
                throw new ID3Exception("Crypto agent for method " + m_byEncryptionMethod + " not registered.  Cannot write frame.");
            }
            
            abyBody = m_oCryptoAgent.encrypt(abyBody, m_abyEncryptionData);
        }
        
        oIDOS.write(abyBody);
    
protected abstract voidwriteBody(ID3DataOutputStream oIDOS)
Write the body of the frame to an ID3 data output stream.

param
oIDOS the output stream to write to
throws
ID3Exception if an error occurs while writing

protected voidwriteHeader(java.io.OutputStream oOS)
Write the header of this frame to an output stream.

param
oOS the output stream to write to
throws
ID3Exception if an error occurs while writing

        try
        {
            ID3DataOutputStream oIDOS = new ID3DataOutputStream(oOS);
            
            // frame id
            oIDOS.write(getFrameId());
            // size
            int iActualLength = getActualLength();
            oIDOS.writeBE32(iActualLength);
            //oIDOS.writeBE32(getLength());
            // first flags
            int iFirstFlags = 0;
            if (m_bTagAlterPreservationFlag)
            {
                iFirstFlags |= (1 << 7);
            }
            if (m_bFileAlterPreservationFlag)
            {
                iFirstFlags |= (1 << 6);
            }
            if (m_bReadOnlyFlag)
            {
                iFirstFlags |= (1 << 5);
            }
            oIDOS.writeUnsignedByte(iFirstFlags);
            // second flags
            int iSecondFlags = 0;
            if (m_bCompressionFlag)
            {
                iSecondFlags |= (1 << 7);
            }
            if (m_bEncryptionFlag)
            {
                iSecondFlags |= (1 << 6);
            }
            if (m_bGroupingIdentityFlag)
            {
                iSecondFlags |= (1 << 5);
            }
            oIDOS.writeUnsignedByte(iSecondFlags);
            
            // write uncompressed length of the body, if it is compressed
            if (m_bCompressionFlag)
            {
                oIDOS.writeBE32(getLength());
            }
            // write encrypted method, if used
            if (m_bEncryptionFlag)
            {
                oIDOS.writeUnsignedByte(m_byEncryptionMethod & 0xff);
            }
        }
        catch (Exception e)
        {
            throw new ID3Exception("Error writing frame: " + e.getMessage(), e);
        }