Bump version.
[LameXP.git] / src / Thread_FileAnalyzer_Task.cpp
blobcc1f7b5aa4af397d71e4c844dee025992dbd56d8
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2015 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, 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"
25 //Internal
26 #include "Global.h"
27 #include "LockedFile.h"
28 #include "Model_AudioFile.h"
30 //MUtils
31 #include <MUtils/Global.h>
32 #include <MUtils/OSSupport.h>
33 #include <MUtils/Exception.h>
35 //Qt
36 #include <QDir>
37 #include <QFileInfo>
38 #include <QProcess>
39 #include <QDate>
40 #include <QTime>
41 #include <QDebug>
42 #include <QImage>
43 #include <QReadLocker>
44 #include <QWriteLocker>
45 #include <QThread>
48 //CRT
49 #include <math.h>
50 #include <time.h>
51 #include <assert.h>
53 #define IS_KEY(KEY) (key.compare(KEY, Qt::CaseInsensitive) == 0)
54 #define IS_SEC(SEC) (key.startsWith((SEC "_"), Qt::CaseInsensitive))
55 #define FIRST_TOK(STR) (STR.split(" ", QString::SkipEmptyParts).first())
57 ////////////////////////////////////////////////////////////
58 // Constructor
59 ////////////////////////////////////////////////////////////
61 AnalyzeTask::AnalyzeTask(const int taskId, const QString &inputFile, const QString &templateFile, volatile bool *abortFlag)
63 m_taskId(taskId),
64 m_inputFile(inputFile),
65 m_templateFile(templateFile),
66 m_mediaInfoBin(lamexp_tools_lookup("mediainfo.exe")),
67 m_avs2wavBin(lamexp_tools_lookup("avs2wav.exe")),
68 m_abortFlag(abortFlag)
70 if(m_mediaInfoBin.isEmpty() || m_avs2wavBin.isEmpty())
72 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
76 AnalyzeTask::~AnalyzeTask(void)
78 emit taskCompleted(m_taskId);
81 ////////////////////////////////////////////////////////////
82 // Thread Main
83 ////////////////////////////////////////////////////////////
85 void AnalyzeTask::run()
87 try
89 run_ex();
91 catch(const std::exception &error)
93 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nException error:\n%s\n", error.what());
94 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
96 catch(...)
98 MUTILS_PRINT_ERROR("\nGURU MEDITATION !!!\n\nUnknown exception error!\n");
99 MUtils::OS::fatal_exit(L"Unhandeled C++ exception error, application will exit!");
103 void AnalyzeTask::run_ex(void)
105 int fileType = fileTypeNormal;
106 QString currentFile = QDir::fromNativeSeparators(m_inputFile);
107 qDebug("Analyzing: %s", MUTILS_UTF8(currentFile));
109 AudioFileModel file = analyzeFile(currentFile, &fileType);
111 if(*m_abortFlag)
113 qWarning("Operation cancelled by user!");
114 return;
117 switch(fileType)
119 case fileTypeDenied:
120 qWarning("Cannot access file for reading, skipping!");
121 break;
122 case fileTypeCDDA:
123 qWarning("Dummy CDDA file detected, skipping!");
124 break;
125 default:
126 if(file.metaInfo().title().isEmpty() || file.techInfo().containerType().isEmpty() || file.techInfo().audioType().isEmpty())
128 fileType = fileTypeUnknown;
129 if(!QFileInfo(currentFile).suffix().compare("cue", Qt::CaseInsensitive))
131 qWarning("Cue Sheet file detected, skipping!");
132 fileType = fileTypeCueSheet;
134 else if(!QFileInfo(currentFile).suffix().compare("avs", Qt::CaseInsensitive))
136 qDebug("Found a potential Avisynth script, investigating...");
137 if(analyzeAvisynthFile(currentFile, file))
139 fileType = fileTypeNormal;
141 else
143 qDebug("Rejected Avisynth file: %s", MUTILS_UTF8(file.filePath()));
146 else
148 qDebug("Rejected file of unknown type: %s", MUTILS_UTF8(file.filePath()));
151 break;
154 //Emit the file now!
155 emit fileAnalyzed(m_taskId, fileType, file);
158 ////////////////////////////////////////////////////////////
159 // Privtae Functions
160 ////////////////////////////////////////////////////////////
162 const AudioFileModel AnalyzeTask::analyzeFile(const QString &filePath, int *type)
164 *type = fileTypeNormal;
165 AudioFileModel audioFile(filePath);
167 QFile readTest(filePath);
168 if(!readTest.open(QIODevice::ReadOnly))
170 *type = fileTypeDenied;
171 return audioFile;
173 if(checkFile_CDDA(readTest))
175 *type = fileTypeCDDA;
176 return audioFile;
178 readTest.close();
180 bool skipNext = false;
181 unsigned int id_val[2] = {UINT_MAX, UINT_MAX};
182 cover_t coverType = coverNone;
183 QByteArray coverData;
185 QStringList params;
186 params << QString("--Inform=file://%1").arg(QDir::toNativeSeparators(m_templateFile));
187 params << QDir::toNativeSeparators(filePath);
189 QProcess process;
190 MUtils::init_process(process, QFileInfo(m_mediaInfoBin).absolutePath());
192 process.start(m_mediaInfoBin, params);
194 if(!process.waitForStarted())
196 qWarning("MediaInfo process failed to create!");
197 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
198 process.kill();
199 process.waitForFinished(-1);
200 return audioFile;
203 while(process.state() != QProcess::NotRunning)
205 if(*m_abortFlag)
207 process.kill();
208 qWarning("Process was aborted on user request!");
209 break;
212 if(!process.waitForReadyRead())
214 if(process.state() == QProcess::Running)
216 qWarning("MediaInfo time out. Killing process and skipping file!");
217 process.kill();
218 process.waitForFinished(-1);
219 return audioFile;
223 QByteArray data;
225 while(process.canReadLine())
227 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
228 if(!line.isEmpty())
230 //qDebug("Line:%s", MUTILS_UTF8(line));
232 int index = line.indexOf('=');
233 if(index > 0)
235 QString key = line.left(index).trimmed();
236 QString val = line.mid(index+1).trimmed();
237 if(!key.isEmpty())
239 updateInfo(audioFile, &skipNext, id_val, &coverType, &coverData, key, val);
246 if(audioFile.metaInfo().title().isEmpty())
248 QString baseName = QFileInfo(filePath).fileName();
249 int index = baseName.lastIndexOf(".");
251 if(index >= 0)
253 baseName = baseName.left(index);
256 baseName = baseName.replace("_", " ").simplified();
257 index = baseName.lastIndexOf(" - ");
259 if(index >= 0)
261 baseName = baseName.mid(index + 3).trimmed();
264 audioFile.metaInfo().setTitle(baseName);
267 process.waitForFinished();
268 if(process.state() != QProcess::NotRunning)
270 process.kill();
271 process.waitForFinished(-1);
274 if((coverType != coverNone) && (!coverData.isEmpty()))
276 retrieveCover(audioFile, coverType, coverData);
279 if((audioFile.techInfo().audioType().compare("PCM", Qt::CaseInsensitive) == 0) && (audioFile.techInfo().audioProfile().compare("Float", Qt::CaseInsensitive) == 0))
281 if(audioFile.techInfo().audioBitdepth() == 32) audioFile.techInfo().setAudioBitdepth(AudioFileModel::BITDEPTH_IEEE_FLOAT32);
284 return audioFile;
287 void AnalyzeTask::updateInfo(AudioFileModel &audioFile, bool *skipNext, unsigned int *id_val, cover_t *coverType, QByteArray *coverData, const QString &key, const QString &value)
289 //qWarning("'%s' -> '%s'", MUTILS_UTF8(key), MUTILS_UTF8(value));
291 /*New Stream*/
292 if(IS_KEY("Gen_ID") || IS_KEY("Aud_ID"))
294 if(value.isEmpty())
296 *skipNext = false;
298 else
300 //We ignore all ID's, except for the lowest one!
301 bool ok = false;
302 unsigned int id = value.toUInt(&ok);
303 if(ok)
305 if(IS_KEY("Gen_ID")) { id_val[0] = qMin(id_val[0], id); *skipNext = (id > id_val[0]); }
306 if(IS_KEY("Aud_ID")) { id_val[1] = qMin(id_val[1], id); *skipNext = (id > id_val[1]); }
308 else
310 *skipNext = true;
313 if(*skipNext)
315 qWarning("Skipping info for non-primary stream!");
317 return;
320 /*Skip or empty?*/
321 if((*skipNext) || value.isEmpty())
323 return;
326 /*Playlist file?*/
327 if(IS_KEY("Aud_Source"))
329 *skipNext = true;
330 audioFile.techInfo().setContainerType(QString());
331 audioFile.techInfo().setAudioType(QString());
332 qWarning("Skipping info for playlist file!");
333 return;
336 /*General Section*/
337 if(IS_SEC("Gen"))
339 if(IS_KEY("Gen_Format"))
341 audioFile.techInfo().setContainerType(value);
343 else if(IS_KEY("Gen_Format_Profile"))
345 audioFile.techInfo().setContainerProfile(value);
347 else if(IS_KEY("Gen_Title") || IS_KEY("Gen_Track"))
349 audioFile.metaInfo().setTitle(value);
351 else if(IS_KEY("Gen_Duration"))
353 unsigned int tmp = parseDuration(value);
354 if(tmp > 0) audioFile.techInfo().setDuration(tmp);
356 else if(IS_KEY("Gen_Artist") || IS_KEY("Gen_Performer"))
358 audioFile.metaInfo().setArtist(value);
360 else if(IS_KEY("Gen_Album"))
362 audioFile.metaInfo().setAlbum(value);
364 else if(IS_KEY("Gen_Genre"))
366 audioFile.metaInfo().setGenre(value);
368 else if(IS_KEY("Gen_Released_Date") || IS_KEY("Gen_Recorded_Date"))
370 unsigned int tmp = parseYear(value);
371 if(tmp > 0) audioFile.metaInfo().setYear(tmp);
373 else if(IS_KEY("Gen_Comment"))
375 audioFile.metaInfo().setComment(value);
377 else if(IS_KEY("Gen_Track/Position"))
379 bool ok = false;
380 unsigned int tmp = value.toUInt(&ok);
381 if(ok) audioFile.metaInfo().setPosition(tmp);
383 else if(IS_KEY("Gen_Cover") || IS_KEY("Gen_Cover_Type"))
385 if(*coverType == coverNone)
387 *coverType = coverJpeg;
390 else if(IS_KEY("Gen_Cover_Mime"))
392 QString temp = FIRST_TOK(value);
393 if(!temp.compare("image/jpeg", Qt::CaseInsensitive)) *coverType = coverJpeg;
394 else if(!temp.compare("image/png", Qt::CaseInsensitive)) *coverType = coverPng;
395 else if(!temp.compare("image/gif", Qt::CaseInsensitive)) *coverType = coverGif;
397 else if(IS_KEY("Gen_Cover_Data"))
399 if(!coverData->isEmpty()) coverData->clear();
400 coverData->append(QByteArray::fromBase64(FIRST_TOK(value).toLatin1()));
402 else
404 qWarning("Unknown key '%s' with value '%s' found!", MUTILS_UTF8(key), MUTILS_UTF8(value));
406 return;
409 /*Audio Section*/
410 if(IS_SEC("Aud"))
413 if(IS_KEY("Aud_Format"))
415 audioFile.techInfo().setAudioType(value);
417 else if(IS_KEY("Aud_Format_Profile"))
419 audioFile.techInfo().setAudioProfile(value);
421 else if(IS_KEY("Aud_Format_Version"))
423 audioFile.techInfo().setAudioVersion(value);
425 else if(IS_KEY("Aud_Channel(s)"))
427 bool ok = false;
428 unsigned int tmp = value.toUInt(&ok);
429 if(ok) audioFile.techInfo().setAudioChannels(tmp);
431 else if(IS_KEY("Aud_SamplingRate"))
433 bool ok = false;
434 unsigned int tmp = value.toUInt(&ok);
435 if(ok) audioFile.techInfo().setAudioSamplerate(tmp);
437 else if(IS_KEY("Aud_BitDepth"))
439 bool ok = false;
440 unsigned int tmp = value.toUInt(&ok);
441 if(ok) audioFile.techInfo().setAudioBitdepth(tmp);
443 else if(IS_KEY("Aud_Duration"))
445 unsigned int tmp = parseDuration(value);
446 if(tmp > 0) audioFile.techInfo().setDuration(tmp);
448 else if(IS_KEY("Aud_BitRate"))
450 bool ok = false;
451 unsigned int tmp = value.toUInt(&ok);
452 if(ok) audioFile.techInfo().setAudioBitrate(tmp/1000);
454 else if(IS_KEY("Aud_BitRate_Mode"))
456 if(!value.compare("CBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeConstant);
457 if(!value.compare("VBR", Qt::CaseInsensitive)) audioFile.techInfo().setAudioBitrateMode(AudioFileModel::BitrateModeVariable);
459 else if(IS_KEY("Aud_Encoded_Library"))
461 audioFile.techInfo().setAudioEncodeLib(value);
463 else
465 qWarning("Unknown key '%s' with value '%s' found!", MUTILS_UTF8(key), MUTILS_UTF8(value));
467 return;
470 /*Section not recognized*/
471 qWarning("Unknown section: %s", MUTILS_UTF8(key));
474 bool AnalyzeTask::checkFile_CDDA(QFile &file)
476 file.reset();
477 QByteArray data = file.read(128);
479 int i = data.indexOf("RIFF");
480 int j = data.indexOf("CDDA");
481 int k = data.indexOf("fmt ");
483 return ((i >= 0) && (j >= 0) && (k >= 0) && (k > j) && (j > i));
486 void AnalyzeTask::retrieveCover(AudioFileModel &audioFile, cover_t coverType, const QByteArray &coverData)
488 qDebug("Retrieving cover!");
489 QString extension;
491 switch(coverType)
493 case coverPng:
494 extension = QString::fromLatin1("png");
495 break;
496 case coverGif:
497 extension = QString::fromLatin1("gif");
498 break;
499 default:
500 extension = QString::fromLatin1("jpg");
501 break;
504 if(!(QImage::fromData(coverData, extension.toUpper().toLatin1().constData()).isNull()))
506 QFile coverFile(QString("%1/%2.%3").arg(MUtils::temp_folder(), MUtils::rand_str(), extension));
507 if(coverFile.open(QIODevice::WriteOnly))
509 coverFile.write(coverData);
510 coverFile.close();
511 audioFile.metaInfo().setCover(coverFile.fileName(), true);
514 else
516 qWarning("Image data seems to be invalid :-(");
520 bool AnalyzeTask::analyzeAvisynthFile(const QString &filePath, AudioFileModel &info)
522 QProcess process;
523 MUtils::init_process(process, QFileInfo(m_avs2wavBin).absolutePath());
525 process.start(m_avs2wavBin, QStringList() << QDir::toNativeSeparators(filePath) << "?");
527 if(!process.waitForStarted())
529 qWarning("AVS2WAV process failed to create!");
530 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
531 process.kill();
532 process.waitForFinished(-1);
533 return false;
536 bool bInfoHeaderFound = false;
538 while(process.state() != QProcess::NotRunning)
540 if(*m_abortFlag)
542 process.kill();
543 qWarning("Process was aborted on user request!");
544 break;
547 if(!process.waitForReadyRead())
549 if(process.state() == QProcess::Running)
551 qWarning("AVS2WAV time out. Killing process and skipping file!");
552 process.kill();
553 process.waitForFinished(-1);
554 return false;
558 QByteArray data;
560 while(process.canReadLine())
562 QString line = QString::fromUtf8(process.readLine().constData()).simplified();
563 if(!line.isEmpty())
565 int index = line.indexOf(':');
566 if(index > 0)
568 QString key = line.left(index).trimmed();
569 QString val = line.mid(index+1).trimmed();
571 if(bInfoHeaderFound && !key.isEmpty() && !val.isEmpty())
573 if(key.compare("TotalSeconds", Qt::CaseInsensitive) == 0)
575 bool ok = false;
576 unsigned int duration = val.toUInt(&ok);
577 if(ok) info.techInfo().setDuration(duration);
579 if(key.compare("SamplesPerSec", Qt::CaseInsensitive) == 0)
581 bool ok = false;
582 unsigned int samplerate = val.toUInt(&ok);
583 if(ok) info.techInfo().setAudioSamplerate (samplerate);
585 if(key.compare("Channels", Qt::CaseInsensitive) == 0)
587 bool ok = false;
588 unsigned int channels = val.toUInt(&ok);
589 if(ok) info.techInfo().setAudioChannels(channels);
591 if(key.compare("BitsPerSample", Qt::CaseInsensitive) == 0)
593 bool ok = false;
594 unsigned int bitdepth = val.toUInt(&ok);
595 if(ok) info.techInfo().setAudioBitdepth(bitdepth);
599 else
601 if(line.contains("[Audio Info]", Qt::CaseInsensitive))
603 info.techInfo().setAudioType("Avisynth");
604 info.techInfo().setContainerType("Avisynth");
605 bInfoHeaderFound = true;
612 process.waitForFinished();
613 if(process.state() != QProcess::NotRunning)
615 process.kill();
616 process.waitForFinished(-1);
619 //Check exit code
620 switch(process.exitCode())
622 case 0:
623 qDebug("Avisynth script was analyzed successfully.");
624 return true;
625 break;
626 case -5:
627 qWarning("It appears that Avisynth is not installed on the system!");
628 return false;
629 break;
630 default:
631 qWarning("Failed to open the Avisynth script, bad AVS file?");
632 return false;
633 break;
637 unsigned int AnalyzeTask::parseYear(const QString &str)
639 if(str.startsWith("UTC", Qt::CaseInsensitive))
641 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
642 if(date.isValid())
644 return date.year();
646 else
648 return 0;
651 else
653 bool ok = false;
654 int year = str.toInt(&ok);
655 if(ok && year > 0)
657 return year;
659 else
661 return 0;
666 unsigned int AnalyzeTask::parseDuration(const QString &str)
668 bool ok = false;
669 unsigned int value = str.toUInt(&ok);
670 return ok ? (value/1000) : 0;
674 ////////////////////////////////////////////////////////////
675 // Public Functions
676 ////////////////////////////////////////////////////////////
678 /*NONE*/
680 ////////////////////////////////////////////////////////////
681 // EVENTS
682 ////////////////////////////////////////////////////////////
684 /*NONE*/