228 lines
6.0 KiB
PHP
228 lines
6.0 KiB
PHP
<?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();
|