* @package WB * @subpackage vfs */ WBClass::load('WBVFS' , 'WBVFS_Mime' , 'WBString' ); /** * Virtual File System: Mime Video * * * * @version 0.3.2 * @package WB * @subpackage vfs */ class WBVFS_Mime_Video extends WBVFS_Mime { /** * convert command * @link http://www.imagemagick.org */ protected static $magickConvert = 'convert'; /** * ffmpeg command * @link http://www.ffmpeg.org/ */ protected static $ffmpeg = 'ffmpeg'; /** * file name extension * @var string */ protected $extension = 'flv'; /** * major mime type * @var string */ protected $mimeMajor = 'video'; /** * Configuration * @var WBConfig */ private $config; /** * initialize tools * * set command line tools */ public static function staticConstruct() { self::$ffmpeg = WBParam::get('wb/vfs/mime/ffmpeg', self::$ffmpeg); self::$magickConvert = WBParam::get('wb/vfs/mime/imagemagick/convert', self::$magickConvert); } /** * 2nd constructor * */ protected function init() { $this->config = WBClass::create('WBConfig'); $this->config->load('vfs/transcode/video'); } /** * get information about this file * * @return array */ public function getInfo() { $ret = null; $out = array(); $cmd = sprintf('%s -hide_banner -vstats -i %s 2>&1', self::$ffmpeg, $this->file); exec($cmd, $out, $ret); $this->info = $this->parseFfprobe($out); return $this->info; } /** * import file * */ public function import() { } /** * queue file! * * @return bool */ public function queue() { return true; } /** * Execute command * * Commands: * - setposter: change poster image * * @param string $cmd * @param array $arg */ public function execute($cmd, $arg = array()) { $cmd = trim(strtolower($cmd)); $log = array( 'file' => $this->vfile->getId(), 'mime' => $this->mimeMajor, 'cmd' => $cmd, 'status' => 'done' ); switch ($cmd) { case 'setposter': $data = base64_decode(substr($arg['data'], 23)); if (false === $data) { return; } $cachDir = $this->vfile->getCacheDir(true); $this->vfile->flushCache('image'); file_put_contents($cachDir . '/image/image', $data); chmod($cachDir . '/image/image', 0666); break; default: $log['status'] = 'commandunknown'; $this->log->err($log); return; break; } $this->log->notice($log); } /** * get file name of requested file * * @return string */ protected function doGetRequestedFile(&$redirect) { $this->file = $this->vfile->getPath(); $org = array($this->vfile->getInfo('width'), $this->vfile->getInfo('height')); $prefix = strtolower($this->requestedMimeMinor); if (WBVFS::MIME_PLAIN == $prefix) { $prefix = ''; } // strip x- from minor mime type if (0 == strncmp($prefix, 'x-', 2)) { $prefix = substr($prefix, 2); } // use default prefix if (empty($prefix)) { $config = $this->config->get(); foreach ($config as $p => $c) { if (empty($c['default'])) { continue; } $prefix = $p; break; } // still empty? if (empty($prefix)) { return ''; } } // verify prefix $config = $this->config->get($prefix); if (empty($config)) { // invalid prefix return ''; } $this->extension = $this->config->get($prefix . '/extension', $prefix); $this->mimeMinor = $this->config->get($prefix . '/mimeminor', 'x-' . $prefix); $cacheDir = $this->mkCacheDir(); if (!file_exists($cacheDir . '/' . $prefix)) { // this sort of locks the file an prevents multi processings touch($cacheDir . '/' . $prefix); chmod($cacheDir . '/' . $prefix, 0666); $this->transcode($org, $prefix, $cacheDir); } $this->file = $cacheDir . '/' . $prefix; return $this->file; } /** * Execute transcoding * * Set current file to file the one coresponding to requested mime minor type * Find matching transcoding targets in config. Populate and execute transcoding * commands. If there is a faststart tag, execute post processing command * * @param array $org original video dimensions * @param string $prefix * @param string $cacheDir */ private function transcode($org, $prefix, $cacheDir) { $now = WBClock::now(); $cwd = $this->mkWorkingDir('video'); $des = 'out.' . $prefix; $size = array( intval($this->config->get($prefix . '/boundingbox/width', '640')), intval($this->config->get($prefix . '/boundingbox/height', '360')) ); $this->calcSize($size, $org); // multi or single pass commands $cmd = $this->config->get($prefix . '/template/cmd', '{FFMPEG} -i {INFILE} -s {SIZE} -f mp4 -y {OUTFILE} 2>&1'); if (!is_array($cmd)) { $cmd = array($cmd); } $vars = array( 'cwd' => $cwd, 'presetdir' => WBParam::get('wb/dir/system') . '/resource/mime/video/ffmpeg', 'ffmpeg' => self::$ffmpeg, 'infile' => $this->file, 'outfile' => $des, 'size' => implode('x', $size), 'option_audio' => implode(' ', $this->config->get($prefix . '/option/audio', array())), 'option_video' => implode(' ', $this->config->get($prefix . '/option/video', array())), ); $oldcwd = getcwd(); chdir($cwd); // execute all commands foreach ($cmd as $c) { $c = WBString::populate($c, $vars); $out = array(); $ret = null; exec($c, $out, $ret); if ($ret > 0) { $log = array( 'file' => basename($this->file), 'size' => filesize($this->file), 'mime' => 'video', 'action' => 'convert', 'prefix' => $prefix, 'cmd' => $c ); $this->log->err($log); } } // qt-faststart $cmd = $this->config->get($prefix . '/template/faststart'); if (!empty($cmd)) { $vars = array( 'infile' => $des, 'outfile' => $cacheDir . '/' . $prefix, ); $cmd = WBString::populate($cmd, $vars); $out = array(); $ret = null; exec($cmd, $out, $ret); } else { rename($des, $cacheDir . '/' . $prefix); } chdir($oldcwd); $this->rmWorkingDir($cwd); chmod($cacheDir . '/' . $prefix, 0666); $log = array( 'file' => basename($this->file), 'size' => filesize($this->file), 'mime' => 'video', 'action' => 'convert', 'target' => $prefix, 'elapsed' => WBClock::stop($now, 1000, 1) . 'ms' ); $this->log->notice($log); } /** * Caluclate new video size * * Avoid up-scaling. Keep aspect. Fit to bounding box * * @param array $size requested image size * @param array $org */ private function calcSize(&$size, $org) { $maxWidth = $size[0]; // get resize factor $divX = $size[0] / $org[0]; $divY = $size[1] / $org[1]; $factor = min($divX, $divY); // make sure there is at least one pixel in width and height $size[0] = max(1, round($org[0] * $factor)); $size[1] = max(1, round($org[1] * $factor)); // width and height must be a multiple of 2 $size[1] = round(($size[1] / 2), 0) * 2; $size[0] = round(($size[0] / 2), 0) * 2; } }