OSDN Git Service

4c758cf4e9fed92b89c8756e58b53c7f1972fefa
[x264-launcher/x264-launcher.git] / src / thread_vapoursynth.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2014 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_vapoursynth.h"
23
24 #include <QLibrary>
25 #include <QEventLoop>
26 #include <QTimer>
27 #include <QMutexLocker>
28 #include <QApplication>
29 #include <QDir>
30 #include <QProcess>
31
32 #include "global.h"
33
34 static const unsigned int VAPOURSYNTH_VERSION_MIN = 20;
35
36 QMutex VapourSynthCheckThread::m_vpsLock;
37 QFile *VapourSynthCheckThread::m_vpsExePath = NULL;
38 QFile *VapourSynthCheckThread::m_vpsDllPath = NULL;
39 QLibrary *VapourSynthCheckThread::m_vpsLib = NULL;
40
41 #define VALID_DIR(STR) ((!(STR).isEmpty()) && QDir((STR)).exists())
42
43 static inline QString &cleanDir(QString &path)
44 {
45         if(!path.isEmpty())
46         {
47                 path = QDir::fromNativeSeparators(path);
48                 while(path.endsWith('/'))
49                 {
50                         path.chop(1);
51                 }
52         }
53         return path;
54 }
55
56 //-------------------------------------
57 // External API
58 //-------------------------------------
59
60 int VapourSynthCheckThread::detect(QString &path)
61 {
62         path.clear();
63         QMutexLocker lock(&m_vpsLock);
64
65         QEventLoop loop;
66         VapourSynthCheckThread thread;
67
68         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
69
70         connect(&thread, SIGNAL(finished()), &loop, SLOT(quit()));
71         connect(&thread, SIGNAL(terminated()), &loop, SLOT(quit()));
72         
73         thread.start();
74         QTimer::singleShot(15000, &loop, SLOT(quit()));
75         
76         qDebug("VapourSynth thread has been created, please wait...");
77         loop.exec(QEventLoop::ExcludeUserInputEvents);
78         qDebug("VapourSynth thread finished.");
79
80         QApplication::restoreOverrideCursor();
81
82         if(!thread.wait(1000))
83         {
84                 qWarning("VapourSynth thread encountered timeout -> probably deadlock!");
85                 thread.terminate();
86                 thread.wait();
87                 return -1;
88         }
89
90         if(thread.getException())
91         {
92                 qWarning("VapourSynth thread encountered an exception !!!");
93                 return -1;
94         }
95         
96         if(thread.getSuccess())
97         {
98                 path = thread.getPath();
99                 qDebug("VapourSynth check completed successfully.");
100                 return 1;
101         }
102
103         qWarning("VapourSynth thread failed to detect installation!");
104         return 0;
105 }
106
107 void VapourSynthCheckThread::unload(void)
108 {
109         QMutexLocker lock(&m_vpsLock);
110
111         if(m_vpsLib)
112         {
113                 if(m_vpsLib->isLoaded())
114                 {
115                         m_vpsLib->unload();
116                 }
117         }
118
119         if(m_vpsExePath)
120         {
121                 if (m_vpsExePath->isOpen())
122                 {
123                         m_vpsExePath->close();
124                 }
125         }
126
127         if(m_vpsDllPath)
128         {
129                 if(m_vpsDllPath->isOpen())
130                 {
131                         m_vpsDllPath->close();
132                 }
133         }
134
135         X264_DELETE(m_vpsExePath);
136         X264_DELETE(m_vpsDllPath);
137         X264_DELETE(m_vpsLib);
138 }
139
140 //-------------------------------------
141 // Thread class
142 //-------------------------------------
143
144 VapourSynthCheckThread::VapourSynthCheckThread(void)
145 {
146         m_success = false;
147         m_exception = false;
148         m_vpsPath.clear();
149 }
150
151 VapourSynthCheckThread::~VapourSynthCheckThread(void)
152 {
153 }
154
155 void VapourSynthCheckThread::run(void)
156 {
157         m_exception = m_success = false;
158         m_success = detectVapourSynthPath1(m_vpsPath, &m_exception);
159 }
160
161 bool VapourSynthCheckThread::detectVapourSynthPath1(QString &path, volatile bool *exception)
162 {
163         __try
164         {
165                 return detectVapourSynthPath2(path, exception);
166         }
167         __except(1)
168         {
169                 *exception = true;
170                 qWarning("Unhandled exception error in VapourSynth thread !!!");
171                 return false;
172         }
173 }
174
175 bool VapourSynthCheckThread::detectVapourSynthPath2(QString &path, volatile bool *exception)
176 {
177         try
178         {
179                 return detectVapourSynthPath3(path);
180         }
181         catch(...)
182         {
183                 *exception = true;
184                 qWarning("VapourSynth initializdation raised an C++ exception!");
185                 return false;
186         }
187 }
188
189 bool VapourSynthCheckThread::detectVapourSynthPath3(QString &path)
190 {
191         bool success = false;
192         X264_DELETE(m_vpsExePath);
193         X264_DELETE(m_vpsDllPath);
194         path.clear();
195
196         static const char *VPS_REG_KEYS[] = 
197         {
198                 "SOFTWARE\\VapourSynth",
199                 "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\VapourSynth_is1",
200                 NULL
201         };
202         static const char *VPS_REG_NAME[] =
203         {
204                 "Path",
205                 "InstallLocation",
206                 "Inno Setup: App Path",
207                 NULL
208         };
209
210         //Read VapourSynth path from registry
211         QString vapoursynthPath;
212         for(size_t i = 0; VPS_REG_KEYS[i]; i++)
213         {
214                 for(size_t j = 0; VPS_REG_NAME[j]; j++)
215                 {
216                         vapoursynthPath = cleanDir(x264_query_reg_string(false, VPS_REG_KEYS[i], VPS_REG_NAME[j]));
217                         if(VALID_DIR(vapoursynthPath)) break;
218                 }
219                 if(VALID_DIR(vapoursynthPath)) break;
220         }
221
222         //Make sure VapourSynth does exist
223         if(!VALID_DIR(vapoursynthPath))
224         {
225                 qWarning("VapourSynth install path not found -> disable VapouSynth support!");
226                 vapoursynthPath.clear();
227         }
228
229         //Make sure that 'vapoursynth.dll' and 'vspipe.exe' are available
230         bool vapoursynthComplete = false;
231         if(!vapoursynthPath.isEmpty())
232         {
233                 static const char *CORE_PATH[3] = { "core32", "core64", "core" };
234                 qDebug("VapourSynth Dir: %s", vapoursynthPath.toUtf8().constData());
235                 for(int i = 0; (i < 3) && (!vapoursynthComplete); i++)
236                 {
237                         QFileInfo vpsExeInfo(QString("%1/%2/vspipe.exe"     ).arg(vapoursynthPath, CORE_PATH[i]));
238                         QFileInfo vpsDllInfo(QString("%1/%2/vapoursynth.dll").arg(vapoursynthPath, CORE_PATH[i]));
239                         qDebug("VapourSynth EXE: %s", vpsExeInfo.absoluteFilePath().toUtf8().constData());
240                         qDebug("VapourSynth DLL: %s", vpsDllInfo.absoluteFilePath().toUtf8().constData());
241                         if(vpsExeInfo.exists() && vpsDllInfo.exists())
242                         {
243                                 m_vpsExePath = new QFile(vpsExeInfo.canonicalFilePath());
244                                 m_vpsDllPath = new QFile(vpsDllInfo.canonicalFilePath());
245                                 if(m_vpsExePath->open(QIODevice::ReadOnly) && m_vpsDllPath->open(QIODevice::ReadOnly))
246                                 {
247                                         if(vapoursynthComplete = x264_is_executable(m_vpsExePath->fileName()))
248                                         {
249                                                 vapoursynthPath.append("/").append(CORE_PATH[i]);
250                                         }
251                                         break;
252                                 }
253                                 X264_DELETE(m_vpsExePath);
254                                 X264_DELETE(m_vpsDllPath);
255                         }
256                 }
257                 if(!vapoursynthComplete)
258                 {
259                         qWarning("VapourSynth installation incomplete -> disable VapouSynth support!");
260                 }
261         }
262
263         //Make sure 'vsscript.dll' can be loaded successfully
264         if(vapoursynthComplete && m_vpsExePath)
265         {
266                 qDebug("VapourSynth detection is running, please stand by...");
267                 success = checkVapourSynthVersion(m_vpsExePath->fileName());
268         }
269
270         //Return VapourSynth path
271         if(success)
272         {
273                 path = vapoursynthPath;
274         }
275
276         return success;
277 }
278
279 bool VapourSynthCheckThread::checkVapourSynthVersion(const QString vspipePath)
280 {
281         QProcess process;
282         QStringList output;
283
284         //Setup process object
285         process.setWorkingDirectory(QDir::tempPath());
286         process.setProcessChannelMode(QProcess::MergedChannels);
287         process.setReadChannel(QProcess::StandardOutput);
288
289         //Try to start VSPIPE.EXE
290         process.start(vspipePath, QStringList() << "-version");
291         if(!process.waitForStarted())
292         {
293                 qWarning("Failed to launch VSPIPE.EXE -> %s", process.errorString().toUtf8().constData());
294                 return false;
295         }
296
297         //Wait for process to finish
298         while(process.state() != QProcess::NotRunning)
299         {
300                 if(process.waitForReadyRead(12000))
301                 {
302                         while(process.canReadLine())
303                         {
304                                 output << QString::fromUtf8(process.readLine()).simplified();
305                         }
306                         continue;
307                 }
308                 if(process.state() != QProcess::NotRunning)
309                 {
310                         qWarning("VSPIPE.EXE process encountered a deadlock -> aborting now!");
311                         break;
312                 }
313         }
314
315         //Make sure VSPIPE.EXE has terminated!
316         process.waitForFinished(2500);
317         if(process.state() != QProcess::NotRunning)
318         {
319                 qWarning("VSPIPE.EXE process still running, going to kill it!");
320                 process.kill();
321                 process.waitForFinished(-1);
322         }
323
324         //Read pending lines
325         while(process.canReadLine())
326         {
327                 output << QString::fromUtf8(process.readLine()).simplified();
328         }
329
330         //Check exit code
331         if(process.exitCode() != 0)
332         {
333                 qWarning("VSPIPE.EXE failed with code 0x%08X -> disable Vapousynth support!", process.exitCode());
334                 return false;
335         }
336
337         //Init regular expressions
338         unsigned int vapursynthVersion = 0;
339         bool vapoursynthLogo = false;
340         QRegExp vpsLogo("VapourSynth\\s+Video\\s+Processing\\s+Library");
341         QRegExp vpsCore("Core\\s+r(\\d+)");
342
343         //Check for version info
344         for(QStringList::ConstIterator iter = output.constBegin(); iter != output.constEnd(); iter++)
345         {
346                 if(vpsLogo.lastIndexIn(*iter) >= 0)
347                 {
348                         vapoursynthLogo = true;
349                         continue;
350                 }
351                 if(vapoursynthLogo && (vpsCore.lastIndexIn(*iter) >= 0))
352                 {
353                         bool ok = false;
354                         const unsigned int temp = vpsCore.cap(1).toUInt(&ok);
355                         if(ok) vapursynthVersion = temp;
356                 }
357         }
358
359         //Minimum required version found?
360         if(vapoursynthLogo && (vapursynthVersion > 0))
361         {
362                 qDebug("VapourSynth version \"Core r%u\" detected.", vapursynthVersion);
363                 if(vapursynthVersion < VAPOURSYNTH_VERSION_MIN)
364                 {
365                         qWarning("VapourSynth version is too old -> disable Vapousynth support!");
366                         return false;
367                 }
368                 return true;
369         }
370
371         //Failed to determine version
372         qWarning("Failed to determine VapourSynth version -> disable Vapousynth support!");
373         qWarning("VapourSynth version is unsupported or VapourSynth installation is corrupted.");
374         return false;
375 }