1 /*
2 * Copyright (c) 2012-2016 Fredrik Mellbin
3 *
4 * This file is part of VapourSynth.
5 *
6 * VapourSynth is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * VapourSynth is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with VapourSynth; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20
21 // loosely based on the relevant parts of main.cpp in avisynth
22
23 #define INITGUID
24 #define WIN32_LEAN_AND_MEAN
25 #define NOMINMAX
26 #include <windows.h>
27 #include <vfw.h>
28 #include <aviriff.h>
29 #include <string>
30 #include <algorithm>
31 #include <mutex>
32 #include <atomic>
33 #include <vector>
34 #include <chrono>
35 #include <thread>
36
37 #include "VSScript.h"
38 #include "VSHelper.h"
39 #include "../common/p2p_api.h"
40 #include "../common/fourcc.h"
41 #include "../common/vsutf16.h"
42
43 static std::atomic<long> refCount(0);
44
45 static const GUID CLSID_VapourSynth
46 = { 0x58f74ca0, 0xbd0e, 0x4664, { 0xa4, 0x9b, 0x8d, 0x10, 0xe6, 0xf0, 0xc1, 0x31 } };
47
48 static const GUID IID_IAvisynthClipInfo
49 = { 0xe6d6b708, 0x124d, 0x11d4, {0x86, 0xf3, 0xdb, 0x80, 0xaf, 0xd9, 0x87, 0x78} };
50
51 struct IAvisynthClipInfo : IUnknown {
52 virtual int __stdcall GetError(const char** ppszMessage) = 0;
53 virtual bool __stdcall GetParity(int n) = 0;
54 virtual bool __stdcall IsFieldBased() = 0;
55 };
56
57 class VapourSynthFile final : public IAVIFile, public IPersistFile, public IClassFactory, public IAvisynthClipInfo {
58 friend class VapourSynthStream;
59 private:
60 int num_threads;
61 const VSAPI *vsapi;
62 VSScript *se;
63 bool enable_v210;
64 VSNodeRef *node;
65 std::atomic<long> m_refs;
66 std::string szScriptName;
67 const VSVideoInfo* vi;
68 std::string error_msg;
69 std::atomic<long> pending_requests;
70
71 std::mutex cs_filter_graph;
72
73 bool DelayInit();
74 bool DelayInit2();
75
76 void Lock();
77 void Unlock();
78 public:
79
80 VapourSynthFile(const CLSID& rclsid);
81 ~VapourSynthFile();
82
83 static HRESULT Create(const CLSID& rclsid, const IID& riid, void **ppv);
84 static void VS_CC frameDoneCallback(void *userData, const VSFrameRef *f, int n, VSNodeRef *, const char *errorMsg);
85
86 //////////// IUnknown
87
88 STDMETHODIMP QueryInterface(const IID& iid, void **ppv);
89 STDMETHODIMP_(ULONG) AddRef();
90 STDMETHODIMP_(ULONG) Release();
91
92 //////////// IClassFactory
93
94 STDMETHODIMP CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void * * ppvObj);
95 STDMETHODIMP LockServer(BOOL fLock);
96
97 //////////// IPersistFile
98
99 STDMETHODIMP GetClassID(LPCLSID lpClassID); // IPersist
100
101 STDMETHODIMP IsDirty();
102 STDMETHODIMP Load(LPCOLESTR lpszFileName, DWORD grfMode);
103 STDMETHODIMP Save(LPCOLESTR lpszFileName, BOOL fRemember);
104 STDMETHODIMP SaveCompleted(LPCOLESTR lpszFileName);
105 STDMETHODIMP GetCurFile(LPOLESTR *lplpszFileName);
106
107 //////////// IAVIFile
108
109 STDMETHODIMP CreateStream(PAVISTREAM *ppStream, AVISTREAMINFOW *psi); // 5
110 STDMETHODIMP EndRecord(); // 8
111 STDMETHODIMP GetStream(PAVISTREAM *ppStream, DWORD fccType, LONG lParam); // 4
112 STDMETHODIMP Info(AVIFILEINFOW *psi, LONG lSize); // 3
113
114 STDMETHODIMP Open(LPCSTR szFile, UINT mode, LPCOLESTR lpszFileName); // ???
115 STDMETHODIMP Save(LPCSTR szFile, AVICOMPRESSOPTIONS FAR *lpOptions, // ???
116 AVISAVECALLBACK lpfnCallback);
117
118 STDMETHODIMP ReadData(DWORD fcc, LPVOID lp, LONG *lpcb); // 7
119 STDMETHODIMP WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer); // 6
120 STDMETHODIMP DeleteStream(DWORD fccType, LONG lParam); // 9
121
122 //////////// IAvisynthClipInfo
123
124 int __stdcall GetError(const char** ppszMessage);
125 bool __stdcall GetParity(int n);
126 bool __stdcall IsFieldBased();
127 };
128
129 ///////////////////////////////////
130
131 class VapourSynthStream final : public IAVIStream, public IAVIStreaming {
132 public:
133
134 //////////// IUnknown
135
136 STDMETHODIMP QueryInterface(const IID& iid, void **ppv);
137 STDMETHODIMP_(ULONG) AddRef();
138 STDMETHODIMP_(ULONG) Release();
139
140 VapourSynthStream(VapourSynthFile *parentPtr, bool isAudio);
141 ~VapourSynthStream();
142
143 //////////// IAVIStream
144
145 STDMETHODIMP Create(LPARAM lParam1, LPARAM lParam2);
146 STDMETHODIMP Delete(LONG lStart, LONG lSamples);
147 STDMETHODIMP_(LONG) Info(AVISTREAMINFOW *psi, LONG lSize);
148 STDMETHODIMP_(LONG) FindSample(LONG lPos, LONG lFlags);
149 STDMETHODIMP Read(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples);
150 STDMETHODIMP ReadData(DWORD fcc, LPVOID lp, LONG *lpcb);
151 STDMETHODIMP ReadFormat(LONG lPos, LPVOID lpFormat, LONG *lpcbFormat);
152 STDMETHODIMP SetFormat(LONG lPos, LPVOID lpFormat, LONG cbFormat);
153 STDMETHODIMP Write(LONG lStart, LONG lSamples, LPVOID lpBuffer,
154 LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten,
155 LONG FAR *plBytesWritten);
156 STDMETHODIMP WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer);
157 STDMETHODIMP SetInfo(AVISTREAMINFOW *psi, LONG lSize);
158
159 //////////// IAVIStreaming
160
161 STDMETHODIMP Begin(LONG lStart, LONG lEnd, LONG lRate);
162 STDMETHODIMP End();
163
164 private:
165 std::atomic<long> m_refs;
166
167 VapourSynthFile *parent;
168 std::string sName;
169
170 //////////// internal
171
172 bool ReadFrame(void* lpBuffer, int n);
173
174 HRESULT Read2(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples);
175 };
176
177
DllMain(HANDLE hModule,ULONG ulReason,LPVOID lpReserved)178 BOOL APIENTRY DllMain(HANDLE hModule, ULONG ulReason, LPVOID lpReserved) {
179 if (ulReason == DLL_PROCESS_ATTACH) {
180 // fixme, move this where threading can't be an issue
181 vsscript_init();
182 } else if (ulReason == DLL_PROCESS_DETACH) {
183 vsscript_finalize();
184 }
185 return TRUE;
186 }
187
DllGetClassObject(const CLSID & rclsid,const IID & riid,void ** ppv)188 STDAPI DllGetClassObject(const CLSID& rclsid, const IID& riid, void **ppv) {
189
190 if (rclsid != CLSID_VapourSynth)
191 return CLASS_E_CLASSNOTAVAILABLE;
192 HRESULT hresult = VapourSynthFile::Create(rclsid, riid, ppv);
193 return hresult;
194 }
195
DllCanUnloadNow()196 STDAPI DllCanUnloadNow() {
197 return refCount ? S_FALSE : S_OK;
198 }
199
200
201 ///////////////////////////////////////////////////////////////////////////
202 //
203 // VapourSynthFile
204 //
205 ///////////////////////////////////////////////////////////////////////////
206 //////////// IClassFactory
207
CreateInstance(LPUNKNOWN pUnkOuter,REFIID riid,void ** ppvObj)208 STDMETHODIMP VapourSynthFile::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, void * * ppvObj) {
209 if (pUnkOuter)
210 return CLASS_E_NOAGGREGATION;
211 HRESULT hresult = Create(CLSID_VapourSynth, riid, ppvObj);
212 return hresult;
213 }
214
LockServer(BOOL fLock)215 STDMETHODIMP VapourSynthFile::LockServer(BOOL fLock) {
216 return S_OK;
217 }
218
219 ///////////////////////////////////////////////////
220 //////////// IPersistFile
221
GetClassID(LPCLSID lpClassID)222 STDMETHODIMP VapourSynthFile::GetClassID(LPCLSID lpClassID) { // IPersist
223 if (!lpClassID)
224 return E_POINTER;
225 *lpClassID = CLSID_VapourSynth;
226 return S_OK;
227 }
228
IsDirty()229 STDMETHODIMP VapourSynthFile::IsDirty() {
230 return S_FALSE;
231 }
232
Load(LPCOLESTR lpszFileName,DWORD grfMode)233 STDMETHODIMP VapourSynthFile::Load(LPCOLESTR lpszFileName, DWORD grfMode) {
234 return Open(utf16_to_utf8(lpszFileName).c_str(), grfMode, lpszFileName);
235 }
236
Save(LPCOLESTR lpszFileName,BOOL fRemember)237 STDMETHODIMP VapourSynthFile::Save(LPCOLESTR lpszFileName, BOOL fRemember) {
238 return E_FAIL;
239 }
240
SaveCompleted(LPCOLESTR lpszFileName)241 STDMETHODIMP VapourSynthFile::SaveCompleted(LPCOLESTR lpszFileName) {
242 return S_OK;
243 }
244
GetCurFile(LPOLESTR * lplpszFileName)245 STDMETHODIMP VapourSynthFile::GetCurFile(LPOLESTR *lplpszFileName) {
246 if (lplpszFileName)
247 *lplpszFileName = nullptr;
248 return E_FAIL;
249 }
250
251 ///////////////////////////////////////////////////
252 /////// static local
253
Create(const CLSID & rclsid,const IID & riid,void ** ppv)254 HRESULT VapourSynthFile::Create(const CLSID& rclsid, const IID& riid, void **ppv) {
255 HRESULT hresult;
256 VapourSynthFile* pAVIFileSynth = new(std::nothrow)VapourSynthFile(rclsid);
257 if (!pAVIFileSynth)
258 return E_OUTOFMEMORY;
259 hresult = pAVIFileSynth->QueryInterface(riid, ppv);
260 pAVIFileSynth->Release();
261 return hresult;
262 }
263
264 ///////////////////////////////////////////////////
265 //////////// IUnknown
266
QueryInterface(const IID & iid,void ** ppv)267 STDMETHODIMP VapourSynthFile::QueryInterface(const IID& iid, void **ppv) {
268 if (!ppv)
269 return E_POINTER;
270
271 if (iid == IID_IUnknown) {
272 *ppv = (IUnknown *)(IAVIFile *)this;
273 } else if (iid == IID_IClassFactory) {
274 *ppv = (IClassFactory *)this;
275 } else if (iid == IID_IPersist) {
276 *ppv = (IPersist *)this;
277 } else if (iid == IID_IPersistFile) {
278 *ppv = (IPersistFile *)this;
279 } else if (iid == IID_IAVIFile) {
280 *ppv = (IAVIFile *)this;
281 } else if (iid == IID_IAvisynthClipInfo) {
282 *ppv = (IAvisynthClipInfo *)this;
283 } else {
284 *ppv = nullptr;
285 return E_NOINTERFACE;
286 }
287
288 AddRef();
289
290 return S_OK;
291 }
292
STDMETHODIMP_(ULONG)293 STDMETHODIMP_(ULONG) VapourSynthFile::AddRef() {
294 const int refs = ++m_refs;
295 ++refCount;
296 return refs;
297 }
298
STDMETHODIMP_(ULONG)299 STDMETHODIMP_(ULONG) VapourSynthFile::Release() {
300 const int refs = --m_refs;
301 --refCount;
302 if (!refs)
303 delete this;
304 return refs;
305 }
306
307 ////////////////////////////////////////////////////////////////////////
308 //
309 // VapourSynthStream
310 //
311 ////////////////////////////////////////////////////////////////////////
312 //////////// IUnknown
313
314
QueryInterface(const IID & iid,void ** ppv)315 STDMETHODIMP VapourSynthStream::QueryInterface(const IID& iid, void **ppv) {
316 if (!ppv)
317 return E_POINTER;
318
319 if (iid == IID_IUnknown) {
320 *ppv = (IUnknown *)(IAVIStream *)this;
321 } else if (iid == IID_IAVIStream) {
322 *ppv = (IAVIStream *)this;
323 } else if (iid == IID_IAVIStreaming) {
324 *ppv = (IAVIStreaming *)this;
325 } else {
326 *ppv = nullptr;
327 return E_NOINTERFACE;
328 }
329
330 AddRef();
331
332 return S_OK;
333 }
334
STDMETHODIMP_(ULONG)335 STDMETHODIMP_(ULONG) VapourSynthStream::AddRef() {
336 const int refs = ++m_refs;
337 ++refCount;
338 return refs;
339 }
340
STDMETHODIMP_(ULONG)341 STDMETHODIMP_(ULONG) VapourSynthStream::Release() {
342 const int refs = --m_refs;
343 --refCount;
344 if (!refs) delete this;
345 return refs;
346 }
347
348 ////////////////////////////////////////////////////////////////////////
349 //
350 // VapourSynthFile
351 //
352 ////////////////////////////////////////////////////////////////////////
353 //////////// IAVIFile
354
CreateStream(PAVISTREAM * ppStream,AVISTREAMINFOW * psi)355 STDMETHODIMP VapourSynthFile::CreateStream(PAVISTREAM *ppStream, AVISTREAMINFOW *psi) {
356 *ppStream = nullptr;
357 return S_OK;
358 }
359
EndRecord()360 STDMETHODIMP VapourSynthFile::EndRecord() {
361 return AVIERR_READONLY;
362 }
363
Save(LPCSTR szFile,AVICOMPRESSOPTIONS FAR * lpOptions,AVISAVECALLBACK lpfnCallback)364 STDMETHODIMP VapourSynthFile::Save(LPCSTR szFile, AVICOMPRESSOPTIONS FAR *lpOptions,
365 AVISAVECALLBACK lpfnCallback) {
366 return AVIERR_READONLY;
367 }
368
ReadData(DWORD fcc,LPVOID lp,LONG * lpcb)369 STDMETHODIMP VapourSynthFile::ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) {
370 return AVIERR_NODATA;
371 }
372
WriteData(DWORD fcc,LPVOID lpBuffer,LONG cbBuffer)373 STDMETHODIMP VapourSynthFile::WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) {
374 return AVIERR_READONLY;
375 }
376
DeleteStream(DWORD fccType,LONG lParam)377 STDMETHODIMP VapourSynthFile::DeleteStream(DWORD fccType, LONG lParam) {
378 return AVIERR_READONLY;
379 }
380
381
382 ///////////////////////////////////////////////////
383 /////// local
384
VapourSynthFile(const CLSID & rclsid)385 VapourSynthFile::VapourSynthFile(const CLSID& rclsid) : num_threads(1), vsapi(nullptr), se(nullptr), enable_v210(false), node(nullptr), m_refs(0), vi(nullptr), pending_requests(0) {
386 vsapi = vsscript_getVSApi();
387 AddRef();
388 }
389
~VapourSynthFile()390 VapourSynthFile::~VapourSynthFile() {
391 Lock();
392 if (vi) {
393 while (pending_requests > 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); };
394 vi = nullptr;
395 vsapi->freeNode(node);
396 vsscript_freeScript(se);
397 }
398 Unlock();
399 }
400
Open(LPCSTR szFile,UINT mode,LPCOLESTR lpszFileName)401 STDMETHODIMP VapourSynthFile::Open(LPCSTR szFile, UINT mode, LPCOLESTR lpszFileName) {
402 if (mode & (OF_CREATE | OF_WRITE))
403 return E_FAIL;
404 szScriptName = szFile;
405 return S_OK;
406 }
407
DelayInit()408 bool VapourSynthFile::DelayInit() {
409 Lock();
410 bool result = DelayInit2();
411 Unlock();
412 return result;
413 }
414
415 static const char *ErrorScript1 = "\
416 import vapoursynth as vs\n\
417 import sys\n\
418 core = vs.get_core()\n\
419 w = 340\n\
420 h = 600\n\
421 red = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[255, 0, 0])\n\
422 green = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[0, 255, 0])\n\
423 blue = core.std.BlankClip(width=w, height=h, format=vs.RGB24, color=[0, 0, 255])\n\
424 stacked = core.std.StackHorizontal([red, green, blue])\n\
425 msg = core.text.Text(stacked, r\"\"\"";
426
427 static const char *ErrorScript2 = "\"\"\")\n\
428 final = core.resize.Bilinear(msg, format=vs.COMPATBGR32)\n\
429 final.set_output()\n";
430
DelayInit2()431 bool VapourSynthFile::DelayInit2() {
432 if (!szScriptName.empty() && !vi) {
433 if (!vsscript_evaluateFile(&se, szScriptName.c_str(), efSetWorkingDir)) {
434 node = vsscript_getOutput(se, 0);
435 if (!node) {
436 error_msg = "Couldn't get output clip, no output set?";
437 goto vpyerror;
438 }
439 vi = vsapi->getVideoInfo(node);
440 error_msg.clear();
441
442 if (vi->width == 0 || vi->height == 0 || vi->format == nullptr || vi->numFrames == 0) {
443 error_msg = "Cannot open clips with varying dimensions or format in vfw";
444 goto vpyerror;
445 }
446
447 int id = vi->format->id;
448 if (!HasSupportedFourCC(id)) {
449 error_msg = "VFW module doesn't support ";
450 error_msg += vi->format->name;
451 error_msg += " output";
452 goto vpyerror;
453 }
454
455 // set the special options hidden in global variables
456 int error;
457 VSMap *options = vsapi->createMap();
458 vsscript_getVariable(se, "enable_v210", options);
459 enable_v210 = !!vsapi->propGetInt(options, "enable_v210", 0, &error);
460 if (error)
461 enable_v210 = false;
462 vsapi->freeMap(options);
463
464 VSCoreInfo info;
465 vsapi->getCoreInfo2(vsscript_getCore(se), &info);
466 num_threads = info.numThreads;
467
468 return true;
469 } else {
470 error_msg = vsscript_getError(se);
471 vpyerror:
472 vi = nullptr;
473 vsscript_freeScript(se);
474 se = nullptr;
475 std::string error_script = ErrorScript1;
476 error_script += error_msg;
477 error_script += ErrorScript2;
478 vsscript_evaluateScript(&se, error_script.c_str(), "vfw_error.message", 0);
479 node = vsscript_getOutput(se, 0);
480 vi = vsapi->getVideoInfo(node);
481 return true;
482 }
483 } else {
484 return !!vi;
485 }
486 }
487
Lock()488 void VapourSynthFile::Lock() {
489 cs_filter_graph.lock();
490 }
491
Unlock()492 void VapourSynthFile::Unlock() {
493 cs_filter_graph.unlock();
494 }
495
496 ///////////////////////////////////////////////////
497 //////////// IAVIFile
498
Info(AVIFILEINFOW * pfi,LONG lSize)499 STDMETHODIMP VapourSynthFile::Info(AVIFILEINFOW *pfi, LONG lSize) {
500 if (!pfi)
501 return E_POINTER;
502
503 if (!DelayInit())
504 return E_FAIL;
505
506 AVIFILEINFOW afi = {};
507
508 afi.dwMaxBytesPerSec = 0;
509 afi.dwFlags = AVIFILEINFO_HASINDEX | AVIFILEINFO_ISINTERLEAVED;
510 afi.dwCaps = AVIFILECAPS_CANREAD | AVIFILECAPS_ALLKEYFRAMES | AVIFILECAPS_NOCOMPRESSION;
511
512 afi.dwStreams = 1;
513 afi.dwSuggestedBufferSize = 0;
514 afi.dwWidth = vi->width;
515 afi.dwHeight = vi->height;
516 afi.dwEditCount = 0;
517
518 afi.dwRate = int64ToIntS(vi->fpsNum ? vi->fpsNum : 1);
519 afi.dwScale = int64ToIntS(vi->fpsDen ? vi->fpsDen : 30);
520 afi.dwLength = vi->numFrames;
521
522 wcscpy(afi.szFileType, L"VapourSynth");
523
524 // Maybe should return AVIERR_BUFFERTOOSMALL for lSize < sizeof(afi)
525 memset(pfi, 0, lSize);
526 memcpy(pfi, &afi, std::min(static_cast<size_t>(lSize), sizeof(afi)));
527 return S_OK;
528 }
529
BePrintable(int ch)530 static inline char BePrintable(int ch) {
531 ch &= 0xff;
532 return isprint(ch) ? ch : '.';
533 }
534
535
GetStream(PAVISTREAM * ppStream,DWORD fccType,LONG lParam)536 STDMETHODIMP VapourSynthFile::GetStream(PAVISTREAM *ppStream, DWORD fccType, LONG lParam) {
537 VapourSynthStream *casr;
538 char fcc[5];
539
540 fcc[0] = BePrintable(fccType);
541 fcc[1] = BePrintable(fccType >> 8);
542 fcc[2] = BePrintable(fccType >> 16);
543 fcc[3] = BePrintable(fccType >> 24);
544 fcc[4] = 0;
545
546 if (!DelayInit())
547 return E_FAIL;
548
549 *ppStream = nullptr;
550
551 if (!fccType) {
552 if (lParam == 0)
553 fccType = streamtypeVIDEO;
554 }
555
556 if (lParam > 0)
557 return AVIERR_NODATA;
558
559 if (fccType == streamtypeVIDEO) {
560 if ((casr = new(std::nothrow)VapourSynthStream(this, false)) == 0)
561 return AVIERR_MEMORY;
562
563 *ppStream = (IAVIStream *)casr;
564
565 } else if (fccType == streamtypeAUDIO) {
566 return AVIERR_NODATA;
567
568 if ((casr = new(std::nothrow)VapourSynthStream(this, true)) == 0)
569 return AVIERR_MEMORY;
570 *ppStream = (IAVIStream *)casr;
571 } else
572 return AVIERR_NODATA;
573
574 return S_OK;
575 }
576
577
578 ////////////////////////////////////////////////////////////////////////
579 //////////// IAvisynthClipInfo
580
GetError(const char ** ppszMessage)581 int __stdcall VapourSynthFile::GetError(const char** ppszMessage) {
582 if (!DelayInit() && error_msg.empty())
583 error_msg = "VapourSynth: script open failed!";
584
585 if (ppszMessage)
586 *ppszMessage = error_msg.c_str();
587 return !error_msg.empty();
588 }
589
GetParity(int n)590 bool __stdcall VapourSynthFile::GetParity(int n) {
591 if (!DelayInit())
592 return false;
593 return false;
594 }
595
IsFieldBased()596 bool __stdcall VapourSynthFile::IsFieldBased() {
597 if (!DelayInit())
598 return false;
599 return false;
600 }
601
602 ////////////////////////////////////////////////////////////////////////
603 //
604 // VapourSynthStream
605 //
606 ////////////////////////////////////////////////////////////////////////
607 //////////// IAVIStreaming
608
Begin(LONG lStart,LONG lEnd,LONG lRate)609 STDMETHODIMP VapourSynthStream::Begin(LONG lStart, LONG lEnd, LONG lRate) {
610 return S_OK;
611 }
612
End()613 STDMETHODIMP VapourSynthStream::End() {
614 return S_OK;
615 }
616
617 //////////// IAVIStream
618
Create(LPARAM lParam1,LPARAM lParam2)619 STDMETHODIMP VapourSynthStream::Create(LPARAM lParam1, LPARAM lParam2) {
620 return AVIERR_READONLY;
621 }
622
Delete(LONG lStart,LONG lSamples)623 STDMETHODIMP VapourSynthStream::Delete(LONG lStart, LONG lSamples) {
624 return AVIERR_READONLY;
625 }
626
ReadData(DWORD fcc,LPVOID lp,LONG * lpcb)627 STDMETHODIMP VapourSynthStream::ReadData(DWORD fcc, LPVOID lp, LONG *lpcb) {
628 return AVIERR_NODATA;
629 }
630
SetFormat(LONG lPos,LPVOID lpFormat,LONG cbFormat)631 STDMETHODIMP VapourSynthStream::SetFormat(LONG lPos, LPVOID lpFormat, LONG cbFormat) {
632 return AVIERR_READONLY;
633 }
634
WriteData(DWORD fcc,LPVOID lpBuffer,LONG cbBuffer)635 STDMETHODIMP VapourSynthStream::WriteData(DWORD fcc, LPVOID lpBuffer, LONG cbBuffer) {
636 return AVIERR_READONLY;
637 }
638
SetInfo(AVISTREAMINFOW * psi,LONG lSize)639 STDMETHODIMP VapourSynthStream::SetInfo(AVISTREAMINFOW *psi, LONG lSize) {
640 return AVIERR_READONLY;
641 }
642
643 ////////////////////////////////////////////////////////////////////////
644 //////////// local
645
VapourSynthStream(VapourSynthFile * parentPtr,bool isAudio)646 VapourSynthStream::VapourSynthStream(VapourSynthFile *parentPtr, bool isAudio) : m_refs(0), sName("video") {
647 AddRef();
648 parent = parentPtr;
649 parent->AddRef();
650 }
651
~VapourSynthStream()652 VapourSynthStream::~VapourSynthStream() {
653 if (parent)
654 parent->Release();
655 }
656
657 ////////////////////////////////////////////////////////////////////////
658 //////////// IAVIStream
659
STDMETHODIMP_(LONG)660 STDMETHODIMP_(LONG) VapourSynthStream::Info(AVISTREAMINFOW *psi, LONG lSize) {
661 if (!psi)
662 return E_POINTER;
663
664
665 const VSVideoInfo* const vi = parent->vi;
666
667 AVISTREAMINFOW asi = {};
668 asi.fccType = streamtypeVIDEO;
669 asi.dwQuality = DWORD(-1);
670
671 int image_size = BMPSize(vi, (vi->format->id == pfYUV422P10 && parent->enable_v210));
672
673 if (!GetFourCC(vi->format->id, (vi->format->id == pfYUV422P10 && parent->enable_v210), asi.fccHandler))
674 return E_FAIL;
675
676 asi.dwScale = int64ToIntS(vi->fpsDen ? vi->fpsDen : 1);
677 asi.dwRate = int64ToIntS(vi->fpsNum ? vi->fpsNum : 30);
678 asi.dwLength = vi->numFrames;
679 asi.rcFrame.right = vi->width;
680 asi.rcFrame.bottom = vi->height;
681 asi.dwSampleSize = image_size;
682 asi.dwSuggestedBufferSize = image_size;
683 wcscpy(asi.szName, L"VapourSynth Video #1");
684
685 // Maybe should return AVIERR_BUFFERTOOSMALL for lSize < sizeof(asi)
686 memset(psi, 0, lSize);
687 memcpy(psi, &asi, std::min(static_cast<size_t>(lSize), sizeof(asi)));
688 return S_OK;
689 }
690
STDMETHODIMP_(LONG)691 STDMETHODIMP_(LONG) VapourSynthStream::FindSample(LONG lPos, LONG lFlags) {
692 if (lFlags & FIND_FORMAT)
693 return -1;
694
695 if (lFlags & FIND_FROM_START)
696 return 0;
697
698 return lPos;
699 }
700
701
702 ////////////////////////////////////////////////////////////////////////
703 //////////// local
704
frameDoneCallback(void * userData,const VSFrameRef * f,int n,VSNodeRef *,const char * errorMsg)705 void VS_CC VapourSynthFile::frameDoneCallback(void *userData, const VSFrameRef *f, int n, VSNodeRef *, const char *errorMsg) {
706 VapourSynthFile *vsfile = static_cast<VapourSynthFile *>(userData);
707 vsfile->vsapi->freeFrame(f);
708 --vsfile->pending_requests;
709 }
710
ReadFrame(void * lpBuffer,int n)711 bool VapourSynthStream::ReadFrame(void* lpBuffer, int n) {
712 const VSAPI *vsapi = parent->vsapi;
713 std::vector<char> errMsg(32 * 1024);
714 const VSFrameRef *f = vsapi->getFrame(n, parent->node, errMsg.data(), static_cast<int>(errMsg.size()));
715 VSScript *errSe = nullptr;
716 if (!f) {
717 std::string matrix;
718 if (parent->vi->format->colorFamily == cmYUV || parent->vi->format->colorFamily == cmGray || parent->vi->format->id == pfCompatYUY2)
719 matrix = ", matrix_s=\"709\"";
720
721 std::string frameErrorScript = "import vapoursynth as vs\nimport sys\ncore = vs.get_core()\n";
722 frameErrorScript += "err_script_formatid = " + std::to_string(parent->vi->format->id) + "\n";
723 frameErrorScript += "err_script_width = " + std::to_string(parent->vi->width) + "\n";
724 frameErrorScript += "err_script_height = " + std::to_string(parent->vi->height) + "\n";
725 frameErrorScript += "err_script_background = core.std.BlankClip(width=err_script_width, height=err_script_height, format=vs.RGB24)\n";
726 frameErrorScript += "err_script_clip = core.text.Text(err_script_background, r\"\"\"";
727 frameErrorScript += errMsg.data();
728 frameErrorScript += "\"\"\")\n";
729 frameErrorScript += "err_script_clip = core.resize.Bilinear(err_script_clip, format=err_script_formatid" + matrix + ")\n";
730 frameErrorScript += "err_script_clip.set_output()\n";
731
732 vsscript_evaluateScript(&errSe, frameErrorScript.c_str(), "vfw_error.message", 0);
733 VSNodeRef *node = vsscript_getOutput(errSe, 0);
734 f = vsapi->getFrame(0, node, nullptr, 0);
735 vsapi->freeNode(node);
736
737 if (!f) {
738 vsscript_freeScript(errSe);
739 return false;
740 }
741 }
742
743 const VSFormat *fi = vsapi->getFrameFormat(f);
744
745 p2p_buffer_param p = {};
746 p.width = vsapi->getFrameWidth(f, 0);
747 p.height = vsapi->getFrameHeight(f, 0);
748 p.dst[0] = lpBuffer;
749 // Used by most
750 p.dst_stride[0] = p.width * 4 * fi->bytesPerSample;
751
752 for (int plane = 0; plane < fi->numPlanes; plane++) {
753 p.src[plane] = vsapi->getReadPtr(f, plane);
754 p.src_stride[plane] = vsapi->getStride(f, plane);
755 }
756
757 if (fi->id == pfRGB24) {
758 p.packing = p2p_argb32_le;
759 for (int plane = 0; plane < 3; plane++) {
760 p.src[plane] = vsapi->getReadPtr(f, plane) + vsapi->getStride(f, plane) * (vsapi->getFrameHeight(f, plane) - 1);
761 p.src_stride[plane] = -vsapi->getStride(f, plane);
762 }
763 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
764 } else if (fi->id == pfRGB30) {
765 p.packing = p2p_rgb30_be;
766 p.dst_stride[0] = ((p.width + 63) / 64) * 256;
767 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
768 } else if (fi->id == pfRGB48) {
769 p.packing = p2p_argb64_be;
770 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
771 } else if (fi->id == pfYUV444P10) {
772 p.packing = p2p_y410_le;
773 p.dst_stride[0] = p.width * 2 * fi->bytesPerSample;
774 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
775 } else if (fi->id == pfYUV444P16) {
776 p.packing = p2p_y416_le;
777 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
778 } else if (fi->id == pfYUV422P10 && parent->enable_v210) {
779 p.packing = p2p_v210_le;
780 p.dst_stride[0] = ((16 * ((p.width + 5) / 6) + 127) & ~127);
781 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
782 } else if ((fi->id == pfYUV420P16) || (fi->id == pfYUV422P16) || (fi->id == pfYUV420P10) || (fi->id == pfYUV422P10)) {
783 switch (fi->id) {
784 case pfYUV420P10: p.packing = p2p_p010_le; break;
785 case pfYUV422P10: p.packing = p2p_p210_le; break;
786 case pfYUV420P16: p.packing = p2p_p016_le; break;
787 case pfYUV422P16: p.packing = p2p_p216_le; break;
788 }
789 p.dst_stride[0] = p.width * fi->bytesPerSample;
790 p.dst_stride[1] = p.width * fi->bytesPerSample;
791 p.dst[1] = (uint8_t *)lpBuffer + p.dst_stride[0] * p.height;
792 p2p_pack_frame(&p, P2P_ALPHA_SET_ONE);
793 } else {
794 const int stride = vsapi->getStride(f, 0);
795 const int height = vsapi->getFrameHeight(f, 0);
796 int row_size = vsapi->getFrameWidth(f, 0) * fi->bytesPerSample;
797 if (fi->numPlanes == 1) {
798 vs_bitblt(lpBuffer, (row_size + 3) & ~3, vsapi->getReadPtr(f, 0), stride, row_size, height);
799 } else if (fi->numPlanes == 3) {
800 int row_size23 = vsapi->getFrameWidth(f, 1) * fi->bytesPerSample;
801
802 int plane2 = (fi->id != pfYUV411P8 ? 2 : 1);
803 int plane3 = (fi->id != pfYUV411P8 ? 1 : 2);
804
805
806 vs_bitblt(lpBuffer, row_size, vsapi->getReadPtr(f, 0), stride, row_size, height);
807
808 vs_bitblt((uint8_t *)lpBuffer + (row_size*height),
809 row_size23, vsapi->getReadPtr(f, plane2),
810 vsapi->getStride(f, plane2), vsapi->getFrameWidth(f, plane2),
811 vsapi->getFrameHeight(f, plane2));
812
813 vs_bitblt((uint8_t *)lpBuffer + (row_size*height + vsapi->getFrameHeight(f, plane2)*row_size23),
814 row_size23, vsapi->getReadPtr(f, plane3),
815 vsapi->getStride(f, plane3), vsapi->getFrameWidth(f, plane3),
816 vsapi->getFrameHeight(f, plane3));
817 }
818 }
819
820 vsapi->freeFrame(f);
821 vsscript_freeScript(errSe);
822
823 if (!errSe) {
824 for (int i = n + 1; i < std::min<int>(n + parent->num_threads, parent->vi->numFrames); i++) {
825 ++parent->pending_requests;
826 vsapi->getFrameAsync(i, parent->node, VapourSynthFile::frameDoneCallback, static_cast<void *>(parent));
827 }
828 }
829
830 return !errSe;
831 }
832
833 ////////////////////////////////////////////////////////////////////////
834 //////////// IAVIStream
835
Read(LONG lStart,LONG lSamples,LPVOID lpBuffer,LONG cbBuffer,LONG * plBytes,LONG * plSamples)836 STDMETHODIMP VapourSynthStream::Read(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples) {
837 parent->Lock();
838 HRESULT result = Read2(lStart, lSamples, lpBuffer, cbBuffer, plBytes, plSamples);
839 parent->Unlock();
840 return result;
841 }
842
Read2(LONG lStart,LONG lSamples,LPVOID lpBuffer,LONG cbBuffer,LONG * plBytes,LONG * plSamples)843 HRESULT VapourSynthStream::Read2(LONG lStart, LONG lSamples, LPVOID lpBuffer, LONG cbBuffer, LONG *plBytes, LONG *plSamples) {
844 if (lStart >= parent->vi->numFrames) {
845 if (plSamples)
846 *plSamples = 0;
847 if (plBytes)
848 *plBytes = 0;
849 return S_OK;
850 }
851
852 int image_size = BMPSize(parent->vi, (parent->vi->format->id == pfYUV422P10 && parent->enable_v210));
853 if (plSamples)
854 *plSamples = 1;
855 if (plBytes)
856 *plBytes = image_size;
857
858 if (!lpBuffer) {
859 return S_OK;
860 } else if (cbBuffer < image_size) {
861 return AVIERR_BUFFERTOOSMALL;
862 }
863
864 if (!ReadFrame(lpBuffer, lStart))
865 return E_FAIL;
866 return S_OK;
867 }
868
ReadFormat(LONG lPos,LPVOID lpFormat,LONG * lpcbFormat)869 STDMETHODIMP VapourSynthStream::ReadFormat(LONG lPos, LPVOID lpFormat, LONG *lpcbFormat) {
870 if (!lpcbFormat)
871 return E_POINTER;
872
873 if (!lpFormat) {
874 *lpcbFormat = sizeof(BITMAPINFOHEADER);
875 return S_OK;
876 }
877
878 memset(lpFormat, 0, *lpcbFormat);
879
880 const VSVideoInfo* const vi = parent->vi;
881
882 BITMAPINFOHEADER bi = {};
883 bi.biSize = sizeof(bi);
884 bi.biWidth = vi->width;
885 bi.biHeight = vi->height;
886 bi.biPlanes = 1;
887 bi.biBitCount = BitsPerPixel(vi, (vi->format->id == pfYUV422P10 && parent->enable_v210));
888 if (!GetBiCompression(vi->format->id, (vi->format->id == pfYUV422P10 && parent->enable_v210), bi.biCompression))
889 return E_FAIL;
890
891 bi.biSizeImage = BMPSize(vi, (vi->format->id == pfYUV422P10 && parent->enable_v210));
892 *lpcbFormat = std::min<LONG>(*lpcbFormat, sizeof(bi));
893 memcpy(lpFormat, &bi, static_cast<size_t>(*lpcbFormat));
894
895 return S_OK;
896 }
897
Write(LONG lStart,LONG lSamples,LPVOID lpBuffer,LONG cbBuffer,DWORD dwFlags,LONG FAR * plSampWritten,LONG FAR * plBytesWritten)898 STDMETHODIMP VapourSynthStream::Write(LONG lStart, LONG lSamples, LPVOID lpBuffer,
899 LONG cbBuffer, DWORD dwFlags, LONG FAR *plSampWritten,
900 LONG FAR *plBytesWritten) {
901 return AVIERR_READONLY;
902 }
903
904