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