Coverage Report - org.jaudiotagger.audio.ogg.OggVorbisTagWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
OggVorbisTagWriter
88%
235/266
83%
62/74
4.538
 
 1  
 /*
 2  
  * Entagged Audio Tag library
 3  
  * Copyright (c) 2003-2005 RaphaĆ«l Slinckx <raphael@slinckx.net>
 4  
  * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de>
 5  
  * 
 6  
  * This library is free software; you can redistribute it and/or
 7  
  * modify it under the terms of the GNU Lesser General Public
 8  
  * License as published by the Free Software Foundation; either
 9  
  * version 2.1 of the License, or (at your option) any later version.
 10  
  *  
 11  
  * This library is distributed in the hope that it will be useful,
 12  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 14  
  * Lesser General Public License for more details.
 15  
  * 
 16  
  * You should have received a copy of the GNU Lesser General Public
 17  
  * License along with this library; if not, write to the Free Software
 18  
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 19  
  */
 20  
 package org.jaudiotagger.audio.ogg;
 21  
 
 22  
 import org.jaudiotagger.audio.exceptions.CannotReadException;
 23  
 import org.jaudiotagger.audio.exceptions.CannotWriteException;
 24  
 import org.jaudiotagger.audio.ogg.util.OggCRCFactory;
 25  
 import org.jaudiotagger.audio.ogg.util.OggPageHeader;
 26  
 import org.jaudiotagger.tag.Tag;
 27  
 import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag;
 28  
 
 29  
 import java.io.ByteArrayOutputStream;
 30  
 import java.io.IOException;
 31  
 import java.io.RandomAccessFile;
 32  
 import java.nio.ByteBuffer;
 33  
 import java.nio.ByteOrder;
 34  
 import java.util.List;
 35  
 import java.util.logging.Logger;
 36  
 
 37  
 /**
 38  
  * Write Vorbis Tag within an ogg
 39  
  * <p/>
 40  
  * VorbisComment holds the tag information within an ogg file
 41  
  */
 42  4
 public class OggVorbisTagWriter
 43  
 {
 44  
     // Logger Object
 45  4
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.ogg");
 46  
 
 47  4
     private OggVorbisCommentTagCreator tc = new OggVorbisCommentTagCreator();
 48  4
     private OggVorbisTagReader reader = new OggVorbisTagReader();
 49  
 
 50  
     public void delete(RandomAccessFile raf, RandomAccessFile tempRaf) throws IOException, CannotReadException, CannotWriteException
 51  
     {
 52  
         try
 53  
         {
 54  8
             reader.read(raf);
 55  
         }
 56  0
         catch (CannotReadException e)
 57  
         {
 58  0
             write(VorbisCommentTag.createNewTag(), raf, tempRaf);
 59  0
             return;
 60  8
         }
 61  
 
 62  8
         VorbisCommentTag emptyTag = VorbisCommentTag.createNewTag();
 63  
 
 64  
         //Go back to start of file
 65  8
         raf.seek(0);
 66  8
         write(emptyTag, raf, tempRaf);
 67  8
     }
 68  
 
 69  
     public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotReadException, CannotWriteException, IOException
 70  
     {
 71  89
         logger.info("Starting to write file:");
 72  
 
 73  
         //1st Page:Identification Header
 74  89
         logger.fine("Read 1st Page:identificationHeader:");
 75  89
         OggPageHeader pageHeader = OggPageHeader.read(raf);
 76  89
         raf.seek(0);
 77  
 
 78  
         //Write 1st page (unchanged) and place writer pointer at end of data
 79  89
         rafTemp.getChannel().transferFrom(raf.getChannel(), 0, pageHeader.getPageLength() + OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
 80  89
         rafTemp.skipBytes(pageHeader.getPageLength() + OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length);
 81  89
         logger.fine("Written identificationHeader:");
 82  
 
 83  
         //2nd page:Comment and Setup if there is enough room, may also (although not normally) contain audio frames
 84  89
         OggPageHeader secondPageHeader = OggPageHeader.read(raf);
 85  
 
 86  
         //2nd Page:Store the end of Header
 87  89
         long secondPageHeaderEndPos = raf.getFilePointer();
 88  89
         logger.fine("Read 2nd Page:comment and setup and possibly audio:Header finishes at file position:" + secondPageHeaderEndPos);
 89  
 
 90  
         //Get header sizes
 91  89
         raf.seek(0);
 92  89
         OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes = reader.readOggVorbisHeaderSizes(raf);
 93  
 
 94  
         //Convert the OggVorbisComment header to raw packet data
 95  89
         ByteBuffer newComment = tc.convert(tag);
 96  
 
 97  
         //Compute new comment length(this may need to be spread over multiple pages)
 98  89
         int newCommentLength = newComment.capacity();
 99  
 
 100  
         //Calculate new size of new 2nd page
 101  89
         int newSecondPageDataLength = vorbisHeaderSizes.getSetupHeaderSize() + newCommentLength + vorbisHeaderSizes.getExtraPacketDataSize();
 102  89
         logger.fine("Old 2nd Page no of packets: " + secondPageHeader.getPacketList().size());
 103  89
         logger.fine("Old 2nd Page size: " + secondPageHeader.getPageLength());
 104  89
         logger.fine("Old last packet incomplete: " + secondPageHeader.isLastPacketIncomplete());
 105  89
         logger.fine("Setup Header Size: " + vorbisHeaderSizes.getSetupHeaderSize());
 106  89
         logger.fine("Extra Packets: " + vorbisHeaderSizes.getExtraPacketList().size());
 107  89
         logger.fine("Extra Packet Data Size: " + vorbisHeaderSizes.getExtraPacketDataSize());
 108  89
         logger.fine("Old comment: " + vorbisHeaderSizes.getCommentHeaderSize());
 109  89
         logger.fine("New comment: " + newCommentLength);
 110  89
         logger.fine("New Page Data Size: " + newSecondPageDataLength);
 111  
         //Second Page containing new vorbis, setup and possibly some extra packets can fit on one page
 112  89
         if (isCommentAndSetupHeaderFitsOnASinglePage(newCommentLength, vorbisHeaderSizes.getSetupHeaderSize(), vorbisHeaderSizes.getExtraPacketList()))
 113  
         {
 114  
             //And if comment and setup header originally fitted on both, the length of the 2nd
 115  
             //page must be less than maximum size allowed
 116  
             //AND
 117  
             //there must be two packets with last being complete because they may have
 118  
             //elected to split the setup over multiple pages instead of using up whole page - (as long
 119  
             //as the last lacing value is 255 they can do this)
 120  
             //   OR
 121  
             //There are more than the packets in which case have complete setup header and some audio packets
 122  
             //we dont care if the last audio packet is split on next page as long as we preserve it
 123  69
             if ((secondPageHeader.getPageLength() < OggPageHeader.MAXIMUM_PAGE_DATA_SIZE) && (((secondPageHeader.getPacketList().size() == 2) && (!secondPageHeader.isLastPacketIncomplete())) || (secondPageHeader.getPacketList().size() > 2)))
 124  
             {
 125  50
                 logger.fine("Header and Setup remain on single page:");
 126  50
                 replaceSecondPageOnly(vorbisHeaderSizes, newCommentLength, newSecondPageDataLength, secondPageHeader, newComment, secondPageHeaderEndPos, raf, rafTemp);
 127  
             }
 128  
             //Original 2nd page spanned multiple pages so more work to do
 129  
             else
 130  
             {
 131  19
                 logger.fine("Header and Setup now on single page:");
 132  19
                 replaceSecondPageAndRenumberPageSeqs(vorbisHeaderSizes, newCommentLength, newSecondPageDataLength, secondPageHeader, newComment, raf, rafTemp);
 133  
             }
 134  
         }
 135  
         //Bit more complicated, have to create more than one new page and renumber subsequent audio
 136  
         else
 137  
         {
 138  20
             logger.fine("Header and Setup with shift audio:");
 139  20
             replacePagesAndRenumberPageSeqs(vorbisHeaderSizes, newCommentLength, secondPageHeader, newComment, raf, rafTemp);
 140  
         }
 141  89
     }
 142  
 
 143  
     /**
 144  
      * Calculate checkSum over the Page
 145  
      *
 146  
      * @param page
 147  
      */
 148  
     private void calculateChecksumOverPage(ByteBuffer page)
 149  
     {           
 150  
         //CRC should be zero before calculating it
 151  14330
         page.putInt(OggPageHeader.FIELD_PAGE_CHECKSUM_POS, 0);
 152  
 
 153  
         //Compute CRC over the  page  //TODO shouldnt really use array();
 154  14330
         byte[] crc = OggCRCFactory.computeCRC(page.array());
 155  71650
         for (int i = 0; i < crc.length; i++)
 156  
         {
 157  57320
             page.put(OggPageHeader.FIELD_PAGE_CHECKSUM_POS + i, crc[i]);
 158  
         }
 159  
 
 160  
         //Rewind to start of Page
 161  14330
         page.rewind();
 162  14330
     }
 163  
 
 164  
     /**
 165  
      * Create a second Page, and add comment header to it, but page is incomplete may want to add addition header and need to calculate CRC
 166  
      *
 167  
      * @param vorbisHeaderSizes
 168  
      * @param newCommentLength
 169  
      * @param newSecondPageLength
 170  
      * @param secondPageHeader
 171  
      * @param newComment
 172  
      * @return
 173  
      * @throws IOException
 174  
      */
 175  
     private ByteBuffer startCreateBasicSecondPage(
 176  
             OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes,
 177  
             int newCommentLength,
 178  
             int newSecondPageLength,
 179  
             OggPageHeader secondPageHeader,
 180  
             ByteBuffer newComment) throws IOException
 181  
     {
 182  80
         logger.fine("WriteOgg Type 1");
 183  80
         byte[] segmentTable = createSegmentTable(newCommentLength, vorbisHeaderSizes.getSetupHeaderSize(), vorbisHeaderSizes.getExtraPacketList());
 184  80
         int newSecondPageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length;
 185  80
         logger.fine("New second page header length:" + newSecondPageHeaderLength);
 186  80
         logger.fine("No of segments:" + segmentTable.length);
 187  
 
 188  80
         ByteBuffer secondPageBuffer = ByteBuffer.allocate(newSecondPageLength + newSecondPageHeaderLength);
 189  80
         secondPageBuffer.order(ByteOrder.LITTLE_ENDIAN);
 190  
 
 191  
         //Build the new 2nd page header, can mostly be taken from the original upto the segment length OggS capture
 192  80
         secondPageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1);
 193  
 
 194  
         //Number of Page Segments
 195  80
         secondPageBuffer.put((byte) segmentTable.length);
 196  
 
 197  
         //Page segment table
 198  4882
         for (byte aSegmentTable : segmentTable)
 199  
         {
 200  4802
             secondPageBuffer.put(aSegmentTable);
 201  
         }
 202  
 
 203  
         //Add New VorbisComment
 204  80
         secondPageBuffer.put(newComment);
 205  80
         return secondPageBuffer;
 206  
 
 207  
     }
 208  
 
 209  
 
 210  
     /**
 211  
      * Usually can use this method, previously comment and setup header all fit on page 2
 212  
      * and they still do, so just replace this page. And copy further pages as is.
 213  
      *
 214  
      * @param vorbisHeaderSizes
 215  
      * @param newCommentLength
 216  
      * @param newSecondPageLength
 217  
      * @param secondPageHeader
 218  
      * @param newComment
 219  
      * @param secondPageHeaderEndPos
 220  
      * @param raf
 221  
      * @param rafTemp
 222  
      * @throws IOException
 223  
      */
 224  
     private void replaceSecondPageOnly(
 225  
             OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes,
 226  
             int newCommentLength,
 227  
             int newSecondPageLength,
 228  
             OggPageHeader secondPageHeader,
 229  
             ByteBuffer newComment,
 230  
             long secondPageHeaderEndPos,
 231  
             RandomAccessFile raf,
 232  
             RandomAccessFile rafTemp) throws IOException
 233  
     {
 234  50
         logger.fine("WriteOgg Type 1");
 235  50
         ByteBuffer secondPageBuffer = startCreateBasicSecondPage(vorbisHeaderSizes, newCommentLength, newSecondPageLength, secondPageHeader, newComment);
 236  
 
 237  50
         raf.seek(secondPageHeaderEndPos);
 238  
         //Skip comment header
 239  50
         raf.skipBytes(vorbisHeaderSizes.getCommentHeaderSize());
 240  
         //Read in setup header and extra packets
 241  50
         raf.getChannel().read(secondPageBuffer);
 242  50
         calculateChecksumOverPage(secondPageBuffer);
 243  50
         rafTemp.getChannel().write(secondPageBuffer);
 244  50
         rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getFilePointer(), raf.length() - raf.getFilePointer());
 245  50
     }
 246  
 
 247  
     /**
 248  
      * Previously comment and/or setup header was on a number of pages now can just replace this page fitting all
 249  
      * on 2nd page, and renumber subsequent sequence pages
 250  
      *
 251  
      * @param originalHeaderSizes
 252  
      * @param newCommentLength
 253  
      * @param newSecondPageLength
 254  
      * @param secondPageHeader
 255  
      * @param newComment
 256  
      * @param raf
 257  
      * @param rafTemp
 258  
      * @throws IOException
 259  
      * @throws org.jaudiotagger.audio.exceptions.CannotReadException
 260  
      * @throws org.jaudiotagger.audio.exceptions.CannotWriteException
 261  
      */
 262  
     private void replaceSecondPageAndRenumberPageSeqs(OggVorbisTagReader.OggVorbisHeaderSizes originalHeaderSizes, int newCommentLength, int newSecondPageLength, OggPageHeader secondPageHeader, ByteBuffer newComment, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException
 263  
     {
 264  19
         logger.fine("WriteOgg Type 2");
 265  19
         ByteBuffer secondPageBuffer = startCreateBasicSecondPage(originalHeaderSizes, newCommentLength, newSecondPageLength, secondPageHeader, newComment);
 266  
 
 267  
         //Add setup header and packets
 268  19
         int pageSequence = secondPageHeader.getPageSequence();
 269  19
         byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf);
 270  19
         logger.finest(setupHeaderData.length + ":" + secondPageBuffer.position() + ":" + secondPageBuffer.capacity());
 271  19
         secondPageBuffer.put(setupHeaderData);
 272  
 
 273  19
         calculateChecksumOverPage(secondPageBuffer);
 274  19
         rafTemp.getChannel().write(secondPageBuffer);
 275  19
         writeRemainingPages(pageSequence, raf, rafTemp);
 276  19
     }
 277  
 
 278  
     /**
 279  
      * CommentHeader extends over multiple pages OR Comment Header doesnt but it's got larger causing some extra
 280  
      * packets to be shifted onto another page.
 281  
      *
 282  
      * @param originalHeaderSizes
 283  
      * @param newCommentLength
 284  
      * @param secondPageHeader
 285  
      * @param newComment
 286  
      * @param raf
 287  
      * @param rafTemp
 288  
      * @throws IOException
 289  
      * @throws CannotReadException
 290  
      * @throws CannotWriteException
 291  
      */
 292  
     private void replacePagesAndRenumberPageSeqs(OggVorbisTagReader.OggVorbisHeaderSizes originalHeaderSizes, int newCommentLength, OggPageHeader secondPageHeader, ByteBuffer newComment, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException
 293  
     {
 294  20
         int pageSequence = secondPageHeader.getPageSequence();
 295  
 
 296  
         //We need to work out how to split the newcommentlength over the pages
 297  20
         int noOfCompletePagesNeededForComment = newCommentLength / OggPageHeader.MAXIMUM_PAGE_DATA_SIZE;
 298  20
         logger.info("Comment requires:" + noOfCompletePagesNeededForComment + " complete pages");
 299  
 
 300  
         //Create the Pages
 301  20
         int newCommentOffset = 0;
 302  20
         if (noOfCompletePagesNeededForComment > 0)
 303  
         {
 304  157
             for (int i = 0; i < noOfCompletePagesNeededForComment; i++)
 305  
             {
 306  
                 //Create ByteBuffer for the New page
 307  138
                 byte[] segmentTable = this.createSegments(OggPageHeader.MAXIMUM_PAGE_DATA_SIZE, false);
 308  138
                 int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length;
 309  138
                 ByteBuffer pageBuffer = ByteBuffer.allocate(pageHeaderLength + OggPageHeader.MAXIMUM_PAGE_DATA_SIZE);
 310  138
                 pageBuffer.order(ByteOrder.LITTLE_ENDIAN);
 311  
 
 312  
                 //Now create the page basing it on the existing 2ndpageheader
 313  138
                 pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1);
 314  
                 //Number of Page Segments
 315  138
                 pageBuffer.put((byte) segmentTable.length);
 316  
                 //Page segment table
 317  35328
                 for (byte aSegmentTable : segmentTable)
 318  
                 {
 319  35190
                     pageBuffer.put(aSegmentTable);
 320  
                 }
 321  
                 //Get next bit of Comment
 322  138
                 ByteBuffer nextPartOfComment = newComment.slice();
 323  138
                 nextPartOfComment.limit(OggPageHeader.MAXIMUM_PAGE_DATA_SIZE);
 324  138
                 pageBuffer.put(nextPartOfComment);
 325  
 
 326  
                 //Recalculate Page Sequence Number
 327  138
                 pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence);
 328  138
                 pageSequence++;
 329  
 
 330  
                 //Set Header Flag to indicate continuous (except for first flag)
 331  138
                 if (i != 0)
 332  
                 {
 333  119
                     pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue());
 334  
                 }
 335  138
                 calculateChecksumOverPage(pageBuffer);
 336  138
                 rafTemp.getChannel().write(pageBuffer);
 337  138
                 newCommentOffset += OggPageHeader.MAXIMUM_PAGE_DATA_SIZE;
 338  138
                 newComment.position(newCommentOffset);
 339  
             }
 340  
         }
 341  
 
 342  20
         int lastPageCommentPacketSize = newCommentLength % OggPageHeader.MAXIMUM_PAGE_DATA_SIZE;
 343  20
         logger.fine("Last comment packet size:" + lastPageCommentPacketSize);
 344  
 
 345  
         //End of comment and setup header cannot fit on the last page
 346  20
         if (!isCommentAndSetupHeaderFitsOnASinglePage(lastPageCommentPacketSize, originalHeaderSizes.getSetupHeaderSize(), originalHeaderSizes.getExtraPacketList()))
 347  
         {
 348  9
             logger.fine("WriteOgg Type 3");
 349  
 
 350  
             //Write the last part of comment only (its possible it might be the only comment)
 351  
             {
 352  9
                 byte[] segmentTable = createSegments(lastPageCommentPacketSize, true);
 353  9
                 int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length;
 354  9
                 ByteBuffer pageBuffer = ByteBuffer.allocate(lastPageCommentPacketSize + pageHeaderLength);
 355  9
                 pageBuffer.order(ByteOrder.LITTLE_ENDIAN);
 356  9
                 pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1);
 357  9
                 pageBuffer.put((byte) segmentTable.length);
 358  1983
                 for (byte aSegmentTable : segmentTable)
 359  
                 {
 360  1974
                     pageBuffer.put(aSegmentTable);
 361  
                 }
 362  9
                 newComment.position(newCommentOffset);
 363  9
                 pageBuffer.put(newComment.slice());
 364  9
                 pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence);
 365  
 
 366  9
                 if(noOfCompletePagesNeededForComment>0)
 367  
                 {
 368  8
                     pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue());
 369  
                 }
 370  9
                 logger.fine("Writing Last Comment Page "+pageSequence +" to file");
 371  9
                 pageSequence++;
 372  9
                 calculateChecksumOverPage(pageBuffer);
 373  9
                 rafTemp.getChannel().write(pageBuffer);
 374  
             }
 375  
 
 376  
             //Now write header and extra packets onto next page
 377  
             {
 378  9
                 byte[] segmentTable = this.createSegmentTable(originalHeaderSizes.getSetupHeaderSize(),originalHeaderSizes.getExtraPacketList());
 379  9
                 int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length;
 380  9
                 byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf);
 381  9
                 ByteBuffer pageBuffer = ByteBuffer.allocate(setupHeaderData.length + pageHeaderLength);
 382  9
                 pageBuffer.order(ByteOrder.LITTLE_ENDIAN);
 383  9
                 pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1);
 384  9
                 pageBuffer.put((byte) segmentTable.length);
 385  383
                 for (byte aSegmentTable : segmentTable)
 386  
                 {
 387  374
                     pageBuffer.put(aSegmentTable);
 388  
                 }
 389  9
                 pageBuffer.put(setupHeaderData);
 390  9
                 pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence);
 391  
                 //pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue());
 392  9
                 logger.fine("Writing Setup Header and packets Page "+pageSequence +" to file");
 393  
 
 394  9
                 calculateChecksumOverPage(pageBuffer);
 395  9
                 rafTemp.getChannel().write(pageBuffer);
 396  9
             }
 397  
         }
 398  
         else
 399  
         {
 400  
             //End of Comment and SetupHeader and extra packets can fit on one page
 401  11
             logger.fine("WriteOgg Type 4");
 402  
 
 403  
             //Create last header page
 404  11
             int newSecondPageDataLength = originalHeaderSizes.getSetupHeaderSize() + lastPageCommentPacketSize + originalHeaderSizes.getExtraPacketDataSize();
 405  11
             newComment.position(newCommentOffset);
 406  11
             ByteBuffer lastComment = newComment.slice();
 407  11
             ByteBuffer lastHeaderBuffer = startCreateBasicSecondPage(
 408  
                                                             originalHeaderSizes,
 409  
                                                             lastPageCommentPacketSize,
 410  
                                                             newSecondPageDataLength,
 411  
                                                             secondPageHeader,
 412  
                                                             lastComment);
 413  
             //Now find the setupheader which is on a different page
 414  11
             raf.seek(originalHeaderSizes.getSetupHeaderStartPosition());
 415  
 
 416  
             //Add setup Header and Extra Packets (although it will fit in this page, it may be over multiple pages in its original form
 417  
             //so need to use this function to convert to raw data
 418  11
             byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf);
 419  11
             lastHeaderBuffer.put(setupHeaderData);
 420  
 
 421  
             //Page Sequence No
 422  11
             lastHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence);
 423  
 
 424  
             //Set Header Flag to indicate continuous (contains end of comment)
 425  11
             lastHeaderBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue());
 426  11
             calculateChecksumOverPage(lastHeaderBuffer);
 427  11
             rafTemp.getChannel().write(lastHeaderBuffer);
 428  
         }
 429  
 
 430  
         //Write the rest of the original file
 431  20
         writeRemainingPages(pageSequence, raf, rafTemp);
 432  20
     }
 433  
 
 434  
     /**
 435  
      * Write all the remaining pages as they are except that the page sequence needs to be modified.
 436  
      *
 437  
      * @param pageSequence
 438  
      * @param raf
 439  
      * @param rafTemp
 440  
      * @throws IOException
 441  
      * @throws CannotReadException
 442  
      * @throws CannotWriteException
 443  
      */
 444  
     public void writeRemainingPages(int pageSequence, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException
 445  
     {
 446  39
         long startAudio = raf.getFilePointer();
 447  39
         long startAudioWritten = rafTemp.getFilePointer();
 448  
 
 449  
         //TODO there is a risk we wont have enough memory to create these buffers
 450  39
         ByteBuffer bb       = ByteBuffer.allocate((int)(raf.length() - raf.getFilePointer()));
 451  39
         ByteBuffer bbTemp   = ByteBuffer.allocate((int)(raf.length() - raf.getFilePointer()));
 452  
 
 453  
         //Read in the rest of the data into bytebuffer and rewind it to start
 454  39
         raf.getChannel().read(bb);
 455  39
         bb.rewind();
 456  14133
         while(bb.hasRemaining())
 457  
         {
 458  14094
             OggPageHeader nextPage = OggPageHeader.read(bb);
 459  
 
 460  
             //Create buffer large enough for next page (header and data) and set byte order to LE so we can use
 461  
             //putInt method
 462  14094
             ByteBuffer nextPageHeaderBuffer = ByteBuffer.allocate(nextPage.getRawHeaderData().length + nextPage.getPageLength());
 463  14094
             nextPageHeaderBuffer.order(ByteOrder.LITTLE_ENDIAN);
 464  14094
             nextPageHeaderBuffer.put(nextPage.getRawHeaderData());
 465  14094
             ByteBuffer data = bb.slice();
 466  14094
             data.limit(nextPage.getPageLength());
 467  14094
             nextPageHeaderBuffer.put(data);
 468  14094
             nextPageHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, ++pageSequence);
 469  14094
             calculateChecksumOverPage(nextPageHeaderBuffer);
 470  14094
             bb.position(bb.position() + nextPage.getPageLength());
 471  
 
 472  14094
             nextPageHeaderBuffer.rewind();
 473  14094
             bbTemp.put(nextPageHeaderBuffer);
 474  14094
         }
 475  
         //Now just write as a single IO operation
 476  39
         bbTemp.rewind();
 477  39
         rafTemp.getChannel().write(bbTemp);
 478  
         //Check weve written all the data
 479  
         //TODO could we do any other checks to check data written correctly ?
 480  39
         if ((raf.length() - startAudio) != (rafTemp.length() - startAudioWritten))
 481  
         {
 482  0
             throw new CannotWriteException("File written counts don't match, file not written");
 483  
         }
 484  39
     }
 485  
     public void writeRemainingPagesOld(int pageSequence, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException
 486  
         {
 487  
         //Now the Page Sequence Number for all the subsequent pages (containing audio frames) are out because there are
 488  
         //less pages before then there used to be, so need to adjust
 489  0
         long startAudio = raf.getFilePointer();
 490  0
         long startAudioWritten = rafTemp.getFilePointer();
 491  0
         logger.fine("Writing audio, audio starts in original file at :" + startAudio + ":Written to:" + startAudioWritten);
 492  0
         while (raf.getFilePointer() < raf.length())
 493  
         {
 494  0
             logger.fine("Reading Ogg Page");
 495  0
             OggPageHeader nextPage = OggPageHeader.read(raf);
 496  
 
 497  
             //Create buffer large enough for next page (header and data) and set byte order to LE so we can use
 498  
             //putInt method
 499  0
             ByteBuffer nextPageHeaderBuffer = ByteBuffer.allocate(nextPage.getRawHeaderData().length + nextPage.getPageLength());
 500  0
             nextPageHeaderBuffer.order(ByteOrder.LITTLE_ENDIAN);
 501  
 
 502  0
             nextPageHeaderBuffer.put(nextPage.getRawHeaderData());
 503  0
             raf.getChannel().read(nextPageHeaderBuffer);
 504  
 
 505  
             //Recalculate Page Sequence Number
 506  0
             nextPageHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, ++pageSequence);
 507  
 
 508  
             //Calculate Checksum
 509  0
             calculateChecksumOverPage(nextPageHeaderBuffer);
 510  0
             rafTemp.getChannel().write(nextPageHeaderBuffer);
 511  0
         }
 512  0
         if ((raf.length() - startAudio) != (rafTemp.length() - startAudioWritten))
 513  
         {
 514  0
             throw new CannotWriteException("File written counts don't match, file not written");
 515  
         }
 516  0
     }
 517  
 
 518  
     /**
 519  
      * This method creates a new segment table for the second page (header).
 520  
      *
 521  
      * @param newCommentLength  The length of the Vorbis Comment
 522  
      * @param setupHeaderLength The length of Setup Header, zero if comment String extends
 523  
      *                          over multiple pages and this is not the last page.
 524  
      * @param extraPackets      If there are packets immediately after setup header in same page, they
 525  
      *                          need including in the segment table
 526  
      * @return new segment table.
 527  
      */
 528  
     private byte[] createSegmentTable(int newCommentLength, int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPackets)
 529  
     {
 530  80
         logger.finest("Create SegmentTable CommentLength:" + newCommentLength + ":SetupHeaderLength:" + setupHeaderLength);
 531  80
         ByteArrayOutputStream resultBaos = new ByteArrayOutputStream();
 532  
 
 533  
         byte[] newStart;
 534  
         byte[] restShouldBe;
 535  
         byte[] nextPacket;
 536  
 
 537  
         //Vorbis Comment
 538  80
         if (setupHeaderLength == 0)
 539  
         {
 540  
             //Comment Stream continues onto next page so last lacing value can be 255
 541  0
             newStart = createSegments(newCommentLength, false);
 542  0
             return newStart;
 543  
         }
 544  
         else
 545  
         {
 546  
             //Comment Stream finishes on this page so if is a multiple of 255
 547  
             //have to add an extra entry.
 548  80
             newStart = createSegments(newCommentLength, true);
 549  
         }
 550  
 
 551  
         //Setup Header, should be closed
 552  80
         if (extraPackets.size() > 0)
 553  
         {
 554  2
             restShouldBe = createSegments(setupHeaderLength, true);
 555  
         }
 556  
         //.. continue sonto next page
 557  
         else
 558  
         {
 559  78
             restShouldBe = createSegments(setupHeaderLength, false);
 560  
         }
 561  
 
 562  80
         logger.finest("Created " + newStart.length + " segments for header");
 563  80
         logger.finest("Created " + restShouldBe.length + " segments for setup");
 564  
 
 565  
         try
 566  
         {
 567  80
             resultBaos.write(newStart);
 568  80
             resultBaos.write(restShouldBe);
 569  80
             if (extraPackets.size() > 0)
 570  
             {
 571  
                 //Packets are being copied literally not converted from a length, so always pass
 572  
                 //false parameter, TODO is this statement correct
 573  2
                 logger.finer("Creating segments for " + extraPackets.size() + " packets");
 574  2
                 for (OggPageHeader.PacketStartAndLength packet : extraPackets)
 575  
                 {
 576  24
                     nextPacket = createSegments(packet.getLength(), false);
 577  24
                     resultBaos.write(nextPacket);
 578  
                 }
 579  
             }
 580  
         }
 581  0
         catch (IOException ioe)
 582  
         {
 583  0
             throw new RuntimeException("Unable to create segment table:" + ioe.getMessage());
 584  80
         }
 585  80
         return resultBaos.toByteArray();
 586  
 
 587  
     }
 588  
 
 589  
     /**
 590  
      * This method creates a new segment table for the second half of setup header
 591  
      *
 592  
      * @param setupHeaderLength The length of Setup Header, zero if comment String extends
 593  
      *                          over multiple pages and this is not the last page.
 594  
      * @param extraPackets      If there are packets immediately after setup header in same page, they
 595  
      *                          need including in the segment table
 596  
      * @return new segment table.
 597  
      */
 598  
     private byte[] createSegmentTable(int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPackets)
 599  
     {
 600  9
         ByteArrayOutputStream resultBaos = new ByteArrayOutputStream();
 601  
 
 602  
         byte[] restShouldBe;
 603  
         byte[] nextPacket;
 604  
 
 605  
         //Setup Header
 606  9
         restShouldBe = createSegments(setupHeaderLength, true);
 607  
 
 608  
         try
 609  
         {
 610  9
             resultBaos.write(restShouldBe);
 611  9
             if (extraPackets.size() > 0)
 612  
             {
 613  
                 //Packets are being copied literally not converted from a length, so always pass
 614  
                 //false parameter, TODO is this statement correct
 615  1
                 for (OggPageHeader.PacketStartAndLength packet : extraPackets)
 616  
                 {
 617  238
                     nextPacket = createSegments(packet.getLength(), false);
 618  238
                     resultBaos.write(nextPacket);
 619  
                 }
 620  
             }
 621  
         }
 622  0
         catch (IOException ioe)
 623  
         {
 624  0
             throw new RuntimeException("Unable to create segment table:" + ioe.getMessage());
 625  9
         }
 626  9
         return resultBaos.toByteArray();
 627  
     }
 628  
 
 629  
 
 630  
     /**
 631  
      * This method creates a byte array of values whose sum should
 632  
      * be the value of <code>length</code>.<br>
 633  
      *
 634  
      * @param length     Size of the page which should be
 635  
      *                   represented as 255 byte packets.
 636  
      * @param quitStream If true and a length is a multiple of 255 we need another
 637  
      *                   segment table entry with the value of 0. Else it's the last stream of the
 638  
      *                   table which is already ended.
 639  
      * @return Array of packet sizes. However only the last packet will
 640  
      *         differ from 255.
 641  
      *         <p/>
 642  
      */
 643  
     //TODO if pass is data of max length (65025 bytes) and have quitStream==true
 644  
     //this will return 256 segments which is illegal, should be checked somewhere
 645  
     private byte[] createSegments(int length, boolean quitStream)
 646  
     {
 647  578
         logger.finest("Create Segments for length:" + length + ":QuitStream:" + quitStream);
 648  
         //It is valid to have nil length packets
 649  578
         if (length == 0)
 650  
         {
 651  240
             byte[] result = new byte[1];
 652  240
             result[0] = (byte) 0x00;
 653  240
             return result;
 654  
         }
 655  
 
 656  338
         byte[] result = new byte[length / OggPageHeader.MAXIMUM_SEGMENT_SIZE + ((length % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0 && !quitStream) ? 0 : 1)];
 657  338
         int i = 0;
 658  83862
         for (; i < result.length - 1; i++)
 659  
         {
 660  41762
             result[i] = (byte) 0xFF;
 661  
         }
 662  338
         result[result.length - 1] = (byte) (length - (i * OggPageHeader.MAXIMUM_SEGMENT_SIZE));
 663  338
         return result;
 664  
     }
 665  
 
 666  
     /**
 667  
      * @param commentLength
 668  
      * @param setupHeaderLength
 669  
      * @param extraPacketList
 670  
      * @return true if there is enough room to fit the comment and the setup headers on one page taking into
 671  
      *         account the maximum no of segments allowed per page and zero lacing values.
 672  
      */
 673  
     private boolean isCommentAndSetupHeaderFitsOnASinglePage(int commentLength, int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPacketList)
 674  
     {
 675  109
         int totalDataSize = 0;
 676  
 
 677  109
         if (commentLength == 0)
 678  
         {
 679  0
             totalDataSize++;
 680  
         }
 681  
         else
 682  
         {
 683  109
             totalDataSize = (commentLength / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1;
 684  109
             if (commentLength % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0)
 685  
             {
 686  0
                 totalDataSize++;
 687  
             }
 688  
         }
 689  109
         logger.finest("Require:" + totalDataSize + " segments for comment");
 690  
 
 691  109
         if (setupHeaderLength == 0)
 692  
         {
 693  0
             totalDataSize++;
 694  
         }
 695  
         else
 696  
         {
 697  109
             totalDataSize += (setupHeaderLength / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1;
 698  109
             if (setupHeaderLength % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0)
 699  
             {
 700  0
                 totalDataSize++;
 701  
             }
 702  
         }
 703  109
         logger.finest("Require:" + totalDataSize + " segments for comment plus setup");
 704  
 
 705  109
         for (OggPageHeader.PacketStartAndLength extraPacket : extraPacketList)
 706  
         {
 707  500
             if (extraPacket.getLength() == 0)
 708  
             {
 709  478
                 totalDataSize++;
 710  
             }
 711  
             else
 712  
             {
 713  22
                 totalDataSize += (extraPacket.getLength() / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1;
 714  22
                 if (extraPacket.getLength() % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0)
 715  
                 {
 716  1
                     totalDataSize++;
 717  
                 }
 718  
             }
 719  
         }
 720  
 
 721  109
         logger.finest("Total No Of Segment If New Comment And Header Put On One Page:" + totalDataSize);
 722  109
         return totalDataSize <= OggPageHeader.MAXIMUM_NO_OF_SEGMENT_SIZE;
 723  
     }
 724  
 
 725  
 }