Coverage Report - org.jaudiotagger.audio.asf.io.AsfStreamer
 
Classes in this File Line Coverage Branch Coverage Complexity
AsfStreamer
98%
61/62
83%
15/18
0
 
 1  
 package org.jaudiotagger.audio.asf.io;
 2  
 
 3  
 import org.jaudiotagger.audio.asf.data.GUID;
 4  
 import org.jaudiotagger.audio.asf.util.Utils;
 5  
 
 6  
 import java.io.*;
 7  
 import java.util.ArrayList;
 8  
 import java.util.List;
 9  
 
 10  
 
 11  
 /**
 12  
  * This class creates a modified copy of an ASF file.<br>  
 13  
  * 
 14  
  * @author Christian Laireiter
 15  
  */
 16  29
 public class AsfStreamer
 17  
 {
 18  
 
 19  
     /**
 20  
      * Simply copies a chunk from <code>source</code> to <code>destination</code>.<br>
 21  
      * The method assumes, that the GUID has already been read and will write the provided one to
 22  
      * the destination.<br> 
 23  
      * The chunk length however will be read and used to determine the amount of bytes to copy.  
 24  
      * 
 25  
      * @param guid GUID of the current cunk.
 26  
      * @param source source of an ASF chunk, which is to be located at the chunk length field.
 27  
      * @param destination the destination to copy the chunk to.
 28  
      * @throws IOException on I/O errors.
 29  
      */
 30  
     private void copyChunk(GUID guid, InputStream source, OutputStream destination) throws IOException
 31  
     {
 32  112
         long chunkSize = Utils.readUINT64(source);
 33  112
         destination.write(guid.getBytes());
 34  112
         Utils.writeUINT64(chunkSize, destination);
 35  112
         Utils.copy(source, destination, chunkSize - 24);
 36  112
     }
 37  
 
 38  
     /**
 39  
      * Reads the <code>source</code> and applies the modifications provided by the given <code>modifiers</code>, 
 40  
      * and puts it to <code>dest</code>.<br>
 41  
      * Each {@linkplain ChunkModifier modifier} is used only once, so if one should be used multiple times, it should
 42  
      * be added multiple times into the list.<br>
 43  
      * 
 44  
      * @param source the source ASF file
 45  
      * @param dest the destination to write the modified version to.
 46  
      * @param modifiers list of chunk modifiers to apply. 
 47  
      * @throws IOException on I/O errors.
 48  
      */
 49  
     public void createModifiedCopy(InputStream source, OutputStream dest, List<ChunkModifier> modifiers) throws IOException
 50  
     {
 51  29
         List<ChunkModifier> modders = new ArrayList<ChunkModifier>();
 52  29
         if (modifiers != null)
 53  
         {
 54  29
             modders.addAll(modifiers);
 55  
         }
 56  
         // Read and check ASF GUID
 57  29
         GUID readGUID = Utils.readGUID(source);
 58  29
         if (GUID.GUID_HEADER.equals(readGUID))
 59  
         {
 60  
             // used to calculate differences
 61  29
             long totalDiff = 0;
 62  29
             long chunkDiff = 0;
 63  
 
 64  
             // read header information
 65  29
             long headerSize = Utils.readUINT64(source);
 66  29
             long chunkCount = Utils.readUINT32(source);
 67  29
             byte[] reserved = new byte[2];
 68  29
             reserved[0] = (byte) (source.read() & 0xFF);
 69  29
             reserved[1] = (byte) (source.read() & 0xFF);
 70  
 
 71  
             /*
 72  
              * bos will get all unmodified and modified header chunks. This is necessary, because
 73  
              * the header chunk (and file properties chunk) need to be adjusted but are written in front of the others.
 74  
              */
 75  29
             final ByteArrayOutputStream bos = new ByteArrayOutputStream();
 76  
             // fileHeader will get the binary representation of the file properties chunk, without GUID
 77  29
             byte[] fileHeader = null;
 78  
 
 79  
             // Iterate through all chunks
 80  201
             for (long i = 0; i < chunkCount; i++)
 81  
             {
 82  
                 // Read GUID
 83  172
                 GUID curr = Utils.readGUID(source);
 84  
                 // special case for file properties chunk
 85  172
                 if (GUID.GUID_FILE.equals(curr))
 86  
                 {
 87  29
                     final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
 88  29
                     long size = Utils.readUINT64(source);
 89  29
                     Utils.writeUINT64(size, tmp);
 90  29
                     Utils.copy(source, tmp, size - 24);
 91  29
                     fileHeader = tmp.toByteArray();
 92  29
                 }
 93  
                 else
 94  
                 {
 95  
                     /*
 96  
                      * Now look for ChunkModifier objects which modify the current chunk
 97  
                      */
 98  143
                     boolean handled = false;
 99  303
                     for (int j = 0; j < modders.size() && !handled; j++)
 100  
                     {
 101  160
                         if (modders.get(j).isApplicable(curr))
 102  
                         {
 103  
                             // alter current chunk
 104  31
                             final ModificationResult result = modders.get(j).modify(curr, source, bos);
 105  
                             // remember size differences.
 106  31
                             chunkDiff += result.getChunkCountDifference();
 107  31
                             totalDiff += result.getByteDifference();
 108  
                             // remove current modifier from index.
 109  31
                             modders.remove(j);
 110  31
                             handled = true;
 111  
                         }
 112  
                     }
 113  143
                     if (!handled)
 114  
                     {
 115  
                         // copy chunks which are not modified.
 116  112
                         copyChunk(curr, source, bos);
 117  
                     }
 118  
                 }
 119  
             }
 120  
             // Now apply the left modifiers. 
 121  29
             for (ChunkModifier curr : modders)
 122  
             {
 123  
                 // chunks, which were not in the source file, will be added to the destination
 124  11
                 final ModificationResult result = curr.modify(null, null, bos);
 125  11
                 chunkDiff += result.getChunkCountDifference();
 126  11
                 totalDiff += result.getByteDifference();
 127  11
             }
 128  
             /*
 129  
              * Now all header objects have been read or manipulated and stored in the internal buffer (bos). 
 130  
              */
 131  
             // write ASF GUID
 132  29
             dest.write(readGUID.getBytes());
 133  
             // write altered header object size
 134  29
             Utils.writeUINT64(headerSize + totalDiff, dest);
 135  
             // write altered number of chunks
 136  29
             Utils.writeUINT32(chunkCount + chunkDiff, dest);
 137  
             // write the reserved 2 bytes (0x01,0x02).
 138  29
             dest.write(reserved);
 139  
             // write the new file header
 140  29
             modifyFileHeader(new ByteArrayInputStream(fileHeader), dest, totalDiff);
 141  
             // write the header objects (chunks)
 142  29
             dest.write(bos.toByteArray());
 143  
             // copy the rest of the file (data and index)
 144  29
             Utils.flush(source, dest);
 145  29
         }
 146  
         else
 147  
         {
 148  0
             throw new IllegalArgumentException("No ASF header object.");
 149  
         }
 150  29
     }
 151  
 
 152  
     /**
 153  
      * This is a slight variation of {@link #copyChunk(GUID, InputStream, OutputStream)}, it only handles file
 154  
      * property chunks correctly.<br>
 155  
      * The copied chunk will have the file size field modified by the given <code>fileSizeDiff</code> value.
 156  
      * 
 157  
      * @param source source of file properties chunk, located at its chunk length field. 
 158  
      * @param destination the destination to copy the chunk to.
 159  
      * @param fileSizeDiff the difference which should be applied. (negative values would subtract the stored file size)
 160  
      * @throws IOException on I/O errors.
 161  
      */
 162  
     private void modifyFileHeader(InputStream source, OutputStream destination, long fileSizeDiff) throws IOException
 163  
     {
 164  29
         destination.write(GUID.GUID_FILE.getBytes());
 165  29
         long chunkSize = Utils.readUINT64(source);
 166  29
         Utils.writeUINT64(chunkSize, destination);
 167  29
         destination.write(Utils.readGUID(source).getBytes());
 168  29
         long fileSize = Utils.readUINT64(source);
 169  29
         Utils.writeUINT64(fileSize + fileSizeDiff, destination);
 170  29
         Utils.copy(source, destination, chunkSize - 48);
 171  29
     }
 172  
 
 173  
 }