~ubuntu-branches/ubuntu/trusty/libjaudiotagger-java/trusty

« back to all changes in this revision

Viewing changes to src/org/jaudiotagger/audio/mp4/Mp4AtomTree.java

  • Committer: Bazaar Package Importer
  • Author(s): Damien Raude-Morvan
  • Date: 2011-04-28 23:52:43 UTC
  • mfrom: (3.1.4 sid)
  • Revision ID: james.westby@ubuntu.com-20110428235243-pzalvw6lncis3ukf
Tags: 2.0.3-1
* d/control: Drop Depends on default-jre per Debian Java Policy as its
  a library package.
* d/watch: Fix to directly monitor SVN tags.
* Switch to 3.0 (quilt) format.
* Bump Standards-Version to 3.9.2 (no changes needed).

Show diffs side-by-side

added added

removed removed

Lines of Context:
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 List<DefaultMutableTreeNode> freeNodes = new ArrayList<DefaultMutableTreeNode>();
49
 
    private List<DefaultMutableTreeNode> mdatNodes = new ArrayList<DefaultMutableTreeNode>();
50
 
    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
 
    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
 
    {
68
 
        buildTree(raf, true);
69
 
    }
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
 
    {
82
 
        buildTree(raf, closeOnExit);
83
 
    }
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
 
        FileChannel fc = null;
97
 
        try
98
 
        {
99
 
            fc = raf.getChannel();
100
 
 
101
 
            //make sure at start of file
102
 
            fc.position(0);
103
 
 
104
 
            //Build up map of nodes
105
 
            rootNode = new DefaultMutableTreeNode();
106
 
            dataTree = new DefaultTreeModel(rootNode);
107
 
 
108
 
            //Iterate though all the top level Nodes
109
 
            ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
110
 
            while (fc.position() < fc.size())
111
 
            {
112
 
                 Mp4BoxHeader boxHeader = new Mp4BoxHeader();
113
 
                headerBuffer.clear();          
114
 
                fc.read(headerBuffer);
115
 
                headerBuffer.rewind();
116
 
 
117
 
                try
118
 
                {
119
 
                    boxHeader.update(headerBuffer);
120
 
                }
121
 
                catch(NullBoxIdException ne)
122
 
                {
123
 
                    //If we only get this error after all the expected data has been found we allow it
124
 
                    if(moovNode!=null&mdatNode!=null)
125
 
                    {
126
 
                        NullPadding np = new NullPadding(fc.position() - Mp4BoxHeader.HEADER_LENGTH,fc.size());
127
 
                        DefaultMutableTreeNode trailingPaddingNode = new DefaultMutableTreeNode(np);
128
 
                        rootNode.add(trailingPaddingNode);
129
 
                        logger.warning(ErrorMessage.NULL_PADDING_FOUND_AT_END_OF_MP4.getMsg(np.getFilePos()));
130
 
                        break;
131
 
                    }
132
 
                    else
133
 
                    {
134
 
                        //File appears invalid
135
 
                        throw ne;
136
 
                    }
137
 
                }
138
 
                                   
139
 
                boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
140
 
                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
141
 
 
142
 
                //Go down moov
143
 
                if (boxHeader.getId().equals(Mp4NotMetaFieldKey.MOOV.getFieldName()))
144
 
                {
145
 
                    moovNode    = newAtom;
146
 
                    moovHeader  = boxHeader;
147
 
 
148
 
                    long filePosStart = fc.position();
149
 
                    moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
150
 
                    fc.read(moovBuffer);
151
 
                    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
 
                    buildChildrenOfNode(moovBuffer, newAtom);
161
 
                    fc.position(filePosStart);
162
 
                }
163
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
164
 
                {
165
 
                    //Might be multiple in different locations
166
 
                    freeNodes.add(newAtom);
167
 
                }
168
 
                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
 
                    mdatNode = newAtom;
178
 
                    mdatNodes.add(newAtom);
179
 
                }
180
 
                rootNode.add(newAtom);
181
 
                fc.position(fc.position() + boxHeader.getDataLength());
182
 
            }
183
 
            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
 
            if(mdatNode==null)
190
 
            {
191
 
                throw new CannotReadException(ErrorMessage.MP4_CANNOT_FIND_AUDIO.getMsg());
192
 
            }
193
 
 
194
 
            if (closeExit)
195
 
            {
196
 
                fc.close();
197
 
            }
198
 
        }
199
 
    }
200
 
 
201
 
    /**
202
 
     * Display atom tree
203
 
     */
204
 
    @SuppressWarnings("unchecked")
205
 
    public void printAtomTree()
206
 
    {
207
 
        Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
208
 
        DefaultMutableTreeNode nextNode;
209
 
        while (e.hasMoreElements())
210
 
        {
211
 
            nextNode = e.nextElement();
212
 
            Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
213
 
            if (header != null)
214
 
            {
215
 
                String tabbing = "";
216
 
                for (int i = 1; i < nextNode.getLevel(); i++)
217
 
                {
218
 
                    tabbing += "\t";
219
 
                }
220
 
 
221
 
                if(header instanceof NullPadding)
222
 
                {
223
 
                    System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));                                        
224
 
                }
225
 
                else
226
 
                {
227
 
                    System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
228
 
                }
229
 
            }
230
 
        }
231
 
    }
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
 
        Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();
246
 
 
247
 
        //We set the buffers position back to this after processing the chikdren
248
 
        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
 
        if (parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
253
 
        {
254
 
            Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
255
 
            meta.processData();
256
 
 
257
 
            try
258
 
            {
259
 
                boxHeader = new Mp4BoxHeader(moovBuffer);
260
 
            }
261
 
            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
 
                moovBuffer.position(moovBuffer.position()-Mp4MetaBox.FLAGS_LENGTH);
266
 
            }
267
 
            finally
268
 
            {
269
 
                //Skip back last header cos this was only a test 
270
 
                moovBuffer.position(moovBuffer.position()-  Mp4BoxHeader.HEADER_LENGTH);
271
 
            }
272
 
        }
273
 
 
274
 
        //Defines where to start looking for the first child node
275
 
        int startPos = moovBuffer.position();        
276
 
        while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH))
277
 
        {
278
 
            boxHeader = new Mp4BoxHeader(moovBuffer);
279
 
            if (boxHeader != null)
280
 
            {
281
 
                boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
282
 
                logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));
283
 
 
284
 
                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
285
 
                parentNode.add(newAtom);
286
 
 
287
 
                if (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
288
 
                {
289
 
                    udtaNode = newAtom;
290
 
                }
291
 
                //only interested in metaNode that is child of udta node
292
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
293
 
                {
294
 
                    metaNode = newAtom;
295
 
                }
296
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
297
 
                {
298
 
                    hdlrWithinMetaNode = newAtom;
299
 
                }
300
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName()))
301
 
                {
302
 
                    hdlrWithinMdiaNode = newAtom;
303
 
                }
304
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.STCO.getFieldName()))
305
 
                {
306
 
                    if (stco == null)
307
 
                    {
308
 
                        stco = new Mp4StcoBox(boxHeader, moovBuffer);
309
 
                        stcoNode = newAtom;
310
 
                    }
311
 
                }
312
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName()))
313
 
                {
314
 
                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNode.getParent();
315
 
                    if(parent!=null)
316
 
                    {
317
 
                        Mp4BoxHeader parentsParent = (Mp4BoxHeader)(parent).getUserObject();
318
 
                        if(parentsParent!=null)
319
 
                        {
320
 
                            if(parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentsParent.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
321
 
                            {
322
 
                                ilstNode = newAtom;
323
 
                            }
324
 
                        }
325
 
                    }    
326
 
                }
327
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
328
 
                {
329
 
                    //Might be multiple in different locations
330
 
                    freeNodes.add(newAtom);
331
 
                }
332
 
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName()))
333
 
                {
334
 
                    //Might be multiple in different locations, although onely one shoud be audio track
335
 
                    trakNodes.add(newAtom);
336
 
                }
337
 
 
338
 
                //For these atoms iterate down to build their children
339
 
                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
 
                    buildChildrenOfNode(moovBuffer, newAtom);
348
 
                }
349
 
                //Now  adjust buffer for the next atom header at this level
350
 
                moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());
351
 
 
352
 
            }
353
 
        }
354
 
        moovBuffer.position(justAfterHeaderPos);
355
 
    }
356
 
 
357
 
 
358
 
    /**
359
 
     *
360
 
     * @return
361
 
     */
362
 
    public DefaultTreeModel getDataTree()
363
 
    {
364
 
        return dataTree;
365
 
    }
366
 
 
367
 
 
368
 
    /**
369
 
     *
370
 
     * @return
371
 
     */
372
 
    public DefaultMutableTreeNode getMoovNode()
373
 
    {
374
 
        return moovNode;
375
 
    }
376
 
 
377
 
    /**
378
 
     *
379
 
     * @return
380
 
     */
381
 
    public DefaultMutableTreeNode getStcoNode()
382
 
    {
383
 
        return stcoNode;
384
 
    }
385
 
 
386
 
    /**
387
 
     *
388
 
     * @return
389
 
     */
390
 
    public DefaultMutableTreeNode getIlstNode()
391
 
    {
392
 
        return ilstNode;
393
 
    }
394
 
 
395
 
    /**
396
 
     *
397
 
     * @param node
398
 
     * @return
399
 
     */
400
 
    public Mp4BoxHeader getBoxHeader(DefaultMutableTreeNode node)
401
 
    {
402
 
        if (node == null)
403
 
        {
404
 
            return null;
405
 
        }
406
 
        return (Mp4BoxHeader) node.getUserObject();
407
 
    }
408
 
 
409
 
    /**
410
 
     *
411
 
     * @return
412
 
     */
413
 
    public DefaultMutableTreeNode getMdatNode()
414
 
    {
415
 
        return mdatNode;
416
 
    }
417
 
 
418
 
    /**
419
 
     *
420
 
     * @return
421
 
     */
422
 
    public DefaultMutableTreeNode getUdtaNode()
423
 
    {
424
 
        return udtaNode;
425
 
    }
426
 
 
427
 
    /**
428
 
     *
429
 
     * @return
430
 
     */
431
 
    public DefaultMutableTreeNode getMetaNode()
432
 
    {
433
 
        return metaNode;
434
 
    }
435
 
 
436
 
    /**
437
 
     *
438
 
     * @return
439
 
     */
440
 
    public DefaultMutableTreeNode getHdlrWithinMetaNode()
441
 
    {
442
 
        return hdlrWithinMetaNode;
443
 
    }
444
 
 
445
 
    /**
446
 
     *
447
 
     * @return
448
 
     */
449
 
    public DefaultMutableTreeNode getHdlrWithinMdiaNode()
450
 
    {
451
 
        return hdlrWithinMdiaNode;
452
 
    }
453
 
 
454
 
    /**
455
 
     *
456
 
     * @return
457
 
     */
458
 
    public List<DefaultMutableTreeNode> getFreeNodes()
459
 
    {
460
 
        return freeNodes;
461
 
    }
462
 
 
463
 
    /**
464
 
     *
465
 
     * @return
466
 
     */
467
 
    public List<DefaultMutableTreeNode> getTrakNodes()
468
 
    {
469
 
        return trakNodes;
470
 
    }
471
 
 
472
 
    /**
473
 
     *
474
 
     * @return
475
 
     */
476
 
    public Mp4StcoBox getStco()
477
 
    {
478
 
        return stco;
479
 
    }
480
 
 
481
 
    /**
482
 
     *
483
 
     * @return
484
 
     */
485
 
    public ByteBuffer getMoovBuffer()
486
 
    {
487
 
        return moovBuffer;
488
 
    }
489
 
 
490
 
    /**
491
 
     *
492
 
     * @return
493
 
     */
494
 
    public Mp4BoxHeader getMoovHeader()
495
 
    {
496
 
        return moovHeader;
497
 
    }
498
 
}
 
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 List<DefaultMutableTreeNode> freeNodes = new ArrayList<DefaultMutableTreeNode>();
 
49
    private List<DefaultMutableTreeNode> mdatNodes = new ArrayList<DefaultMutableTreeNode>();
 
50
    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
    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
    {
 
68
        buildTree(raf, true);
 
69
    }
 
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
    {
 
82
        buildTree(raf, closeOnExit);
 
83
    }
 
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
        FileChannel fc = null;
 
97
        try
 
98
        {
 
99
            fc = raf.getChannel();
 
100
 
 
101
            //make sure at start of file
 
102
            fc.position(0);
 
103
 
 
104
            //Build up map of nodes
 
105
            rootNode = new DefaultMutableTreeNode();
 
106
            dataTree = new DefaultTreeModel(rootNode);
 
107
 
 
108
            //Iterate though all the top level Nodes
 
109
            ByteBuffer headerBuffer = ByteBuffer.allocate(Mp4BoxHeader.HEADER_LENGTH);
 
110
            while (fc.position() < fc.size())
 
111
            {
 
112
                 Mp4BoxHeader boxHeader = new Mp4BoxHeader();
 
113
                headerBuffer.clear();          
 
114
                fc.read(headerBuffer);
 
115
                headerBuffer.rewind();
 
116
 
 
117
                try
 
118
                {
 
119
                    boxHeader.update(headerBuffer);
 
120
                }
 
121
                catch(NullBoxIdException ne)
 
122
                {
 
123
                    //If we only get this error after all the expected data has been found we allow it
 
124
                    if(moovNode!=null&mdatNode!=null)
 
125
                    {
 
126
                        NullPadding np = new NullPadding(fc.position() - Mp4BoxHeader.HEADER_LENGTH,fc.size());
 
127
                        DefaultMutableTreeNode trailingPaddingNode = new DefaultMutableTreeNode(np);
 
128
                        rootNode.add(trailingPaddingNode);
 
129
                        logger.warning(ErrorMessage.NULL_PADDING_FOUND_AT_END_OF_MP4.getMsg(np.getFilePos()));
 
130
                        break;
 
131
                    }
 
132
                    else
 
133
                    {
 
134
                        //File appears invalid
 
135
                        throw ne;
 
136
                    }
 
137
                }
 
138
                                   
 
139
                boxHeader.setFilePos(fc.position() - Mp4BoxHeader.HEADER_LENGTH);
 
140
                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 
141
 
 
142
                //Go down moov
 
143
                if (boxHeader.getId().equals(Mp4NotMetaFieldKey.MOOV.getFieldName()))
 
144
                {
 
145
                    moovNode    = newAtom;
 
146
                    moovHeader  = boxHeader;
 
147
 
 
148
                    long filePosStart = fc.position();
 
149
                    moovBuffer = ByteBuffer.allocate(boxHeader.getDataLength());
 
150
                    fc.read(moovBuffer);
 
151
                    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
                    buildChildrenOfNode(moovBuffer, newAtom);
 
161
                    fc.position(filePosStart);
 
162
                }
 
163
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 
164
                {
 
165
                    //Might be multiple in different locations
 
166
                    freeNodes.add(newAtom);
 
167
                }
 
168
                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
                    mdatNode = newAtom;
 
178
                    mdatNodes.add(newAtom);
 
179
                }
 
180
                rootNode.add(newAtom);
 
181
                fc.position(fc.position() + boxHeader.getDataLength());
 
182
            }
 
183
            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
            if(mdatNode==null)
 
190
            {
 
191
                throw new CannotReadException(ErrorMessage.MP4_CANNOT_FIND_AUDIO.getMsg());
 
192
            }
 
193
 
 
194
            if (closeExit)
 
195
            {
 
196
                fc.close();
 
197
            }
 
198
        }
 
199
    }
 
200
 
 
201
    /**
 
202
     * Display atom tree
 
203
     */
 
204
    @SuppressWarnings("unchecked")
 
205
    public void printAtomTree()
 
206
    {
 
207
        Enumeration<DefaultMutableTreeNode> e = rootNode.preorderEnumeration();
 
208
        DefaultMutableTreeNode nextNode;
 
209
        while (e.hasMoreElements())
 
210
        {
 
211
            nextNode = e.nextElement();
 
212
            Mp4BoxHeader header = (Mp4BoxHeader) nextNode.getUserObject();
 
213
            if (header != null)
 
214
            {
 
215
                String tabbing = "";
 
216
                for (int i = 1; i < nextNode.getLevel(); i++)
 
217
                {
 
218
                    tabbing += "\t";
 
219
                }
 
220
 
 
221
                if(header instanceof NullPadding)
 
222
                {
 
223
                    System.out.println(tabbing + "Null pad " + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));                                        
 
224
                }
 
225
                else
 
226
                {
 
227
                    System.out.println(tabbing + "Atom " + header.getId() + " @ " + header.getFilePos() + " of size:" + header.getLength() + " ,ends @ " + (header.getFilePos() + header.getLength()));
 
228
                }
 
229
            }
 
230
        }
 
231
    }
 
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
        Mp4BoxHeader parentBoxHeader = (Mp4BoxHeader) parentNode.getUserObject();
 
246
 
 
247
        //We set the buffers position back to this after processing the chikdren
 
248
        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
        if (parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 
253
        {
 
254
            Mp4MetaBox meta = new Mp4MetaBox(parentBoxHeader, moovBuffer);
 
255
            meta.processData();
 
256
 
 
257
            try
 
258
            {
 
259
                boxHeader = new Mp4BoxHeader(moovBuffer);
 
260
            }
 
261
            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
                moovBuffer.position(moovBuffer.position()-Mp4MetaBox.FLAGS_LENGTH);
 
266
            }
 
267
            finally
 
268
            {
 
269
                //Skip back last header cos this was only a test 
 
270
                moovBuffer.position(moovBuffer.position()-  Mp4BoxHeader.HEADER_LENGTH);
 
271
            }
 
272
        }
 
273
 
 
274
        //Defines where to start looking for the first child node
 
275
        int startPos = moovBuffer.position();        
 
276
        while (moovBuffer.position() < ((startPos + parentBoxHeader.getDataLength()) - Mp4BoxHeader.HEADER_LENGTH))
 
277
        {
 
278
            boxHeader = new Mp4BoxHeader(moovBuffer);
 
279
            if (boxHeader != null)
 
280
            {
 
281
                boxHeader.setFilePos(moovHeader.getFilePos() + moovBuffer.position());
 
282
                logger.finest("Atom " + boxHeader.getId() + " @ " + boxHeader.getFilePos() + " of size:" + boxHeader.getLength() + " ,ends @ " + (boxHeader.getFilePos() + boxHeader.getLength()));
 
283
 
 
284
                DefaultMutableTreeNode newAtom = new DefaultMutableTreeNode(boxHeader);
 
285
                parentNode.add(newAtom);
 
286
 
 
287
                if (boxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 
288
                {
 
289
                    udtaNode = newAtom;
 
290
                }
 
291
                //only interested in metaNode that is child of udta node
 
292
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 
293
                {
 
294
                    metaNode = newAtom;
 
295
                }
 
296
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName())&&parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName()))
 
297
                {
 
298
                    hdlrWithinMetaNode = newAtom;
 
299
                }
 
300
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.HDLR.getFieldName()))
 
301
                {
 
302
                    hdlrWithinMdiaNode = newAtom;
 
303
                }
 
304
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.STCO.getFieldName()))
 
305
                {
 
306
                    if (stco == null)
 
307
                    {
 
308
                        stco = new Mp4StcoBox(boxHeader, moovBuffer);
 
309
                        stcoNode = newAtom;
 
310
                    }
 
311
                }
 
312
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName()))
 
313
                {
 
314
                    DefaultMutableTreeNode parent = (DefaultMutableTreeNode)parentNode.getParent();
 
315
                    if(parent!=null)
 
316
                    {
 
317
                        Mp4BoxHeader parentsParent = (Mp4BoxHeader)(parent).getUserObject();
 
318
                        if(parentsParent!=null)
 
319
                        {
 
320
                            if(parentBoxHeader.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())&&parentsParent.getId().equals(Mp4NotMetaFieldKey.UDTA.getFieldName()))
 
321
                            {
 
322
                                ilstNode = newAtom;
 
323
                            }
 
324
                        }
 
325
                    }    
 
326
                }
 
327
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.FREE.getFieldName()))
 
328
                {
 
329
                    //Might be multiple in different locations
 
330
                    freeNodes.add(newAtom);
 
331
                }
 
332
                else if (boxHeader.getId().equals(Mp4NotMetaFieldKey.TRAK.getFieldName()))
 
333
                {
 
334
                    //Might be multiple in different locations, although onely one shoud be audio track
 
335
                    trakNodes.add(newAtom);
 
336
                }
 
337
 
 
338
                //For these atoms iterate down to build their children
 
339
                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
                    buildChildrenOfNode(moovBuffer, newAtom);
 
348
                }
 
349
                //Now  adjust buffer for the next atom header at this level
 
350
                moovBuffer.position(moovBuffer.position() + boxHeader.getDataLength());
 
351
 
 
352
            }
 
353
        }
 
354
        moovBuffer.position(justAfterHeaderPos);
 
355
    }
 
356
 
 
357
 
 
358
    /**
 
359
     *
 
360
     * @return
 
361
     */
 
362
    public DefaultTreeModel getDataTree()
 
363
    {
 
364
        return dataTree;
 
365
    }
 
366
 
 
367
 
 
368
    /**
 
369
     *
 
370
     * @return
 
371
     */
 
372
    public DefaultMutableTreeNode getMoovNode()
 
373
    {
 
374
        return moovNode;
 
375
    }
 
376
 
 
377
    /**
 
378
     *
 
379
     * @return
 
380
     */
 
381
    public DefaultMutableTreeNode getStcoNode()
 
382
    {
 
383
        return stcoNode;
 
384
    }
 
385
 
 
386
    /**
 
387
     *
 
388
     * @return
 
389
     */
 
390
    public DefaultMutableTreeNode getIlstNode()
 
391
    {
 
392
        return ilstNode;
 
393
    }
 
394
 
 
395
    /**
 
396
     *
 
397
     * @param node
 
398
     * @return
 
399
     */
 
400
    public Mp4BoxHeader getBoxHeader(DefaultMutableTreeNode node)
 
401
    {
 
402
        if (node == null)
 
403
        {
 
404
            return null;
 
405
        }
 
406
        return (Mp4BoxHeader) node.getUserObject();
 
407
    }
 
408
 
 
409
    /**
 
410
     *
 
411
     * @return
 
412
     */
 
413
    public DefaultMutableTreeNode getMdatNode()
 
414
    {
 
415
        return mdatNode;
 
416
    }
 
417
 
 
418
    /**
 
419
     *
 
420
     * @return
 
421
     */
 
422
    public DefaultMutableTreeNode getUdtaNode()
 
423
    {
 
424
        return udtaNode;
 
425
    }
 
426
 
 
427
    /**
 
428
     *
 
429
     * @return
 
430
     */
 
431
    public DefaultMutableTreeNode getMetaNode()
 
432
    {
 
433
        return metaNode;
 
434
    }
 
435
 
 
436
    /**
 
437
     *
 
438
     * @return
 
439
     */
 
440
    public DefaultMutableTreeNode getHdlrWithinMetaNode()
 
441
    {
 
442
        return hdlrWithinMetaNode;
 
443
    }
 
444
 
 
445
    /**
 
446
     *
 
447
     * @return
 
448
     */
 
449
    public DefaultMutableTreeNode getHdlrWithinMdiaNode()
 
450
    {
 
451
        return hdlrWithinMdiaNode;
 
452
    }
 
453
 
 
454
    /**
 
455
     *
 
456
     * @return
 
457
     */
 
458
    public List<DefaultMutableTreeNode> getFreeNodes()
 
459
    {
 
460
        return freeNodes;
 
461
    }
 
462
 
 
463
    /**
 
464
     *
 
465
     * @return
 
466
     */
 
467
    public List<DefaultMutableTreeNode> getTrakNodes()
 
468
    {
 
469
        return trakNodes;
 
470
    }
 
471
 
 
472
    /**
 
473
     *
 
474
     * @return
 
475
     */
 
476
    public Mp4StcoBox getStco()
 
477
    {
 
478
        return stco;
 
479
    }
 
480
 
 
481
    /**
 
482
     *
 
483
     * @return
 
484
     */
 
485
    public ByteBuffer getMoovBuffer()
 
486
    {
 
487
        return moovBuffer;
 
488
    }
 
489
 
 
490
    /**
 
491
     *
 
492
     * @return
 
493
     */
 
494
    public Mp4BoxHeader getMoovHeader()
 
495
    {
 
496
        return moovHeader;
 
497
    }
 
498
}