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