Updated CueImportDialog and CueSheetModel as well as the CueSheet helper classes...
[LameXP.git] / src / Thread_CueSplitter.cpp
blobd98834fd7967a450f859615202b9eaaf66a25542
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2013 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.
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.
19 // http://www.gnu.org/licenses/gpl-2.0.txt
20 ///////////////////////////////////////////////////////////////////////////////
22 #include "Thread_CueSplitter.h"
24 #include "Global.h"
25 #include "LockedFile.h"
26 #include "Model_AudioFile.h"
27 #include "Model_CueSheet.h"
28 #include "Registry_Decoder.h"
29 #include "Decoder_Abstract.h"
31 #include <QDir>
32 #include <QFileInfo>
33 #include <QProcess>
34 #include <QDate>
35 #include <QTime>
36 #include <QDebug>
38 #include <math.h>
39 #include <float.h>
40 #include <limits>
42 ////////////////////////////////////////////////////////////
43 // Constructor
44 ////////////////////////////////////////////////////////////
46 CueSplitter::CueSplitter(const QString &outputDir, const QString &baseName, CueSheetModel *model, const QList<AudioFileModel> &inputFilesInfo)
48 m_model(model),
49 m_outputDir(outputDir),
50 m_baseName(baseName),
51 m_soxBin(lamexp_lookup_tool("sox.exe"))
53 if(m_soxBin.isEmpty())
55 qFatal("Invalid path to SoX binary. Tool not initialized properly.");
58 m_decompressedFiles.clear();
59 m_tempFiles.clear();
61 qDebug("\n[CueSplitter]");
63 int nInputFiles = inputFilesInfo.count();
64 for(int i = 0; i < nInputFiles; i++)
66 m_inputFilesInfo.insert(inputFilesInfo[i].filePath(), inputFilesInfo[i]);
67 qDebug("File %02d: <%s>", i, inputFilesInfo[i].filePath().toUtf8().constData());
70 qDebug("All input files added.");
71 m_bSuccess = false;
74 CueSplitter::~CueSplitter(void)
76 while(!m_tempFiles.isEmpty())
78 lamexp_remove_file(m_tempFiles.takeFirst());
82 ////////////////////////////////////////////////////////////
83 // Thread Main
84 ////////////////////////////////////////////////////////////
86 void CueSplitter::run()
88 m_bSuccess = false;
89 m_bAborted = false;
90 m_abortFlag = false;
91 m_nTracksSuccess = 0;
92 m_nTracksSkipped = 0;
93 m_decompressedFiles.clear();
94 m_activeFile.clear();
96 if(!QDir(m_outputDir).exists())
98 qWarning("Output directory \"%s\" does not exist!", m_outputDir.toUtf8().constData());
99 return;
102 QStringList inputFileList = m_inputFilesInfo.keys();
103 int nInputFiles = inputFileList.count();
105 emit progressMaxChanged(nInputFiles);
106 emit progressValChanged(0);
108 //Decompress all input files
109 for(int i = 0; i < nInputFiles; i++)
111 const AudioFileModel_TechInfo &inputFileInfo = m_inputFilesInfo[inputFileList.at(i)].techInfo();
112 if(inputFileInfo.containerType().compare("Wave", Qt::CaseInsensitive) || inputFileInfo.audioType().compare("PCM", Qt::CaseInsensitive))
114 AbstractDecoder *decoder = DecoderRegistry::lookup(inputFileInfo.containerType(), inputFileInfo.containerProfile(), inputFileInfo.audioType(), inputFileInfo.audioProfile(), inputFileInfo.audioVersion());
115 if(decoder)
117 m_activeFile = shortName(QFileInfo(inputFileList.at(i)).fileName());
119 emit fileSelected(m_activeFile);
120 emit progressValChanged(i+1);
122 QString tempFile = QString("%1/~%2.wav").arg(m_outputDir, lamexp_rand_str());
123 connect(decoder, SIGNAL(statusUpdated(int)), this, SLOT(handleUpdate(int)), Qt::DirectConnection);
125 if(decoder->decode(inputFileList.at(i), tempFile, &m_abortFlag))
127 m_decompressedFiles.insert(inputFileList.at(i), tempFile);
128 m_tempFiles.append(tempFile);
130 else
132 qWarning("Failed to decompress file: <%s>", inputFileList.at(i).toLatin1().constData());
133 lamexp_remove_file(tempFile);
136 m_activeFile.clear();
137 LAMEXP_DELETE(decoder);
139 else
141 qWarning("Unsupported input file: <%s>", inputFileList.at(i).toLatin1().constData());
144 else
146 m_decompressedFiles.insert(inputFileList.at(i), inputFileList.at(i));
149 if(m_abortFlag)
151 m_bAborted = true;
152 qWarning("The user has requested to abort the process!");
153 return;
157 int nFiles = m_model->getFileCount();
158 int nTracksTotal = 0, nTracksComplete = 0;
160 for(int i = 0; i < nFiles; i++)
162 nTracksTotal += m_model->getTrackCount(i);
165 emit progressMaxChanged(nTracksTotal);
166 emit progressValChanged(0);
168 const AudioFileModel_MetaInfo *albumInfo = m_model->getAlbumInfo();
170 //Now split all files
171 for(int i = 0; i < nFiles; i++)
173 int nTracks = m_model->getTrackCount(i);
174 QString trackFile = m_model->getFileName(i);
175 int maxProgress = 0;
177 //Process all tracks
178 for(int j = 0; j < nTracks; j++)
180 const AudioFileModel_MetaInfo *trackInfo = m_model->getTrackInfo(i, j);
181 const int trackNo = trackInfo->position();
182 double trackOffset = std::numeric_limits<double>::quiet_NaN();
183 double trackLength = std::numeric_limits<double>::quiet_NaN();
184 m_model->getTrackIndex(i, j, &trackOffset, &trackLength);
186 if((trackNo < 0) || _isnan(trackOffset) || _isnan(trackLength))
188 qWarning("Failed to fetch information for track #%d of file #%d!", j, i);
189 continue;
192 //Setup meta info
193 AudioFileModel_MetaInfo trackMetaInfo(*trackInfo);
195 //Apply album meta data on files
196 if(trackMetaInfo.title().trimmed().isEmpty())
198 trackMetaInfo.setTitle(QString().sprintf("Track %02d", trackNo));
200 trackMetaInfo.update(*albumInfo, false);
202 //Generate output file name
203 QString trackTitle = trackMetaInfo.title().isEmpty() ? QString().sprintf("Track %02d", trackNo) : trackMetaInfo.title();
204 QString outputFile = QString("%1/[%2] %3 - %4.wav").arg(m_outputDir, QString().sprintf("%02d", trackNo), lamexp_clean_filename(m_baseName), lamexp_clean_filename(trackTitle));
205 for(int n = 2; QFileInfo(outputFile).exists(); n++)
207 outputFile = QString("%1/[%2] %3 - %4 (%5).wav").arg(m_outputDir, QString().sprintf("%02d", trackNo), lamexp_clean_filename(m_baseName), lamexp_clean_filename(trackTitle), QString::number(n));
210 //Call split function
211 emit fileSelected(shortName(QFileInfo(outputFile).fileName()));
212 splitFile(outputFile, trackNo, trackFile, trackOffset, trackLength, trackMetaInfo, maxProgress);
213 emit progressValChanged(++nTracksComplete);
215 if(m_abortFlag)
217 m_bAborted = true;
218 qWarning("The user has requested to abort the process!");
219 return;
224 emit progressValChanged(nTracksTotal);
225 lamexp_sleep(333);
227 qDebug("All files were split.\n");
228 m_bSuccess = true;
231 ////////////////////////////////////////////////////////////
232 // Slots
233 ////////////////////////////////////////////////////////////
235 void CueSplitter::handleUpdate(int progress)
237 //QString("%1 [%2]").arg(m_activeFile, QString::number(progress)))
240 ////////////////////////////////////////////////////////////
241 // Privtae Functions
242 ////////////////////////////////////////////////////////////
244 void CueSplitter::splitFile(const QString &output, const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel_MetaInfo &metaInfo, int &maxProgress)
246 qDebug("[Track %02d]", trackNo);
247 qDebug("File: <%s>", file.toUtf8().constData());
248 qDebug("Offset: <%f> <%s>", offset, indexToString(offset).toLatin1().constData());
249 qDebug("Length: <%f> <%s>", length, indexToString(length).toLatin1().constData());
250 qDebug("Artist: <%s>", metaInfo.artist().toUtf8().constData());
251 qDebug("Title: <%s>", metaInfo.title().toUtf8().constData());
252 qDebug("Album: <%s>", metaInfo.album().toUtf8().constData());
254 if(!m_decompressedFiles.contains(file))
256 qWarning("Unknown or unsupported input file, skipping!");
257 m_nTracksSkipped++;
258 return;
261 QString baseName = shortName(QFileInfo(output).fileName());
262 QString decompressedInput = m_decompressedFiles[file];
263 qDebug("Input: <%s>", decompressedInput.toUtf8().constData());
265 //emit fileSelected(QString("%1 [%2%]").arg(baseName, QString::number(maxProgress)));
267 AudioFileModel outFileInfo(output);
268 outFileInfo.setMetaInfo(metaInfo);
270 AudioFileModel_TechInfo &outFileTechInfo = outFileInfo.techInfo();
271 outFileTechInfo.setContainerType("Wave");
272 outFileTechInfo.setAudioType("PCM");
273 outFileTechInfo.setDuration(static_cast<unsigned int>(abs(length)));
275 QStringList args;
276 args << "-S" << "-V3";
277 args << "--guard" << "--temp" << ".";
278 args << QDir::toNativeSeparators(decompressedInput);
279 args << QDir::toNativeSeparators(output);
281 //Add trim parameters, if needed
282 if(_finite(offset))
284 args << "trim";
285 args << indexToString(offset);
287 if(_finite(length))
289 args << indexToString(length);
293 QRegExp rxProgress("In:(\\d+)(\\.\\d+)*%", Qt::CaseInsensitive);
294 QRegExp rxChannels("Channels\\s*:\\s*(\\d+)", Qt::CaseInsensitive);
295 QRegExp rxSamplerate("Sample Rate\\s*:\\s*(\\d+)", Qt::CaseInsensitive);
296 QRegExp rxPrecision("Precision\\s*:\\s*(\\d+)-bit", Qt::CaseInsensitive);
297 QRegExp rxDuration("Duration\\s*:\\s*(\\d\\d):(\\d\\d):(\\d\\d).(\\d\\d)", Qt::CaseInsensitive);
299 QProcess process;
300 process.setProcessChannelMode(QProcess::MergedChannels);
301 process.setReadChannel(QProcess::StandardOutput);
302 process.setWorkingDirectory(m_outputDir);
303 process.start(m_soxBin, args);
305 if(!process.waitForStarted())
307 qWarning("SoX process failed to create!");
308 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
309 process.kill();
310 process.waitForFinished(-1);
311 m_nTracksSkipped++;
312 return;
315 while(process.state() != QProcess::NotRunning)
317 if(m_abortFlag)
319 process.kill();
320 qWarning("Process was aborted on user request!");
321 break;
323 process.waitForReadyRead(m_processTimeoutInterval);
324 if(!process.bytesAvailable() && process.state() == QProcess::Running)
326 process.kill();
327 qWarning("SoX process timed out <-- killing!");
328 break;
330 while(process.bytesAvailable() > 0)
332 QByteArray line = process.readLine();
333 QString text = QString::fromUtf8(line.constData()).simplified();
334 if(rxProgress.lastIndexIn(text) >= 0)
336 bool ok = false;
337 int progress = rxProgress.cap(1).toInt(&ok);
338 if(ok)
340 maxProgress = qMax(maxProgress, progress);
341 //emit fileSelected(QString("%1 [%2%]").arg(baseName, QString::number(maxProgress)));
344 else if(rxChannels.lastIndexIn(text) >= 0)
346 bool ok = false;
347 unsigned int channels = rxChannels.cap(1).toUInt(&ok);
348 if(ok) outFileInfo.techInfo().setAudioChannels(channels);
350 else if(rxSamplerate.lastIndexIn(text) >= 0)
352 bool ok = false;
353 unsigned int samplerate = rxSamplerate.cap(1).toUInt(&ok);
354 if(ok) outFileInfo.techInfo().setAudioSamplerate(samplerate);
356 else if(rxPrecision.lastIndexIn(text) >= 0)
358 bool ok = false;
359 unsigned int precision = rxPrecision.cap(1).toUInt(&ok);
360 if(ok) outFileInfo.techInfo().setAudioBitdepth(precision);
362 else if(rxDuration.lastIndexIn(text) >= 0)
364 bool ok1 = false, ok2 = false, ok3 = false;
365 unsigned int hh = rxDuration.cap(1).toUInt(&ok1);
366 unsigned int mm = rxDuration.cap(2).toUInt(&ok2);
367 unsigned int ss = rxDuration.cap(3).toUInt(&ok3);
368 if(ok1 && ok2 && ok3)
370 unsigned intputLen = (hh * 3600) + (mm * 60) + ss;
371 if(length == std::numeric_limits<double>::infinity())
373 qDebug("Duration updated from SoX info!");
374 int duration = intputLen - static_cast<int>(floor(offset + 0.5));
375 if(duration < 0) qWarning("Track is out of bounds: Track offset exceeds input file duration!");
376 outFileInfo.techInfo().setDuration(qMax(0, duration));
378 else
380 unsigned int trackEnd = static_cast<unsigned int>(floor(offset + 0.5)) + static_cast<unsigned int>(floor(length + 0.5));
381 if(trackEnd > intputLen) qWarning("Track is out of bounds: End of track exceeds input file duration!");
388 process.waitForFinished();
389 if(process.state() != QProcess::NotRunning)
391 process.kill();
392 process.waitForFinished(-1);
395 if(process.exitCode() != EXIT_SUCCESS || QFileInfo(output).size() == 0)
397 qWarning("Splitting has failed !!!");
398 m_nTracksSkipped++;
399 return;
402 emit fileSplit(outFileInfo);
403 m_nTracksSuccess++;
406 QString CueSplitter::indexToString(const double index) const
408 if(!_finite(index) || (index < 0.0) || (index > 86400.0))
410 return QString();
413 QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
414 return time.toString(time.hour() ? "H:mm:ss.zzz" : "m:ss.zzz");
417 QString CueSplitter::shortName(const QString &longName) const
419 static const int maxLen = 54;
421 if(longName.length() > maxLen)
423 return QString("%1...%2").arg(longName.left(maxLen/2).trimmed(), longName.right(maxLen/2).trimmed());
426 return longName;
429 ////////////////////////////////////////////////////////////
430 // EVENTS
431 ////////////////////////////////////////////////////////////
433 /*NONE*/