1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2014 LoRd_MuldeR <MuldeR2@GMX.de>
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, but always including the *additional*
9 // restrictions defined in the "License.txt" file.
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
16 // You should have received a copy of the GNU General Public License along
17 // with this program; if not, write to the Free Software Foundation, Inc.,
18 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 // http://www.gnu.org/licenses/gpl-2.0.txt
21 ///////////////////////////////////////////////////////////////////////////////
23 #include "Thread_FileAnalyzer_Task.h"
26 #include "LockedFile.h"
27 #include "Model_AudioFile.h"
36 #include <QReadLocker>
37 #include <QWriteLocker>
44 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
45 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
46 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
48 ////////////////////////////////////////////////////////////
50 ////////////////////////////////////////////////////////////
52 AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
55 m_inputFile(inputFile),
56 m_templateFile(templateFile),
57 m_mediaInfoBin(lamexp_lookup_tool("mediainfo.exe")),
58 m_avs2wavBin(lamexp_lookup_tool("avs2wav.exe")),
59 m_abortFlag(abortFlag)
61 if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
63 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
67 AnalyzeTask::~AnalyzeTask(void)
69 emit taskCompleted(m_taskId);
72 ////////////////////////////////////////////////////////////
74 ////////////////////////////////////////////////////////////
76 void AnalyzeTask::run()
82 catch(const std::exception &error)
84 fflush(stdout); fflush(stderr);
85 fprintf(stderr, "\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
86 lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
90 fflush(stdout); fflush(stderr);
91 fprintf(stderr, "\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
92 lamexp_fatal_exit(L"Unhandeled C++ exception error, application will exit!");
96 void AnalyzeTask::run_ex(void)
98 int fileType = fileTypeNormal;
99 QString currentFile = QDir::fromNativeSeparators(m_inputFile);
100 qDebug("Analyzing: %s", QUTF8(currentFile));
102 AudioFileModel file = analyzeFile(currentFile, &fileType);
106 qWarning("Operation cancelled by user!");
113 qWarning("Cannot access file for reading, skipping!");
116 qWarning("Dummy CDDA file detected, skipping!");
119 if(file.metaInfo().title().isEmpty() || file.techInfo().containerType().isEmpty() || file.techInfo().audioType().isEmpty())
121 fileType = fileTypeUnknown;
122 if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
124 qWarning("Cue Sheet file detected, skipping!");
125 fileType = fileTypeCueSheet;
127 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
129 qDebug("Found a potential Avisynth script, investigating...");
130 if(analyzeAvisynthFile(currentFile, file))
132 fileType = fileTypeNormal;
136 qDebug("Rejected Avisynth file: %s", QUTF8(file.filePath()));
141 qDebug("Rejected file of unknown type: %s", QUTF8(file.filePath()));
148 emit fileAnalyzed(m_taskId, fileType, file);
151 ////////////////////////////////////////////////////////////
153 ////////////////////////////////////////////////////////////
155 const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type)
157 *type = fileTypeNormal;
158 AudioFileModel audioFile(filePath);
160 QFile readTest(filePath);
161 if(!readTest.open(QIODevice::ReadOnly))
163 *type = fileTypeDenied;
166 if(checkFile_CDDA(readTest))
168 *type = fileTypeCDDA;
173 bool skipNext = false;
174 unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
175 cover_t coverType = coverNone;
176 QByteArray coverData;
179 params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile));
180 params << QDir::toNativeSeparators(filePath);
183 lamexp_init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
185 process.start(m_mediaInfoBin, params);
187 if(!process.waitForStarted())
189 qWarning("MediaInfo process failed to create!");
190 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
192 process.waitForFinished(-1);
196 while(process.state() != QProcess::NotRunning)
201 qWarning("Process was aborted on user request!");
205 if(!process.waitForReadyRead())
207 if(process.state() == QProcess::Running)
209 qWarning("MediaInfo time out. Killing process and skipping file!");
211 process.waitForFinished(-1);
218 while(process.canReadLine())
220 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
223 //qDebug("Line:%s", QUTF8(line));
225 int index = line.indexOf('=');
228 QString key = line.left(index).trimmed();
229 QString val = line.mid(index+1).trimmed();
232 updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
239 if(audioFile.metaInfo().title().isEmpty())
241 QString baseName = QFileInfo(filePath).fileName();
242 int index = baseName.lastIndexOf(".");
246 baseName = baseName.left(index);
249 baseName = baseName.replace("_", " ").simplified();
250 index = baseName.lastIndexOf(" - ");
254 baseName = baseName.mid(index + 3).trimmed();
257 audioFile.metaInfo().setTitle(baseName);
260 process.waitForFinished();
261 if(process.state() != QProcess::NotRunning)
264 process.waitForFinished(-1);
267 if((coverType != coverNone) && (!coverData.isEmpty()))
269 retrieveCover(audioFile, coverType, coverData);
272 if((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
274 if(audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
280 void AnalyzeTask::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
282 //qWarning("'%s' -> '%s'", QUTF8(key), QUTF8(value));
285 if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
293 //We ignore all ID's, except for the lowest one!
295 unsigned int id = value.toUInt(&ok);
298 if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
299 if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
308 qWarning("Skipping info for non-primary stream!");
314 if((*skipNext) || value.isEmpty())
320 if(IS_KEY("Aud_Source"))
323 audioFile.techInfo().setContainerType(QString());
324 audioFile.techInfo().setAudioType(QString());
325 qWarning("Skipping info for playlist file!");
332 if(IS_KEY("Gen_Format"))
334 audioFile.techInfo().setContainerType(value);
336 else if(IS_KEY("Gen_Format_Profile"))
338 audioFile.techInfo().setContainerProfile(value);
340 else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
342 audioFile.metaInfo().setTitle(value);
344 else if(IS_KEY("Gen_Duration"))
346 unsigned int tmp = parseDuration(value);
347 if(tmp > 0) audioFile.techInfo().setDuration(tmp);
349 else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
351 audioFile.metaInfo().setArtist(value);
353 else if(IS_KEY("Gen_Album"))
355 audioFile.metaInfo().setAlbum(value);
357 else if(IS_KEY("Gen_Genre"))
359 audioFile.metaInfo().setGenre(value);
361 else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
363 unsigned int tmp = parseYear(value);
364 if(tmp > 0) audioFile.metaInfo().setYear(tmp);
366 else if(IS_KEY("Gen_Comment"))
368 audioFile.metaInfo().setComment(value);
370 else if(IS_KEY("Gen_Track/Position"))
373 unsigned int tmp = value.toUInt(&ok);
374 if(ok) audioFile.metaInfo().setPosition(tmp);
376 else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
378 if(*coverType == coverNone)
380 *coverType = coverJpeg;
383 else if(IS_KEY("Gen_Cover_Mime"))
385 QString temp = FIRST_TOK(value);
386 if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
387 else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
388 else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
390 else if(IS_KEY("Gen_Cover_Data"))
392 if(!coverData->isEmpty()) coverData->clear();
393 coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
397 qWarning("Unknown key '%s' with value '%s' found!", QUTF8(key), QUTF8(value));
406 if(IS_KEY("Aud_Format"))
408 audioFile.techInfo().setAudioType(value);
410 else if(IS_KEY("Aud_Format_Profile"))
412 audioFile.techInfo().setAudioProfile(value);
414 else if(IS_KEY("Aud_Format_Version"))
416 audioFile.techInfo().setAudioVersion(value);
418 else if(IS_KEY("Aud_Channel(s)"))
421 unsigned int tmp = value.toUInt(&ok);
422 if(ok) audioFile.techInfo().setAudioChannels(tmp);
424 else if(IS_KEY("Aud_SamplingRate"))
427 unsigned int tmp = value.toUInt(&ok);
428 if(ok) audioFile.techInfo().setAudioSamplerate(tmp);
430 else if(IS_KEY("Aud_BitDepth"))
433 unsigned int tmp = value.toUInt(&ok);
434 if(ok) audioFile.techInfo().setAudioBitdepth(tmp);
436 else if(IS_KEY("Aud_Duration"))
438 unsigned int tmp = parseDuration(value);
439 if(tmp > 0) audioFile.techInfo().setDuration(tmp);
441 else if(IS_KEY("Aud_BitRate"))
444 unsigned int tmp = value.toUInt(&ok);
445 if(ok) audioFile.techInfo().setAudioBitrate(tmp/1000);
447 else if(IS_KEY("Aud_BitRate_Mode"))
449 if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeConstant);
450 if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeVariable);
452 else if(IS_KEY("Aud_Encoded_Library"))
454 audioFile.techInfo().setAudioEncodeLib(value);
458 qWarning("Unknown key '%s' with value '%s' found!", QUTF8(key), QUTF8(value));
463 /*Section not recognized*/
464 qWarning("Unknown section: %s", QUTF8(key));
467 bool AnalyzeTask::checkFile_CDDA(QFile &file)
470 QByteArray data = file.read(128);
472 int i = data.indexOf("RIFF");
473 int j = data.indexOf("CDDA");
474 int k = data.indexOf("fmt ");
476 return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
479 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
481 qDebug("Retrieving cover!");
487 extension = QString::fromLatin1("png");
490 extension = QString::fromLatin1("gif");
493 extension = QString::fromLatin1("jpg");
497 if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
499 QFile coverFile(QString("%1/%2.%3").arg(lamexp_temp_folder2(), lamexp_rand_str(), extension));
500 if(coverFile.open(QIODevice::WriteOnly))
502 coverFile.write(coverData);
504 audioFile.metaInfo().setCover(coverFile.fileName(), true);
509 qWarning("Image data seems to be invalid :-(");
513 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
516 lamexp_init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
518 process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
520 if(!process.waitForStarted())
522 qWarning("AVS2WAV process failed to create!");
523 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
525 process.waitForFinished(-1);
529 bool bInfoHeaderFound = false;
531 while(process.state() != QProcess::NotRunning)
536 qWarning("Process was aborted on user request!");
540 if(!process.waitForReadyRead())
542 if(process.state() == QProcess::Running)
544 qWarning("AVS2WAV time out. Killing process and skipping file!");
546 process.waitForFinished(-1);
553 while(process.canReadLine())
555 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
558 int index = line.indexOf(':');
561 QString key = line.left(index).trimmed();
562 QString val = line.mid(index+1).trimmed();
564 if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
566 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
569 unsigned int duration = val.toUInt(&ok);
570 if(ok) info.techInfo().setDuration(duration);
572 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
575 unsigned int samplerate = val.toUInt(&ok);
576 if(ok) info.techInfo().setAudioSamplerate (samplerate);
578 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
581 unsigned int channels = val.toUInt(&ok);
582 if(ok) info.techInfo().setAudioChannels(channels);
584 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
587 unsigned int bitdepth = val.toUInt(&ok);
588 if(ok) info.techInfo().setAudioBitdepth(bitdepth);
594 if(line.contains("[Audio Info]", Qt::CaseInsensitive))
596 info.techInfo().setAudioType("Avisynth");
597 info.techInfo().setContainerType("Avisynth");
598 bInfoHeaderFound = true;
605 process.waitForFinished();
606 if(process.state() != QProcess::NotRunning)
609 process.waitForFinished(-1);
613 switch(process.exitCode())
616 qDebug("Avisynth script was analyzed successfully.");
620 qWarning("It appears that Avisynth is not installed on the system!");
624 qWarning("Failed to open the Avisynth script, bad AVS file?");
630 unsigned int AnalyzeTask::parseYear(const QString &str)
632 if(str.startsWith("UTC", Qt::CaseInsensitive))
634 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
647 int year = str.toInt(&ok);
659 unsigned int AnalyzeTask::parseDuration(const QString &str)
662 unsigned int value = str.toUInt(&ok);
663 return ok ? (value/1000) : 0;
667 ////////////////////////////////////////////////////////////
669 ////////////////////////////////////////////////////////////
673 ////////////////////////////////////////////////////////////
675 ////////////////////////////////////////////////////////////