OSDN Git Service

Some more clean-up for FhgAacEnc support + updated documents.
[lamexp/LameXP.git] / src / Dialog_Processing.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 "Dialog_Processing.h"
23
24 #include "Global.h"
25 #include "Resource.h"
26 #include "Model_FileList.h"
27 #include "Model_Progress.h"
28 #include "Model_Settings.h"
29 #include "Thread_Process.h"
30 #include "Thread_DiskObserver.h"
31 #include "Dialog_LogView.h"
32 #include "Encoder_MP3.h"
33 #include "Encoder_Vorbis.h"
34 #include "Encoder_AAC.h"
35 #include "Encoder_AAC_FHG.h"
36 #include "Encoder_AC3.h"
37 #include "Encoder_FLAC.h"
38 #include "Encoder_Wave.h"
39 #include "Filter_Downmix.h"
40 #include "Filter_Normalize.h"
41 #include "Filter_Resample.h"
42 #include "Filter_ToneAdjust.h"
43 #include "WinSevenTaskbar.h"
44
45 #include <QApplication>
46 #include <QRect>
47 #include <QDesktopWidget>
48 #include <QMovie>
49 #include <QMessageBox>
50 #include <QTimer>
51 #include <QCloseEvent>
52 #include <QDesktopServices>
53 #include <QUrl>
54 #include <QUuid>
55 #include <QFileInfo>
56 #include <QDir>
57 #include <QMenu>
58 #include <QSystemTrayIcon>
59 #include <QProcess>
60 #include <QProgressDialog>
61
62 #include <MMSystem.h>
63
64 ////////////////////////////////////////////////////////////
65
66 //Maximum number of parallel instances
67 #define MAX_INSTANCES 16
68
69 //Maximum number of CPU cores for auto-detection
70 #define MAX_CPU_COUNT 4
71
72 ////////////////////////////////////////////////////////////
73
74 #define CHANGE_BACKGROUND_COLOR(WIDGET, COLOR) \
75 { \
76         QPalette palette = WIDGET->palette(); \
77         palette.setColor(QPalette::Background, COLOR); \
78         WIDGET->setPalette(palette); \
79 }
80
81 #define SET_PROGRESS_TEXT(TXT) \
82 { \
83         label_progress->setText(TXT); \
84         m_systemTray->setToolTip(QString().sprintf("LameXP v%d.%02d\n%ls", lamexp_version_major(), lamexp_version_minor(), QString(TXT).utf16())); \
85 }
86
87 #define SET_FONT_BOLD(WIDGET,BOLD) { QFont _font = WIDGET->font(); _font.setBold(BOLD); WIDGET->setFont(_font); }
88
89 ////////////////////////////////////////////////////////////
90 // Constructor
91 ////////////////////////////////////////////////////////////
92
93 ProcessingDialog::ProcessingDialog(FileListModel *fileListModel, AudioFileModel *metaInfo, SettingsModel *settings, QWidget *parent)
94 :
95         QDialog(parent),
96         m_systemTray(new QSystemTrayIcon(QIcon(":/icons/cd_go.png"), this)),
97         m_settings(settings),
98         m_metaInfo(metaInfo),
99         m_shutdownFlag(false),
100         m_diskObserver(NULL)
101 {
102         //Init the dialog, from the .ui file
103         setupUi(this);
104         setWindowFlags(windowFlags() ^ Qt::WindowContextHelpButtonHint);
105         
106         //Setup version info
107         label_versionInfo->setText(QString().sprintf("v%d.%02d %s (Build %d)", lamexp_version_major(), lamexp_version_minor(), lamexp_version_release(), lamexp_version_build()));
108         label_versionInfo->installEventFilter(this);
109
110         //Register meta type
111         qRegisterMetaType<QUuid>("QUuid");
112
113         //Center window in screen
114         QRect desktopRect = QApplication::desktop()->screenGeometry();
115         QRect thisRect = this->geometry();
116         move((desktopRect.width() - thisRect.width()) / 2, (desktopRect.height() - thisRect.height()) / 2);
117         setMinimumSize(thisRect.width(), thisRect.height());
118
119         //Enable buttons
120         connect(button_AbortProcess, SIGNAL(clicked()), this, SLOT(abortEncoding()));
121         
122         //Init progress indicator
123         m_progressIndicator = new QMovie(":/images/Working.gif");
124         label_headerWorking->setMovie(m_progressIndicator);
125         progressBar->setValue(0);
126
127         //Init progress model
128         m_progressModel = new ProgressModel();
129         view_log->setModel(m_progressModel);
130         view_log->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
131         view_log->verticalHeader()->hide();
132         view_log->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);
133         view_log->horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
134         connect(m_progressModel, SIGNAL(rowsInserted(QModelIndex,int,int)), this, SLOT(progressModelChanged()));
135         connect(m_progressModel, SIGNAL(modelReset()), this, SLOT(progressModelChanged()));
136         connect(view_log, SIGNAL(activated(QModelIndex)), this, SLOT(logViewDoubleClicked(QModelIndex)));
137
138         //Create context menu
139         m_contextMenu = new QMenu();
140         QAction *contextMenuDetailsAction = m_contextMenu->addAction(QIcon(":/icons/zoom.png"), tr("Show details for selected job"));
141         QAction *contextMenuShowFileAction = m_contextMenu->addAction(QIcon(":/icons/folder_go.png"), tr("Browse Output File Location"));
142
143         view_log->setContextMenuPolicy(Qt::CustomContextMenu);
144         connect(view_log, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuTriggered(QPoint)));
145         connect(contextMenuDetailsAction, SIGNAL(triggered(bool)), this, SLOT(contextMenuDetailsActionTriggered()));
146         connect(contextMenuShowFileAction, SIGNAL(triggered(bool)), this, SLOT(contextMenuShowFileActionTriggered()));
147         SET_FONT_BOLD(contextMenuDetailsAction, true);
148
149         //Enque jobs
150         if(fileListModel)
151         {
152                 for(int i = 0; i < fileListModel->rowCount(); i++)
153                 {
154                         m_pendingJobs.append(fileListModel->getFile(fileListModel->index(i,0)));
155                 }
156         }
157
158         //Translate
159         label_headerStatus->setText(QString("<b>%1</b><br>%2").arg(tr("Encoding Files"), tr("Your files are being encoded, please be patient...")));
160         
161         //Enable system tray icon
162         connect(m_systemTray, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(systemTrayActivated(QSystemTrayIcon::ActivationReason)));
163
164         //Init other vars
165         m_runningThreads = 0;
166         m_currentFile = 0;
167         m_allJobs.clear();
168         m_succeededJobs.clear();
169         m_failedJobs.clear();
170         m_userAborted = false;
171 }
172
173 ////////////////////////////////////////////////////////////
174 // Destructor
175 ////////////////////////////////////////////////////////////
176
177 ProcessingDialog::~ProcessingDialog(void)
178 {
179         view_log->setModel(NULL);
180
181         if(m_progressIndicator)
182         {
183                 m_progressIndicator->stop();
184         }
185
186         if(m_diskObserver)
187         {
188                 m_diskObserver->stop();
189                 m_diskObserver->wait(15000);
190         }
191
192         LAMEXP_DELETE(m_progressIndicator);
193         LAMEXP_DELETE(m_progressModel);
194         LAMEXP_DELETE(m_contextMenu);
195         LAMEXP_DELETE(m_systemTray);
196         LAMEXP_DELETE(m_diskObserver);
197
198         WinSevenTaskbar::setOverlayIcon(this, NULL);
199         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNoState);
200
201         while(!m_threadList.isEmpty())
202         {
203                 ProcessThread *thread = m_threadList.takeFirst();
204                 thread->terminate();
205                 thread->wait(15000);
206                 delete thread;
207         }
208 }
209
210 ////////////////////////////////////////////////////////////
211 // EVENTS
212 ////////////////////////////////////////////////////////////
213
214 void ProcessingDialog::showEvent(QShowEvent *event)
215 {
216         setCloseButtonEnabled(false);
217         button_closeDialog->setEnabled(false);
218         button_AbortProcess->setEnabled(false);
219         m_systemTray->setVisible(true);
220         
221         if(!SetPriorityClass(GetCurrentProcess(), ABOVE_NORMAL_PRIORITY_CLASS))
222         {
223                 SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS);
224         }
225
226         QTimer::singleShot(1000, this, SLOT(initEncoding()));
227 }
228
229 void ProcessingDialog::closeEvent(QCloseEvent *event)
230 {
231         if(!button_closeDialog->isEnabled())
232         {
233                 event->ignore();
234         }
235         else
236         {
237                 m_systemTray->setVisible(false);
238         }
239 }
240
241 bool ProcessingDialog::eventFilter(QObject *obj, QEvent *event)
242 {
243         static QColor defaultColor = QColor();
244
245         if(obj == label_versionInfo)
246         {
247                 if(event->type() == QEvent::Enter)
248                 {
249                         QPalette palette = label_versionInfo->palette();
250                         defaultColor = palette.color(QPalette::Normal, QPalette::WindowText);
251                         palette.setColor(QPalette::Normal, QPalette::WindowText, Qt::red);
252                         label_versionInfo->setPalette(palette);
253                 }
254                 else if(event->type() == QEvent::Leave)
255                 {
256                         QPalette palette = label_versionInfo->palette();
257                         palette.setColor(QPalette::Normal, QPalette::WindowText, defaultColor);
258                         label_versionInfo->setPalette(palette);
259                 }
260                 else if(event->type() == QEvent::MouseButtonPress)
261                 {
262                         QUrl url(lamexp_website_url());
263                         QDesktopServices::openUrl(url);
264                 }
265         }
266
267         return false;
268 }
269
270 ////////////////////////////////////////////////////////////
271 // SLOTS
272 ////////////////////////////////////////////////////////////
273
274 void ProcessingDialog::initEncoding(void)
275 {
276         m_runningThreads = 0;
277         m_currentFile = 0;
278         m_allJobs.clear();
279         m_succeededJobs.clear();
280         m_failedJobs.clear();
281         m_userAborted = false;
282         m_playList.clear();
283         
284         CHANGE_BACKGROUND_COLOR(frame_header, QColor(Qt::white));
285         SET_PROGRESS_TEXT(tr("Encoding files, please wait..."));
286         m_progressIndicator->start();
287         
288         button_closeDialog->setEnabled(false);
289         button_AbortProcess->setEnabled(true);
290         progressBar->setRange(0, m_pendingJobs.count());
291         checkBox_shutdownComputer->setEnabled(true);
292         checkBox_shutdownComputer->setChecked(false);
293
294         WinSevenTaskbar::initTaskbar();
295         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNormalState);
296         WinSevenTaskbar::setTaskbarProgress(this, 0, m_pendingJobs.count());
297         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/control_play_blue.png"));
298
299         if(!m_diskObserver)
300         {
301                 m_diskObserver = new DiskObserverThread(m_settings->customTempPathEnabled() ? m_settings->customTempPath() : lamexp_temp_folder2());
302                 connect(m_diskObserver, SIGNAL(messageLogged(QString,bool)), m_progressModel, SLOT(addSystemMessage(QString,bool)), Qt::QueuedConnection);
303                 m_diskObserver->start();
304         }
305         
306         int maximumInstances = max(min(m_settings->maximumInstances(), MAX_INSTANCES), 0);
307         if(maximumInstances < 1)
308         {
309                 lamexp_cpu_t cpuFeatures = lamexp_detect_cpu_features();
310                 maximumInstances = max(min(cpuFeatures.count, MAX_CPU_COUNT), 1);
311         }
312
313         int parallelThreadCount = max(min(maximumInstances, m_pendingJobs.count()), 1);
314         if(parallelThreadCount > 1)
315         {
316                 m_progressModel->addSystemMessage(tr("Multi-threading enabled: Running %1 instances in parallel!").arg(QString::number(parallelThreadCount)));
317         }
318
319         for(int i = 0; i < parallelThreadCount; i++)
320         {
321                 startNextJob();
322         }
323 }
324
325 void ProcessingDialog::abortEncoding(void)
326 {
327         m_userAborted = true;
328         button_AbortProcess->setEnabled(false);
329         
330         SET_PROGRESS_TEXT(tr("Aborted! Waiting for running jobs to terminate..."));
331
332         for(int i = 0; i < m_threadList.count(); i++)
333         {
334                 m_threadList.at(i)->abort();
335         }
336 }
337
338 void ProcessingDialog::doneEncoding(void)
339 {
340         m_runningThreads--;
341         progressBar->setValue(progressBar->value() + 1);
342         
343         if(!m_userAborted)
344         {
345                 SET_PROGRESS_TEXT(tr("Encoding: %1 files of %2 completed so far, please wait...").arg(QString::number(progressBar->value()), QString::number(progressBar->maximum())));
346                 WinSevenTaskbar::setTaskbarProgress(this, progressBar->value(), progressBar->maximum());
347         }
348         
349         int index = m_threadList.indexOf(dynamic_cast<ProcessThread*>(QWidget::sender()));
350         if(index >= 0)
351         {
352                 m_threadList.takeAt(index)->deleteLater();
353         }
354
355         if(!m_pendingJobs.isEmpty() && !m_userAborted)
356         {
357                 startNextJob();
358                 qDebug("Running jobs: %u", m_runningThreads);
359                 return;
360         }
361         
362         if(m_runningThreads > 0)
363         {
364                 qDebug("Running jobs: %u", m_runningThreads);
365                 return;
366         }
367
368         QApplication::setOverrideCursor(Qt::WaitCursor);
369         qDebug("Running jobs: %u", m_runningThreads);
370
371         if(!m_userAborted && m_settings->createPlaylist() && !m_settings->outputToSourceDir())
372         {
373                 SET_PROGRESS_TEXT(tr("Creating the playlist file, please wait..."));
374                 QApplication::processEvents();
375                 writePlayList();
376         }
377         
378         if(m_userAborted)
379         {
380                 CHANGE_BACKGROUND_COLOR(frame_header, QColor("#FFF3BA"));
381                 WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarErrorState);
382                 WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/error.png"));
383                 SET_PROGRESS_TEXT((m_succeededJobs.count() > 0) ? tr("Process was aborted by the user after %1 file(s)!").arg(QString::number(m_succeededJobs.count())) : tr("Process was aborted prematurely by the user!"));
384                 m_systemTray->showMessage(tr("LameXP - Aborted"), tr("Process was aborted by the user."), QSystemTrayIcon::Warning);
385                 m_systemTray->setIcon(QIcon(":/icons/cd_delete.png"));
386                 QApplication::processEvents();
387                 if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ABORTED), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
388         }
389         else
390         {
391                 if(m_failedJobs.count() > 0)
392                 {
393                         CHANGE_BACKGROUND_COLOR(frame_header, QColor("#FFBABA"));
394                         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarErrorState);
395                         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/exclamation.png"));
396                         SET_PROGRESS_TEXT(tr("Error: %1 of %2 files failed. Double-click failed items for detailed information!").arg(QString::number(m_failedJobs.count()), QString::number(m_failedJobs.count() + m_succeededJobs.count())));
397                         m_systemTray->showMessage(tr("LameXP - Error"), tr("At least one file has failed!"), QSystemTrayIcon::Critical);
398                         m_systemTray->setIcon(QIcon(":/icons/cd_delete.png"));
399                         QApplication::processEvents();
400                         if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_ERROR), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
401                 }
402                 else
403                 {
404                         CHANGE_BACKGROUND_COLOR(frame_header, QColor("#E0FFE2"));
405                         WinSevenTaskbar::setTaskbarState(this, WinSevenTaskbar::WinSevenTaskbarNormalState);
406                         WinSevenTaskbar::setOverlayIcon(this, &QIcon(":/icons/accept.png"));
407                         SET_PROGRESS_TEXT(tr("All files completed successfully."));
408                         m_systemTray->showMessage(tr("LameXP - Done"), tr("All files completed successfully."), QSystemTrayIcon::Information);
409                         m_systemTray->setIcon(QIcon(":/icons/cd_add.png"));
410                         QApplication::processEvents();
411                         if(m_settings->soundsEnabled()) PlaySound(MAKEINTRESOURCE(IDR_WAVE_SUCCESS), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
412                 }
413         }
414         
415         setCloseButtonEnabled(true);
416         button_closeDialog->setEnabled(true);
417         button_AbortProcess->setEnabled(false);
418         checkBox_shutdownComputer->setEnabled(false);
419
420         m_progressModel->restoreHiddenItems();
421         view_log->scrollToBottom();
422         m_progressIndicator->stop();
423         progressBar->setValue(progressBar->maximum());
424         WinSevenTaskbar::setTaskbarProgress(this, progressBar->value(), progressBar->maximum());
425
426         QApplication::restoreOverrideCursor();
427
428         if(!m_userAborted && checkBox_shutdownComputer->isChecked())
429         {
430                 if(shutdownComputer())
431                 {
432                         m_shutdownFlag = true;
433                         accept();
434                 }
435         }
436 }
437
438 void ProcessingDialog::processFinished(const QUuid &jobId, const QString &outFileName, bool success)
439 {
440         if(success)
441         {
442                 m_playList.insert(jobId, outFileName);
443                 m_succeededJobs.append(jobId);
444         }
445         else
446         {
447                 m_failedJobs.append(jobId);
448         }
449 }
450
451 void ProcessingDialog::progressModelChanged(void)
452 {
453         view_log->scrollToBottom();
454 }
455
456 void ProcessingDialog::logViewDoubleClicked(const QModelIndex &index)
457 {
458         if(m_runningThreads == 0)
459         {
460                 const QStringList &logFile = m_progressModel->getLogFile(index);
461                 
462                 if(!logFile.isEmpty())
463                 {
464                         LogViewDialog *logView = new LogViewDialog(this);
465                         logView->setWindowTitle(QString("LameXP - [%1]").arg(m_progressModel->data(index, Qt::DisplayRole).toString()));
466                         logView->exec(logFile);
467                         LAMEXP_DELETE(logView);
468                 }
469                 else
470                 {
471                         MessageBeep(MB_ICONWARNING);
472                 }
473         }
474         else
475         {
476                 MessageBeep(MB_ICONWARNING);
477         }
478 }
479
480 void ProcessingDialog::contextMenuTriggered(const QPoint &pos)
481 {
482         QAbstractScrollArea *scrollArea = dynamic_cast<QAbstractScrollArea*>(QObject::sender());
483         QWidget *sender = scrollArea ? scrollArea->viewport() : dynamic_cast<QWidget*>(QObject::sender());      
484
485         if(pos.x() <= sender->width() && pos.y() <= sender->height() && pos.x() >= 0 && pos.y() >= 0)
486         {
487                 m_contextMenu->popup(sender->mapToGlobal(pos));
488         }
489 }
490
491 void ProcessingDialog::contextMenuDetailsActionTriggered(void)
492 {
493         QModelIndex index = view_log->indexAt(view_log->mapFromGlobal(m_contextMenu->pos()));
494         logViewDoubleClicked(index.isValid() ? index : view_log->currentIndex());
495 }
496
497 void ProcessingDialog::contextMenuShowFileActionTriggered(void)
498 {
499         QModelIndex index = view_log->indexAt(view_log->mapFromGlobal(m_contextMenu->pos()));
500         const QUuid &jobId = m_progressModel->getJobId(index.isValid() ? index : view_log->currentIndex());
501         QString filePath = m_playList.value(jobId, QString());
502
503         if(filePath.isEmpty())
504         {
505                 MessageBeep(MB_ICONWARNING);
506                 return;
507         }
508
509         if(QFileInfo(filePath).exists())
510         {
511                 QString systemRootPath;
512
513                 QDir systemRoot(lamexp_known_folder(lamexp_folder_systemfolder));
514                 if(systemRoot.exists() && systemRoot.cdUp())
515                 {
516                         systemRootPath = systemRoot.canonicalPath();
517                 }
518
519                 if(!systemRootPath.isEmpty())
520                 {
521                         QFileInfo explorer(QString("%1/explorer.exe").arg(systemRootPath));
522                         if(explorer.exists() && explorer.isFile())
523                         {
524                                 QProcess::execute(explorer.canonicalFilePath(), QStringList() << "/select," << QDir::toNativeSeparators(QFileInfo(filePath).canonicalFilePath()));
525                                 return;
526                         }
527                 }
528                 else
529                 {
530                         qWarning("SystemRoot directory could not be detected!");
531                 }
532         }
533         else
534         {
535                 qWarning("File not found: %s", filePath.toLatin1().constData());
536                 MessageBeep(MB_ICONERROR);
537         }
538 }
539
540 ////////////////////////////////////////////////////////////
541 // Private Functions
542 ////////////////////////////////////////////////////////////
543
544 void ProcessingDialog::startNextJob(void)
545 {
546         if(m_pendingJobs.isEmpty())
547         {
548                 return;
549         }
550         
551         m_currentFile++;
552         AudioFileModel currentFile = updateMetaInfo(m_pendingJobs.takeFirst());
553         AbstractEncoder *encoder = NULL;
554         bool nativeResampling = false;
555
556         //Create encoder instance
557         switch(m_settings->compressionEncoder())
558         {
559         case SettingsModel::MP3Encoder:
560                 {
561                         MP3Encoder *mp3Encoder = new MP3Encoder();
562                         mp3Encoder->setBitrate(m_settings->compressionBitrate());
563                         mp3Encoder->setRCMode(m_settings->compressionRCMode());
564                         mp3Encoder->setAlgoQuality(m_settings->lameAlgoQuality());
565                         if(m_settings->bitrateManagementEnabled())
566                         {
567                                 mp3Encoder->setBitrateLimits(m_settings->bitrateManagementMinRate(), m_settings->bitrateManagementMaxRate());
568                         }
569                         if(m_settings->samplingRate() > 0)
570                         {
571                                 mp3Encoder->setSamplingRate(SettingsModel::samplingRates[m_settings->samplingRate()]);
572                                 nativeResampling = true;
573                         }
574                         mp3Encoder->setChannelMode(m_settings->lameChannelMode());
575                         mp3Encoder->setCustomParams(m_settings->customParametersLAME());
576                         encoder = mp3Encoder;
577                 }
578                 break;
579         case SettingsModel::VorbisEncoder:
580                 {
581                         VorbisEncoder *vorbisEncoder = new VorbisEncoder();
582                         vorbisEncoder->setBitrate(m_settings->compressionBitrate());
583                         vorbisEncoder->setRCMode(m_settings->compressionRCMode());
584                         if(m_settings->bitrateManagementEnabled())
585                         {
586                                 vorbisEncoder->setBitrateLimits(m_settings->bitrateManagementMinRate(), m_settings->bitrateManagementMaxRate());
587                         }
588                         if(m_settings->samplingRate() > 0)
589                         {
590                                 vorbisEncoder->setSamplingRate(SettingsModel::samplingRates[m_settings->samplingRate()]);
591                                 nativeResampling = true;
592                         }
593                         vorbisEncoder->setCustomParams(m_settings->customParametersOggEnc());
594                         encoder = vorbisEncoder;
595                 }
596                 break;
597         case SettingsModel::AACEncoder:
598                 {
599                         if(lamexp_check_tool("fhgaacenc.exe") && lamexp_check_tool("enc_fhgaac.dll"))
600                         {
601                                 FHGAACEncoder *aacEncoder = new FHGAACEncoder();
602                                 aacEncoder->setBitrate(m_settings->compressionBitrate());
603                                 aacEncoder->setRCMode(m_settings->compressionRCMode());
604                                 aacEncoder->setProfile(m_settings->aacEncProfile());
605                                 aacEncoder->setCustomParams(m_settings->customParametersAacEnc());
606                                 encoder = aacEncoder;
607                         }
608                         else
609                         {
610                                 AACEncoder *aacEncoder = new AACEncoder();
611                                 aacEncoder->setBitrate(m_settings->compressionBitrate());
612                                 aacEncoder->setRCMode(m_settings->compressionRCMode());
613                                 aacEncoder->setEnable2Pass(m_settings->neroAACEnable2Pass());
614                                 aacEncoder->setProfile(m_settings->aacEncProfile());
615                                 aacEncoder->setCustomParams(m_settings->customParametersAacEnc());
616                                 encoder = aacEncoder;
617                         }
618                 }
619                 break;
620         case SettingsModel::AC3Encoder:
621                 {
622                         AC3Encoder *ac3Encoder = new AC3Encoder();
623                         ac3Encoder->setBitrate(m_settings->compressionBitrate());
624                         ac3Encoder->setRCMode(m_settings->compressionRCMode());
625                         ac3Encoder->setCustomParams(m_settings->customParametersAften());
626                         ac3Encoder->setAudioCodingMode(m_settings->aftenAudioCodingMode());
627                         ac3Encoder->setDynamicRangeCompression(m_settings->aftenDynamicRangeCompression());
628                         ac3Encoder->setExponentSearchSize(m_settings->aftenExponentSearchSize());
629                         ac3Encoder->setFastBitAllocation(m_settings->aftenFastBitAllocation());
630                         encoder = ac3Encoder;
631                 }
632                 break;
633         case SettingsModel::FLACEncoder:
634                 {
635                         FLACEncoder *flacEncoder = new FLACEncoder();
636                         flacEncoder->setBitrate(m_settings->compressionBitrate());
637                         flacEncoder->setRCMode(m_settings->compressionRCMode());
638                         flacEncoder->setCustomParams(m_settings->customParametersFLAC());
639                         encoder = flacEncoder;
640                 }
641                 break;
642         case SettingsModel::PCMEncoder:
643                 {
644                         WaveEncoder *waveEncoder = new WaveEncoder();
645                         waveEncoder->setBitrate(m_settings->compressionBitrate());
646                         waveEncoder->setRCMode(m_settings->compressionRCMode());
647                         encoder = waveEncoder;
648                 }
649                 break;
650         default:
651                 throw "Unsupported encoder!";
652         }
653
654         //Create processing thread
655         ProcessThread *thread = new ProcessThread
656         (
657                 currentFile,
658                 (m_settings->outputToSourceDir() ? QFileInfo(currentFile.filePath()).absolutePath() : m_settings->outputDir()),
659                 (m_settings->customTempPathEnabled() ? m_settings->customTempPath() : lamexp_temp_folder2()),
660                 encoder,
661                 m_settings->prependRelativeSourcePath()
662         );
663
664         //Add audio filters
665         if(m_settings->forceStereoDownmix())
666         {
667                 thread->addFilter(new DownmixFilter());
668         }
669         if((m_settings->samplingRate() > 0) && !nativeResampling)
670         {
671                 if(SettingsModel::samplingRates[m_settings->samplingRate()] != currentFile.formatAudioSamplerate() || currentFile.formatAudioSamplerate() == 0)
672                 {
673                         thread->addFilter(new ResampleFilter(SettingsModel::samplingRates[m_settings->samplingRate()]));
674                 }
675         }
676         if((m_settings->toneAdjustBass() != 0) || (m_settings->toneAdjustTreble() != 0))
677         {
678                 thread->addFilter(new ToneAdjustFilter(m_settings->toneAdjustBass(), m_settings->toneAdjustTreble()));
679         }
680         if(m_settings->normalizationFilterEnabled())
681         {
682                 thread->addFilter(new NormalizeFilter(m_settings->normalizationFilterMaxVolume()));
683         }
684         if(m_settings->renameOutputFilesEnabled() && (!m_settings->renameOutputFilesPattern().simplified().isEmpty()))
685         {
686                 thread->setRenamePattern(m_settings->renameOutputFilesPattern());
687         }
688
689         m_threadList.append(thread);
690         m_allJobs.append(thread->getId());
691         
692         //Connect thread signals
693         connect(thread, SIGNAL(finished()), this, SLOT(doneEncoding()), Qt::QueuedConnection);
694         connect(thread, SIGNAL(processStateInitialized(QUuid,QString,QString,int)), m_progressModel, SLOT(addJob(QUuid,QString,QString,int)), Qt::QueuedConnection);
695         connect(thread, SIGNAL(processStateChanged(QUuid,QString,int)), m_progressModel, SLOT(updateJob(QUuid,QString,int)), Qt::QueuedConnection);
696         connect(thread, SIGNAL(processStateFinished(QUuid,QString,bool)), this, SLOT(processFinished(QUuid,QString,bool)), Qt::QueuedConnection);
697         connect(thread, SIGNAL(processMessageLogged(QUuid,QString)), m_progressModel, SLOT(appendToLog(QUuid,QString)), Qt::QueuedConnection);
698         
699         //Give it a go!
700         m_runningThreads++;
701         thread->start();
702 }
703
704 void ProcessingDialog::writePlayList(void)
705 {
706         if(m_succeededJobs.count() <= 0 || m_allJobs.count() <= 0)
707         {
708                 qWarning("WritePlayList: Nothing to do!");
709                 return;
710         }
711         
712         //Init local variables
713         const static char *invalidChars = "\\/:*?\"<>|";
714         QStringList list;
715         bool useUtf8 = false;
716         int counter = 1;
717
718         //Generate playlist name
719         QString playListName = (m_metaInfo->fileAlbum().isEmpty() ? "Playlist" : m_metaInfo->fileAlbum());
720         if(!m_metaInfo->fileArtist().isEmpty())
721         {
722                 playListName = QString("%1 - %2").arg(m_metaInfo->fileArtist(), playListName);
723         }
724
725         //Clean playlist name
726         for(int i = 0; invalidChars[i]; i++)
727         {
728                 playListName = playListName.replace(invalidChars[i], ' ').simplified();
729         }
730
731         //Create list of audio files
732         for(int i = 0; i < m_allJobs.count(); i++)
733         {
734                 if(!m_succeededJobs.contains(m_allJobs.at(i))) continue;
735                 list << QDir::toNativeSeparators(QDir(m_settings->outputDir()).relativeFilePath(m_playList.value(m_allJobs.at(i), "N/A")));
736         }
737
738         //Do we need an UTF-8 playlist?
739         for(int i = 0; i < list.count(); i++)
740         {
741                 if(wcscmp(QWCHAR(QString::fromLatin1(list.at(i).toLatin1().constData())), QWCHAR(list.at(i))))
742                 {
743                         useUtf8 = true;
744                         break;
745                 }
746         }
747
748         //Generate playlist output file
749         QString playListFile = QString("%1/%2.%3").arg(m_settings->outputDir(), playListName, (useUtf8 ? "m3u8" : "m3u"));
750         while(QFileInfo(playListFile).exists())
751         {
752                 playListFile = QString("%1/%2 (%3).%4").arg(m_settings->outputDir(), playListName, QString::number(++counter), (useUtf8 ? "m3u8" : "m3u"));
753         }
754
755         //Now write playlist to output file
756         QFile playList(playListFile);
757         if(playList.open(QIODevice::WriteOnly))
758         {
759                 if(useUtf8)
760                 {
761                         playList.write("\xef\xbb\xbf");
762                 }
763                 playList.write("#EXTM3U\r\n");
764                 while(!list.isEmpty())
765                 {
766                         playList.write(useUtf8 ? list.takeFirst().toUtf8().constData() : list.takeFirst().toLatin1().constData());
767                         playList.write("\r\n");
768                 }
769                 playList.close();
770         }
771         else
772         {
773                 QMessageBox::warning(this, tr("Playlist creation failed"), QString("%1<br><nobr>%2</nobr>").arg(tr("The playlist file could not be created:"), playListFile));
774         }
775 }
776
777 AudioFileModel ProcessingDialog::updateMetaInfo(const AudioFileModel &audioFile)
778 {
779         if(!m_settings->writeMetaTags())
780         {
781                 return AudioFileModel(audioFile, false);
782         }
783         
784         AudioFileModel result = audioFile;
785         result.updateMetaInfo(*m_metaInfo);
786         
787         if(m_metaInfo->filePosition() == UINT_MAX)
788         {
789                 result.setFilePosition(m_currentFile);
790         }
791
792         return result;
793 }
794
795 void ProcessingDialog::setCloseButtonEnabled(bool enabled)
796 {
797         HMENU hMenu = GetSystemMenu((HWND) winId(), FALSE);
798         EnableMenuItem(hMenu, SC_CLOSE, MF_BYCOMMAND | (enabled ? MF_ENABLED : MF_GRAYED));
799 }
800
801 void ProcessingDialog::systemTrayActivated(QSystemTrayIcon::ActivationReason reason)
802 {
803         if(reason == QSystemTrayIcon::DoubleClick)
804         {
805                 SetForegroundWindow(this->winId());
806         }
807 }
808
809 bool ProcessingDialog::shutdownComputer(void)
810 {
811         const int iTimeout = 30;
812         const Qt::WindowFlags flags = Qt::WindowStaysOnTopHint | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::MSWindowsFixedSizeDialogHint | Qt::WindowSystemMenuHint;
813         const QString text = QString("%1%2%1").arg(QString().fill(' ', 18), tr("Warning: Computer will shutdown in %1 seconds..."));
814         
815         qWarning("Initiating shutdown sequence!");
816         
817         QProgressDialog progressDialog(text.arg(iTimeout), tr("Cancel Shutdown"), 0, iTimeout + 1, this, flags);
818         QPushButton *cancelButton = new QPushButton(tr("Cancel Shutdown"), &progressDialog);
819         cancelButton->setIcon(QIcon(":/icons/power_on.png"));
820         progressDialog.setModal(true);
821         progressDialog.setAutoClose(false);
822         progressDialog.setAutoReset(false);
823         progressDialog.setWindowIcon(QIcon(":/icons/power_off.png"));
824         progressDialog.setCancelButton(cancelButton);
825         progressDialog.show();
826         
827         QApplication::processEvents();
828
829         if(m_settings->soundsEnabled())
830         {
831                 QApplication::setOverrideCursor(Qt::WaitCursor);
832                 PlaySound(MAKEINTRESOURCE(IDR_WAVE_SHUTDOWN), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
833                 QApplication::restoreOverrideCursor();
834         }
835
836         QTimer timer;
837         timer.setInterval(1000);
838         timer.start();
839
840         QEventLoop eventLoop(this);
841         connect(&timer, SIGNAL(timeout()), &eventLoop, SLOT(quit()));
842         connect(&progressDialog, SIGNAL(canceled()), &eventLoop, SLOT(quit()));
843
844         for(int i = 1; i <= iTimeout; i++)
845         {
846                 eventLoop.exec();
847                 if(progressDialog.wasCanceled())
848                 {
849                         progressDialog.close();
850                         return false;
851                 }
852                 progressDialog.setValue(i+1);
853                 progressDialog.setLabelText(text.arg(iTimeout-i));
854                 if(iTimeout-i == 3) progressDialog.setCancelButton(NULL);
855                 QApplication::processEvents();
856                 PlaySound(MAKEINTRESOURCE((i < iTimeout) ? IDR_WAVE_BEEP : IDR_WAVE_BEEP_LONG), GetModuleHandle(NULL), SND_RESOURCE | SND_SYNC);
857         }
858         
859         progressDialog.close();
860         return true;
861 }