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