xref: /haiku/src/add-ons/media/plugins/ape_reader/MAClib/APESimple.cpp (revision d9ef4f90bb61109a48bb66254a784461313ea5ff)
1 #include <algorithm>
2 
3 #include "All.h"
4 #include "APEInfo.h"
5 #include "APECompress.h"
6 #include "APEDecompress.h"
7 #include "WAVInputSource.h"
8 #include IO_HEADER_FILE
9 #include "MACProgressHelper.h"
10 #include "GlobalFunctions.h"
11 #include "CharacterHelper.h"
12 #include "MD5.h"
13 
14 #define UNMAC_DECODER_OUTPUT_NONE       0
15 #define UNMAC_DECODER_OUTPUT_WAV        1
16 #define UNMAC_DECODER_OUTPUT_APE        2
17 
18 #define BLOCKS_PER_DECODE               9216
19 
20 int DecompressCore(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nOutputMode, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag);
21 
22 /*****************************************************************************************
23 ANSI wrappers
24 *****************************************************************************************/
CompressFile(const str_ansi * pInputFilename,const str_ansi * pOutputFilename,int nCompressionLevel,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)25 int __stdcall CompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
26 {
27     CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
28     CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
29     return CompressFileW(spInputFile, spOutputFile, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
30 }
31 
DecompressFile(const str_ansi * pInputFilename,const str_ansi * pOutputFilename,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)32 int __stdcall DecompressFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
33 {
34     CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
35     CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
36     return DecompressFileW(spInputFile, pOutputFilename ? static_cast<str_utf16*>(spOutputFile) : NULL, pPercentageDone, ProgressCallback, pKillFlag);
37 }
38 
ConvertFile(const str_ansi * pInputFilename,const str_ansi * pOutputFilename,int nCompressionLevel,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)39 int __stdcall ConvertFile(const str_ansi * pInputFilename, const str_ansi * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
40 {
41     CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
42     CSmartPtr<str_utf16> spOutputFile(GetUTF16FromANSI(pOutputFilename), TRUE);
43     return ConvertFileW(spInputFile, spOutputFile, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
44 }
45 
VerifyFile(const str_ansi * pInputFilename,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag,BOOL bQuickVerifyIfPossible)46 int __stdcall VerifyFile(const str_ansi * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible)
47 {
48     CSmartPtr<str_utf16> spInputFile(GetUTF16FromANSI(pInputFilename), TRUE);
49     return VerifyFileW(spInputFile, pPercentageDone, ProgressCallback, pKillFlag, FALSE);
50 }
51 
52 /*****************************************************************************************
53 Compress file
54 *****************************************************************************************/
CompressFileW(const str_utf16 * pInputFilename,const str_utf16 * pOutputFilename,int nCompressionLevel,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)55 int __stdcall CompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
56 {
57     // declare the variables
58     int nFunctionRetVal = ERROR_SUCCESS;
59     WAVEFORMATEX WaveFormatEx;
60     CSmartPtr<CMACProgressHelper> spMACProgressHelper;
61     CSmartPtr<unsigned char> spBuffer;
62     CSmartPtr<IAPECompress> spAPECompress;
63 
64     try
65     {
66         // create the input source
67         int nRetVal = ERROR_UNDEFINED;
68         int nAudioBlocks = 0; int nHeaderBytes = 0; int nTerminatingBytes = 0;
69         CSmartPtr<CInputSource> spInputSource(CreateInputSource(pInputFilename, &WaveFormatEx, &nAudioBlocks,
70             &nHeaderBytes, &nTerminatingBytes, &nRetVal));
71 
72         if ((spInputSource == NULL) || (nRetVal != ERROR_SUCCESS))
73             throw nRetVal;
74 
75         // create the compressor
76         spAPECompress.Assign(CreateIAPECompress());
77         if (spAPECompress == NULL) throw ERROR_UNDEFINED;
78 
79         // figure the audio bytes
80         int nAudioBytes = nAudioBlocks * WaveFormatEx.nBlockAlign;
81 
82         // start the encoder
83         if (nHeaderBytes > 0) spBuffer.Assign(new unsigned char [nHeaderBytes], TRUE);
84         THROW_ON_ERROR(spInputSource->GetHeaderData(spBuffer.GetPtr()))
85         THROW_ON_ERROR(spAPECompress->Start(pOutputFilename, &WaveFormatEx, nAudioBytes,
86             nCompressionLevel, spBuffer.GetPtr(), nHeaderBytes));
87 
88         spBuffer.Delete();
89 
90         // set-up the progress
91         spMACProgressHelper.Assign(new CMACProgressHelper(nAudioBytes, pPercentageDone, ProgressCallback, pKillFlag));
92 
93         // master loop
94         int nBytesLeft = nAudioBytes;
95 
96         while (nBytesLeft > 0)
97         {
98             int nBytesAdded = 0;
99             THROW_ON_ERROR(spAPECompress->AddDataFromInputSource(spInputSource.GetPtr(), nBytesLeft, &nBytesAdded))
100 
101             nBytesLeft -= nBytesAdded;
102 
103             // update the progress
104             spMACProgressHelper->UpdateProgress(nAudioBytes - nBytesLeft);
105 
106             // process the kill flag
107             if (spMACProgressHelper->ProcessKillFlag(TRUE) != ERROR_SUCCESS)
108                 throw(ERROR_USER_STOPPED_PROCESSING);
109         }
110 
111         // finalize the file
112         if (nTerminatingBytes > 0) spBuffer.Assign(new unsigned char [nTerminatingBytes], TRUE);
113         THROW_ON_ERROR(spInputSource->GetTerminatingData(spBuffer.GetPtr()));
114         THROW_ON_ERROR(spAPECompress->Finish(spBuffer.GetPtr(), nTerminatingBytes, nTerminatingBytes))
115 
116         // update the progress to 100%
117         spMACProgressHelper->UpdateProgressComplete();
118     }
119     catch(int nErrorCode)
120     {
121         nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
122     }
123     catch(...)
124     {
125         nFunctionRetVal = ERROR_UNDEFINED;
126     }
127 
128     // kill the compressor if we failed
129     if ((nFunctionRetVal != 0) && (spAPECompress != NULL))
130         spAPECompress->Kill();
131 
132     // return
133     return nFunctionRetVal;
134 }
135 
136 
137 /*****************************************************************************************
138 Verify file
139 *****************************************************************************************/
VerifyFileW(const str_utf16 * pInputFilename,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag,BOOL bQuickVerifyIfPossible)140 int __stdcall VerifyFileW(const str_utf16 * pInputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag, BOOL bQuickVerifyIfPossible)
141 {
142     // error check the function parameters
143     if (pInputFilename == NULL)
144     {
145         return ERROR_INVALID_FUNCTION_PARAMETER;
146     }
147 
148 
149     // return value
150     int nRetVal = ERROR_UNDEFINED;
151 
152     // see if we can quick verify
153     if (bQuickVerifyIfPossible)
154     {
155         CSmartPtr<IAPEDecompress> spAPEDecompress;
156         try
157         {
158             int nFunctionRetVal = ERROR_SUCCESS;
159 
160             spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
161             if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
162 
163             APE_FILE_INFO * pInfo = (APE_FILE_INFO *) spAPEDecompress->GetInfo(APE_INTERNAL_INFO);
164             if ((pInfo->nVersion < 3980) || (pInfo->spAPEDescriptor == NULL))
165                 throw(ERROR_UPSUPPORTED_FILE_VERSION);
166         }
167         catch(...)
168         {
169             bQuickVerifyIfPossible = FALSE;
170         }
171     }
172 
173     // if we can and should quick verify, then do it
174     if (bQuickVerifyIfPossible)
175     {
176         // variable declares
177         int nFunctionRetVal = ERROR_SUCCESS;
178         unsigned int nBytesRead = 0;
179         CSmartPtr<IAPEDecompress> spAPEDecompress;
180 
181         // run the quick verify
182         try
183         {
184             spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
185             if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
186 
187             CMD5Helper MD5Helper;
188 
189             CIO * pIO = GET_IO(spAPEDecompress);
190             APE_FILE_INFO * pInfo = (APE_FILE_INFO *) spAPEDecompress->GetInfo(APE_INTERNAL_INFO);
191 
192             if ((pInfo->nVersion < 3980) || (pInfo->spAPEDescriptor == NULL))
193                 throw(ERROR_UPSUPPORTED_FILE_VERSION);
194 
195             int nHead = pInfo->nJunkHeaderBytes + pInfo->spAPEDescriptor->nDescriptorBytes;
196             int nStart = nHead + pInfo->spAPEDescriptor->nHeaderBytes + pInfo->spAPEDescriptor->nSeekTableBytes;
197 
198             pIO->Seek(nHead, FILE_BEGIN);
199             int nHeadBytes = nStart - nHead;
200             CSmartPtr<unsigned char> spHeadBuffer(new unsigned char [nHeadBytes], TRUE);
201             if ((pIO->Read(spHeadBuffer, nHeadBytes, &nBytesRead) != ERROR_SUCCESS) || (nHeadBytes != int(nBytesRead)))
202                 throw(ERROR_IO_READ);
203 
204             int nBytesLeft = pInfo->spAPEDescriptor->nHeaderDataBytes + pInfo->spAPEDescriptor->nAPEFrameDataBytes + pInfo->spAPEDescriptor->nTerminatingDataBytes;
205             CSmartPtr<unsigned char> spBuffer(new unsigned char [16384], TRUE);
206             nBytesRead = 1;
207             while ((nBytesLeft > 0) && (nBytesRead > 0))
208             {
209                 int nBytesToRead = min(16384, nBytesLeft);
210                 if (pIO->Read(spBuffer, nBytesToRead, &nBytesRead) != ERROR_SUCCESS)
211                     throw(ERROR_IO_READ);
212 
213                 MD5Helper.AddData(spBuffer, nBytesRead);
214                 nBytesLeft -= nBytesRead;
215             }
216 
217             if (nBytesLeft != 0)
218                 throw(ERROR_IO_READ);
219 
220             MD5Helper.AddData(spHeadBuffer, nHeadBytes);
221 
222             unsigned char cResult[16];
223             MD5Helper.GetResult(cResult);
224 
225             if (memcmp(cResult, pInfo->spAPEDescriptor->cFileMD5, 16) != 0)
226                 nFunctionRetVal = ERROR_INVALID_CHECKSUM;
227 
228         }
229         catch(int nErrorCode)
230         {
231             nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
232         }
233         catch(...)
234         {
235             nFunctionRetVal = ERROR_UNDEFINED;
236         }
237 
238         // return value
239         nRetVal = nFunctionRetVal;
240     }
241     else
242     {
243         nRetVal = DecompressCore(pInputFilename, NULL, UNMAC_DECODER_OUTPUT_NONE, -1, pPercentageDone, ProgressCallback, pKillFlag);
244     }
245 
246 
247     return nRetVal;
248 }
249 
250 /*****************************************************************************************
251 Decompress file
252 *****************************************************************************************/
DecompressFileW(const str_utf16 * pInputFilename,const str_utf16 * pOutputFilename,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)253 int __stdcall DecompressFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
254 {
255     if (pOutputFilename == NULL)
256         return VerifyFileW(pInputFilename, pPercentageDone, ProgressCallback, pKillFlag);
257     else
258         return DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_WAV, -1, pPercentageDone, ProgressCallback, pKillFlag);
259 }
260 
261 /*****************************************************************************************
262 Convert file
263 *****************************************************************************************/
ConvertFileW(const str_utf16 * pInputFilename,const str_utf16 * pOutputFilename,int nCompressionLevel,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)264 int __stdcall ConvertFileW(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
265 {
266     return DecompressCore(pInputFilename, pOutputFilename, UNMAC_DECODER_OUTPUT_APE, nCompressionLevel, pPercentageDone, ProgressCallback, pKillFlag);
267 }
268 
269 /*****************************************************************************************
270 Decompress a file using the specified output method
271 *****************************************************************************************/
DecompressCore(const str_utf16 * pInputFilename,const str_utf16 * pOutputFilename,int nOutputMode,int nCompressionLevel,int * pPercentageDone,APE_PROGRESS_CALLBACK ProgressCallback,int * pKillFlag)272 int DecompressCore(const str_utf16 * pInputFilename, const str_utf16 * pOutputFilename, int nOutputMode, int nCompressionLevel, int * pPercentageDone, APE_PROGRESS_CALLBACK ProgressCallback, int * pKillFlag)
273 {
274     // error check the function parameters
275     if (pInputFilename == NULL)
276     {
277         return ERROR_INVALID_FUNCTION_PARAMETER;
278     }
279 
280     // variable declares
281     int nFunctionRetVal = ERROR_SUCCESS;
282     CSmartPtr<IO_CLASS_NAME> spioOutput;
283     CSmartPtr<IAPECompress> spAPECompress;
284     CSmartPtr<IAPEDecompress> spAPEDecompress;
285     CSmartPtr<unsigned char> spTempBuffer;
286     CSmartPtr<CMACProgressHelper> spMACProgressHelper;
287     WAVEFORMATEX wfeInput;
288 
289     try
290     {
291         // create the decoder
292         spAPEDecompress.Assign(CreateIAPEDecompress(pInputFilename, &nFunctionRetVal));
293         if (spAPEDecompress == NULL || nFunctionRetVal != ERROR_SUCCESS) throw(nFunctionRetVal);
294 
295         // get the input format
296         THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAVEFORMATEX, (int) &wfeInput))
297 
298         // allocate space for the header
299         spTempBuffer.Assign(new unsigned char [spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)], TRUE);
300         if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
301 
302         // get the header
303         THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_DATA, (int) spTempBuffer.GetPtr(), spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)));
304 
305         // initialize the output
306         if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
307         {
308             // create the file
309             spioOutput.Assign(new IO_CLASS_NAME); THROW_ON_ERROR(spioOutput->Create(pOutputFilename))
310 
311             // output the header
312             THROW_ON_ERROR(WriteSafe(spioOutput, spTempBuffer, spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)));
313         }
314         else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
315         {
316             // quit if there is nothing to do
317             if (spAPEDecompress->GetInfo(APE_INFO_FILE_VERSION) == MAC_VERSION_NUMBER && spAPEDecompress->GetInfo(APE_INFO_COMPRESSION_LEVEL) == nCompressionLevel)
318                 throw(ERROR_SKIPPED);
319 
320             // create and start the compressor
321             spAPECompress.Assign(CreateIAPECompress());
322             THROW_ON_ERROR(spAPECompress->Start(pOutputFilename, &wfeInput, spAPEDecompress->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS) * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN),
323                 nCompressionLevel, spTempBuffer, spAPEDecompress->GetInfo(APE_INFO_WAV_HEADER_BYTES)))
324         }
325 
326         // allocate space for decompression
327         spTempBuffer.Assign(new unsigned char [spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN) * BLOCKS_PER_DECODE], TRUE);
328         if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
329 
330         int nBlocksLeft = spAPEDecompress->GetInfo(APE_DECOMPRESS_TOTAL_BLOCKS);
331 
332         // create the progress helper
333         spMACProgressHelper.Assign(new CMACProgressHelper(nBlocksLeft / BLOCKS_PER_DECODE, pPercentageDone, ProgressCallback, pKillFlag));
334 
335         // main decoding loop
336         while (nBlocksLeft > 0)
337         {
338             // decode data
339             int nBlocksDecoded = -1;
340             int nRetVal = spAPEDecompress->GetData((char *) spTempBuffer.GetPtr(), BLOCKS_PER_DECODE, &nBlocksDecoded);
341             if (nRetVal != ERROR_SUCCESS)
342                 throw(ERROR_INVALID_CHECKSUM);
343 
344             // handle the output
345             if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
346             {
347                 unsigned int nBytesToWrite = (nBlocksDecoded * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN));
348                 unsigned int nBytesWritten = 0;
349                 int nRetVal = spioOutput->Write(spTempBuffer, nBytesToWrite, &nBytesWritten);
350                 if ((nRetVal != 0) || (nBytesToWrite != nBytesWritten))
351                     throw(ERROR_IO_WRITE);
352             }
353             else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
354             {
355                 THROW_ON_ERROR(spAPECompress->AddData(spTempBuffer, nBlocksDecoded * spAPEDecompress->GetInfo(APE_INFO_BLOCK_ALIGN)))
356             }
357 
358             // update amount remaining
359             nBlocksLeft -= nBlocksDecoded;
360 
361             // update progress and kill flag
362             spMACProgressHelper->UpdateProgress();
363             if (spMACProgressHelper->ProcessKillFlag(TRUE) != 0)
364                 throw(ERROR_USER_STOPPED_PROCESSING);
365         }
366 
367         // terminate the output
368         if (nOutputMode == UNMAC_DECODER_OUTPUT_WAV)
369         {
370             // write any terminating WAV data
371             if (spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES) > 0)
372             {
373                 spTempBuffer.Assign(new unsigned char[spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)], TRUE);
374                 if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
375                 THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_DATA, (int) spTempBuffer.GetPtr(), spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)))
376 
377                 unsigned int nBytesToWrite = spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES);
378                 unsigned int nBytesWritten = 0;
379                 int nRetVal = spioOutput->Write(spTempBuffer, nBytesToWrite, &nBytesWritten);
380                 if ((nRetVal != 0) || (nBytesToWrite != nBytesWritten))
381                     throw(ERROR_IO_WRITE);
382             }
383         }
384         else if (nOutputMode == UNMAC_DECODER_OUTPUT_APE)
385         {
386             // write the WAV data and any tag
387             int nTagBytes = GET_TAG(spAPEDecompress)->GetTagBytes();
388             BOOL bHasTag = (nTagBytes > 0);
389             int nTerminatingBytes = nTagBytes;
390             nTerminatingBytes += spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES);
391 
392             if (nTerminatingBytes > 0)
393             {
394                 spTempBuffer.Assign(new unsigned char[nTerminatingBytes], TRUE);
395                 if (spTempBuffer == NULL) throw(ERROR_INSUFFICIENT_MEMORY);
396 
397                 THROW_ON_ERROR(spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_DATA, (int) spTempBuffer.GetPtr(), nTerminatingBytes))
398 
399                 if (bHasTag)
400                 {
401                     unsigned int nBytesRead = 0;
402                     THROW_ON_ERROR(GET_IO(spAPEDecompress)->Seek(-(nTagBytes), FILE_END))
403                     THROW_ON_ERROR(GET_IO(spAPEDecompress)->Read(&spTempBuffer[spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)], nTagBytes, &nBytesRead))
404                 }
405 
406                 THROW_ON_ERROR(spAPECompress->Finish(spTempBuffer, nTerminatingBytes, spAPEDecompress->GetInfo(APE_INFO_WAV_TERMINATING_BYTES)));
407             }
408             else
409             {
410                 THROW_ON_ERROR(spAPECompress->Finish(NULL, 0, 0));
411             }
412         }
413 
414         // fire the "complete" progress notification
415         spMACProgressHelper->UpdateProgressComplete();
416     }
417     catch(int nErrorCode)
418     {
419         nFunctionRetVal = (nErrorCode == 0) ? ERROR_UNDEFINED : nErrorCode;
420     }
421     catch(...)
422     {
423         nFunctionRetVal = ERROR_UNDEFINED;
424     }
425 
426     // return
427     return nFunctionRetVal;
428 }
429