516 lines
No EOL
15 KiB
PHP
516 lines
No EOL
15 KiB
PHP
<?php
|
|
|
|
class FFMpeg{
|
|
private $command = "";
|
|
public $defaultOptions = array();
|
|
public $videoDetails = array();
|
|
private $options = array();
|
|
private $outputFile = false;
|
|
private $inputFile = false;
|
|
private $conversionLog = "";
|
|
public $ffMpegPath = FFMPEG_BINARY;
|
|
private $mp4BoxPath = MP4Box_BINARY;
|
|
private $flvTool2 = FLVTool2_BINARY;
|
|
private $videosDirPath = VIDEOS_DIR;
|
|
private $logDir = "";
|
|
private $log = false;
|
|
private $logFile = "";
|
|
private $sdFile = false;
|
|
private $hdFile = false;
|
|
private $resolution16_9 = array(
|
|
'240' => array('428','240'),
|
|
'360' => array('640','360'),
|
|
'480' => array('854','480'),
|
|
'720' => array('1280','720'),
|
|
'1080' => array('1920','1080'),
|
|
);
|
|
// this is test comment
|
|
|
|
private $resolution4_3 = array(
|
|
'240' => array('320','240'),
|
|
'360' => array('480','360'),
|
|
'480' => array('640','480'),
|
|
'720' => array('960','720'),
|
|
'1080' => array('1440','1080'),
|
|
);
|
|
|
|
/*
|
|
Coversion command example
|
|
/usr/local/bin/ffmpeg
|
|
-i /var/www/clipbucket/files/conversion_queue/13928857226cc42.mp4
|
|
-f mp4
|
|
-vcodec libx264
|
|
-vpre normal
|
|
-r 30
|
|
-b:v 300000
|
|
-s 426x240
|
|
-aspect 1.7777777777778
|
|
-vf pad=0:0:0:0:black
|
|
-acodec libfaac
|
|
-ab 128000
|
|
-ar 22050
|
|
/var/www/clipbucket/files/videos/13928857226cc42-sd.mp4
|
|
2> /var/www/clipbucket/files/temp/139288572277710.tmp
|
|
*/
|
|
|
|
public function __construct($options = false, $log = false){
|
|
$this->setDefaults();
|
|
if($options && !empty($options)){
|
|
$this->setOptions($options);
|
|
}else{
|
|
$this->setOptions($this->defaultOptions);
|
|
}
|
|
if($log) $this->log = $log;
|
|
|
|
$this->log->writeLine("in class", "ffmpeg");
|
|
$this->logDir = BASEDIR . "/files/logs/";
|
|
}
|
|
|
|
public function convertVideo($inputFile = false, $options = array(), $isHd = false){
|
|
$this->startLog($this->getInputFileName($inputFile));
|
|
//$this->log->newSection("Video Conversion", "Starting");
|
|
if($inputFile){
|
|
if(!empty($options)){
|
|
$this->setOptions($options);
|
|
}
|
|
$this->inputFile = $inputFile;
|
|
$this->log->writeLine("input file", $inputFile);
|
|
$this->outputFile = $this->videosDirPath . '/'. $this->options['outputPath'] . '/' . $this->getInputFileName($inputFile);
|
|
$this->log->writeLine("outputFile", $this->outputFile);
|
|
$videoDetails = $this->getVideoDetails($inputFile);
|
|
$this->videoDetails = $videoDetails;
|
|
$this->log->writeLine("videoDetails", $videoDetails);
|
|
|
|
|
|
//$this->log->writeLine("Thumbs Generation", "Starting");
|
|
try{
|
|
$this->generateThumbs($this->inputFile, $videoDetails['duration']);
|
|
}catch(Exception $e){
|
|
$this->log->writeLine("Errot Occured", $e->getMessage());
|
|
}
|
|
|
|
/*
|
|
Low Resolution Conversion Starts here
|
|
*/
|
|
$this->log->newSection("Low Resolution Conversion");
|
|
|
|
$this->convertToLowResolutionVideo($videoDetails);
|
|
/*
|
|
High Resoution Coversion Starts here
|
|
*/
|
|
$this->log->newSection("High Resolution Conversion");
|
|
$this->convertToHightResolutionVideo($videoDetails);
|
|
$this->log->writeLine("videoDetails", $videoDetails);
|
|
|
|
}else{
|
|
//$this->logData("no input file");
|
|
}
|
|
}
|
|
|
|
private function convertToLowResolutionVideo($videoDetails = false){
|
|
if($videoDetails){
|
|
$this->log->writeLine("Generating low resolution video", "Starting");
|
|
$this->sdFile = "{$this->outputFile}-sd.{$this->options['format']}";
|
|
$fullCommand = $this->ffMpegPath . " -i {$this->inputFile}" . $this->generateCommand($videoDetails, false) . " {$this->sdFile}";
|
|
|
|
$this->log->writeLine("command", $fullCommand);
|
|
|
|
$conversionOutput = $this->executeCommand($fullCommand);
|
|
$this->log->writeLine("ffmpeg output", $conversionOutput);
|
|
|
|
$this->log->writeLine("MP4Box Conversion for SD", "Starting");
|
|
$fullCommand = $this->mp4BoxPath . " -inter 0.5 {$this->outputFile}-sd.{$this->options['format']}";
|
|
$this->log->writeLine("command", $fullCommand);
|
|
$output = $this->executeCommand($fullCommand);
|
|
$this->log->writeLine("output", $output);
|
|
|
|
}
|
|
}
|
|
|
|
private function convertToHightResolutionVideo($videoDetails = false){
|
|
if($videoDetails && ((int)$videoDetails['video_height'] >= "720")){
|
|
$this->log->writeLine("Generating high resolution video", "Starting");
|
|
$this->hdFile = "{$this->outputFile}-hd.{$this->options['format']}";
|
|
$fullCommand = $this->ffMpegPath . " -i {$this->inputFile}" . $this->generateCommand($videoDetails, true) . " {$this->hdFile}";
|
|
$this->log->writeLine("Command", $fullCommand);
|
|
$conversionOutput = $this->executeCommand($fullCommand);
|
|
$this->log->writeLine("ffmpeg output", $conversionOutput);
|
|
$this->log->writeLine("MP4Box Conversion for HD", "Starting");
|
|
$fullCommand = $this->mp4BoxPath . " -inter 0.5 {$this->outputFile}-hd.{$this->options['format']}";
|
|
$this->log->writeLine("command", $fullCommand);
|
|
$output = $this->executeCommand($fullCommand);
|
|
$this->log->writeLine("output", $output);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function getPadding($padding = array()){
|
|
if(!empty($padding)){
|
|
return " pad={$padding['top']}:{$padding['right']}:{$padding['bottom']}:{$padding['left']}:{$padding['color']} ";
|
|
}
|
|
}
|
|
|
|
private function getInputFileName($filePath = false){
|
|
if($filePath){
|
|
$path = explode("/", $filePath);
|
|
$name = array_pop($path);
|
|
$name = substr($name, 0, strrpos($name, "."));
|
|
return $name;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public function setOptions($options = array()){
|
|
if(!empty($options)){
|
|
foreach ($options as $key => $value) {
|
|
if(isset($this->defaultOptions[$key]) && !empty($value)){
|
|
$this->options[$key] = $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private function generateCommand($videoDetails = false, $isHd = false){
|
|
if($videoDetails){
|
|
$commandSwitches = "";
|
|
$videoRatio = substr($videoDetails['video_wh_ratio'], 0, 3);
|
|
/*
|
|
Setting the aspect ratio of output video
|
|
*/
|
|
$aspectRatio = $videoDetails['video_wh_ratio'];
|
|
if("1.7" === $videoRatio){
|
|
$ratio = $this->resolution16_9;
|
|
}elseif("1.6" === $ratio){
|
|
$ratio = $this->resolution4_3;
|
|
}else{
|
|
$ratio = $this->resolution4_3;
|
|
}
|
|
$commandSwitches .= " -aspect {$aspectRatio}";
|
|
|
|
if(isset($this->options['video_codec'])){
|
|
$commandSwitches .= " -vcodec " .$this->options['video_codec'];
|
|
}
|
|
if(isset($this->options['audio_codec'])){
|
|
$commandSwitches .= " -acodec " .$this->options['audio_codec'];
|
|
}
|
|
|
|
|
|
/*
|
|
Setting Size Of output video
|
|
*/
|
|
if($isHd){
|
|
$defaultVideoHeight = $this->options['high_res'];
|
|
$size = "{$ratio[$defaultVideoHeight][0]}x{$ratio[$defaultVideoHeight][1]}";
|
|
$vpre = "hq";
|
|
}else{
|
|
$defaultVideoHeight = $this->options['normal_res'];
|
|
$size = "{$ratio[$defaultVideoHeight][0]}x{$ratio[$defaultVideoHeight][1]}";
|
|
$vpre = "normal";
|
|
}
|
|
|
|
$commandSwitches .= " -s {$size} -vpre {$vpre}";
|
|
|
|
/*$videoHeight = $videoDetails['video_height'];
|
|
if(array_key_exists($videoHeight, $ratio)){
|
|
//logData($ratio[$videoHeight]);
|
|
$size = "{$ratio[$videoHeight][0]}x{$ratio[$videoHeight][0]}";
|
|
}*/
|
|
|
|
if(isset($this->options['format'])){
|
|
$commandSwitches .= " -f " .$this->options['format'];
|
|
}
|
|
|
|
if(isset($this->options['video_bitrate'])){
|
|
$videoBitrate = (int)$this->options['video_bitrate'];
|
|
if($isHd){
|
|
$videoBitrate = (int)($this->options['video_bitrate_hd']);
|
|
//logData($this->options);
|
|
}
|
|
$commandSwitches .= " -b:v " . $videoBitrate;
|
|
}
|
|
if(isset($this->options['audio_bitrate'])){
|
|
$commandSwitches .= " -b:a " .$this->options['audio_bitrate'];
|
|
}
|
|
if(isset($this->options['video_rate'])){
|
|
$commandSwitches .= " -r " .$this->options['video_rate'];
|
|
}
|
|
if(isset($this->options['audio_rate'])){
|
|
$commandSwitches .= " -ar " .$this->options['audio_rate'];
|
|
}
|
|
return $commandSwitches;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function executeCommand($command = false){
|
|
// the last 2>&1 is for forcing the shell_exec to return the output
|
|
if($command) return shell_exec($command . " 2>&1");
|
|
return false;
|
|
}
|
|
|
|
private function setDefaults(){
|
|
$this->defaultOptions = array(
|
|
'format' => 'mp4',
|
|
'video_codec'=> 'libx264',
|
|
'audio_codec'=> 'libfaac',
|
|
'audio_rate'=> '22050',
|
|
'audio_bitrate'=> '128000',
|
|
'video_rate'=> '25',
|
|
'video_bitrate'=> '300000',
|
|
'video_bitrate_hd'=> '500000',
|
|
'normal_res' => false,
|
|
'high_res' => false,
|
|
'max_video_duration' => false,
|
|
'resolution16_9' => $this->resolution16_9,
|
|
'resolution4_3' => $this->resolution4_3,
|
|
'resize'=>'max',
|
|
'outputPath' => false,
|
|
);
|
|
}
|
|
|
|
private function getVideoDetails( $videoPath = false) {
|
|
if($videoPath){
|
|
# init the info to N/A
|
|
$info['format'] = 'N/A';
|
|
$info['duration'] = 'N/A';
|
|
$info['size'] = 'N/A';
|
|
$info['bitrate'] = 'N/A';
|
|
$info['video_width'] = 'N/A';
|
|
$info['video_height'] = 'N/A';
|
|
$info['video_wh_ratio'] = 'N/A';
|
|
$info['video_codec'] = 'N/A';
|
|
$info['video_rate'] = 'N/A';
|
|
$info['video_bitrate'] = 'N/A';
|
|
$info['video_color'] = 'N/A';
|
|
$info['audio_codec'] = 'N/A';
|
|
$info['audio_bitrate'] = 'N/A';
|
|
$info['audio_rate'] = 'N/A';
|
|
$info['audio_channels'] = 'N/A';
|
|
$info['path'] = $videoPath;
|
|
|
|
/*
|
|
get the information about the file
|
|
returns array of stats
|
|
*/
|
|
$stats = stat($videoPath);
|
|
if($stats && is_array($stats)){
|
|
$ffmpegOutput = $this->executeCommand( $this->ffMpegPath . " -i {$videoPath} -acodec copy -vcodec copy -y -f null /dev/null 2>&1" );
|
|
$info = $this->parseVideoInfo($ffmpegOutput);
|
|
$info['size'] = (integer)$stats['size'];
|
|
return $info;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function parseVideoInfo($output = "") {
|
|
# search the output for specific patterns and extract info
|
|
# check final encoding message
|
|
$audio_codec = false;
|
|
if($args = $this->pregMatch( 'Unknown format', $output) ) {
|
|
$Unkown = "Unkown";
|
|
} else {
|
|
$Unkown = "";
|
|
}
|
|
if( $args = $this->pregMatch( 'video:([0-9]+)kB audio:([0-9]+)kB global headers:[0-9]+kB muxing overhead', $output) ) {
|
|
$video_size = (float)$args[1];
|
|
$audio_size = (float)$args[2];
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
# check for last enconding update message
|
|
if($args = $this->pregMatch( '(frame=([^=]*) fps=[^=]* q=[^=]* L)?size=[^=]*kB time=([^=]*) bitrate=[^=]*kbits\/s[^=]*$', $output) ) {
|
|
$frame_count = $args[2] ? (float)ltrim($args[2]) : 0;
|
|
$duration = (float)$args[3];
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
|
|
if(!$duration)
|
|
{
|
|
$duration = $this->pregMatch( 'Duration: ([0-9.:]+),', $output );
|
|
$duration = $duration[1];
|
|
|
|
$duration = explode(':',$duration);
|
|
//Convert Duration to seconds
|
|
$hours = $duration[0];
|
|
$minutes = $duration[1];
|
|
$seconds = $duration[2];
|
|
|
|
$hours = $hours * 60 * 60;
|
|
$minutes = $minutes * 60;
|
|
|
|
$duration = $hours+$minutes+$seconds;
|
|
}
|
|
|
|
$info['duration'] = $duration;
|
|
if($duration)
|
|
{
|
|
$info['bitrate' ] = (integer)($info['size'] * 8 / 1024 / $duration);
|
|
if( $frame_count > 0 )
|
|
$info['video_rate'] = (float)$frame_count / (float)$duration;
|
|
if( $video_size > 0 )
|
|
$info['video_bitrate'] = (integer)($video_size * 8 / $duration);
|
|
if( $audio_size > 0 )
|
|
$info['audio_bitrate'] = (integer)($audio_size * 8 / $duration);
|
|
# get format information
|
|
if($args = $this->pregMatch( "Input #0, ([^ ]+), from", $output) ) {
|
|
$info['format'] = $args[1];
|
|
}
|
|
}
|
|
|
|
# get video information
|
|
if( $args= $this->pregMatch( '([0-9]{2,4})x([0-9]{2,4})', $output ) ) {
|
|
|
|
$info['video_width' ] = $args[1];
|
|
$info['video_height' ] = $args[2];
|
|
$info['video_wh_ratio'] = (float) $info['video_width'] / (float)$info['video_height'];
|
|
}
|
|
|
|
if($args= $this->pregMatch('Video: ([^ ^,]+)',$output))
|
|
{
|
|
$info['video_codec' ] = $args[1];
|
|
}
|
|
|
|
# get audio information
|
|
if($args = $this->pregMatch( "Audio: ([^ ]+), ([0-9]+) Hz, ([^\n,]*)", $output) ) {
|
|
$audio_codec = $info['audio_codec' ] = $args[1];
|
|
$audio_rate = $info['audio_rate' ] = $args[2];
|
|
$info['audio_channels'] = $args[3];
|
|
}
|
|
|
|
|
|
if((isset($audio_codec) && !$audio_codec) || !$audio_rate)
|
|
{
|
|
$args = $this->pregMatch( "Audio: ([a-zA-Z0-9]+)(.*), ([0-9]+) Hz, ([^\n,]*)", $output);
|
|
$info['audio_codec' ] = $args[1];
|
|
$info['audio_rate' ] = $args[3];
|
|
$info['audio_channels'] = $args[4];
|
|
}
|
|
|
|
return $info;
|
|
}
|
|
|
|
private function pregMatch($in = false, $str = false){
|
|
if($in && $str){
|
|
preg_match("/$in/",$str,$args);
|
|
return $args;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private function generateThumbs($input_file,$duration,$dim='120x90',$num=3,$rand=NULL,$is_big=false){
|
|
|
|
$tmpDir = TEMP_DIR.'/'.getName($input_file);
|
|
|
|
/*
|
|
The format of $this->options["outputPath"] should be like this
|
|
year/month/day/
|
|
the trailing slash is important in creating directories for thumbs
|
|
*/
|
|
if(substr($this->options["outputPath"], strlen($this->options["outputPath"]) - 1) !== "/"){
|
|
$this->options["outputPath"] .= "/";
|
|
}
|
|
mkdir($tmpDir,0777);
|
|
|
|
|
|
$output_dir = THUMBS_DIR;
|
|
$dimension = '';
|
|
|
|
$big = "";
|
|
|
|
if($is_big=='big')
|
|
{
|
|
$big = 'big-';
|
|
}
|
|
|
|
if($num > 1 && $duration > 14)
|
|
{
|
|
$duration = $duration - 5;
|
|
$division = $duration / $num;
|
|
$count=1;
|
|
|
|
|
|
for($id=3;$id<=$duration;$id++)
|
|
{
|
|
$file_name = getName($input_file)."-{$big}{$count}.jpg";
|
|
$file_path = THUMBS_DIR.'/' . $this->options['outputPath'] . $file_name;
|
|
$id = $id + $division - 1;
|
|
if($rand != "") {
|
|
$time = $this->ChangeTime($id,1);
|
|
} elseif($rand == "") {
|
|
$time = $this->ChangeTime($id);
|
|
}
|
|
|
|
if($dim!='original')
|
|
{
|
|
$dimension = " -s $dim ";
|
|
$mplayer_dim = "-vf scale=$width:$height";
|
|
}
|
|
|
|
$command = $this->ffMpegPath." -i $input_file -an -ss $time -an -r 1 $dimension -y -f image2 -vframes 1 $file_path ";
|
|
|
|
$output = $this->executeCommand($command);
|
|
//$this->logData($output);
|
|
//checking if file exists in temp dir
|
|
if(file_exists($tmpDir.'/00000001.jpg'))
|
|
{
|
|
rename($tmpDir.'/00000001.jpg',THUMBS_DIR.'/'.$file_name);
|
|
}
|
|
$count = $count+1;
|
|
}
|
|
}else{
|
|
$file_name = getName($input_file).".jpg";
|
|
$file_path = THUMBS_DIR.'/' . $this->options['outputPath'] . "/" . $file_name;
|
|
$command = $this->ffMpegPath." -i $input_file -an -s $dim -y -f image2 -vframes $num $file_path ";
|
|
$output = $this->executeCommand($command);
|
|
}
|
|
|
|
rmdir($tmpDir);
|
|
}
|
|
|
|
/**
|
|
* Function used to convert seconds into proper time format
|
|
* @param : INT duration
|
|
* @parma : rand
|
|
*/
|
|
|
|
private function ChangeTime($duration, $rand = "") {
|
|
if($rand != "") {
|
|
if($duration / 3600 > 1) {
|
|
$time = date("H:i:s", $duration - rand(0,$duration));
|
|
} else {
|
|
$time = "00:";
|
|
$time .= date("i:s", $duration - rand(0,$duration));
|
|
}
|
|
return $time;
|
|
} elseif($rand == "") {
|
|
if($duration / 3600 > 1 ) {
|
|
$time = date("H:i:s",$duration);
|
|
} else {
|
|
$time = "00:";
|
|
$time .= date("i:s",$duration);
|
|
}
|
|
return $time;
|
|
}
|
|
}
|
|
|
|
private function startLog($logFileName){
|
|
$this->logFile = $this->logDir . $logFileName . ".log";
|
|
$this->log->setLogFile($this->logFile);
|
|
}
|
|
|
|
public function isConversionSuccessful(){
|
|
if($this->sdFile){
|
|
if($this->hdFile){
|
|
return (file_exists($this->sdFile) && (filesize($this->sdFile) > 0)) && (file_exists($this->hdFile) && (filesize($this->hdFile) > 0));
|
|
}else{
|
|
return (file_exists($this->sdFile) && (filesize($this->sdFile) > 0));
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} |