OSDN Git Service

Updated Ukrainian translation file.
[lamexp/LameXP.git] / src / Dialog_CueImport.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2012 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_CueImport.h"
23
24 #include "Global.h"
25 #include "Model_CueSheet.h"
26 #include "Model_AudioFile.h"
27 #include "Model_FileList.h"
28 #include "Dialog_WorkingBanner.h"
29 #include "Thread_FileAnalyzer.h"
30 #include "Thread_CueSplitter.h"
31 #include "LockedFile.h"
32
33 #include <QFileInfo>
34 #include <QMessageBox>
35 #include <QTimer>
36 #include <QFileDialog>
37 #include <QProgressDialog>
38 #include <QMenu>
39 #include <QTextCodec>
40 #include <QInputDialog>
41
42 #define SET_FONT_BOLD(WIDGET,BOLD) { QFont _font = WIDGET->font(); _font.setBold(BOLD); WIDGET->setFont(_font); }
43 #define EXPAND(STR) QString(STR).leftJustified(96, ' ')
44
45 ////////////////////////////////////////////////////////////
46 // Constructor & Destructor
47 ////////////////////////////////////////////////////////////
48
49 CueImportDialog::CueImportDialog(QWidget *parent, FileListModel *fileList, const QString &cueFile)
50 :
51         QDialog(parent),
52         m_cueFileName(cueFile),
53         m_fileList(fileList)
54 {
55         //Init the dialog, from the .ui file
56         setupUi(this);
57
58         //Fix size
59         setMinimumSize(this->size());
60         setMaximumHeight(this->height());
61
62         //Create model
63         m_model = new CueSheetModel();
64         connect(m_model, SIGNAL(modelReset()), this, SLOT(modelChanged()));
65         
66         //Setup table view
67         treeView->setModel(m_model);
68         treeView->header()->setStretchLastSection(false);
69         treeView->header()->setResizeMode(QHeaderView::ResizeToContents);
70         treeView->header()->setResizeMode(1, QHeaderView::Stretch);
71         treeView->header()->setMovable(false);
72         treeView->setItemsExpandable(false);
73
74         //Enable up/down button
75         connect(imprtButton, SIGNAL(clicked()), this, SLOT(importButtonClicked()));
76         connect(browseButton, SIGNAL(clicked()), this, SLOT(browseButtonClicked()));
77         connect(loadOtherButton, SIGNAL(clicked()), this, SLOT(loadOtherButtonClicked()));
78
79         //Translate
80         labelHeaderText->setText(QString("<b>%1</b><br>%2").arg(tr("Import Cue Sheet"), tr("The following Cue Sheet will be split and imported into LameXP.")));
81 }
82
83 CueImportDialog::~CueImportDialog(void)
84 {
85         LAMEXP_DELETE(m_model);
86 }
87
88 ////////////////////////////////////////////////////////////
89 // EVENTS
90 ////////////////////////////////////////////////////////////
91
92 void CueImportDialog::showEvent(QShowEvent *event)
93 {
94         QDialog::showEvent(event);
95         modelChanged();
96 }
97
98 ////////////////////////////////////////////////////////////
99 // Slots
100 ////////////////////////////////////////////////////////////
101
102 int CueImportDialog::exec(void)
103 {
104         WorkingBanner *progress = new WorkingBanner(dynamic_cast<QWidget*>(parent()));
105         progress->show(tr("Loading Cue Sheet file, please be patient..."));
106
107         QFileInfo cueFileInfo(m_cueFileName);
108         if(!cueFileInfo.exists() || !cueFileInfo.isFile())
109         {
110                 QString text = QString("<nobr>%1</nobr><br><nobr>%2</nobr><br><br><nobr>%3</nobr>").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), tr("The specified file could not be found!")).replace("-", "&minus;");
111                 QMessageBox::warning(progress, tr("Cue Sheet Error"), text);
112                 progress->close();
113                 LAMEXP_DELETE(progress);
114                 return CueSheetModel::ErrorIOFailure;
115         }
116
117         //----------------------//
118
119         QTextCodec *codec = NULL;
120
121         QFile cueFile(cueFileInfo.canonicalFilePath());
122         cueFile.open(QIODevice::ReadOnly);
123         QByteArray bomCheck = cueFile.isOpen() ? cueFile.peek(16) : QByteArray();
124
125         if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xef\xbb\xbf"))
126         {
127                 codec = QTextCodec::codecForName("UTF-8");
128         }
129         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xff\xfe"))
130         {
131                 codec = QTextCodec::codecForName("UTF-16LE");
132         }
133         else if((!bomCheck.isEmpty()) && bomCheck.startsWith("\xfe\xff"))
134         {
135                 codec = QTextCodec::codecForName("UTF-16BE");
136         }
137         else
138         {
139                 const QString systemDefault = tr("(System Default)");
140
141                 QStringList codecList;
142                 codecList.append(systemDefault);
143                 codecList.append(lamexp_available_codepages());
144
145                 QInputDialog *input = new QInputDialog(progress);
146                 input->setLabelText(EXPAND(tr("Select ANSI Codepage for Cue Sheet file:")));
147                 input->setOkButtonText(tr("OK"));
148                 input->setCancelButtonText(tr("Cancel"));
149                 input->setTextEchoMode(QLineEdit::Normal);
150                 input->setComboBoxItems(codecList);
151         
152                 if(input->exec() < 1)
153                 {
154                         progress->close();
155                         LAMEXP_DELETE(input);
156                         LAMEXP_DELETE(progress);
157                         return Rejected;
158                 }
159         
160                 if(input->textValue().compare(systemDefault, Qt::CaseInsensitive))
161                 {
162                         qDebug("User-selected codec is: %s", input->textValue().toLatin1().constData());
163                         codec = QTextCodec::codecForName(input->textValue().toLatin1().constData());
164                 }
165                 else
166                 {
167                         qDebug("Going to use the system's default codec!");
168                         codec = QTextCodec::codecForName("System");
169                 }
170
171                 LAMEXP_DELETE(input);
172         }
173
174         bomCheck.clear();
175
176         //----------------------//
177
178         QString baseName = cueFileInfo.completeBaseName().simplified();
179         while(baseName.endsWith(".") || baseName.endsWith(" ")) baseName.chop(1);
180         if(baseName.isEmpty()) baseName = tr("New Folder");
181
182         m_outputDir = QString("%1/%2").arg(cueFileInfo.canonicalPath(), baseName);
183         for(int n = 2; QDir(m_outputDir).exists() || QFileInfo(m_outputDir).exists(); n++)
184         {
185                 m_outputDir = QString("%1/%2 (%3)").arg(cueFileInfo.canonicalPath(), baseName, QString::number(n));
186         }
187
188         setWindowTitle(QString("%1: %2").arg(windowTitle().split(":", QString::SkipEmptyParts).first().trimmed(), cueFileInfo.fileName()));
189
190         int iResult = m_model->loadCueSheet(m_cueFileName, QApplication::instance(), codec);
191         if(iResult != CueSheetModel::ErrorSuccess)
192         {
193                 QString errorMsg = tr("An unknown error has occured!");
194                 
195                 switch(iResult)
196                 {
197                 case CueSheetModel::ErrorIOFailure:
198                         errorMsg = tr("The file could not be opened for reading. Make sure you have the required rights!");
199                         break;
200                 case CueSheetModel::ErrorBadFile:
201                         errorMsg = tr("The provided file does not look like a valid Cue Sheet disc image file!");
202                         break;
203                 case CueSheetModel::ErrorUnsupported:
204                         errorMsg = QString("%1<br>%2").arg(tr("Could not find any supported audio track in the Cue Sheet image!"), tr("Note that LameXP can not handle \"binary\" Cue Sheet images."));
205                         break;
206                 case CueSheetModel::ErrorInconsistent:
207                         errorMsg = tr("The selected Cue Sheet file contains inconsistent information. Take care!");
208                         break;
209                 }
210                 
211                 QString text = QString("<nobr>%1</nobr><br><nobr>%2</nobr><br><br><nobr>%3</nobr>").arg(tr("Failed to load the Cue Sheet file:"), QDir::toNativeSeparators(m_cueFileName), errorMsg).replace("-", "&minus;");
212                 QMessageBox::warning(progress, tr("Cue Sheet Error"), text);
213                 progress->close();
214                 LAMEXP_DELETE(progress);
215                 return iResult;
216         }
217         
218         progress->close();
219         LAMEXP_DELETE(progress);
220         return QDialog::exec();
221 }
222
223 void CueImportDialog::modelChanged(void)
224 {
225         treeView->expandAll();
226         editOutputDir->setText(QDir::toNativeSeparators(m_outputDir));
227         labelArtist->setText(m_model->getAlbumPerformer().isEmpty() ? tr("Unknown Artist") : m_model->getAlbumPerformer());
228         labelAlbum->setText(m_model->getAlbumTitle().isEmpty() ? tr("Unknown Album") : m_model->getAlbumTitle());
229 }
230
231 void CueImportDialog::browseButtonClicked(void)
232 {
233         QString newOutDir, currentDir = m_outputDir;
234         
235         while(QDir(currentDir).exists())
236         {
237                 int pos = qMax(currentDir.lastIndexOf(QChar('\\')), currentDir.lastIndexOf(QChar('/')));
238                 if(pos > 0) currentDir.left(pos - 1); else break;
239         }
240
241         if(lamexp_themes_enabled() || ((QSysInfo::windowsVersion() & QSysInfo::WV_NT_based) < QSysInfo::WV_XP))
242         {
243                 newOutDir = QFileDialog::getExistingDirectory(this, tr("Choose Output Directory"), currentDir);
244         }
245         else
246         {
247                 QFileDialog dialog(this, tr("Choose Output Directory"));
248                 dialog.setFileMode(QFileDialog::DirectoryOnly);
249                 dialog.setDirectory(currentDir);
250                 if(dialog.exec())
251                 {
252                         newOutDir = dialog.selectedFiles().first();
253                 }
254         }
255
256         if(!newOutDir.isEmpty())
257         {
258                 m_outputDir = newOutDir;
259                 modelChanged();
260         }
261 }
262
263 void CueImportDialog::importButtonClicked(void)
264 {
265         static const unsigned __int64 oneGigabyte = 1073741824ui64; 
266         static const unsigned __int64 minimumFreeDiskspaceMultiplier = 2ui64;
267         static const char *writeTestBuffer = "LAMEXP_WRITE_TEST";
268         
269         QDir outputDir(m_outputDir);
270         outputDir.mkpath(".");
271         if(!(outputDir.exists() && outputDir.isReadable()))
272         {
273                 QMessageBox::warning(this, tr("LameXP"), QString("<nobr>%2</nobr>").arg(tr("Error: The selected output directory could not be created!")));
274                 return;
275         }
276
277         QFile writeTest(QString("%1/~%2.txt").arg(m_outputDir, lamexp_rand_str()));
278         if(!(writeTest.open(QIODevice::ReadWrite) && (writeTest.write(writeTestBuffer) == strlen(writeTestBuffer))))
279         {
280                 QMessageBox::warning(this, tr("LameXP"), QString("<nobr>%2</nobr>").arg(tr("Error: The selected output directory is not writable!")));
281                 return;
282         }
283         else
284         {
285                 writeTest.close();
286                 writeTest.remove();
287         }
288
289         bool ok = false;
290         unsigned __int64 currentFreeDiskspace = lamexp_free_diskspace(m_outputDir, &ok);
291
292         if(ok && (currentFreeDiskspace < (oneGigabyte * minimumFreeDiskspaceMultiplier)))
293         {
294                 QMessageBox::warning(this, tr("Low Diskspace Warning"), QString("<nobr>%1</nobr><br><nobr>%2</nobr>").arg(tr("There are less than %1 GB of free diskspace available in the selected output directory.").arg(QString::number(minimumFreeDiskspaceMultiplier)), tr("It is highly recommend to free up more diskspace before proceeding with the import!")));
295                 return;
296         }
297
298         importCueSheet();
299         accept();
300 }
301
302 void CueImportDialog::loadOtherButtonClicked(void)
303 {
304         done(-1);
305 }
306
307 void CueImportDialog::analyzedFile(const AudioFileModel &file)
308 {
309         qDebug("Received result: <%s> <%s/%s>", file.filePath().toLatin1().constData(), file.formatContainerType().toLatin1().constData(), file.formatAudioType().toLatin1().constData());
310         m_fileInfo << file;
311 }
312
313 ////////////////////////////////////////////////////////////
314 // Private Functions
315 ////////////////////////////////////////////////////////////
316
317 void CueImportDialog::importCueSheet(void)
318 {
319         QStringList files;
320
321         //Fetch all files that are referenced in the Cue Sheet and lock them
322         int nFiles = m_model->getFileCount();
323         for(int i = 0; i < nFiles; i++)
324         {
325                 QString temp = m_model->getFileName(i);
326                 try
327                 {
328                         m_locks << new LockedFile(temp);
329                 }
330                 catch(char *err)
331                 {
332                         qWarning("Failed to lock file: %s", err);
333                         continue;
334                 }
335                 files << temp;
336         }
337         
338         //Analyze all source files first
339         if(analyzeFiles(files))
340         {
341                 //Now split files according to Cue Sheet
342                 splitFiles();
343         }
344         
345         //Release locks
346         while(!m_locks.isEmpty())
347         {
348                 delete m_locks.takeFirst();
349         }
350 }
351
352 bool CueImportDialog::analyzeFiles(QStringList &files)
353 {
354         m_fileInfo.clear();
355         bool bSuccess = true;
356
357         WorkingBanner *progress = new WorkingBanner(this);
358         FileAnalyzer *analyzer = new FileAnalyzer(files);
359         
360         connect(analyzer, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection);
361         connect(analyzer, SIGNAL(fileAnalyzed(AudioFileModel)), this, SLOT(analyzedFile(AudioFileModel)), Qt::QueuedConnection);
362         connect(progress, SIGNAL(userAbort()), analyzer, SLOT(abortProcess()), Qt::DirectConnection);
363
364         progress->show(tr("Analyzing file(s), please wait..."), analyzer);
365         progress->close();
366
367         if(analyzer->filesAccepted() < static_cast<unsigned int>(files.count()))
368         {
369                 if(QMessageBox::warning(this, tr("Analysis Failed"), tr("Warning: The format of some of the input files could not be determined!"), tr("Continue Anyway"), tr("Abort")) == 1)
370                 {
371                         bSuccess = false;
372                 }
373         }
374
375         LAMEXP_DELETE(progress);
376         LAMEXP_DELETE(analyzer);
377
378         return bSuccess;
379 }
380
381 void CueImportDialog::splitFiles(void)
382 {
383         QString baseName = QFileInfo(m_cueFileName).completeBaseName().replace(".", " ").simplified();
384
385         WorkingBanner *progress = new WorkingBanner(this);
386         CueSplitter *splitter  = new CueSplitter(m_outputDir, baseName, m_model, m_fileInfo);
387
388         connect(splitter, SIGNAL(fileSelected(QString)), progress, SLOT(setText(QString)), Qt::QueuedConnection);
389         connect(splitter, SIGNAL(fileSplit(AudioFileModel)), m_fileList, SLOT(addFile(AudioFileModel)), Qt::QueuedConnection);
390         connect(splitter, SIGNAL(progressValChanged(unsigned int)), progress, SLOT(setProgressVal(unsigned int)), Qt::QueuedConnection);
391         connect(splitter, SIGNAL(progressMaxChanged(unsigned int)), progress, SLOT(setProgressMax(unsigned int)), Qt::QueuedConnection);
392         connect(progress, SIGNAL(userAbort()), splitter, SLOT(abortProcess()), Qt::DirectConnection);
393
394         progress->show(tr("Splitting file(s), please wait..."), splitter);
395         progress->close();
396
397         if(splitter->getAborted())      
398         {
399                 QMessageBox::warning(this, tr("Cue Sheet Error"), tr("Process was aborted by the user after %1 track(s)!").arg(QString::number(splitter->getTracksSuccess())));
400         }
401         else if(!splitter->getSuccess())
402         {
403                 QMessageBox::warning(this, tr("Cue Sheet Error"), tr("An unexpected error has occured while splitting the Cue Sheet!"));
404         }
405         else
406         {
407                 QString text = QString("<nobr>%1</nobr>").arg(tr("Imported %1 track(s) from the Cue Sheet and skipped %2 track(s).").arg(QString::number(splitter->getTracksSuccess()), QString::number(splitter->getTracksSkipped() /*+ nTracksSkipped*/)));
408                 QMessageBox::information(this, tr("Cue Sheet Completed"), text);
409         }
410
411         LAMEXP_DELETE(splitter);
412         LAMEXP_DELETE(progress);
413 }