Coverage Report - org.jaudiotagger.audio.flac.FlacTagWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
FlacTagWriter
86%
82/95
76%
30/39
7
FlacTagWriter$1
100%
1/1
N/A
7
 
 1  
 /*
 2  
  * Entagged Audio Tag library
 3  
  * Copyright (c) 2003-2005 RaphaĆ«l Slinckx <raphael@slinckx.net>
 4  
  * 
 5  
  * This library is free software; you can redistribute it and/or
 6  
  * modify it under the terms of the GNU Lesser General Public
 7  
  * License as published by the Free Software Foundation; either
 8  
  * version 2.1 of the License, or (at your option) any later version.
 9  
  *  
 10  
  * This library is distributed in the hope that it will be useful,
 11  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
  * Lesser General Public License for more details.
 14  
  * 
 15  
  * You should have received a copy of the GNU Lesser General Public
 16  
  * License along with this library; if not, write to the Free Software
 17  
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 18  
  */
 19  
 package org.jaudiotagger.audio.flac;
 20  
 
 21  
 import org.jaudiotagger.audio.exceptions.CannotReadException;
 22  
 import org.jaudiotagger.audio.exceptions.CannotWriteException;
 23  
 import org.jaudiotagger.audio.flac.metadatablock.*;
 24  
 import org.jaudiotagger.tag.Tag;
 25  
 import org.jaudiotagger.tag.flac.FlacTag;
 26  
 
 27  
 import java.io.IOException;
 28  
 import java.io.RandomAccessFile;
 29  
 import java.util.ArrayList;
 30  
 import java.util.List;
 31  
 import java.util.logging.Logger;
 32  
 
 33  
 
 34  
 /**
 35  
  * Write Flac Tag
 36  
  */
 37  4
 public class FlacTagWriter
 38  
 {
 39  
     // Logger Object
 40  4
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.flac");
 41  
 
 42  4
     private List<MetadataBlock> metadataBlockPadding = new ArrayList<MetadataBlock>(1);
 43  4
     private List<MetadataBlock> metadataBlockApplication = new ArrayList<MetadataBlock>(1);
 44  4
     private List<MetadataBlock> metadataBlockSeekTable = new ArrayList<MetadataBlock>(1);
 45  4
     private List<MetadataBlock> metadataBlockCueSheet = new ArrayList<MetadataBlock>(1);
 46  
 
 47  4
     private FlacTagCreator tc = new FlacTagCreator();
 48  4
     private FlacTagReader reader = new FlacTagReader();
 49  
 
 50  
     /**
 51  
      * Delete Tag from file
 52  
      *
 53  
      * @param raf
 54  
      * @param tempRaf
 55  
      * @throws IOException
 56  
      * @throws CannotWriteException
 57  
      */
 58  
     public void delete(RandomAccessFile raf, RandomAccessFile tempRaf) throws IOException, CannotWriteException
 59  
     {
 60  
         //This will save the file without any Comment or PictureData blocks  
 61  8
         FlacTag emptyTag = new FlacTag(null, new ArrayList<MetadataBlockDataPicture>());
 62  8
         raf.seek(0);
 63  8
         tempRaf.seek(0);
 64  8
         write(emptyTag, raf, tempRaf);
 65  8
     }
 66  
 
 67  
     /**
 68  
      * Write tag to file
 69  
      *
 70  
      * @param tag
 71  
      * @param raf
 72  
      * @param rafTemp
 73  
      * @throws CannotWriteException
 74  
      * @throws IOException
 75  
      */
 76  
     public void write(Tag tag, RandomAccessFile raf, RandomAccessFile rafTemp) throws CannotWriteException, IOException
 77  
     {
 78  38
         logger.info("Writing tag");
 79  
 
 80  
         //Clean up old data
 81  38
         metadataBlockPadding.clear();
 82  38
         metadataBlockApplication.clear();
 83  38
         metadataBlockSeekTable.clear();
 84  38
         metadataBlockCueSheet.clear();
 85  
 
 86  
 
 87  
         //Read existing data
 88  38
         FlacStreamReader flacStream = new FlacStreamReader(raf);
 89  
         try
 90  
         {
 91  38
             flacStream.findStream();
 92  
         }
 93  0
         catch (CannotReadException cre)
 94  
         {
 95  0
             throw new CannotWriteException(cre.getMessage());
 96  38
         }
 97  
 
 98  38
         boolean isLastBlock = false;
 99  238
         while (!isLastBlock)
 100  
         {
 101  200
             MetadataBlockHeader mbh = MetadataBlockHeader.readHeader(raf);
 102  200
             switch (mbh.getBlockType())
 103  
             {
 104  
                 case VORBIS_COMMENT:
 105  
                 case PADDING:
 106  
                 case PICTURE:
 107  
                 {
 108  
                     //All these will be replaced by the new metadata so we just treat as padding in order
 109  
                     //to determine how much space is already allocated in the file
 110  120
                     raf.seek(raf.getFilePointer() + mbh.getDataLength());
 111  120
                     MetadataBlockData mbd = new MetadataBlockDataPadding(mbh.getDataLength());
 112  120
                     metadataBlockPadding.add(new MetadataBlock(mbh, mbd));
 113  120
                     break;
 114  
                 }
 115  
                 case APPLICATION:
 116  
                 {
 117  0
                     MetadataBlockData mbd = new MetadataBlockDataApplication(mbh, raf);
 118  0
                     metadataBlockApplication.add(new MetadataBlock(mbh, mbd));
 119  0
                     break;
 120  
                 }
 121  
                 case SEEKTABLE:
 122  
                 {
 123  38
                     MetadataBlockData mbd = new MetadataBlockDataSeekTable(mbh, raf);
 124  38
                     metadataBlockSeekTable.add(new MetadataBlock(mbh, mbd));
 125  38
                     break;
 126  
                 }
 127  
                 case CUESHEET:
 128  
                 {
 129  4
                     MetadataBlockData mbd = new MetadataBlockDataCueSheet(mbh, raf);
 130  4
                     metadataBlockCueSheet.add(new MetadataBlock(mbh, mbd));
 131  4
                     break;
 132  
                 }
 133  
                 default:
 134  
                 {
 135  
                     //What are the consequences of doing this
 136  38
                     raf.seek(raf.getFilePointer() + mbh.getDataLength());
 137  
                     break;
 138  
                 }
 139  
             }
 140  200
             isLastBlock = mbh.isLastBlock();
 141  200
         }
 142  
 
 143  
         //Number of bytes in the existing file available before audio data
 144  38
         int availableRoom = computeAvailableRoom();
 145  
 
 146  
         //Minimum Size of the New tag data without padding         
 147  38
         int newTagSize = tc.convert(tag).limit();
 148  
 
 149  
         //Number of bytes required for new tagdata and other metadata blocks
 150  38
         int neededRoom = newTagSize + computeNeededRoom();
 151  
 
 152  
         //Go to start of Flac within file
 153  38
         raf.seek(flacStream.getStartOfFlacInFile());
 154  
 
 155  38
         logger.info("Writing tag available bytes:" + availableRoom + ":needed bytes:" + neededRoom);
 156  
 
 157  
         //There is enough room to fit the tag without moving the audio just need to
 158  
         //adjust padding accordingly need to allow space for padding header if padding required
 159  38
         if ((availableRoom == neededRoom) || (availableRoom > neededRoom + MetadataBlockHeader.HEADER_LENGTH))
 160  
         {
 161  
             //Jump over Id3 (if exists) Flac and StreamInfoBlock
 162  29
             raf.seek(flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH + MetadataBlockHeader.HEADER_LENGTH + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH);
 163  
 
 164  
             //Write Application Blocks
 165  29
             for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
 166  
             {
 167  0
                 raf.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
 168  0
                 raf.write(aMetadataBlockApplication.getData().getBytes());
 169  
             }
 170  
 
 171  
             //Write Seek Table Blocks
 172  29
             for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
 173  
             {
 174  29
                 raf.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
 175  29
                 raf.write(aMetadataBlockSeekTable.getData().getBytes());
 176  
             }
 177  
 
 178  
             //Write Cue sheet Blocks
 179  29
             for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
 180  
             {
 181  4
                 raf.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
 182  4
                 raf.write(aMetadataBlockCueSheet.getData().getBytes());
 183  
             }
 184  
 
 185  
             //Write tag (and padding)
 186  29
             raf.getChannel().write(tc.convert(tag, availableRoom - neededRoom));
 187  
         }
 188  
         //Need to move audio
 189  
         else
 190  
         {
 191  
             //Skip to start of Audio
 192  
             //Write FlacStreamReader and StreamIfoMetablock to new file
 193  9
             int dataStartSize = flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH + MetadataBlockHeader.HEADER_LENGTH + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH;
 194  9
             raf.seek(0);
 195  9
             rafTemp.getChannel().transferFrom(raf.getChannel(), 0, dataStartSize);
 196  9
             rafTemp.seek(dataStartSize);
 197  
 
 198  
             //Write all the metadatablocks
 199  9
             for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
 200  
             {
 201  0
                 rafTemp.write(aMetadataBlockApplication.getHeader().getBytesWithoutIsLastBlockFlag());
 202  0
                 rafTemp.write(aMetadataBlockApplication.getData().getBytes());
 203  
             }
 204  
 
 205  9
             for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
 206  
             {
 207  9
                 rafTemp.write(aMetadataBlockSeekTable.getHeader().getBytesWithoutIsLastBlockFlag());
 208  9
                 rafTemp.write(aMetadataBlockSeekTable.getData().getBytes());
 209  
             }
 210  
 
 211  9
             for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
 212  
             {
 213  0
                 rafTemp.write(aMetadataBlockCueSheet.getHeader().getBytesWithoutIsLastBlockFlag());
 214  0
                 rafTemp.write(aMetadataBlockCueSheet.getData().getBytes());
 215  
             }
 216  
 
 217  
             //Write tag data use default padding
 218  9
             rafTemp.write(tc.convert(tag, FlacTagCreator.DEFAULT_PADDING).array());
 219  
             //Write audio to new file
 220  9
             raf.seek(dataStartSize + availableRoom);
 221  9
             rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getChannel().position(), raf.getChannel().size());
 222  
         }
 223  38
     }
 224  
 
 225  
     /**
 226  
      * @return space currently availble for writing all Flac metadatablocks exceprt for StreamInfo which is fixed size
 227  
      */
 228  
     private int computeAvailableRoom()
 229  
     {
 230  38
         int length = 0;
 231  
 
 232  38
         for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
 233  
         {
 234  0
             length += aMetadataBlockApplication.getLength();
 235  
         }
 236  
 
 237  38
         for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
 238  
         {
 239  38
             length += aMetadataBlockSeekTable.getLength();
 240  
         }
 241  
 
 242  38
         for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
 243  
         {
 244  4
             length += aMetadataBlockCueSheet.getLength();
 245  
         }
 246  
 
 247  38
         for (MetadataBlock aMetadataBlockPadding : metadataBlockPadding)
 248  
         {
 249  120
             length += aMetadataBlockPadding.getLength();
 250  
         }
 251  
 
 252  38
         return length;
 253  
     }
 254  
 
 255  
     /**
 256  
      * @return space required to write the metadata blocks that are part of Flac but are not part of tagdata
 257  
      *         in the normal sense.
 258  
      */
 259  
     private int computeNeededRoom()
 260  
     {
 261  38
         int length = 0;
 262  
 
 263  38
         for (MetadataBlock aMetadataBlockApplication : metadataBlockApplication)
 264  
         {
 265  0
             length += aMetadataBlockApplication.getLength();
 266  
         }
 267  
 
 268  38
         for (MetadataBlock aMetadataBlockSeekTable : metadataBlockSeekTable)
 269  
         {
 270  38
             length += aMetadataBlockSeekTable.getLength();
 271  
         }
 272  
 
 273  38
         for (MetadataBlock aMetadataBlockCueSheet : metadataBlockCueSheet)
 274  
         {
 275  4
             length += aMetadataBlockCueSheet.getLength();
 276  
         }
 277  
 
 278  38
         return length;
 279  
     }
 280  
 }
 281