#include "avfsincludes.h"
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
namespace avs {
const AVS_Linkage *AVS_linkage = nullptr;
}
typedef avs::IScriptEnvironment* (__stdcall *ICreateScriptEnvironment)(int version);
class Avisynther final:
public Avisynther_
{
int references = 1;
ICreateScriptEnvironment CreateScriptEnvironment = nullptr;
HMODULE hlib = nullptr;
avs::IScriptEnvironment* env = nullptr;
int alt_output = 0;
avs::PClip *clip = nullptr;
std::wstring errText;
avs::VideoInfo vi = {};
std::string lastStringValue;
std::vector<uint8_t> packedFrame;
HANDLE fraThread = nullptr;
CRITICAL_SECTION fraMutex;
HANDLE fraResumeEvent;
HANDLE fraSuspendedEvent;
int fraPosition = 0;
int fraEndPosition = 0;
int fraSuspendCount = 0;
enum { fraDefaultFrameCount = 0 };
enum { fraMaxFrameCount = 100 };
int fraFrameCount = 0;
enum { fraMaxResumeDelay = 1000 };
enum { fraDefaultResumeDelay = 10 };
int fraResumeDelay = 0;
int lastPosition = -1;
avs::PVideoFrame *lastFrame = nullptr;
void setError(const char *text, const wchar_t *alt = 0);
const wchar_t* getError();
int newEnv();
avs::AVSValue Invoke(const char* name, const avs::AVSValue &args, const char * const * arg_names = nullptr);
int Import(const wchar_t* szScriptName);
void reportFormat(AvfsLog_* log);
void FraThreadMain();
static DWORD __stdcall FraThreadMainThunk(void* param);
public:
int BitsPerPixel();
int BMPSize();
uint8_t *GetPackedFrame();
bool GetAudio(AvfsLog_* log, void* buf, __int64 start, unsigned count);
avs::PVideoFrame GetFrame(AvfsLog_* log, int n, bool *success=0);
VideoInfoAdapter GetVideoInfo();
const char* GetVarAsString(const char* varName, const char* defVal);
bool GetVarAsBool(const char* varName, bool defVal);
int GetVarAsInt(const char* varName, int defVal);
void FraSuspend();
void FraResume();
Avisynther(void);
~Avisynther(void);
int Init(AvfsLog_* log,AvfsVolume_* volume);
void AddRef(void);
void Release(void);
};
int Avisynther::Import(const wchar_t* wszScriptName)
{
int error = ERROR_OUTOFMEMORY;
std::string szScriptName = utf16_to_utf8(wszScriptName);
if(!szScriptName.empty())
{
error = newEnv();
if(!error)
{
avs::AVSValue invUtf8[2]{ szScriptName.c_str(), true };
const char *arg_names[] = { nullptr, "utf8" };
avs::AVSValue var = Invoke("Import", avs::AVSValue(invUtf8, 2), arg_names);
if (!var.Defined())
var = Invoke("Import", szScriptName.c_str());
if (var.Defined()) {
if (var.IsClip()) {
if (var.AsClip()->GetVideoInfo().IsRGB48() || var.AsClip()->GetVideoInfo().IsRGB64())
var = Invoke("ConvertToPlanarRGB", var);
var = Invoke("Cache", var);
*clip = var.AsClip();
if (*clip) {
vi = (*clip)->GetVideoInfo();
}
alt_output = GetVarAsInt("alt_output", 0);
if (!HasSupportedFourCC(VideoInfoAdapter(&vi, this, alt_output).vf)) {
setError("AVFS module doesn't support output of the current format");
error = ERROR_ACCESS_DENIED;
} else {
packedFrame.clear();
packedFrame.resize(BMPSize());
}
}
else {
setError("The script's return value was not a video clip.");
error = ERROR_ACCESS_DENIED;
}
}
else {
error = ERROR_ACCESS_DENIED;
}
}
}
return error;
}
void Avisynther::reportFormat(AvfsLog_* log)
{
if (vi.HasVideo()) {
log->Print(L"Video stream :-\n");
int msLen = (int)(1000.0 * vi.num_frames * vi.fps_denominator / vi.fps_numerator);
log->Printf(L" Duration: %8d frames, %02d:%02d:%02d.%03d\n", vi.num_frames,
(msLen/(60*60*1000)), (msLen/(60*1000))%60 ,(msLen/1000)%60, msLen%1000);
const char* c_space = "";
if (vi.IsYV12()) c_space = "YV12";
else if (vi.IsYV16()) c_space = "YV16";
else if (vi.IsYV24()) c_space = "YV24";
else if (vi.IsYV411()) c_space = "YV411";
else if (vi.IsY8()) c_space = "Y8";
else if (vi.IsYUY2()) c_space = "YUY2";
else if (vi.IsRGB32()) c_space = "RGB32";
else if (vi.IsRGB24()) c_space = "RGB24";
log->Printf(L" ColorSpace: %hs\n", c_space);
log->Printf(L" Width:%4d pixels, Height:%4d pixels.\n", vi.width, vi.height);
log->Printf(L" Frames per second: %7.4f (%u/%u)\n", (double)vi.fps_numerator/vi.fps_denominator,
vi.fps_numerator, vi.fps_denominator);
log->Printf(L" FieldBased (Separated) Video: %hs\n", vi.IsFieldBased() ? "Yes" : "No");
try {
log->Printf(L" Parity: %hs field first.\n", ((*clip)->GetParity(0) ? "Top" : "Bottom"));
}
catch (...) { }
const char* s_parity;
if (vi.IsTFF() && vi.IsBFF())
s_parity="Invalid";
else if (vi.IsTFF())
s_parity="Assumed Top Field First";
else if (vi.IsBFF())
s_parity="Assumed Bottom Field First";
else
s_parity="Unspecified";
log->Printf(L" Field order: %hs\n", s_parity);
}
else
log->Print(L"No video stream.\n");
if (vi.HasAudio()) {
log->Print(L"Audio stream :-\n");
int msLen = (int)(1000.0 * vi.num_audio_samples / vi.audio_samples_per_second);
log->Printf(L" Audio length: %I64u samples. %02d:%02d:%02d.%03d\n", vi.num_audio_samples,
(msLen/(60*60*1000)), (msLen/(60*1000))%60, (msLen/1000)%60, msLen%1000);
log->Printf(L" Samples Per Second: %5d\n", vi.audio_samples_per_second);
log->Printf(L" Audio Channels: %-8d\n", vi.AudioChannels());
const char* s_type = "";
if (vi.SampleType()==avs::SAMPLE_INT8) s_type = "Integer 8 bit";
else if (vi.SampleType()==avs::SAMPLE_INT16) s_type = "Integer 16 bit";
else if (vi.SampleType()==avs::SAMPLE_INT24) s_type = "Integer 24 bit";
else if (vi.SampleType()==avs::SAMPLE_INT32) s_type = "Integer 32 bit";
else if (vi.SampleType()==avs::SAMPLE_FLOAT) s_type = "Float 32 bit";
log->Printf(L" Sample Type: %hs\n", s_type);
} else {
log->Print(L"No audio stream.\n");
}
}
void Avisynther::FraThreadMain()
{
int position;
EnterCriticalSection(&fraMutex);
while (fraSuspendCount != INT_MAX) {
if (fraSuspendCount > 0 || fraPosition == fraEndPosition) {
ResetEvent(fraResumeEvent);
SetEvent(fraSuspendedEvent);
LeaveCriticalSection(&fraMutex);
WaitForSingleObject(fraResumeEvent,INFINITE);
Sleep(fraResumeDelay);
EnterCriticalSection(&fraMutex);
}
else {
ResetEvent(fraSuspendedEvent);
position = fraPosition;
fraPosition ++;
LeaveCriticalSection(&fraMutex);
try {
avs::PVideoFrame frame = (*clip)->GetFrame(position, env);
}
catch (...) { }
EnterCriticalSection(&fraMutex);
}
}
LeaveCriticalSection(&fraMutex);
}
DWORD __stdcall Avisynther::FraThreadMainThunk(void* param)
{
static_cast<Avisynther*>(param)->FraThreadMain();
return 0;
}
void Avisynther::FraSuspend()
{
if (fraThread) {
EnterCriticalSection(&fraMutex);
fraSuspendCount ++;
LeaveCriticalSection(&fraMutex);
WaitForSingleObject(fraSuspendedEvent,INFINITE);
}
}
void Avisynther::FraResume()
{
if (fraThread) {
EnterCriticalSection(&fraMutex);
ASSERT(fraSuspendCount);
fraSuspendCount --;
if (fraSuspendCount == 0 && fraEndPosition > fraPosition) {
SetEvent(fraResumeEvent);
}
LeaveCriticalSection(&fraMutex);
}
}
bool Avisynther::GetAudio(AvfsLog_* log, void* buf, __int64 start, unsigned count) {
FraSuspend();
bool success = false;
if (*clip) {
if (vi.HasAudio()) {
try {
(*clip)->GetAudio(buf, start, (__int64)count, env);
success = true;
}
catch(avs::AvisynthError &err) {
setError(err.msg, L"GetAudio: AvisynthError.msg corrupted.");
}
catch (...) {
setError("GetAudio: Unknown exception.");
}
}
}
if(!success)
{
log->Line(getError());
}
FraResume();
return success;
}
static const BYTE *GetReadPtr(avs::PVideoFrame &f, const avs::VideoInfo &vi, int plane) {
if (!vi.IsPlanar())
return f->GetReadPtr();
else if (vi.IsYUV() || vi.IsY() || vi.IsY8())
return f->GetReadPtr(plane == 0 ? avs::PLANAR_Y : (plane == 1 ? avs::PLANAR_U : avs::PLANAR_V));
else if (vi.IsRGB())
return f->GetReadPtr(plane == 0 ? avs::PLANAR_R : (plane == 1 ? avs::PLANAR_G : avs::PLANAR_B));
else
return nullptr;
}
static int GetStride(avs::PVideoFrame &f, const avs::VideoInfo &vi, int plane) {
if (!vi.IsPlanar())
return f->GetPitch();
else if (vi.IsYUV() || vi.IsY() || vi.IsY8())
return f->GetPitch(plane == 0 ? avs::PLANAR_Y : (plane == 1 ? avs::PLANAR_U : avs::PLANAR_V));
else if (vi.IsRGB())
return f->GetPitch(plane == 0 ? avs::PLANAR_R : (plane == 1 ? avs::PLANAR_G : avs::PLANAR_B));
else
return 0;
}
avs::PVideoFrame Avisynther::GetFrame(AvfsLog_* log, int n, bool *_success) {
avs::PVideoFrame f;
bool success = false;
if (n == lastPosition) {
f = *lastFrame;
success = true;
}
else {
FraSuspend();
lastPosition = -1;
*lastFrame = nullptr;
if (*clip) {
if (vi.HasVideo()) {
try {
f = (*clip)->GetFrame(n, env);
success = true;
VideoInfoAdapter via = GetVideoInfo();
if (NeedsPacking(via.vf, via.alt_output)) {
const uint8_t *src[3] = {};
ptrdiff_t src_stride[3] = {};
for (int plane = 0; plane < via.vf.numPlanes; plane++) {
src[plane] = GetReadPtr(f, vi, plane);
src_stride[plane] = GetStride(f, vi, plane);
}
PackOutputFrame(src, src_stride, packedFrame.data(), via.width, via.height, via.vf, alt_output);
}
}
catch(avs::AvisynthError &err) {
setError(err.msg, L"GetFrame: AvisynthError.msg corrupted.");
}
catch (...) {
setError("GetFrame: Unknown exception.");
}
}
}
if (!success) {
log->Line(getError());
}
else {
lastPosition = n;
*lastFrame = f;
if(fraThread) {
if (n > fraEndPosition || n+fraFrameCount*2 < fraPosition) {
fraPosition = n+1;
}
fraEndPosition = n+1+fraFrameCount;
}
}
FraResume();
}
if (_success) *_success = success;
return f;
}
VideoInfoAdapter Avisynther::GetVideoInfo() {
return VideoInfoAdapter(&vi, this, alt_output);
}
int Avisynther::BMPSize() {
if (!vi.HasVideo())
return 0;
VideoInfoAdapter via = GetVideoInfo();
VSVideoInfo vi = {};
vi.format = via.vf;
vi.width = via.width;
vi.height = via.height;
return ::BMPSize(&vi, alt_output);
}
int Avisynther::BitsPerPixel() {
if (!vi.HasVideo())
return 0;
VideoInfoAdapter via = GetVideoInfo();
return ::BitsPerPixel(via.vf, 0);
}
const char* Avisynther::GetVarAsString(
const char* varName, const char* defVal) {
FraSuspend();
const char *string = defVal;
try {
avs::AVSValue value = env->GetVar(varName);
if (value.IsString()) {
string = value.AsString(defVal);
}
} catch (...) {
}
lastStringValue = string ? string : "";
FraResume();
return lastStringValue.c_str();
}
bool Avisynther::GetVarAsBool(const char* varName, bool defVal) {
FraSuspend();
bool result = defVal;
try {
avs::AVSValue value = env->GetVar(varName);
if (value.IsBool()) {
result = value.AsBool(defVal);
}
} catch (...) {
}
FraResume();
return result;
}
int Avisynther::GetVarAsInt(const char* varName, int defVal) {
FraSuspend();
int result = defVal;
try {
avs::AVSValue value = env->GetVar(varName);
result = value.AsInt(defVal);
} catch (...) {
}
FraResume();
return result;
}
void Avisynther::setError(const char *_text, const wchar_t *alt) {
errText.clear();
if (_text)
errText = utf16_from_utf8(_text);
else if (alt)
errText = alt;
else
errText = L"AvisynthError.msg corrupted.";
}
const wchar_t* Avisynther::getError() {
return errText.c_str();
}
uint8_t *Avisynther::GetPackedFrame() {
return packedFrame.data();
}
int Avisynther::newEnv() {
int error = ERROR_OUTOFMEMORY;
ASSERT(CreateScriptEnvironment);
if (env) {
delete lastFrame;
lastFrame = nullptr;
delete clip;
clip = nullptr;
try {
delete env;
} catch (...) {}
env = nullptr;
}
try {
env = CreateScriptEnvironment(avs::AVISYNTH_INTERFACE_VERSION);
if (env) {
avs::AVS_linkage = env->GetAVSLinkage();
clip = new avs::PClip;
lastFrame = new avs::PVideoFrame;
error = 0;
}
} catch (avs::AvisynthError &err) {
setError(err.msg, L"CreateScriptEnvironment: AvisynthError.msg corrupted.");
} catch (...) {
setError("CreateScriptEnvironment: Unknown exception.");
}
ASSERT(!error == !!env);
return error;
}
avs::AVSValue Avisynther::Invoke(const char* name, const avs::AVSValue &args, const char* const* arg_names) {
if (env) {
try {
return env->Invoke(name, args, arg_names);
} catch (avs::IScriptEnvironment::NotFound &) {
setError("Invoke: Function NotFound.");
} catch (avs::AvisynthError err) {
setError(err.msg, L"Invoke: AvisynthError.msg corrupted.");
} catch (...) {
setError("Invoke: Unknown exception.");
}
}
return avs::AVSValue();
}
Avisynther::Avisynther(void) {
InitializeCriticalSection(&fraMutex);
fraResumeEvent = CreateEvent(0, 1, 0, 0);
fraSuspendedEvent = CreateEvent(0, 1, 0, 0);
}
Avisynther::~Avisynther(void) {
ASSERT(!references);
if (fraThread) {
VERIFY(CloseHandle(fraThread));
}
if (fraResumeEvent) {
VERIFY(CloseHandle(fraResumeEvent));
}
if (fraSuspendedEvent) {
VERIFY(CloseHandle(fraSuspendedEvent));
}
DeleteCriticalSection(&fraMutex);
if (env) {
delete lastFrame;
lastFrame = nullptr;
delete clip;
clip = nullptr;
try {
delete env;
} catch (...) {}
env = 0;
}
if (hlib) {
avs::AVS_linkage = nullptr;
ASSERT(FreeLibrary(hlib));
hlib = nullptr;
CreateScriptEnvironment = nullptr;
}
}
int Avisynther::Init(
AvfsLog_* log,
AvfsVolume_* volume) {
int error = 0;
hlib = LoadLibrary(L"avisynth.dll");
if (hlib) {
CreateScriptEnvironment = (ICreateScriptEnvironment)GetProcAddress(hlib, "CreateScriptEnvironment");
if (!CreateScriptEnvironment) {
error = GetLastError();
setError("Cannot find \"CreateScriptEnvironment\" entry point.");
} else {
error = Import(volume->GetScriptFileName());
}
} else {
error = GetLastError();
setError("Cannot load \"avisynth.dll\".");
}
if (!error) {
fraFrameCount = GetVarAsInt("AVFS_ReadAheadFrameCount",
fraDefaultFrameCount);
if (fraFrameCount < 0) {
fraFrameCount = 0;
}
if (fraFrameCount > fraMaxFrameCount) {
fraFrameCount = fraMaxFrameCount;
}
fraResumeDelay = GetVarAsInt("AVFS_ReadAheadDelayMsecs",
fraDefaultResumeDelay);
if (fraResumeDelay > fraMaxResumeDelay) {
fraResumeDelay = fraMaxResumeDelay;
}
if (fraFrameCount && fraResumeEvent && fraSuspendedEvent) {
ResetEvent(fraResumeEvent);
SetEvent(fraSuspendedEvent);
DWORD unusedThreadId;
fraThread = CreateThread(0, 0, FraThreadMainThunk, this, 0, &unusedThreadId);
}
}
if (error) {
log->Line(getError());
} else {
reportFormat(log);
}
return error;
}
void Avisynther::AddRef(void) {
ASSERT(references);
references++;
}
void Avisynther::Release(void) {
ASSERT(references);
if (!--references) {
if (fraThread) {
FraSuspend();
EnterCriticalSection(&fraMutex);
fraSuspendCount = INT_MAX;
SetEvent(fraResumeEvent);
LeaveCriticalSection(&fraMutex);
WaitForSingleObject(fraThread, INFINITE);
}
delete this;
}
}
void AvfsProcessScript(
AvfsLog_* log,
AvfsVolume_* volume) {
Avisynther* avs = new(std::nothrow) Avisynther();
if (avs && avs->Init(log, volume) != 0) {
avs->Release();
avs = nullptr;
}
if (avs) {
AvfsWavMediaInit(log, avs, volume);
AvfsAviMediaInit(log, avs, volume);
avs->Release();
}
}