OSDN Git Service

Merge branch 'master' of github.com:lordmulder/LameXP
[lamexp/LameXP.git] / src / Thread_Process.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2011 LoRd_MuldeR <MuldeR2@GMX.de>
4 //
5 // This program is free software; you can redistribute it and/or modify
6 // it under the terms of the GNU General Public License as published by
7 // the Free Software Foundation; either version 2 of the License, or
8 // (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License along
16 // with this program; if not, write to the Free Software Foundation, Inc.,
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 //
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
21
22 #include "Thread_Process.h"
23
24 #include "Global.h"
25 #include "Model_AudioFile.h"
26 #include "Model_Progress.h"
27 #include "Encoder_Abstract.h"
28 #include "Decoder_Abstract.h"
29 #include "Filter_Abstract.h"
30 #include "Filter_Downmix.h"
31 #include "Filter_Resample.h"
32 #include "Registry_Decoder.h"
33 #include "Model_Settings.h"
34
35 #include <QUuid>
36 #include <QFileInfo>
37 #include <QDir>
38 #include <QMutex>
39 #include <QMutexLocker>
40 #include <QDate>
41
42 #include <limits.h>
43 #include <time.h>
44 #include <stdlib.h>
45
46 #define DIFF(X,Y) ((X > Y) ? (X-Y) : (Y-X))
47
48 QMutex *ProcessThread::m_mutex_genFileName = NULL;
49
50 ////////////////////////////////////////////////////////////
51 // Constructor
52 ////////////////////////////////////////////////////////////
53
54 ProcessThread::ProcessThread(const AudioFileModel &audioFile, const QString &outputDirectory, const QString &tempDirectory, AbstractEncoder *encoder, const bool prependRelativeSourcePath)
55 :
56         m_audioFile(audioFile),
57         m_outputDirectory(outputDirectory),
58         m_tempDirectory(tempDirectory),
59         m_encoder(encoder),
60         m_jobId(QUuid::createUuid()),
61         m_prependRelativeSourcePath(prependRelativeSourcePath),
62         m_aborted(false)
63 {
64         if(m_mutex_genFileName)
65         {
66                 m_mutex_genFileName = new QMutex;
67         }
68
69         connect(m_encoder, SIGNAL(statusUpdated(int)), this, SLOT(handleUpdate(int)), Qt::DirectConnection);
70         connect(m_encoder, SIGNAL(messageLogged(QString)), this, SLOT(handleMessage(QString)), Qt::DirectConnection);
71
72         m_currentStep = UnknownStep;
73 }
74
75 ProcessThread::~ProcessThread(void)
76 {
77         while(!m_tempFiles.isEmpty())
78         {
79                 lamexp_remove_file(m_tempFiles.takeFirst());
80         }
81
82         while(!m_filters.isEmpty())
83         {
84                 delete m_filters.takeFirst();
85         }
86
87         LAMEXP_DELETE(m_encoder);
88 }
89
90 ////////////////////////////////////////////////////////////
91 // Thread Entry Point
92 ////////////////////////////////////////////////////////////
93
94 void ProcessThread::run()
95 {
96         try
97         {
98                 processFile();
99         }
100         catch(...)
101         {
102                 fflush(stdout);
103                 fflush(stderr);
104                 fprintf(stderr, "\nGURU MEDITATION !!!\n");
105                 FatalAppExit(0, L"Unhandeled exception error, application will exit!");
106                 TerminateProcess(GetCurrentProcess(), -1);
107         }
108 }
109
110 void ProcessThread::processFile()
111 {
112         m_aborted = false;
113         bool bSuccess = true;
114                 
115         qDebug("Process thread %s has started.", m_jobId.toString().toLatin1().constData());
116         emit processStateInitialized(m_jobId, QFileInfo(m_audioFile.filePath()).fileName(), tr("Starting..."), ProgressModel::JobRunning);
117         handleMessage(QString().sprintf("LameXP v%u.%02u (Build #%u), compiled at %s", lamexp_version_major(), lamexp_version_minor(), lamexp_version_build(), lamexp_version_date().toString(Qt::ISODate).toLatin1().constData()));
118         handleMessage("\n-------------------------------\n");
119
120         //Generate output file name
121         QString outFileName = generateOutFileName();
122         if(outFileName.isEmpty())
123         {
124                 emit processStateChanged(m_jobId, tr("Not found!"), ProgressModel::JobFailed);
125                 emit processStateFinished(m_jobId, outFileName, false);
126                 return;
127         }
128
129         //Do we need to take of downsampling the input?
130         if(m_encoder->requiresDownsample())
131         {
132                 insertDownsampleFilter();
133         }
134
135         //Do we need Stereo downmix?
136         if(m_audioFile.formatAudioChannels() > 2 && m_encoder->requiresDownmix())
137         {
138                 m_filters.prepend(new DownmixFilter());
139         }
140
141         QString sourceFile = m_audioFile.filePath();
142
143         //Decode source file
144         if(!m_filters.isEmpty() || !m_encoder->isFormatSupported(m_audioFile.formatContainerType(), m_audioFile.formatContainerProfile(), m_audioFile.formatAudioType(), m_audioFile.formatAudioProfile(), m_audioFile.formatAudioVersion()))
145         {
146                 m_currentStep = DecodingStep;
147                 AbstractDecoder *decoder = DecoderRegistry::lookup(m_audioFile.formatContainerType(), m_audioFile.formatContainerProfile(), m_audioFile.formatAudioType(), m_audioFile.formatAudioProfile(), m_audioFile.formatAudioVersion());
148                 
149                 if(decoder)
150                 {
151                         QString tempFile = generateTempFileName();
152
153                         connect(decoder, SIGNAL(statusUpdated(int)), this, SLOT(handleUpdate(int)), Qt::DirectConnection);
154                         connect(decoder, SIGNAL(messageLogged(QString)), this, SLOT(handleMessage(QString)), Qt::DirectConnection);
155
156                         bSuccess = decoder->decode(sourceFile, tempFile, &m_aborted);
157
158                         if(bSuccess)
159                         {
160                                 sourceFile = tempFile;
161                                 handleMessage("\n-------------------------------\n");
162                         }
163                 }
164                 else
165                 {
166                         handleMessage(QString("%1\n%2\n\n%3\t%4\n%5\t%6").arg(tr("The format of this file is NOT supported:"), m_audioFile.filePath(), tr("Container Format:"), m_audioFile.formatContainerInfo(), tr("Audio Format:"), m_audioFile.formatAudioCompressInfo()));
167                         emit processStateChanged(m_jobId, tr("Unsupported!"), ProgressModel::JobFailed);
168                         emit processStateFinished(m_jobId, outFileName, false);
169                         return;
170                 }
171         }
172
173         //Apply all filters
174         while(!m_filters.isEmpty())
175         {
176                 QString tempFile = generateTempFileName();
177                 AbstractFilter *poFilter = m_filters.takeFirst();
178
179                 if(bSuccess)
180                 {
181                         connect(poFilter, SIGNAL(statusUpdated(int)), this, SLOT(handleUpdate(int)), Qt::DirectConnection);
182                         connect(poFilter, SIGNAL(messageLogged(QString)), this, SLOT(handleMessage(QString)), Qt::DirectConnection);
183
184                         m_currentStep = FilteringStep;
185                         bSuccess = poFilter->apply(sourceFile, tempFile, &m_aborted);
186
187                         if(bSuccess)
188                         {
189                                 sourceFile = tempFile;
190                                 handleMessage("\n-------------------------------\n");
191                         }
192                 }
193
194                 delete poFilter;
195         }
196
197         //Encode audio file
198         if(bSuccess)
199         {
200                 m_currentStep = EncodingStep;
201                 bSuccess = m_encoder->encode(sourceFile, m_audioFile, outFileName, &m_aborted);
202         }
203
204         //Make sure output file exists
205         if(bSuccess)
206         {
207                 QFileInfo fileInfo(outFileName);
208                 bSuccess = fileInfo.exists() && fileInfo.isFile() && (fileInfo.size() > 0);
209         }
210
211         //Report result
212         emit processStateChanged(m_jobId, (bSuccess ? tr("Done.") : (m_aborted ? tr("Aborted!") : tr("Failed!"))), (bSuccess ? ProgressModel::JobComplete : ProgressModel::JobFailed));
213         emit processStateFinished(m_jobId, outFileName, bSuccess);
214
215         qDebug("Process thread is done.");
216 }
217
218 ////////////////////////////////////////////////////////////
219 // SLOTS
220 ////////////////////////////////////////////////////////////
221
222 void ProcessThread::handleUpdate(int progress)
223 {
224         switch(m_currentStep)
225         {
226         case EncodingStep:
227                 emit processStateChanged(m_jobId, QString("%1 (%2%)").arg(tr("Encoding"), QString::number(progress)), ProgressModel::JobRunning);
228                 break;
229         case FilteringStep:
230                 emit processStateChanged(m_jobId, QString("%1 (%2%)").arg(tr("Filtering"), QString::number(progress)), ProgressModel::JobRunning);
231                 break;
232         case DecodingStep:
233                 emit processStateChanged(m_jobId, QString("%1 (%2%)").arg(tr("Decoding"), QString::number(progress)), ProgressModel::JobRunning);
234                 break;
235         }
236 }
237
238 void ProcessThread::handleMessage(const QString &line)
239 {
240         emit processMessageLogged(m_jobId, line);
241 }
242
243 ////////////////////////////////////////////////////////////
244 // PRIVAE FUNCTIONS
245 ////////////////////////////////////////////////////////////
246
247 QString ProcessThread::generateOutFileName(void)
248 {
249         QMutexLocker lock(m_mutex_genFileName);
250         
251         int n = 1;
252
253         QFileInfo sourceFile(m_audioFile.filePath());
254         if(!sourceFile.exists() || !sourceFile.isFile())
255         {
256                 handleMessage(QString("%1\n%2").arg(tr("The source audio file could not be found:"), sourceFile.absoluteFilePath()));
257                 return QString();
258         }
259
260         QFile readTest(sourceFile.canonicalFilePath());
261         if(!readTest.open(QIODevice::ReadOnly))
262         {
263                 handleMessage(QString("%1\n%2").arg(tr("The source audio file could not be opened for reading:"), readTest.fileName()));
264                 return QString();
265         }
266         else
267         {
268                 readTest.close();
269         }
270
271         QString baseName = sourceFile.completeBaseName();
272         QDir targetDir(m_outputDirectory.isEmpty() ? sourceFile.canonicalPath() : m_outputDirectory);
273
274         if(m_prependRelativeSourcePath && !m_outputDirectory.isEmpty())
275         {
276                 QDir rootDir = sourceFile.dir();
277                 while(!rootDir.isRoot())
278                 {
279                         if(!rootDir.cdUp()) break;
280                 }
281                 targetDir.setPath(QString("%1/%2").arg(targetDir.absolutePath(), QFileInfo(rootDir.relativeFilePath(sourceFile.canonicalFilePath())).path()));
282         }
283         
284         if(!targetDir.exists())
285         {
286                 targetDir.mkpath(".");
287                 if(!targetDir.exists())
288                 {
289                         handleMessage(QString("%1\n%2").arg(tr("The target output directory doesn't exist and could NOT be created:"), targetDir.absolutePath()));
290                         return QString();
291                 }
292         }
293         
294         QFile writeTest(QString("%1/.%2").arg(targetDir.canonicalPath(), lamexp_rand_str()));
295         if(!writeTest.open(QIODevice::ReadWrite))
296         {
297                 handleMessage(QString("%1\n%2").arg(tr("The target output directory is NOT writable:"), targetDir.absolutePath()));
298                 return QString();
299         }
300         else
301         {
302                 writeTest.close();
303                 writeTest.remove();
304         }
305
306         QString outFileName = QString("%1/%2.%3").arg(targetDir.canonicalPath(), baseName, m_encoder->extension());
307         while(QFileInfo(outFileName).exists())
308         {
309                 outFileName = QString("%1/%2 (%3).%4").arg(targetDir.canonicalPath(), baseName, QString::number(++n), m_encoder->extension());
310         }
311
312         QFile placeholder(outFileName);
313         if(placeholder.open(QIODevice::WriteOnly))
314         {
315                 placeholder.close();
316         }
317
318         return outFileName;
319 }
320
321 QString ProcessThread::generateTempFileName(void)
322 {
323         QMutexLocker lock(m_mutex_genFileName);
324         QString tempFileName = QString("%1/%2.wav").arg(m_tempDirectory, lamexp_rand_str());
325
326         while(QFileInfo(tempFileName).exists())
327         {
328                 tempFileName = QString("%1/%2.wav").arg(m_tempDirectory, lamexp_rand_str());
329         }
330
331         QFile file(tempFileName);
332         if(file.open(QFile::ReadWrite))
333         {
334                 file.close();
335         }
336
337         m_tempFiles << tempFileName;
338         return tempFileName;
339 }
340
341 void ProcessThread::insertDownsampleFilter(void)
342 {
343         bool applyDownsampling = true;
344                 
345         //Check if downsampling filter is already in the chain
346         for(int i = 0; i < m_filters.count(); i++)
347         {
348                 if(dynamic_cast<ResampleFilter*>(m_filters.at(i)))
349                 {
350                         qWarning("Encoder requires downsampling, but user has already set resamling filter!");
351                         applyDownsampling = false;
352                 }
353         }
354                 
355         //Now add the downsampling filter, if needed
356         if(applyDownsampling)
357         {
358                 const unsigned int *supportedRates = m_encoder->requiresDownsample();
359                 const unsigned int inputRate = m_audioFile.formatAudioSamplerate();
360                 unsigned int currentDiff = UINT_MAX, minimumDiff = UINT_MAX, bestRate = UINT_MAX;
361
362                 //Find the most suitable supported sampling rate
363                 for(int i = 0; supportedRates[i]; i++)
364                 {
365                         currentDiff = DIFF(inputRate, supportedRates[i]);
366                         if(currentDiff < minimumDiff)
367                         {
368                                 bestRate = supportedRates[i];
369                                 minimumDiff = currentDiff;
370                                 if(!(minimumDiff > 0)) break;
371                         }
372                 }
373                 
374                 if(bestRate != inputRate)
375                 {
376                         m_filters.prepend(new ResampleFilter((bestRate != UINT_MAX) ? bestRate : supportedRates[0]));
377                 }
378         }
379 }
380
381 ////////////////////////////////////////////////////////////
382 // PUBLIC FUNCTIONS
383 ////////////////////////////////////////////////////////////
384
385 void ProcessThread::addFilter(AbstractFilter *filter)
386 {
387         m_filters.append(filter);
388 }
389
390 ////////////////////////////////////////////////////////////
391 // EVENTS
392 ////////////////////////////////////////////////////////////
393
394 /*NONE*/