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