OSDN Git Service

129fb3d7c01784b7acc00d83e469c6981756979d
[x264-launcher/x264-launcher.git] / src / thread_avisynth.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Simple x264 Launcher
3 // Copyright (C) 2004-2019 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_avisynth.h"
23
24 //Qt
25 #include <QLibrary>
26 #include <QEventLoop>
27 #include <QTimer>
28 #include <QApplication>
29 #include <QDir>
30
31 //Internal
32 #include "global.h"
33 #include "model_sysinfo.h"
34
35 //MUtils
36 #include <MUtils/Global.h>
37 #include <MUtils/OSSupport.h>
38
39 //Const
40 static const bool ENABLE_PORTABLE_AVS = true;
41
42 //Static
43 QMutex AvisynthCheckThread::m_avsLock;
44 QScopedPointer<QFile> AvisynthCheckThread::m_avsDllPath[2];
45
46 //Helper
47 #define VALID_DIR(STR) ((!(STR).isEmpty()) && QDir((STR)).exists())
48 #define BOOLIFY(X) ((X) ? '1' : '0')
49
50 //Utility function
51 QString AVS_CHECK_BINARY(const SysinfoModel *sysinfo, const bool& x64)
52 {
53         return QString("%1/toolset/%2/avs_check_%2.exe").arg(sysinfo->getAppPath(), (x64 ? "x64": "x86"));
54 }
55
56 class Wow64RedirectionDisabler
57 {
58 public:
59         Wow64RedirectionDisabler(void)
60         {
61                 m_oldValue = NULL;
62                 m_disabled = MUtils::OS::wow64fsredir_disable(m_oldValue);
63         }
64         ~Wow64RedirectionDisabler(void)
65         {
66                 if(m_disabled)
67                 {
68                         if(!MUtils::OS::wow64fsredir_revert(m_oldValue))
69                         {
70                                 qWarning("Failed to renable WOW64 filesystem redirection!");
71                         }
72                 }
73         }
74 private:
75         bool  m_disabled;
76         void* m_oldValue;
77 };
78
79 //-------------------------------------
80 // External API
81 //-------------------------------------
82
83 bool AvisynthCheckThread::detect(SysinfoModel *sysinfo)
84 {
85         sysinfo->clearAvisynth();
86         double version = 0.0;
87         QMutexLocker lock(&m_avsLock);
88
89         QEventLoop loop;
90         AvisynthCheckThread thread(sysinfo);
91
92         QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
93
94         connect(&thread, SIGNAL(finished()), &loop, SLOT(quit()));
95         connect(&thread, SIGNAL(terminated()), &loop, SLOT(quit()));
96         
97         thread.start();
98         QTimer::singleShot(30000, &loop, SLOT(quit()));
99         
100         qDebug("Avisynth thread has been created, please wait...");
101         loop.exec(QEventLoop::ExcludeUserInputEvents);
102         qDebug("Avisynth thread finished.");
103
104         QApplication::restoreOverrideCursor();
105
106         if(!thread.wait(1000))
107         {
108                 qWarning("Avisynth thread encountered timeout -> probably deadlock!");
109                 thread.terminate();
110                 thread.wait();
111                 return false;
112         }
113
114         if(thread.getException())
115         {
116                 qWarning("Avisynth thread encountered an exception !!!");
117                 return false;
118         }
119         
120         if(thread.getSuccess())
121         {
122                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X86, thread.getSuccess() & AVISYNTH_X86);
123                 sysinfo->setAvisynth(SysinfoModel::Avisynth_X64, thread.getSuccess() & AVISYNTH_X64);
124                 sysinfo->setAVSPath(thread.getPath());
125                 qDebug("Avisynth support is officially enabled now! [x86=%c, x64=%c]", BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X86)), BOOLIFY(sysinfo->getAvisynth(SysinfoModel::Avisynth_X64)));
126         }
127         else
128         {
129                 qWarning("Avisynth could not be found -> Avisynth support disabled!");
130         }
131
132         return true;
133 }
134
135 //-------------------------------------
136 // Thread class
137 //-------------------------------------
138
139 AvisynthCheckThread::AvisynthCheckThread(const SysinfoModel *const sysinfo)
140 :
141         m_sysinfo(sysinfo)
142 {
143         m_success = false;
144         m_exception = false;
145 }
146
147 AvisynthCheckThread::~AvisynthCheckThread(void)
148 {
149 }
150
151 void AvisynthCheckThread::run(void)
152 {
153         m_exception = false;
154         m_success &= 0;
155         m_basePath.clear();
156
157         detectAvisynthVersion1(m_success, m_basePath, m_sysinfo, &m_exception);
158 }
159
160 void AvisynthCheckThread::detectAvisynthVersion1(int &success, QString &basePath, const SysinfoModel *const sysinfo, volatile bool *exception)
161 {
162         __try
163         {
164                 detectAvisynthVersion2(success, basePath, sysinfo, exception);
165         }
166         __except(1)
167         {
168                 *exception = true;
169                 qWarning("Unhandled exception error in Avisynth thread !!!");
170         }
171 }
172
173 void AvisynthCheckThread::detectAvisynthVersion2(int &success, QString &basePath, const SysinfoModel *const sysinfo, volatile bool *exception)
174 {
175         try
176         {
177                 return detectAvisynthVersion3(success, basePath, sysinfo);
178         }
179         catch(...)
180         {
181                 *exception = true;
182                 qWarning("Avisynth initializdation raised an C++ exception!");
183         }
184 }
185
186 void AvisynthCheckThread::detectAvisynthVersion3(int &success, QString &basePath, const SysinfoModel *const sysinfo)
187 {
188         success &= 0;
189
190         QFile *avsPath32;
191         if(checkAvisynth(basePath, sysinfo, avsPath32, false))
192         {
193                 m_avsDllPath[0].reset(avsPath32);
194                 success |= AVISYNTH_X86;
195                 qDebug("Avisynth 32-Bit edition found!");
196         }
197         else
198         {
199                 qDebug("Avisynth 32-Bit edition *not* found!");
200         }
201
202         if(sysinfo->getCPUFeatures(SysinfoModel::CPUFeatures_X64))
203         {
204                 QFile *avsPath64;
205                 if(checkAvisynth(basePath, sysinfo, avsPath64, true))
206                 {
207                         m_avsDllPath[1].reset(avsPath64);
208                         success |= AVISYNTH_X64;
209                         qDebug("Avisynth 64-Bit edition found!");
210                 }
211                 else
212                 {
213                         qDebug("Avisynth 64-Bit edition *not* found!");
214                 }
215         }
216         else
217         {
218                 qWarning("Skipping 64-Bit Avisynth check on non-x64 system!");
219         }
220 }
221
222 bool AvisynthCheckThread::checkAvisynth(QString &basePath, const SysinfoModel *const sysinfo, QFile *&path, const bool &x64)
223 {
224         qDebug("Avisynth %s-Bit support is being tested.", x64 ? "64" : "32");
225
226         //Look for "portable" Avisynth version
227         static const char *const ARCH_DIR[] = { "x64", "x86" };
228         const QLatin1String archSuffix = QLatin1String(ARCH_DIR[x64 ? 1 : 0]);
229         if (ENABLE_PORTABLE_AVS)
230         {
231                 const QString avsPortableDir = QString("%1/extra/Avisynth").arg(QCoreApplication::applicationDirPath());
232                 if (VALID_DIR(avsPortableDir))
233                 {
234                         QFileInfo avsDllFile(QString("%1/%2/avisynth.dll").arg(avsPortableDir, archSuffix)), devilDllFile(QString("%1/%2/devil.dll").arg(avsPortableDir, archSuffix));
235                         if (avsDllFile.exists() && devilDllFile.exists() && avsDllFile.isFile() && devilDllFile.isFile())
236                         {
237                                 qWarning("Adding portable Avisynth to PATH environment variable: %s", MUTILS_UTF8(avsPortableDir));
238                                 basePath = avsPortableDir;
239                         }
240                 }
241         }
242
243         //Get extra paths
244         QStringList avisynthExtraPaths;
245         if (!basePath.isEmpty())
246         {
247                 avisynthExtraPaths << QString("%1/%2").arg(basePath, archSuffix);
248         }
249
250         //Setup process object
251         const QStringList output = runProcess(AVS_CHECK_BINARY(sysinfo, x64), QStringList(), &avisynthExtraPaths);
252
253         //Init regular expressions
254         QRegExp avsLogo("Avisynth\\s+Checker\\s+(x86|x64)");
255         QRegExp avsPath("Avisynth_DLLPath=(.+)");
256         QRegExp avsVers("Avisynth_Version=(\\d+)\\.(\\d+)");
257         
258         //Check for version info
259         bool avisynthLogo = false;
260         quint32 avisynthVersion[2] = { 0, 0 };
261         QString avisynthPath;
262         for(QStringList::ConstIterator iter = output.constBegin(); iter != output.constEnd(); iter++)
263         {
264                 if(avisynthLogo)
265                 {
266                         if(avsPath.indexIn(*iter) >= 0)
267                         {
268                                 avisynthPath =  avsPath.cap(1).trimmed();
269                         }
270                         else if(avsVers.indexIn(*iter) >= 0)
271                         {
272                                 quint32 temp[2];
273                                 if(MUtils::regexp_parse_uint32(avsVers, temp, 2))
274                                 {
275                                         avisynthVersion[0] = temp[0];
276                                         avisynthVersion[1] = temp[1];
277                                 }
278                         }
279                 }
280                 else
281                 {
282                         if(avsLogo.lastIndexIn(*iter) >= 0)
283                         {
284                                 avisynthLogo = true;
285                         }
286                 }
287         }
288         
289         //Minimum required version found?
290         if((avisynthVersion[0] >= 2) && (avisynthVersion[1] >= 50) && (!avisynthPath.isEmpty()))
291         {
292                 Wow64RedirectionDisabler disableWow64Redir;
293                 path = new QFile(avisynthPath);
294                 if(!path->open(QIODevice::ReadOnly))
295                 {
296                         MUTILS_DELETE(path);
297                 }
298                 qDebug("Avisynth was detected successfully (current version: %u.%02u).", avisynthVersion[0], avisynthVersion[1]);
299                 qDebug("Avisynth DLL path: %s", MUTILS_UTF8(avisynthPath));
300                 return true;
301         }
302         
303         //Failed to determine version
304         qWarning("Failed to determine Avisynth version!");
305         return false;
306 }