/* * Entagged Audio Tag library * Copyright (c) 2003-2005 Raphael Slinckx <raphael@slinckx.net> * Copyright (c) 2004-2005 Christian Laireiter <liree@web.de> * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ package org.jaudiotagger.audio.ogg; import org.jaudiotagger.audio.exceptions.CannotReadException; import org.jaudiotagger.audio.exceptions.CannotWriteException; import org.jaudiotagger.audio.ogg.util.OggCRCFactory; import org.jaudiotagger.audio.ogg.util.OggPageHeader; import org.jaudiotagger.tag.Tag; import org.jaudiotagger.tag.vorbiscomment.VorbisCommentTag; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.List; import java.util.logging.Logger; /** * Write Vorbis Tag within an ogg * <p/> * VorbisComment holds the tag information within an ogg file */ public class OggVorbisTagWriter { // Logger Object public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.ogg"); private OggVorbisCommentTagCreator tc = new OggVorbisCommentTagCreator(); private OggVorbisTagReader reader = new OggVorbisTagReader(); public void delete(RandomAccessFile raf, RandomAccessFile tempRaf) throws IOException, CannotReadException, CannotWriteException { try { reader.read(raf); } catch (CannotReadException e) { write(VorbisCommentTag.createNewTag(), raf, tempRaf); return; } VorbisCommentTag emptyTag = VorbisCommentTag.createNewTag(); //Go back to start of file raf.seek(0); write(emptyTag, raf, tempRaf); } public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotReadException, CannotWriteException, IOException { //logger.info("Starting to write file:"); //1st Page:Identification Header //logger.fine("Read 1st Page:identificationHeader:"); OggPageHeader pageHeader = OggPageHeader.read(raf); raf.seek(0); //Write 1st page (unchanged) and place writer pointer at end of data rafTemp.getChannel().transferFrom(raf.getChannel(), 0, pageHeader.getPageLength() + OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length); rafTemp.skipBytes(pageHeader.getPageLength() + OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + pageHeader.getSegmentTable().length); //logger.fine("Written identificationHeader:"); //2nd page:Comment and Setup if there is enough room, may also (although not normally) contain audio frames OggPageHeader secondPageHeader = OggPageHeader.read(raf); //2nd Page:Store the end of Header long secondPageHeaderEndPos = raf.getFilePointer(); //logger.fine("Read 2nd Page:comment and setup and possibly audio:Header finishes at file position:" + secondPageHeaderEndPos); //Get header sizes raf.seek(0); OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes = reader.readOggVorbisHeaderSizes(raf); //Convert the OggVorbisComment header to raw packet data ByteBuffer newComment = tc.convert(tag); //Compute new comment length(this may need to be spread over multiple pages) int newCommentLength = newComment.capacity(); //Calculate new size of new 2nd page int newSecondPageDataLength = vorbisHeaderSizes.getSetupHeaderSize() + newCommentLength + vorbisHeaderSizes.getExtraPacketDataSize(); //logger.fine("Old 2nd Page no of packets: " + secondPageHeader.getPacketList().size()); //logger.fine("Old 2nd Page size: " + secondPageHeader.getPageLength()); // logger.fine("Old last packet incomplete: " + secondPageHeader.isLastPacketIncomplete()); //logger.fine("Setup Header Size: " + vorbisHeaderSizes.getSetupHeaderSize()); // logger.fine("Extra Packets: " + vorbisHeaderSizes.getExtraPacketList().size()); //logger.fine("Extra Packet Size: " + vorbisHeaderSizes.getExtraPacketDataSize()); //logger.fine("Old comment: " + vorbisHeaderSizes.getCommentHeaderSize()); //logger.fine("New comment: " + newCommentLength); //logger.fine("New Page Data Size: " + newSecondPageDataLength); //Second Page containing new vorbis, setup and possibly some extra packets can fit on one page if (isCommentAndSetupHeaderFitsOnASinglePage(newCommentLength, vorbisHeaderSizes.getSetupHeaderSize(), vorbisHeaderSizes.getExtraPacketList())) { //And if comment and setup header originally fitted on both, the length of the 2nd //page must be less than maximum size allowed //AND //there must be two packets with last being complete because they may have //elected to split the setup over multiple pages instead of using up whole page - (as long //as the last lacing value is 255 they can do this) // OR //There are more than the packets in which case have complete setup header and some audio packets //we dont care if the last audio packet is split on next page as long as we preserve it if ((secondPageHeader.getPageLength() < OggPageHeader.MAXIMUM_PAGE_DATA_SIZE) && (((secondPageHeader.getPacketList().size() == 2) && (!secondPageHeader.isLastPacketIncomplete())) || (secondPageHeader.getPacketList().size() > 2))) { //logger.info("Header and Setup remain on single page:"); replaceSecondPageOnly(vorbisHeaderSizes, newCommentLength, newSecondPageDataLength, secondPageHeader, newComment, secondPageHeaderEndPos, raf, rafTemp); } //Original 2nd page spanned multiple pages so more work to do else { //logger.info("Header and Setup now on single page:"); replaceSecondPageAndRenumberPageSeqs(vorbisHeaderSizes, newCommentLength, newSecondPageDataLength, secondPageHeader, newComment, raf, rafTemp); } } //Bit more complicated, have to create a load of new pages and renumber audio else { //logger.info("Header and Setup with shift audio:"); replacePagesAndRenumberPageSeqs(vorbisHeaderSizes, newCommentLength, secondPageHeader, newComment, raf, rafTemp); } } /** * Calculate checkSum over the Page * * @param page */ private void calculateChecksumOverPage(ByteBuffer page) { //CRC should be zero before calculating it page.putInt(OggPageHeader.FIELD_PAGE_CHECKSUM_POS, 0); //Compute CRC over the page //TODO shouldnt really use array(); byte[] crc = OggCRCFactory.computeCRC(page.array()); for (int i = 0; i < crc.length; i++) { page.put(OggPageHeader.FIELD_PAGE_CHECKSUM_POS + i, crc[i]); } //Rewind to start of Page page.rewind(); } /** * 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 * * @param vorbisHeaderSizes * @param newCommentLength * @param newSecondPageLength * @param secondPageHeader * @param newComment * @return * @throws IOException */ private ByteBuffer startCreateBasicSecondPage( OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes, int newCommentLength, int newSecondPageLength, OggPageHeader secondPageHeader, ByteBuffer newComment) throws IOException { // logger.fine("WriteOgg Type 1"); byte[] segmentTable = createSegmentTable(newCommentLength, vorbisHeaderSizes.getSetupHeaderSize(), vorbisHeaderSizes.getExtraPacketList()); int newSecondPageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length; // logger.fine("New second page header length:" + newSecondPageHeaderLength); // logger.fine("No of segments:" + segmentTable.length); ByteBuffer secondPageBuffer = ByteBuffer.allocate(newSecondPageLength + newSecondPageHeaderLength); secondPageBuffer.order(ByteOrder.LITTLE_ENDIAN); //Build the new 2nd page header, can mostly be taken from the original upto the segment length OggS capture secondPageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1); //Number of Page Segments secondPageBuffer.put((byte) segmentTable.length); //Page segment table for (byte aSegmentTable : segmentTable) { secondPageBuffer.put(aSegmentTable); } //Add New VorbisComment secondPageBuffer.put(newComment); return secondPageBuffer; } /** * Usually can use this method, previously comment and setup header all fit on page 2 * and they still do, so just replace this page. And copy further pages as is. * * @param vorbisHeaderSizes * @param newCommentLength * @param newSecondPageLength * @param secondPageHeader * @param newComment * @param secondPageHeaderEndPos * @param raf * @param rafTemp * @throws IOException */ private void replaceSecondPageOnly( OggVorbisTagReader.OggVorbisHeaderSizes vorbisHeaderSizes, int newCommentLength, int newSecondPageLength, OggPageHeader secondPageHeader, ByteBuffer newComment, long secondPageHeaderEndPos, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException { // logger.fine("WriteOgg Type 1"); ByteBuffer secondPageBuffer = startCreateBasicSecondPage(vorbisHeaderSizes, newCommentLength, newSecondPageLength, secondPageHeader, newComment); raf.seek(secondPageHeaderEndPos); //Skip comment header raf.skipBytes(vorbisHeaderSizes.getCommentHeaderSize()); //Read in setup header and extra packets raf.getChannel().read(secondPageBuffer); calculateChecksumOverPage(secondPageBuffer); rafTemp.getChannel().write(secondPageBuffer); rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getFilePointer(), raf.length() - raf.getFilePointer()); } /** * Previously comment and/or setup header was on a number of pages now can just replace this page fitting all * on 2nd page, and renumber subsequent sequence pages * * @param originalHeaderSizes * @param newCommentLength * @param newSecondPageLength * @param secondPageHeader * @param newComment * @param raf * @param rafTemp * @throws IOException * @throws org.jaudiotagger.audio.exceptions.CannotReadException * * @throws org.jaudiotagger.audio.exceptions.CannotWriteException * */ private void replaceSecondPageAndRenumberPageSeqs(OggVorbisTagReader.OggVorbisHeaderSizes originalHeaderSizes, int newCommentLength, int newSecondPageLength, OggPageHeader secondPageHeader, ByteBuffer newComment, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException { // logger.fine("WriteOgg Type 2"); ByteBuffer secondPageBuffer = startCreateBasicSecondPage(originalHeaderSizes, newCommentLength, newSecondPageLength, secondPageHeader, newComment); //Add setup header and packets int pageSequence = secondPageHeader.getPageSequence(); byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf); // logger.finest(setupHeaderData.length + ":" + secondPageBuffer.position() + ":" + secondPageBuffer.capacity()); secondPageBuffer.put(setupHeaderData); calculateChecksumOverPage(secondPageBuffer); rafTemp.getChannel().write(secondPageBuffer); writeRemainingPages(pageSequence, raf, rafTemp); } /** * CommentHeader extends over multiple pages OR Comment Header doesnt but it's got larger causing some extra * packets to be shifted onto another page. * * @param originalHeaderSizes * @param newCommentLength * @param secondPageHeader * @param newComment * @param raf * @param rafTemp * @throws IOException * @throws CannotReadException * @throws CannotWriteException */ private void replacePagesAndRenumberPageSeqs(OggVorbisTagReader.OggVorbisHeaderSizes originalHeaderSizes, int newCommentLength, OggPageHeader secondPageHeader, ByteBuffer newComment, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException { int pageSequence = secondPageHeader.getPageSequence(); //We need to work out how to split the newcommentlength over the pages int noOfCompletePagesNeededForComment = newCommentLength / OggPageHeader.MAXIMUM_PAGE_DATA_SIZE; // logger.info("Comment requires:" + noOfCompletePagesNeededForComment + " complete pages"); //Create the Pages int newCommentOffset = 0; if (noOfCompletePagesNeededForComment > 0) { for (int i = 0; i < noOfCompletePagesNeededForComment; i++) { //Create ByteBuffer for the New page byte[] segmentTable = this.createSegments(OggPageHeader.MAXIMUM_PAGE_DATA_SIZE, false); int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length; ByteBuffer pageBuffer = ByteBuffer.allocate(pageHeaderLength + OggPageHeader.MAXIMUM_PAGE_DATA_SIZE); pageBuffer.order(ByteOrder.LITTLE_ENDIAN); //Now create the page basing it on the existing 2ndpageheader pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1); //Number of Page Segments pageBuffer.put((byte) segmentTable.length); //Page segment table for (byte aSegmentTable : segmentTable) { pageBuffer.put(aSegmentTable); } //Get next bit of Comment ByteBuffer nextPartOfComment = newComment.slice(); nextPartOfComment.limit(OggPageHeader.MAXIMUM_PAGE_DATA_SIZE); pageBuffer.put(nextPartOfComment); //Recalculate Page Sequence Number pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence); pageSequence++; //Set Header Flag to indicate continuous (except for first flag) if (i != 0) { pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue()); } calculateChecksumOverPage(pageBuffer); rafTemp.getChannel().write(pageBuffer); newCommentOffset += OggPageHeader.MAXIMUM_PAGE_DATA_SIZE; newComment.position(newCommentOffset); } } int lastPageCommentPacketSize = newCommentLength % OggPageHeader.MAXIMUM_PAGE_DATA_SIZE; // logger.fine("Last comment packet size:" + lastPageCommentPacketSize); //End of comment and setup header cannot fit on the last page if (!isCommentAndSetupHeaderFitsOnASinglePage(lastPageCommentPacketSize, originalHeaderSizes.getSetupHeaderSize(), originalHeaderSizes.getExtraPacketList())) { // logger.fine("WriteOgg Type 3"); //Write the last part of comment only (its possible it might be the only comment) { byte[] segmentTable = createSegments(lastPageCommentPacketSize, true); int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length; ByteBuffer pageBuffer = ByteBuffer.allocate(lastPageCommentPacketSize + pageHeaderLength); pageBuffer.order(ByteOrder.LITTLE_ENDIAN); pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1); pageBuffer.put((byte) segmentTable.length); for (byte aSegmentTable : segmentTable) { pageBuffer.put(aSegmentTable); } newComment.position(newCommentOffset); pageBuffer.put(newComment.slice()); pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence); if (noOfCompletePagesNeededForComment > 0) { pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue()); } // logger.fine("Writing Last Comment Page " + pageSequence + " to file"); pageSequence++; calculateChecksumOverPage(pageBuffer); rafTemp.getChannel().write(pageBuffer); } //Now write header and extra packets onto next page { byte[] segmentTable = this.createSegmentTable(originalHeaderSizes.getSetupHeaderSize(), originalHeaderSizes.getExtraPacketList()); int pageHeaderLength = OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH + segmentTable.length; byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf); ByteBuffer pageBuffer = ByteBuffer.allocate(setupHeaderData.length + pageHeaderLength); pageBuffer.order(ByteOrder.LITTLE_ENDIAN); pageBuffer.put(secondPageHeader.getRawHeaderData(), 0, OggPageHeader.OGG_PAGE_HEADER_FIXED_LENGTH - 1); pageBuffer.put((byte) segmentTable.length); for (byte aSegmentTable : segmentTable) { pageBuffer.put(aSegmentTable); } pageBuffer.put(setupHeaderData); pageBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence); //pageBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue()); // logger.fine("Writing Setup Header and packets Page " + pageSequence + " to file"); calculateChecksumOverPage(pageBuffer); rafTemp.getChannel().write(pageBuffer); } } else { //End of Comment and SetupHeader and extra packets can fit on one page // logger.fine("WriteOgg Type 4"); //Create last header page int newSecondPageDataLength = originalHeaderSizes.getSetupHeaderSize() + lastPageCommentPacketSize + originalHeaderSizes.getExtraPacketDataSize(); newComment.position(newCommentOffset); ByteBuffer lastComment = newComment.slice(); ByteBuffer lastHeaderBuffer = startCreateBasicSecondPage( originalHeaderSizes, lastPageCommentPacketSize, newSecondPageDataLength, secondPageHeader, lastComment); //Now find the setupheader which is on a different page raf.seek(originalHeaderSizes.getSetupHeaderStartPosition()); //Add setup Header and Extra Packets (although it will fit in this page, it may be over multiple pages in its original form //so need to use this function to convert to raw data byte[] setupHeaderData = reader.convertToVorbisSetupHeaderPacketAndAdditionalPackets(originalHeaderSizes.getSetupHeaderStartPosition(), raf); lastHeaderBuffer.put(setupHeaderData); //Page Sequence No lastHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, pageSequence); //Set Header Flag to indicate continuous (contains end of comment) lastHeaderBuffer.put(OggPageHeader.FIELD_HEADER_TYPE_FLAG_POS, OggPageHeader.HeaderTypeFlag.CONTINUED_PACKET.getFileValue()); calculateChecksumOverPage(lastHeaderBuffer); rafTemp.getChannel().write(lastHeaderBuffer); } //Write the rest of the original file writeRemainingPages(pageSequence, raf, rafTemp); } /** * Write all the remaining pages as they are except that the page sequence needs to be modified. * * @param pageSequence * @param raf * @param rafTemp * @throws IOException * @throws CannotReadException * @throws CannotWriteException */ public void writeRemainingPages(int pageSequence, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException { long startAudio = raf.getFilePointer(); long startAudioWritten = rafTemp.getFilePointer(); //TODO there is a risk we wont have enough memory to create these buffers ByteBuffer bb = ByteBuffer.allocate((int) (raf.length() - raf.getFilePointer())); ByteBuffer bbTemp = ByteBuffer.allocate((int) (raf.length() - raf.getFilePointer())); //Read in the rest of the data into bytebuffer and rewind it to start raf.getChannel().read(bb); bb.rewind(); while (bb.hasRemaining()) { OggPageHeader nextPage = OggPageHeader.read(bb); //Create buffer large enough for next page (header and data) and set byte order to LE so we can use //putInt method ByteBuffer nextPageHeaderBuffer = ByteBuffer.allocate(nextPage.getRawHeaderData().length + nextPage.getPageLength()); nextPageHeaderBuffer.order(ByteOrder.LITTLE_ENDIAN); nextPageHeaderBuffer.put(nextPage.getRawHeaderData()); ByteBuffer data = bb.slice(); data.limit(nextPage.getPageLength()); nextPageHeaderBuffer.put(data); nextPageHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, ++pageSequence); calculateChecksumOverPage(nextPageHeaderBuffer); bb.position(bb.position() + nextPage.getPageLength()); nextPageHeaderBuffer.rewind(); bbTemp.put(nextPageHeaderBuffer); } //Now just write as a single IO operation bbTemp.rewind(); rafTemp.getChannel().write(bbTemp); //Check we have written all the data //TODO could we do any other checks to check data written correctly ? if ((raf.length() - startAudio) != (rafTemp.length() - startAudioWritten)) { throw new CannotWriteException("File written counts don't match, file not written"); } } public void writeRemainingPagesOld(int pageSequence, RandomAccessFile raf, RandomAccessFile rafTemp) throws IOException, CannotReadException, CannotWriteException { //Now the Page Sequence Number for all the subsequent pages (containing audio frames) are out because there are //less pages before then there used to be, so need to adjust long startAudio = raf.getFilePointer(); long startAudioWritten = rafTemp.getFilePointer(); // logger.fine("Writing audio, audio starts in original file at :" + startAudio + ":Written to:" + startAudioWritten); while (raf.getFilePointer() < raf.length()) { // logger.fine("Reading Ogg Page"); OggPageHeader nextPage = OggPageHeader.read(raf); //Create buffer large enough for next page (header and data) and set byte order to LE so we can use //putInt method ByteBuffer nextPageHeaderBuffer = ByteBuffer.allocate(nextPage.getRawHeaderData().length + nextPage.getPageLength()); nextPageHeaderBuffer.order(ByteOrder.LITTLE_ENDIAN); nextPageHeaderBuffer.put(nextPage.getRawHeaderData()); raf.getChannel().read(nextPageHeaderBuffer); //Recalculate Page Sequence Number nextPageHeaderBuffer.putInt(OggPageHeader.FIELD_PAGE_SEQUENCE_NO_POS, ++pageSequence); //Calculate Checksum calculateChecksumOverPage(nextPageHeaderBuffer); rafTemp.getChannel().write(nextPageHeaderBuffer); } if ((raf.length() - startAudio) != (rafTemp.length() - startAudioWritten)) { throw new CannotWriteException("File written counts don't match, file not written"); } } /** * This method creates a new segment table for the second page (header). * * @param newCommentLength The length of the Vorbis Comment * @param setupHeaderLength The length of Setup Header, zero if comment String extends * over multiple pages and this is not the last page. * @param extraPackets If there are packets immediately after setup header in same page, they * need including in the segment table * @return new segment table. */ private byte[] createSegmentTable(int newCommentLength, int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPackets) { // logger.finest("Create SegmentTable CommentLength:" + newCommentLength + ":SetupHeaderLength:" + setupHeaderLength); ByteArrayOutputStream resultBaos = new ByteArrayOutputStream(); byte[] newStart; byte[] restShouldBe; byte[] nextPacket; //Vorbis Comment if (setupHeaderLength == 0) { //Comment Stream continues onto next page so last lacing value can be 255 newStart = createSegments(newCommentLength, false); return newStart; } else { //Comment Stream finishes on this page so if is a multiple of 255 //have to add an extra entry. newStart = createSegments(newCommentLength, true); } //Setup Header, should be closed if (extraPackets.size() > 0) { restShouldBe = createSegments(setupHeaderLength, true); } //.. continue sonto next page else { restShouldBe = createSegments(setupHeaderLength, false); } // logger.finest("Created " + newStart.length + " segments for header"); // logger.finest("Created " + restShouldBe.length + " segments for setup"); try { resultBaos.write(newStart); resultBaos.write(restShouldBe); if (extraPackets.size() > 0) { //Packets are being copied literally not converted from a length, so always pass //false parameter, TODO is this statement correct // logger.finer("Creating segments for " + extraPackets.size() + " packets"); for (OggPageHeader.PacketStartAndLength packet : extraPackets) { nextPacket = createSegments(packet.getLength(), false); resultBaos.write(nextPacket); } } } catch (IOException ioe) { throw new RuntimeException("Unable to create segment table:" + ioe.getMessage()); } return resultBaos.toByteArray(); } /** * This method creates a new segment table for the second half of setup header * * @param setupHeaderLength The length of Setup Header, zero if comment String extends * over multiple pages and this is not the last page. * @param extraPackets If there are packets immediately after setup header in same page, they * need including in the segment table * @return new segment table. */ private byte[] createSegmentTable(int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPackets) { ByteArrayOutputStream resultBaos = new ByteArrayOutputStream(); byte[] restShouldBe; byte[] nextPacket; //Setup Header restShouldBe = createSegments(setupHeaderLength, true); try { resultBaos.write(restShouldBe); if (extraPackets.size() > 0) { //Packets are being copied literally not converted from a length, so always pass //false parameter, TODO is this statement correct for (OggPageHeader.PacketStartAndLength packet : extraPackets) { nextPacket = createSegments(packet.getLength(), false); resultBaos.write(nextPacket); } } } catch (IOException ioe) { throw new RuntimeException("Unable to create segment table:" + ioe.getMessage()); } return resultBaos.toByteArray(); } /** * This method creates a byte array of values whose sum should * be the value of <code>length</code>.<br> * * @param length Size of the page which should be * represented as 255 byte packets. * @param quitStream If true and a length is a multiple of 255 we need another * segment table entry with the value of 0. Else it's the last stream of the * table which is already ended. * @return Array of packet sizes. However only the last packet will * differ from 255. * <p/> */ //TODO if pass is data of max length (65025 bytes) and have quitStream==true //this will return 256 segments which is illegal, should be checked somewhere private byte[] createSegments(int length, boolean quitStream) { // logger.finest("Create Segments for length:" + length + ":QuitStream:" + quitStream); //It is valid to have nil length packets if (length == 0) { byte[] result = new byte[1]; result[0] = (byte) 0x00; return result; } byte[] result = new byte[length / OggPageHeader.MAXIMUM_SEGMENT_SIZE + ((length % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0 && !quitStream) ? 0 : 1)]; int i = 0; for (; i < result.length - 1; i++) { result[i] = (byte) 0xFF; } result[result.length - 1] = (byte) (length - (i * OggPageHeader.MAXIMUM_SEGMENT_SIZE)); return result; } /** * @param commentLength * @param setupHeaderLength * @param extraPacketList * @return true if there is enough room to fit the comment and the setup headers on one page taking into * account the maximum no of segments allowed per page and zero lacing values. */ private boolean isCommentAndSetupHeaderFitsOnASinglePage(int commentLength, int setupHeaderLength, List<OggPageHeader.PacketStartAndLength> extraPacketList) { int totalDataSize = 0; if (commentLength == 0) { totalDataSize++; } else { totalDataSize = (commentLength / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1; if (commentLength % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0) { totalDataSize++; } } // logger.finest("Require:" + totalDataSize + " segments for comment"); if (setupHeaderLength == 0) { totalDataSize++; } else { totalDataSize += (setupHeaderLength / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1; if (setupHeaderLength % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0) { totalDataSize++; } } // logger.finest("Require:" + totalDataSize + " segments for comment plus setup"); for (OggPageHeader.PacketStartAndLength extraPacket : extraPacketList) { if (extraPacket.getLength() == 0) { totalDataSize++; } else { totalDataSize += (extraPacket.getLength() / OggPageHeader.MAXIMUM_SEGMENT_SIZE) + 1; if (extraPacket.getLength() % OggPageHeader.MAXIMUM_SEGMENT_SIZE == 0) { totalDataSize++; } } } // logger.finest("Total No Of Segment If New Comment And Header Put On One Page:" + totalDataSize); return totalDataSize <= OggPageHeader.MAXIMUM_NO_OF_SEGMENT_SIZE; } }