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