forked from mia/Aegisub
73e1dc1b60
If the script's matrix matches the video's matrix, use that even if Force BT.601 is on, and if it's TV.601, use that even if Force BT.601 is off. This should mostly eliminate the common problems resulting from mixed values of the Force BT.601 option. Closes #1649.
276 lines
9.9 KiB
C++
276 lines
9.9 KiB
C++
// Copyright (c) 2006, Fredrik Mellbin
|
|
// All rights reserved.
|
|
//
|
|
// Redistribution and use in source and binary forms, with or without
|
|
// modification, are permitted provided that the following conditions are met:
|
|
//
|
|
// * Redistributions of source code must retain the above copyright notice,
|
|
// this list of conditions and the following disclaimer.
|
|
// * Redistributions in binary form must reproduce the above copyright notice,
|
|
// this list of conditions and the following disclaimer in the documentation
|
|
// and/or other materials provided with the distribution.
|
|
// * Neither the name of the Aegisub Group nor the names of its contributors
|
|
// may be used to endorse or promote products derived from this software
|
|
// without specific prior written permission.
|
|
//
|
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
// POSSIBILITY OF SUCH DAMAGE.
|
|
//
|
|
// Aegisub Project http://www.aegisub.org/
|
|
|
|
/// @file video_provider_avs.cpp
|
|
/// @brief Avisynth-based video provider
|
|
/// @ingroup video_input
|
|
///
|
|
|
|
#include "config.h"
|
|
|
|
#ifdef WITH_AVISYNTH
|
|
#include "video_provider_avs.h"
|
|
|
|
#include "options.h"
|
|
#include "video_frame.h"
|
|
|
|
#include <libaegisub/access.h>
|
|
#include <libaegisub/charset_conv.h>
|
|
#include <libaegisub/fs.h>
|
|
#include <libaegisub/log.h>
|
|
#include <libaegisub/path.h>
|
|
|
|
#include <boost/algorithm/string/predicate.hpp>
|
|
#include <mutex>
|
|
|
|
#ifdef _WIN32
|
|
#include <vfw.h>
|
|
#endif
|
|
|
|
AvisynthVideoProvider::AvisynthVideoProvider(agi::fs::path const& filename, std::string const& colormatrix)
|
|
{
|
|
agi::acs::CheckFileRead(filename);
|
|
|
|
std::lock_guard<std::mutex> lock(avs.GetMutex());
|
|
|
|
#ifdef _WIN32
|
|
if (agi::fs::HasExtension(filename, "avi")) {
|
|
// Try to read the keyframes before actually opening the file as trying
|
|
// to open the file while it's already open can cause problems with
|
|
// badly written VFW decoders
|
|
AVIFileInit();
|
|
|
|
PAVIFILE pfile;
|
|
long hr = AVIFileOpen(&pfile, filename.c_str(), OF_SHARE_DENY_WRITE, 0);
|
|
if (hr) {
|
|
warning = "Unable to open AVI file for reading keyframes:\n";
|
|
switch (hr) {
|
|
case AVIERR_BADFORMAT:
|
|
warning += "The file is corrupted, incomplete or has an otherwise bad format.";
|
|
break;
|
|
case AVIERR_MEMORY:
|
|
warning += "The file could not be opened because of insufficient memory.";
|
|
break;
|
|
case AVIERR_FILEREAD:
|
|
warning += "An error occurred reading the file. There might be a problem with the storage media.";
|
|
break;
|
|
case AVIERR_FILEOPEN:
|
|
warning += "The file could not be opened. It might be in use by another application, or you do not have permission to access it.";
|
|
break;
|
|
case REGDB_E_CLASSNOTREG:
|
|
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.";
|
|
break;
|
|
default:
|
|
warning += "Unknown error.";
|
|
break;
|
|
}
|
|
goto file_exit;
|
|
}
|
|
|
|
PAVISTREAM ppavi;
|
|
if (hr = AVIFileGetStream(pfile, &ppavi, streamtypeVIDEO, 0)) {
|
|
warning = "Unable to open AVI video stream for reading keyframes:\n";
|
|
switch (hr) {
|
|
case AVIERR_NODATA:
|
|
warning += "The file does not contain a usable video stream.";
|
|
break;
|
|
case AVIERR_MEMORY:
|
|
warning += "Not enough memory.";
|
|
break;
|
|
default:
|
|
warning += "Unknown error.";
|
|
break;
|
|
}
|
|
goto file_release;
|
|
}
|
|
|
|
AVISTREAMINFO avis;
|
|
if (FAILED(AVIStreamInfo(ppavi,&avis,sizeof(avis)))) {
|
|
warning = "Unable to read keyframes from AVI file:\nCould not get stream information.";
|
|
goto stream_release;
|
|
}
|
|
|
|
for (size_t i = 0; i < avis.dwLength; i++) {
|
|
if (AVIStreamIsKeyFrame(ppavi, i))
|
|
keyframes.push_back(i);
|
|
}
|
|
|
|
// If every frame is a keyframe then just discard the keyframe data as it's useless
|
|
if (keyframes.size() == (size_t)avis.dwLength)
|
|
keyframes.clear();
|
|
|
|
// Clean up
|
|
stream_release:
|
|
AVIStreamRelease(ppavi);
|
|
file_release:
|
|
AVIFileRelease(pfile);
|
|
file_exit:
|
|
AVIFileExit();
|
|
}
|
|
#endif
|
|
|
|
try {
|
|
auto script = Open(filename);
|
|
|
|
// Check if video was loaded properly
|
|
if (!script.IsClip() || !script.AsClip()->GetVideoInfo().HasVideo())
|
|
throw VideoNotSupported("No usable video found");
|
|
|
|
vi = script.AsClip()->GetVideoInfo();
|
|
if (!vi.IsRGB()) {
|
|
/// @todo maybe read ColorMatrix hints for d2v files?
|
|
AVSValue args[2] = { script, "Rec601" };
|
|
bool force_bt601 = OPT_GET("Video/Force BT.601")->GetBool() || colormatrix == "TV.601";
|
|
bool bt709 = vi.width > 1024 || vi.height >= 600;
|
|
if (bt709 && (!force_bt601 || colormatrix == "TV.709")) {
|
|
args[1] = "Rec709";
|
|
colorspace = "TV.709";
|
|
}
|
|
else
|
|
colorspace = "TV.601";
|
|
const char *argnames[2] = { 0, "matrix" };
|
|
script = avs.GetEnv()->Invoke("ConvertToRGB32", AVSValue(args, 2), argnames);
|
|
}
|
|
else
|
|
colorspace = "None";
|
|
|
|
RGB32Video = avs.GetEnv()->Invoke("Cache", script).AsClip();
|
|
vi = RGB32Video->GetVideoInfo();
|
|
fps = (double)vi.fps_numerator / vi.fps_denominator;
|
|
}
|
|
catch (AvisynthError const& err) {
|
|
throw VideoOpenError("Avisynth error: " + std::string(err.msg));
|
|
}
|
|
}
|
|
|
|
AVSValue AvisynthVideoProvider::Open(agi::fs::path const& filename) {
|
|
IScriptEnvironment *env = avs.GetEnv();
|
|
char *videoFilename = env->SaveString(agi::fs::ShortName(filename).c_str());
|
|
|
|
// Avisynth file, just import it
|
|
if (agi::fs::HasExtension(filename, "avs")) {
|
|
LOG_I("avisynth/video") << "Opening .avs file with Import";
|
|
decoder_name = "Avisynth/Import";
|
|
return env->Invoke("Import", videoFilename);
|
|
}
|
|
|
|
// Open avi file with AviSource
|
|
if (agi::fs::HasExtension(filename, "avi")) {
|
|
LOG_I("avisynth/video") << "Opening .avi file with AviSource";
|
|
try {
|
|
const char *argnames[2] = { 0, "audio" };
|
|
AVSValue args[2] = { videoFilename, false };
|
|
decoder_name = "Avisynth/AviSource";
|
|
return env->Invoke("AviSource", AVSValue(args,2), argnames);
|
|
}
|
|
// On Failure, fallback to DSS
|
|
catch (AvisynthError &err) {
|
|
LOG_E("avisynth/video") << err.msg;
|
|
LOG_I("avisynth/video") << "Failed to open .avi file with AviSource, trying DirectShowSource";
|
|
}
|
|
}
|
|
|
|
// Open d2v with mpeg2dec3
|
|
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Dec3_Mpeg2Source")) {
|
|
LOG_I("avisynth/video") << "Opening .d2v file with Mpeg2Dec3_Mpeg2Source";
|
|
auto script = env->Invoke("Mpeg2Dec3_Mpeg2Source", videoFilename);
|
|
decoder_name = "Avisynth/Mpeg2Dec3_Mpeg2Source";
|
|
|
|
//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
|
|
if (env->FunctionExists("SetPlanarLegacyAlignment")) {
|
|
AVSValue args[2] = { script, true };
|
|
script = env->Invoke("SetPlanarLegacyAlignment", AVSValue(args,2));
|
|
}
|
|
return script;
|
|
}
|
|
|
|
// If that fails, try opening it with DGDecode
|
|
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("DGDecode_Mpeg2Source")) {
|
|
LOG_I("avisynth/video") << "Opening .d2v file with DGDecode_Mpeg2Source";
|
|
decoder_name = "DGDecode_Mpeg2Source";
|
|
return env->Invoke("Avisynth/Mpeg2Source", videoFilename);
|
|
|
|
//note that DGDecode will also have issues like if the version is too
|
|
// ancient but no sane person would use that anyway
|
|
}
|
|
|
|
if (agi::fs::HasExtension(filename, "d2v") && env->FunctionExists("Mpeg2Source")) {
|
|
LOG_I("avisynth/video") << "Opening .d2v file with other Mpeg2Source";
|
|
AVSValue script = env->Invoke("Mpeg2Source", videoFilename);
|
|
decoder_name = "Avisynth/Mpeg2Source";
|
|
|
|
//if avisynth is 2.5.7 beta 2 or newer old mpeg2decs will crash without this
|
|
if (env->FunctionExists("SetPlanarLegacyAlignment"))
|
|
script = env->Invoke("SetPlanarLegacyAlignment", script);
|
|
|
|
return script;
|
|
}
|
|
|
|
// Try loading DirectShowSource2
|
|
if (!env->FunctionExists("dss2")) {
|
|
auto dss2path(config::path->Decode("?data/avss.dll"));
|
|
if (agi::fs::FileExists(dss2path))
|
|
env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dss2path).c_str()));
|
|
}
|
|
|
|
// If DSS2 loaded properly, try using it
|
|
if (env->FunctionExists("dss2")) {
|
|
LOG_I("avisynth/video") << "Opening file with DSS2";
|
|
decoder_name = "Avisynth/DSS2";
|
|
return env->Invoke("DSS2", videoFilename);
|
|
}
|
|
|
|
// Try DirectShowSource
|
|
// Load DirectShowSource.dll from app dir if it exists
|
|
auto dsspath(config::path->Decode("?data/DirectShowSource.dll"));
|
|
if (agi::fs::FileExists(dsspath))
|
|
env->Invoke("LoadPlugin", env->SaveString(agi::fs::ShortName(dsspath).c_str()));
|
|
|
|
// Then try using DSS
|
|
if (env->FunctionExists("DirectShowSource")) {
|
|
const char *argnames[3] = { 0, "video", "audio" };
|
|
AVSValue args[3] = { videoFilename, true, false };
|
|
decoder_name = "Avisynth/DirectShowSource";
|
|
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!";
|
|
LOG_I("avisynth/video") << "Opening file with DirectShowSource";
|
|
return env->Invoke("DirectShowSource", AVSValue(args,3), argnames);
|
|
}
|
|
|
|
// Failed to find a suitable function
|
|
LOG_E("avisynth/video") << "DSS function not found";
|
|
throw VideoNotSupported("No function suitable for opening the video found");
|
|
}
|
|
|
|
std::shared_ptr<VideoFrame> AvisynthVideoProvider::GetFrame(int n) {
|
|
std::lock_guard<std::mutex> lock(avs.GetMutex());
|
|
|
|
auto frame = RGB32Video->GetFrame(n, avs.GetEnv());
|
|
return std::make_shared<VideoFrame>(frame->GetReadPtr(), frame->GetRowSize() / 4, frame->GetHeight(), frame->GetPitch(), true);
|
|
}
|
|
#endif // HAVE_AVISYNTH
|