commit 58f27830f4951e131736551501c402da576faba6 Author: Christopher Vagnetoft Date: Thu Dec 22 03:15:02 2016 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61ead86 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/vendor diff --git a/README.md b/README.md new file mode 100644 index 0000000..69a4d4a --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +VfxApply: Video Effect Toolkit +============================== + +VfxApply is a tool to apply effects and filters using presets and plugins. A preset +can for example apply audio normalization, visualize audio as video, or apply a +Natron pipeline. + + + +## Usage + + vfxapply [-i ] [-o |-O] [-p ] [-c =] + + -i Set the input file (if omitted, a file picker will be displayed) + -o Set the output file (if omitted, a file picker will be displayed) + -O Automatically assign output filename based on input filename + -p Select the preset to apply + -c Specify parameters for the preset + +Examples: + + vfxapply + Ask for input, output and preset + + vfxapply -i in.mp4 -o out.m4 + Ask for preset to apply and process in.mp4 into out.mp4 + + vfxapply -O -p ffmpeg-audio-normalize + Normalize audio after picking input, output name will be generated. + +## Plugins + +### ffmpeg + +### natron diff --git a/bin/vfxapply b/bin/vfxapply new file mode 100755 index 0000000..d365625 --- /dev/null +++ b/bin/vfxapply @@ -0,0 +1,9 @@ +#!/usr/bin/env php +get("filter"); + $type = $preset->get("type")?:"complex"; + $extra = $preset->get("extra"); + + switch ($type) { + case 'audio': + $filter_type = "-af"; + $filter_extra = ""; + break; + case 'video': + $filter_type = "-vf"; + $filter_extra = ""; + break; + case 'complex': + default: + $filter_type = "-filter_complex"; + $filter_extra = ""; + break; + } + /*if (($size = $preset->getParam("size"))) { + $filter = $this->appendFilterOption($filter, "s", $size); + }*/ + + // Create a progress dialog + $dialog = $this->createDialog(DIALOG_PROGRESS, "Applying filter"); + $dialog->show(); + + // Create command line to run ffmpeg + $cmdline = sprintf( + "ffmpeg -loglevel error -y -stats -i %s %s %s %s %s 2>&1", + escapeshellarg($input->getFilename()), + $filter_type, + escapeshellarg($filter), + trim($extra." ".$filter_extra), + escapeshellarg($output->getFilename()) + ); + + // Create a coprocess for ffmpeg + $frames = $input->getVideoDuration()->frames; + $ffmpeg = $this->createProcess($cmdline, function ($read) use ($dialog,$frames) { + $dialog->setText(trim($read)); + if (preg_match('/^frame=[\s]*([0-9]+)\s/', trim($read), $match)) { + $frame = intval($match[1]); + $pc = round($frame/$frames*100); + $dialog->setProgress($pc); + } + if ($dialog->isCancelled()) return false; + + // frame= 4140 fps=102 q=-1.0 Lsize= 4559kB time=00:00:48.04 bitrate= 777.3kbits/s speed=1.18x + }); + $ffmpeg->run(); + + unset($dialog); + + } + +} + +return new FfmpegPlugin(); \ No newline at end of file diff --git a/plugins/natron/plugin.php b/plugins/natron/plugin.php new file mode 100644 index 0000000..1ab9206 --- /dev/null +++ b/plugins/natron/plugin.php @@ -0,0 +1,24 @@ +isDir()) + continue; + if (!file_exists($dir->getPathname()."/plugin.php")) + continue; + $this->loadPlugin($dir->getPathname()); + } + } + + public function readPresets() + { + $iter = new \RecursiveIteratorIterator( + new \RecursiveDirectoryIterator( + __DIR__."/../presets" + )); + foreach ($iter as $file) { + if ($file->isDir() || ($file->getExtension()!='yml')) + continue; + $this->loadPreset($file->getPathname()); + } + } + + private function loadPlugin($path) + { + $plugin = require_once $path."/plugin.php"; + $this->plugins[$plugin->getName()] = $plugin; + } + + private function loadPreset($file) + { + $preset = Preset::createFromFile($file); + $id = basename($file,".yml"); + $this->presets[$id] = $preset; + } + + private function selectPreset() + { + $cmdl = "zenity --list --column=Group --column=Preset --column=Description ". + "--title=\"Apply VFX\" ". + "--width=400 --height=400 ". + "--text=\"Select the preset to apply\" ". + "--mid-search --print-column=2 "; + foreach ($this->presets as $id=>$preset) { + $cmdl.=sprintf( + "%s %s %s ", + escapeshellarg($preset->getGroup()), + escapeshellarg($id), + escapeshellarg($preset->getName()) + ); + } + + exec($cmdl." 2>/dev/null", $output, $retval); + if ($retval == 0) { + $name = trim($output[0]); + return $this->presets[$name]; + } else { + return false; + } + } + + public function run() + { + $this->readPlugins(); + $this->readPresets(); + + $opts = getopt("i:o:"); + + $input = new Input(); + if (!empty($opts['i'])) { + $input->setFilename($opts['i']); + } else { + if (!$input->selectFile()) { + return 1; + } + } + $input->analyze(); + + $dur = $input->getVideoDuration(); + printf("Input: %s %.2fs (%d frames)\n", $input->getFilename(), $dur->seconds, $dur->frames); + + if (!($preset = $this->selectPreset())) { + return 1; + } + $plugin_name = $preset->getPlugin(); + $plugin = $this->plugins[$plugin_name]; + + $output = new Output(); + $output->setFilename("/tmp/out.mp4"); + + $plugin->applyPreset($preset, $input, $output); + + } + +} \ No newline at end of file diff --git a/src/Dialog.php b/src/Dialog.php new file mode 100644 index 0000000..1b8f919 --- /dev/null +++ b/src/Dialog.php @@ -0,0 +1,29 @@ +title = $title; + } + + public function __destruct() + { + $this->onDestroy(); + } + + public function show() + { + $this->onCreate(); + } + + protected function onCreate() + {} + + protected function onDestroy() + {} +} \ No newline at end of file diff --git a/src/Dialog/ProgressDialog.php b/src/Dialog/ProgressDialog.php new file mode 100644 index 0000000..f49fde3 --- /dev/null +++ b/src/Dialog/ProgressDialog.php @@ -0,0 +1,57 @@ +out = fopen("php://temp", "wb"); + $cmdl = sprintf("zenity --progress --title=%s --auto-close --text='Running' --width=400 --progress=0 2>/dev/null", escapeshellarg($this->title)); + $ds = [ + 0 => [ "pipe", "r" ], + 1 => [ "pipe", "w" ], + 2 => [ "pipe", "w" ] + ]; + $this->ps = proc_open($cmdl, $ds, $pipes); + $this->pipe = $pipes[0]; + } + + protected function onDestroy() + { + /* + $status = proc_get_status($this->ps); + $pid = $status['pid']; + posix_kill($pid,15); + pcntl_waitpid($pid,$status); + print_r($status); + */ + proc_terminate($this->ps); + proc_close($this->ps); + } + + public function setProgress($pc) + { + fprintf($this->pipe, "%d\n", $pc); + } + + public function setText($text) + { + fprintf($this->pipe, "# %s\r\n", addslashes($text)); + } + + public function isCancelled() + { + $status = proc_get_status($this->ps); + return ($status['running']==false); + } + +} \ No newline at end of file diff --git a/src/Input.php b/src/Input.php new file mode 100644 index 0000000..9c8367e --- /dev/null +++ b/src/Input.php @@ -0,0 +1,69 @@ +filename = $filename; + } + + public function getFilename() + { + return $this->filename; + } + + public function selectFile() + { + $cmdl = "zenity --file-selection --title \"Select input file\" "; + $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; + } + + public function analyze() + { + $cmdl = "ffprobe -loglevel error -show_streams -print_format json ".escapeshellarg($this->filename); + + exec($cmdl, $out, $ret); + if ($ret != 0) { + throw new \Exception("Unable to analyze file {$this->filename}"); + } + $info = json_decode(join("\n",$out)); + + foreach ($info->streams as $stream) { + if ($stream->codec_type == 'video') { + $this->videostreams[] = $stream; + } elseif ($stream->codec_type == 'audio') { + $this->audiostreams[] = $stream; + } + } + + } + + public function getVideoDuration() + { + $duration = $this->videostreams[0]->duration; + $frames = $this->videostreams[0]->nb_frames; + + return (object)[ + "seconds" => floatval($duration), + "frames" => intval($frames) + ]; + } + +} \ No newline at end of file diff --git a/src/Output.php b/src/Output.php new file mode 100644 index 0000000..b7f0257 --- /dev/null +++ b/src/Output.php @@ -0,0 +1,18 @@ +filename = $filename; + } + + public function getFilename() + { + return $this->filename; + } +} \ No newline at end of file diff --git a/src/Plugin.php b/src/Plugin.php new file mode 100644 index 0000000..d6597e6 --- /dev/null +++ b/src/Plugin.php @@ -0,0 +1,22 @@ +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']; + } + + public function getName() + { + return $this->name; + } + + public function getGroup() + { + return $this->group; + } + + public function getPlugin() + { + return $this->plugin; + } + + public function get($prop) + { + if (!array_key_exists($prop, $this->props)) { + return null; + } + return $this->props[$prop]; + } + + public function getParam($param) + { + if (!array_key_exists($param, $this->params)) { + throw new \Exception("No such param defined in preset: {$param}"); + } + return $this->params[$param]; + } +} \ No newline at end of file diff --git a/src/Process.php b/src/Process.php new file mode 100644 index 0000000..db506c7 --- /dev/null +++ b/src/Process.php @@ -0,0 +1,47 @@ +cmdline = $cmdline; + $this->callback = $callback; + } + + public function __destruct() + { + } + + public function run() + { + $ds = [ + 0 => [ "pipe", "r" ], + 1 => [ "pipe", "w" ], + 2 => [ "pipe", "w" ], + ]; + echo "Exec: {$this->cmdline}\n"; + $ps = proc_open($this->cmdline, $ds, $pipes); + list($stdin,$stdout,$stderr) = $pipes; + + stream_set_blocking($stdout,false); + + $buf = null; + while (true) { + $str = fgets($stdout, 4192); + $status = proc_get_status($ps); + if ($str) { + $ret = call_user_func($this->callback, $str); + if ($ret === false) { + proc_terminate($ps); + } + } + + if ($status['running']==false) break; + usleep(10000); + } + proc_close($ps); + return $status['exitcode']; + } +} diff --git a/src/app.php b/src/app.php new file mode 100644 index 0000000..860e8e3 --- /dev/null +++ b/src/app.php @@ -0,0 +1,15 @@ +run(); \ No newline at end of file