Source for file mime.video.php
Documentation is available at mime.video.php
* @author xing <xing@synapse.plus.com>
* created Thursday May 08, 2008
* @subpackage liberty_mime_handler
* This is the name of the plugin - max char length is 16
* As a naming convention, the liberty mime handler definition should start with:
define( 'PLUGIN_MIME_GUID_VIDEO', 'mimevideo' );
// Set of functions and what they are called in this paricular plugin
// Use the GUID as your namespace
'preload_function' => 'mime_video_preload',
'verify_function' => 'mime_default_verify',
'store_function' => 'mime_video_store',
'update_function' => 'mime_video_update',
'load_function' => 'mime_video_load',
'download_function' => 'mime_default_download',
'expunge_function' => 'mime_default_expunge',
// Brief description of what the plugin does
'title' => 'Convert Video to Flash Video',
'description' => 'This plugin will use ffmpeg to convert any compatible uploaded video to flash video. It will also make the video available for viewing if you have flash installed. Please consult the README on how to use this plugin.',
// Templates to display the files
'view_tpl' => 'bitpackage:liberty/mime/video/view.tpl',
'inline_tpl' => 'bitpackage:liberty/mime/video/inline.tpl',
'storage_tpl' => 'bitpackage:liberty/mime/video/storage.tpl',
'attachment_tpl' => 'bitpackage:liberty/mime/video/attachment.tpl',
'edit_tpl' => 'bitpackage:liberty/mime/video/edit.tpl',
// url to page with options for this plugin
// This should be the same for all mime plugins
// Set this to TRUE if you want the plugin active right after installation
'auto_activate' => FALSE,
// Help page on bitweaver.org
'help_page' => 'LibertyMime+Video+Plugin',
// this should pick up all videos
* mime_video_preload This function is loaded on every page load before anything happens and is used to load required scripts.
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
$gBitThemes->loadJavascript( UTIL_PKG_PATH. "javascript/flv_player/swfobject.js", FALSE, 25 );
* Store the data in the database
* @param array $pStoreRow File data needed to store details in the database - sanitised and generated in the verify function
* @return TRUE on success, FALSE on failure - $pStoreRow['errors'] will contain reason
// this will set the correct pluign guid, even if we let default handle the store process
// if storing works, we process the video
$pStoreRow['errors'] = $pStoreRow['log'];
* @param array $pStoreRow
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( BitBase::verifyId( $pStoreRow['attachment_id'] )) {
$pStoreRow['log'] = array();
// set the correct pluign guid, even if we let default handle the store process
// remove the entire directory
$pStoreRow['unlink_dir'] = TRUE;
// if storing works, we process the video
// if it all goes tits up, we'll know why
$pStoreRow['errors'] = $pStoreRow['log'];
// if there was no upload we'll process the file parameters
if( empty( $pStoreRow['upload'] ) && isset ( $pParams['meta']['aspect'] )) {
// set aspect NULL that it's removed from the database
if( empty( $pParams['meta']['aspect'] )) {
$pParams['meta']['aspect'] = NULL;
// we store the custom aspect ratio as a preference which we will use to override the original one
if( !LibertyMime::storeAttachmentPreference( $pStoreRow['attachment_id'], 'aspect', $pParams['meta']['aspect'] )) {
$log['store_meta'] = "There was a problem storing the preference in the database";
$pStoreRow['errors'] = $log;
* Load file data from the database
* @param array $pFileHash Contains all file information
* @param array $pPrefs Attachment preferences taken liberty_attachment_prefs
* @param array $pParams Parameters for loading the plugin - e.g.: might contain values from the view page
* @return TRUE on success, FALSE on failure - ['errors'] will contain reason for failure
global $gLibertySystem, $gBitThemes;
// check for status of conversion
if( !empty( $ret['source_file'] )) {
if( is_file( $source_path. 'error' )) {
$ret['status']['error'] = TRUE;
} elseif( is_file( $source_path. 'processing' )) {
$ret['status']['processing'] = TRUE;
} elseif( is_file( $source_path. 'flick.flv' )) {
} elseif( is_file( $source_path. 'flick.mp4' )) {
// now that we have the original width and height, we can get the displayed values
* This function will add an entry to the process queue for the cron job to take care of
* @param array $pContentId
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
if( @BitBase::verifyId( $pStoreRow['content_id'] )) {
WHERE `content_id`=? AND `process_status`=?";
$gBitSystem->mDb->query( $query, array( 'defunkt', $pStoreRow['content_id'], 'pending' ));
'content_id' => $pStoreRow['content_id'],
'queue_date' => $gBitSystem->getUTCTime(),
'process_status' => 'pending',
'processor' => dirname( __FILE__ ). '/mime.video.php',
$gBitSystem->mDb->associateInsert( BIT_DB_PREFIX. "liberty_process_queue", $storeHash );
* Convert a stored video file to flashvideo
* @param array $pParamHash
* @return TRUE on success, FALSE on failure - mErrors will contain reason for failure
// video conversion can take a while
ini_set( "max_execution_time", "1800" );
if( @BitBase::verifyId( $pParamHash['attachment_id'] )) {
// we might have some attachment preferences set if this is an update
LibertyMime::expungeAttachmentPreferences( $pParamHash['attachment_id'] );
// these are set in the liberty plugin admin screen
$width = trim( $gBitSystem->getConfig( 'mime_video_width', 320 ));
$log = $actionLog = array();
$log['time'] = date( 'Y-M-d - H:i:s O' );
$log['message'] = 'ERROR: ffmpeg does not seem to be available on your system at: '. $ffmpeg. ' Please set the path to ffmpeg in the liberty plugin administration screen.';
$actionLog['log_message'] = "ERROR: ffmpeg does not seem to be available on your system at: '$ffmpeg' Please set the path to ffmpeg in the liberty plugin administration screen.";
// this is the codec we'll use - currently this might be: flv, h264, h264-2pass
$codec = $gBitSystem->getConfig( "mime_video_video_codec", "flv" );
$source = STORAGE_PKG_PATH. $pParamHash['upload']['dest_branch']. $pParamHash['upload']['name'];
// set some default values if ffpeg-php isn't available or fails
$default['aspect'] = 4 / 3;
$default['video_width'] = $width;
$default['video_height'] = round( $width / 4 * 3 );
$default['size'] = "{ $default['video_width']}x{ $default['video_height']}";
$default['offset'] = '00:00:10';
// we silence these calls since they might spew errors
$movie = @new ffmpeg_movie( $source );
'vcodec' => @$movie->getVideoCodec(),
'duration' => round( @$movie->getDuration() ),
'width' => @$movie->getFrameWidth(),
'height' => @$movie->getFrameHeight(),
'video_bitrate' => @$movie->getVideoBitRate(),
'acodec' => @$movie->getAudioCodec(),
'audio_bitrate' => @$movie->getAudioBitRate(),
'audio_samplerate' => @$movie->getAudioSampleRate(),
// make sure audio sample rate is valid
if( !empty( $info['audio_samplerate'] ) && !in_array( $info['audio_samplerate'], array( 11025, 22050, 44100 ))) {
unset ( $info['audio_samplerate'] );
// alternative method using ffmpeg to fetch source dimensions
exec( $command, $output, $status );
if( !preg_match( '/Stream #(?:[0-9\.]+)(?:.*)\: Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)/', implode( '\n', $output ), $matches )) {
preg_match( '/Could not find codec parameters \(Video: (?P<videocodec>.*) (?P<width>[0-9]*)x(?P<height>[0-9]*)\)/', implode( '\n', $output ), $matches );
if( !empty( $matches['width'] ) && !empty( $matches['height'] )) {
$info['width'] = $matches['width'];
$info['height'] = $matches['height'];
// our player supports flv and h264 so we might as well use the default
if( !$gBitSystem->isFeatureActive( 'mime_video_force_encode' ) && !empty( $info )
// accepted video + audio combinations that can be played by the video player directly
( $info['vcodec'] == 'h264' && ( empty( $info['acodec'] ) || $info['acodec'] == 'mpeg4aac' || $info['acodec'] == 'aac' ))
|| ( $info['vcodec'] == 'flv' && ( empty( $info['acodec'] ) || $info['acodec'] == 'mp3' ))
// work out what the target filename is
$extension = (( $info['vcodec'] == "flv" ) ? "flv" : "mp4" );
$dest_file = $destPath. "/flick.$extension";
// if the video can be processed by ffmpeg-php, width and height are greater than 1
if( !empty( $info['width'] ) && $info['width'] > 1 ) {
$info['aspect'] = $info['width'] / $info['height'];
$info['offset'] = strftime( "%T", round( $info['duration'] / 5 - ( 60 * 60 )));
// store prefs and create thumbnails
LibertyMime::expungeMetaData( $pParamHash['attachment_id'] );
LibertyMime::storeMetaData( $pParamHash['attachment_id'], 'Video', $info );
if( !is_file( $dest_file ) && !link( $source, $dest_file )) {
copy( $source, $dest_file );
$log['message'] = 'SUCCESS: Converted to flash video';
$actionLog['log_message'] = "Video file was successfully uploaded and thumbnails extracted.";
// work out what the target filename is
$extension = (( $codec == "flv" ) ? "flv" : "mp4" );
$dest_file = $destPath. "/flick.$extension";
// if the video can be processed by ffmpeg-php, width and height are greater than 1
if( !empty( $info['width'] ) && $info['width'] > 1 ) {
// reset some values to reduce video size
if( $info['width'] < $width ) {
// here we calculate the size and aspect ratio of the output video
$size_ratio = $width / $info['width'];
$info['aspect'] = $info['width'] / $info['height'];
$info['video_width'] = $width;
$info['video_height'] = round( $size_ratio * $info['height'] );
// height of video needs to be an even number
if( $info['video_height'] % 2 ) {
$info['size'] = "{ $info['video_width']}x{ $info['video_height']}";
// transfer settings to vars for easy manipulation for various APIs of ffmpeg
$audio_bitrate = ( $gBitSystem->getConfig( 'mime_video_audio_bitrate', 32000 ) / 1000 ). 'kb';
$audio_samplerate = $gBitSystem->getConfig( 'mime_video_audio_samplerate', 22050 );
$video_bitrate = ( $gBitSystem->getConfig( 'mime_video_video_bitrate', 160000 ) / 1000 ). 'kb';
$acodec_mp3 = $gBitSystem->getConfig( 'ffmpeg_mp3_lib', 'libmp3lame' );
$me_param = $gBitSystem->getConfig( 'ffmpeg_me_method', 'me' );
" -ar $audio_samplerate".
" -aspect ". $info['aspect'].
" -flags +loop -cmp +chroma -refs 1 -coder 0 -me_range 16 -g 300 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -maxrate 10M -bufsize 10M -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -level 30".
" -partitions +parti4x4+partp8x8+partb8x8 -$me_param epzs -subq 5 -trellis 1".
} elseif( $codec == "h264-2pass" ) {
// it is not possible to pass in the path for the x264 log file and it is always generated in the working dir.
$passlogfile = dirname( $dest_file ). "/ffmpeg2pass";
" -passlogfile $passlogfile".
" -aspect ". $info['aspect'].
" -flags +loop -cmp +chroma -refs 1 -coder 0 -me_range 16 -g 300 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -bf 16 -maxrate 10M -bufsize 10M -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4 -level 30".
" -partitions 0 -$me_param epzs -subq 1 -trellis 0".
" -ar $audio_samplerate".
" -passlogfile $passlogfile".
" -aspect ". $info['aspect'].
" -flags +loop -cmp +chroma -me_range 16 -g 250 -keyint_min 25 -sc_threshold 40 -i_qfactor 0.71 -rc_eq 'blurCplx^(1-qComp)' -qcomp 0.6 -qmin 10 -qmax 51 -qdiff 4".
" -partitions +parti8x8+parti4x4+partp8x8+partp4x4+partb8x8 -flags2 +brdo+dct8x8+wpred+bpyramid+mixed_refs -$me_param epzs -subq 7 -trellis 1 -refs 6 -bf 16 -directpred 3 -b_strategy 1 -bidir_refine 1 -coder 1".
" -ar $audio_samplerate".
" -aspect ". $info['aspect'].
if( $pOnlyGetParameters ) {
// we keep the output of this that we can store it to the error file if we need to do so
$debug = shell_exec( "$ffmpeg $parameters 2>&1" );
if( !empty( $parameters2 )) {
$debug .= shell_exec( "$ffmpeg $parameters2 2>&1" );
// change back to whence we came
// make sure the conversion was successfull
// try to work out a reasonable timepoint where to extract a screenshot
if( preg_match( '!Duration: ([\d:\.]*)!', $debug, $time )) {
list ( $h, $m, $s ) = explode( ':', $time[1] );
$seconds = round( 60 * 60 * (int) $h + 60 * (int) $m + (float) $s );
// we need to subract one hour from our time for strftime to return the correct value
$info['offset'] = strftime( "%T", round( $seconds / 5 - ( 60 * 60 )));
$info['offset'] = "00:00:10";
// store some video specific settings
LibertyMime::expungeMetaData( $pParamHash['attachment_id'] );
LibertyMime::storeMetaData( $pParamHash['attachment_id'], 'Video', $info );
// since the flv conversion worked, we will create a preview screenshots to show.
$log['message'] = 'SUCCESS: Converted to flash video';
$actionLog['log_message'] = "Converted to flashvideo in ". ( date( 'U' ) - $begin ). " seconds";
// remove unsuccessfully converted file
$log['message'] = "ERROR: The video you uploaded could not be converted by ffmpeg.\nDEBUG OUTPUT:\n\n". $debug;
$actionLog['log_message'] = "Video could not be converted to flashvideo. An error dump was saved to: ". $destPath. '/error';
// write error message to error file
$h = fopen( $destPath. "/error", 'w' );
fwrite( $h, "$ffmpeg $parameters\n\n$debug" );
@unlink( $destPath. '/processing' );
$log['time'] = date( 'd/M/Y:H:i:s O' );
$log['duration'] = date( 'U' ) - $begin;
// we'll insert some info into the database for reference
$actionLog['content_id'] = $pParamHash['content_id'];
$actionLog['title'] = "Uploaded file: {$pParamHash['upload']['name']} [Attchment ID: {$pParamHash['attachment_id']}]";
// if this all goes tits up, we'll know why
$pParamHash['log'] = $log;
// we'll add an entry in the action logs
LibertyContent::storeActionLogFromHash( array( 'action_log' => $actionLog ));
$pParamHash['log'] = $log;
* This function will create a thumbnail for a given video
* @param string $pFile path to video file
* @param numric $pOffset Offset in seconds to use to create thumbnail from
* @return TRUE on success, FALSE on failure
if( !empty( $pFile ) && is_file( $pFile )) {
// try to use an app designed specifically to extract a thumbnail
shell_exec( "$thumbnailer -i '$pFile' -o '$destPath/thumb.jpg' -s 1024" );
if( is_file( "$destPath/thumb.jpg" ) && filesize( "$destPath/thumb.jpg" ) > 1 ) {
$fileHash['type'] = 'image/jpg';
$fileHash['source_file'] = "$destPath/thumb.jpg";
@unlink( "$destPath/thumb.jpg" );
// fall back to using ffmepg
shell_exec( "$ffmpeg -i '$pFile' -an -ss $pOffset -t 00:00:01 -r 1 -y '$destPath/preview%d.jpg'" );
if( is_file( "$destPath/preview1.jpg" )) {
$fileHash['type'] = 'image/jpg';
$fileHash['source_file'] = "$destPath/preview1.jpg";
@unlink( "$destPath/preview1.jpg" );
* mime_video_calculate_videosize Calculate the display video size
* @param array $pFileHash File information including attachment_id
* @param array $pCommonObject common object - calculations will be stored in $pCommonObject->mStoragePrefs
global $gBitSystem, $gThumbSizes;
// fetch default if width is missing
if( empty( $pMetaData['width'] )) {
$pMetaData['width'] = $gBitSystem->getConfig( 'mime_video_width', 320 );
// use aspect to calculate height since it might be different from original
$pMetaData['height'] = ( $pMetaData['width'] / ( !empty( $pMetaData['aspect'] ) ? $pMetaData['aspect'] : 4 / 3 ));
// if we want to display a different size
if( !empty( $pParams['size'] ) && !empty( $gThumbSizes[$pParams['size']]['width'] )) {
$new_width = $gThumbSizes[$pParams['size']]['width'];
} elseif( $gBitSystem->isFeatureActive( 'mime_video_default_size' ) && !empty( $gThumbSizes[$gBitSystem->getConfig( 'mime_video_default_size' )]['width'] )) {
$new_width = $gThumbSizes[$gBitSystem->getConfig( 'mime_video_default_size' )]['width'];
// if we want to change the video size
if( !empty( $new_width )) {
$ratio = $pMetaData['width'] / $new_width;
$pMetaData['height'] = round( $pMetaData['height'] / $ratio );
// now that all calculations are done, we apply the width
$pMetaData['width'] = $new_width;
* mime_video_fix_streaming will make sure the MOOV atom is at the beginning of the MP4 file to enable streaming
* @param array $pVideoFile
* @return string shell result on success, FALSE on failure
$ret = shell_exec( $gBitSystem->getConfig( 'mp4box_path' ). " -add $pVideoFile -new $pVideoFile" );
|