package org.jaudiotagger.audio.asf.io;
import org.jaudiotagger.audio.asf.data.GUID;
import org.jaudiotagger.audio.asf.util.Utils;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
/**
* This class creates a modified copy of an ASF file.<br>
*
* @author Christian Laireiter
*/
public class AsfStreamer {
/**
* Simply copies a chunk from <code>source</code> to
* <code>destination</code>.<br>
* The method assumes, that the GUID has already been read and will write
* the provided one to the destination.<br>
* The chunk length however will be read and used to determine the amount of
* bytes to copy.
*
* @param guid
* GUID of the current chunk.
* @param source
* source of an ASF chunk, which is to be located at the chunk
* length field.
* @param destination
* the destination to copy the chunk to.
* @throws IOException
* on I/O errors.
*/
private void copyChunk(final GUID guid, final InputStream source,
final OutputStream destination) throws IOException {
final long chunkSize = Utils.readUINT64(source);
destination.write(guid.getBytes());
Utils.writeUINT64(chunkSize, destination);
Utils.copy(source, destination, chunkSize - 24);
}
/**
* Reads the <code>source</code> and applies the modifications provided by
* the given <code>modifiers</code>, and puts it to <code>dest</code>.<br>
* Each {@linkplain ChunkModifier modifier} is used only once, so if one
* should be used multiple times, it should be added multiple times into the
* list.<br>
*
* @param source
* the source ASF file
* @param dest
* the destination to write the modified version to.
* @param modifiers
* list of chunk modifiers to apply.
* @throws IOException
* on I/O errors.
*/
public void createModifiedCopy(final InputStream source,
final OutputStream dest, final List<ChunkModifier> modifiers)
throws IOException {
final List<ChunkModifier> modders = new ArrayList<ChunkModifier>();
if (modifiers != null) {
modders.addAll(modifiers);
}
// Read and check ASF GUID
final GUID readGUID = Utils.readGUID(source);
if (GUID.GUID_HEADER.equals(readGUID)) {
// used to calculate differences
long totalDiff = 0;
long chunkDiff = 0;
// read header information
final long headerSize = Utils.readUINT64(source);
final long chunkCount = Utils.readUINT32(source);
final byte[] reserved = new byte[2];
reserved[0] = (byte) (source.read() & 0xFF);
reserved[1] = (byte) (source.read() & 0xFF);
/*
* bos will get all unmodified and modified header chunks. This is
* necessary, because the header chunk (and file properties chunk)
* need to be adjusted but are written in front of the others.
*/
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
// fileHeader will get the binary representation of the file
// properties chunk, without GUID
byte[] fileHeader = null;
// Iterate through all chunks
for (long i = 0; i < chunkCount; i++) {
// Read GUID
final GUID curr = Utils.readGUID(source);
// special case for file properties chunk
if (GUID.GUID_FILE.equals(curr)) {
final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
final long size = Utils.readUINT64(source);
Utils.writeUINT64(size, tmp);
Utils.copy(source, tmp, size - 24);
fileHeader = tmp.toByteArray();
} else {
/*
* Now look for ChunkModifier objects which modify the
* current chunk
*/
boolean handled = false;
for (int j = 0; j < modders.size() && !handled; j++) {
if (modders.get(j).isApplicable(curr)) {
// alter current chunk
final ModificationResult result = modders.get(j)
.modify(curr, source, bos);
// remember size differences.
chunkDiff += result.getChunkCountDifference();
totalDiff += result.getByteDifference();
// remove current modifier from index.
modders.remove(j);
handled = true;
}
}
if (!handled) {
// copy chunks which are not modified.
copyChunk(curr, source, bos);
}
}
}
// Now apply the left modifiers.
for (final ChunkModifier curr : modders) {
// chunks, which were not in the source file, will be added to
// the destination
final ModificationResult result = curr.modify(null, null, bos);
chunkDiff += result.getChunkCountDifference();
totalDiff += result.getByteDifference();
}
/*
* Now all header objects have been read or manipulated and stored
* in the internal buffer (bos).
*/
// write ASF GUID
dest.write(readGUID.getBytes());
// write altered header object size
Utils.writeUINT64(headerSize + totalDiff, dest);
// write altered number of chunks
Utils.writeUINT32(chunkCount + chunkDiff, dest);
// write the reserved 2 bytes (0x01,0x02).
dest.write(reserved);
// write the new file header
modifyFileHeader(new ByteArrayInputStream(fileHeader), dest,
totalDiff);
// write the header objects (chunks)
dest.write(bos.toByteArray());
// copy the rest of the file (data and index)
Utils.flush(source, dest);
} else {
throw new IllegalArgumentException("No ASF header object.");
}
}
/**
* This is a slight variation of
* {@link #copyChunk(GUID, InputStream, OutputStream)}, it only handles file
* property chunks correctly.<br>
* The copied chunk will have the file size field modified by the given
* <code>fileSizeDiff</code> value.
*
* @param source
* source of file properties chunk, located at its chunk length
* field.
* @param destination
* the destination to copy the chunk to.
* @param fileSizeDiff
* the difference which should be applied. (negative values would
* subtract the stored file size)
* @throws IOException
* on I/O errors.
*/
private void modifyFileHeader(final InputStream source,
final OutputStream destination, final long fileSizeDiff)
throws IOException {
destination.write(GUID.GUID_FILE.getBytes());
final long chunkSize = Utils.readUINT64(source);
Utils.writeUINT64(chunkSize, destination);
destination.write(Utils.readGUID(source).getBytes());
final long fileSize = Utils.readUINT64(source);
Utils.writeUINT64(fileSize + fileSizeDiff, destination);
Utils.copy(source, destination, chunkSize - 48);
}
}