video_ffmpeg_helper.module in Video 6.2
Same filename and directory in other branches
Provide some api for use ffmpeg. Simplify video nodes creation.
@author Fabio Varesano <fvaresano at yahoo dot it> @author Heshan Wanigasooriya <heshan at heidisoft.com><heshanmw at gmail dot com> @todo 1. Remove modules and user existing ffmpeg wrapper module
File
plugins/video_ffmpeg_helper/video_ffmpeg_helper.moduleView source
<?php
/**
* @file
* Provide some api for use ffmpeg. Simplify video nodes creation.
*
* @author Fabio Varesano <fvaresano at yahoo dot it>
* @author Heshan Wanigasooriya <heshan at heidisoft.com><heshanmw at gmail dot com>
* @todo
* 1. Remove modules and user existing ffmpeg wrapper module
*/
/**
* Define some constants
*/
define('VIDEO_RENDERING_PENDING', 1);
define('VIDEO_RENDERING_ACTIVE', 5);
define('VIDEO_RENDERING_COMPLETE', 10);
define('VIDEO_RENDERING_FAILED', 20);
function video_ffmpeg_helper_cron() {
global $base_url;
if (variable_get('video_ffmpeg_helper_auto_cvr_cron', true)) {
exec("php video_scheduler.php {$base_url} > /dev/null &");
}
}
/**
* Implementatio of hook_perm()
*/
function video_ffmpeg_helper_perm() {
return array(
'bypass automatic video conversion',
);
}
/**
* Implementation of hook_help().
*/
function video_ffmpeg_helper_help($path, $arg) {
switch ($path) {
case 'admin/modules#description':
return t('Enable ffmpeg support for video module.');
}
}
/**
* Implementation of hook_menu()
*/
function video_ffmpeg_helper_menu() {
$items = array();
$items['admin/settings/video/ffmpeg_helper'] = array(
'title' => 'Video ffmpeg Helper',
'description' => 'Administer video_ffmpeg_helper module settings',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'video_ffmpeg_helper_admin_settings',
),
'access arguments' => array(
'administer site configuration',
),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Validation for settings form
*/
function video_ffmpeg_helper_admin_settings_validate($form, &$form_state) {
if (variable_get('video_image_auto_thumbnail', 0)) {
if (!_video_ffmpeg_helper_check_exe_path($form_state['values']['video_ffmpeg_helper_ffmpeg_path'])) {
form_set_error('video_ffmpeg_helper_ffmpeg_path', t('Set correct path for ffmpeg'));
}
if (!is_numeric($form_state['values']['video_ffmpeg_helper_auto_thumbnail_seek'])) {
form_set_error('video_ffmpeg_helper_auto_thumbnail_seek', t('Seek time must be an integer'));
}
$options = $form_state['values']['video_ffmpeg_helper_thumbnailer_options'];
if (!strstr($options, '%videofile') || !strstr($options, '%thumbfile')) {
form_set_error('video_ffmpeg_helper_thumbnailer_options', t('Thumbnail options must contain mandatory arguments %videofile and %thumbfile'));
}
}
}
/**
* Settings form
*/
function video_ffmpeg_helper_admin_settings() {
// let's execute after video_image and video_upload
if (module_exists('video_image') && variable_get('video_image_auto_thumbnail', 0)) {
$weight = db_result(db_query("SELECT weight FROM {system} WHERE name='video_image'"));
}
else {
// video_image might be disabled.. execute after video_upload
$weight = db_result(db_query("SELECT weight FROM {system} WHERE name='video_upload'"));
}
// update the weight in the system table
db_query("UPDATE {system} SET weight=" . ($weight + 1) . " WHERE name='video_ffmpeg_helper'");
$form['video_ffmpeg_helper_ffmpeg_path'] = array(
'#type' => 'textfield',
'#title' => t('FFmpeg executable path'),
'#description' => t('Set the full path to the ffmpeg executable here.'),
'#default_value' => variable_get('video_ffmpeg_helper_ffmpeg_path', '/usr/bin/ffmpeg'),
);
$form['video_ffmpeg_helper_auto_resolution'] = array(
'#type' => 'checkbox',
'#title' => t('Enable resolution helper'),
'#description' => t('Use ffmpeg Helper to automatically get the resolution from the video.'),
'#default_value' => variable_get('video_ffmpeg_helper_auto_resolution', false),
);
$form['video_ffmpeg_helper_auto_playtime'] = array(
'#type' => 'checkbox',
'#title' => t('Enable playtime helper'),
'#description' => t('Use ffmpeg Helper to automaticcally get the playtime from the video.'),
'#default_value' => variable_get('video_ffmpeg_helper_auto_playtime', false),
);
$form['autothumb'] = array(
'#type' => 'fieldset',
'#title' => t('Automatic video thumbnailing'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['autothumb']['video_ffmpeg_helper_thumbnailer_options'] = array(
'#type' => 'textfield',
'#title' => t('Video thumbnailer options'),
'#description' => t('Provide the options for the thumbnailer. Available argument values are: ') . '<ol><li>' . t('%videofile (the video file to thumbnail)') . '<li>' . t('%thumbfile (a newly created temporary file to overwrite with the thumbnail)') . '<li>' . t('%seek (seconds to seek into video before extracting image).') . '</ol>' . t('Only the first two are mandatory. For example, older versions of ffmpeg should use something like: !old While newer versions should use something like: !new', array(
'!old' => "<div>-i %videofile -y -an -f mjpeg -ss %seek -t 0.001 %thumbfile</div>",
'!new' => '<div>-i %videofile -an -y -f mjpeg -ss %seek -vframes 1 %thumbfile</div>',
)),
'#default_value' => variable_get('video_ffmpeg_helper_thumbnailer_options', '-i %videofile -an -y -f mjpeg -ss %seek -vframes 1 %thumbfile'),
);
$form['autothumb']['video_ffmpeg_helper_auto_thumbnail_seek'] = array(
'#type' => 'textfield',
'#title' => t('Video seek offset for thumbnail'),
'#description' => t('Time in seconds to seek into video before extracting the thumbnail'),
'#default_value' => variable_get('video_ffmpeg_helper_auto_thumbnail_seek', 2),
);
// automatic video conversion settings
$form['autoconv'] = array(
'#type' => 'fieldset',
'#title' => t('Automatic video conversion'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['autoconv']['video_ffmpeg_helper_auto_cvr_cron'] = array(
'#type' => 'checkbox',
'#title' => t('Use drupal cron for autoconversion'),
'#description' => t('Click this if you want to execute the video_scheduler.php from the standard drupal cron. If you want to use distributed encodings you might want to disable this.'),
'#default_value' => variable_get('video_ffmpeg_helper_auto_cvr_cron', FALSE),
);
$form['autoconv']['video_ffmpeg_helper_auto_conversion'] = array(
'#type' => 'checkbox',
'#title' => t('Auto conversion for videos'),
'#description' => t('If set up correctly, this will auto-convert each uploaded video to the configured format.') . '<br />' . t("IMPORTANT: you will need the video_render.php correctly configured and run by cron. See README.txt in the video_ffmpeg_helper folder or <a title='User Guide' href='http://video.heidisoft.com/docs/users-guide-26'>click here</a> for more informations."),
'#default_value' => variable_get('video_ffmpeg_helper_auto_conversion', TRUE),
);
$form['autoconv']['video_ffmpeg_helper_auto_cvr_width'] = array(
'#type' => 'textfield',
'#title' => t('Video rendering width'),
'#description' => t('The width of the converted video. The height will be automatically calculated to maintain aspect ratio.'),
'#size' => 3,
'#maxlength' => 3,
'#default_value' => variable_get('video_ffmpeg_helper_auto_cvr_width', 400),
);
$form['autoconv']['video_ffmpeg_helper_auto_cvr_video_bitrate'] = array(
'#type' => 'textfield',
'#title' => t('Video bitrate'),
'#description' => t('The video bitrate in bit/s of the converted video.'),
'#size' => 10,
'#maxlength' => 10,
'#default_value' => variable_get('video_ffmpeg_helper_auto_cvr_video_bitrate', 200000),
);
$form['autoconv']['video_ffmpeg_helper_auto_cvr_audio_bitrate'] = array(
'#type' => 'textfield',
'#title' => t('Audio bitrate'),
'#description' => t('The audio bitrate in bit/s of the converted video.'),
'#size' => 10,
'#maxlength' => 10,
'#default_value' => variable_get('video_ffmpeg_helper_auto_cvr_audio_bitrate', 64000),
);
$form['autoconv']['advanced'] = array(
'#type' => 'fieldset',
'#title' => t('Advanced settings'),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['autoconv']['advanced']['video_ffmpeg_helper_auto_cvr_options'] = array(
'#type' => 'textfield',
'#title' => t('Video converter options'),
'#description' => t('Provide the ffmpeg options to configure the video conversion. Available argument values are: ') . '<ul>' . '<li>' . t('%videofile (the video file to convert)') . '<li>' . t('%convertfile (a newly created file to store the converted file)') . '<li>' . t('%size (video resolution of the converted file)') . '</ul>' . t('For further informations refer to the !ffmpegdoc', array(
'!ffmpegdoc' => l(t('Official FFMpeg documentation.'), 'http://ffmpeg.mplayerhq.hu/ffmpeg-doc.html', array(
'fragment' => TRUE,
)),
)),
'#default_value' => variable_get('video_ffmpeg_helper_auto_cvr_options', '-y -i %videofile -f flv -ar 22050 -ab %audiobitrate -s %size -b %videobitrate -qscale 1 %convertfile'),
);
return system_settings_form($form);
}
/**
* Implementation of hook_form_alter()
*/
function video_ffmpeg_helper_form_alter(&$form, &$form_state, $form_id) {
$node = $form['#node'];
//print_r($form);
if ($form_id == 'video_node_form') {
if (function_exists('_image_check_settings')) {
_image_check_settings();
$form['#attributes'] = array(
"enctype" => "multipart/form-data",
);
}
if ($node->vtype == 'upload' && user_access('bypass automatic video conversion') && variable_get('video_ffmpeg_helper_auto_conversion', false)) {
$form['video']['video_ffmpeg_helper_convertion_bypass'] = array(
'#type' => 'checkbox',
'#title' => t('Bypass automatic video conversion'),
'#description' => t('Check this if you want that your video is submitted as it is, without being converted.'),
'#default_value' => $node->video_ffmpeg_helper_convertion_bypass,
);
}
}
}
/**
* Implementation of hook_nodeapi()
*
* NOTE: video_ffmpeg nodeapi is executed after video_player rendering
*/
function video_ffmpeg_helper_nodeapi(&$node, $op, $teaser) {
if ($node->type == 'video' && $node->vtype == 'upload') {
switch ($op) {
case 'load':
// let's check if we have a valid encoded video
if (isset($node->serial_data['video_encoded_fid']) && is_numeric($node->serial_data['video_encoded_fid']) && $node->serial_data['video_encoded_fid'] > 0) {
// this video have an encoded version. let's use it insted of the original one. We can safely do this as video_ffmpeg_helper will be scheduled by Drupal after video and video_upload modules
$output = array();
$file = _video_upload_get_file($node->serial_data['video_encoded_fid']);
$output['current_video_rendered_file'] = $file;
$output['vidfile'] = file_create_url($file->filepath);
// set the filesize
$output['size'] = $file->filesize;
return $output;
}
//print $node->serial_data['video_encoded_fid']; die;
if (variable_get('video_ffmpeg_helper_auto_resolution', false) || variable_get('video_ffmpeg_helper_auto_playtime', false)) {
_video_ffmpeg_helper_get_video_info($node);
}
break;
case 'presve':
break;
case 'update':
/* delete the already existing batch script, we'll recreate it below */
if (variable_get('video_ffmpeg_helper_auto_conversion', false)) {
db_query('DELETE FROM {video_rendering} WHERE vid = %d AND nid = %d', $node->vid, $node->nid);
}
/* FALLTHROUGH */
case 'insert':
if (variable_get('video_ffmpeg_helper_auto_conversion', false) && $node->new_video_upload_file_fid > 0 && !$node->video_ffmpeg_helper_convertion_bypass) {
// add rendering job to queue
_video_ffmpeg_helper_add_rendering($node);
}
break;
case 'prepare':
// for future uses
break;
case 'view':
if ($teaser == FALSE) {
if (_video_ffmpeg_helper_is_being_processed($node)) {
// if the video is still being processed we display a "rendering in progress" message
$node->content['video_player']['#value'] = theme('video_ffmpeg_helper_inprogress', $node);
}
else {
if ($node->serial_data['video_encoded_fid'] == -1) {
// conversion failed
$node->content['video_player']['#value'] = theme('video_ffmpeg_helper_encoding_failed', $node);
}
}
}
//print_r($node); die;
break;
case 'alter':
// for future uses
break;
case 'delete':
db_query('DELETE FROM {video_rendering} WHERE vid = %d AND nid = %d', $node->vid, $node->nid);
if ($node->serial_data['video_encoded_fid'] > 0) {
$file = _video_upload_get_file($node->serial_data['video_encoded_fid']);
_video_upload_delete_file($file);
}
break;
}
}
}
/**
* Add a video conversion rendering process to the queue
*/
function _video_ffmpeg_helper_add_rendering(&$node) {
$file = _video_upload_get_file($node->new_video_upload_file_fid);
//print_r($node); die;
db_query('INSERT INTO {video_rendering} (vid, nid, origfile, pid, status, started, completed) VALUES (%d, %d, \'%s\', %d, %d, %d, %d)', $node->vid, $node->nid, $file->filepath, 0, VIDEO_RENDERING_PENDING, 0, 0);
drupal_set_message(t('Video submission queued for processing. Please wait: our servers are preparing your video for web displaying.'));
// let's add the rendering in progress video
$node->vidfile = variable_get('video_ffmpeg_helper_auto_cvr_busy_video_path', 'busy.flv');
db_query('UPDATE {video} SET vidfile = \'%s\' WHERE nid=%d AND vid=%d', $node->vidfile, $node->nid, $node->vid);
}
/**
* Returns true if the video is being encoded or queeded
*/
function _video_ffmpeg_helper_is_being_processed($node) {
$result = db_query("SELECT status FROM {video_rendering} WHERE vid = %d AND nid = %d", $node->vid, $node->nid);
$status = db_result($result);
if ($status == VIDEO_RENDERING_PENDING || $status == VIDEO_RENDERING_ACTIVE) {
// video is still being converted
return TRUE;
}
return FALSE;
}
/**
* Get some informations from the video file
*/
function _video_ffmpeg_helper_get_video_info(&$node, $value = null) {
static $ffmpeg_info;
if (isset($value)) {
$ffmpeg_info[$node->nid] = $value;
return;
}
$fileobj = $node->new_video_upload_file ? $node->new_video_upload_file : _video_upload_get_file($node->new_video_upload_file_fid);
// check if we have some info in the cache for the given node
if (isset($ffmpeg_info[$fileobj->filename])) {
return $ffmpeg_info[$fileobj->filename];
}
// escape file name for safety
$file = escapeshellarg($fileobj->filepath);
// create the full command to execute
$command = variable_get('video_ffmpeg_helper_ffmpeg_path', '/usr/bin/ffmpeg') . ' -i ' . $file;
//execute the command
ob_start();
passthru($command . " 2>&1", $command_return);
$command_output = ob_get_contents();
ob_end_clean();
// cache the result for further calls
$ffmpeg_info[$node->nid] = $command_output;
return $command_output;
}
/**
* Return the video resolution
*/
function _video_ffmpeg_helper_auto_resolution(&$node) {
if (variable_get('video_ffmpeg_helper_auto_resolution', false)) {
// call ffmpeg -i
$ffmpeg_output = _video_ffmpeg_helper_get_video_info($node);
// get resolution
$pattern = '/Video: .*, ([0-9]{2,4}x[0-9]{2,4})/';
preg_match_all($pattern, $ffmpeg_output, $matches, PREG_PATTERN_ORDER);
$resolution = $matches[1][0];
return explode("x", $resolution);
}
return null;
}
/**
* Return the playtime seconds of a video
*/
function _video_ffmpeg_helper_auto_playtime(&$node) {
if (variable_get('video_ffmpeg_helper_auto_playtime', false)) {
// call ffmpeg -i
$ffmpeg_output = _video_ffmpeg_helper_get_video_info($node);
// get playtime
$pattern = '/Duration: ([0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9])/';
preg_match_all($pattern, $ffmpeg_output, $matches, PREG_PATTERN_ORDER);
$playtime = $matches[1][0];
// ffmpeg return lenght as 00:00:31.1 Let's get playtime from that
$hmsmm = explode(":", $playtime);
$tmp = explode(".", $hmsmm[2]);
$seconds = $tmp[0];
$hours = $hmsmm[0];
$minutes = $hmsmm[1];
return $seconds + $hours * 3600 + $minutes * 60;
}
}
/**
* Generates a thumbnail from the video file
*
* @param $node
* object with node information
*
* @return
* a drupal file object
*/
function _video_ffmpeg_helper_auto_thumbnail(&$node) {
if (!$node->new_video_upload_file || $node->new_video_upload_file && count($_POST) && $_POST['new_video_upload_file_fid']) {
// we have already thumbnailed this new upload file
return NULL;
}
if (!$node->new_video_upload_file && $node->current_video_upload_file_fid) {
// no new files uploaded. skipping thumnailing stuff
_video_image_thumbnail_debug(t('No new files to thumbnail'));
return NULL;
}
// gets the newly uploaded file object
$uploaded_file = $node->new_video_upload_file;
// are we debugging?
// escape the filename for safety
$videofile = escapeshellarg($uploaded_file->filepath);
// let's create a temp filename into the drupal temp directory
$thumbfile = tempnam(file_directory_temp(), 'tnail-thumb');
// get ffmpeg configurations
$seek = variable_get('video_ffmpeg_helper_auto_thumbnail_seek', 2);
$tnail = variable_get('video_ffmpeg_helper_ffmpeg_path', '/usr/bin/ffmpeg');
$options = preg_replace(array(
'/%videofile/',
'/%thumbfile/',
'/%seek/',
), array(
$videofile,
$thumbfile,
$seek,
), variable_get('video_image_thumbnailer_options', '-i %videofile -an -y -f mjpeg -ss %seek -vframes 1 %thumbfile'));
// executes the command
$command = "{$tnail} {$options}";
ob_start();
passthru($command . " 2>&1", $tnail_return);
$tnail_output = ob_get_contents();
ob_end_clean();
_video_ffmpeg_helper_get_video_info($node, $tnail_output);
_video_image_thumbnail_debug(t('Thumbnailer command: ') . $command);
_video_image_thumbnail_debug(t('Thumbnailer output: ') . "<pre>\n{$tnail_output}\n</pre>");
if (!file_exists($thumbfile)) {
$error_param = array(
'%file' => $thumbfile,
'%cmd' => $command,
'%out' => $tnail_output,
);
$error_msg = t("error generating thumbnail for video: generated file %file does not exist.<br />Command Executed:<br />%cmd<br />Command Output:<br />%out", $error_param);
// let's log this
watchdog('video_ffmpeg_helper', $error_msg);
return false;
}
$file = array(
'filename' => $uploaded_file->filename . ".video-thumb.jpg",
'filemime' => 'image/jpeg',
'filesize' => filesize($thumbfile),
'filepath' => $thumbfile,
'nid' => $node->nid,
);
if ($tnail_return) {
_video_image_thumbnail_debug(t('Failed to thumbnail video'));
return $false;
}
_video_image_thumbnail_debug(t('Successfully thumbnailed video'));
return (object) $file;
}
function _video_ffmpeg_helper_check_exe_path($path = NULL) {
if (!$path) {
$path = variable_get('video_ffmpeg_helper_ffmpeg_path', '/usr/bin/ffmpeg');
}
if (function_exists('is_executable')) {
$test = 'is_executable';
}
else {
$test = 'file_exists';
}
return $test($path);
}
/**
* Displays a "encoding in progress message"
*/
function theme_video_ffmpeg_helper_inprogress($node) {
return '<div class="video-ffmpeg-helper-inprogress">' . t('This video is currently being processed. Please wait.') . '</div>';
}
/**
* Display an "encoding failed" message"
*/
function theme_video_ffmpeg_helper_encoding_failed($node) {
return '<div class="video-ffmpeg-helper-encoding-failed">' . t('The video conversion process has failed. You might want to submit a simpler video format like <em>mpeg</em> or <em>divx avi</em>.<br />If the problem persists please contact website administrators.') . '</div>';
}
/**
* Implementation of hook_theme().
*/
function video_ffmpeg_helper_theme() {
return array(
'video_ffmpeg_helper_encoding_failed' => array(
'arguments' => array(
'node' => NULL,
),
),
'video_ffmpeg_helper_inprogress' => array(
'arguments' => array(
'node' => NULL,
),
),
);
}
Functions
Constants
Name | Description |
---|---|
VIDEO_RENDERING_ACTIVE | |
VIDEO_RENDERING_COMPLETE | |
VIDEO_RENDERING_FAILED | |
VIDEO_RENDERING_PENDING | Define some constants |