Coverage Report - org.jaudiotagger.audio.flac.FlacTagWriter
 
Classes in this File Line Coverage Branch Coverage Complexity
FlacTagWriter
86%
82/95
77%
30/39
0
FlacTagWriter$1
100%
1/1
N/A
0
 
 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  42
 public class FlacTagWriter
 38  
 {
 39  
     // Logger Object
 40  42
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.flac");
 41  
 
 42  42
     private List<MetadataBlock> metadataBlockPadding = new ArrayList<MetadataBlock>(1);
 43  42
     private List<MetadataBlock> metadataBlockApplication = new ArrayList<MetadataBlock>(1);
 44  42
     private List<MetadataBlock> metadataBlockSeekTable = new ArrayList<MetadataBlock>(1);
 45  42
     private List<MetadataBlock> metadataBlockCueSheet = new ArrayList<MetadataBlock>(1);
 46  
 
 47  42
     private FlacTagCreator tc = new FlacTagCreator();
 48  42
     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  2
         FlacTag emptyTag = new FlacTag(null, new ArrayList<MetadataBlockDataPicture>());
 62  2
         raf.seek(0);
 63  2
         tempRaf.seek(0);
 64  2
         write(emptyTag, raf, tempRaf);
 65  2
     }
 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  10
         logger.info("Writing tag");
 79  
 
 80  
         //Clean up old data
 81  10
         metadataBlockPadding.clear();
 82  10
         metadataBlockApplication.clear();
 83  10
         metadataBlockSeekTable.clear();
 84  10
         metadataBlockCueSheet.clear();
 85  
 
 86  
 
 87  
         byte[] b;
 88  
         //Read existing data
 89  10
         FlacStreamReader flacStream = new FlacStreamReader(raf);
 90  
         try
 91  
         {
 92  10
             flacStream.findStream();
 93  
         }
 94  0
         catch (CannotReadException cre)
 95  
         {
 96  0
             throw new CannotWriteException(cre.getMessage());
 97  10
         }
 98  
 
 99  10
         boolean isLastBlock = false;
 100  60
         while (!isLastBlock)
 101  
         {
 102  50
             MetadataBlockHeader mbh = MetadataBlockHeader.readHeader(raf);
 103  4
             switch (mbh.getBlockType())
 104  
             {
 105  
                 case VORBIS_COMMENT:
 106  
                 case PADDING:
 107  
                 case PICTURE:
 108  
                 {
 109  
                     //All these will be replaced by the new metadata so we just treat as padding in order
 110  
                     //to determine how much space is already allocated in the file
 111  29
                     raf.seek(raf.getFilePointer() + mbh.getDataLength());
 112  29
                     MetadataBlockData mbd = new MetadataBlockDataPadding(mbh.getDataLength());
 113  29
                     metadataBlockPadding.add(new MetadataBlock(mbh, mbd));
 114  29
                     break;
 115  
                 }
 116  
                 case APPLICATION:
 117  
                 {
 118  0
                     MetadataBlockData mbd = new MetadataBlockDataApplication(mbh, raf);
 119  0
                     metadataBlockApplication.add(new MetadataBlock(mbh, mbd));
 120  0
                     break;
 121  
                 }
 122  
                 case SEEKTABLE:
 123  
                 {
 124  10
                     MetadataBlockData mbd = new MetadataBlockDataSeekTable(mbh, raf);
 125  10
                     metadataBlockSeekTable.add(new MetadataBlock(mbh, mbd));
 126  10
                     break;
 127  
                 }
 128  
                 case CUESHEET:
 129  
                 {
 130  1
                     MetadataBlockData mbd = new MetadataBlockDataCueSheet(mbh, raf);
 131  1
                     metadataBlockCueSheet.add(new MetadataBlock(mbh, mbd));
 132  1
                     break;
 133  
                 }
 134  
                 default:
 135  
                 {
 136  
                     //What are the consequences of doing this
 137  10
                     raf.seek(raf.getFilePointer() + mbh.getDataLength());
 138  
                     break;
 139  
                 }
 140  
             }
 141  50
             isLastBlock = mbh.isLastBlock();
 142  50
         }
 143  
 
 144  
         //Number of bytes in the existing file available before audio data
 145  10
         int availableRoom = computeAvailableRoom();
 146  
 
 147  
         //Minimum Size of the New tag data without padding         
 148  10
         int newTagSize = tc.convert(tag).limit();
 149  
 
 150  
         //Number of bytes required for new tagdata and other metadata blocks
 151  10
         int neededRoom = newTagSize + computeNeededRoom();
 152  
 
 153  
         //Go to start of Flac within file
 154  10
         raf.seek(flacStream.getStartOfFlacInFile());
 155  
 
 156  10
         logger.info("Writing tag available bytes:" + availableRoom + ":needed bytes:" + neededRoom);
 157  
 
 158  
         //There is enough room to fit the tag without moving the audio just need to
 159  
         //adjust padding accordingly need to allow space for padding header if padding required
 160  10
         if ((availableRoom == neededRoom) || (availableRoom > neededRoom + MetadataBlockHeader.HEADER_LENGTH))
 161  
         {
 162  
             //Jump over Id3 (if exists) Flac and StreamInfoBlock
 163  7
             raf.seek(flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH + MetadataBlockHeader.HEADER_LENGTH + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH);
 164  
 
 165  
             //Write Application Blocks
 166  7
             for (int i = 0; i < metadataBlockApplication.size(); i++)
 167  
             {
 168  0
                 raf.write(metadataBlockApplication.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 169  0
                 raf.write(metadataBlockApplication.get(i).getData().getBytes());
 170  
             }
 171  
 
 172  
             //Write Seek Table Blocks
 173  14
             for (int i = 0; i < metadataBlockSeekTable.size(); i++)
 174  
             {
 175  7
                 raf.write(metadataBlockSeekTable.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 176  7
                 raf.write(metadataBlockSeekTable.get(i).getData().getBytes());
 177  
             }
 178  
 
 179  
             //Write Cue sheet Blocks
 180  8
             for (int i = 0; i < metadataBlockCueSheet.size(); i++)
 181  
             {
 182  1
                 raf.write(metadataBlockCueSheet.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 183  1
                 raf.write(metadataBlockCueSheet.get(i).getData().getBytes());
 184  
             }
 185  
 
 186  
             //Write tag (and padding)
 187  7
             raf.getChannel().write(tc.convert(tag, availableRoom - neededRoom));
 188  
         }
 189  
         //Need to move audio
 190  
         else
 191  
         {
 192  
             //Skip to start of Audio
 193  
             //Write FlacStreamReader and StreamIfoMetablock to new file
 194  3
             int dataStartSize = flacStream.getStartOfFlacInFile() + FlacStreamReader.FLAC_STREAM_IDENTIFIER_LENGTH + MetadataBlockHeader.HEADER_LENGTH + MetadataBlockDataStreamInfo.STREAM_INFO_DATA_LENGTH;
 195  3
             raf.seek(0);
 196  3
             rafTemp.getChannel().transferFrom(raf.getChannel(), 0, dataStartSize);
 197  3
             rafTemp.seek(dataStartSize);
 198  
 
 199  
             //Write all the metadatablocks
 200  3
             for (int i = 0; i < metadataBlockApplication.size(); i++)
 201  
             {
 202  0
                 rafTemp.write(metadataBlockApplication.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 203  0
                 rafTemp.write(metadataBlockApplication.get(i).getData().getBytes());
 204  
             }
 205  
 
 206  6
             for (int i = 0; i < metadataBlockSeekTable.size(); i++)
 207  
             {
 208  3
                 rafTemp.write(metadataBlockSeekTable.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 209  3
                 rafTemp.write(metadataBlockSeekTable.get(i).getData().getBytes());
 210  
             }
 211  
 
 212  3
             for (int i = 0; i < metadataBlockCueSheet.size(); i++)
 213  
             {
 214  0
                 rafTemp.write(metadataBlockCueSheet.get(i).getHeader().getBytesWithoutIsLastBlockFlag());
 215  0
                 rafTemp.write(metadataBlockCueSheet.get(i).getData().getBytes());
 216  
             }
 217  
 
 218  
             //Write tag data use default padding
 219  3
             rafTemp.write(tc.convert(tag, FlacTagCreator.DEFAULT_PADDING).array());
 220  
             //Write audio to new file
 221  3
             raf.seek(dataStartSize + availableRoom);
 222  3
             rafTemp.getChannel().transferFrom(raf.getChannel(), rafTemp.getChannel().position(), raf.getChannel().size());
 223  
         }
 224  10
     }
 225  
 
 226  
     /**
 227  
      * @return space currently availble for writing all Flac metadatablocks exceprt for StreamInfo which is fixed size
 228  
      */
 229  
     private int computeAvailableRoom()
 230  
     {
 231  10
         int length = 0;
 232  
 
 233  10
         for (int i = 0; i < metadataBlockApplication.size(); i++)
 234  
         {
 235  0
             length += metadataBlockApplication.get(i).getLength();
 236  
         }
 237  
 
 238  20
         for (int i = 0; i < metadataBlockSeekTable.size(); i++)
 239  
         {
 240  10
             length += metadataBlockSeekTable.get(i).getLength();
 241  
         }
 242  
 
 243  11
         for (int i = 0; i < metadataBlockCueSheet.size(); i++)
 244  
         {
 245  1
             length += metadataBlockCueSheet.get(i).getLength();
 246  
         }
 247  
 
 248  39
         for (int i = 0; i < metadataBlockPadding.size(); i++)
 249  
         {
 250  29
             length += metadataBlockPadding.get(i).getLength();
 251  
         }
 252  
 
 253  10
         return length;
 254  
     }
 255  
 
 256  
     /**
 257  
      * @return space required to write the metadata blocks that are part of Flac but are not part of tagdata
 258  
      *         in the normal sense.
 259  
      */
 260  
     private int computeNeededRoom()
 261  
     {
 262  10
         int length = 0;
 263  
 
 264  10
         for (int i = 0; i < metadataBlockApplication.size(); i++)
 265  
         {
 266  0
             length += metadataBlockApplication.get(i).getLength();
 267  
         }
 268  
 
 269  20
         for (int i = 0; i < metadataBlockSeekTable.size(); i++)
 270  
         {
 271  10
             length += metadataBlockSeekTable.get(i).getLength();
 272  
         }
 273  
 
 274  11
         for (int i = 0; i < metadataBlockCueSheet.size(); i++)
 275  
         {
 276  1
             length += metadataBlockCueSheet.get(i).getLength();
 277  
         }
 278  
 
 279  10
         return length;
 280  
     }
 281  
 }
 282