diff --git a/README.md b/README.md index 69a4d4a..7a380f3 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Natron pipeline. -O Automatically assign output filename based on input filename -p Select the preset to apply -c Specify parameters for the preset + -b Batch mode, input parameter is a list of files Examples: diff --git a/plugins/executor/README.md b/plugins/executor/README.md new file mode 100644 index 0000000..0555c11 --- /dev/null +++ b/plugins/executor/README.md @@ -0,0 +1,50 @@ +preset: + name: Stabilize video clip + group: video + plugin: transcode + props: + set: + inputtrf: { value:"{%uinput}.trf", escape:true } + script: + analyze: + info: Getting stabilization vectors + exec: "transcode -J stabilize -i {%input}" + parse: { regex: '/^encoding frames [0-([0-9]+?)], ([0-9\.]+?) fps/', frame:1, eta:2 } + stabilize: + info Stabilizing video + exec: "transcode -J transform -i {%input} -o {%output}" + parse: { regex: '/^encoding frames [0-([0-9]+?)], ([0-9\.]+?) fps/', frame:1, eta:2 } + cleanup: + exec: "rm {%inputtrf}" + + + +## Scripts + +Each script command is named, and will be executed in order of appearance. Errors +will be reported and execution will stop if the exit code is non-zero, using +the names as logical pointers. + +### Command lines + +Command line uses placeholders. + + {%input} The input filename, escaped for the command line + {%uinput} Unescaped input filename + {%output} The output filename, escaped for command line + {%uoutput} Unescaped outptu filename + +Any parameters will also be available: + + {%paramname} + +And you can use `set` to assign stuff to variables. + +### Parsing output + +To parse the output, add a `parse` key to the command block. + + parse: + regex: <- expression + frame: <- index of frame number, or leave out + fps: <- index of frames per second, or leave out diff --git a/plugins/executor/plugin.php b/plugins/executor/plugin.php new file mode 100644 index 0000000..f58df04 --- /dev/null +++ b/plugins/executor/plugin.php @@ -0,0 +1,227 @@ + $input->getFilename(), + 'uoutput' => $output->getFilename(), + 'frames' => $input->getVideoDuration()->frames, + 'seconds' => $input->getVideoDuration()->seconds, + ]; + $env['input'] = escapeshellarg($env['uinput']); + $env['output'] = escapeshellarg($env['uoutput']); + + $script = $this->loadScript($preset); + $script->execute($env); + } + + + private function loadScript(Preset $preset) + { + $script = new Script(); + foreach ((array)$preset->get('set') as $k=>$v) { + $script->set($k,$v); + } + foreach ((array)$preset->get('script') as $name=>$step) { + $op = new Operation($name, $step); + $script->addOperation($op); + } + return $script; + } + +} + +class Script +{ + /** @var array Variables */ + protected $vars = []; + + public function set($k,$v) + { + $this->vars[$k] = $v; + } + + public function addOperation(Operation $operation) + { + $operation->setScript($this); + $this->operations[] = $operation; + } + + public function execute(array $env) + { + $env = $this->buildEnvironment($env, $this->vars); + //print_r($env); + foreach ($this->operations as $operation) { + //printf("Executing: %s\n", $operation->getInfo()); + $operation->execute($env); + } + } + + private function buildEnvironment(array $env, array $vars) + { + $vars = array_merge($env, $vars); + foreach ($vars as $k=>$var) { + if (is_array($var)) { + $esc = empty($var['escape'])?false:$var['escape']; + $var = preg_replace_callback('/\{\%([a-z]+?)\}/i', function ($match) use (&$vars,$var) { + $k = $match[1]; + if (empty($vars[$k])) return null; + return $vars[$k]; + }, $var['value']); + if ($esc) $var = escapeshellarg($var); + $vars[$k] = $var; + } + } + return $vars; + } + + public function parseVariable($value, array $env) + { + return preg_replace_callback('/\{\%([a-z]+?)\}/i', function ($match) use (&$env) { + $k = $match[1]; + if (empty($env[$k])) return null; + return $env[$k]; + }, $value); + } +} + +class Operation +{ + protected $name; + + protected $info; + + protected $parser; + + protected $command; + + protected $script; + + public function __construct($name, array $data) + { + $_ = function($a,$k,$d=null) { return empty($a[$k])?$d:$a[$k]; }; + $this->name = $name; + $this->info = $_($data,'info'); + $this->command = $_($data,'exec'); + $this->parser = new Parser($_($data,'parse')); + } + + public function getName() + { + return $this->name; + } + + public function getInfo() + { + return $this->info; + } + + public function setScript(Script $script) + { + $this->script = $script; + } + + public function execute(array $env) + { + $cmdl = $this->script->parseVariable($this->command, $env); + //printf(" cmd: %s\n", $this->command); + //printf(" eval: %s\n", $cmdl); + + $descr = [ + 0 => [ 'pipe', 'r' ], + 1 => [ 'pipe', 'w' ], + 2 => [ 'pipe', 'w' ] + ]; + echo "Exec: ".$cmdl."\n"; + $proc = proc_open($cmdl, $descr, $pipes); + $tot = (int)$env['frames']-1; + printf("\r%s [%s]", $this->info, $this->name); + $this->parser->parse($pipes, function ($status) use ($tot) { + $curr = (int)$status['frame']; + $pc = min(100,100/$tot*$curr); + printf("\r%s [%s]: %.1f%%", $this->info, $this->name, $pc); + }); + proc_close($proc); + printf("\n"); + + } +} + +class Parser +{ + const STDOUT = 1; + const STDERR = 2; + + /** @var array Resources */ + protected $streams = []; + /** @var string The expression to match (preg match) */ + protected $expression; + /** @var int The stream to watch */ + protected $source; + /** @var array Index to name mappings */ + protected $names = []; + + public function __construct(array $parser=null) + { + $_ = function($a,$k,$d=null) { return empty($a[$k])?$d:$a[$k]; }; + $this->expression = $_($parser,'regex'); + $this->source = $_($parser,'from'); + foreach ((array)$parser as $k=>$v) { + if (is_numeric($v)) { + $this->names[$k] = $v; + } + } + } + + public function parse(array $streams, callable $callback) + { + $this->streams = $streams; + $match = [ 'stdout' => 1, 'stderr' => 2 ]; + $source = $this->source?:'stdout'; + + foreach ($this->streams as $stream) + stream_set_blocking($stream, false); + + while (!feof($this->streams[1])) { + $stdout = fread($this->streams[1],8192); + $stderr = fread($this->streams[2],8192); + //($stdout) && printf("OUT: <<%s>>\n", $stdout); + //($stderr) && printf("ERR: <<%s>>\n", $stderr); + if (($stdout && $this->expression) && ($source=='stdout')) + if (preg_match($this->expression, $stdout, $match)) + call_user_func($callback, $this->parseNames($match)); + if (($stderr && $this->expression) && ($source=='stderr')) + if (preg_match($this->expression, $stderr, $match)) + call_user_func($callback, $this->parseNames($match)); + usleep(10000); + } + + } + + protected function parseNames(array $match) + { + $ret = []; + foreach ($this->names as $k=>$index) { + $ret[$k] = $match[$index]; + } + return $ret; + } + +} + +return new ExecutorPlugin(); diff --git a/plugins/natron/plugin.php b/plugins/natron/plugin.php index 1ab9206..8832ffb 100644 --- a/plugins/natron/plugin.php +++ b/plugins/natron/plugin.php @@ -16,7 +16,22 @@ class NatronPlugin extends Plugin public function applyPreset(Preset $preset, Input $input, Output $output) { + $project = $preset->get("project"); + $file = $preset->getResourcePath($project); + $in_node = $preset->get("reader"); + $out_node = $preset->get("writer"); + $in_file = $input->getFilename(); + $out_file = $output->getFilename(); + + $cmdl = sprintf( + "NatronRenderer %s -i %s %s -o %s %s", + escapeshellarg($file), + $in_node, escapeshellarg($in_file), + $out_node, escapeshellarg($out_file) + ); + + echo $cmdl."\n"; } } diff --git a/presets/executor/executor-vidstab.yml b/presets/executor/executor-vidstab.yml new file mode 100644 index 0000000..200a2c7 --- /dev/null +++ b/presets/executor/executor-vidstab.yml @@ -0,0 +1,34 @@ +preset: + name: Stabilize video clip (two-pass) + group: video + plugin: executor + props: + set: + inputtrf: { value:"{%uinput}.trf", escape:true } + script: + analyze: + info: Getting stabilization vectors + exec: "transcode -i {%input} -J stabilize" + parse: { from: 'stderr', regex: '/encoding frames \[0-([0-9]+?)\],\s+([0-9\.]+?) fps/', frame:1, eta:2 } + stabilize: + info: Stabilizing video + exec: "transcode -i {%input} -o {%output} -J transform -y dv" + # -y ffmpeg -F mpeg4" + parse: { from: 'stderr', regex: '/encoding frames \[0-([0-9]+?)\],\s+([0-9\.]+?) fps/', frame:1, eta:2 } + cleanup: + exec: "ls {%inputtrf}" +#!/bin/bash + +#INPUT="$1" +#OUTPUT="$1.stab.avi" +#CODEC="-y xvid" + +#if [ -e "$INPUT.trf" ]; then +# echo "Cache found, not re-calling stabilize..." +#else + # Start the deshake process +# transcode -J stabilize -i $INPUT || transcode -J stabilize --mplayer_probe -i $INPUT +#fi + +# Now, stabilize the video +#transcode -J transform -i $INPUT $CODEC -o $OUTPUT diff --git a/presets/natron/natron-cctv.ntp b/presets/natron/natron-cctv.ntp new file mode 100644 index 0000000..88b4ed1 --- /dev/null +++ b/presets/natron/natron-cctv.ntp @@ -0,0 +1,2122 @@ + + + +0 + + 2 + 1 + 9 + tags/2.1.9 + 8bd12128704f0cb8f0bb7e11b96f6e64ce42ecd4 + Linux + 64 + + 10 + + LensDistortion1 + LensDistortion1 + net.sf.openfx.LensDistortion + + 0 + 2 + 0 + 7 + + k3 + Double + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + p1 + Double + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + p2 + Double + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + asymmetricDistortion + Double + 2 + 0 + 0 + + 1 + 0 + -0.26200000000000001 + 0 + 0 + + 0 + + + 1 + 0 + -0.26200000000000001 + 0 + 0 + + 0 + + 0 + + + filter + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 2 + 0 + + 0 + + Bilinear + 0 + + + black_outside + Bool + 1 + 0 + 0 + + 1 + 0 + 1 + 0 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + 1 + 0 + + Source + Read1 + + + 27 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + LensDistortion1 + + + Transform1 + Transform1 + net.sf.openfx.TransformPlugin + + 0 + 1 + 0 + 3 + + scale + Double + 2 + 0 + 0 + + 1 + 0 + 1.5 + 1 + 0 + + 0 + + + 1 + 0 + 1.5 + 1 + 0 + + 0 + + 0 + + + uniform + Bool + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + 1 + 0 + + Source + LensDistortion1 + + + 14 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Transform1 + + + CheckerBoard1 + CheckerBoard1 + net.sf.openfx.CheckerBoardPlugin + + 0 + 1 + 0 + 11 + + NatronParamFormatChoice + Choice + 1 + 1 + 0 + + 0 + 0 + 0 + 16 + 0 + + 0 + + PC_Video 640x480 + 0 + + + bottomLeft + Double + 2 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + size + Double + 2 + 1 + 0 + + 0 + 0 + 1280 + 1 + 0 + + 0 + + + 0 + 0 + 720 + 1 + 0 + + 0 + + 0 + + + interactive + Bool + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + boxSize + Double + 2 + 0 + 0 + + 1 + 0 + 3.7000000000000002 + 64 + 0 + + 0 + + + 1 + 0 + 3.7000000000000002 + 64 + 0 + + 0 + + 0 + + + color0 + Color + 4 + 0 + 0 + + 1 + 0 + 0 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 0 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 0 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + color1 + Color + 4 + 0 + 0 + + 1 + 0 + 0 + 0.5 + 0 + + 0 + + + 1 + 0 + 0 + 0.5 + 0 + + 0 + + + 1 + 0 + 0 + 0.5 + 0 + + 0 + + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + color2 + Color + 4 + 0 + 0 + + 1 + 0 + 1 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 1 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 1 + 0.10000000000000001 + 0 + + 0 + + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + color3 + Color + 4 + 0 + 0 + + 1 + 0 + 1 + 0.5 + 0 + + 0 + + + 1 + 0 + 1 + 0.5 + 0 + + 0 + + + 1 + 0 + 1 + 0.5 + 0 + + 0 + + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + centerlineWidth + Double + 1 + 0 + 0 + + 1 + 0 + 0 + 1 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + 0 + 0 + + 198 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + CheckerBoard1 + + + Merge1 + Merge1 + net.sf.openfx.MergePlugin + + 0 + 1 + 0 + 10 + + NatronOfxParamStringSublabelName + String + 1 + 1 + 0 + + 0 + 0 + overlay + over + 0 + + 0 + + + 0 + 0 + + 0 + + + operation + Choice + 1 + 0 + 0 + + 1 + 0 + 29 + 28 + 0 + + 0 + + overlay + 0 + + + screenAlpha + Bool + 1 + 0 + 0 + + 1 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + BChannelsA + Bool + 1 + 0 + 0 + + 1 + 0 + 0 + 1 + 0 + + 0 + + 0 + + + mix + Double + 1 + 0 + 0 + + 1 + 0 + 0.90700000000000003 + 1 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"><Natron>(overlay)</Natron></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + B_channels + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + Color.RGBA + 0 + + + A_channels + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + Color.RGBA + 0 + + + enableMask_Mask + Bool + 1 + 0 + 0 + + 1 + 0 + 1 + 0 + 0 + + 0 + + 0 + + + maskChannel_Mask + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 4 + 0 + + 0 + + RGBA.R + 0 + + + 3 + 0 + + A + Constant1 + + + B + Saturation1 + + + Mask + CheckerBoard1 + + + 20 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Merge1 + + + Constant1 + Constant1 + net.sf.openfx.ConstantPlugin + + 0 + 1 + 0 + 6 + + NatronParamFormatChoice + Choice + 1 + 1 + 0 + + 0 + 0 + 0 + 16 + 0 + + 0 + + PC_Video 640x480 + 0 + + + bottomLeft + Double + 2 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + size + Double + 2 + 1 + 0 + + 0 + 0 + 1280 + 1 + 0 + + 0 + + + 0 + 0 + 720 + 1 + 0 + + 0 + + 0 + + + interactive + Bool + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + color + Color + 4 + 0 + 0 + + 1 + 0 + 0.35199999999999998 + 0 + 0 + + 0 + + + 1 + 0 + 0.35199999999999998 + 0 + 0 + + 0 + + + 1 + 0 + 0.35199999999999998 + 0 + 0 + + 0 + + + 1 + 0 + 0.35199999999999998 + 0 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + 0 + 0 + + 40 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Constant1 + + + Bloom1 + Bloom1 + net.sf.cimg.CImgBloom + + 0 + 4 + 0 + 4 + + size + Double + 2 + 0 + 0 + + 1 + 0 + 2 + 0 + 0 + + 0 + + + 1 + 0 + 2 + 0 + 0 + + 0 + + 0 + + + uniform + Bool + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + Source_channels + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + Color.RGBA + 0 + + + 1 + 0 + + Source + Merge1 + + + 8 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Bloom1 + + + Quantize1 + Quantize1 + net.sf.openfx.Quantize + + 0 + 1 + 0 + 3 + + colors + Double + 1 + 0 + 0 + + 1 + 0 + 164 + 16 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + Source_channels + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + Color.RGBA + 0 + + + 1 + 0 + + Source + Transform1 + + + 8 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Quantize1 + + + Saturation1 + Saturation1 + net.sf.openfx.SaturationPlugin + + 0 + 2 + 0 + 3 + + saturation + Double + 1 + 0 + 0 + + 1 + 0 + 0.23999999999999999 + 1 + 0 + + 0 + + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + Source_channels + Choice + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + Color.RGBA + 0 + + + 1 + 0 + + Source + Quantize1 + + + 1 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Saturation1 + + + Read1 + Read1 + fr.inria.built-in.Read + + 0 + 1 + 0 + 13 + + fileInfo + Button + 1 + 1 + 0 + + 1 + 0 + 0 + + 0 + + 0 + + + decodingPluginChoice + Choice + 1 + 1 + 0 + + 1 + 0 + 0 + 0 + 0 + + 0 + + Default + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"><Natron>(Thousands join pro-refugee protest in London-xvDqCwBTCxg.mp4)</Natron></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + firstFrame + Int + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + lastFrame + Int + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + startingTime + Int + 1 + 0 + 0 + + 1 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + timeOffset + Int + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + proxyThreshold + Double + 2 + 1 + 0 + + 0 + 0 + 1 + 1 + 0 + + 0 + + + 0 + 0 + 1 + 1 + 0 + + 0 + + 0 + + + customProxyScale + Bool + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + frameRate + Double + 1 + 0 + 0 + + 0 + 0 + 24 + 24 + 0 + + 0 + + 0 + + + ParamExistingInstance + Bool + 1 + 1 + 0 + + 0 + 0 + 1 + 0 + 0 + + 0 + + 0 + + + ocioInputSpace + String + 1 + 1 + 0 + + 0 + 0 + scene_linear + scene_linear + 0 + + 0 + + + 0 + 0 + + 0 + + + ocioOutputSpace + String + 1 + 1 + 0 + + 0 + 0 + scene_linear + scene_linear + 0 + + 0 + + + 0 + 0 + + 0 + + + 0 + 0 + + 19 + + 0 + 0 + + 0 + 3 + Controls + Node + Info + 0 + + 0 + 0 + + Read1 + + + Write1 + Write1 + fr.inria.built-in.Write + + 0 + 1 + 0 + 8 + + encodingPluginChoice + Choice + 1 + 1 + 0 + + 1 + 0 + 0 + 0 + 0 + + 0 + + Default + 0 + + + userTextArea + String + 1 + 0 + 0 + + 1 + 0 + <font size="6" color="#000000" face="Droid Sans"></font> + + 0 + + 0 + + + 0 + 0 + + 0 + + + NatronParamFormatChoice + Choice + 1 + 1 + 0 + + 0 + 0 + 3 + 16 + 0 + + 0 + + HD 1920x1080 + 0 + + + ocioInputSpace + String + 1 + 1 + 0 + + 0 + 0 + scene_linear + scene_linear + 0 + + 0 + + + 0 + 0 + + 0 + + + ocioOutputSpace + String + 1 + 1 + 0 + + 0 + 0 + scene_linear + scene_linear + 0 + + 0 + + + 0 + 0 + + 0 + + + firstFrame + Int + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + lastFrame + Int + 1 + 1 + 0 + + 0 + 0 + 0 + 0 + 0 + + 0 + + 0 + + + ParamExistingInstance + Bool + 1 + 1 + 0 + + 0 + 0 + 1 + 0 + 0 + + 0 + + 0 + + + 1 + 0 + + Source + Bloom1 + + + 0 + + 0 + 0 + + 0 + 3 + Controls + Node + Python + 0 + + 0 + 0 + + Write2 + + + 4 + + projectPaths + Path + 1 + 0 + 0 + + 1 + 0 + <Name>OCIO</Name><Value>/opt/Natron2/Resources/OpenColorIO-Configs/blender</Value><Name>Project</Name><Value>/media/noccy/DiskStation/Development/php-vfxapply/presets/natron</Value> + + 0 + + 0 + + 0 + + + outputFormat + Choice + 1 + 0 + 0 + + 1 + 0 + 16 + 3 + 0 + + 0 + + 1280x720 + 0 + + + frameRange + Int + 2 + 0 + 0 + + 1 + 0 + 0 + 1 + 0 + + 0 + + + 1 + 0 + 5923 + 250 + 0 + + 0 + + 0 + + + lastSaveDate + String + 1 + 0 + 0 + + 0 + 0 + fre dec 23 02:06:57 2016 + + 0 + + 0 + + + 0 + 0 + + 0 + + + 2 + 3 + + + 0 + 0 + 1280 + 720 + + 1 + + + + + 0 + 0 + 640 + 360 + + 1 + + + + 108 + 1481071182927 + + + + 10 + 6 + + LensDistortion1 + 201.54168216880439 + 216.5 + 0 + 0.69999236 + 0.30000764 + 0.10000763 + 0 + 75 + 34 + 0 + 0 + + + Transform1 + 307 + 219 + 0 + 0.69999236 + 0.30000764 + 0.10000763 + 0 + 75 + 34 + 0 + 0 + + + CheckerBoard1 + 611.5 + 136.91981552876334 + 0 + 0.30000764 + 0.50000763 + 0.2 + 0 + 75 + 34 + 0 + 0 + + + Merge1 + 611.5 + 208.5 + 0 + 0.30000764 + 0.37000075 + 0.77599758 + 0 + 75 + 51 + 0 + 0 + + + Constant1 + 608.03618137482817 + 296.99874489567105 + 0 + 0.30000764 + 0.50000763 + 0.2 + 0 + 75 + 34 + 0 + 0 + + + Bloom1 + 735.44770360707741 + 216.5 + 0 + 0.80000001 + 0.50000763 + 0.30000764 + 0 + 75 + 34 + 0 + 0 + + + Quantize1 + 416.27720961887314 + 216.5 + 0 + 0.48000306 + 0.65999848 + 1 + 0 + 59 + 34 + 0 + 0 + + + Saturation1 + 502.15288367745075 + 216.5 + 0 + 0.48000306 + 0.65999848 + 1 + 0 + 75 + 34 + 0 + 0 + + + Read1 + 192.54168216880439 + 111.5 + 1 + 0.69999236 + 0.69999236 + 0.69999236 + 0 + 92 + 60 + 0 + 0 + + + Write2 + 848.05815824503213 + 210.93466924091206 + 0 + 0.74999619 + 0.74999619 + 0 + 0 + 75 + 34 + 0 + 0 + + + + 1 + + 0 + + 876 431 + 1 + 1 + + 361 360 + 2 + 0 + + + 0 + 0 + + -1 + pane1 + 1 + + 0 + + + 3 + 0 + nodeGraph + curveEditor + dopeSheetEditor + + 0 + pane3 + 0 + + + 0 + + + 1 + 0 + properties + + 0 + pane2 + 0 + + + 1 + 31 + 21 + 1335 + 747 + + + + 0 + 0 + + + 0 + 0 + + + 2 + 0 + Write2 + Read1 + + + 0 + + + diff --git a/presets/natron/natron-cctv.ntp.lock b/presets/natron/natron-cctv.ntp.lock new file mode 100644 index 0000000..ac892a6 --- /dev/null +++ b/presets/natron/natron-cctv.ntp.lock @@ -0,0 +1,4 @@ +fre dec 23 02:08:06 2016 +noccy@noccy-aspire5551 +9030 +noccy-aspire5551 \ No newline at end of file diff --git a/presets/natron/natron-cctv.yml b/presets/natron/natron-cctv.yml new file mode 100644 index 0000000..d89996f --- /dev/null +++ b/presets/natron/natron-cctv.yml @@ -0,0 +1,8 @@ +preset: + name: Apply a CCTV look to the video + group: natron + plugin: natron + props: + project: natron-cctv.ntp + reader: Input1 + writer: Output1 diff --git a/src/Application.php b/src/Application.php index 567a8bd..6428b88 100644 --- a/src/Application.php +++ b/src/Application.php @@ -44,9 +44,13 @@ class Application private function loadPreset($file) { - $preset = Preset::createFromFile($file); - $id = basename($file,".yml"); - $this->presets[$id] = $preset; + try { + $preset = Preset::createFromFile($file); + $id = basename($file,".yml"); + $this->presets[$id] = $preset; + } catch (\Exception $e) { + fprintf(STDERR, "Warning: The preset %s could not be loaded. %s\n", $file, $e->getMessage()); + } } private function selectPreset() @@ -79,7 +83,14 @@ class Application $this->readPlugins(); $this->readPresets(); - $opts = getopt("i:o:"); + $opts = getopt("i:o:p:l"); + + if (array_key_exists('l',$opts)) { + foreach ($this->presets as $name=>$preset) { + printf("%s: %s\n", $name, $preset->getName()); + } + return 0; + } $input = new Input(); if (!empty($opts['i'])) { @@ -91,20 +102,36 @@ class Application } $input->analyze(); - $dur = $input->getVideoDuration(); - printf("Input: %s %.2fs (%d frames)\n", $input->getFilename(), $dur->seconds, $dur->frames); - - if (!($preset = $this->selectPreset())) { - return 1; + $output = new Output(); + if (empty($opts['o'])) { + $output->setFilenameFromInput($input); + } else { + $output->setFilename($opts['o']); } + + $dur = $input->getVideoDuration(); + printf("Input: %s %.2fs (%d frames)\n", $input->getFilename(), $dur->seconds, $dur->frames); + printf("Output: %s\n", $output->getFilename()); + + if (empty($opts['p'])) { + if (!($preset = $this->selectPreset())) { + return 1; + } + } else { + if (!array_key_exists($opts['p'], $this->presets)) { + fprintf(STDERR, "Error: No such preset %s\n", $opts['p']); + } + $preset = $this->presets[$opts['p']]; + } + $plugin_name = $preset->getPlugin(); $plugin = $this->plugins[$plugin_name]; - $output = new Output(); - $output->setFilename("/tmp/out.mp4"); - $plugin->applyPreset($preset, $input, $output); + $msg = "Process completed.\n\nCreated ".$output->getFilename(); + exec("notify-send -h int:transient:1 'Completed' ".escapeshellarg($msg)); + } -} \ No newline at end of file +} diff --git a/src/Output.php b/src/Output.php index b7f0257..4d93a1d 100644 --- a/src/Output.php +++ b/src/Output.php @@ -11,8 +11,37 @@ class Output $this->filename = $filename; } + public function setFilenameFromInput(Input $input) + { + $filename = $input->getFilename(); + $ext = pathinfo($filename, PATHINFO_EXTENSION); + $dir = dirname($filename); + $base = basename($filename, $ext); + $suggest = $dir.DIRECTORY_SEPARATOR.$base."vfx.".$ext; + + $this->selectFile($suggest); + } + public function getFilename() { return $this->filename; } + + public function selectFile($suggest=null) + { + $cmdl = "zenity --file-selection --title \"Select output file\" "; + $cmdl.= "--save --confirm-overwrite "; + if ($suggest) $cmdl.= "--filename=".escapeshellarg($suggest)." "; + $cmdl.= "--file-filter=\"Video files (mp4,m4v,avi,ogv,mkv)|*.mp4 *.m4v *.avi *.ogv *.mkv\" "; + $cmdl.= "--file-filter=\"Audio files (mp3,m4a,wav,ogg)|*.mp3 *.m4a *.wav *.ogg\" "; + $cmdl.= "--file-filter=\"All files|*\" 2>/dev/null"; + + exec($cmdl, $out, $ret); + if ($ret == 0) { + $this->filename = trim(array_shift($out)); + return true; + } + return false; + } + } \ No newline at end of file diff --git a/src/Preset.php b/src/Preset.php index b734079..2bb88cb 100644 --- a/src/Preset.php +++ b/src/Preset.php @@ -16,6 +16,8 @@ class Preset protected $props = []; /** @var array The params can be assigned from user data */ protected $params = []; + /** @var string The directory of the preset */ + protected $directory = null; public static function createFromFile($filename) { @@ -25,33 +27,58 @@ class Preset throw new \Exception("File does not appear to be a valid preset"); } - return new Preset($conf['preset']); + return new Preset($conf['preset'], dirname($filename)); } - public function __construct(array $preset) + /** + * Constructor + * + * @param array $preset The preset data + * @param string $directory The directory containing the preset + */ + public function __construct(array $preset, $directory) { $this->name = $preset['name']; $this->group = empty($preset['group'])?null:$preset['group']; $this->plugin = $preset['plugin']; $this->props = $preset['props']; $this->params = empty($preset['params'])?null:$preset['params']; + $this->directory = $directory; } + /** + * + * @return string Preset name + */ public function getName() { return $this->name; } + /** + * + * @return string Preset logical group + */ public function getGroup() { return $this->group; } + /** + * Get the plugin name, for resolving a plugin instance to use. + * + * @return string The plugin name/id + */ public function getPlugin() { return $this->plugin; } + /** + * + * @param string $prop The property to get + * @return mixed The property if it exists, null otherwise + */ public function get($prop) { if (!array_key_exists($prop, $this->props)) { @@ -60,6 +87,12 @@ class Preset return $this->props[$prop]; } + /** + * + * @param string $prop The parameter to get + * @return mixed The parameter if it exists, throws exception otherwise + * @throws Exception + */ public function getParam($param) { if (!array_key_exists($param, $this->params)) { @@ -67,4 +100,20 @@ class Preset } return $this->params[$param]; } + + /** + * Return the directory containing this preset, for referencing resources + * bundled with presets. + * + * @return string The root directory + */ + public function getDirectory() + { + return $this->directory; + } + + public function getResourcePath($file) + { + return $this->directory.DIRECTORY_SEPARATOR.$file; + } } \ No newline at end of file