Coverage Report - org.jaudiotagger.audio.mp4.Mp4AtomTree
 
Classes in this File Line Coverage Branch Coverage Complexity
Mp4AtomTree
97%
128/132
91%
69/76
0
 
 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  
     private DefaultMutableTreeNode trailingPaddingNode;
 49  234
     private List<DefaultMutableTreeNode> freeNodes = new ArrayList<DefaultMutableTreeNode>();
 50  234
     private List<DefaultMutableTreeNode> mdatNodes = new ArrayList<DefaultMutableTreeNode>();
 51  234
     private List<DefaultMutableTreeNode> trakNodes = new ArrayList<DefaultMutableTreeNode>();
 52  
 
 53  
     private Mp4StcoBox stco;
 54  
     private ByteBuffer moovBuffer; //Contains all the data under moov
 55  
     private Mp4BoxHeader moovHeader;
 56  
 
 57  
     //Logger Object
 58  17
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.mp4");
 59  
 
 60  
     /**
 61  
      * Create Atom Tree
 62  
      *
 63  
      * @param raf
 64  
      * @throws IOException
 65  
      * @throws CannotReadException
 66  
      */
 67  
     public Mp4AtomTree(RandomAccessFile raf) throws IOException, CannotReadException
 68  14
     {
 69  14
         buildTree(raf, true);
 70  14
     }
 71  
 
 72  
     /**
 73  
      * Create Atom Tree and maintain open channel to raf, should only be used if will continue
 74  
      * to use raf after this call, you will have to close raf yourself.
 75  
      *
 76  
      * @param raf
 77  
      * @param closeOnExit to keep randomfileaccess open, only used when randomaccessfile already being used
 78  
      * @throws IOException
 79  
      * @throws CannotReadException
 80  
      */
 81  
     public Mp4AtomTree(RandomAccessFile raf, boolean closeOnExit) throws IOException, CannotReadException
 82  220
     {
 83  220
         buildTree(raf, closeOnExit);
 84  218
     }
 85  
 
 86  
     /**
 87  
      * Build a tree of the atoms in the file
 88  
      *
 89  
      * @param raf
 90  
      * @param closeExit false to keep randomfileacces open, only used when randomaccessfile already being used
 91  
      * @return
 92  
      * @throws java.io.IOException
 93  
      */
 94  
     public DefaultTreeModel buildTree(RandomAccessFile raf, boolean closeExit) throws IOException, CannotReadException
 95  
     {
 96  234
         FileChannel fc = null;
 97  
         try
 98  
         {
 99  234
             fc = raf.getChannel();
 100  
 
 101  
             //make sure at start of file
 102  234
             fc.position(0);
 103  
 
 104  
             //Build up map of nodes
 105  234
             rootNode = new DefaultMutableTreeNode();
 106  234
             dataTree = new DefaultTreeModel(rootNode);
 107  
 
 108  
             //Iterate though all the top level Nodes
 109  234
             ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
 110  1137
             while (fc.position() < fc.size())
 111  
             {
 112  910
                  Mp4BoxHeader boxHeader = new Mp4BoxHeader();
 113  910
                 headerBuffer.clear();          
 114  910
                 fc.read(headerBuffer);
 115  910
                 headerBuffer.rewind();
 116  
 
 117  
                 try
 118  
                 {
 119  910
                     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
                         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  904
                 }
 138  
                                    
 139  904
                 boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
 140  904
                 DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 141  
 
 142  
                 //Go down moov
 143  904
                 if (boxHeader.getId().equals(Mp4NotMetaFieldKey.MOOV.getFieldName()))
 144  
                 {
 145  234
                     moovNode    = newAtom;
 146  234
                     moovHeader  = boxHeader;
 147  
 
 148  234
                     long filePosStart = fc.position();
 149  234
                     moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
 150  234
                     fc.read(moovBuffer);
 151  234
                     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  234
                     buildChildrenOfNode(moovBuffer, newAtom);
 161  233
                     fc.position(filePosStart);
 162  233
                 }
 163  670
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 164  
                 {
 165  
                     //Might be multiple in different locations
 166  203
                     freeNodes.add(newAtom);
 167  
                 }
 168  467
                 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  233
                     mdatNode = newAtom;
 178  233
                     mdatNodes.add(newAtom);
 179  
                 }
 180  903
                 rootNode.add(newAtom);
 181  903
                 fc.position(fc.position() + boxHeader.getDataLength());
 182  903
             }
 183  232
             return dataTree;
 184  
         }
 185  
         finally
 186  
         {
 187  234
             if (closeExit)
 188  
             {
 189  14
                 fc.close();
 190  
             }
 191  
         }
 192  
     }
 193  
 
 194  
     /**
 195  
      * Display atom tree
 196  
      */
 197  
     @SuppressWarnings("unchecked")
 198  
     public void printAtomTree()
 199  
     {
 200  14
         Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
 201  
         DefaultMutableTreeNode nextNode;
 202  677
         while (e.hasMoreElements())
 203  
         {
 204  663
             nextNode = e.nextElement();
 205  663
             Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
 206  663
             if (header != null)
 207  
             {
 208  649
                 String tabbing = new String();
 209  2725
                 for (int i = 1; i < nextNode.getLevel(); i++)
 210  
                 {
 211  2076
                     tabbing += "\t";
 212  
                 }
 213  
 
 214  649
                 if(header instanceof NullPadding)
 215  
                 {
 216  1
                     System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));                                        
 217  
                 }
 218  
                 else
 219  
                 {
 220  648
                     System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
 221  
                 }
 222  
             }
 223  663
         }
 224  14
     }
 225  
 
 226  
     /**
 227  
      *
 228  
      * @param moovBuffer
 229  
      * @param parentNode
 230  
      * @throws IOException
 231  
      * @throws CannotReadException
 232  
      */
 233  
     public void buildChildrenOfNode(ByteBuffer moovBuffer, DefaultMutableTreeNode parentNode) throws IOException, CannotReadException
 234  
     {
 235  
         Mp4BoxHeader boxHeader;
 236  
 
 237  
         //Preprocessing for nodes that contain data before their children atoms
 238  2143
         Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();
 239  
 
 240  
         //We set the buffers position back to this after processing the chikdren
 241  2143
         int justAfterHeaderPos = moovBuffer.position();
 242  
 
 243  
         //Preprocessing for meta that normally contains 4 data bytes, but doesnt whre found under trak atom
 244  
         //TODO is it always under TRAK dont really know the rule
 245  2143
         if (parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 246  
         {
 247  246
             Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
 248  246
             meta.processData();
 249  
 
 250  
             try
 251  
             {
 252  246
                 boxHeader = new Mp4BoxHeader(moovBuffer);
 253  
             }
 254  18
             catch(NullBoxIdException nbe)
 255  
             {
 256  
                 //It might be that the meta box didnt actually have any additional data after it so we adjust the buffer
 257  
                 //to be immediately after metabox and code can retry
 258  18
                 moovBuffer.position(moovBuffer.position()-Mp4MetaBox.FLAGS_LENGTH);
 259  
             }
 260  
             finally
 261  
             {
 262  
                 //Skip back last header cos this was only a test 
 263  246
                 moovBuffer.position(moovBuffer.position()-  Mp4BoxHeader.HEADER_LENGTH);
 264  246
             }
 265  
         }
 266  
 
 267  
         //Defines where to start looking for the first child node
 268  2143
         int startPos = moovBuffer.position();        
 269  12219
         while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH))
 270  
         {
 271  10079
             boxHeader = new Mp4BoxHeader(moovBuffer);
 272  10078
             if (boxHeader != null)
 273  
             {
 274  10078
                 boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
 275  10078
                 logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));
 276  
 
 277  10078
                 DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 278  10078
                 parentNode.add(newAtom);
 279  
 
 280  10078
                 if (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 281  
                 {
 282  228
                     udtaNode = newAtom;
 283  
                 }
 284  
                 //only interested in metaNode that is child of udta node
 285  9850
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 286  
                 {
 287  228
                     metaNode = newAtom;
 288  
                 }
 289  9622
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 290  
                 {
 291  246
                     hdlrWithinMetaNode = newAtom;
 292  
                 }
 293  9376
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName()))
 294  
                 {
 295  298
                     hdlrWithinMdiaNode = newAtom;
 296  
                 }
 297  9078
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.STCO.getFieldName()))
 298  
                 {
 299  298
                     if (stco == null)
 300  
                     {
 301  234
                         stco = new Mp4StcoBox(boxHeader, moovBuffer);
 302  234
                         stcoNode = newAtom;
 303  
                     }
 304  
                 }
 305  8780
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName()))
 306  
                 {
 307  243
                     DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNode.getParent();
 308  243
                     if(parent!=null)
 309  
                     {
 310  243
                         Mp4BoxHeader parentsParent = (Mp4BoxHeader)(parent).getUserObject();
 311  243
                         if(parentsParent!=null)
 312  
                         {
 313  243
                             if(parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentsParent.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 314  
                             {
 315  225
                                 ilstNode = newAtom;
 316  
                             }
 317  
                         }
 318  
                     }    
 319  243
                 }
 320  8537
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 321  
                 {
 322  
                     //Might be multiple in different locations
 323  114
                     freeNodes.add(newAtom);
 324  
                 }
 325  8423
                 else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName()))
 326  
                 {
 327  
                     //Might be multiple in different locations, although onely one shoud be audio track
 328  298
                     trakNodes.add(newAtom);
 329  
                 }
 330  
 
 331  
                 //For these atoms iterate down to build their children
 332  10078
                 if ((boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName())) ||
 333  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.MDIA.getFieldName())) ||
 334  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.MINF.getFieldName())) ||
 335  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.STBL.getFieldName())) ||
 336  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName())) ||
 337  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())) ||
 338  
                         (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName())))
 339  
                 {                
 340  1909
                     buildChildrenOfNode(moovBuffer, newAtom);
 341  
                 }
 342  
                 //Now  adjust buffer for the next atom header at this level
 343  10076
                 moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());
 344  
 
 345  10076
             }
 346  
         }
 347  2140
         moovBuffer.position(justAfterHeaderPos);
 348  2140
     }
 349  
 
 350  
 
 351  
     /**
 352  
      *
 353  
      * @return
 354  
      */
 355  
     public DefaultTreeModel getDataTree()
 356  
     {
 357  0
         return dataTree;
 358  
     }
 359  
 
 360  
 
 361  
     /**
 362  
      *
 363  
      * @return
 364  
      */
 365  
     public DefaultMutableTreeNode getMoovNode()
 366  
     {
 367  52
         return moovNode;
 368  
     }
 369  
 
 370  
     /**
 371  
      *
 372  
      * @return
 373  
      */
 374  
     public DefaultMutableTreeNode getStcoNode()
 375  
     {
 376  0
         return stcoNode;
 377  
     }
 378  
 
 379  
     /**
 380  
      *
 381  
      * @return
 382  
      */
 383  
     public DefaultMutableTreeNode getIlstNode()
 384  
     {
 385  52
         return ilstNode;
 386  
     }
 387  
 
 388  
     /**
 389  
      *
 390  
      * @param node
 391  
      * @return
 392  
      */
 393  
     public Mp4BoxHeader getBoxHeader(DefaultMutableTreeNode node)
 394  
     {
 395  520
         if (node == null)
 396  
         {
 397  8
             return null;
 398  
         }
 399  512
         return (Mp4BoxHeader) node.getUserObject();
 400  
     }
 401  
 
 402  
     /**
 403  
      *
 404  
      * @return
 405  
      */
 406  
     public DefaultMutableTreeNode getMdatNode()
 407  
     {
 408  104
         return mdatNode;
 409  
     }
 410  
 
 411  
     /**
 412  
      *
 413  
      * @return
 414  
      */
 415  
     public DefaultMutableTreeNode getUdtaNode()
 416  
     {
 417  104
         return udtaNode;
 418  
     }
 419  
 
 420  
     /**
 421  
      *
 422  
      * @return
 423  
      */
 424  
     public DefaultMutableTreeNode getMetaNode()
 425  
     {
 426  104
         return metaNode;
 427  
     }
 428  
 
 429  
     /**
 430  
      *
 431  
      * @return
 432  
      */
 433  
     public DefaultMutableTreeNode getHdlrWithinMetaNode()
 434  
     {
 435  52
         return hdlrWithinMetaNode;
 436  
     }
 437  
 
 438  
     /**
 439  
      *
 440  
      * @return
 441  
      */
 442  
     public DefaultMutableTreeNode getHdlrWithinMdiaNode()
 443  
     {
 444  52
         return hdlrWithinMdiaNode;
 445  
     }
 446  
 
 447  
     /**
 448  
      *
 449  
      * @return
 450  
      */
 451  
     public List<DefaultMutableTreeNode> getFreeNodes()
 452  
     {
 453  104
         return freeNodes;
 454  
     }
 455  
 
 456  
     /**
 457  
      *
 458  
      * @return
 459  
      */
 460  
     public List<DefaultMutableTreeNode> getTrakNodes()
 461  
     {
 462  52
         return trakNodes;
 463  
     }
 464  
 
 465  
     /**
 466  
      *
 467  
      * @return
 468  
      */
 469  
     public Mp4StcoBox getStco()
 470  
     {
 471  104
         return stco;
 472  
     }
 473  
 
 474  
     /**
 475  
      *
 476  
      * @return
 477  
      */
 478  
     public ByteBuffer getMoovBuffer()
 479  
     {
 480  52
         return moovBuffer;
 481  
     }
 482  
 
 483  
     /**
 484  
      *
 485  
      * @return
 486  
      */
 487  
     public Mp4BoxHeader getMoovHeader()
 488  
     {
 489  0
         return moovHeader;
 490  
     }
 491  
 }