FileDocCategorySizeDatePackage
Mp4AtomTree.javaAPI DocJaudiotagger 2.0.418183Wed Mar 30 16:11:44 BST 2011org.jaudiotagger.audio.mp4

Mp4AtomTree

public class Mp4AtomTree extends Object
Tree representing atoms in the mp4 file

Note it doesn't create the complete tree it delves into subtrees for atom we know about and are interested in. (Note it would be impossible to create a complete tree for any file without understanding all the nodes because some atoms such as meta contain data and children and therefore need to be specially preprocessed)

This class is currently only used when writing tags because it better handles the difficulties of mdat and free atoms being optional/multiple places then the older sequential method. It is expected this class will eventually be used when reading tags as well.

Uses a TreeModel for the tree, with convenience methods holding onto references to most common nodes so they can be used without having to traverse the tree again.

Fields Summary
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
rootNode
private org.jaudiotagger.utils.tree.DefaultTreeModel
dataTree
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
moovNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
mdatNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
stcoNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
ilstNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
metaNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
tagsNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
udtaNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
hdlrWithinMdiaNode
private org.jaudiotagger.utils.tree.DefaultMutableTreeNode
hdlrWithinMetaNode
private List
freeNodes
private List
mdatNodes
private List
trakNodes
private org.jaudiotagger.audio.mp4.atom.Mp4StcoBox
stco
private ByteBuffer
moovBuffer
private org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader
moovHeader
public static Logger
logger
Constructors Summary
public Mp4AtomTree(RandomAccessFile raf)
Create Atom Tree

param
raf
throws
IOException
throws
CannotReadException


                  
         
    
        buildTree(raf, true);
    
public Mp4AtomTree(RandomAccessFile raf, boolean closeOnExit)
Create Atom Tree and maintain open channel to raf, should only be used if will continue to use raf after this call, you will have to close raf yourself.

param
raf
param
closeOnExit to keep randomfileaccess open, only used when randomaccessfile already being used
throws
IOException
throws
CannotReadException

        buildTree(raf, closeOnExit);
    
Methods Summary
public voidbuildChildrenOfNode(java.nio.ByteBuffer moovBuffer, org.jaudiotagger.utils.tree.DefaultMutableTreeNode parentNode)

param
moovBuffer
param
parentNode
throws
IOException
throws
CannotReadException

        Mp4BoxHeader boxHeader;

        //Preprocessing for nodes that contain data before their children atoms
        Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();

        //We set the buffers position back to this after processing the children
        int justAfterHeaderPos = moovBuffer.position();

        //Preprocessing for meta that normally contains 4 data bytes, but doesn't where found under track or tags atom
        if (parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName()))
        {
            Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
            meta.processData();

            try
            {
                boxHeader = new Mp4BoxHeader(moovBuffer);
            }
            catch(NullBoxIdException nbe)
            {
                //It might be that the meta box didn't actually have any additional data after it so we adjust the buffer
                //to be immediately after metabox and code can retry
                moovBuffer.position(moovBuffer.position()-Mp4MetaBox.FLAGS_LENGTH);
            }
            finally
            {
                //Skip back last header cos this was only a test 
                moovBuffer.position(moovBuffer.position()-  Mp4BoxHeader.HEADER_LENGTH);
            }
        }

        //Defines where to start looking for the first child node
        int startPos = moovBuffer.position();        
        while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH))
        {
            boxHeader = new Mp4BoxHeader(moovBuffer);
            if (boxHeader != null)
            {
                boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
                logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));

                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
                parentNode.add(newAtom);

                if (boxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName()))
                {
                    udtaNode = newAtom;
                }
                //only interested in metaNode that is child of udta node
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())&&parentBoxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName()))
                {
                    metaNode = newAtom;
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.HDLR.getFieldName())&&parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName()))
                {
                    hdlrWithinMetaNode = newAtom;
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.HDLR.getFieldName()))
                {
                    hdlrWithinMdiaNode = newAtom;
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.TAGS.getFieldName()))
                {
                    tagsNode = newAtom;
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.STCO.getFieldName()))
                {
                    if (stco == null)
                    {
                        stco = new Mp4StcoBox(boxHeader, moovBuffer);
                        stcoNode = newAtom;
                    }
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.ILST.getFieldName()))
                {
                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNode.getParent();
                    if(parent!=null)
                    {
                        Mp4BoxHeader parentsParent = (Mp4BoxHeader)(parent).getUserObject();
                        if(parentsParent!=null)
                        {
                            if(parentBoxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())&&parentsParent.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName()))
                            {
                                ilstNode = newAtom;
                            }
                        }
                    }    
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.FREE.getFieldName()))
                {
                    //Might be multiple in different locations
                    freeNodes.add(newAtom);
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.TRAK.getFieldName()))
                {
                    //Might be multiple in different locations, although only one should be audio track
                    trakNodes.add(newAtom);
                }

                //For these atoms iterate down to build their children
                if ((boxHeader.getId().equals(Mp4AtomIdentifier.TRAK.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.MDIA.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.MINF.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.STBL.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.UDTA.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.META.getFieldName())) ||
                        (boxHeader.getId().equals(Mp4AtomIdentifier.ILST.getFieldName())))
                {                
                    buildChildrenOfNode(moovBuffer, newAtom);
                }
                //Now  adjust buffer for the next atom header at this level
                moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());

            }
        }
        moovBuffer.position(justAfterHeaderPos);
    
public org.jaudiotagger.utils.tree.DefaultTreeModelbuildTree(java.io.RandomAccessFile raf, boolean closeExit)
Build a tree of the atoms in the file

param
raf
param
closeExit false to keep randomfileacces open, only used when randomaccessfile already being used
return
throws
java.io.IOException
throws
org.jaudiotagger.audio.exceptions.CannotReadException

        FileChannel fc = null;
        try
        {
            fc = raf.getChannel();

            //make sure at start of file
            fc.position(0);

            //Build up map of nodes
            rootNode = new DefaultMutableTreeNode();
            dataTree = new DefaultTreeModel(rootNode);

            //Iterate though all the top level Nodes
            ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
            while (fc.position() < fc.size())
            {
                Mp4BoxHeader boxHeader = new Mp4BoxHeader();
                headerBuffer.clear();          
                fc.read(headerBuffer);
                headerBuffer.rewind();

                try
                {
                    boxHeader.update(headerBuffer);
                }
                catch(NullBoxIdException ne)
                {
                    //If we only get this error after all the expected data has been found we allow it
                    if(moovNode!=null&mdatNode!=null)
                    {
                        NullPadding np = new NullPadding(fc.position() - Mp4BoxHeader.HEADER_LENGTH,fc.size());
                        DefaultMutableTreeNode trailingPaddingNode = new DefaultMutableTreeNode(np);
                        rootNode.add(trailingPaddingNode);
                        logger.warning(ErrorMessage.NULL_PADDING_FOUND_AT_END_OF_MP4.getMsg(np.getFilePos()));
                        break;
                    }
                    else
                    {
                        //File appears invalid
                        throw ne;
                    }
                }
                                   
                boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);

                //Go down moov
                if (boxHeader.getId().equals(Mp4AtomIdentifier.MOOV.getFieldName()))
                {
                    moovNode    = newAtom;
                    moovHeader  = boxHeader;

                    long filePosStart = fc.position();
                    moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
                    fc.read(moovBuffer);
                    moovBuffer.rewind();

                    /*Maybe needed but dont have test case yet
                    if(filePosStart + boxHeader.getDataLength() > fc.size())
                    {
                        throw new CannotReadException("The atom states its datalength to be "+boxHeader.getDataLength()
                                + "but there are only "+fc.size()+"bytes in the file and already at position "+filePosStart);    
                    }
                    */
                    buildChildrenOfNode(moovBuffer, newAtom);
                    fc.position(filePosStart);
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.FREE.getFieldName()))
                {
                    //Might be multiple in different locations
                    freeNodes.add(newAtom);
                }
                else if (boxHeader.getId().equals(Mp4AtomIdentifier.MDAT.getFieldName()))
                {
                    //mdatNode always points to the last mDatNode, normally there is just one mdatnode but do have
                    //a valid example of multiple mdatnode

                    //if(mdatNode!=null)
                    //{
                    //    throw new CannotReadException(ErrorMessage.MP4_FILE_CONTAINS_MULTIPLE_DATA_ATOMS.getMsg());
                    //}
                    mdatNode = newAtom;
                    mdatNodes.add(newAtom);
                }
                rootNode.add(newAtom);
                fc.position(fc.position() + boxHeader.getDataLength());
            }
            return dataTree;
        }
        finally
        {
            //If we cant find the audio then we cannot modify this file so better to throw exception
            //now rather than later when try and write to it.
            if(mdatNode==null)
            {
                throw new CannotReadException(ErrorMessage.MP4_CANNOT_FIND_AUDIO.getMsg());
            }

            if (closeExit)
            {
                fc.close();
            }
        }
    
public org.jaudiotagger.audio.mp4.atom.Mp4BoxHeadergetBoxHeader(org.jaudiotagger.utils.tree.DefaultMutableTreeNode node)

param
node
return

        if (node == null)
        {
            return null;
        }
        return (Mp4BoxHeader) node.getUserObject();
    
public org.jaudiotagger.utils.tree.DefaultTreeModelgetDataTree()

return

        return dataTree;
    
public java.util.ListgetFreeNodes()

return

        return freeNodes;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetHdlrWithinMdiaNode()

return

        return hdlrWithinMdiaNode;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetHdlrWithinMetaNode()

return

        return hdlrWithinMetaNode;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetIlstNode()

return

        return ilstNode;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetMdatNode()

return

        return mdatNode;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetMetaNode()

return

        return metaNode;
    
public java.nio.ByteBuffergetMoovBuffer()

return

        return moovBuffer;
    
public org.jaudiotagger.audio.mp4.atom.Mp4BoxHeadergetMoovHeader()

return

        return moovHeader;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetMoovNode()

return

        return moovNode;
    
public org.jaudiotagger.audio.mp4.atom.Mp4StcoBoxgetStco()

return

        return stco;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetStcoNode()

return

        return stcoNode;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetTagsNode()

return

        return tagsNode;
    
public java.util.ListgetTrakNodes()

return

        return trakNodes;
    
public org.jaudiotagger.utils.tree.DefaultMutableTreeNodegetUdtaNode()

return

        return udtaNode;
    
public voidprintAtomTree()
Display atom tree

        Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
        DefaultMutableTreeNode nextNode;
        while (e.hasMoreElements())
        {
            nextNode = e.nextElement();
            Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
            if (header != null)
            {
                String tabbing = "";
                for (int i = 1; i < nextNode.getLevel(); i++)
                {
                    tabbing += "\t";
                }

                if(header instanceof NullPadding)
                {
                    System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));                                        
                }
                else
                {
                    System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
                }
            }
        }