FileDocCategorySizeDatePackage
FlacTagWriter.javaAPI DocJaudiotagger 2.0.413519Fri Oct 28 12:58:38 BST 2011org.jaudiotagger.audio.flac

FlacTagWriter

public class FlacTagWriter extends Object
Write Flac Tag

Fields Summary
public static Logger
logger
private MetadataBlock
streamInfoBlock
private List
metadataBlockPadding
private List
metadataBlockApplication
private List
metadataBlockSeekTable
private List
metadataBlockCueSheet
private FlacTagCreator
tc
private FlacTagReader
reader
Constructors Summary
Methods Summary
private intcomputeAvailableRoom()

return
space currently available for writing all Flac metadatablocks except for StreamInfo which is fixed size

        int length = 0;

        for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
        {
            length += aMetadataBlockApplication.getLength();
        }

        for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
        {
            length += aMetadataBlockSeekTable.getLength();
        }

        for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
        {
            length += aMetadataBlockCueSheet.getLength();
        }

        for (MetadataBlock aMetadataBlockPadding : metadataBlockPadding)
        {
            length += aMetadataBlockPadding.getLength();
        }

        return length;
    
private intcomputeNeededRoom()

return
space required to write the metadata blocks that are part of Flac but are not part of tagdata in the normal sense.

        int length = 0;

        for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
        {
            length += aMetadataBlockApplication.getLength();
        }

        for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
        {
            length += aMetadataBlockSeekTable.getLength();
        }

        for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
        {
            length += aMetadataBlockCueSheet.getLength();
        }

        return length;
    
public voiddelete(java.io.RandomAccessFile raf, java.io.RandomAccessFile tempRaf)
Delete Tag from file

param
raf
param
tempRaf
throws
IOException
throws
CannotWriteException


                     
            
    
        //This will save the file without any Comment or PictureData blocks  
        FlacTag emptyTag = new FlacTag(null, new ArrayList<MetadataBlockDataPicture>());
        raf.seek(0);
        tempRaf.seek(0);
        write(emptyTag, raf, tempRaf);
    
public voidwrite(org.jaudiotagger.tag.Tag tag, java.io.RandomAccessFile raf, java.io.RandomAccessFile rafTemp)
Write tag to file

param
tag
param
raf
param
rafTemp
throws
CannotWriteException
throws
IOException

        logger.config("Writing tag");

        //Clean up old data
        streamInfoBlock=null;
        metadataBlockPadding.clear();
        metadataBlockApplication.clear();
        metadataBlockSeekTable.clear();
        metadataBlockCueSheet.clear();


        //Read existing data
        FlacStreamReader flacStream = new FlacStreamReader(raf);
        try
        {
            flacStream.findStream();
        }
        catch (CannotReadException cre)
        {
            throw new CannotWriteException(cre.getMessage());
        }

        boolean isLastBlock = false;
        while (!isLastBlock)
        {
            MetadataBlockHeader mbh = MetadataBlockHeader.readHeader(raf);
            switch (mbh.getBlockType())
            {
                case STREAMINFO:
                {
                    streamInfoBlock = new MetadataBlock(mbh,new MetadataBlockDataStreamInfo(mbh, raf));
                    break;
                }

                case VORBIS_COMMENT:
                case PADDING:
                case PICTURE:
                {
                    //All these will be replaced by the new metadata so we just treat as padding in order
                    //to determine how much space is already allocated in the file
                    raf.seek(raf.getFilePointer() + mbh.getDataLength());
                    MetadataBlockData mbd = new MetadataBlockDataPadding(mbh.getDataLength());
                    metadataBlockPadding.add(new MetadataBlock(mbh, mbd));
                    break;
                }
                case APPLICATION:
                {
                    MetadataBlockData mbd = new MetadataBlockDataApplication(mbh, raf);
                    metadataBlockApplication.add(new MetadataBlock(mbh, mbd));
                    break;
                }
                case SEEKTABLE:
                {
                    MetadataBlockData mbd = new MetadataBlockDataSeekTable(mbh, raf);
                    metadataBlockSeekTable.add(new MetadataBlock(mbh, mbd));
                    break;
                }
                case CUESHEET:
                {
                    MetadataBlockData mbd = new MetadataBlockDataCueSheet(mbh, raf);
                    metadataBlockCueSheet.add(new MetadataBlock(mbh, mbd));
                    break;
                }
                default:
                {
                    //What are the consequences of doing this
                    raf.seek(raf.getFilePointer() + mbh.getDataLength());
                    break;
                }
            }
            isLastBlock = mbh.isLastBlock();
        }

        //Number of bytes in the existing file available before audio data
        int availableRoom = computeAvailableRoom();

        //Minimum Size of the New tag data without padding         
        int newTagSize = tc.convert(tag).limit();

        //Number of bytes required for new tagdata and other metadata blocks
        int neededRoom = newTagSize + computeNeededRoom();

        //Go to start of Flac within file
        raf.seek(flacStream.getStartOfFlacInFile());

        logger.config("Writing tag available bytes:" + availableRoom + ":needed bytes:" + neededRoom);

        //There is enough room to fit the tag without moving the audio just need to
        //adjust padding accordingly need to allow space for padding header if padding required
        if ((availableRoom == neededRoom) || (availableRoom > neededRoom + MetadataBlockHeader.HEADER_LENGTH))
        {
            //Jump over Id3 (if exists) Flac and StreamInfoBlock
            raf.seek(flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH);

            //Write StreamInfo, we always write this first even if wasn't first in original spec
            raf.write(streamInfoBlock.getHeader().getBytesWithoutIsLastBlockFlag());
            raf.write(streamInfoBlock.getData().getBytes());

            //Write Application Blocks
            for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
            {
                raf.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
                raf.write(aMetadataBlockApplication.getData().getBytes());
            }

            //Write Seek Table Blocks
            for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
            {
                raf.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
                raf.write(aMetadataBlockSeekTable.getData().getBytes());
            }

            //Write Cue sheet Blocks
            for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
            {
                raf.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
                raf.write(aMetadataBlockCueSheet.getData().getBytes());
            }

            //Write tag (and padding)
            raf.getChannel().write(tc.convert(tag, availableRoom - neededRoom));
        }
        //Need to move audio
        else
        {
            //Skip to start of Audio

            //If Flac tag contains ID3header or something before start of official Flac header copy it over
            if(flacStream.getStartOfFlacInFile()>0)
            {
                raf.seek(0);
                rafTemp.getChannel().transferFrom(raf.getChannel(), 0, flacStream.getStartOfFlacInFile());
                rafTemp.seek(flacStream.getStartOfFlacInFile());
            }
            rafTemp.writeBytes(FlacStreamReader.FLAC_STREAM_IDENTIFIER);
            rafTemp.writeByte(0);  //To ensure never set Last-metadata-block flag even if was before

            int uptoStreamHeaderSize = flacStream.getStartOfFlacInFile()
                    + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH
                    + MetadataBlockHeader.BLOCK_TYPE_LENGTH;
            rafTemp.seek(uptoStreamHeaderSize);
            raf.seek(uptoStreamHeaderSize);

            rafTemp.getChannel().transferFrom(
                    raf.getChannel(),
                    uptoStreamHeaderSize,
                    MetadataBlockHeader.BLOCK_LENGTH + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH);

            int dataStartSize = flacStream.getStartOfFlacInFile()
                    + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH
                    + MetadataBlockHeader.HEADER_LENGTH
                    + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH;
            rafTemp.seek(dataStartSize);

            //Write all the metadatablocks
            for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
            {
                rafTemp.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
                rafTemp.write(aMetadataBlockApplication.getData().getBytes());
            }

            for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
            {
                rafTemp.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
                rafTemp.write(aMetadataBlockSeekTable.getData().getBytes());
            }

            for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
            {
                rafTemp.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
                rafTemp.write(aMetadataBlockCueSheet.getData().getBytes());
            }

            //Write tag data use default padding
            rafTemp.write(tc.convert(tag, FlacTagCreator.DEFAULT_PADDING).array());
            //Write audio to new file
            raf.seek(dataStartSize + availableRoom);

            //Issue #385
            //Transfer 'size' bytes from raf at its current position to rafTemp at position but do it in batches
            //to prevent OutOfMemory exceptions
            long amountToBeWritten=raf.getChannel().size() - raf.getChannel().position();
            long written   = 0;
            long chunksize = TagOptionSingleton.getInstance().getWriteChunkSize();
            long count = amountToBeWritten / chunksize;
            long mod   = amountToBeWritten % chunksize;
            for(int i = 0; i<count; i++)
            {
                written+=rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getChannel().position(), chunksize);
                rafTemp.getChannel().position(rafTemp.getChannel().position() + chunksize);
            }
            written+=rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getChannel().position(), mod);
            if(written!=amountToBeWritten)
            {
                throw new CannotWriteException("Was meant to write "+amountToBeWritten+" bytes but only written "+written+" bytes");
            }
        }