Coverage Report - org.jaudiotagger.audio.mp4.Mp4TagWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
Mp4TagWriter
92%
255/275
84%
76/90
10.667
 
 1  
 /*
 2  
  * Entagged Audio Tag library
 3  
  * Copyright (c) 2003-2005 RaphaĆ«l Slinckx <raphael@slinckx.net>
 4  
  * 
 5  
  * This library is free software; you can redistribute it and/or
 6  
  * modify it under the terms of the GNU Lesser General Public
 7  
  * License as published by the Free Software Foundation; either
 8  
  * version 2.1 of the License, or (at your option) any later version.
 9  
  *  
 10  
  * This library is distributed in the hope that it will be useful,
 11  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
  * Lesser General Public License for more details.
 14  
  * 
 15  
  * You should have received a copy of the GNU Lesser General Public
 16  
  * License along with this library; if not, write to the Free Software
 17  
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 18  
  */
 19  
 package org.jaudiotagger.audio.mp4;
 20  
 
 21  
 import org.jaudiotagger.audio.exceptions.CannotReadException;
 22  
 import org.jaudiotagger.audio.exceptions.CannotWriteException;
 23  
 import org.jaudiotagger.audio.mp4.atom.*;
 24  
 import org.jaudiotagger.logging.ErrorMessage;
 25  
 import org.jaudiotagger.tag.Tag;
 26  
 import org.jaudiotagger.tag.mp4.Mp4Tag;
 27  
 import org.jaudiotagger.tag.mp4.Mp4TagCreator;
 28  
 
 29  
 import javax.swing.tree.DefaultMutableTreeNode;
 30  
 import java.io.IOException;
 31  
 import java.io.RandomAccessFile;
 32  
 import java.nio.ByteBuffer;
 33  
 import java.nio.channels.FileChannel;
 34  
 import java.util.logging.Logger;
 35  
 
 36  
 
 37  
 /**
 38  
  * Writes metadata from mp4, the metadata tags are held under the ilst atom as shown below
 39  
  * <p/>
 40  
  * <p/>
 41  
  * When writing changes the size of all the atoms upto ilst has to be recalculated, then if the size of
 42  
  * the metadata is increased the size of the free atom (below meta) should be reduced accordingly or vice versa.
 43  
  * If the size of the metadata has increased by more than the size of the free atom then the size of meta, udta
 44  
  * and moov should be recalculated and the top level free atom reduced accordingly
 45  
  * If there is not enough space even if using both of the free atoms, then the mdat atom has to be shifted down
 46  
  * accordingly to make space, and the stco atom has to have its offsets to mdat chunks table adjusted accordingly.
 47  
  *
 48  
  * Exceptions are that the meta/udta/ilst do not currently exist, in which udta/meta/ilst are created. Note it is valid
 49  
  * to have meta/ilst without udta but this is less common so we always try to write files according to the Apple/iTunes
 50  
  * specification. *
 51  
  * <p/>
 52  
  * <p/>
 53  
  * <pre>
 54  
  * |--- ftyp
 55  
  * |--- moov
 56  
  * |......|
 57  
  * |......|----- mvdh
 58  
  * |......|----- trak
 59  
  * |......|----- udta
 60  
  * |..............|
 61  
  * |..............|-- meta
 62  
  * |....................|
 63  
  * |....................|-- hdlr
 64  
  * |....................|-- ilst
 65  
  * |....................|.. ..|
 66  
  * |....................|.....|---- @nam (Optional for each metadatafield)
 67  
  * |....................|.....|.......|-- data
 68  
  * |....................|.....|....... ecetera
 69  
  * |....................|.....|---- ---- (Optional for reverse dns field)
 70  
  * |....................|.............|-- mean
 71  
  * |....................|.............|-- name
 72  
  * |....................|.............|-- data
 73  
  * |....................|................ ecetere
 74  
  * |....................|-- free
 75  
  * |--- free
 76  
  * |--- mdat
 77  
  * </pre>
 78  
  */
 79  16
 public class Mp4TagWriter
 80  
 {
 81  
     // Logger Object
 82  4
     public static Logger logger = Logger.getLogger("org.jaudiotagger.tag.mp4");
 83  
 
 84  16
     private Mp4TagCreator tc = new Mp4TagCreator();
 85  
 
 86  
 
 87  
     /**
 88  
      * Replace the ilst metadata
 89  
      * <p/>
 90  
      * Because it is the same size as the original data nothing else has to be modified
 91  
      *
 92  
      * @param rawIlstData
 93  
      * @param oldIlstSize
 94  
      * @param startIstWithinFile
 95  
      * @param fileReadChannel
 96  
      * @param fileWriteChannel
 97  
      * @throws CannotWriteException
 98  
      * @throws IOException
 99  
      */
 100  
     private void writeMetadataSameSize(ByteBuffer rawIlstData, long oldIlstSize, long startIstWithinFile, FileChannel fileReadChannel, FileChannel fileWriteChannel) throws CannotWriteException, IOException
 101  
     {
 102  24
         fileReadChannel.position(0);
 103  24
         fileWriteChannel.transferFrom(fileReadChannel, 0, startIstWithinFile);
 104  24
         fileWriteChannel.position(startIstWithinFile);
 105  24
         fileWriteChannel.write(rawIlstData);
 106  24
         fileReadChannel.position(startIstWithinFile + oldIlstSize);
 107  24
         fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 108  24
     }
 109  
 
 110  
     /**
 111  
      * When the size of the metadata has changed and it cant be compensated for by free atom
 112  
      * we have to adjust the size of the size field upto the moovheader level for the udta atom and
 113  
      * its child meta atom.
 114  
      *
 115  
      * @param moovHeader
 116  
      * @param moovBuffer
 117  
      * @param sizeAdjustment can be negative or positive     *
 118  
      * @param udtaHeader
 119  
      * @param metaHeader
 120  
      * @return
 121  
      * @throws java.io.IOException
 122  
      */
 123  
     private void adjustSizeOfMoovHeader
 124  
             (Mp4BoxHeader moovHeader,
 125  
              ByteBuffer moovBuffer,
 126  
              int sizeAdjustment,
 127  
              Mp4BoxHeader udtaHeader,
 128  
              Mp4BoxHeader metaHeader) throws IOException
 129  
     {
 130  
         //Adjust moov header size, adjusts the underlying buffer
 131  77
         moovHeader.setLength(moovHeader.getLength() + sizeAdjustment);
 132  
 
 133  
         //Edit the fields in moovBuffer (note moovbuffer doesnt include header)
 134  77
         if(udtaHeader!=null)
 135  
         {
 136  
             //Write the updated udta atom header to moov buffer
 137  77
             udtaHeader.setLength(udtaHeader.getLength() + sizeAdjustment);
 138  77
             moovBuffer.position((int)(udtaHeader.getFilePos() - moovHeader.getFilePos() - Mp4BoxHeader.HEADER_LENGTH));
 139  77
             moovBuffer.put(udtaHeader.getHeaderData());
 140  
         }
 141  
 
 142  77
         if(metaHeader!=null)
 143  
         {
 144  
             //Write the updated udta atom header to moov buffer
 145  76
             metaHeader.setLength(metaHeader.getLength() + sizeAdjustment);
 146  76
             moovBuffer.position((int)(metaHeader.getFilePos() - moovHeader.getFilePos()- Mp4BoxHeader.HEADER_LENGTH));
 147  76
             moovBuffer.put(metaHeader.getHeaderData());
 148  
         }
 149  77
     }
 150  
 
 151  
 
 152  
     private void createMetadataAtoms
 153  
            (Mp4BoxHeader moovHeader,
 154  
              ByteBuffer moovBuffer,
 155  
              int sizeAdjustment,
 156  
              Mp4BoxHeader udtaHeader,
 157  
              Mp4BoxHeader metaHeader) throws IOException
 158  
     {
 159  
         //Adjust moov header size
 160  0
         moovHeader.setLength(moovHeader.getLength() + sizeAdjustment);
 161  
 
 162  0
     }
 163  
 
 164  
     /**
 165  
      * Write tag to rafTemp file
 166  
      *
 167  
      * @param tag     tag data
 168  
      * @param raf     current file
 169  
      * @param rafTemp temporary file for writing
 170  
      * @throws CannotWriteException
 171  
      * @throws IOException
 172  
      */
 173  
     public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotWriteException, IOException
 174  
     {
 175  183
         logger.info("Started writing tag data");
 176  
         //Go through every field constructing the data that will appear starting from ilst box
 177  183
         ByteBuffer rawIlstData = tc.convert(tag);
 178  183
         rawIlstData.rewind();
 179  
 
 180  
         //Read Channel for reading from old file
 181  183
         FileChannel fileReadChannel = raf.getChannel();
 182  
 
 183  
         //Write channel for writing to new file
 184  183
         FileChannel fileWriteChannel = rafTemp.getChannel();
 185  
 
 186  
         //Reference to a Box Header
 187  
         Mp4BoxHeader boxHeader;
 188  
 
 189  
         //TODO we shouldn't need all these variables, and some are very badly named - used by new and old methods
 190  183
         int oldIlstSize = 0;
 191  
         int relativeIlstposition;
 192  
         int relativeIlstEndPosition;
 193  
         int startIlstWithinFile;
 194  
         int newIlstSize;
 195  
         int oldMetaLevelFreeAtomSize;
 196  
         long extraDataSize;
 197  183
         int level1SearchPosition = 0;
 198  
         int topLevelFreePosition;
 199  
         int topLevelFreeSize;
 200  
         boolean topLevelFreeAtomComesBeforeMdatAtom;
 201  
         Mp4BoxHeader topLevelFreeHeader;
 202  
 
 203  
         Mp4AtomTree atomTree;
 204  
 
 205  
         //Build tree to check file is readable
 206  
         try
 207  
         {
 208  183
             atomTree = new Mp4AtomTree(raf, false);
 209  
         }
 210  0
         catch (CannotReadException cre)
 211  
         {
 212  0
             throw new CannotWriteException(cre.getMessage());
 213  183
         }
 214  
 
 215  
         //Moov Box header
 216  183
         Mp4BoxHeader moovHeader = atomTree.getBoxHeader(atomTree.getMoovNode());
 217  183
         long positionWithinFileAfterFindingMoovHeader = moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH;
 218  
 
 219  183
         Mp4StcoBox      stco = atomTree.getStco();
 220  183
         Mp4BoxHeader ilstHeader         = atomTree.getBoxHeader(atomTree.getIlstNode());
 221  183
         Mp4BoxHeader udtaHeader         = atomTree.getBoxHeader(atomTree.getUdtaNode());
 222  183
         Mp4BoxHeader metaHeader         = atomTree.getBoxHeader(atomTree.getMetaNode());
 223  183
         Mp4BoxHeader hdlrMetaHeader     = atomTree.getBoxHeader(atomTree.getHdlrWithinMetaNode());
 224  183
         Mp4BoxHeader hdlrMdiaHeader     = atomTree.getBoxHeader(atomTree.getHdlrWithinMdiaNode());
 225  183
         Mp4BoxHeader mdatHeader         = atomTree.getBoxHeader(atomTree.getMdatNode());
 226  
 
 227  
         //Unable to find audio so no chance of saving any changes
 228  183
         if(mdatHeader==null)
 229  
         {
 230  0
             throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_CANNOT_FIND_AUDIO.getMsg());    
 231  
         }
 232  
 
 233  183
         Mp4BoxHeader trakHeader         = (Mp4BoxHeader)atomTree.getTrakNodes().get(0).getUserObject();
 234  
 
 235  183
         ByteBuffer   moovBuffer         = atomTree.getMoovBuffer();
 236  
 
 237  
         //Work out if we/what kind of metadata hierachy we currently have in the file
 238  
         //Udta
 239  183
         if(udtaHeader !=null)
 240  
         {
 241  
             //Meta
 242  175
             if(metaHeader != null)
 243  
             {
 244  
                 //ilst - record where ilst is,and where it ends
 245  174
                 if (ilstHeader != null)
 246  
                 {
 247  170
                     oldIlstSize = ilstHeader.getLength();
 248  
 
 249  
                     //Relative means relative to moov buffer after moov header
 250  170
                     startIlstWithinFile = (int) ilstHeader.getFilePos();
 251  170
                     relativeIlstposition = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
 252  170
                     relativeIlstEndPosition = relativeIlstposition + ilstHeader.getLength();
 253  
                 }
 254  
                 else
 255  
                 {
 256  
                     //Place ilst immediately after existing hdlr atom
 257  4
                     if(hdlrMetaHeader!=null)
 258  
                     {
 259  4
                         startIlstWithinFile      = (int) hdlrMetaHeader.getFilePos() + hdlrMetaHeader.getLength();
 260  4
                         relativeIlstposition    = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
 261  4
                         relativeIlstEndPosition = relativeIlstposition;
 262  
                     }
 263  
                     //Place ilst after data fields in meta atom
 264  
                     //TODO Should we create a hdlr atom
 265  
                     else
 266  
                     {
 267  0
                         startIlstWithinFile = (int) metaHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH + Mp4MetaBox.FLAGS_LENGTH;
 268  0
                         relativeIlstposition = (int) ((startIlstWithinFile) - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
 269  0
                         relativeIlstEndPosition = relativeIlstposition;
 270  
                     }
 271  
                 }
 272  
             }
 273  
             else
 274  
             {
 275  
                 //There no ilst or meta header so we set to position where it would be if it existed
 276  1
                 relativeIlstposition = moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH;
 277  1
                 relativeIlstEndPosition = relativeIlstposition ;
 278  1
                 startIlstWithinFile = (int)(moovHeader.getFilePos() + moovHeader.getLength());
 279  
             }
 280  
         }
 281  
         //There no udta header so we are going to create a new structure, but we have to be aware that there might be
 282  
         //an existing meta box structure in which case we preserve it but rigth are new structure before it.
 283  
         else
 284  
         {
 285  
             //Create new structure just after the end of the trak atom
 286  8
             if(metaHeader != null)
 287  
             {
 288  0
                 startIlstWithinFile = (int)trakHeader.getFilePos() + trakHeader.getLength();
 289  0
                 relativeIlstposition = (int) (startIlstWithinFile - (moovHeader.getFilePos() + Mp4BoxHeader.HEADER_LENGTH));
 290  0
                 relativeIlstEndPosition = relativeIlstposition + ilstHeader.getLength();
 291  
             }
 292  
             else
 293  
             {
 294  
                 //There no udta,ilst or meta header so we set to position where it would be if it existed
 295  8
                 relativeIlstposition = moovHeader.getLength() - Mp4BoxHeader.HEADER_LENGTH;
 296  8
                 relativeIlstEndPosition = relativeIlstposition;
 297  8
                 startIlstWithinFile = (int)(moovHeader.getFilePos() + moovHeader.getLength());
 298  
             }
 299  
         }
 300  183
         newIlstSize = rawIlstData.limit();
 301  
 
 302  
         //Level 4 - Free
 303  183
         oldMetaLevelFreeAtomSize = 0;
 304  183
         extraDataSize = 0;
 305  183
         for (DefaultMutableTreeNode freeNode : atomTree.getFreeNodes())
 306  
         {
 307  155
             DefaultMutableTreeNode parentNode   = (DefaultMutableTreeNode) freeNode.getParent();
 308  155
             DefaultMutableTreeNode brotherNode =  freeNode.getPreviousSibling();
 309  155
             if (!parentNode.isRoot())
 310  
             {
 311  74
                 Mp4BoxHeader header = ((Mp4BoxHeader) parentNode.getUserObject());
 312  74
                 Mp4BoxHeader freeHeader = ((Mp4BoxHeader) freeNode.getUserObject());
 313  
 
 314  
                 //We are only interested in free atoms at this level if they come after the ilst node
 315  74
                 if(brotherNode!=null)
 316  
                 {
 317  74
                     Mp4BoxHeader brotherHeader = ((Mp4BoxHeader) brotherNode.getUserObject());
 318  
 
 319  74
                     if (header.getId().equals(Mp4NotMetaFieldKey.META.getFieldName())
 320  
                             &&
 321  
                         brotherHeader.getId().equals(Mp4NotMetaFieldKey.ILST.getFieldName()))
 322  
                     {
 323  73
                         oldMetaLevelFreeAtomSize = freeHeader.getLength();
 324  
 
 325  
                         //Is there anything else here that needs to be preserved such as uuid atoms
 326  73
                         extraDataSize = moovHeader.getFilePos() + moovHeader.getLength() - (freeHeader.getFilePos() + freeHeader.getLength());
 327  73
                         break;
 328  
                     }
 329  
                 }
 330  
             }
 331  82
         }
 332  
         //If no free atom, still check for unexpected items within meta, after ilst
 333  
         //TODO this logic probably incomplete
 334  183
         if (oldMetaLevelFreeAtomSize == 0)
 335  
         {
 336  110
             extraDataSize = moovHeader.getDataLength() - relativeIlstEndPosition;
 337  
         }
 338  
 
 339  
         //Level-1 free atom
 340  183
         level1SearchPosition = 0;
 341  183
         topLevelFreePosition = 0;
 342  183
         topLevelFreeSize = 0;
 343  183
         topLevelFreeAtomComesBeforeMdatAtom = true;
 344  183
         for (DefaultMutableTreeNode freeNode : atomTree.getFreeNodes())
 345  
         {
 346  210
             DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) freeNode.getParent();
 347  210
             if (parentNode.isRoot())
 348  
             {
 349  138
                 topLevelFreeHeader = ((Mp4BoxHeader) freeNode.getUserObject());
 350  138
                 topLevelFreeSize = topLevelFreeHeader.getLength();
 351  138
                 topLevelFreePosition = (int) topLevelFreeHeader.getFilePos();
 352  138
                 level1SearchPosition = topLevelFreePosition;
 353  138
                 break;
 354  
             }
 355  72
         }
 356  
 
 357  183
         if (topLevelFreeSize > 0)
 358  
         {
 359  138
             if (topLevelFreePosition > mdatHeader.getFilePos())
 360  
             {
 361  9
                 topLevelFreeAtomComesBeforeMdatAtom = false;
 362  9
                 level1SearchPosition = (int) mdatHeader.getFilePos();
 363  
             }
 364  
         }
 365  
         else
 366  
         {
 367  45
             topLevelFreePosition = (int) mdatHeader.getFilePos();
 368  45
             level1SearchPosition = topLevelFreePosition;
 369  
         }
 370  
 
 371  
 
 372  183
         logger.info("Read header successfully ready for writing");
 373  
         //The easiest option since no difference in the size of the metadata so all we have to do is
 374  
         //create a new file identical to first file but with replaced metadata
 375  183
         if (oldIlstSize == newIlstSize)
 376  
         {
 377  24
             logger.info("Writing:Option 1:Same Size");
 378  24
             writeMetadataSameSize(rawIlstData, oldIlstSize, startIlstWithinFile, fileReadChannel, fileWriteChannel);
 379  
         }
 380  
         //.. we just need to increase the size of the free atom below the meta atom, and replace the metadata
 381  
         //no other changes neccessary and total file size remains the same
 382  159
         else if (oldIlstSize > newIlstSize)
 383  
         {
 384  
             //Create an amended freeBaos atom and write it if it previously existed
 385  66
             if (oldMetaLevelFreeAtomSize > 0)
 386  
             {
 387  20
                 logger.info("Writing:Option 2:Smaller Size have free atom:" + oldIlstSize + ":" + newIlstSize);
 388  20
                 fileReadChannel.position(0);
 389  20
                 fileWriteChannel.transferFrom(fileReadChannel, 0, startIlstWithinFile);
 390  20
                 fileWriteChannel.position(startIlstWithinFile);
 391  20
                 fileWriteChannel.write(rawIlstData);
 392  20
                 fileReadChannel.position(startIlstWithinFile + oldIlstSize);
 393  
 
 394  20
                 int newFreeSize = oldMetaLevelFreeAtomSize + (oldIlstSize - newIlstSize);
 395  20
                 Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize - Mp4BoxHeader.HEADER_LENGTH);
 396  20
                 fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
 397  20
                 fileWriteChannel.write(newFreeBox.getData());
 398  
 
 399  
                 //Skip over the read channel old free atom
 400  20
                 fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
 401  
 
 402  
                 //Now write the rest of the file which won't have changed
 403  20
                 fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 404  20
             }
 405  
             //No free atom we need to create a new one or adjust top level free atom
 406  
             else
 407  
             {
 408  46
                 int newFreeSize = (oldIlstSize - newIlstSize) - Mp4BoxHeader.HEADER_LENGTH;
 409  
                 //We need to create a new one, so dont have to adjust all the headers but only works if the size
 410  
                 //of tags has decreased by more 8 characters so there is enough room for the free boxes header we take
 411  
                 //into account size of new header in calculating size of box
 412  46
                 if (newFreeSize > 0)
 413  
                 {
 414  26
                     logger.info("Writing:Option 3:Smaller Size can create free atom");
 415  26
                     fileReadChannel.position(0);
 416  26
                     fileWriteChannel.transferFrom(fileReadChannel, 0, startIlstWithinFile);
 417  26
                     fileWriteChannel.position(startIlstWithinFile);
 418  26
                     fileWriteChannel.write(rawIlstData);
 419  26
                     fileReadChannel.position(startIlstWithinFile + oldIlstSize);
 420  
 
 421  
                     //Create new free box
 422  26
                     Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize);
 423  26
                     fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
 424  26
                     fileWriteChannel.write(newFreeBox.getData());
 425  
 
 426  
                     //Now write the rest of the file which wont have changed
 427  26
                     fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 428  26
                 }
 429  
                 //Ok everything in this bit of tree has to be recalculated because eight or less bytes smaller
 430  
                 else
 431  
                 {
 432  20
                     logger.info("Writing:Option 4:Smaller Size <=8 cannot create free atoms");
 433  
 
 434  
                     //Size will be this amount smaller
 435  20
                     int sizeReducedBy = oldIlstSize - newIlstSize;
 436  
 
 437  
                     //Write stuff before Moov (ftyp)
 438  20
                     fileReadChannel.position(0);
 439  20
                     fileWriteChannel.transferFrom(fileReadChannel, 0, moovHeader.getFilePos());
 440  20
                     fileWriteChannel.position(moovHeader.getFilePos());
 441  
 
 442  
                     //Edit stco atom within moov header,  we need to adjust offsets by the amount mdat is going to be shifted
 443  
                     //unless mdat is at start of file
 444  20
                     if (mdatHeader.getFilePos() > moovHeader.getFilePos())
 445  
                     {
 446  16
                         stco.adjustOffsets(-sizeReducedBy);
 447  
                     }
 448  
 
 449  
                     //Edit and rewrite the Moov,Udta and Ilst header in moov buffer
 450  20
                     adjustSizeOfMoovHeader(moovHeader, moovBuffer, -sizeReducedBy,udtaHeader,metaHeader);
 451  20
                     fileWriteChannel.write(moovHeader.getHeaderData());
 452  20
                     moovBuffer.rewind();
 453  20
                     moovBuffer.limit(relativeIlstposition);
 454  20
                     fileWriteChannel.write(moovBuffer);
 455  
 
 456  
                     //Now write ilst data
 457  20
                     fileWriteChannel.write(rawIlstData);
 458  
 
 459  20
                     fileReadChannel.position(startIlstWithinFile + oldIlstSize);
 460  
 
 461  
                     //Writes any extra info such as uuid fields at the end of the meta atom after the ilst atom
 462  20
                     if (extraDataSize > 0)
 463  
                     {
 464  12
                         fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), extraDataSize);
 465  12
                         fileWriteChannel.position(fileWriteChannel.position() + extraDataSize);
 466  
                     }
 467  
 
 468  
                     //Now write the rest of the file which won't have changed
 469  20
                     fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 470  
 
 471  
                 }
 472  46
             }
 473  
         }
 474  
         //Size of metadata has increased, the most complex situation, more atoms affected
 475  
         else
 476  
         {
 477  93
             int additionalSpaceRequiredForMetadata = newIlstSize - oldIlstSize;
 478  
 
 479  
             //We can fit the metadata in under the meta item just by using some of the padding available in the free
 480  
             //atom under the meta atom need to take of the side of free header otherwise might end up with
 481  
             //solution where can fit in data, but cant fit in free atom header
 482  93
             if (additionalSpaceRequiredForMetadata <= (oldMetaLevelFreeAtomSize - Mp4BoxHeader.HEADER_LENGTH))
 483  
             {
 484  28
                 int newFreeSize = oldMetaLevelFreeAtomSize - (additionalSpaceRequiredForMetadata);
 485  28
                 logger.info("Writing:Option 5;Larger Size can use meta free atom need extra:" + newFreeSize + "bytes");
 486  
 
 487  28
                 fileReadChannel.position(0);
 488  28
                 fileWriteChannel.transferFrom(fileReadChannel, 0, startIlstWithinFile);
 489  28
                 fileWriteChannel.position(startIlstWithinFile);
 490  28
                 fileWriteChannel.write(rawIlstData);
 491  28
                 fileReadChannel.position(startIlstWithinFile + oldIlstSize);
 492  
 
 493  
                 //Create an amended smaller freeBaos atom and write it to file
 494  28
                 Mp4FreeBox newFreeBox = new Mp4FreeBox(newFreeSize - Mp4BoxHeader.HEADER_LENGTH);
 495  28
                 fileWriteChannel.write(newFreeBox.getHeader().getHeaderData());
 496  28
                 fileWriteChannel.write(newFreeBox.getData());
 497  
 
 498  
                 //Skip over the read channel old free atom
 499  28
                 fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
 500  
 
 501  
                 //Now write the rest of the file which won't have changed
 502  28
                 fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 503  28
             }
 504  
             //There is not enough padding in the metadata free atom anyway
 505  
             //Size meta needs to be increased by (if not writing a free atom)
 506  
             //Special Case this could actually be negative (upto -8)if is actually enough space but would
 507  
             //not be able to write free atom properly, it doesnt matter the parent atoms would still
 508  
             //need their sizes adjusted.
 509  
             else
 510  
             {
 511  65
                 int additionalMetaSizeThatWontFitWithinMetaAtom = additionalSpaceRequiredForMetadata - (oldMetaLevelFreeAtomSize);
 512  
 
 513  
                 //Write stuff before Moov (ftyp)
 514  65
                 fileReadChannel.position(0);
 515  65
                 fileWriteChannel.transferFrom(fileReadChannel, 0, positionWithinFileAfterFindingMoovHeader - Mp4BoxHeader.HEADER_LENGTH);
 516  65
                 fileWriteChannel.position(positionWithinFileAfterFindingMoovHeader - Mp4BoxHeader.HEADER_LENGTH);
 517  
 
 518  65
                 if(udtaHeader==null)
 519  
                 {
 520  8
                     logger.info("Writing:Option 5.1;No udta atom");
 521  
 
 522  8
                     Mp4HdlrBox hdlrBox = Mp4HdlrBox.createiTunesStyleHdlrBox();
 523  8
                     Mp4MetaBox metaBox = Mp4MetaBox.createiTunesStyleMetaBox(hdlrBox.getHeader().getLength() +  rawIlstData.limit());
 524  8
                     udtaHeader = new Mp4BoxHeader(Mp4NotMetaFieldKey.UDTA.getFieldName());
 525  8
                     udtaHeader.setLength(Mp4BoxHeader.HEADER_LENGTH +metaBox.getHeader().getLength());
 526  
 
 527  8
                     additionalMetaSizeThatWontFitWithinMetaAtom =
 528  
                         additionalMetaSizeThatWontFitWithinMetaAtom + (udtaHeader.getLength()  - rawIlstData.limit());
 529  
 
 530  
                     //Edit stco atom within moov header, if the free atom comes after mdat OR
 531  
                     //(there is not enough space in the top level free atom
 532  
                     //or special case of matching exactly the free atom plus header)
 533  8
                     if ((!topLevelFreeAtomComesBeforeMdatAtom)
 534  
                            || ((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH < additionalMetaSizeThatWontFitWithinMetaAtom)
 535  
                            && (topLevelFreeSize != additionalMetaSizeThatWontFitWithinMetaAtom)))
 536  
                     {
 537  
                        //We dont bother using the top level free atom coz not big enough anyway, we need to adjust offsets
 538  
                        //by the amount mdat is going to be shifted
 539  4
                        if (mdatHeader.getFilePos() > moovHeader.getFilePos())
 540  
                        {
 541  4
                            stco.adjustOffsets(additionalMetaSizeThatWontFitWithinMetaAtom);
 542  
                        }
 543  
                     }
 544  
 
 545  
                     //Edit and rewrite the Moov header
 546  8
                     moovHeader.setLength(moovHeader.getLength() + additionalMetaSizeThatWontFitWithinMetaAtom);
 547  
                    
 548  
                     //Adjust moov header size to allow a udta,meta and hdlr atom, we have already accounted for ilst data
 549  
                     //moovHeader.setLength(moovHeader.getLength() + udtaHeader.getLength()  - rawIlstData.limit());
 550  
                     
 551  8
                     fileWriteChannel.write(moovHeader.getHeaderData());
 552  8
                     moovBuffer.rewind();
 553  8
                     moovBuffer.limit(relativeIlstposition);
 554  8
                     fileWriteChannel.write(moovBuffer);
 555  
 
 556  
                     //Write new atoms required for holding metadata in itunes format
 557  8
                     fileWriteChannel.write(udtaHeader.getHeaderData());
 558  8
                     fileWriteChannel.write(metaBox.getHeader().getHeaderData());                                        
 559  8
                     fileWriteChannel.write(metaBox.getData());
 560  8
                     fileWriteChannel.write(hdlrBox.getHeader().getHeaderData());
 561  8
                     fileWriteChannel.write(hdlrBox.getData());
 562  8
                 }
 563  
                 else
 564  
                 {
 565  57
                     logger.info("Writing:Option 5.2;udta atom exists");
 566  
                     
 567  
                      //Edit stco atom within moov header, if the free atom comes after mdat OR
 568  
                     //(there is not enough space in the top level free atom
 569  
                     //or special case of matching exactly the free atom plus header)
 570  57
                     if ((!topLevelFreeAtomComesBeforeMdatAtom)
 571  
                             || ((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH < additionalMetaSizeThatWontFitWithinMetaAtom)
 572  
                             && (topLevelFreeSize != additionalMetaSizeThatWontFitWithinMetaAtom)))
 573  
                     {
 574  
                         //We dont bother using the top level free atom coz not big enough anyway, we need to adjust offsets
 575  
                         //by the amount mdat is going to be shifted
 576  32
                         if (mdatHeader.getFilePos() > moovHeader.getFilePos())
 577  
                         {
 578  23
                             stco.adjustOffsets(additionalMetaSizeThatWontFitWithinMetaAtom);
 579  
                         }
 580  
                     }
 581  
 
 582  
                     //Edit and rewrite the Moov header
 583  57
                     adjustSizeOfMoovHeader(moovHeader, moovBuffer, additionalMetaSizeThatWontFitWithinMetaAtom,udtaHeader,metaHeader);
 584  
 
 585  57
                     fileWriteChannel.write(moovHeader.getHeaderData());
 586  
 
 587  
                     //Now write from this edited buffer up until ilst atom
 588  57
                     moovBuffer.rewind();
 589  57
                     moovBuffer.limit(relativeIlstposition);
 590  57
                     fileWriteChannel.write(moovBuffer);
 591  
                 }
 592  
 
 593  
                 //Now write ilst data
 594  65
                 fileWriteChannel.write(rawIlstData);
 595  
 
 596  
                 //Skip over the read channel old meta level free atom because now used up
 597  65
                 fileReadChannel.position(startIlstWithinFile + oldIlstSize);
 598  65
                 fileReadChannel.position(fileReadChannel.position() + oldMetaLevelFreeAtomSize);
 599  
 
 600  
                 //Writes any extra info such as uuid fields at the end of the meta atom after the ilst atom
 601  65
                 if (extraDataSize > 0)
 602  
                 {
 603  40
                     fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), extraDataSize);
 604  40
                     fileWriteChannel.position(fileWriteChannel.position() + extraDataSize);
 605  
                 }
 606  
 
 607  
                 //fileReadChannel.position(topLevelFreePosition);
 608  
                 //fileReadChannel.position(level1SearchPosition);
 609  
 
 610  
 
 611  
                 //If we have top level free atom that comes before mdat we might be able to use it but only if
 612  
                 //the free atom actually come after the the metadata
 613  65
                 if (topLevelFreeAtomComesBeforeMdatAtom&&(topLevelFreePosition>startIlstWithinFile))
 614  
                 {
 615  
                     //If the shift is less than the space available in this second free atom data size we should
 616  
                     //minimize the free atom accordingly (then we don't have to update stco atom)
 617  
                     //note could be a double negative as additionalMetaSizeThatWontFitWithinMetaAtom could be -1 to -8 but thats ok stills works
 618  
                     //ok
 619  44
                     if (topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH >= additionalMetaSizeThatWontFitWithinMetaAtom)
 620  
                     {
 621  16
                         logger.info("Writing:Option 6;Larger Size can use top free atom");
 622  16
                         Mp4FreeBox freeBox = new Mp4FreeBox((topLevelFreeSize - Mp4BoxHeader.HEADER_LENGTH) - additionalMetaSizeThatWontFitWithinMetaAtom);
 623  16
                         fileWriteChannel.write(freeBox.getHeader().getHeaderData());
 624  16
                         fileWriteChannel.write(freeBox.getData());
 625  
 
 626  
                         //Skip over the read channel old free atom
 627  16
                         fileReadChannel.position(fileReadChannel.position() + topLevelFreeSize);
 628  
 
 629  
                         //Write Mdat
 630  16
                         fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 631  16
                     }
 632  
                     //If the space required is identical to total size of the free space (inc header)
 633  
                     //we could just remove the header
 634  28
                     else if (topLevelFreeSize == additionalMetaSizeThatWontFitWithinMetaAtom)
 635  
                     {
 636  8
                         logger.info("Writing:Option 7;Larger Size uses top free atom including header");
 637  
                         //Skip over the read channel old free atom
 638  8
                         fileReadChannel.position(fileReadChannel.position() + topLevelFreeSize);
 639  
 
 640  
                         //Write Mdat
 641  8
                         fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 642  
                     }
 643  
                     //Mdat is going to have to move anyway, so keep free atom as is and write it and mdat
 644  
                     //(have already updated stco above)
 645  
                     else
 646  
                     {
 647  20
                         logger.info("Writing:Option 8;Larger Size cannot use top free atom");
 648  20
                         fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 649  
                     }
 650  
                 }
 651  
                 else
 652  
                 {
 653  21
                     logger.info("Writing:Option 9;Top Level Free comes after Mdat or before Metadata so cant use it");
 654  21
                     fileWriteChannel.transferFrom(fileReadChannel, fileWriteChannel.position(), fileReadChannel.size() - fileReadChannel.position());
 655  
                 }
 656  
             }
 657  
         }
 658  
         //Close all channels to original file
 659  183
         fileReadChannel.close();
 660  183
         raf.close();
 661  
 
 662  183
         checkFileWrittenCorrectly(rafTemp,mdatHeader,fileWriteChannel,stco);
 663  182
     }
 664  
 
 665  
     /**
 666  
      * Check File Written Correctly
 667  
      * 
 668  
      * @param rafTemp
 669  
      * @param mdatHeader
 670  
      * @param fileWriteChannel
 671  
      * @param stco
 672  
      * @throws CannotWriteException
 673  
      * @throws IOException
 674  
      */
 675  
     private void checkFileWrittenCorrectly(RandomAccessFile rafTemp,Mp4BoxHeader mdatHeader,FileChannel fileWriteChannel, Mp4StcoBox stco)
 676  
         throws CannotWriteException,IOException
 677  
     {
 678  
 
 679  183
         logger.info("Checking file has been written correctly");
 680  
 
 681  
         try
 682  
         {
 683  
             //Create a tree from the new file
 684  
             Mp4AtomTree newAtomTree;
 685  183
             newAtomTree = new Mp4AtomTree(rafTemp, false);
 686  
 
 687  
             //Check we still have audio data file, and check length
 688  183
             Mp4BoxHeader newMdatHeader = newAtomTree.getBoxHeader(newAtomTree.getMdatNode());
 689  183
             if (newMdatHeader == null)
 690  
             {
 691  0
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_NO_DATA.getMsg());
 692  
             }
 693  183
             if (newMdatHeader.getLength() != mdatHeader.getLength())
 694  
             {
 695  0
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_DATA_CORRUPT.getMsg());
 696  
             }
 697  
 
 698  
             //Should always have udta atom after writing to file
 699  183
             Mp4BoxHeader newUdtaHeader = newAtomTree.getBoxHeader(newAtomTree.getUdtaNode());
 700  183
             if (newUdtaHeader == null)
 701  
             {
 702  0
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_NO_TAG_DATA.getMsg());
 703  
             }
 704  
 
 705  
             //Should always have meta atom after writing to file
 706  183
             Mp4BoxHeader newMetaHeader = newAtomTree.getBoxHeader(newAtomTree.getMetaNode());
 707  183
             if (newMetaHeader == null)
 708  
             {
 709  1
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_NO_TAG_DATA.getMsg());
 710  
             }
 711  
 
 712  
             //Check offsets are correct, may not match exactly in original file so just want to make
 713  
             //sure that the discrepancy if any is preserved
 714  182
             Mp4StcoBox newStco = newAtomTree.getStco();
 715  
 
 716  182
             logger.finer("stco:Original First Offset" + stco.getFirstOffSet());
 717  182
             logger.finer("stco:Original Diff" + (int) (stco.getFirstOffSet() - mdatHeader.getFilePos()));
 718  182
             logger.finer("stco:Original Mdat Pos" + mdatHeader.getFilePos());
 719  182
             logger.finer("stco:New First Offset" + newStco.getFirstOffSet());
 720  182
             logger.finer("stco:New Diff" + (int) ((newStco.getFirstOffSet() - newMdatHeader.getFilePos())));
 721  182
             logger.finer("stco:New Mdat Pos" + newMdatHeader.getFilePos());
 722  182
             int diff = (int) (stco.getFirstOffSet() - mdatHeader.getFilePos());
 723  182
             if ((newStco.getFirstOffSet() - newMdatHeader.getFilePos()) != diff)
 724  
             {
 725  0
                 int discrepancy = (int)((newStco.getFirstOffSet() - newMdatHeader.getFilePos()) - diff);
 726  0
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED_INCORRECT_OFFSETS.getMsg(discrepancy));
 727  
             }
 728  182
         }
 729  1
         catch (Exception e)
 730  
         {
 731  1
             if (e instanceof CannotWriteException)
 732  
             {
 733  1
                 throw (CannotWriteException) e;
 734  
             }
 735  
             else
 736  
             {
 737  0
                 e.printStackTrace();
 738  0
                 throw new CannotWriteException(ErrorMessage.MP4_CHANGES_TO_FILE_FAILED.getMsg() + ":" + e.getMessage());
 739  
             }
 740  
         }
 741  
         finally
 742  
         {
 743  
             //Close references to new file
 744  1
             rafTemp.close();
 745  183
             fileWriteChannel.close();
 746  182
         }
 747  182
         logger.info("File has been written correctly");
 748  182
     }
 749  
     /**
 750  
      * Delete the tag
 751  
      * <p/>
 752  
      * <p/>
 753  
      * <p>This is achieved by writing an empty ilst atom
 754  
      *
 755  
      * @param raf
 756  
      * @param rafTemp
 757  
      * @throws IOException
 758  
      */
 759  
     public void delete(RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException
 760  
     {
 761  8
         Mp4Tag tag = new Mp4Tag();
 762  
 
 763  
         try
 764  
         {
 765  8
             write(tag, raf, rafTemp);
 766  
         }
 767  0
         catch (CannotWriteException cwe)
 768  
         {
 769  0
             throw new IOException(cwe.getMessage());
 770  8
         }
 771  8
     }
 772  
 }