1 /* ***** BEGIN LICENSE BLOCK *****
2  * This file is part of openfx-io <https://github.com/MrKepzie/openfx-io>,
3  * Copyright (C) 2013-2018 INRIA
4  *
5  * openfx-io is free software: you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * openfx-io is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with openfx-io.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>
17  * ***** END LICENSE BLOCK ***** */
18 
19 /*
20  * OFX GenericWriter plugin.
21  * A base class for all OpenFX-based encoders.
22  */
23 
24 #ifndef Io_GenericWriter_h
25 #define Io_GenericWriter_h
26 
27 #include <memory>
28 #include <ofxsImageEffect.h>
29 #include <ofxsMultiPlane.h>
30 #include "IOUtility.h"
31 #include "ofxsMacros.h"
32 #include "ofxsPixelProcessor.h" // for getImageData
33 #include "ofxsCopier.h" // for copyPixels
34 
35 namespace OFX {
36 class PixelProcessorFilterBase;
37 
38 namespace IO {
39 #ifdef OFX_IO_USING_OCIO
40 class GenericOCIO;
41 #endif
42 
43 enum LayerViewsPartsEnum
44 {
45     eLayerViewsSinglePart = 0,
46     eLayerViewsSplitViews,
47     eLayerViewsSplitViewsLayers
48 };
49 
50 #define kGenericWriterViewDefault -2 // Indicates that we want to render what the host request via the render action (the default)
51 #define kGenericWriterViewAll -1 // the write will write all views when rendering view 0
52 
53 /**
54  * @brief A generic writer plugin, derive this to create a new writer for a specific file format.
55  * This class propose to handle the common stuff among writers:
56  * - common params
57  * - a way to inform the host about the colour-space of the data.
58  **/
59 class GenericWriterPlugin
60     : public OFX::MultiPlane::MultiPlaneEffect
61 {
62 public:
63 
64     GenericWriterPlugin(OfxImageEffectHandle handle,
65                         const std::vector<std::string>& extensions,
66                         bool supportsRGBA, bool supportsRGB, bool supportsXY, bool supportsAlpha);
67 
68     virtual ~GenericWriterPlugin();
69 
70     /**
71      * @brief Don't override this function, the GenericWriterPlugin class already does the rendering. The "encoding" of the frame
72      * must be done by the pure virtual function encode(...) instead.
73      * The render function also copies the image from the input clip to the output clip (only if the effect is connected downstream)
74      * in order to be able to branch this effect in the middle of an effect tree.
75      **/
76     virtual void render(const OFX::RenderArguments &args) OVERRIDE FINAL;
77 
78     /* override is identity */
79     virtual bool isIdentity(const OFX::IsIdentityArguments &args, OFX::Clip * &identityClip, double &identityTime, int& view, std::string& plane) OVERRIDE;
80 
81     /** @brief client begin sequence render function */
82     virtual void beginSequenceRender(const OFX::BeginSequenceRenderArguments &args) OVERRIDE;
83 
84     /** @brief client end sequence render function */
85     virtual void endSequenceRender(const OFX::EndSequenceRenderArguments &args) OVERRIDE;
86 
87     /**
88      * @brief Don't override this. It returns the projects region of definition.
89      **/
90     virtual bool getRegionOfDefinition(const OFX::RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
91 
92     /**
93      * @brief Don't override this. It returns the source region of definition.
94      **/
95     virtual void getRegionsOfInterest(const OFX::RegionsOfInterestArguments &args, OFX::RegionOfInterestSetter &rois) OVERRIDE FINAL;
96 
97     /**
98      * @brief Don't override this. It returns the frame range to render.
99      **/
100     virtual bool getTimeDomain(OfxRangeD &range) OVERRIDE FINAL;
101 
102     /**
103      * @brief You can override this to take actions in response to a param change.
104      * Make sure you call the base-class version of this function at the end: i.e:
105      *
106      * void MyReader::changedParam(const OFX::InstanceChangedArgs &args, const std::string &paramName) {
107      *      if (.....) {
108      *
109      *      } else if(.....) {
110      *
111      *      } else {
112      *          GenericReaderPlugin::changedParam(args,paramName);
113      *      }
114      * }
115      **/
116     virtual void changedParam(const OFX::InstanceChangedArgs &args, const std::string &paramName) OVERRIDE;
117 
118     /**
119      * @brief Overriden to handle premultiplication parameter given the input clip.
120      * Make sure you call the base class implementation if you override it.
121      **/
122     virtual void changedClip(const OFX::InstanceChangedArgs &args, const std::string &clipName) OVERRIDE;
123 
124     /**
125      * @brief Overriden to set the clips premultiplication according to the user and plug-ins wishes.
126      * It also set the output components from the output components parameter
127      **/
128     virtual void getClipPreferences(OFX::ClipPreferencesSetter &clipPreferences) OVERRIDE;
129 
130 
131     /**
132      * @brief Overriden to request the views needed to render.
133      **/
134     virtual void getFrameViewsNeeded(const OFX::FrameViewsNeededArguments& args, OFX::FrameViewsNeededSetter& frameViews) OVERRIDE FINAL;
135 
136     /**
137      * @brief Overriden to clear any OCIO cache.
138      * This function calls clearAnyCache() if you have any cache to clear.
139      **/
140     virtual void purgeCaches(void) OVERRIDE FINAL;
141 
142     /**
143      * @brief Restore any state from the parameters set
144      * Called from createInstance() and changedParam() (via outputFileChanged()), must restore the
145      * state of the Reader, such as Choice param options, data members and non-persistent param values.
146      * We don't do this in the ctor of the plug-in since we can't call virtuals yet.
147      * Any derived implementation must call GenericWriterPlugin::restoreStateFromParams() first
148      **/
149     virtual void restoreStateFromParams();
150 
151 protected:
152 
153     void setOutputComponentsParam(OFX::PixelComponentEnum components);
154 
155 
156     /**
157      * @brief Override this function to actually encode the image in the file pointed to by filename.
158      * If the file is a video-stream then you should encode the frame at the time given in parameters.
159      * You must write the decoded image into dstImg. This function should convert the  pixels from srcImg
160      * into the color-space and bitdepths of the newly created images's file.
161      * You can inform the host of the bitdepth you support in input in the describe() function.
162      * You can always skip the color-space conversion, but for all linear hosts it would produce either
163      * false colors or sub-par performances in the case the end-user has to prepend a color-space conversion
164      * effect her/himself.
165      *
166      * @param filename The output file to write to
167      * @param time The frame number
168      * @param viewName The name of the view to render
169      * @param pixelData Pointer to the start of the input image
170      * @param bounds The bounds of the pixelData buffer
171      * @param pixelAspectRatio The PAR of the source image
172      * @param pixelDataNComps The number of components per pixel in pixelData
173      * @param dstNCompsStartIndex The start index where the first component of dstNComps is to be read (in the range of pixelDataNComps)
174      * @param dstNComps The desired number of components in the written file
175      * @param rowBytes The number of bytes in a row of pixelData.
176      * The following assert should hold true:
177      * assert(((bounds.x2 - bounds.x1) * pixelDataNComps * sizeof(float)) == rowBytes);
178      *
179      * @pre The filename has been validated against the supported file extensions.
180      * You don't need to check this yourself.
181      * The source image has been correctly color-converted
182      **/
183     virtual void encode(const std::string& filename,
184                         const OfxTime time,
185                         const std::string& viewName,
186                         const float *pixelData,
187                         const OfxRectI& bounds,
188                         const float pixelAspectRatio,
189                         const int pixelDataNComps,
190                         const int dstNCompsStartIndex,
191                         const int dstNComps,
192                         const int rowBytes);
beginEncode(const std::string &,const OfxRectI &,float,const OFX::BeginSequenceRenderArguments &)193     virtual void beginEncode(const std::string& /*filename*/,
194                              const OfxRectI& /*rodPixel*/,
195                              float /*pixelAspectRatio*/,
196                              const OFX::BeginSequenceRenderArguments & /*args*/) {}
197 
endEncode(const OFX::EndSequenceRenderArguments &)198     virtual void endEncode(const OFX::EndSequenceRenderArguments & /*args*/) {}
199 
200     friend class EncodePlanesLocalData_RAII;
201     ///Used to allocate/free userdata passed to beginEncodePlanes,endEncodePlanes and encodePlane
allocateEncodePlanesUserData()202     virtual void* allocateEncodePlanesUserData() { return (void*)0; }
203 
destroyEncodePlanesUserData(void *)204     virtual void destroyEncodePlanesUserData(void* /*data*/) {}
205 
206     /**
207      * @brief When writing multiple planes, should allocate data that are shared amongst all planes
208      **/
209     virtual void beginEncodeParts(void* user_data,
210                                   const std::string& filename,
211                                   OfxTime time,
212                                   float pixelAspectRatio,
213                                   LayerViewsPartsEnum partsSplitting,
214                                   const std::map<int, std::string>& viewsToRender,
215                                   const std::list<std::string>& planes,
216                                   const bool packingRequired,
217                                   const std::vector<int>& packingMapping,
218                                   const OfxRectI& bounds);
endEncodeParts(void *)219     virtual void endEncodeParts(void* /*user_data*/) {}
220 
221     virtual void encodePart(void* user_data, const std::string& filename, const float *pixelData, int pixelDataNComps, int planeIndex, int rowBytes);
222 
223     /**
224      * @brief Should return the view index needed to render.
225      * Possible return values:
226      * -2 or kGenericWriterViewDefault: Indicates that we want to render what the host request via the render action (the default)
227      * -1 or kGenericWriterViewAll: Indicates that we want to render all views in a single file. In this case, rendering view 0 requires all views.
228      * >= 0: Indicates the view index to render
229      **/
getViewToRender()230     virtual int getViewToRender() const { return -2; }
231 
getPartsSplittingPreference()232     virtual LayerViewsPartsEnum getPartsSplittingPreference() const { return eLayerViewsSinglePart; }
233 
234     /**
235      * @brief Overload to return false if the given file extension is a video file extension or
236      * true if this is an image file extension.
237      **/
238     virtual bool isImageFile(const std::string& fileExtension) const = 0;
setOutputFrameRate(double)239     virtual void setOutputFrameRate(double /*fps*/) {}
240 
241     /**
242      * @brief Must return whether your plug-in expects an input stream to be premultiplied or unpremultiplied to encode
243      * properly into the file.
244      **/
245     virtual OFX::PreMultiplicationEnum getExpectedInputPremultiplication() const = 0;
246 
247     /**
248      * @brief To implement if you added supportsDisplayWindow = true to GenericWriterDescribe().
249      * Basically only EXR file format can handle this.
250      **/
displayWindowSupportedByFormat(const std::string &)251     virtual bool displayWindowSupportedByFormat(const std::string& /*filename*/) const { return false; }
252 
253 
254     OFX::Clip* _inputClip; //< Mantated input clip
255     OFX::Clip *_outputClip; //< Mandated output clip
256     OFX::StringParam  *_fileParam; //< The output file
257     OFX::BooleanParam  *_overwrite; //< Do we overwrite existing files?
258     OFX::ChoiceParam *_frameRange; //<The frame range type
259     OFX::IntParam* _firstFrame; //< the first frame if the frame range type is "Manual"
260     OFX::IntParam* _lastFrame; //< the last frame if the frame range type is "Manual"
261     OFX::ChoiceParam* _outputFormatType; //< the type of output format
262     OFX::ChoiceParam* _outputFormat; //< the output format to render
263     OFX::Int2DParam* _outputFormatSize;
264     OFX::DoubleParam* _outputFormatPar;
265     OFX::ChoiceParam* _premult;
266     OFX::BooleanParam* _clipToRoD;
267 
268     OFX::StringParam* _sublabel;
269     OFX::BooleanParam* _processChannels[4];
270     OFX::ChoiceParam* _outputComponents;
271     OFX::BooleanParam* _guessedParams; //!< was guessParamsFromFilename already successfully called once on this instance
272 
273 #ifdef OFX_IO_USING_OCIO
274     OFX::BooleanParam* _outputSpaceSet;
275     auto_ptr<GenericOCIO> _ocio;
276 #endif
277 
278     const std::vector<std::string>& _extensions;
279     bool _supportsRGBA;
280     bool _supportsRGB;
281     bool _supportsXY;
282     bool _supportsAlpha;
283 
284     std::vector<OFX::PixelComponentEnum> _outputComponentsTable;
285 
286 private:
287 
288 
289     class InputImagesHolder
290     {
291         std::list<const OFX::Image*> _imgs;
292         std::list<OFX::ImageMemory*> _mems;
293 
294 public:
295 
296         InputImagesHolder();
297         void addImage(const OFX::Image* img);
298         void addMemory(OFX::ImageMemory* mem);
299         ~InputImagesHolder();
300     };
301 
302     /*
303      * @brief Fetch the given plane for the given view at the given time and convert into suited color-space
304      * using OCIO if needed.
305      *
306      * If view == renderRequestedView the output image will be fetched on the output clip
307      * and written to aswell.
308      *
309      * Post-condition:
310      * - srcImgsHolder had the srcImg appended to it so it gets correctly released when it is
311      * destroyed.
312      * - tmpMemPtr is never NULL and points to either srcImg buffer or tmpMem buffer
313      * - If a color-space conversion occured, tmpMem/tmpMemPtr is non-null and tmpMem was added to srcImgsHolder
314      * so it gets correctly released upon destruction.
315      *
316      * This function MAY throw exceptions aborting the action, that is why we use the InputImagesHolder RAII style class
317      * that will properly release resources.
318      */
319     void fetchPlaneConvertAndCopy(const std::string& plane,
320                                   bool failIfNoSrcImg,
321                                   int view,
322                                   int renderRequestedView,
323                                   double time,
324                                   const OfxRectI& renderWindow,
325                                   const OfxPointD& renderScale,
326                                   OFX::FieldEnum fieldToRender,
327                                   OFX::PreMultiplicationEnum pluginExpectedPremult,
328                                   OFX::PreMultiplicationEnum userPremult,
329                                   const bool isOCIOIdentity,
330                                   const bool doAnyPacking,
331                                   const bool packingContiguous,
332                                   const std::vector<int>& packingMapping,
333                                   InputImagesHolder* srcImgsHolder,
334                                   OfxRectI* bounds,
335                                   OFX::ImageMemory** tmpMem,
336                                   const OFX::Image** inputImage,
337                                   float** tmpMemPtr,
338                                   int* rowBytes,
339                                   OFX::PixelComponentEnum* mappedComponents,
340                                   int* mappedComponentsCount);
341 
342     /**
343      * @brief Checks if the extension is supported.
344      **/
345     bool checkExtension(const std::string& filename);
346 
347     /**
348      * @brief Override if you want to do something when the output image/video file changed.
349      * You shouldn't do any strong processing as this is called on the main thread and
350      * the getRegionOfDefinition() and  decode() should open the file in a separate thread.
351      **/
352     virtual void onOutputFileChanged(const std::string& newFile, bool setColorSpace) = 0;
353 
354     /**
355      * @brief Override to clear any cache you may have.
356      **/
clearAnyCache()357     virtual void clearAnyCache() {}
358 
359     void getOutputRoD(OfxTime time, int view, OfxRectD* rod, double* par);
360 
361 protected:
362 
363     void getSelectedOutputFormat(OfxRectI* format, double* par);
364 
365     void refreshRGBAParamsFromOutputComponents();
366 
367 private:
368 
copyPixelData(const OfxRectI & renderWindow,const OFX::Image * srcImg,OFX::Image * dstImg)369     void copyPixelData(const OfxRectI &renderWindow,
370                        const OFX::Image* srcImg,
371                        OFX::Image* dstImg)
372     {
373         const void* srcPixelData;
374         OfxRectI srcBounds;
375 
376         OFX::PixelComponentEnum srcPixelComponents;
377         OFX::BitDepthEnum srcBitDepth;
378         int srcRowBytes;
379         getImageData(srcImg, &srcPixelData, &srcBounds, &srcPixelComponents, &srcBitDepth, &srcRowBytes);
380         int srcPixelComponentCount = srcImg->getPixelComponentCount();
381         void* dstPixelData;
382         OfxRectI dstBounds;
383         OFX::PixelComponentEnum dstPixelComponents;
384         OFX::BitDepthEnum dstBitDepth;
385         int dstRowBytes;
386         getImageData(dstImg, &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDepth, &dstRowBytes);
387         int dstPixelComponentCount = dstImg->getPixelComponentCount();
388         copyPixels(*this,
389                    renderWindow,
390                    srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
391                    dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
392     }
393 
copyPixelData(const OfxRectI & renderWindow,const void * srcPixelData,const OfxRectI & srcBounds,OFX::PixelComponentEnum srcPixelComponents,int srcPixelComponentCount,OFX::BitDepthEnum srcBitDepth,int srcRowBytes,OFX::Image * dstImg)394     void copyPixelData(const OfxRectI &renderWindow,
395                        const void *srcPixelData,
396                        const OfxRectI& srcBounds,
397                        OFX::PixelComponentEnum srcPixelComponents,
398                        int srcPixelComponentCount,
399                        OFX::BitDepthEnum srcBitDepth,
400                        int srcRowBytes,
401                        OFX::Image* dstImg)
402     {
403         void* dstPixelData;
404         OfxRectI dstBounds;
405 
406         OFX::PixelComponentEnum dstPixelComponents;
407         OFX::BitDepthEnum dstBitDepth;
408         int dstRowBytes;
409         getImageData(dstImg, &dstPixelData, &dstBounds, &dstPixelComponents, &dstBitDepth, &dstRowBytes);
410         int dstPixelComponentCount = dstImg->getPixelComponentCount();
411         copyPixels(*this,
412                    renderWindow,
413                    srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
414                    dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
415     }
416 
copyPixelData(const OfxRectI & renderWindow,const OFX::Image * srcImg,void * dstPixelData,const OfxRectI & dstBounds,OFX::PixelComponentEnum dstPixelComponents,int dstPixelComponentCount,OFX::BitDepthEnum dstBitDepth,int dstRowBytes)417     void copyPixelData(const OfxRectI &renderWindow,
418                        const OFX::Image* srcImg,
419                        void *dstPixelData,
420                        const OfxRectI& dstBounds,
421                        OFX::PixelComponentEnum dstPixelComponents,
422                        int dstPixelComponentCount,
423                        OFX::BitDepthEnum dstBitDepth,
424                        int dstRowBytes)
425     {
426         const void* srcPixelData;
427         OfxRectI srcBounds;
428 
429         OFX::PixelComponentEnum srcPixelComponents;
430         OFX::BitDepthEnum srcBitDepth;
431         int srcRowBytes;
432         getImageData(srcImg, &srcPixelData, &srcBounds, &srcPixelComponents, &srcBitDepth, &srcRowBytes);
433         int srcPixelComponentCount = srcImg->getPixelComponentCount();
434         copyPixels(*this,
435                    renderWindow,
436                    srcPixelData, srcBounds, srcPixelComponents, srcPixelComponentCount, srcBitDepth, srcRowBytes,
437                    dstPixelData, dstBounds, dstPixelComponents, dstPixelComponentCount, dstBitDepth, dstRowBytes);
438     }
439 
440     void packPixelBuffer(const OfxRectI& renderWindow,
441                          const void *srcPixelData,
442                          const OfxRectI& bounds,
443                          OFX::BitDepthEnum bitDepth,
444                          int srcRowBytes,
445                          OFX::PixelComponentEnum srcPixelComponents,
446                          const std::vector<int>& channelsMapping, //maps dst channels to input channels
447                          int dstRowBytes,
448                          void* dstPixelData);
449 
450     void interleavePixelBuffers(const OfxRectI& renderWindow,
451                                 const void *srcPixelData,
452                                 const OfxRectI& bounds,
453                                 const OFX::PixelComponentEnum srcPixelComponents,
454                                 const int srcPixelComponentCount,
455                                 const int srcNCompsStartIndex,
456                                 const int desiredSrcNComps,
457                                 const OFX::BitDepthEnum bitDepth,
458                                 const int srcRowBytes,
459                                 const OfxRectI& dstBounds,
460                                 const OFX::PixelComponentEnum dstPixelComponents,
461                                 const int dstPixelComponentStartIndex,
462                                 const int dstPixelComponentCount,
463                                 const int dstRowBytes,
464                                 void* dstPixelData);
465 
466     void unPremultPixelData(const OfxRectI &renderWindow,
467                             const void *srcPixelData,
468                             const OfxRectI& srcBounds,
469                             OFX::PixelComponentEnum srcPixelComponents,
470                             int srcPixelComponentCount,
471                             OFX::BitDepthEnum srcPixelDepth,
472                             int srcRowBytes,
473                             void *dstPixelData,
474                             const OfxRectI& dstBounds,
475                             OFX::PixelComponentEnum dstPixelComponents,
476                             int dstPixelComponentCount,
477                             OFX::BitDepthEnum dstBitDepth,
478                             int dstRowBytes);
479 
480     void premultPixelData(const OfxRectI &renderWindow,
481                           const void *srcPixelData,
482                           const OfxRectI& srcBounds,
483                           OFX::PixelComponentEnum srcPixelComponents,
484                           int srcPixelComponentCount,
485                           OFX::BitDepthEnum srcPixelDepth,
486                           int srcRowBytes,
487                           void *dstPixelData,
488                           const OfxRectI& dstBounds,
489                           OFX::PixelComponentEnum dstPixelComponents,
490                           int dstPixelComponentCount,
491                           OFX::BitDepthEnum dstBitDepth,
492                           int dstRowBytes);
493 
494     void getPackingOptions(bool *allCheckboxHidden, std::vector<int>* packingMapping) const;
495 
496     void outputFileChanged(OFX::InstanceChangeReason reason, bool restoreExistingWriter, bool throwErrors);
497 };
498 
499 class EncodePlanesLocalData_RAII
500 {
501     GenericWriterPlugin* _w;
502     void* data;
503 
504 public:
505 
EncodePlanesLocalData_RAII(GenericWriterPlugin * w)506     EncodePlanesLocalData_RAII(GenericWriterPlugin* w)
507         : _w(w)
508         , data(NULL)
509     {
510         data = w->allocateEncodePlanesUserData();
511     }
512 
~EncodePlanesLocalData_RAII()513     ~EncodePlanesLocalData_RAII()
514     {
515         _w->destroyEncodePlanesUserData(data);
516     }
517 
getData()518     void* getData() const { return data; }
519 };
520 
521 void GenericWriterDescribe(OFX::ImageEffectDescriptor &desc,
522                            OFX::RenderSafetyEnum safety,
523                            const std::vector<std::string>& extensions,
524                            int evaluation,
525                            bool isMultiPlanar,
526                            bool isMultiView);
527 
528 OFX::PageParamDescriptor* GenericWriterDescribeInContextBegin(OFX::ImageEffectDescriptor &desc,
529                                                               OFX::ContextEnum context,
530                                                               bool supportsRGBA,
531                                                               bool supportsRGB,
532                                                               bool supportsXY,
533                                                               bool supportsAlpha,
534                                                               const char* inputSpaceNameDefault,
535                                                               const char* outputSpaceNameDefault,
536                                                               bool supportsDisplayWindow);
537 
538 void GenericWriterDescribeInContextEnd(OFX::ImageEffectDescriptor &desc,
539                                        OFX::ContextEnum context,
540                                        OFX::PageParamDescriptor* defaultPage);
541 
542 // the load() member has to be provided, and it should fill the _extensions list of valid file extensions
543 #define mDeclareWriterPluginFactory(CLASS, UNLOADFUNCDEF, ISVIDEOSTREAM) \
544     class CLASS \
545         : public OFX::PluginFactoryHelper<CLASS>                       \
546     {                                                                     \
547 public:                                                                \
548         CLASS(const std::string & id, unsigned int verMaj, unsigned int verMin) \
549             : OFX::PluginFactoryHelper<CLASS>(id, verMaj, verMin) {} \
550         virtual void load();                                   \
551         virtual void unload() UNLOADFUNCDEF;                               \
552         virtual OFX::ImageEffect* createInstance(OfxImageEffectHandle handle, OFX::ContextEnum context); \
553         bool isVideoStreamPlugin() const { return ISVIDEOSTREAM; }  \
554         virtual void describe(OFX::ImageEffectDescriptor & desc);      \
555         virtual void describeInContext(OFX::ImageEffectDescriptor & desc, OFX::ContextEnum context); \
556 private: \
557         std::vector<std::string> _extensions; \
558     };
559 } // namespace IO
560 } // namespace OFX
561 
562 #endif // ifndef Io_GenericWriter_h
563