#define INITGUID
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <vfw.h>
#include <aviriff.h>
#include <string>
#include <algorithm>
#include <mutex>
#include <atomic>
#include <vector>
#include <chrono>
#include <thread>
#include <filesystem>
#include "VSScript4.h"
#include "VSHelper4.h"
#include "../common/fourcc.h"
#include "../common/wave.h"
using namespace vsh;
static std::atomic<long> refCount(0);
static const GUID CLSID_VapourSynth
= { 0x58f74ca0, 0xbd0e, 0x4664, { 0xa4, 0x9b, 0x8d, 0x10, 0xe6, 0xf0, 0xc1, 0x31 } };
static const GUID IID_IAvisynthClipInfo
= { 0xe6d6b708, 0x124d, 0x11d4, {0x86, 0xf3, 0xdb, 0x80, 0xaf, 0xd9, 0x87, 0x78} };
struct IAvisynthClipInfo : IUnknown {
virtual int __stdcall GetError(const char** ppszMessage) = 0;
virtual bool __stdcall GetParity(int n) = 0;
virtual bool __stdcall IsFieldBased() = 0;
};
class VapourSynthFile final : public IAVIFile, public IPersistFile, public IClassFactory, public IAvisynthClipInfo {
friend class VapourSynthStream;
private:
int num_threads = 1;
const VSAPI *vsapi = nullptr;
const VSSCRIPTAPI *vssapi = nullptr;
VSScript *se = nullptr;
int alt_output = 0;
VSNode *videoNode = nullptr;
VSNode *audioNode = nullptr;
std::atomic<long> m_refs;
std::filesystem::path szScriptName;
const VSVideoInfo *vi = nullptr;
const VSAudioInfo* ai = nullptr;
std::string error_msg;
std::atomic<long> pending_requests;
std::mutex cs_filter_graph;
bool DelayInit();
bool DelayInit2();
void Lock();
void Unlock();
public:
VapourSynthFile(const CLSID& rclsid);
~VapourSynthFile();
static HRESULT Create(const CLSID& rclsid, const IID& riid, void **ppv);
static void VS_CC frameDoneCallback(void *userData, const VSFrame *f, int n, VSNode *, const char *errorMsg);
STDMETHODIMP QueryInterface(const IID& iid, void **ppv) noexcept;
STDMETHODIMP_(ULONG) AddRef() noexcept;
STDMETHODIMP_(ULONG) Release() noexcept;
STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void * * ppvObj);
STDMETHODIMP LockServer(BOOL fLock);
STDMETHODIMP GetClassID(LPCLSID lpClassID);
STDMETHODIMP IsDirty();
STDMETHODIMP Load(LPCOLESTR lpszFileName, DWORD grfMode);
STDMETHODIMP Save(LPCOLESTR lpszFileName, BOOL fRemember);
STDMETHODIMP SaveCompleted(LPCOLESTR lpszFileName);
STDMETHODIMP GetCurFile(LPOLESTR *lplpszFileName);
STDMETHODIMP CreateStream(PAVISTREAM *ppStream, AVISTREAMINFOW *psi) noexcept;
STDMETHODIMP EndRecord() noexcept;
STDMETHODIMP GetStream(PAVISTREAM *ppStream, DWORD fccType, LONG lParam) noexcept;
STDMETHODIMP Info(AVIFILEINFOW *psi, LONG lSize) noexcept;
STDMETHODIMP ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) noexcept;
STDMETHODIMP WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) noexcept;
STDMETHODIMP DeleteStream(DWORD fccType, LONG lParam) noexcept;
int __stdcall GetError(const char** ppszMessage);
bool __stdcall GetParity(int n);
bool __stdcall IsFieldBased();
};
class VapourSynthStream final : public IAVIStream, public IAVIStreaming {
public:
STDMETHODIMP QueryInterface(const IID& iid, void **ppv) noexcept;
STDMETHODIMP_(ULONG) AddRef() noexcept;
STDMETHODIMP_(ULONG) Release() noexcept;
VapourSynthStream(VapourSynthFile *parentPtr, bool isAudio);
~VapourSynthStream();
STDMETHODIMP Create(LPARAM lParam1, LPARAM lParam2) noexcept;
STDMETHODIMP Delete(LONG lStart, LONG lSamples) noexcept;
STDMETHODIMP Info(AVISTREAMINFOW *psi, LONG lSize) noexcept;
STDMETHODIMP_(LONG) FindSample(LONG lPos, LONG lFlags) noexcept;
STDMETHODIMP Read(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples) noexcept;
STDMETHODIMP ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) noexcept;
STDMETHODIMP ReadFormat(LONG lPos, LPVOID lpFormat, LONG *lpcbFormat) noexcept;
STDMETHODIMP SetFormat(LONG lPos, LPVOID lpFormat, LONG cbFormat) noexcept;
STDMETHODIMP Write(LONG lStart, LONG lSamples, LPVOID lpBuffer,
LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten,
LONG FAR *plBytesWritten) noexcept;
STDMETHODIMP WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) noexcept;
STDMETHODIMP SetInfo(AVISTREAMINFOW *psi, LONG lSize) noexcept;
STDMETHODIMP Begin(LONG lStart, LONG lEnd, LONG lRate) noexcept;
STDMETHODIMP End() noexcept;
private:
std::atomic<long> m_refs;
VapourSynthFile *parent;
std::string sName;
bool fAudio = false;
bool ReadFrame(void* lpBuffer, int n);
HRESULT Read2(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples);
};
STDAPI DllGetClassObject(const CLSID& rclsid, const IID& riid, void **ppv) {
if (rclsid != CLSID_VapourSynth)
return CLASS_E_CLASSNOTAVAILABLE;
HRESULT hresult = VapourSynthFile::Create(rclsid, riid, ppv);
return hresult;
}
STDAPI DllCanUnloadNow() {
return refCount ? S_FALSE : S_OK;
}
STDMETHODIMP VapourSynthFile::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void * * ppvObj) {
if (pUnkOuter)
return CLASS_E_NOAGGREGATION;
HRESULT hresult = Create(CLSID_VapourSynth, riid, ppvObj);
return hresult;
}
STDMETHODIMP VapourSynthFile::LockServer(BOOL fLock) {
return S_OK;
}
STDMETHODIMP VapourSynthFile::GetClassID(LPCLSID lpClassID) { if (!lpClassID)
return E_POINTER;
*lpClassID = CLSID_VapourSynth;
return S_OK;
}
STDMETHODIMP VapourSynthFile::IsDirty() {
return S_FALSE;
}
STDMETHODIMP VapourSynthFile::Load(LPCOLESTR lpszFileName, DWORD grfMode) {
if (grfMode & (OF_CREATE | OF_WRITE))
return E_FAIL;
szScriptName = lpszFileName;
return S_OK;
}
STDMETHODIMP VapourSynthFile::Save(LPCOLESTR lpszFileName, BOOL fRemember) {
return E_FAIL;
}
STDMETHODIMP VapourSynthFile::SaveCompleted(LPCOLESTR lpszFileName) {
return S_OK;
}
STDMETHODIMP VapourSynthFile::GetCurFile(LPOLESTR *lplpszFileName) {
if (lplpszFileName)
*lplpszFileName = nullptr;
return E_FAIL;
}
HRESULT VapourSynthFile::Create(const CLSID& rclsid, const IID& riid, void **ppv) {
HRESULT hresult;
VapourSynthFile* pAVIFileSynth = new(std::nothrow)VapourSynthFile(rclsid);
if (!pAVIFileSynth)
return E_OUTOFMEMORY;
hresult = pAVIFileSynth->QueryInterface(riid, ppv);
pAVIFileSynth->Release();
return hresult;
}
STDMETHODIMP VapourSynthFile::QueryInterface(const IID& iid, void **ppv) noexcept {
if (!ppv)
return E_POINTER;
if (iid == IID_IUnknown) {
*ppv = (IUnknown *)(IAVIFile *)this;
} else if (iid == IID_IClassFactory) {
*ppv = (IClassFactory *)this;
} else if (iid == IID_IPersist) {
*ppv = (IPersist *)this;
} else if (iid == IID_IPersistFile) {
*ppv = (IPersistFile *)this;
} else if (iid == IID_IAVIFile) {
*ppv = (IAVIFile *)this;
} else if (iid == IID_IAvisynthClipInfo) {
*ppv = (IAvisynthClipInfo *)this;
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) VapourSynthFile::AddRef() noexcept {
const int refs = ++m_refs;
++refCount;
return refs;
}
STDMETHODIMP_(ULONG) VapourSynthFile::Release() noexcept {
const int refs = --m_refs;
--refCount;
if (!refs)
delete this;
return refs;
}
STDMETHODIMP VapourSynthStream::QueryInterface(const IID& iid, void **ppv) noexcept {
if (!ppv)
return E_POINTER;
if (iid == IID_IUnknown) {
*ppv = (IUnknown *)(IAVIStream *)this;
} else if (iid == IID_IAVIStream) {
*ppv = (IAVIStream *)this;
} else if (iid == IID_IAVIStreaming) {
*ppv = (IAVIStreaming *)this;
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
AddRef();
return S_OK;
}
STDMETHODIMP_(ULONG) VapourSynthStream::AddRef() noexcept {
const int refs = ++m_refs;
++refCount;
return refs;
}
STDMETHODIMP_(ULONG) VapourSynthStream::Release() noexcept {
const int refs = --m_refs;
--refCount;
if (!refs) delete this;
return refs;
}
STDMETHODIMP VapourSynthFile::CreateStream(PAVISTREAM *ppStream, AVISTREAMINFOW *psi) noexcept {
*ppStream = nullptr;
return S_OK;
}
STDMETHODIMP VapourSynthFile::EndRecord() noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthFile::ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) noexcept {
return AVIERR_NODATA;
}
STDMETHODIMP VapourSynthFile::WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthFile::DeleteStream(DWORD fccType, LONG lParam) noexcept {
return AVIERR_READONLY;
}
VapourSynthFile::VapourSynthFile(const CLSID& rclsid) : m_refs(0), pending_requests(0) {
vssapi = getVSScriptAPI(VSSCRIPT_API_VERSION);
assert(vssapi);
vsapi = vssapi->getVSAPI(VAPOURSYNTH_API_VERSION);
AddRef();
}
VapourSynthFile::~VapourSynthFile() {
Lock();
if (vi) {
while (pending_requests > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); };
vsapi->freeNode(videoNode);
videoNode = nullptr;
}
if (ai) {
vsapi->freeNode(audioNode);
audioNode = nullptr;
}
if (vi || ai) {
vi = nullptr;
ai = nullptr;
vssapi->freeScript(se);
se = nullptr;
}
Unlock();
}
bool VapourSynthFile::DelayInit() {
Lock();
bool result = DelayInit2();
Unlock();
return result;
}
static const char *ErrorScript1 = "\
import vapoursynth as vs\n\
import sys\n\
core = vs.core\n\
w = 340\n\
h = 600\n\
red = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[255, 0, 0])\n\
green = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[0, 255, 0])\n\
blue = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[0, 0, 255])\n\
stacked = core.std.StackHorizontal([red, green, blue])\n\
msg = core.text.Text(stacked, r\"\"\"";
static const char *ErrorScript2 = "\"\"\")\n\
msg.set_output()\n";
bool VapourSynthFile::DelayInit2() {
if (!szScriptName.empty() && !vi) {
se = vssapi->createScript(nullptr);
vssapi->evalSetWorkingDir(se, 1);
vssapi->evaluateFile(se, szScriptName.u8string().c_str());
if (!vssapi->getError(se)) {
error_msg.clear();
VSCoreInfo info;
vsapi->getCoreInfo(vssapi->getCore(se), &info);
num_threads = info.numThreads;
videoNode = vssapi->getOutputNode(se, 0);
if (!videoNode) {
error_msg = "Couldn't get output clip, no output set?";
goto vpyerror;
}
if (vsapi->getNodeType(videoNode) != mtVideo) {
error_msg = "Output index 0 is not video";
goto vpyerror;
}
vi = vsapi->getVideoInfo(videoNode);
if (!isConstantVideoFormat(vi)) {
error_msg = "Cannot open clips with varying dimensions or format in vfw";
goto vpyerror;
}
if (!HasSupportedFourCC(vi->format)) {
error_msg = "VFW module doesn't support ";
char nameBuffer[32];
vsapi->getVideoFormatName(&vi->format, nameBuffer);
error_msg += nameBuffer;
error_msg += " output";
goto vpyerror;
}
vsapi->setCacheMode(videoNode, cmForceEnable);
vsapi->setCacheOptions(videoNode, -1, num_threads * 2, -1);
alt_output = vssapi->getAltOutputMode(se, 0);
audioNode = vssapi->getOutputNode(se, 1);
if (audioNode) {
if (vsapi->getNodeType(audioNode) != mtAudio) {
error_msg = "Output index 1 is not audio";
goto vpyerror;
}
ai = vsapi->getAudioInfo(audioNode);
if (ai->numSamples > std::numeric_limits<DWORD>::max()) {
error_msg = "Audio has more samples than can be represented in VFW structures";
goto vpyerror;
}
vsapi->setCacheMode(audioNode, cmForceEnable);
vsapi->setCacheOptions(audioNode, -1, num_threads * 2, -1);
}
return true;
} else {
error_msg = vssapi->getError(se);
vpyerror:
vsapi->freeNode(videoNode);
vsapi->freeNode(audioNode);
videoNode = nullptr;
audioNode = nullptr;
vi = nullptr;
ai = nullptr;
vssapi->freeScript(se);
se = nullptr;
std::string error_script = ErrorScript1;
error_script += error_msg;
error_script += ErrorScript2;
se = vssapi->createScript(nullptr);
vssapi->evaluateBuffer(se, error_script.c_str(), "vfw_error.message");
videoNode = vssapi->getOutputNode(se, 0);
vi = vsapi->getVideoInfo(videoNode);
return true;
}
} else {
return !!vi;
}
}
void VapourSynthFile::Lock() {
cs_filter_graph.lock();
}
void VapourSynthFile::Unlock() {
cs_filter_graph.unlock();
}
STDMETHODIMP VapourSynthFile::Info(AVIFILEINFOW *pfi, LONG lSize) noexcept {
if (!pfi)
return E_POINTER;
if (!DelayInit())
return E_FAIL;
AVIFILEINFOW afi = {};
afi.dwMaxBytesPerSec = 0;
afi.dwFlags = AVIFILEINFO_HASINDEX | AVIFILEINFO_ISINTERLEAVED;
afi.dwCaps = AVIFILECAPS_CANREAD | AVIFILECAPS_ALLKEYFRAMES | AVIFILECAPS_NOCOMPRESSION;
afi.dwStreams = 1;
if (audioNode)
afi.dwStreams++;
afi.dwSuggestedBufferSize = 0;
afi.dwWidth = vi->width;
afi.dwHeight = vi->height;
afi.dwEditCount = 0;
afi.dwRate = int64ToIntS(vi->fpsNum ? vi->fpsNum : 1);
afi.dwScale = int64ToIntS(vi->fpsDen ? vi->fpsDen : 30);
afi.dwLength = vi->numFrames;
wcscpy(afi.szFileType, L"VapourSynth");
memset(pfi, 0, lSize);
memcpy(pfi, &afi, std::min(static_cast<size_t>(lSize), sizeof(afi)));
return S_OK;
}
STDMETHODIMP VapourSynthFile::GetStream(PAVISTREAM *ppStream, DWORD fccType, LONG lParam) noexcept {
VapourSynthStream *casr;
if (!DelayInit())
return E_FAIL;
*ppStream = nullptr;
if (!fccType) {
if (lParam == 0 && videoNode) {
fccType = streamtypeVIDEO;
} else {
if ((lParam == 1 && videoNode) || (lParam == 0 && audioNode)) {
lParam = 0;
fccType = streamtypeAUDIO;
}
}
}
if (lParam > 0)
return AVIERR_NODATA;
if (fccType == streamtypeVIDEO) {
if ((casr = new(std::nothrow)VapourSynthStream(this, false)) == 0)
return AVIERR_MEMORY;
*ppStream = (IAVIStream *)casr;
} else if (fccType == streamtypeAUDIO && ai) {
if ((casr = new(std::nothrow)VapourSynthStream(this, true)) == 0)
return AVIERR_MEMORY;
*ppStream = (IAVIStream *)casr;
} else {
return AVIERR_NODATA;
}
return S_OK;
}
int __stdcall VapourSynthFile::GetError(const char** ppszMessage) {
if (!DelayInit() && error_msg.empty())
error_msg = "VapourSynth: script open failed!";
if (ppszMessage)
*ppszMessage = error_msg.c_str();
return !error_msg.empty();
}
bool __stdcall VapourSynthFile::GetParity(int n) {
if (!DelayInit())
return false;
return false;
}
bool __stdcall VapourSynthFile::IsFieldBased() {
if (!DelayInit())
return false;
return false;
}
STDMETHODIMP VapourSynthStream::Begin(LONG lStart, LONG lEnd, LONG lRate) noexcept {
return S_OK;
}
STDMETHODIMP VapourSynthStream::End() noexcept {
return S_OK;
}
STDMETHODIMP VapourSynthStream::Create(LPARAM lParam1, LPARAM lParam2) noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthStream::Delete(LONG lStart, LONG lSamples) noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthStream::ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) noexcept {
return AVIERR_NODATA;
}
STDMETHODIMP VapourSynthStream::SetFormat(LONG lPos, LPVOID lpFormat, LONG cbFormat) noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthStream::WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) noexcept {
return AVIERR_READONLY;
}
STDMETHODIMP VapourSynthStream::SetInfo(AVISTREAMINFOW *psi, LONG lSize) noexcept {
return AVIERR_READONLY;
}
VapourSynthStream::VapourSynthStream(VapourSynthFile *parentPtr, bool isAudio) : m_refs(0), sName(isAudio ? "audio" : "video"), fAudio(isAudio) {
AddRef();
parent = parentPtr;
parent->AddRef();
}
VapourSynthStream::~VapourSynthStream() {
if (parent)
parent->Release();
}
STDMETHODIMP VapourSynthStream::Info(AVISTREAMINFOW *psi, LONG lSize) noexcept {
if (!psi)
return E_POINTER;
AVISTREAMINFOW asi = {};
asi.dwQuality = DWORD(-1);
if (fAudio) {
const VSAudioInfo* const ai = parent->ai;
size_t bytesPerOutputSample = (ai->format.bitsPerSample + 7) / 8;
asi.fccType = streamtypeAUDIO;
asi.dwScale = static_cast<DWORD>(bytesPerOutputSample);
asi.dwRate = static_cast<DWORD>(ai->sampleRate * bytesPerOutputSample);
asi.dwLength = static_cast<DWORD>(ai->numSamples);
asi.dwSampleSize = static_cast<DWORD>(bytesPerOutputSample);
wcscpy(asi.szName, L"VapourSynth Audio #1");
} else {
const VSVideoInfo* const vi = parent->vi;
int image_size = BMPSize(vi, parent->alt_output);
if (!GetFourCC(vi->format, parent->alt_output, asi.fccHandler))
return E_FAIL;
asi.fccType = streamtypeVIDEO;
asi.dwScale = int64ToIntS(vi->fpsDen ? vi->fpsDen : 1);
asi.dwRate = int64ToIntS(vi->fpsNum ? vi->fpsNum : 30);
asi.dwLength = vi->numFrames;
asi.rcFrame.right = vi->width;
asi.rcFrame.bottom = vi->height;
asi.dwSampleSize = image_size;
asi.dwSuggestedBufferSize = image_size;
wcscpy(asi.szName, L"VapourSynth Video #1");
}
memset(psi, 0, lSize);
memcpy(psi, &asi, std::min(static_cast<size_t>(lSize), sizeof(asi)));
return S_OK;
}
STDMETHODIMP_(LONG) VapourSynthStream::FindSample(LONG lPos, LONG lFlags) noexcept {
if (lFlags & FIND_FORMAT)
return -1;
if (lFlags & FIND_FROM_START)
return 0;
return lPos;
}
void VS_CC VapourSynthFile::frameDoneCallback(void *userData, const VSFrame *f, int n, VSNode *, const char *errorMsg) {
VapourSynthFile *vsfile = static_cast<VapourSynthFile *>(userData);
vsfile->vsapi->freeFrame(f);
--vsfile->pending_requests;
}
bool VapourSynthStream::ReadFrame(void* lpBuffer, int n) {
const VSAPI *vsapi = parent->vsapi;
const VSSCRIPTAPI *vssapi = parent->vssapi;
std::vector<char> errMsg(32 * 1024);
const VSFrame *f = vsapi->getFrame(n, parent->videoNode, errMsg.data(), static_cast<int>(errMsg.size()));
VSScript *errSe = nullptr;
if (!f) {
std::string matrix;
if (parent->vi->format.colorFamily == cfYUV || parent->vi->format.colorFamily == cfGray)
matrix = ", matrix_s=\"709\"";
char nameBuffer[32];
vsapi->getVideoFormatName(&parent->vi->format, nameBuffer);
std::string frameErrorScript = "import vapoursynth as vs\nimport sys\ncore = vs.core\n";
frameErrorScript += "err_script_formatid = vs." + std::string(nameBuffer) + "\n";
frameErrorScript += "err_script_width = " + std::to_string(parent->vi->width) + "\n";
frameErrorScript += "err_script_height = " + std::to_string(parent->vi->height) + "\n";
frameErrorScript += "err_script_background = core.std.BlankClip(width=err_script_width, height=err_script_height, format=vs.RGB24)\n";
frameErrorScript += "err_script_clip = core.text.Text(err_script_background, r\"\"\"";
frameErrorScript += errMsg.data();
frameErrorScript += "\"\"\")\n";
frameErrorScript += "err_script_clip = core.resize.Bilinear(err_script_clip, format=err_script_formatid" + matrix + ")\n";
frameErrorScript += "err_script_clip.set_output()\n";
errSe = vssapi->createScript(nullptr);
vssapi->evaluateBuffer(errSe, frameErrorScript.c_str(), "vfw_error.message");
VSNode *node = vssapi->getOutputNode(errSe, 0);
f = vsapi->getFrame(0, node, nullptr, 0);
vsapi->freeNode(node);
if (!f) {
vssapi->freeScript(errSe);
return false;
}
}
const uint8_t *src[3] = {};
ptrdiff_t src_stride[3] = {};
for (int plane = 0; plane < parent->vi->format.numPlanes; plane++) {
src[plane] = vsapi->getReadPtr(f, plane);
src_stride[plane] = vsapi->getStride(f, plane);
}
PackOutputFrame(src, src_stride, reinterpret_cast<uint8_t *>(lpBuffer), vsapi->getFrameWidth(f, 0), vsapi->getFrameHeight(f, 0), parent->vi->format, parent->alt_output);
vsapi->freeFrame(f);
vssapi->freeScript(errSe);
if (!errSe) {
for (int i = n + 1; i < std::min<int>(n + parent->num_threads, parent->vi->numFrames); i++) {
++parent->pending_requests;
vsapi->getFrameAsync(i, parent->videoNode, VapourSynthFile::frameDoneCallback, static_cast<void *>(parent));
}
}
return !errSe;
}
STDMETHODIMP VapourSynthStream::Read(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples) noexcept {
parent->Lock();
HRESULT result = Read2(lStart, lSamples, lpBuffer, cbBuffer, plBytes, plSamples);
parent->Unlock();
return result;
}
HRESULT VapourSynthStream::Read2(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples) {
const VSVideoInfo *vi = parent->vi;
if (fAudio) {
const VSAudioInfo *ai = parent->ai;
if (lSamples == AVISTREAMREAD_CONVENIENT)
lSamples = ai->sampleRate;
if (static_cast<int64_t>(lStart) + lSamples > ai->numSamples)
lSamples = std::max<long>(static_cast<long>(ai->numSamples - lStart), 0);
size_t bytesPerOutputSample = (ai->format.bitsPerSample + 7) / 8;
LONG bytes = static_cast<LONG>(lSamples * bytesPerOutputSample * ai->format.numChannels);
if (lpBuffer && bytes > cbBuffer) {
lSamples = static_cast<LONG>(cbBuffer / (bytesPerOutputSample * ai->format.numChannels));
bytes = static_cast<LONG>(lSamples * bytesPerOutputSample * ai->format.numChannels);
}
if (plBytes)
*plBytes = bytes;
if (plSamples)
*plSamples = lSamples;
if (!lpBuffer || !lSamples)
return S_OK;
const VSAudioFormat &af = ai->format;
int startFrame = lStart / VS_AUDIO_FRAME_SAMPLES;
int endFrame = (lStart + lSamples - 1) / VS_AUDIO_FRAME_SAMPLES;
std::vector<const uint8_t *> tmp;
tmp.resize(ai->format.numChannels);
const VSAPI *vsapi = parent->vsapi;
size_t dstPos = 0;
for (int i = startFrame; i <= endFrame; i++) {
const VSFrame *f = vsapi->getFrame(i, parent->audioNode, nullptr, 0);
if (!f)
return E_FAIL;
int64_t firstFrameSample = i * static_cast<int64_t>(VS_AUDIO_FRAME_SAMPLES);
size_t offset = 0;
size_t copyLength = VS_AUDIO_FRAME_SAMPLES;
if (firstFrameSample < lStart) {
offset = (lStart - firstFrameSample) * ai->format.bytesPerSample;
copyLength -= (lStart - firstFrameSample);
}
if (lSamples < copyLength)
copyLength = lSamples;
for (int c = 0; c < ai->format.numChannels; c++)
tmp[c] = vsapi->getReadPtr(f, c) + offset;
if (bytesPerOutputSample == 2)
PackChannels16to16le(tmp.data(), reinterpret_cast<uint8_t *>(lpBuffer) + dstPos, copyLength, af.numChannels);
else if (bytesPerOutputSample == 3)
PackChannels32to24le(tmp.data(), reinterpret_cast<uint8_t *>(lpBuffer) + dstPos, copyLength, af.numChannels);
else if (bytesPerOutputSample == 4)
PackChannels32to32le(tmp.data(), reinterpret_cast<uint8_t *>(lpBuffer) + dstPos, copyLength, af.numChannels);
lSamples -= copyLength;
dstPos += copyLength * af.numChannels * bytesPerOutputSample;
vsapi->freeFrame(f);
}
return S_OK;
} else {
if (lStart >= vi->numFrames) {
if (plSamples)
*plSamples = 0;
if (plBytes)
*plBytes = 0;
return S_OK;
}
int image_size = BMPSize(vi, parent->alt_output);
if (plSamples)
*plSamples = 1;
if (plBytes)
*plBytes = image_size;
if (!lpBuffer) {
return S_OK;
} else if (cbBuffer < image_size) {
return AVIERR_BUFFERTOOSMALL;
}
if (!ReadFrame(lpBuffer, lStart))
return E_FAIL;
return S_OK;
}
}
static bool IsSimpleAudio(const VSAudioInfo *ai) {
if (((ai->format.sampleType == stInteger && (ai->format.bitsPerSample == 16 || ai->format.bitsPerSample == 24 || ai->format.bitsPerSample == 32)) || (ai->format.sampleType == stFloat && ai->format.bitsPerSample == 32))) {
if (ai->format.numChannels == 1)
return true;
else if (ai->format.numChannels == 2 && ai->format.channelLayout == ((1 << acFrontLeft) | (1 << acFrontRight)))
return true;
else if (ai->format.numChannels == 2 && ai->format.channelLayout == ((1 << acStereoLeft) | (1 << acStereoRight)))
return true;
}
return false;
}
STDMETHODIMP VapourSynthStream::ReadFormat(LONG lPos, LPVOID lpFormat, LONG *lpcbFormat) noexcept {
static_assert(sizeof(WAVEFORMATEXTENSIBLE) == sizeof(WaveFormatExtensible));
if (!lpcbFormat)
return E_POINTER;
if (!lpFormat) {
if (fAudio) {
*lpcbFormat = IsSimpleAudio(parent->ai) ? sizeof(WAVEFORMATEX) : sizeof(WaveFormatExtensible);
} else {
*lpcbFormat = sizeof(BITMAPINFOHEADER);
}
return S_OK;
}
memset(lpFormat, 0, *lpcbFormat);
if (fAudio) {
const VSAudioInfo *const ai = parent->ai;
WaveFormatExtensible wfxt;
if (!CreateWaveFormatExtensible(wfxt, ai->format.sampleType == stFloat, ai->format.bitsPerSample, ai->sampleRate, ai->format.channelLayout))
return E_FAIL;
if (IsSimpleAudio(ai)) {
wfxt.wFormatTag = (ai->format.sampleType == stFloat) ? WAVE_FORMAT_IEEE_FLOAT : WAVE_FORMAT_PCM;
wfxt.cbSize = 0;
*lpcbFormat = std::min<LONG>(*lpcbFormat, sizeof(WAVEFORMATEX));
} else {
*lpcbFormat = std::min<LONG>(*lpcbFormat, sizeof(wfxt));
}
memcpy(lpFormat, &wfxt, size_t(*lpcbFormat));
} else {
const VSVideoInfo *const vi = parent->vi;
BITMAPINFOHEADER bi = {};
bi.biSize = sizeof(bi);
bi.biWidth = vi->width;
bi.biHeight = vi->height;
bi.biPlanes = 1;
bi.biBitCount = BitsPerPixel(vi->format, parent->alt_output);
if (!GetBiCompression(vi->format, parent->alt_output, bi.biCompression))
return E_FAIL;
bi.biSizeImage = BMPSize(vi, parent->alt_output);
*lpcbFormat = std::min<LONG>(*lpcbFormat, sizeof(bi));
memcpy(lpFormat, &bi, static_cast<size_t>(*lpcbFormat));
}
return S_OK;
}
STDMETHODIMP VapourSynthStream::Write(LONG lStart, LONG lSamples, LPVOID lpBuffer,
LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten,
LONG FAR *plBytesWritten) noexcept {
return AVIERR_READONLY;
}