OSDN Git Service

Update MediaInfo to v0.7.38.
[lamexp/LameXP.git] / src / Thread_FileAnalyzer.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // LameXP - Audio Encoder Front-End
3 // Copyright (C) 2004-2010 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 "Thread_FileAnalyzer.h"
23
24 #include "Global.h"
25 #include "LockedFile.h"
26 #include "Model_AudioFile.h"
27
28 #include <QDir>
29 #include <QFileInfo>
30 #include <QProcess>
31 #include <QDate>
32 #include <QTime>
33 #include <QDebug>
34
35 #include <math.h>
36
37 ////////////////////////////////////////////////////////////
38 // Constructor
39 ////////////////////////////////////////////////////////////
40
41 FileAnalyzer::FileAnalyzer(const QStringList &inputFiles)
42 :
43         m_inputFiles(inputFiles),
44         m_mediaInfoBin_x86(lamexp_lookup_tool("mediainfo_i386.exe")),
45         m_mediaInfoBin_x64(lamexp_lookup_tool("mediainfo_x64.exe"))
46 {
47         m_bSuccess = false;
48         
49         if(m_mediaInfoBin_x86.isEmpty() || m_mediaInfoBin_x64.isEmpty())
50         {
51                 qFatal("Invalid path to MediaInfo binary. Tool not initialized properly.");
52         }
53
54         m_filesAccepted = 0;
55         m_filesRejected = 0;
56         m_filesDenied = 0;
57 }
58
59 ////////////////////////////////////////////////////////////
60 // Thread Main
61 ////////////////////////////////////////////////////////////
62
63 void FileAnalyzer::run()
64 {
65         m_bSuccess = false;
66
67         m_filesAccepted = 0;
68         m_filesRejected = 0;
69         m_filesDenied = 0;
70
71         m_inputFiles.sort();
72
73         while(!m_inputFiles.isEmpty())
74         {
75                 QString currentFile = QDir::fromNativeSeparators(m_inputFiles.takeFirst());
76                 qDebug("Analyzing: %s", currentFile.toUtf8().constData());
77                 emit fileSelected(QFileInfo(currentFile).fileName());
78                 AudioFileModel file = analyzeFile(currentFile);
79                 if(file.fileName().isEmpty() || file.formatContainerType().isEmpty() || file.formatAudioType().isEmpty())
80                 {
81                         m_filesRejected++;
82                         qDebug("Skipped: %s", file.filePath().toUtf8().constData());
83                         continue;
84                 }
85                 m_filesAccepted++;
86                 emit fileAnalyzed(file);
87         }
88
89         qDebug("All files added.\n");
90         m_bSuccess = true;
91 }
92
93 ////////////////////////////////////////////////////////////
94 // Public Functions
95 ////////////////////////////////////////////////////////////
96
97 const AudioFileModel FileAnalyzer::analyzeFile(const QString &filePath)
98 {
99         lamexp_cpu_t cpuInfo = lamexp_detect_cpu_features();
100         const QString mediaInfoBin = cpuInfo.x64 ? m_mediaInfoBin_x64 : m_mediaInfoBin_x86;
101         
102         AudioFileModel audioFile(filePath);
103         m_currentSection = sectionOther;
104
105         QFile readTest(filePath);
106         if(!readTest.open(QIODevice::ReadOnly))
107         {
108                 qWarning("Cannot access file for reading, skipping!");
109                 m_filesDenied++;
110                 return audioFile;
111         }
112         else
113         {
114                 readTest.close();
115         }
116
117         QProcess process;
118         process.setProcessChannelMode(QProcess::MergedChannels);
119         process.setReadChannel(QProcess::StandardOutput);
120         process.start(mediaInfoBin, QStringList() << QDir::toNativeSeparators(filePath));
121         
122         if(!process.waitForStarted())
123         {
124                 qWarning("MediaInfo process failed to create!");
125                 qWarning("Error message: \"%s\"\n", process.errorString().toLatin1().constData());
126                 process.kill();
127                 process.waitForFinished(-1);
128                 return audioFile;
129         }
130
131         while(process.state() != QProcess::NotRunning)
132         {
133                 if(!process.waitForReadyRead())
134                 {
135                         if(process.state() == QProcess::Running)
136                         {
137                                 qWarning("MediaInfo time out. Killing process and skipping file!");
138                                 process.kill();
139                                 process.waitForFinished(-1);
140                                 return audioFile;
141                         }
142                 }
143
144                 QByteArray data;
145
146                 while(process.canReadLine())
147                 {
148                         QString line = QString::fromUtf8(process.readLine().constData()).simplified();
149                         if(!line.isEmpty())
150                         {
151                                 int index = line.indexOf(':');
152                                 if(index > 0)
153                                 {
154                                         QString key = line.left(index-1).trimmed();
155                                         QString val = line.mid(index+1).trimmed();
156                                         if(!key.isEmpty() && !val.isEmpty())
157                                         {
158                                                 updateInfo(audioFile, key, val);
159                                         }
160                                 }
161                                 else
162                                 {
163                                         updateSection(line);
164                                 }
165                         }
166                 }
167         }
168
169         if(audioFile.fileName().isEmpty())
170         {
171                 QString baseName = QFileInfo(filePath).fileName();
172                 int index = baseName.lastIndexOf(".");
173
174                 if(index >= 0)
175                 {
176                         baseName = baseName.left(index);
177                 }
178
179                 baseName = baseName.replace("_", " ").simplified();
180                 index = baseName.lastIndexOf(" - ");
181
182                 if(index >= 0)
183                 {
184                         baseName = baseName.mid(index + 3).trimmed();
185                 }
186
187                 audioFile.setFileName(baseName);
188         }
189         
190         return audioFile;
191 }
192
193 void FileAnalyzer::updateSection(const QString &section)
194 {
195         if(section.startsWith("General", Qt::CaseInsensitive))
196         {
197                 m_currentSection = sectionGeneral;
198         }
199         else if(!section.compare("Audio", Qt::CaseInsensitive) || section.startsWith("Audio #1", Qt::CaseInsensitive))
200         {
201                 m_currentSection = sectionAudio;
202         }
203         else if(section.startsWith("Audio", Qt::CaseInsensitive) || section.startsWith("Video", Qt::CaseInsensitive) || section.startsWith("Text", Qt::CaseInsensitive) ||
204                 section.startsWith("Menu", Qt::CaseInsensitive) || section.startsWith("Image", Qt::CaseInsensitive) || section.startsWith("Chapters", Qt::CaseInsensitive))
205         {
206                 m_currentSection = sectionOther;
207         }
208         else
209         {
210                 qWarning("Unknown section: %s", section.toUtf8().constData());
211         }
212 }
213
214 void FileAnalyzer::updateInfo(AudioFileModel &audioFile, const QString &key, const QString &value)
215 {
216         switch(m_currentSection)
217         {
218         case sectionGeneral:
219                 if(!key.compare("Title", Qt::CaseInsensitive) || !key.compare("Track", Qt::CaseInsensitive) || !key.compare("Track Name", Qt::CaseInsensitive))
220                 {
221                         if(audioFile.fileName().isEmpty()) audioFile.setFileName(value);
222                 }
223                 else if(!key.compare("Duration", Qt::CaseInsensitive))
224                 {
225                         if(!audioFile.fileDuration()) audioFile.setFileDuration(parseDuration(value));
226                 }
227                 else if(!key.compare("Artist", Qt::CaseInsensitive) || !key.compare("Performer", Qt::CaseInsensitive))
228                 {
229                         if(audioFile.fileArtist().isEmpty()) audioFile.setFileArtist(value);
230                 }
231                 else if(!key.compare("Album", Qt::CaseInsensitive))
232                 {
233                         if(audioFile.fileAlbum().isEmpty()) audioFile.setFileAlbum(value);
234                 }
235                 else if(!key.compare("Genre", Qt::CaseInsensitive))
236                 {
237                         if(audioFile.fileGenre().isEmpty()) audioFile.setFileGenre(value);
238                 }
239                 else if(!key.compare("Year", Qt::CaseInsensitive) || !key.compare("Recorded Date", Qt::CaseInsensitive) || !key.compare("Encoded Date", Qt::CaseInsensitive))
240                 {
241                         if(!audioFile.fileYear()) audioFile.setFileYear(parseYear(value));
242                 }
243                 else if(!key.compare("Comment", Qt::CaseInsensitive))
244                 {
245                         if(audioFile.fileComment().isEmpty()) audioFile.setFileComment(value);
246                 }
247                 else if(!key.compare("Track Name/Position", Qt::CaseInsensitive))
248                 {
249                         if(!audioFile.filePosition()) audioFile.setFilePosition(value.toInt());
250                 }
251                 else if(!key.compare("Format", Qt::CaseInsensitive))
252                 {
253                         if(audioFile.formatContainerType().isEmpty()) audioFile.setFormatContainerType(value);
254                 }
255                 else if(!key.compare("Format Profile", Qt::CaseInsensitive))
256                 {
257                         if(audioFile.formatContainerProfile().isEmpty()) audioFile.setFormatContainerProfile(value);
258                 }
259                 break;
260
261         case sectionAudio:
262                 if(!key.compare("Year", Qt::CaseInsensitive) || !key.compare("Recorded Date", Qt::CaseInsensitive) || !key.compare("Encoded Date", Qt::CaseInsensitive))
263                 {
264                         if(!audioFile.fileYear()) audioFile.setFileYear(parseYear(value));
265                 }
266                 else if(!key.compare("Format", Qt::CaseInsensitive))
267                 {
268                         if(audioFile.formatAudioType().isEmpty()) audioFile.setFormatAudioType(value);
269                 }
270                 else if(!key.compare("Format Profile", Qt::CaseInsensitive))
271                 {
272                         if(audioFile.formatAudioProfile().isEmpty()) audioFile.setFormatAudioProfile(value);
273                 }
274                 else if(!key.compare("Format Version", Qt::CaseInsensitive))
275                 {
276                         if(audioFile.formatAudioVersion().isEmpty()) audioFile.setFormatAudioVersion(value);
277                 }
278                 else if(!key.compare("Channel(s)", Qt::CaseInsensitive))
279                 {
280                         if(!audioFile.formatAudioChannels()) audioFile.setFormatAudioChannels(value.split(" ", QString::SkipEmptyParts).first().toInt());
281                 }
282                 else if(!key.compare("Sampling rate", Qt::CaseInsensitive))
283                 {
284                         if(!audioFile.formatAudioSamplerate()) audioFile.setFormatAudioSamplerate(ceil(value.split(" ", QString::SkipEmptyParts).first().toFloat() * 1000.0f));
285                 }
286                 else if(!key.compare("Bit depth", Qt::CaseInsensitive))
287                 {
288                         if(!audioFile.formatAudioBitdepth()) audioFile.setFormatAudioBitdepth(value.split(" ", QString::SkipEmptyParts).first().toInt());
289                 }
290                 else if(!key.compare("Duration", Qt::CaseInsensitive))
291                 {
292                         if(!audioFile.fileDuration()) audioFile.setFileDuration(parseDuration(value));
293                 }
294                 break;
295         }
296 }
297
298 unsigned int FileAnalyzer::parseYear(const QString &str)
299 {
300         if(str.startsWith("UTC", Qt::CaseInsensitive))
301         {
302                 QDate date = QDate::fromString(str.mid(3).trimmed().left(10), "yyyy-MM-dd");
303                 if(date.isValid())
304                 {
305                         return date.year();
306                 }
307                 else
308                 {
309                         return 0;
310                 }
311         }
312         else
313         {
314                 bool ok = false;
315                 int year = str.toInt(&ok);
316                 if(ok && year > 0)
317                 {
318                         return year;
319                 }
320                 else
321                 {
322                         return 0;
323                 }
324         }
325 }
326
327 unsigned int FileAnalyzer::parseDuration(const QString &str)
328 {
329         QTime time;
330
331         time = QTime::fromString(str, "z'ms'");
332         if(time.isValid())
333         {
334                 return max(1, (time.hour() * 60 * 60) + (time.minute() * 60) + time.second());
335         }
336
337         time = QTime::fromString(str, "s's 'z'ms'");
338         if(time.isValid())
339         {
340                 return max(1, (time.hour() * 60 * 60) + (time.minute() * 60) + time.second());
341         }
342
343         time = QTime::fromString(str, "m'mn 's's'");
344         if(time.isValid())
345         {
346                 return max(1, (time.hour() * 60 * 60) + (time.minute() * 60) + time.second());
347         }
348
349         time = QTime::fromString(str, "h'h 'm'mn'");
350         if(time.isValid())
351         {
352                 return max(1, (time.hour() * 60 * 60) + (time.minute() * 60) + time.second());
353         }
354
355         return 0;
356 }
357
358 unsigned int FileAnalyzer::filesAccepted(void)
359 {
360         return m_filesAccepted;
361 }
362
363 unsigned int FileAnalyzer::filesRejected(void)
364 {
365         return m_filesRejected - m_filesDenied;
366 }
367
368 unsigned int FileAnalyzer::filesDenied(void)
369 {
370         return m_filesDenied;
371 }
372
373 ////////////////////////////////////////////////////////////
374 // EVENTS
375 ////////////////////////////////////////////////////////////
376
377 /*NONE*/