* porting to Drupal 6 * @author Heshan Wanigasooriya * @todo */ /** * 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: ').'
  1. '.t('%videofile (the video file to thumbnail)').'
  2. '.t('%thumbfile (a newly created temporary file to overwrite with the thumbnail)').'
  3. '.t('%seek (seconds to seek into video before extracting image).').'
'.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' => "
-i %videofile -y -an -f mjpeg -ss %seek -t 0.001 %thumbfile
", '!new' => '
-i %videofile -an -y -f mjpeg -ss %seek -vframes 1 %thumbfile
')), '#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', true), ); $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.') . '
' . 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 click here for more informations."), '#default_value' => variable_get('video_ffmpeg_helper_auto_conversion', false), ); $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: ').''. 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, // for node previews ); } } } /** * 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: ')."
\n$tnail_output\n
"); 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.
Command Executed:
%cmd
Command Output:
%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 '
'. t('This video is currently being processed. Please wait.') . '
'; } /** * Display an "encoding failed" message" */ function theme_video_ffmpeg_helper_encoding_failed($node) { return '
'. t('The video conversion process has failed. You might want to submit a simpler video format like mpeg or divx avi.
If the problem persists please contact website administrators.') . '
'; } /** * 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), ), ); }