2006-01-22 13:44:53 +01:00
// 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.
//
2009-07-29 07:43:02 +02:00
// Aegisub Project http://www.aegisub.org/
2008-01-22 03:54:16 +01:00
# ifdef WITH_AVISYNTH
2014-03-24 15:05:01 +01:00
# include "include/aegisub/video_provider.h"
2012-04-17 01:55:15 +02:00
2013-01-04 16:01:50 +01:00
# include "options.h"
2013-07-01 05:15:43 +02:00
# include "video_frame.h"
2013-01-04 16:01:50 +01:00
# include <libaegisub/access.h>
# include <libaegisub/charset_conv.h>
# include <libaegisub/fs.h>
# include <libaegisub/log.h>
2013-01-30 04:35:37 +01:00
# include <libaegisub/path.h>
2014-04-23 22:53:24 +02:00
# include <libaegisub/make_unique.h>
2013-01-04 16:01:50 +01:00
# include <boost/algorithm/string/predicate.hpp>
# include <mutex>
2009-09-10 15:06:40 +02:00
2011-08-27 08:30:03 +02:00
# ifdef _WIN32
# include <vfw.h>
# endif
2014-03-24 15:05:01 +01:00
# define VideoFrame AVSVideoFrame
# include "avisynth.h"
# undef VideoFrame
# include "avisynth_wrap.h"
namespace {
class AvisynthVideoProvider : public VideoProvider {
AviSynthWrapper avs ;
std : : string decoder_name ;
agi : : vfr : : Framerate fps ;
std : : vector < int > keyframes ;
std : : string warning ;
std : : string colorspace ;
2014-05-20 01:47:54 +02:00
std : : string real_colorspace ;
2014-05-23 15:34:52 +02:00
bool has_audio = false ;
2014-03-24 15:05:01 +01:00
2014-05-20 04:21:50 +02:00
AVSValue source_clip ;
2014-03-24 15:05:01 +01:00
PClip RGB32Video ;
VideoInfo vi ;
AVSValue Open ( agi : : fs : : path const & filename ) ;
2014-05-20 04:21:50 +02:00
void Init ( std : : string const & matrix ) ;
2014-03-24 15:05:01 +01:00
public :
AvisynthVideoProvider ( agi : : fs : : path const & filename , std : : string const & colormatrix ) ;
2014-06-12 23:34:21 +02:00
void GetFrame ( int n , VideoFrame & frame ) override ;
2014-03-24 15:05:01 +01:00
2014-05-20 04:21:50 +02:00
void SetColorSpace ( std : : string const & matrix ) override {
// Can't really do anything if this fails
try { Init ( matrix ) ; } catch ( AvisynthError const & ) { }
}
2014-05-23 15:34:52 +02:00
int GetFrameCount ( ) const override { return vi . num_frames ; }
agi : : vfr : : Framerate GetFPS ( ) const override { return fps ; }
int GetWidth ( ) const override { return vi . width ; }
int GetHeight ( ) const override { return vi . height ; }
double GetDAR ( ) const override { return 0 ; }
2014-03-24 15:05:01 +01:00
std : : vector < int > GetKeyFrames ( ) const override { return keyframes ; }
2014-05-23 15:34:52 +02:00
std : : string GetWarning ( ) const override { return warning ; }
std : : string GetDecoderName ( ) const override { return decoder_name ; }
std : : string GetColorSpace ( ) const override { return colorspace ; }
2014-05-20 01:47:54 +02:00
std : : string GetRealColorSpace ( ) const override { return real_colorspace ; }
2014-05-23 15:34:52 +02:00
bool HasAudio ( ) const override { return has_audio ; }
2014-03-24 15:05:01 +01:00
} ;
AvisynthVideoProvider : : AvisynthVideoProvider ( agi : : fs : : path const & filename , std : : string const & colormatrix ) {
2013-01-04 16:01:50 +01:00
agi : : acs : : CheckFileRead ( filename ) ;
std : : lock_guard < std : : mutex > lock ( avs . GetMutex ( ) ) ;
2011-08-27 08:30:03 +02:00
# ifdef _WIN32
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " avi " ) ) {
2011-08-27 08:30:03 +02:00
// 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 ;
2013-01-04 16:01:50 +01:00
long hr = AVIFileOpen ( & pfile , filename . c_str ( ) , OF_SHARE_DENY_WRITE , 0 ) ;
2011-08-27 08:30:03 +02:00
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 :
2012-12-01 02:06:01 +01:00
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. " ;
2011-08-27 08:30:03 +02:00
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 ;
2011-12-26 23:20:49 +01:00
if ( FAILED ( AVIStreamInfo ( ppavi , & avis , sizeof ( avis ) ) ) ) {
2011-08-27 08:30:03 +02:00
warning = " Unable to read keyframes from AVI file: \n Could not get stream information. " ;
goto stream_release ;
}
for ( size_t i = 0 ; i < avis . dwLength ; i + + ) {
if ( AVIStreamIsKeyFrame ( ppavi , i ) )
2013-07-01 05:15:43 +02:00
keyframes . push_back ( i ) ;
2011-08-27 08:30:03 +02:00
}
2006-01-22 13:44:53 +01:00
2011-08-27 08:30:03 +02:00
// If every frame is a keyframe then just discard the keyframe data as it's useless
2013-07-01 05:15:43 +02:00
if ( keyframes . size ( ) = = ( size_t ) avis . dwLength )
keyframes . clear ( ) ;
2011-08-27 08:30:03 +02:00
// Clean up
stream_release :
AVIStreamRelease ( ppavi ) ;
file_release :
AVIFileRelease ( pfile ) ;
file_exit :
AVIFileExit ( ) ;
}
# endif
2012-02-02 21:51:07 +01:00
try {
2014-05-20 04:21:50 +02:00
source_clip = Open ( filename ) ;
Init ( colormatrix ) ;
2012-02-02 21:51:07 +01:00
}
catch ( AvisynthError const & err ) {
throw VideoOpenError ( " Avisynth error: " + std : : string ( err . msg ) ) ;
}
2010-08-02 08:32:01 +02:00
}
2006-01-22 13:44:53 +01:00
2014-05-20 04:21:50 +02:00
void AvisynthVideoProvider : : Init ( std : : string const & colormatrix ) {
auto script = source_clip ;
vi = script . AsClip ( ) - > GetVideoInfo ( ) ;
2014-05-23 15:34:52 +02:00
has_audio = vi . HasAudio ( ) ;
2014-05-20 04:21:50 +02:00
if ( vi . IsRGB ( ) )
real_colorspace = colorspace = " None " ;
else {
/// @todo maybe read ColorMatrix hints for d2v files?
2018-03-25 15:30:29 +02:00
AVSValue args [ 2 ] = { script , " Rec709 " } ;
2014-05-20 04:21:50 +02:00
bool bt709 = vi . width > 1024 | | vi . height > = 600 ;
2018-03-25 15:30:29 +02:00
if ( colormatrix = = " TV.601 " ) {
args [ 1 ] = " Rec601 " ;
colorspace = " TV.601 " ;
2014-05-20 04:21:50 +02:00
}
else {
2018-03-25 15:30:29 +02:00
colorspace = " TV.709 " ;
2014-05-20 04:21:50 +02:00
}
2018-03-25 15:30:29 +02:00
real_colorspace = bt709 ? " TV.709 " : " TV.601 " ;
2014-05-20 04:21:50 +02:00
const char * argnames [ 2 ] = { 0 , " matrix " } ;
script = avs . GetEnv ( ) - > Invoke ( " ConvertToRGB32 " , AVSValue ( args , 2 ) , argnames ) ;
}
RGB32Video = avs . GetEnv ( ) - > Invoke ( " Cache " , script ) . AsClip ( ) ;
vi = RGB32Video - > GetVideoInfo ( ) ;
fps = ( double ) vi . fps_numerator / vi . fps_denominator ;
}
2013-01-04 16:01:50 +01:00
AVSValue AvisynthVideoProvider : : Open ( agi : : fs : : path const & filename ) {
2012-01-18 21:08:32 +01:00
IScriptEnvironment * env = avs . GetEnv ( ) ;
2013-01-04 16:01:50 +01:00
char * videoFilename = env - > SaveString ( agi : : fs : : ShortName ( filename ) . c_str ( ) ) ;
2006-01-22 13:44:53 +01:00
2010-08-02 08:32:01 +02:00
// Avisynth file, just import it
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " avs " ) ) {
2010-08-02 08:32:01 +02:00
LOG_I ( " avisynth/video " ) < < " Opening .avs file with Import " ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/Import " ;
2010-08-02 08:32:01 +02:00
return env - > Invoke ( " Import " , videoFilename ) ;
}
2006-01-22 13:44:53 +01:00
2010-08-02 08:32:01 +02:00
// Open avi file with AviSource
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " avi " ) ) {
2010-08-02 08:32:01 +02:00
LOG_I ( " avisynth/video " ) < < " Opening .avi file with AviSource " ;
try {
const char * argnames [ 2 ] = { 0 , " audio " } ;
AVSValue args [ 2 ] = { videoFilename , false } ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/AviSource " ;
2010-08-02 08:32:01 +02:00
return env - > Invoke ( " AviSource " , AVSValue ( args , 2 ) , argnames ) ;
2006-12-17 05:58:10 +01:00
}
2010-08-02 08:32:01 +02:00
// On Failure, fallback to DSS
2011-08-27 08:30:03 +02:00
catch ( AvisynthError & err ) {
LOG_E ( " avisynth/video " ) < < err . msg ;
LOG_I ( " avisynth/video " ) < < " Failed to open .avi file with AviSource, trying DirectShowSource " ;
2006-01-22 13:44:53 +01:00
}
2010-08-02 08:32:01 +02:00
}
// Open d2v with mpeg2dec3
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " d2v " ) & & env - > FunctionExists ( " Mpeg2Dec3_Mpeg2Source " ) ) {
2010-08-02 08:32:01 +02:00
LOG_I ( " avisynth/video " ) < < " Opening .d2v file with Mpeg2Dec3_Mpeg2Source " ;
2013-01-04 16:01:50 +01:00
auto script = env - > Invoke ( " Mpeg2Dec3_Mpeg2Source " , videoFilename ) ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/Mpeg2Dec3_Mpeg2Source " ;
2006-12-19 18:30:25 +01:00
2010-08-02 08:32:01 +02:00
//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 ) ) ;
2007-01-11 20:49:37 +01:00
}
2010-08-02 08:32:01 +02:00
return script ;
}
2007-01-11 20:49:37 +01:00
2010-08-02 08:32:01 +02:00
// If that fails, try opening it with DGDecode
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " d2v " ) & & env - > FunctionExists ( " DGDecode_Mpeg2Source " ) ) {
2010-08-02 08:32:01 +02:00
LOG_I ( " avisynth/video " ) < < " Opening .d2v file with DGDecode_Mpeg2Source " ;
2013-07-01 05:15:43 +02:00
decoder_name = " DGDecode_Mpeg2Source " ;
2011-08-27 08:30:03 +02:00
return env - > Invoke ( " Avisynth/Mpeg2Source " , videoFilename ) ;
2007-01-11 20:49:37 +01:00
2011-08-27 08:30:03 +02:00
//note that DGDecode will also have issues like if the version is too
// ancient but no sane person would use that anyway
2010-08-02 08:32:01 +02:00
}
2006-12-19 18:30:25 +01:00
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : HasExtension ( filename , " d2v " ) & & env - > FunctionExists ( " Mpeg2Source " ) ) {
2010-08-02 08:32:01 +02:00
LOG_I ( " avisynth/video " ) < < " Opening .d2v file with other Mpeg2Source " ;
AVSValue script = env - > Invoke ( " Mpeg2Source " , videoFilename ) ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/Mpeg2Source " ;
2007-01-11 20:49:37 +01:00
2010-08-02 08:32:01 +02:00
//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 ;
}
2006-12-19 18:30:25 +01:00
2010-08-02 08:32:01 +02:00
// Try loading DirectShowSource2
if ( ! env - > FunctionExists ( " dss2 " ) ) {
2013-01-30 04:35:37 +01:00
auto dss2path ( config : : path - > Decode ( " ?data/avss.dll " ) ) ;
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : FileExists ( dss2path ) )
env - > Invoke ( " LoadPlugin " , env - > SaveString ( agi : : fs : : ShortName ( dss2path ) . c_str ( ) ) ) ;
2006-12-19 18:30:25 +01:00
}
2010-08-02 08:32:01 +02:00
// If DSS2 loaded properly, try using it
if ( env - > FunctionExists ( " dss2 " ) ) {
LOG_I ( " avisynth/video " ) < < " Opening file with DSS2 " ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/DSS2 " ;
2010-08-02 08:32:01 +02:00
return env - > Invoke ( " DSS2 " , videoFilename ) ;
}
// Try DirectShowSource
// Load DirectShowSource.dll from app dir if it exists
2013-01-30 04:35:37 +01:00
auto dsspath ( config : : path - > Decode ( " ?data/DirectShowSource.dll " ) ) ;
2013-01-04 16:01:50 +01:00
if ( agi : : fs : : FileExists ( dsspath ) )
env - > Invoke ( " LoadPlugin " , env - > SaveString ( agi : : fs : : ShortName ( dsspath ) . c_str ( ) ) ) ;
2010-08-02 08:32:01 +02:00
// Then try using DSS
if ( env - > FunctionExists ( " DirectShowSource " ) ) {
const char * argnames [ 3 ] = { 0 , " video " , " audio " } ;
AVSValue args [ 3 ] = { videoFilename , true , false } ;
2013-07-01 05:15:43 +02:00
decoder_name = " Avisynth/DirectShowSource " ;
2011-08-27 08:30:03 +02:00
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! " ;
2010-08-02 08:32:01 +02:00
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 " ) ;
}
2014-06-12 23:34:21 +02:00
void AvisynthVideoProvider : : GetFrame ( int n , VideoFrame & out ) {
2013-01-04 16:01:50 +01:00
std : : lock_guard < std : : mutex > lock ( avs . GetMutex ( ) ) ;
2007-01-21 07:30:19 +01:00
2013-01-04 16:01:50 +01:00
auto frame = RGB32Video - > GetFrame ( n , avs . GetEnv ( ) ) ;
2014-06-12 23:34:21 +02:00
auto ptr = frame - > GetReadPtr ( ) ;
out . data . assign ( ptr , ptr + frame - > GetPitch ( ) * frame - > GetHeight ( ) ) ;
out . flipped = true ;
out . height = frame - > GetHeight ( ) ;
out . width = frame - > GetRowSize ( ) / 4 ;
out . pitch = frame - > GetPitch ( ) ;
2007-07-29 11:06:38 +02:00
}
2014-03-24 15:05:01 +01:00
}
2014-03-25 17:51:38 +01:00
namespace agi { class BackgroundRunner ; }
std : : unique_ptr < VideoProvider > CreateAvisynthVideoProvider ( agi : : fs : : path const & path , std : : string const & colormatrix , agi : : BackgroundRunner * ) {
2014-04-23 22:53:24 +02:00
return agi : : make_unique < AvisynthVideoProvider > ( path , colormatrix ) ;
2014-03-24 15:05:01 +01:00
}
2011-08-27 08:30:03 +02:00
# endif // HAVE_AVISYNTH