1 // Copyright (c) 2006, Fredrik Mellbin
2 // All rights reserved.
3 //
4 // Redistribution and use in source and binary forms, with or without
5 // modification, are permitted provided that the following conditions are met:
6 //
7 //   * Redistributions of source code must retain the above copyright notice,
8 //     this list of conditions and the following disclaimer.
9 //   * Redistributions in binary form must reproduce the above copyright notice,
10 //     this list of conditions and the following disclaimer in the documentation
11 //     and/or other materials provided with the distribution.
12 //   * Neither the name of the Aegisub Group nor the names of its contributors
13 //     may be used to endorse or promote products derived from this software
14 //     without specific prior written permission.
15 //
16 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
20 // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 // POSSIBILITY OF SUCH DAMAGE.
27 //
28 // Aegisub Project http://www.aegisub.org/
29 
30 #ifdef WITH_AVISYNTH
31 #include "include/aegisub/video_provider.h"
32 
33 #include "options.h"
34 #include "video_frame.h"
35 
36 #include <libaegisub/access.h>
37 #include <libaegisub/charset_conv.h>
38 #include <libaegisub/fs.h>
39 #include <libaegisub/log.h>
40 #include <libaegisub/path.h>
41 #include <libaegisub/make_unique.h>
42 
43 #include <boost/algorithm/string/predicate.hpp>
44 #include <mutex>
45 
46 #ifdef _WIN32
47 #include <vfw.h>
48 #endif
49 
50 #define VideoFrame AVSVideoFrame
51 #include "avisynth.h"
52 #undef VideoFrame
53 #include "avisynth_wrap.h"
54 
55 namespace {
56 class AvisynthVideoProvider: public VideoProvider {
57 	AviSynthWrapper avs;
58 	std::string decoder_name;
59 	agi::vfr::Framerate fps;
60 	std::vector<int> keyframes;
61 	std::string warning;
62 	std::string colorspace;
63 	std::string real_colorspace;
64 	bool has_audio = false;
65 
66 	AVSValue source_clip;
67 	PClip RGB32Video;
68 	VideoInfo vi;
69 
70 	AVSValue Open(agi::fs::path const& filename);
71 	void Init(std::string const& matrix);
72 
73 public:
74 	AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix);
75 
76 	void GetFrame(int n, VideoFrame &frame) override;
77 
SetColorSpace(std::string const & matrix)78 	void SetColorSpace(std::string const& matrix) override {
79 		// Can't really do anything if this fails
80 		try { Init(matrix); } catch (AvisynthError const&) { }
81 	}
82 
GetFrameCount() const83 	int GetFrameCount() const override             { return vi.num_frames; }
GetFPS() const84 	agi::vfr::Framerate GetFPS() const override    { return fps; }
GetWidth() const85 	int GetWidth() const override                  { return vi.width; }
GetHeight() const86 	int GetHeight() const override                 { return vi.height; }
GetDAR() const87 	double GetDAR() const override                 { return 0; }
GetKeyFrames() const88 	std::vector<int> GetKeyFrames() const override { return keyframes; }
GetWarning() const89 	std::string GetWarning() const override        { return warning; }
GetDecoderName() const90 	std::string GetDecoderName() const override    { return decoder_name; }
GetColorSpace() const91 	std::string GetColorSpace() const override     { return colorspace; }
GetRealColorSpace() const92 	std::string GetRealColorSpace() const override { return real_colorspace; }
HasAudio() const93 	bool HasAudio() const override                 { return has_audio; }
94 };
95 
AvisynthVideoProvider(agi::fs::path const & filename,std::string const & colormatrix)96 AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix) {
97 	agi::acs::CheckFileRead(filename);
98 
99 	std::lock_guard<std::mutex> lock(avs.GetMutex());
100 
101 #ifdef _WIN32
102 	if (agi::fs::HasExtension(filename, "avi")) {
103 		// Try to read the keyframes before actually opening the file as trying
104 		// to open the file while it's already open can cause problems with
105 		// badly written VFW decoders
106 		AVIFileInit();
107 
108 		PAVIFILE pfile;
109 		long hr = AVIFileOpen(&pfile, filename.c_str(), OF_SHARE_DENY_WRITE, 0);
110 		if (hr) {
111 			warning = "Unable to open AVI file for reading keyframes:\n";
112 			switch (hr) {
113 				case AVIERR_BADFORMAT:
114 					warning += "The file is corrupted, incomplete or has an otherwise bad format.";
115 					break;
116 				case AVIERR_MEMORY:
117 					warning += "The file could not be opened because of insufficient memory.";
118 					break;
119 				case AVIERR_FILEREAD:
120 					warning += "An error occurred reading the file. There might be a problem with the storage media.";
121 					break;
122 				case AVIERR_FILEOPEN:
123 					warning += "The file could not be opened. It might be in use by another application, or you do not have permission to access it.";
124 					break;
125 				case REGDB_E_CLASSNOTREG:
126 					warning += "There is no handler installed for the file extension. This might indicate a fundamental problem in your Video for Windows installation, and can be caused by extremely stripped Windows installations.";
127 					break;
128 				default:
129 					warning += "Unknown error.";
130 					break;
131 			}
132 			goto file_exit;
133 		}
134 
135 		PAVISTREAM ppavi;
136 		if (hr = AVIFileGetStream(pfile, &ppavi, streamtypeVIDEO, 0)) {
137 			warning = "Unable to open AVI video stream for reading keyframes:\n";
138 			switch (hr) {
139 				case AVIERR_NODATA:
140 					warning += "The file does not contain a usable video stream.";
141 					break;
142 				case AVIERR_MEMORY:
143 					warning += "Not enough memory.";
144 					break;
145 				default:
146 					warning += "Unknown error.";
147 					break;
148 			}
149 			goto file_release;
150 		}
151 
152 		AVISTREAMINFO avis;
153 		if (FAILED(AVIStreamInfo(ppavi,&avis,sizeof(avis)))) {
154 			warning = "Unable to read keyframes from AVI file:\nCould not get stream information.";
155 			goto stream_release;
156 		}
157 
158 		for (size_t i = 0; i < avis.dwLength; i++) {
159 			if (AVIStreamIsKeyFrame(ppavi, i))
160 				keyframes.push_back(i);
161 		}
162 
163 		// If every frame is a keyframe then just discard the keyframe data as it's useless
164 		if (keyframes.size() == (size_t)avis.dwLength)
165 			keyframes.clear();
166 
167 		// Clean up
168 stream_release:
169 		AVIStreamRelease(ppavi);
170 file_release:
171 		AVIFileRelease(pfile);
172 file_exit:
173 		AVIFileExit();
174 	}
175 #endif
176 
177 	try {
178 		source_clip = Open(filename);
179 		Init(colormatrix);
180 	}
181 	catch (AvisynthError const& err) {
182 		throw VideoOpenError("Avisynth error: " + std::string(err.msg));
183 	}
184 }
185 
Init(std::string const & colormatrix)186 void AvisynthVideoProvider::Init(std::string const& colormatrix) {
187 	auto script = source_clip;
188 	vi = script.AsClip()->GetVideoInfo();
189 	has_audio = vi.HasAudio();
190 	if (vi.IsRGB())
191 		real_colorspace = colorspace = "None";
192 	else {
193 		/// @todo maybe read ColorMatrix hints for d2v files?
194 		AVSValue args[2] = { script, "Rec601" };
195 		bool force_bt601 = OPT_GET("Video/Force BT.601")->GetBool() || colormatrix == "TV.601";
196 		bool bt709 = vi.width > 1024 || vi.height >= 600;
197 		if (bt709 && (!force_bt601 || colormatrix == "TV.709")) {
198 			args[1] = "Rec709";
199 			real_colorspace = colorspace = "TV.709";
200 		}
201 		else {
202 			colorspace = "TV.601";
203 			real_colorspace = bt709 ? "TV.709" : "TV.601";
204 		}
205 		const char *argnames[2] = { 0, "matrix" };
206 		script = avs.GetEnv()->Invoke("ConvertToRGB32", AVSValue(args, 2), argnames);
207 	}
208 
209 	RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip();
210 	vi = RGB32Video->GetVideoInfo();
211 	fps = (double)vi.fps_numerator / vi.fps_denominator;
212 }
213 
Open(agi::fs::path const & filename)214 AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
215 	IScriptEnvironment *env = avs.GetEnv();
216 	char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str());
217 
218 	// Avisynth file, just import it
219 	if (agi::fs::HasExtension(filename, "avs")) {
220 		LOG_I("avisynth/video") << "Opening .avs file with Import";
221 		decoder_name = "Avisynth/Import";
222 		return env->Invoke("Import", videoFilename);
223 	}
224 
225 	// Open avi file with AviSource
226 	if (agi::fs::HasExtension(filename, "avi")) {
227 		LOG_I("avisynth/video") << "Opening .avi file with AviSource";
228 		try {
229 			const char *argnames[2] = { 0, "audio" };
230 			AVSValue args[2] = { videoFilename, false };
231 			decoder_name = "Avisynth/AviSource";
232 			return env->Invoke("AviSource", AVSValue(args,2), argnames);
233 		}
234 		// On Failure, fallback to DSS
235 		catch (AvisynthError &err) {
236 			LOG_E("avisynth/video") << err.msg;
237 			LOG_I("avisynth/video") << "Failed to open .avi file with AviSource, trying DirectShowSource";
238 		}
239 	}
240 
241 	// Open d2v with mpeg2dec3
242 	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) {
243 		LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source";
244 		auto script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename);
245 		decoder_name = "Avisynth/Mpeg2Dec3_Mpeg2Source";
246 
247 		//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
248 		if (env->FunctionExists("SetPlanarLegacyAlignment")) {
249 			AVSValue args[2] = { script, true };
250 			script = env->Invoke("SetPlanarLegacyAlignment", AVSValue(args,2));
251 		}
252 		return script;
253 	}
254 
255 	// If that fails, try opening it with DGDecode
256 	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("DGDecode_Mpeg2Source")) {
257 		LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source";
258 		decoder_name = "DGDecode_Mpeg2Source";
259 		return env->Invoke("Avisynth/Mpeg2Source", videoFilename);
260 
261 		//note that DGDecode will also have issues like if the version is too
262 		// ancient but no sane person would use that anyway
263 	}
264 
265 	if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Source")) {
266 		LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source";
267 		AVSValue script = env->Invoke("Mpeg2Source", videoFilename);
268 		decoder_name = "Avisynth/Mpeg2Source";
269 
270 		//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
271 		if (env->FunctionExists("SetPlanarLegacyAlignment"))
272 			script = env->Invoke("SetPlanarLegacyAlignment", script);
273 
274 		return script;
275 	}
276 
277 	// Try loading DirectShowSource2
278 	if (!env->FunctionExists("dss2")) {
279 		auto dss2path(config::path->Decode("?data/avss.dll"));
280 		if (agi::fs::FileExists(dss2path))
281 			env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dss2path).c_str()));
282 	}
283 
284 	// If DSS2 loaded properly, try using it
285 	if (env->FunctionExists("dss2")) {
286 		LOG_I("avisynth/video") << "Opening file with DSS2";
287 		decoder_name = "Avisynth/DSS2";
288 		return env->Invoke("DSS2", videoFilename);
289 	}
290 
291 	// Try DirectShowSource
292 	// Load DirectShowSource.dll from app dir if it exists
293 	auto dsspath(config::path->Decode("?data/DirectShowSource.dll"));
294 	if (agi::fs::FileExists(dsspath))
295 		env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dsspath).c_str()));
296 
297 	// Then try using DSS
298 	if (env->FunctionExists("DirectShowSource")) {
299 		const char *argnames[3] = { 0, "video", "audio" };
300 		AVSValue args[3] = { videoFilename, true, false };
301 		decoder_name = "Avisynth/DirectShowSource";
302 		warning = "Warning! The file is being opened using Avisynth's DirectShowSource, which has unreliable seeking. Frame numbers might not match the real number. PROCEED AT YOUR OWN RISK!";
303 		LOG_I("avisynth/video") << "Opening file with DirectShowSource";
304 		return env->Invoke("DirectShowSource", AVSValue(args,3), argnames);
305 	}
306 
307 	// Failed to find a suitable function
308 	LOG_E("avisynth/video") << "DSS function not found";
309 	throw VideoNotSupported("No function suitable for opening the video found");
310 }
311 
GetFrame(int n,VideoFrame & out)312 void AvisynthVideoProvider::GetFrame(int n, VideoFrame &out) {
313 	std::lock_guard<std::mutex> lock(avs.GetMutex());
314 
315 	auto frame = RGB32Video->GetFrame(n, avs.GetEnv());
316 	auto ptr = frame->GetReadPtr();
317 	out.data.assign(ptr, ptr + frame->GetPitch() * frame->GetHeight());
318 	out.flipped = true;
319 	out.height = frame->GetHeight();
320 	out.width = frame->GetRowSize() / 4;
321 	out.pitch = frame->GetPitch();
322 }
323 }
324 
325 namespace agi { class BackgroundRunner; }
CreateAvisynthVideoProvider(agi::fs::path const & path,std::string const & colormatrix,agi::BackgroundRunner *)326 std::unique_ptr<VideoProvider> CreateAvisynthVideoProvider(agi::fs::path const& path, std::string const& colormatrix, agi::BackgroundRunner *) {
327 	return agi::make_unique<AvisynthVideoProvider>(path, colormatrix);
328 }
329 #endif // HAVE_AVISYNTH
330