New plugin, executor.

This commit is contained in:
2016-12-25 17:49:43 +01:00
parent 58f27830f4
commit 1938337bb0
11 changed files with 2581 additions and 15 deletions

View File

@ -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

227
plugins/executor/plugin.php Normal file
View File

@ -0,0 +1,227 @@
<?php
namespace VfxApply\Plugin\Executor;
use VfxApply\Plugin;
use VfxApply\Input;
use VfxApply\Output;
use VfxApply\Preset;
class ExecutorPlugin extends Plugin
{
public function getName()
{
return "executor";
}
public function applyPreset(Preset $preset, Input $input, Output $output)
{
$env = [
'uinput' => $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();

View File

@ -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";
}
}