/*
* 21.04.2004 Original verion. davagin@udm.ru.
*-----------------------------------------------------------------------
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*----------------------------------------------------------------------
*/
package davaguine.jmac.core;
import davaguine.jmac.decoder.IAPEDecompress;
import davaguine.jmac.encoder.IAPECompress;
import davaguine.jmac.info.APEFileInfo;
import davaguine.jmac.info.InputSource;
import davaguine.jmac.info.WaveFormat;
import davaguine.jmac.tools.*;
import java.io.IOException;
import java.io.RandomAccessFile;
/**
* Author: Dmitry Vaguine
* Date: 11.05.2004
* Time: 16:26:19
*/
public class APESimple {
public final static int UNMAC_DECODER_OUTPUT_APE = 2;
public final static int UNMAC_DECODER_OUTPUT_WAV = 1;
public final static int UNMAC_DECODER_OUTPUT_NONE = 0;
public final static int BLOCKS_PER_DECODE = 9216;
public static void VerifyFile(String pInputFilename, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
// error check the function parameters
if (pInputFilename == null)
throw new JMACException("Bad Parameters");
// see if we can quick verify
File file = File.createFile(pInputFilename, "r");
IAPEDecompress spAPEDecompress = null;
try {
spAPEDecompress = IAPEDecompress.CreateIAPEDecompress(file);
APEFileInfo pInfo = spAPEDecompress.getApeInfoInternalInfo();
if ((pInfo.nVersion < 3980) || (pInfo.spAPEDescriptor == null))
DecompressCore(spAPEDecompress, null, UNMAC_DECODER_OUTPUT_NONE, -1, progressor);
else {
MD5 MD5Helper = new MD5();
File pIO = spAPEDecompress.getApeInfoIoSource();
int nHead = (int) (pInfo.nJunkHeaderBytes + pInfo.spAPEDescriptor.nDescriptorBytes);
int nStart = (int) (nHead + pInfo.spAPEDescriptor.nHeaderBytes + pInfo.spAPEDescriptor.nSeekTableBytes);
pIO.seek(nHead);
int nHeadBytes = nStart - nHead;
byte[] spHeadBuffer = new byte[nHeadBytes];
pIO.readFully(spHeadBuffer);
int nBytesLeft = (int) (pInfo.spAPEDescriptor.nHeaderDataBytes + pInfo.spAPEDescriptor.nAPEFrameDataBytes + pInfo.spAPEDescriptor.nTerminatingDataBytes);
// create the progress helper
ProgressHelper spMACProgressHelper = new ProgressHelper(nBytesLeft / 16384, progressor);
byte[] spBuffer = new byte[16384];
int nBytesRead = 1;
while ((nBytesLeft > 0) && (nBytesRead > 0)) {
int nBytesToRead = Math.min(16384, nBytesLeft);
nBytesRead = pIO.read(spBuffer, 0, nBytesToRead);
MD5Helper.Update(spBuffer, nBytesRead);
nBytesLeft -= nBytesRead;
spMACProgressHelper.UpdateProgress();
if (spMACProgressHelper.isKillFlag())
throw new JMACStoppedByUserException();
}
if (nBytesLeft != 0)
throw new JMACException("The File Is Broken");
MD5Helper.Update(spHeadBuffer, nHeadBytes);
// fire the "complete" progress notification
spMACProgressHelper.UpdateProgressComplete();
byte[] cResult = MD5Helper.Final();
for (int i = 0; i < 16; i++)
if (cResult[i] != pInfo.spAPEDescriptor.cFileMD5[i])
throw new JMACException("Invalid Checksum");
}
} finally {
file.close();
}
}
/**
* **************************************************************************************
* Convert file
* ***************************************************************************************
*/
public static void ConvertFile(String pInputFilename, String pOutputFilename, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_APE, nCompressionLevel, progressor);
}
/**
* **************************************************************************************
* Decompress file
* ***************************************************************************************
*/
public static void DecompressFile(String pInputFilename, String pOutputFilename, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
if (pOutputFilename == null)
VerifyFile(pInputFilename, progressor);
else
DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_WAV, -1, progressor);
}
public static void DecompressCore(String pInputFilename, String pOutputFilename, int nOutputMode, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
// error check the function parameters
if (pInputFilename == null)
throw new JMACException("Bad Parameters");
// variable declares
IAPEDecompress spAPEDecompress = null;
File file = File.createFile(pInputFilename, "r");
try {
spAPEDecompress = IAPEDecompress.CreateIAPEDecompress(file);
DecompressCore(spAPEDecompress, pOutputFilename, nOutputMode, nCompressionLevel, progressor);
} finally {
file.close();
}
}
public static void DecompressCore(IAPEDecompress spAPEDecompress, String pOutputFilename, int nOutputMode, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACSkippedException, JMACStoppedByUserException {
// variable declares
java.io.RandomAccessFile spioOutput = null;
IAPECompress spAPECompress = null;
try {
// create the core
WaveFormat wfeInput = spAPEDecompress.getApeInfoWaveFormatEx();
// allocate space for the header
byte[] waveHeaderBuffer = spAPEDecompress.getApeInfoWavHeaderData(spAPEDecompress.getApeInfoWavHeaderBytes());
// initialize the output
if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV) {
// create the file
spioOutput = new RandomAccessFile(pOutputFilename, "rw");
// output the header
spioOutput.write(waveHeaderBuffer);
} else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE) {
// quit if there is nothing to do
if (spAPEDecompress.getApeInfoFileVersion() == Globals.MAC_VERSION_NUMBER && spAPEDecompress.getApeInfoCompressionLevel() == nCompressionLevel)
throw new JMACSkippedException();
// create and start the compressor
spAPECompress = IAPECompress.CreateIAPECompress();
spAPECompress.Start(pOutputFilename, wfeInput, spAPEDecompress.getApeInfoDecompressTotalBlocks() * spAPEDecompress.getApeInfoBlockAlign(),
nCompressionLevel, waveHeaderBuffer, spAPEDecompress.getApeInfoWavHeaderBytes());
}
int blockAlign = spAPEDecompress.getApeInfoBlockAlign();
// allocate space for decompression
byte[] spTempBuffer = new byte[blockAlign * BLOCKS_PER_DECODE];
int nBlocksLeft = spAPEDecompress.getApeInfoDecompressTotalBlocks();
// create the progress helper
ProgressHelper spMACProgressHelper = new ProgressHelper(nBlocksLeft / BLOCKS_PER_DECODE, progressor);
// main decoding loop
while (nBlocksLeft > 0) {
// decode data
int nBlocksDecoded = spAPEDecompress.GetData(spTempBuffer, BLOCKS_PER_DECODE);
// handle the output
if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
spioOutput.write(spTempBuffer, 0, nBlocksDecoded * blockAlign);
else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
spAPECompress.AddData(spTempBuffer, nBlocksDecoded * spAPEDecompress.getApeInfoBlockAlign());
// update amount remaining
nBlocksLeft -= nBlocksDecoded;
// update progress and kill flag
spMACProgressHelper.UpdateProgress();
if (spMACProgressHelper.isKillFlag())
throw new JMACStoppedByUserException();
}
// terminate the output
if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV) {
// write any terminating WAV data
if (spAPEDecompress.getApeInfoWavTerminatingBytes() > 0) {
byte[] termData = spAPEDecompress.getApeInfoWavTerminatingData(spAPEDecompress.getApeInfoWavTerminatingBytes());
int nBytesToWrite = spAPEDecompress.getApeInfoWavTerminatingBytes();
spioOutput.write(termData, 0, nBytesToWrite);
}
} else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE) {
// write the WAV data and any tag
int nTagBytes = spAPEDecompress.getApeInfoTag().GetTagBytes();
boolean bHasTag = (nTagBytes > 0);
int nTerminatingBytes = nTagBytes;
nTerminatingBytes += spAPEDecompress.getApeInfoWavTerminatingBytes();
if (nTerminatingBytes > 0) {
spTempBuffer = spAPEDecompress.getApeInfoWavTerminatingData(nTerminatingBytes);
if (bHasTag) {
spAPEDecompress.getApeInfoIoSource().seek(spAPEDecompress.getApeInfoIoSource().length() - nTagBytes);
spAPEDecompress.getApeInfoIoSource().read(spTempBuffer, spAPEDecompress.getApeInfoWavTerminatingBytes(), nTagBytes);
}
spAPECompress.Finish(spTempBuffer, nTerminatingBytes, spAPEDecompress.getApeInfoWavTerminatingBytes());
} else
spAPECompress.Finish(null, 0, 0);
}
// fire the "complete" progress notification
spMACProgressHelper.UpdateProgressComplete();
} finally {
if (spioOutput != null)
spioOutput.close();
if (spAPECompress != null)
spAPECompress.Kill();
}
}
public static void CompressFile(String pInputFilename, String pOutputFilename, int nCompressionLevel, ProgressCallback progressor) throws IOException, JMACStoppedByUserException {
// declare the variables
IAPECompress spAPECompress = null;
InputSource spInputSource = null;
try {
byte[] spBuffer = null;
WaveFormat WaveFormatEx = new WaveFormat();
// create the input source
IntegerPointer nAudioBlocks = new IntegerPointer();
nAudioBlocks.value = 0;
IntegerPointer nHeaderBytes = new IntegerPointer();
nHeaderBytes.value = 0;
IntegerPointer nTerminatingBytes = new IntegerPointer();
nTerminatingBytes.value = 0;
spInputSource = InputSource.CreateInputSource(pInputFilename, WaveFormatEx, nAudioBlocks,
nHeaderBytes, nTerminatingBytes);
// create the compressor
spAPECompress = IAPECompress.CreateIAPECompress();
// figure the audio bytes
int nAudioBytes = nAudioBlocks.value * WaveFormatEx.nBlockAlign;
// start the encoder
if (nHeaderBytes.value > 0) spBuffer = new byte[nHeaderBytes.value];
spInputSource.GetHeaderData(spBuffer);
spAPECompress.Start(pOutputFilename, WaveFormatEx, nAudioBytes,
nCompressionLevel, spBuffer, nHeaderBytes.value);
// set-up the progress
ProgressHelper spMACProgressHelper = new ProgressHelper(nAudioBytes, progressor);
// master loop
int nBytesLeft = nAudioBytes;
spMACProgressHelper.UpdateStatus("Process data by compressor");
while (nBytesLeft > 0) {
int nBytesAdded = spAPECompress.AddDataFromInputSource(spInputSource, nBytesLeft);
nBytesLeft -= nBytesAdded;
// update the progress
spMACProgressHelper.UpdateProgress(nAudioBytes - nBytesLeft);
// process the kill flag
if (spMACProgressHelper.isKillFlag())
throw new JMACStoppedByUserException();
}
spMACProgressHelper.UpdateStatus("Finishing compression");
// finalize the file
if (nTerminatingBytes.value > 0) spBuffer = new byte[nTerminatingBytes.value];
spInputSource.GetTerminatingData(spBuffer);
spAPECompress.Finish(spBuffer, nTerminatingBytes.value, nTerminatingBytes.value);
// update the progress to 100%
spMACProgressHelper.UpdateStatus("Compression finished");
} finally {
// kill the compressor if we failed
if (spAPECompress != null)
spAPECompress.Kill();
if (spInputSource != null)
spInputSource.Close();
}
}
}