Coverage Report - org.jaudiotagger.audio.mp4.Mp4AtomTree
 
Classes in this File Line Coverage Branch Coverage Complexity
Mp4AtomTree
96%
131/136
92%
72/78
3.15
 
 1  
 package org.jaudiotagger.audio.mp4;
 2  
 
 3  
 import org.jaudiotagger.audio.exceptions.CannotReadException;
 4  
 import org.jaudiotagger.audio.exceptions.NullBoxIdException;
 5  
 import org.jaudiotagger.audio.mp4.atom.Mp4BoxHeader;
 6  
 import org.jaudiotagger.audio.mp4.atom.Mp4MetaBox;
 7  
 import org.jaudiotagger.audio.mp4.atom.Mp4StcoBox;
 8  
 import org.jaudiotagger.audio.mp4.atom.NullPadding;
 9  
 import org.jaudiotagger.logging.ErrorMessage;
 10  
 
 11  
 import javax.swing.tree.DefaultMutableTreeNode;
 12  
 import javax.swing.tree.DefaultTreeModel;
 13  
 import java.io.IOException;
 14  
 import java.io.RandomAccessFile;
 15  
 import java.nio.ByteBuffer;
 16  
 import java.nio.channels.FileChannel;
 17  
 import java.util.ArrayList;
 18  
 import java.util.Enumeration;
 19  
 import java.util.List;
 20  
 import java.util.logging.Logger;
 21  
 
 22  
 /**
 23  
  * Tree representing atoms in the mp4 file
 24  
  * <p/>
 25  
  * Note it doesn't create the complete tree it delves into subtrees for atom we know about and are interested in. (Note
 26  
  * it would be impossible to create a complete tree for any file without understanding all the nodes because
 27  
  * some atoms such as meta contain data and children and therefore need to be specially preprocessed)
 28  
  * <p/>
 29  
  * This class is currently only used when writing tags because it better handles the difficulties of mdat aand free
 30  
  * atoms being optional/multiple places then the older sequential method. It is expected this class will eventually
 31  
  * be used when reading tags as well.
 32  
  * <p/>
 33  
  * Uses a TreeModel for the tree, with convenience methods holding onto references to most common nodes so they
 34  
  * can be used without having to traverse the tree again.
 35  
  */
 36  
 public class Mp4AtomTree
 37  
 {
 38  
     private DefaultMutableTreeNode rootNode;
 39  
     private DefaultTreeModel dataTree;
 40  
     private DefaultMutableTreeNode moovNode;
 41  
     private DefaultMutableTreeNode mdatNode;
 42  
     private DefaultMutableTreeNode stcoNode;
 43  
     private DefaultMutableTreeNode ilstNode;
 44  
     private DefaultMutableTreeNode metaNode;
 45  
     private DefaultMutableTreeNode udtaNode;
 46  
     private DefaultMutableTreeNode hdlrWithinMdiaNode;
 47  
     private DefaultMutableTreeNode hdlrWithinMetaNode;
 48  823
     private List<DefaultMutableTreeNode> freeNodes = new ArrayList<DefaultMutableTreeNode>();
 49  823
     private List<DefaultMutableTreeNode> mdatNodes = new ArrayList<DefaultMutableTreeNode>();
 50  823
     private List<DefaultMutableTreeNode> trakNodes = new ArrayList<DefaultMutableTreeNode>();
 51  
 
 52  
     private Mp4StcoBox stco;
 53  
     private ByteBuffer moovBuffer; //Contains all the data under moov
 54  
     private Mp4BoxHeader moovHeader;
 55  
 
 56  
     //Logger Object
 57  4
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.mp4");
 58  
 
 59  
     /**
 60  
      * Create Atom Tree
 61  
      *
 62  
      * @param raf
 63  
      * @throws IOException
 64  
      * @throws CannotReadException
 65  
      */
 66  
     public Mp4AtomTree(RandomAccessFile raf) throws IOException, CannotReadException
 67  53
     {
 68  53
         buildTree(raf, true);
 69  53
     }
 70  
 
 71  
     /**
 72  
      * Create Atom Tree and maintain open channel to raf, should only be used if will continue
 73  
      * to use raf after this call, you will have to close raf yourself.
 74  
      *
 75  
      * @param raf
 76  
      * @param closeOnExit to keep randomfileaccess open, only used when randomaccessfile already being used
 77  
      * @throws IOException
 78  
      * @throws CannotReadException
 79  
      */
 80  
     public Mp4AtomTree(RandomAccessFile raf, boolean closeOnExit) throws IOException, CannotReadException
 81  770
     {
 82  770
         buildTree(raf, closeOnExit);
 83  767
     }
 84  
 
 85  
     /**
 86  
      * Build a tree of the atoms in the file
 87  
      *
 88  
      * @param raf
 89  
      * @param closeExit false to keep randomfileacces open, only used when randomaccessfile already being used
 90  
      * @return
 91  
      * @throws java.io.IOException
 92  
      * @throws org.jaudiotagger.audio.exceptions.CannotReadException
 93  
      */
 94  
     public DefaultTreeModel buildTree(RandomAccessFile raf, boolean closeExit) throws IOException, CannotReadException
 95  
     {
 96  823
         FileChannel fc = null;
 97  
         try
 98  
         {
 99  823
             fc = raf.getChannel();
 100  
 
 101  
             //make sure at start of file
 102  823
             fc.position(0);
 103  
 
 104  
             //Build up map of nodes
 105  823
             rootNode = new DefaultMutableTreeNode();
 106  823
             dataTree = new DefaultTreeModel(rootNode);
 107  
 
 108  
             //Iterate though all the top level Nodes
 109  823
             ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
 110  3947
             while (fc.position() < fc.size())
 111  
             {
 112  3131
                  Mp4BoxHeader boxHeader = new Mp4BoxHeader();
 113  3131
                 headerBuffer.clear();          
 114  3131
                 fc.read(headerBuffer);
 115  3131
                 headerBuffer.rewind();
 116  
 
 117  
                 try
 118  
                 {
 119  3131
                     boxHeader.update(headerBuffer);
 120  
                 }
 121  5
                 catch(NullBoxIdException ne)
 122  
                 {
 123  
                     //If we only get this error after all the expected data has been found we allow it
 124  5
                     if(moovNode!=null&mdatNode!=null)
 125  
                     {
 126  5
                         NullPadding np = new NullPadding(fc.position() - Mp4BoxHeader.HEADER_LENGTH,fc.size());
 127  5
                         DefaultMutableTreeNode trailingPaddingNode = new DefaultMutableTreeNode(np);
 128  5
                         rootNode.add(trailingPaddingNode);
 129  5
                         logger.warning(ErrorMessage.NULL_PADDING_FOUND_AT_END_OF_MP4.getMsg(np.getFilePos()));
 130  5
                         break;
 131  
                     }
 132  
                     else
 133  
                     {
 134  
                         //File appears invalid
 135  0
                         throw ne;
 136  
                     }
 137  3125
                 }
 138  
                                    
 139  3125
                 boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
 140  3125
                 DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 141  
 
 142  
                 //Go down moov
 143  3125
                 if (boxHeader.getId().equals(Mp4NotMetaFieldKey.MOOV.getFieldName()))
 144  
                 {
 145  823
                     moovNode    = newAtom;
 146  823
                     moovHeader  = boxHeader;
 147  
 
 148  823
                     long filePosStart = fc.position();
 149  823
                     moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
 150  823
                     fc.read(moovBuffer);
 151  823
                     moovBuffer.rewind();
 152  
 
 153  
                     /*Maybe needed but dont have test case yet
 154  
                     if(filePosStart + boxHeader.getDataLength() > fc.size())
 155  
                     {
 156  
                         throw new CannotReadException("The atom states its datalength to be "+boxHeader.getDataLength()
 157  
                                 + "but there are only "+fc.size()+"bytes in the file and already at position "+filePosStart);    
 158  
                     }
 159  
                     */
 160  823
                     buildChildrenOfNode(moovBuffer, newAtom);
 161  822
                     fc.position(filePosStart);
 162  822
                 }
 163  2302
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 164  
                 {
 165  
                     //Might be multiple in different locations
 166  654
                     freeNodes.add(newAtom);
 167  
                 }
 168  1648
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.MDAT.getFieldName()))
 169  
                 {
 170  
                     //mdatNode always points to the last mDatNode, normally there is just one mdatnode but do have
 171  
                     //a valid example of multiple mdatnode
 172  
 
 173  
                     //if(mdatNode!=null)
 174  
                     //{
 175  
                     //    throw new CannotReadException(ErrorMessage.MP4_FILE_CONTAINS_MULTIPLE_DATA_ATOMS.getMsg());
 176  
                     //}
 177  824
                     mdatNode = newAtom;
 178  824
                     mdatNodes.add(newAtom);
 179  
                 }
 180  3124
                 rootNode.add(newAtom);
 181  3124
                 fc.position(fc.position() + boxHeader.getDataLength());
 182  3124
             }
 183  821
             return dataTree;
 184  
         }
 185  
         finally
 186  
         {
 187  
             //If we cant find the audio then we cannot modify this file so better to throw exception
 188  
             //now rather than later when try and write to it.
 189  2
             if(mdatNode==null)
 190  
             {
 191  3
                 throw new CannotReadException(ErrorMessage.MP4_CANNOT_FIND_AUDIO.getMsg());
 192  
             }
 193  
 
 194  820
             if (closeExit)
 195  
             {
 196  53
                 fc.close();
 197  
             }
 198  
         }
 199  
     }
 200  
 
 201  
     /**
 202  
      * Display atom tree
 203  
      */
 204  
     @SuppressWarnings("unchecked")
 205  
     public void printAtomTree()
 206  
     {
 207  53
         Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
 208  
         DefaultMutableTreeNode nextNode;
 209  2585
         while (e.hasMoreElements())
 210  
         {
 211  2532
             nextNode = e.nextElement();
 212  2532
             Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
 213  2532
             if (header != null)
 214  
             {
 215  2479
                 String tabbing = "";
 216  10423
                 for (int i = 1; i < nextNode.getLevel(); i++)
 217  
                 {
 218  7944
                     tabbing += "\t";
 219  
                 }
 220  
 
 221  2479
                 if(header instanceof NullPadding)
 222  
                 {
 223  1
                     System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));                                        
 224  
                 }
 225  
                 else
 226  
                 {
 227  2478
                     System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
 228  
                 }
 229  
             }
 230  2532
         }
 231  53
     }
 232  
 
 233  
     /**
 234  
      *
 235  
      * @param moovBuffer
 236  
      * @param parentNode
 237  
      * @throws IOException
 238  
      * @throws CannotReadException
 239  
      */
 240  
     public void buildChildrenOfNode(ByteBuffer moovBuffer, DefaultMutableTreeNode parentNode) throws IOException, CannotReadException
 241  
     {
 242  
         Mp4BoxHeader boxHeader;
 243  
 
 244  
         //Preprocessing for nodes that contain data before their children atoms
 245  7693
         Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();
 246  
 
 247  
         //We set the buffers position back to this after processing the chikdren
 248  7693
         int justAfterHeaderPos = moovBuffer.position();
 249  
 
 250  
         //Preprocessing for meta that normally contains 4 data bytes, but doesnt whre found under trak atom
 251  
         //TODO is it always under TRAK dont really know the rule
 252  7693
         if (parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 253  
         {
 254  870
             Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
 255  870
             meta.processData();
 256  
 
 257  
             try
 258  
             {
 259  870
                 boxHeader = new Mp4BoxHeader(moovBuffer);
 260  798
             }
 261  72
             catch(NullBoxIdException nbe)
 262  
             {
 263  
                 //It might be that the meta box didnt actually have any additional data after it so we adjust the buffer
 264  
                 //to be immediately after metabox and code can retry
 265  72
                 moovBuffer.position(moovBuffer.position()-Mp4MetaBox.FLAGS_LENGTH);
 266  72
             }
 267  
             finally
 268  
             {
 269  
                 //Skip back last header cos this was only a test 
 270  0
                 moovBuffer.position(moovBuffer.position()-  Mp4BoxHeader.HEADER_LENGTH);
 271  870
             }
 272  
         }
 273  
 
 274  
         //Defines where to start looking for the first child node
 275  7693
         int startPos = moovBuffer.position();        
 276  44203
         while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH))
 277  
         {
 278  36513
             boxHeader = new Mp4BoxHeader(moovBuffer);
 279  36512
             if (boxHeader != null)
 280  
             {
 281  36512
                 boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
 282  36512
                 logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));
 283  
 
 284  36512
                 DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 285  36512
                 parentNode.add(newAtom);
 286  
 
 287  36512
                 if (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 288  
                 {
 289  801
                     udtaNode = newAtom;
 290  
                 }
 291  
                 //only interested in metaNode that is child of udta node
 292  35711
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 293  
                 {
 294  798
                     metaNode = newAtom;
 295  
                 }
 296  34913
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 297  
                 {
 298  870
                     hdlrWithinMetaNode = newAtom;
 299  
                 }
 300  34043
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName()))
 301  
                 {
 302  1085
                     hdlrWithinMdiaNode = newAtom;
 303  
                 }
 304  32958
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.STCO.getFieldName()))
 305  
                 {
 306  1085
                     if (stco == null)
 307  
                     {
 308  823
                         stco = new Mp4StcoBox(boxHeader, moovBuffer);
 309  823
                         stcoNode = newAtom;
 310  
                     }
 311  
                 }
 312  31873
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName()))
 313  
                 {
 314  859
                     DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNode.getParent();
 315  859
                     if(parent!=null)
 316  
                     {
 317  859
                         Mp4BoxHeader parentsParent = (Mp4BoxHeader)(parent).getUserObject();
 318  859
                         if(parentsParent!=null)
 319  
                         {
 320  859
                             if(parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentsParent.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 321  
                             {
 322  786
                                 ilstNode = newAtom;
 323  
                             }
 324  
                         }
 325  
                     }    
 326  859
                 }
 327  31014
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 328  
                 {
 329  
                     //Might be multiple in different locations
 330  350
                     freeNodes.add(newAtom);
 331  
                 }
 332  30664
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName()))
 333  
                 {
 334  
                     //Might be multiple in different locations, although onely one shoud be audio track
 335  1085
                     trakNodes.add(newAtom);
 336  
                 }
 337  
 
 338  
                 //For these atoms iterate down to build their children
 339  36512
                 if ((boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName())) ||
 340  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.MDIA.getFieldName())) ||
 341  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.MINF.getFieldName())) ||
 342  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.STBL.getFieldName())) ||
 343  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName())) ||
 344  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())) ||
 345  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName())))
 346  
                 {                
 347  6870
                     buildChildrenOfNode(moovBuffer, newAtom);
 348  
                 }
 349  
                 //Now  adjust buffer for the next atom header at this level
 350  36510
                 moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());
 351  
 
 352  36510
             }
 353  
         }
 354  7690
         moovBuffer.position(justAfterHeaderPos);
 355  7690
     }
 356  
 
 357  
 
 358  
     /**
 359  
      *
 360  
      * @return
 361  
      */
 362  
     public DefaultTreeModel getDataTree()
 363  
     {
 364  0
         return dataTree;
 365  
     }
 366  
 
 367  
 
 368  
     /**
 369  
      *
 370  
      * @return
 371  
      */
 372  
     public DefaultMutableTreeNode getMoovNode()
 373  
     {
 374  183
         return moovNode;
 375  
     }
 376  
 
 377  
     /**
 378  
      *
 379  
      * @return
 380  
      */
 381  
     public DefaultMutableTreeNode getStcoNode()
 382  
     {
 383  0
         return stcoNode;
 384  
     }
 385  
 
 386  
     /**
 387  
      *
 388  
      * @return
 389  
      */
 390  
     public DefaultMutableTreeNode getIlstNode()
 391  
     {
 392  183
         return ilstNode;
 393  
     }
 394  
 
 395  
     /**
 396  
      *
 397  
      * @param node
 398  
      * @return
 399  
      */
 400  
     public Mp4BoxHeader getBoxHeader(DefaultMutableTreeNode node)
 401  
     {
 402  1830
         if (node == null)
 403  
         {
 404  36
             return null;
 405  
         }
 406  1794
         return (Mp4BoxHeader) node.getUserObject();
 407  
     }
 408  
 
 409  
     /**
 410  
      *
 411  
      * @return
 412  
      */
 413  
     public DefaultMutableTreeNode getMdatNode()
 414  
     {
 415  366
         return mdatNode;
 416  
     }
 417  
 
 418  
     /**
 419  
      *
 420  
      * @return
 421  
      */
 422  
     public DefaultMutableTreeNode getUdtaNode()
 423  
     {
 424  366
         return udtaNode;
 425  
     }
 426  
 
 427  
     /**
 428  
      *
 429  
      * @return
 430  
      */
 431  
     public DefaultMutableTreeNode getMetaNode()
 432  
     {
 433  366
         return metaNode;
 434  
     }
 435  
 
 436  
     /**
 437  
      *
 438  
      * @return
 439  
      */
 440  
     public DefaultMutableTreeNode getHdlrWithinMetaNode()
 441  
     {
 442  183
         return hdlrWithinMetaNode;
 443  
     }
 444  
 
 445  
     /**
 446  
      *
 447  
      * @return
 448  
      */
 449  
     public DefaultMutableTreeNode getHdlrWithinMdiaNode()
 450  
     {
 451  183
         return hdlrWithinMdiaNode;
 452  
     }
 453  
 
 454  
     /**
 455  
      *
 456  
      * @return
 457  
      */
 458  
     public List<DefaultMutableTreeNode> getFreeNodes()
 459  
     {
 460  366
         return freeNodes;
 461  
     }
 462  
 
 463  
     /**
 464  
      *
 465  
      * @return
 466  
      */
 467  
     public List<DefaultMutableTreeNode> getTrakNodes()
 468  
     {
 469  183
         return trakNodes;
 470  
     }
 471  
 
 472  
     /**
 473  
      *
 474  
      * @return
 475  
      */
 476  
     public Mp4StcoBox getStco()
 477  
     {
 478  365
         return stco;
 479  
     }
 480  
 
 481  
     /**
 482  
      *
 483  
      * @return
 484  
      */
 485  
     public ByteBuffer getMoovBuffer()
 486  
     {
 487  183
         return moovBuffer;
 488  
     }
 489  
 
 490  
     /**
 491  
      *
 492  
      * @return
 493  
      */
 494  
     public Mp4BoxHeader getMoovHeader()
 495  
     {
 496  0
         return moovHeader;
 497  
     }
 498  
 }