Created
April 29, 2010 04:45
-
-
Save marijn/383153 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
class VideoEncoder | |
{ | |
/** | |
* @var string | |
*/ | |
private $_outputFile; | |
/** | |
* @var array | |
*/ | |
private $_options; | |
const SIZE_128_96 = 'sqcif'; | |
const SIZE_176_144 = 'qcif'; | |
const SIZE_352_288 = 'cif'; | |
const SIZE_704_576 = '4cif'; | |
const SIZE_1408_1152 = '16cif'; | |
const SIZE_160_120 = 'qqvga'; | |
const SIZE_320_240 = 'qvga'; | |
const SIZE_640_480 = 'vga'; | |
const SIZE_800_600 = 'svga'; | |
const SIZE_1024_768 = 'xga'; | |
const SIZE_1600_1200 = 'uxga'; | |
const SIZE_2048_1536 = 'qxga'; | |
const SIZE_1280_1240 = 'sxga'; | |
const SIZE_2560_2048 = 'qsxga'; | |
const SIZE_5120_4096 = 'hsxga'; | |
const SIZE_852_480 = 'wvga'; | |
const SIZE_1366_768 = 'wxga'; | |
const SIZE_1600_1024 = 'wsxga'; | |
const SIZE_1920_1200 = 'wuxga'; | |
const SIZE_2560_1600 = 'woxga'; | |
const SIZE_3200_2048 = 'wqsxga'; | |
const SIZE_3840_2400 = 'wquxga'; | |
const SIZE_6400_4096 = 'whsxga'; | |
const SIZE_7680_4800 = 'whuxga'; | |
const SIZE_320_200 = 'cga'; | |
const SIZE_640_350 = 'ega'; | |
const SIZE_852_480_HD = 'hd480'; | |
const SIZE_1280_720_HD = 'hd720'; | |
const SIZE_1920_1080_HD = 'hd1080'; | |
/** | |
* The flvtool2 binary location | |
*/ | |
const FLVTOOL2_BIN = '/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/bin/flvtool2'; | |
/** | |
* The ffmepeg binary location | |
*/ | |
const FFMPEG_BIN = '/usr/local/bin/ffmpeg'; | |
const PATTERN_MATCH_FRAME = 'frame=\s{1,9}\d{1,9}'; | |
const PATTERN_MATCH_FRAMERATE = 'fps=\s{0,9}\d{1,9}'; | |
const PATTERN_MATCH_QUALITY = 'q=\d{1,3}.\d'; | |
const PATTERN_MATCH_SIZE = 'size=\s{0,9}\d{0,9}kB'; | |
const PATTERN_MATCH_ELAPSED = 'time=\d{1,9}.\d{2}'; | |
const PATTERN_MATCH_BITRATE = 'bitrate=\s{0,5}\d{1,5}.\d{1}kbits\/s'; | |
const PATTERN_REMEMBER_FRAME = 'frame=\s{1,9}(?P<frame>\d{1,9}) '; | |
const PATTERN_REMEMBER_FRAMERATE = 'fps=\s{0,9}(?P<framerate>\d{1,9})'; | |
const PATTERN_REMEMBER_QUALITY = 'q=(?P<quality>\d.\d)'; | |
const PATTERN_REMEMBER_SIZE = 'size=\s{0,9}(?P<size>\d{0,9})kB'; | |
const PATTERN_REMEMBER_ELAPSED = 'time=(?P<elapsed>\d{1,2}.\d{2})'; | |
const PATTERN_REMEMBER_BITRATE = 'bitrate=\s{0,5}(?P<bitrate>\d{1,5}.\d{1})kbits\/s'; | |
/** | |
* The 4X3 aspect ratio. | |
*/ | |
const ASPECT_4_3 = 1.3333; | |
/** | |
* The 16X9 aspect ratio. | |
*/ | |
const ASPECT_16_9 = 1.7777; | |
/** | |
* Load up an encoder instance with an original file. | |
* | |
* @param string $arg_originalFile The original file path. | |
* | |
* @return VideoEncoder A new rsfVideoEncoder instance. | |
*/ | |
public static function load ($arg_originalFile) | |
{ | |
return new VideoEncoder($arg_originalFile); | |
} | |
/** | |
* Load up an encoder instance with an original file. | |
* | |
* @param string $arg_originalFile The original file path. | |
* | |
* @return void | |
*/ | |
public function __construct ($arg_originalFile) | |
{ | |
$this->_options = array('f' => NULL | |
,'i' => NULL | |
,'y' => NULL | |
,'t' => NULL | |
,'fs' => NULL | |
,'title' => NULL | |
,'timestamp' => NULL | |
,'author' => NULL | |
,'copyright' => NULL | |
,'comment' => NULL | |
,'album' => NULL | |
,'track' => NULL | |
,'year' => NULL | |
,'dframes' => NULL | |
,'vframes' => NULL | |
,'b' => NULL | |
,'r' => NULL | |
,'s' => NULL | |
,'aspect' => NULL | |
,'croptop' => NULL | |
,'cropbottom' => NULL | |
,'cropleft' => NULL | |
,'cropright' => NULL | |
,'padtop' => NULL | |
,'padbottom' => NULL | |
,'padleft' => NULL | |
,'padright' => NULL | |
,'padcolor' => NULL | |
,'ar' => NULL | |
,'ab' => NULL | |
,'an' => NULL | |
,'copyts' => TRUE); | |
$this->setOriginalFile($arg_originalFile); | |
} | |
public function setFormat ($arg_format) | |
{ | |
$this->_options['f'] = $arg_format; | |
return $this; | |
} | |
public function setOriginalFile ($arg_originalFile) | |
{ | |
if ( ! $this->_fileExists($arg_originalFile)) | |
{ | |
throw new \InvalidArgumentException(sprintf('No file was found for filename %s', $arg_originalFile)); | |
} | |
$this->_options['i'] = $arg_originalFile; | |
return $this; | |
} | |
public function setOutputFile ($arg_outputFile) | |
{ | |
if ($this->_fileExists($arg_outputFile) && ! (isset($this->_options['y']) || $this->_options['y'])) | |
{ | |
throw new \InvalidArgumentException(sprintf('OutputFile already exists %s', $arg_outputFile)); | |
} | |
if ( ! is_writable(dirname($arg_outputFile))) | |
{ | |
throw new \InvalidArgumentException(sprintf('OutputFile can\'t be written', $arg_outputFile)); | |
} | |
$this->_outputFile = $arg_outputFile; | |
return $this; | |
} | |
public function setOverwrite ($arg_overwrite) | |
{ | |
if ( ! is_bool($arg_overwrite)) | |
{ | |
throw new \InvalidArgumentException('Overwrite parameter should be of type boolean'); | |
} | |
$this->_options['y'] = $arg_overwrite; | |
return $this; | |
} | |
public function setDuration ($arg_duration) | |
{ | |
if ( ! is_int($arg_duration)) | |
{ | |
throw new \InvalidArgumentException('Overwrite parameter should be of type boolean'); | |
} | |
$this->_options['t'] = $arg_duration; | |
return $this; | |
} | |
public function setFilesize ($arg_filesize) | |
{ | |
$this->_options['fs'] = $arg_filesize; | |
return $this; | |
} | |
public function setTitle ($arg_title) | |
{ | |
if ( ! is_string($arg_title)) | |
{ | |
throw new \InvalidArgumentException('Title should be a string value'); | |
} | |
$this->_options['title'] = $arg_title; | |
return $this; | |
} | |
public function setTimestamp ($arg_timestamp) | |
{ | |
$this->_options['timestamp'] = $arg_timestamp; | |
return $this; | |
} | |
public function setAuthor ($arg_author) | |
{ | |
if ( ! is_string($arg_author)) | |
{ | |
throw new \InvalidArgumentException('Author should be a string value'); | |
} | |
$this->_options['author'] = $arg_author; | |
return $this; | |
} | |
public function setCopyright ($arg_copyright) | |
{ | |
if ( ! is_string($arg_copyright)) | |
{ | |
throw new \InvalidArgumentException('Copyright should be a string value'); | |
} | |
$this->_options['copyright'] = $arg_copyright; | |
return $this; | |
} | |
public function setComment ($arg_comment) | |
{ | |
if ( ! is_string($arg_comment)) | |
{ | |
throw new \InvalidArgumentException('Comment should be a string value'); | |
} | |
$this->_options['comment'] = $arg_comment; | |
return $this; | |
} | |
public function setAlbum ($arg_album) | |
{ | |
if ( ! is_string($arg_album)) | |
{ | |
throw new \InvalidArgumentException('Album should be a string value'); | |
} | |
$this->_options['album'] = $arg_album; | |
return $this; | |
} | |
public function setTrack ($arg_track) | |
{ | |
if ( ! is_numeric($arg_track)) | |
{ | |
throw new \InvalidArgumentException('Track should be a numeric value'); | |
} | |
$this->_options['track'] = $arg_track; | |
return $this; | |
} | |
public function setYear ($arg_year) | |
{ | |
if ( ! is_int($year)) | |
{ | |
throw new \InvalidArgumentException('Year should be a 4 digit integer'); | |
} | |
$this->_options['year'] = $arg_year; | |
return $this; | |
} | |
public function setDataFrames ($arg_dataFrames) | |
{ | |
if ( ! is_int($arg_dataFrames)) | |
{ | |
throw new \InvalidArgumentException('DataFrames should be an integer'); | |
} | |
$this->_options['dframes'] = $arg_dataFrames; | |
return $this; | |
} | |
public function setVideoFrames ($arg_videoFrames) | |
{ | |
if ( ! is_int($arg_videoFrames)) | |
{ | |
throw new \InvalidArgumentException('videoFrames should be an integer'); | |
} | |
$this->_options['vframes'] = $arg_videoFrames; | |
return $this; | |
} | |
public function setVideoBitrate ($arg_videoBitrate) | |
{ | |
$this->_options['b'] = $arg_videoBitrate; | |
return $this; | |
} | |
public function setVideoFrameRate ($arg_videoFrameRate) | |
{ | |
$this->_options['r'] = $arg_videoFrameRate; | |
return $this; | |
} | |
public function setSize ($arg_size) | |
{ | |
switch($arg_size) | |
{ | |
case self::SIZE_128_96 : | |
case self::SIZE_176_144 : | |
case self::SIZE_352_288 : | |
case self::SIZE_704_576 : | |
case self::SIZE_1408_1152 : | |
case self::SIZE_160_120 : | |
case self::SIZE_320_240 : | |
case self::SIZE_640_480 : | |
case self::SIZE_800_600 : | |
case self::SIZE_1024_768 : | |
case self::SIZE_1600_1200 : | |
case self::SIZE_2048_1536 : | |
case self::SIZE_1280_1240 : | |
case self::SIZE_2560_2048 : | |
case self::SIZE_5120_4096 : | |
case self::SIZE_852_480 : | |
case self::SIZE_1366_768 : | |
case self::SIZE_1600_1024 : | |
case self::SIZE_1920_1200 : | |
case self::SIZE_2560_1600 : | |
case self::SIZE_3200_2048 : | |
case self::SIZE_3840_2400 : | |
case self::SIZE_6400_4096 : | |
case self::SIZE_7680_4800 : | |
case self::SIZE_320_200 : | |
case self::SIZE_640_350 : | |
case self::SIZE_852_480_HD : | |
case self::SIZE_1280_720_HD : | |
case self::SIZE_1920_1080_HD : | |
$this->_options['s'] = $arg_size; | |
break; | |
default: | |
throw new \InvalidArgumentException('Invalid size'); | |
} | |
return $this; | |
} | |
public function setAspectRatio ($arg_aspectRatio) | |
{ | |
switch ($arg_aspectRatio) | |
{ | |
case self::ASPECT_4_3 : | |
case self::ASPECT_16_9 : | |
$this->_options['aspect'] = $arg_aspectRatio; | |
break; | |
default: | |
throw new \InvalidArgumentException('Invalid aspect ratio'); | |
} | |
return $this; | |
} | |
public function setCropTop ($arg_cropTop) | |
{ | |
if ( ! is_int($arg_cropTop)) | |
{ | |
throw new \InvalidArgumentException('CropTop should be an integer value'); | |
} | |
$this->_options['croptop'] = $arg_cropTop; | |
return $this; | |
} | |
public function setCropBottom ($arg_cropBottom) | |
{ | |
if ( ! is_int($arg_cropBottom)) | |
{ | |
throw new \InvalidArgumentException('CropBottom should be an integer value'); | |
} | |
$this->_options['cropbottom'] = $arg_cropBottom; | |
return $this; | |
} | |
public function setCropLeft ($arg_cropLeft) | |
{ | |
if ( ! is_int($arg_cropLeft)) | |
{ | |
throw new \InvalidArgumentException('CropLeft should be an integer value'); | |
} | |
$this->_options['cropleft'] = $arg_cropLeft; | |
return $this; | |
} | |
public function setCropRight ($arg_cropRight) | |
{ | |
if ( ! is_int($arg_cropRight)) | |
{ | |
throw new \InvalidArgumentException('CropRight should be an integer value'); | |
} | |
$this->_options['cropright'] = $arg_cropRight; | |
return $this; | |
} | |
public function setPaddingTop ($arg_paddingTop) | |
{ | |
if ( ! is_int($arg_paddingTop)) | |
{ | |
throw new \InvalidArgumentException('PaddingTop should be an integer value'); | |
} | |
$this->_options['padtop'] = $arg_paddingTop; | |
return $this; | |
} | |
public function setPaddingBottom ($arg_paddingBottom) | |
{ | |
if ( ! is_int($arg_paddingBottom)) | |
{ | |
throw new \InvalidArgumentException('PaddingBottom should be an integer value'); | |
} | |
$this->_options['padbottom'] = $arg_paddingBottom; | |
return $this; | |
} | |
public function setPaddingLeft ($arg_paddingLeft) | |
{ | |
if ( ! is_int($arg_paddingLeft)) | |
{ | |
throw new \InvalidArgumentException('PaddingLeft should be an integer value'); | |
} | |
$this->_options['padleft'] = $arg_paddingLeft; | |
return $this; | |
} | |
public function setPaddingRight ($arg_paddingRight) | |
{ | |
if ( ! is_int($arg_paddingRight)) | |
{ | |
throw new \InvalidArgumentException('PaddingRight should be an integer value'); | |
} | |
$this->_options['padright'] = $arg_paddingRight; | |
return $this; | |
} | |
public function setPaddingColor ($arg_paddingColor) | |
{ | |
$this->_options['padcolor'] = $arg_paddingColor; | |
return $this; | |
} | |
public function setAudioFrequency ($arg_audioFrequency) | |
{ | |
$this->_options['ar'] = $arg_audioFrequency; | |
return $this; | |
} | |
public function setAudioBitrate ($arg_audioBitrate) | |
{ | |
$this->_options['ab'] = $arg_audioBitrate; | |
return $this; | |
} | |
public function setAudioDisabled ($arg_audioDisabled) | |
{ | |
if ( ! is_bool($arg_audioDisabled)) | |
{ | |
throw new \InvalidArgumentException('AudioDisabled must be a boolean value'); | |
} | |
$this->_options['an'] = $arg_audioDisabled; | |
return $this; | |
} | |
public function setSeekable ($arg_seekable) | |
{ | |
if ( ! is_bool($arg_seekable)) | |
{ | |
throw new \InvalidArgumentException('Seekable must be a boolean value'); | |
} | |
$this->_seekable = $arg_seekable; | |
return $this; | |
} | |
public function execute () | |
{ | |
$command = array(self::FFMPEG_BIN); | |
foreach ($this->_options as $option => $value) | |
{ | |
if (NULL !== $value) | |
{ | |
if (TRUE === $value) | |
{ | |
$command[] = $option; | |
} | |
elseif (FALSE !== $value) | |
{ | |
$command[] = $option . ' ' . $value; | |
} | |
} | |
} | |
$command = implode(' -', $command) . ' ' . $this->_outputFile; | |
// ini_set('auto_detect_line_endings', TRUE); | |
$handle = popen($command . ' 2>&1', 'r'); | |
$pattern = sprintf('/%s\s%s\s%s\s%s\s%s\s%s/' | |
,self::PATTERN_MATCH_FRAME | |
,self::PATTERN_MATCH_FRAMERATE | |
,self::PATTERN_MATCH_QUALITY | |
,self::PATTERN_MATCH_SIZE | |
,self::PATTERN_REMEMBER_ELAPSED | |
,self::PATTERN_MATCH_BITRATE); | |
while ( ! feof($handle)) | |
{ | |
$line = fgets($handle); | |
preg_match_all($pattern, $line, $data); | |
// printf("<pre>%s</pre>\n<hr />", $line); | |
// printf("<pre style='background: #111; color: #ccc; padding: 20px; font-weight: bold;'>%s</pre>\n<hr />", var_export($data, TRUE)); | |
// flush(); | |
} | |
pclose($handle); | |
if ($this->_seekable) | |
{ | |
$command = sprintf("\n\n%s -UP %s", self::FLVTOOL2_BIN, $this->_outputFile); | |
exec($command); | |
} | |
return $this->_outputFile; | |
} | |
private function _fileExists ($arg_file) | |
{ | |
return file_exists($arg_file) && ! is_dir($arg_file); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment