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 RunScript plugin.
21  * Run a shell script.
22  */
23 
24 #if !( defined(_WIN32) || defined(__WIN32__) || defined(WIN32 ) ) // Sorry, MS Windows users, this plugin won't work for you
25 
26 #include "RunScript.h"
27 #include "ofxsMacros.h"
28 
29 #include <cfloat> // DBL_MAX
30 #undef DEBUG
31 #ifdef DEBUG
32 #include <iostream>
33 #define DBG(x) x
34 #else
35 #define DBG(x) (void)0
36 #endif
37 #include <string>
38 #include <sstream>
39 #include <cstdlib>
40 #include <unistd.h>
41 #include <sys/types.h>
42 #include <sys/stat.h>
43 #include <stdio.h> // for snprintf & _snprintf
44 #if defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
45 #  include <windows.h>
46 #  if defined(_MSC_VER) && _MSC_VER < 1900
47 #    define snprintf _snprintf
48 #  endif
49 #endif // defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
50 
51 #include "ofxsCopier.h"
52 
53 #include "pstream.h"
54 
55 using namespace OFX;
56 
57 using std::string;
58 using std::vector;
59 
60 OFXS_NAMESPACE_ANONYMOUS_ENTER
61 
62 #define kPluginName "RunScriptOFX"
63 #define kPluginGrouping "Image"
64 #define kPluginDescription \
65     "Run a script with the given arguments.\n" \
66     "This is mostly useful to execute an external program on a set of input images files, which outputs image files.\n" \
67     "Writers should be connected to each input, so that the image files are written before running the script, and the output of this node should be fed into one or more Readers, which read the images written by the script.\n" \
68     "\n" \
69     "Sample section of a node graph which uses RunScript:\n" \
70     "\n" \
71     "```\n" \
72     "              ...\n" \
73     "               ^\n" \
74     "               |\n" \
75     "Write([Project]/scriptinput#####.png)\n" \
76     "               ^\n" \
77     "               |\n" \
78     "RunScript1(processes [Project]/scriptinput#####.png, output is [Project]/scriptoutput#####.png)\n" \
79     "               ^\n" \
80     "               |\n" \
81     "Read([Project]/scriptoutput#####.png, set the frame range manually)\n" \
82     "               ^\n" \
83     "               |\n" \
84     "RunScript2(deletes temporary files [Project]/scriptinput#####.png and [Project]/scriptoutput#####.png, optional)\n" \
85     "               ^\n" \
86     "               |\n" \
87     "              ...\n" \
88     "```\n" \
89     "Keep in mind that the input and output files are never removed in the above graph.\n" \
90     "The output of RunScript is a copy of its first input.\n" \
91     "\n" \
92     "Each argument may be:\n" \
93     "\n" \
94     "- A filename (RunScript1 and RunScript2 in the example above should have `[Project]/scriptinput#####.png` and `[Project]/scriptoutput#####.png` as filename parameters 1 and 2)\n" \
95     "- A floating-point value (which can be linked to any plugin)\n" \
96     "- An integer\n" \
97     "- A string\n" \
98     "\n" \
99     "Under Unix, the script should begin with a traditional shebang line, e.g. '#!/bin/sh' or '#!/usr/bin/env python'\n" \
100     "The arguments can be accessed as usual from the script (in a Unix shell-script, argument 1 would be accessed as \"$1\" - use double quotes to avoid problems with spaces).\n" \
101     "For example, the script in RunScript2 in the above example would be:\n" \
102     "\n" \
103     "```\n" \
104     "#!/bin/sh\n" \
105     "rm \"$1\" \"$2\"\n" \
106     "```\n" \
107     "\n" \
108     "This plugin uses pstream (http://pstreams.sourceforge.net), which is distributed under the Boost Software License, Version 1.0.\n"
109 
110 #define kPluginIdentifier "fr.inria.openfx.RunScript"
111 #define kPluginVersionMajor 1 // Incrementing this number means that you have broken backwards compatibility of the plug-in.
112 #define kPluginVersionMinor 0 // Increment this when you have fixed a bug or made it faster.
113 
114 #define kSupportsTiles 0
115 #define kSupportsMultiResolution 0
116 #define kSupportsRenderScale 0
117 #define kRenderThreadSafety eRenderInstanceSafe
118 
119 #define kRunScriptPluginSourceClipCount 10
120 #define kRunScriptPluginArgumentsCount 10
121 
122 #define kGroupRunScriptPlugin                   "scriptParameters"
123 #define kGroupRunScriptPluginLabel              "Script Parameters"
124 #define kGroupRunScriptPluginHint               "The list of command-line parameters passed to the script."
125 
126 #define kParamCount                   "paramCount"
127 #define kParamCountLabel              "Number of Parameters"
128 
129 #define kParamType                    "type"
130 #define kParamTypeLabel               "Type of Parameter "
131 
132 #define kParamTypeFilenameName  "filename"
133 #define kParamTypeFilenameLabel "File Name"
134 #define kParamTypeFilenameHint  "A constant or animated string containing a filename.\nIf the string contains hashes (like ####) or a printf token (like %04d), they will be replaced by the frame number, and if it contains %v or %V, it will be replaced by the view ID (\"l\" or \"r\" for %v, \"left\" or \"right\" for %V).\nThis is usually linked to the output filename of an upstream Writer node, or to the input filename of a downstream Reader node."
135 #define kParamTypeStringName          "string"
136 #define kParamTypeStringLabel         "String"
137 #define kParamTypeStringHint          "A string (or sequence of characters)."
138 #define kParamTypeDoubleName          "double"
139 #define kParamTypeDoubleLabel         "Floating Point"
140 #define kParamTypeDoubleHint          "A floating point numerical value."
141 #define kParamTypeIntName             "integer"
142 #define kParamTypeIntLabel            "Integer"
143 #define kParamTypeIntHint             "An integer numerical value."
144 
145 #define kNukeWarnTcl "On Nuke, the characters '$', '[' ']' must be preceded with a backslash (as '\\$', '\\[', '\\]') to avoid TCL variable and expression substitution."
146 
147 #define kParamScript                  "script"
148 #define kParamScriptLabel             "Script"
149 #define kParamScriptHint \
150     "Contents of the script. Under Unix, the script should begin with a traditional shebang line, e.g. '#!/bin/sh' or '#!/usr/bin/env python'\n" \
151     "The arguments can be accessed as usual from the script (in a Unix shell-script, argument 1 would be accessed as \"$1\" - use double quotes to avoid problems with spaces)."
152 
153 #define kParamValidate                  "validate"
154 #define kParamValidateLabel             "Validate"
155 #define kParamValidateHint              "Validate the script contents and execute it on next render. This locks the script and all its parameters."
156 
157 enum ERunScriptPluginParamType
158 {
159     eRunScriptPluginParamTypeFilename = 0,
160     eRunScriptPluginParamTypeString,
161     eRunScriptPluginParamTypeDouble,
162     eRunScriptPluginParamTypeInteger
163 };
164 
165 static
166 string
unsignedToString(unsigned i)167 unsignedToString(unsigned i)
168 {
169     if (i == 0) {
170         return "0";
171     }
172     string nb;
173     for (unsigned j = i; j != 0; j /= 10) {
174         nb = (char)( '0' + (j % 10) ) + nb;
175     }
176 
177     return nb;
178 }
179 
180 ////////////////////////////////////////////////////////////////////////////////
181 /** @brief The plugin that does our work */
182 class RunScriptPlugin
183     : public ImageEffect
184 {
185 public:
186     /** @brief ctor */
187     RunScriptPlugin(OfxImageEffectHandle handle);
188 
189     /* Override the render */
190     virtual void render(const RenderArguments &args) OVERRIDE FINAL;
191 
192     /* override is identity */
isIdentity(const IsIdentityArguments &,Clip * &,double &,int &,std::string &)193     virtual bool isIdentity(const IsIdentityArguments & /*args*/,
194                             Clip * & /*identityClip*/,
195                             double & /*identityTime*/, int& /*view*/, std::string& /*plane*/) OVERRIDE FINAL
196     {
197         return false;
198     }
199 
200     /* override changedParam */
201     virtual void changedParam(const InstanceChangedArgs &args, const string &paramName) OVERRIDE FINAL;
202 
203     // override the rod call
204     virtual bool getRegionOfDefinition(const RegionOfDefinitionArguments &args, OfxRectD &rod) OVERRIDE FINAL;
205 
206     // override the roi call
207     virtual void getRegionsOfInterest(const RegionsOfInterestArguments &args, RegionOfInterestSetter &rois) OVERRIDE FINAL;
208 
209     /** @brief The sync private data action, called when the effect needs to sync any private data to persistent parameters */
syncPrivateData(void)210     virtual void syncPrivateData(void) OVERRIDE FINAL
211     {
212         updateVisibility();
213     }
214 
215 private:
216     void updateVisibility(void);
217 
218 private:
219     Clip *_srcClip[kRunScriptPluginSourceClipCount];
220     Clip *_dstClip;
221     IntParam *_param_count;
222     ChoiceParam *_type[kRunScriptPluginArgumentsCount];
223     StringParam *_filename[kRunScriptPluginArgumentsCount];
224     StringParam *_string[kRunScriptPluginArgumentsCount];
225     DoubleParam *_double[kRunScriptPluginArgumentsCount];
226     IntParam *_int[kRunScriptPluginArgumentsCount];
227     StringParam *_script;
228     BooleanParam *_validate;
229 };
230 
RunScriptPlugin(OfxImageEffectHandle handle)231 RunScriptPlugin::RunScriptPlugin(OfxImageEffectHandle handle)
232     : ImageEffect(handle)
233 {
234     if (getContext() != eContextGenerator) {
235         for (int i = 0; i < kRunScriptPluginSourceClipCount; ++i) {
236             if ( (i == 0) && (getContext() == eContextFilter) ) {
237                 _srcClip[i] = fetchClip(kOfxImageEffectSimpleSourceClipName);
238             } else {
239                 const string istr = unsignedToString(i + 1);
240                 _srcClip[i] = fetchClip(istr);
241             }
242             assert(_srcClip[i]);
243         }
244     }
245     _dstClip = fetchClip(kOfxImageEffectOutputClipName);
246     assert(_dstClip);
247 
248     _param_count = fetchIntParam(kParamCount);
249 
250     for (int i = 0; i < kRunScriptPluginArgumentsCount; ++i) {
251         const string istr = unsignedToString(i + 1);
252         _type[i] = fetchChoiceParam(kParamType + istr);
253         _filename[i] = fetchStringParam(kParamTypeFilenameName + istr);
254         _string[i] = fetchStringParam(kParamTypeStringName + istr);
255         _double[i] = fetchDoubleParam(kParamTypeDoubleName + istr);
256         _int[i] = fetchIntParam(kParamTypeIntName + istr);
257         assert(_type[i] && _filename[i] && _string[i] && _double[i] && _int[i]);
258     }
259     _script = fetchStringParam(kParamScript);
260     _validate = fetchBooleanParam(kParamValidate);
261     assert(_script && _validate);
262 
263     // finally
264     syncPrivateData();
265 }
266 
267 void
render(const RenderArguments & args)268 RunScriptPlugin::render(const RenderArguments &args)
269 {
270     DBG(std::cout << "rendering time " << args.time << " scale " << args.renderScale.x << ',' << args.renderScale.y << " window " << args.renderWindow.x1 << ',' << args.renderWindow.y1 << " - " << args.renderWindow.x2 << ',' << args.renderWindow.y2 << " field " << (int)args.fieldToRender << " view " << args.renderView << std::endl);
271 
272     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
273         throwSuiteStatusException(kOfxStatFailed);
274 
275         return;
276     }
277 
278     bool validated;
279     _validate->getValue(validated);
280     if (!validated) {
281         setPersistentMessage(Message::eMessageError, "", "Validate the script before rendering/running.");
282         throwSuiteStatusException(kOfxStatFailed);
283 
284         return;
285     }
286 
287     // fetch images corresponding to all connected inputs,
288     // since it may trigger render actions upstream
289     for (int i = 0; i < kRunScriptPluginSourceClipCount; ++i) {
290         if ( _srcClip[i]->isConnected() ) {
291             auto_ptr<const Image> srcImg( _srcClip[i]->fetchImage(args.time) );
292             if ( !srcImg.get() ) {
293                 throwSuiteStatusException(kOfxStatFailed);
294 
295                 return;
296             }
297             if ( (srcImg->getRenderScale().x != args.renderScale.x) ||
298                  ( srcImg->getRenderScale().y != args.renderScale.y) ||
299                  ( srcImg->getField() != args.fieldToRender) ) {
300                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
301                 throwSuiteStatusException(kOfxStatFailed);
302 
303                 return;
304             }
305         }
306     }
307 
308     // We must at least fetch the output image, even if we don't touch it,
309     // or the host may think we couldn't render.
310     // Nuke executes hundreds of render() if we don't.
311     if (!_dstClip) {
312         throwSuiteStatusException(kOfxStatFailed);
313 
314         return;
315     }
316     assert(_dstClip);
317     auto_ptr<Image> dstImg( _dstClip->fetchImage(args.time) );
318     if ( !dstImg.get() ) {
319         throwSuiteStatusException(kOfxStatFailed);
320 
321         return;
322     }
323     if ( (dstImg->getRenderScale().x != args.renderScale.x) ||
324          ( dstImg->getRenderScale().y != args.renderScale.y) ||
325          ( dstImg->getField() != args.fieldToRender) ) {
326         setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
327         throwSuiteStatusException(kOfxStatFailed);
328 
329         return;
330     }
331 
332     // create the script
333     char scriptname[] = "/tmp/runscriptXXXXXX";
334     // Coverity suggests to call umask here for compatibility with POSIX<2008 systems,
335     // but umask affects the whole process. We prefer to ignore this.
336     // coverity[secure_temp]
337     int fd = mkstemp(scriptname); // modifies template
338     if (fd < 0) {
339         throwSuiteStatusException(kOfxStatFailed);
340 
341         return;
342     }
343     string script;
344     _script->getValue(script);
345     ssize_t s = write( fd, script.c_str(), script.size() );
346     close(fd);
347     if (s < 0) {
348         throwSuiteStatusException(kOfxStatFailed);
349 
350         return;
351     }
352 
353     // make the script executable
354     int stat = chmod(scriptname, S_IRWXU);
355     if (stat != 0) {
356         throwSuiteStatusException(kOfxStatFailed);
357 
358         return;
359     }
360 
361     // build the command-line
362     vector<string> argv;
363     argv.push_back(scriptname);
364 
365     int param_count;
366     _param_count->getValue(param_count);
367 
368     char name[256];
369     for (int i = 0; i < param_count; ++i) {
370         int t_int;
371         _type[i]->getValue(t_int);
372         ERunScriptPluginParamType t = (ERunScriptPluginParamType)t_int;
373         ValueParam *p = NULL;
374         switch (t) {
375         case eRunScriptPluginParamTypeFilename: {
376             string s;
377             _filename[i]->getValue(s);
378             p = _filename[i];
379             DBG(std::cout << p->getName() << "=" << s);
380             argv.push_back(s);
381             break;
382         }
383         case eRunScriptPluginParamTypeString: {
384             string s;
385             _string[i]->getValue(s);
386             p = _string[i];
387             DBG(std::cout << p->getName() << "=" << s);
388             argv.push_back(s);
389             break;
390         }
391         case eRunScriptPluginParamTypeDouble: {
392             double v;
393             _double[i]->getValue(v);
394             p = _double[i];
395             DBG(std::cout << p->getName() << "=" << v);
396             snprintf(name, sizeof(name), "%g", v);
397             argv.push_back(name);
398             break;
399         }
400         case eRunScriptPluginParamTypeInteger: {
401             int v;
402             _int[i]->getValue(v);
403             p = _int[i];
404             DBG(std::cout << p->getName() << "=" << v);
405             snprintf(name, sizeof(name), "%d", v);
406             argv.push_back(name);
407             break;
408         }
409         }
410         if (p) {
411             DBG( std::cout << "; IsAnimating=" << (p->getIsAnimating() ? "true" : "false") );
412             DBG( std::cout << "; IsAutoKeying=" << (p->getIsAutoKeying() ? "true" : "false") );
413             DBG( std::cout << "; NumKeys=" << p->getNumKeys() );
414         }
415         DBG(std::cout << std::endl);
416     }
417 
418     // execute the script
419     vector<string> errors;
420     redi::ipstream in(scriptname, argv, redi::pstreambuf::pstderr | redi::pstreambuf::pstderr);
421     string errmsg;
422     while ( std::getline(in, errmsg) ) {
423         errors.push_back(errmsg);
424         DBG(std::cout << "output: " << errmsg << std::endl);
425     }
426 
427     // remove the script
428     (void)unlink(scriptname);
429 
430     // now copy the first input to output
431 
432     if ( _dstClip->isConnected() ) {
433         auto_ptr<Image> dstImg( _dstClip->fetchImage(args.time) );
434         if ( !dstImg.get() ) {
435             throwSuiteStatusException(kOfxStatFailed);
436 
437             return;
438         }
439         if ( (dstImg->getRenderScale().x != args.renderScale.x) ||
440              ( dstImg->getRenderScale().y != args.renderScale.y) ||
441              ( dstImg->getField() != args.fieldToRender) ) {
442             setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
443             throwSuiteStatusException(kOfxStatFailed);
444 
445             return;
446         }
447 
448         auto_ptr<const Image> srcImg( _srcClip[0]->fetchImage(args.time) );
449 
450         if ( !srcImg.get() ) {
451             // fill output with black
452             fillBlack( *this, args.renderWindow, dstImg.get() );
453         } else {
454             if ( (srcImg->getRenderScale().x != args.renderScale.x) ||
455                  ( srcImg->getRenderScale().y != args.renderScale.y) ||
456                  ( srcImg->getField() != args.fieldToRender) ) {
457                 setPersistentMessage(Message::eMessageError, "", "OFX Host gave image with wrong scale or field properties");
458                 throwSuiteStatusException(kOfxStatFailed);
459 
460                 return;
461             }
462 
463             // copy the source image (the writer is a no-op)
464             copyPixels( *this, args.renderWindow, srcImg.get(), dstImg.get() );
465         }
466     }
467 } // RunScriptPlugin::render
468 
469 void
changedParam(const InstanceChangedArgs & args,const string & paramName)470 RunScriptPlugin::changedParam(const InstanceChangedArgs &args,
471                               const string &paramName)
472 {
473     DBG(std::cout << "changed param " << paramName << " at time " << args.time << " reason = " << (int)args.reason <<  std::endl);
474 
475     // must clear persistent message, or render() is not called by Nuke after an error
476     clearPersistentMessage();
477 
478     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
479         throwSuiteStatusException(kOfxStatFailed);
480 
481         return;
482     }
483 
484     int param_count;
485     _param_count->getValue(param_count);
486 
487     if (paramName == kParamCount) {
488         // update the parameters visibility
489         updateVisibility();
490     } else if (paramName == kParamValidate) {
491         bool validated;
492         _validate->getValue(validated);
493         _param_count->setEnabled(!validated);
494         _param_count->setEvaluateOnChange(validated);
495         for (int i = 0; i < param_count; ++i) {
496             _type[i]->setEnabled(!validated);
497             _type[i]->setEvaluateOnChange(validated);
498             _filename[i]->setEnabled(!validated);
499             _filename[i]->setEvaluateOnChange(validated);
500             _string[i]->setEnabled(!validated);
501             _string[i]->setEvaluateOnChange(validated);
502             _double[i]->setEnabled(!validated);
503             _double[i]->setEvaluateOnChange(validated);
504             _int[i]->setEnabled(!validated);
505             _int[i]->setEvaluateOnChange(validated);
506         }
507         _script->setEnabled(!validated);
508         _script->setEvaluateOnChange(validated);
509         clearPersistentMessage();
510     } else {
511         for (int i = 0; i < param_count; ++i) {
512             if ( ( paramName == _type[i]->getName() ) && (args.reason == eChangeUserEdit) ) {
513                 int t_int;
514                 _type[i]->getValue(t_int);
515                 ERunScriptPluginParamType t = (ERunScriptPluginParamType)t_int;
516                 _filename[i]->setIsSecretAndDisabled(t != eRunScriptPluginParamTypeFilename);
517                 _string[i]->setIsSecretAndDisabled(t != eRunScriptPluginParamTypeString);
518                 _double[i]->setIsSecretAndDisabled(t != eRunScriptPluginParamTypeDouble);
519                 _int[i]->setIsSecretAndDisabled(t != eRunScriptPluginParamTypeInteger);
520             }
521         }
522     }
523 
524     for (int i = 0; i < param_count; ++i) {
525         int t_int;
526         _type[i]->getValue(t_int);
527         ERunScriptPluginParamType t = (ERunScriptPluginParamType)t_int;
528         ValueParam *p = NULL;
529         switch (t) {
530         case eRunScriptPluginParamTypeFilename: {
531             string s;
532             _filename[i]->getValue(s);
533             p = _filename[i];
534             DBG(std::cout << p->getName() << "=" << s);
535             break;
536         }
537         case eRunScriptPluginParamTypeString: {
538             string s;
539             _string[i]->getValue(s);
540             p = _string[i];
541             DBG(std::cout << p->getName() << "=" << s);
542             break;
543         }
544         case eRunScriptPluginParamTypeDouble: {
545             double v;
546             _double[i]->getValue(v);
547             p = _double[i];
548             DBG(std::cout << p->getName() << "=" << v);
549             break;
550         }
551         case eRunScriptPluginParamTypeInteger: {
552             int v;
553             _int[i]->getValue(v);
554             p = _int[i];
555             DBG(std::cout << p->getName() << "=" << v);
556             break;
557         }
558         }
559         if (p) {
560             DBG( std::cout << "; IsAnimating=" << (p->getIsAnimating() ? "true" : "false") );
561             DBG( std::cout << "; IsAutoKeying=" << (p->getIsAutoKeying() ? "true" : "false") );
562             DBG( std::cout << "; NumKeys=" << p->getNumKeys() );
563         }
564         DBG(std::cout << std::endl);
565     }
566 } // RunScriptPlugin::changedParam
567 
568 void
updateVisibility(void)569 RunScriptPlugin::updateVisibility(void)
570 {
571     // Due to a bug in Nuke, all visibility changes have to be done after instance creation.
572     // It is not possible in Nuke to show a parameter that was set as secret/hidden in describeInContext()
573 
574     int param_count;
575 
576     _param_count->getValue(param_count);
577 
578     bool validated;
579     _validate->getValue(validated);
580 
581     _param_count->setEnabled(!validated);
582     _param_count->setEvaluateOnChange(validated);
583     for (int i = 0; i < kRunScriptPluginArgumentsCount; ++i) {
584         if (i >= param_count) {
585             _type[i]->setIsSecret(true);
586             _filename[i]->setIsSecret(true);
587             _string[i]->setIsSecret(true);
588             _double[i]->setIsSecret(true);
589             _int[i]->setIsSecret(true);
590         } else {
591             _type[i]->setIsSecret(false);
592             int t_int;
593             _type[i]->getValue(t_int);
594             ERunScriptPluginParamType t = (ERunScriptPluginParamType)t_int;
595             _filename[i]->setIsSecret(t != eRunScriptPluginParamTypeFilename);
596             _string[i]->setIsSecret(t != eRunScriptPluginParamTypeString);
597             _double[i]->setIsSecret(t != eRunScriptPluginParamTypeDouble);
598             _int[i]->setIsSecret(t != eRunScriptPluginParamTypeInteger);
599         }
600         _type[i]->setEnabled(!validated);
601         _type[i]->setEvaluateOnChange(validated);
602         _filename[i]->setEnabled(!validated);
603         _filename[i]->setEvaluateOnChange(validated);
604         _string[i]->setEnabled(!validated);
605         _string[i]->setEvaluateOnChange(validated);
606         _double[i]->setEnabled(!validated);
607         _double[i]->setEvaluateOnChange(validated);
608         _int[i]->setEnabled(!validated);
609         _int[i]->setEvaluateOnChange(validated);
610     }
611     _script->setEnabled(!validated);
612     _script->setEvaluateOnChange(validated);
613 }
614 
615 // override the roi call
616 void
getRegionsOfInterest(const RegionsOfInterestArguments & args,RegionOfInterestSetter & rois)617 RunScriptPlugin::getRegionsOfInterest(const RegionsOfInterestArguments &args,
618                                       RegionOfInterestSetter &rois)
619 {
620     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
621         throwSuiteStatusException(kOfxStatFailed);
622 
623         return;
624     }
625 
626     if (!kSupportsTiles) {
627         // The effect requires full images to render any region
628         for (int i = 0; i < kRunScriptPluginSourceClipCount; ++i) {
629             OfxRectD srcRoI;
630 
631             if ( _srcClip[i] && _srcClip[i]->isConnected() ) {
632                 srcRoI = _srcClip[i]->getRegionOfDefinition(args.time);
633                 rois.setRegionOfInterest(*_srcClip[i], srcRoI);
634             }
635         }
636     }
637 }
638 
639 bool
getRegionOfDefinition(const RegionOfDefinitionArguments & args,OfxRectD &)640 RunScriptPlugin::getRegionOfDefinition(const RegionOfDefinitionArguments &args,
641                                        OfxRectD & /*rod*/)
642 {
643     if ( !kSupportsRenderScale && ( (args.renderScale.x != 1.) || (args.renderScale.y != 1.) ) ) {
644         throwSuiteStatusException(kOfxStatFailed);
645     }
646 
647     // use the default RoD
648     return false;
649 }
650 
651 mDeclarePluginFactory(RunScriptPluginFactory, {ofxsThreadSuiteCheck();}, {});
652 void
describe(ImageEffectDescriptor & desc)653 RunScriptPluginFactory::describe(ImageEffectDescriptor &desc)
654 {
655     DBG(std::cout << "describing!\n");
656     // basic labels
657     desc.setLabel(kPluginName);
658     desc.setPluginGrouping(kPluginGrouping);
659     desc.setPluginDescription(kPluginDescription);
660 #ifdef OFX_EXTENSIONS_NATRON
661    desc.setDescriptionIsMarkdown(true);
662 #endif
663 
664     // add the supported contexts
665     desc.addSupportedContext(eContextFilter);
666     desc.addSupportedContext(eContextGeneral);
667     desc.addSupportedContext(eContextGenerator);
668 
669     // add supported pixel depths
670     desc.addSupportedBitDepth(eBitDepthUByte);
671     desc.addSupportedBitDepth(eBitDepthUShort);
672     desc.addSupportedBitDepth(eBitDepthHalf);
673     desc.addSupportedBitDepth(eBitDepthFloat);
674     desc.addSupportedBitDepth(eBitDepthCustom);
675 
676     // set a few flags
677     desc.setSingleInstance(false);
678     desc.setHostFrameThreading(false);
679     desc.setSupportsTiles(kSupportsTiles);
680     desc.setSupportsMultiResolution(kSupportsMultiResolution);
681     desc.setRenderThreadSafety(kRenderThreadSafety);
682     desc.setTemporalClipAccess(false);
683     desc.setRenderTwiceAlways(false);
684     desc.setSupportsMultipleClipPARs(false);
685 }
686 
687 void
describeInContext(ImageEffectDescriptor & desc,ContextEnum context)688 RunScriptPluginFactory::describeInContext(ImageEffectDescriptor &desc,
689                                           ContextEnum context)
690 {
691     DBG(std::cout << "describing in context " << (int)context << std::endl);
692 
693     const ImageEffectHostDescription &gHostDescription = *getImageEffectHostDescription();
694     bool hostIsNuke = (gHostDescription.hostName.find("nuke") != string::npos ||
695                        gHostDescription.hostName.find("Nuke") != string::npos);
696 
697     // Source clip only in the filter context
698     // create the mandated source clip
699     for (int i = 0; i < kRunScriptPluginSourceClipCount; ++i) {
700         ClipDescriptor *srcClip;
701         if ( (i == 0) && (context == eContextFilter) ) {
702             srcClip = desc.defineClip(kOfxImageEffectSimpleSourceClipName); // mandatory clip for the filter context
703         } else {
704             const string istr = unsignedToString(i + 1);
705             srcClip = desc.defineClip(istr);
706         }
707         srcClip->addSupportedComponent(ePixelComponentRGB);
708         srcClip->addSupportedComponent(ePixelComponentRGBA);
709         srcClip->addSupportedComponent(ePixelComponentAlpha);
710         srcClip->addSupportedComponent(ePixelComponentCustom);
711         srcClip->setTemporalClipAccess(false);
712         srcClip->setSupportsTiles(false);
713         srcClip->setIsMask(false);
714         srcClip->setOptional(true);
715     }
716 
717     // create the mandated output clip
718     ClipDescriptor *dstClip = desc.defineClip(kOfxImageEffectOutputClipName);
719     dstClip->addSupportedComponent(ePixelComponentRGB);
720     dstClip->addSupportedComponent(ePixelComponentRGBA);
721     dstClip->addSupportedComponent(ePixelComponentAlpha);
722     dstClip->addSupportedComponent(ePixelComponentCustom);
723     dstClip->setSupportsTiles(false);
724 
725     // make some pages and to things in
726     PageParamDescriptor *page = desc.definePageParam("Controls");
727 
728     {
729         GroupParamDescriptor *group = desc.defineGroupParam(kGroupRunScriptPlugin);
730         if (group) {
731             group->setHint(kGroupRunScriptPluginHint);
732             group->setLabel(kGroupRunScriptPluginLabel);
733             if (page) {
734                 page->addChild(*group);
735             }
736         }
737         {
738             IntParamDescriptor *param = desc.defineIntParam(kParamCount);
739             param->setLabel(kParamCountLabel);
740             param->setAnimates(true);
741             param->setRange(0, kRunScriptPluginArgumentsCount);
742             param->setDisplayRange(0, kRunScriptPluginArgumentsCount);
743             if (group) {
744                 param->setParent(*group);
745             }
746             if (page) {
747                 page->addChild(*param);
748             }
749         }
750 
751         // Note: if we use setIsSecret() here, the parameters cannot be shown again in Nuke.
752         // We thus hide them in updateVisibility(), which is called after instance creation
753         for (int i = 0; i < kRunScriptPluginArgumentsCount; ++i) {
754             const string istr = unsignedToString(i + 1);
755             {
756                 ChoiceParamDescriptor* param = desc.defineChoiceParam(kParamType + istr);
757                 param->setLabel(kParamTypeLabel + istr);
758                 param->setAnimates(true);
759                 param->appendOption(kParamTypeFilenameLabel, kParamTypeFilenameHint);
760                 param->appendOption(kParamTypeStringLabel,   kParamTypeStringHint);
761                 param->appendOption(kParamTypeDoubleLabel,   kParamTypeDoubleHint);
762                 param->appendOption(kParamTypeIntLabel,      kParamTypeIntHint);
763                 //param->setIsSecretAndDisabled(true); // done in the plugin constructor
764                 if (group) {
765                     param->setParent(*group);
766                 }
767                 if (page) {
768                     page->addChild(*param);
769                 }
770             }
771 
772             {
773                 StringParamDescriptor* param = desc.defineStringParam(kParamTypeFilenameName + istr);
774                 param->setLabel(kParamTypeFilenameLabel + istr);
775                 param->setHint(kParamTypeFilenameHint);
776                 param->setStringType(eStringTypeFilePath);
777                 param->setFilePathExists(false); // the file may or may not exist
778                 param->setAnimates(true); // the file name may change with time
779                 //param->setIsSecretAndDisabled(true); // done in the plugin constructor
780                 if (group) {
781                     param->setParent(*group);
782                 }
783                 if (page) {
784                     page->addChild(*param);
785                 }
786             }
787 
788             {
789                 StringParamDescriptor* param = desc.defineStringParam(kParamTypeStringName + istr);
790                 param->setLabel(kParamTypeStringLabel + istr);
791                 param->setHint(kParamTypeStringHint);
792                 param->setAnimates(true);
793                 //param->setIsSecretAndDisabled(true); // done in the plugin constructor
794                 if (group) {
795                     param->setParent(*group);
796                 }
797                 if (page) {
798                     page->addChild(*param);
799                 }
800             }
801 
802             {
803                 DoubleParamDescriptor* param = desc.defineDoubleParam(kParamTypeDoubleName + istr);
804                 param->setLabel(kParamTypeDoubleLabel + istr);
805                 param->setHint(kParamTypeDoubleHint);
806                 param->setAnimates(true);
807                 //param->setIsSecretAndDisabled(true); // done in the plugin constructor
808                 param->setRange(-DBL_MAX, DBL_MAX);
809                 param->setDisplayRange(-1000., 1000.);
810                 if (group) {
811                     param->setParent(*group);
812                 }
813                 if (page) {
814                     page->addChild(*param);
815                 }
816             }
817 
818             {
819                 IntParamDescriptor* param = desc.defineIntParam(kParamTypeIntName + istr);
820                 param->setLabel(kParamTypeIntLabel + istr);
821                 param->setHint(kParamTypeIntHint);
822                 param->setAnimates(true);
823                 //param->setIsSecretAndDisabled(true); // done in the plugin constructor
824                 if (group) {
825                     param->setParent(*group);
826                 }
827                 if (page) {
828                     page->addChild(*param);
829                 }
830             }
831         }
832     }
833 
834     {
835         StringParamDescriptor *param = desc.defineStringParam(kParamScript);
836         param->setLabel(kParamScriptLabel);
837         if (hostIsNuke) {
838             param->setHint(string(kParamScriptHint) + " " kNukeWarnTcl);
839         } else {
840             param->setHint(kParamScriptHint);
841         }
842         param->setStringType(eStringTypeMultiLine);
843         param->setAnimates(true);
844         param->setDefault("#!/bin/sh\n");
845         if (page) {
846             page->addChild(*param);
847         }
848     }
849 
850     {
851         BooleanParamDescriptor *param = desc.defineBooleanParam(kParamValidate);
852         param->setLabel(kParamValidateLabel);
853         param->setHint(kParamValidateHint);
854         param->setEvaluateOnChange(true);
855         if (page) {
856             page->addChild(*param);
857         }
858     }
859 } // RunScriptPluginFactory::describeInContext
860 
861 ImageEffect*
createInstance(OfxImageEffectHandle handle,ContextEnum)862 RunScriptPluginFactory::createInstance(OfxImageEffectHandle handle,
863                                        ContextEnum /*context*/)
864 {
865     return new RunScriptPlugin(handle);
866 }
867 
868 static RunScriptPluginFactory p(kPluginIdentifier, kPluginVersionMajor, kPluginVersionMinor);
869 mRegisterPluginFactoryInstance(p)
870 
871 OFXS_NAMESPACE_ANONYMOUS_EXIT
872 
873 #endif // defined(_WIN32) || defined(__WIN32__) || defined(WIN32)
874