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 ¶mName) 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 ¶mName)
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