Updated avs2wav tool.
[LameXP.git] / src / Thread_CueSplitter.cpp
blobb004749c4acbf7128e3b599aa18984b9b459e81b
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.
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>
40 ////////////////////////////////////////////////////////////
41 // Constructor
42 ////////////////////////////////////////////////////////////
44 CueSplitter::CueSplitter(const QString &outputDir, const QString &baseName, CueSheetModel *model, const QList<AudioFileModel> &inputFilesInfo)
46 m_model(model),
47 m_outputDir(outputDir),
48 m_baseName(baseName),
49 m_soxBin(lamexp_lookup_tool("sox.exe"))
51 if(m_soxBin.isEmpty())
53 qFatal("Invalid path to SoX binary. Tool not initialized properly.");
56 m_decompressedFiles.clear();
57 m_tempFiles.clear();
59 qDebug("\n[CueSplitter]");
61 int nInputFiles = inputFilesInfo.count();
62 for(int i = 0; i < nInputFiles; i++)
64 m_inputFilesInfo.insert(inputFilesInfo[i].filePath(), inputFilesInfo[i]);
65 qDebug("File %02d: <%s>", i, inputFilesInfo[i].filePath().toUtf8().constData());
68 qDebug("All input files added.");
69 m_bSuccess = false;
72 CueSplitter::~CueSplitter(void)
74 while(!m_tempFiles.isEmpty())
76 lamexp_remove_file(m_tempFiles.takeFirst());
80 ////////////////////////////////////////////////////////////
81 // Thread Main
82 ////////////////////////////////////////////////////////////
84 void CueSplitter::run()
86 m_bSuccess = false;
87 m_bAborted = false;
88 m_abortFlag = false;
89 m_nTracksSuccess = 0;
90 m_nTracksSkipped = 0;
91 m_decompressedFiles.clear();
92 m_activeFile.clear();
94 if(!QDir(m_outputDir).exists())
96 qWarning("Output directory \"%s\" does not exist!", m_outputDir.toUtf8().constData());
97 return;
100 QStringList inputFileList = m_inputFilesInfo.keys();
101 int nInputFiles = inputFileList.count();
103 //Decompress all input files
104 for(int i = 0; i < nInputFiles; i++)
106 AudioFileModel &inputFileInfo = m_inputFilesInfo[inputFileList.at(i)];
107 if(inputFileInfo.formatContainerType().compare("Wave", Qt::CaseInsensitive) || inputFileInfo.formatAudioType().compare("PCM", Qt::CaseInsensitive))
109 AbstractDecoder *decoder = DecoderRegistry::lookup(inputFileInfo.formatContainerType(), inputFileInfo.formatContainerProfile(), inputFileInfo.formatAudioType(), inputFileInfo.formatAudioProfile(), inputFileInfo.formatAudioVersion());
110 if(decoder)
112 m_activeFile = QFileInfo(inputFileList.at(i)).fileName().left(54).trimmed();
113 emit fileSelected(m_activeFile);
114 QString tempFile = QString("%1/~%2.wav").arg(m_outputDir, lamexp_rand_str());
115 connect(decoder, SIGNAL(statusUpdated(int)), this, SLOT(handleUpdate(int)), Qt::DirectConnection);
116 if(decoder->decode(inputFileList.at(i), tempFile, &m_abortFlag))
118 m_decompressedFiles.insert(inputFileList.at(i), tempFile);
119 m_tempFiles.append(tempFile);
121 else
123 qWarning("Failed to decompress file: <%s>", inputFileList.at(i).toLatin1().constData());
124 lamexp_remove_file(tempFile);
126 m_activeFile.clear();
127 LAMEXP_DELETE(decoder);
129 else
131 qWarning("Unsupported input file: <%s>", inputFileList.at(i).toLatin1().constData());
134 else
136 m_decompressedFiles.insert(inputFileList.at(i), inputFileList.at(i));
138 if(m_abortFlag)
140 m_bAborted = true;
141 qWarning("The user has requested to abort the process!");
142 return;
146 int nFiles = m_model->getFileCount();
147 QString albumPerformer = m_model->getAlbumPerformer();
148 QString albumTitle = m_model->getAlbumTitle();
150 //Now split all tracks
151 for(int i = 0; i < nFiles; i++)
153 int nTracks = m_model->getTrackCount(i);
154 QString trackFile = m_model->getFileName(i);
155 int maxProgress = 0;
157 for(int j = 0; j < nTracks; j++)
159 int trackNo = m_model->getTrackNo(i, j);
160 double trackOffset = std::numeric_limits<double>::quiet_NaN();
161 double trackLength = std::numeric_limits<double>::quiet_NaN();
162 m_model->getTrackIndex(i, j, &trackOffset, &trackLength);
164 if((trackNo < 0) || _isnan(trackOffset) || _isnan(trackLength))
166 qWarning("Failed to fetch information for track #%d of file #%d!", j, i);
167 continue;
170 AudioFileModel trackMetaInfo(QString().sprintf("cue://File%02d/Track%02d", i, j));
171 trackMetaInfo.setFileName(m_model->getTrackTitle(i, j));
172 trackMetaInfo.setFileArtist(m_model->getTrackPerformer(i, j));
173 trackMetaInfo.setFilePosition(trackNo);
175 if(!albumTitle.isEmpty())
177 trackMetaInfo.setFileAlbum(albumTitle);
179 if(!albumPerformer.isEmpty() && trackMetaInfo.fileArtist().isEmpty())
181 trackMetaInfo.setFileArtist(albumPerformer);
183 if(_finite(trackLength))
185 trackMetaInfo.setFileDuration(static_cast<unsigned int>(abs(trackLength)));
188 QString outputFile = QString("%1/%2 - Track %3.wav").arg(m_outputDir, m_baseName, QString().sprintf("%02d", trackNo));
189 for(int n = 2; QFileInfo(outputFile).exists(); n++)
191 outputFile = QString("%1/%2 - Track %3 (%4).wav").arg(m_outputDir, m_baseName, QString().sprintf("%02d", trackNo), QString::number(n));
194 emit fileSelected(QFileInfo(outputFile).fileName());
195 splitFile(outputFile, trackNo, trackFile, trackOffset, trackLength, trackMetaInfo, maxProgress);
197 if(m_abortFlag)
199 m_bAborted = true;
200 qWarning("The user has requested to abort the process!");
201 return;
206 qDebug("All files were split.\n");
207 m_bSuccess = true;
210 ////////////////////////////////////////////////////////////
211 // Slots
212 ////////////////////////////////////////////////////////////
214 void CueSplitter::handleUpdate(int progress)
216 emit fileSelected(QString("%1 [%2%]").arg(m_activeFile, QString::number(progress)));
219 ////////////////////////////////////////////////////////////
220 // Privtae Functions
221 ////////////////////////////////////////////////////////////
223 void CueSplitter::splitFile(const QString &output, const int trackNo, const QString &file, const double offset, const double length, const AudioFileModel &metaInfo, int &maxProgress)
225 qDebug("[Track %02d]", trackNo);
226 qDebug("File: <%s>", file.toUtf8().constData());
227 qDebug("Offset: <%f> <%s>", offset, indexToString(offset).toLatin1().constData());
228 qDebug("Length: <%f> <%s>", length, indexToString(length).toLatin1().constData());
229 qDebug("Artist: <%s>", metaInfo.fileArtist().toUtf8().constData());
230 qDebug("Title: <%s>", metaInfo.fileName().toUtf8().constData());
232 if(!m_decompressedFiles.contains(file))
234 qWarning("Unknown or unsupported input file, skipping!");
235 m_nTracksSkipped++;
236 return;
239 QString baseName = QFileInfo(output).fileName();
240 QString decompressedInput = m_decompressedFiles[file];
241 qDebug("Input: <%s>", decompressedInput.toUtf8().constData());
243 emit fileSelected(QString("%1 [%2%]").arg(baseName, QString::number(maxProgress)));
245 AudioFileModel outFileInfo(metaInfo);
246 outFileInfo.setFilePath(output);
247 outFileInfo.setFormatContainerType("Wave");
248 outFileInfo.setFormatAudioType("PCM");
250 QStringList args;
251 args << "-S" << "-V3";
252 args << "--guard" << "--temp" << ".";
253 args << QDir::toNativeSeparators(decompressedInput);
254 args << QDir::toNativeSeparators(output);
256 //Add trim parameters, if needed
257 if(_finite(offset))
259 args << "trim";
260 args << indexToString(offset);
262 if(_finite(length))
264 args << indexToString(length);
268 QRegExp rxProgress("In:(\\d+)(\\.\\d+)*%", Qt::CaseInsensitive);
269 QRegExp rxChannels("Channels\\s*:\\s*(\\d+)", Qt::CaseInsensitive);
270 QRegExp rxSamplerate("Sample Rate\\s*:\\s*(\\d+)", Qt::CaseInsensitive);
271 QRegExp rxPrecision("Precision\\s*:\\s*(\\d+)-bit", Qt::CaseInsensitive);
272 QRegExp rxDuration("Duration\\s*:\\s*(\\d\\d):(\\d\\d):(\\d\\d).(\\d\\d)", Qt::CaseInsensitive);
274 QProcess process;
275 process.setProcessChannelMode(QProcess::MergedChannels);
276 process.setReadChannel(QProcess::StandardOutput);
277 process.setWorkingDirectory(m_outputDir);
278 process.start(m_soxBin, args);
280 if(!process.waitForStarted())
282 qWarning("SoX process failed to create!");
283 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
284 process.kill();
285 process.waitForFinished(-1);
286 m_nTracksSkipped++;
287 return;
290 while(process.state() != QProcess::NotRunning)
292 if(m_abortFlag)
294 process.kill();
295 qWarning("Process was aborted on user request!");
296 break;
298 process.waitForReadyRead();
299 if(!process.bytesAvailable() && process.state() == QProcess::Running)
301 process.kill();
302 qWarning("SoX process timed out <-- killing!");
303 break;
305 while(process.bytesAvailable() > 0)
307 QByteArray line = process.readLine();
308 QString text = QString::fromUtf8(line.constData()).simplified();
309 if(rxProgress.lastIndexIn(text) >= 0)
311 bool ok = false;
312 int progress = rxProgress.cap(1).toInt(&ok);
313 if(ok)
315 maxProgress = max(maxProgress, progress);
316 emit fileSelected(QString("%1 [%2%]").arg(baseName, QString::number(maxProgress)));
319 else if(rxChannels.lastIndexIn(text) >= 0)
321 bool ok = false;
322 unsigned int channels = rxChannels.cap(1).toUInt(&ok);
323 if(ok) outFileInfo.setFormatAudioChannels(channels);
325 else if(rxSamplerate.lastIndexIn(text) >= 0)
327 bool ok = false;
328 unsigned int samplerate = rxSamplerate.cap(1).toUInt(&ok);
329 if(ok) outFileInfo.setFormatAudioSamplerate(samplerate);
331 else if(rxPrecision.lastIndexIn(text) >= 0)
333 bool ok = false;
334 unsigned int precision = rxPrecision.cap(1).toUInt(&ok);
335 if(ok) outFileInfo.setFormatAudioBitdepth(precision);
337 else if(rxDuration.lastIndexIn(text) >= 0)
339 bool ok1 = false, ok2 = false, ok3 = false;
340 unsigned int hh = rxDuration.cap(1).toUInt(&ok1);
341 unsigned int mm = rxDuration.cap(2).toUInt(&ok2);
342 unsigned int ss = rxDuration.cap(3).toUInt(&ok3);
343 if(ok1 && ok2 && ok3)
345 unsigned intputLen = (hh * 3600) + (mm * 60) + ss;
346 if(length == std::numeric_limits<double>::infinity())
348 qDebug("Duration updated from SoX info!");
349 int duration = intputLen - static_cast<int>(floor(offset + 0.5));
350 if(duration < 0) qWarning("Track is out of bounds: Track offset exceeds input file duration!");
351 outFileInfo.setFileDuration(max(0, duration));
353 else
355 unsigned int trackEnd = static_cast<unsigned int>(floor(offset + 0.5)) + static_cast<unsigned int>(floor(length + 0.5));
356 if(trackEnd > intputLen) qWarning("Track is out of bounds: End of track exceeds input file duration!");
363 process.waitForFinished();
364 if(process.state() != QProcess::NotRunning)
366 process.kill();
367 process.waitForFinished(-1);
370 if(process.exitStatus() != QProcess::NormalExit || QFileInfo(output).size() == 0)
372 qWarning("Splitting has failed !!!");
373 m_nTracksSkipped++;
374 return;
377 emit fileSplit(outFileInfo);
378 m_nTracksSuccess++;
381 QString CueSplitter::indexToString(const double index) const
383 if(!_finite(index) || (index < 0.0) || (index > 86400.0))
385 return QString();
388 QTime time = QTime().addMSecs(static_cast<int>(floor(0.5 + (index * 1000.0))));
389 return time.toString(time.hour() ? "H:mm:ss.zzz" : "m:ss.zzz");
392 ////////////////////////////////////////////////////////////
393 // EVENTS
394 ////////////////////////////////////////////////////////////
396 /*NONE*/