Skip to content

Instantly share code, notes, and snippets.

@marijn
Created April 29, 2010 04:45
Show Gist options
  • Save marijn/383153 to your computer and use it in GitHub Desktop.
Save marijn/383153 to your computer and use it in GitHub Desktop.
<?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