diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/conversion.inc | 262 | ||||
-rw-r--r-- | includes/metadata.inc | 57 | ||||
-rw-r--r-- | includes/transcoder.inc | 96 | ||||
-rw-r--r-- | includes/video_helper.inc | 116 |
4 files changed, 531 insertions, 0 deletions
diff --git a/includes/conversion.inc b/includes/conversion.inc new file mode 100644 index 0000000..3f1165e --- /dev/null +++ b/includes/conversion.inc @@ -0,0 +1,262 @@ +<?php +//$Id$ +/* + * @file + * Class file to handle video conversion using ffmpeg. + * + */ +define('VIDEO_RENDERING_PENDING', 1); +define('VIDEO_RENDERING_ACTIVE', 5); +define('VIDEO_RENDERING_COMPLETE', 10); +define('VIDEO_RENDERING_FAILED', 20); + +class video_conversion { + + /** + * Our main function to call when converting queued up jobs. + */ + public function run_queue() { + if ($videos = $this->select_queue()) { + foreach ($videos as $video) { + $this->process($video); + } + } + } + + /** + * Select videos from our queue + * + * @return + * An array containing all the videos to be proccessed. + */ + private function select_queue() { + $total_videos = variable_get('video_ffmpeg_instances', 5); + $videos = array(); + $result = db_query_range('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status FROM {video_files} vf LEFT JOIN {files} f ON vf.fid = f.fid WHERE vf.status = %d AND f.status = %d ORDER BY f.timestamp', + VIDEO_RENDERING_PENDING, FILE_STATUS_PERMANENT, 0, $total_videos); + + while ($row = db_fetch_object($result)) { + $videos[] = $row; + } + return $videos; + } + + /** + * Process the video through ffmpeg. + * + * @param $video + * This can either be the file object or the file id (fid) + * + * @return + * TRUE of FALSE if video was converted successfully. + */ + public function process($video) { + if (is_object($video) && isset($video->fid)) { + $return = $this->render($video); + } + else { + $video_object = $this->load_video($video); + $return = $this->render($video_object); + } + return $return; + } + + private function render($video) { + if (!is_object($video)) { + watchdog('video_conversion', 'Video object is not present', array(), WATCHDOG_ERROR); + return FALSE; + } + // Make sure this video is pending or do nothing. + if($video->video_status == VIDEO_RENDERING_PENDING) { + // This will update our current video status to active. + $this->change_status($video->vid, VIDEO_RENDERING_ACTIVE); + // Get the converted file object + //we are going to move our video to an "original" folder + //we are going to transcode the video to the "converted" folder + $pathinfo = pathinfo($video->filepath); + $original = $pathinfo['dirname'] .'/original'; + $converted = $pathinfo['dirname'] .'/converted'; + + if (!field_file_check_directory($original, FILE_CREATE_DIRECTORY)) { + watchdog('video_transcoder', 'Video conversion failed. Could not create the directory: '.$orginal, array(), WATCHDOG_ERROR); + return false; + } + if (!field_file_check_directory($converted, FILE_CREATE_DIRECTORY)) { + watchdog('video_transcoder', 'Video conversion failed. Could not create the directory: '.$converted, array(), WATCHDOG_ERROR); + return false; + } + + $original = $original .'/'. $video->filename; + //lets move our video and then convert it. + if(file_move($video, $original)) { + //update our filename after the move to maintain filename uniqueness. + $converted = $converted .'/'. pathinfo($video->filepath, PATHINFO_FILENAME) .'.'. $this->video_extension(); + // Update our filepath since we moved it + $update = drupal_write_record('files', $video, 'fid'); + //call our transcoder + $command_output = $this->convert_video($video, $converted); + //lets check to make sure our file exists, if not error out + if(!file_exists($converted) || !filesize($converted)) { + watchdog('video_conversion', 'Video conversion failed. FFMPEG reported the following output: '.$command_output, array(), WATCHDOG_ERROR); + $this->change_status($video->vid, VIDEO_RENDERING_FAILED); + return FALSE; + } + // Setup our converted video object + $video_info = pathinfo($converted); + //update our converted video + $video->converted = new stdClass(); + $video->converted->vid = $video->vid; + $video->converted->filename = $video_info['basename']; + $video->converted->filepath = $converted; + $video->converted->filemime = file_get_mimetype($converted); + $video->converted->filesize = filesize($converted); + $video->converted->status = VIDEO_RENDERING_COMPLETE; + $video->converted->completed = time(); + //clear our cache so our video path is updated. + cache_clear_all('*', 'cache_content', true); + // Update our video_files table with the converted video information. + $result = db_query("UPDATE {video_files} SET filename='%s', filepath='%s', filemime='%s', filesize=%d, status=%d, completed=%d WHERE vid=%d", + $video->converted->filename, $video->converted->filepath, $video->converted->filemime, $video->converted->filesize, $video->converted->status, $video->converted->completed, $video->converted->vid); + + // Update our node id to published. We do not do a node_load as it causes editing problems when saving. + db_query("UPDATE {node} SET status=%d WHERE nid=%d", 1, $video->nid); + watchdog('video_conversion', 'Successfully converted %orig to %dest', array('%orig' => $video->filepath, '%dest' => $video->converted->filepath), WATCHDOG_INFO); + return TRUE; + } + else { + watchdog('video_conversion', 'Cound not move the video to the original folder.', array(), WATCHDOG_ERROR); + $this->change_status($video->vid, VIDEO_RENDERING_FAILED); + return FALSE; + } + } + return NULL; + } + + /** + * Calls the transcoder class to convert the video. + * + * @param $job + * Video object to be transcoded + * + * @return + * TRUE or FALSE + */ + private function convert_video($video, $converted) { + //get our dimensions and pass them along. + $dimensions = $this->dimensions($video); + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + return $transcoder->convert_video($video, $converted, $dimensions); + } + + private function video_extension() { + module_load_include('inc', 'video', '/includes/transcoder'); + $transcoder = new video_transcoder; + return $transcoder->video_converted_extension(); + } + + /* + * Function determines the dimensions you want and compares with the actual wxh of the video. + * + * If they are not exact or the aspect ratio does not match, we then figure out how much padding + * we should add. We will either add a black bar on the top/bottom or on the left/right. + * + * @TODO I need to look more at this function. I don't really like the guess work here. Need to implement + * a better way to check the end WxH. Maybe compare the final resolution to our defaults? I don't think + * that just checking to make sure the final number is even is accurate enough. + */ + public function dimensions($video) { + //lets setup our dimensions. Make sure our aspect ratio matches the dimensions to be used, if not lets add black bars. + $aspect_ratio = _video_aspect_ratio($video->filepath); + $ratio = $aspect_ratio['ratio']; + $width = $aspect_ratio ['width']; + $height = $aspect_ratio['height']; + + $wxh = explode('x', $video->dimensions); + $output_width = $wxh[0]; + $output_height = $wxh[1]; + $output_ratio = number_format($output_width / $output_height, 4); + + if($output_ratio != $ratio && $width && $height) { + $options = array(); + // Figure out our black bar padding. + if ($ratio < $output_width / $output_height) { + $end_width = $output_height * $ratio; + $end_height = $output_height; + } + else { + $end_height = $output_width / $ratio; + $end_width = $output_width; + } + + // We need to get back to an even resolution and maybe compare with our defaults? + // @TODO Make this more exact on actual video dimensions instead of making sure the wxh are even numbers + + if ($end_width == $output_width) { + // We need to pad the top/bottom of the video + $padding = round($output_height - $end_height); + $pad1 = $pad2 = floor($padding / 2); + if ($pad1 %2 !== 0) { + $pad1++; + $pad2--; + } + $options[] = '-padtop '. $pad1; + $options[] = '-padbottom '. $pad2; + } + else { + // We are padding the left/right of the video. + $padding = round($output_width - $end_width); + $pad1 = $pad2 = floor($padding / 2); //@todo does padding need to be an even number? + if ($pad1 %2 !== 0) { + $pad1++; + $pad2--; + } + $options[] = '-padleft '. $pad1; + $options[] = '-padright '. $pad2; + } + + $end_width = round($end_width) %2 !==0 ? round($end_width) + 1 : round($end_width); + $end_height = round($end_height) %2 !==0 ? round($end_height) + 1 : round($end_height); + //add our size to the beginning to make sure it hits our -s + array_unshift($options, $end_width .'x'. $end_height); + return implode(' ', $options); + } + else { + return $video->dimensions; + } + } + + /** + * Load a file based on the file id ($fid) + * + * @param $fid + * Integer of the file id to be loaded. + */ + public function load_video($fid) { + $result = db_query('SELECT f.*, vf.vid, vf.nid, vf.dimensions, vf.status as video_status FROM {video_files} vf LEFT JOIN {files} f ON vf.fid = f.fid WHERE f.fid=vf.fid AND f.fid = %d', $fid); + return db_fetch_object($result); + } + + /** + * Load a converted video based on the file id ($fid) + * + * @todo: Need to figure something out here for multiple files (HTML 5) + * @param $fid + * Integer of the file id to be loaded. + */ + public function load_converted_video($fid) { + $result = db_query('SELECT * FROM {video_files} WHERE fid = %d', $fid); + return db_fetch_object($result); + } + + /** + * Change the status of the file. + * + * @param (int) $vid + * @param (int) $status + */ + public function change_status($vid, $status) { + $result = db_query('UPDATE {video_files} SET status = %d WHERE vid = %d ', $status, $vid); + } +} +?>
\ No newline at end of file diff --git a/includes/metadata.inc b/includes/metadata.inc new file mode 100644 index 0000000..905a7fa --- /dev/null +++ b/includes/metadata.inc @@ -0,0 +1,57 @@ +<?php +//$Id$ +/* + * @file + * Class file used to store metadata on the video. + * + */ + +class video_metadata { + protected $params = array(); + protected $nice = 'nice -n 19'; + protected $meta_command = '-UP'; + protected $meta_command_path = '/usr/bin/flvtool2'; + + public function __construct() { + $this->params['cmd_path'] = variable_get('video_metadata_path', $this->meta_command_path); + } + + public function run_command($options) { + $command = $this->nice .' '. $this->params['cmd_path'].' '.$options.' 2>&1'; + watchdog('video_metadata', 'Executing command: '. $command, array(), WATCHDOG_DEBUG); + ob_start(); + passthru($command, $command_return); + $output = ob_get_contents(); + ob_end_clean(); + return $output; + } + + public function process($video) { + $command_output = $this->run_command($this->meta_command .' '. $video); + return $command_output; + } + + + public function admin_settings() { + $form = array(); + $form['video_metadata'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Metadata'), + '#default_value' => variable_get('video_metadata', FALSE), + '#description' => t('Enables metadata for videos by using flvtool2. It cuts FLV files and adds cue Points (onCuePoint). If you are converting your files to FLV then this is highly recommended.'), + ); + $form['video_metadata_path'] = array( + '#type' => 'textfield', + '#title' => t('Path to FLVTool2'), + '#description' => t('Absolute path to flvtool2.'), + '#default_value' => variable_get('video_metadata_path', $this->meta_command_path), + ); + $form['video_metadata_dimensions'] = array( + '#type' => 'textarea', + '#title' => t('Selectable Dimensions when uploading videos.'), + '#description' => t('Enter one dimension per line as Video Resolutions. Each resolution must be in the form of WxH where W=Width and H=Height in pixels. Example dimensions are 1280x720.'), + '#default_value' => variable_get("video_metadata_dimensions", video_default_dimensions()), + ); + return $form; + } +}
\ No newline at end of file diff --git a/includes/transcoder.inc b/includes/transcoder.inc new file mode 100644 index 0000000..96b9f57 --- /dev/null +++ b/includes/transcoder.inc @@ -0,0 +1,96 @@ +<?php +//$Id$ +/* + * @file + * Class file used to wrap the transcoder functions. + * + * @todo need more commenting + */ + +class video_transcoder { + private $transcoder; + + public function __construct() { + //get our configured transcoder. + $transcoder = variable_get('vid_convertor', 'video_ffmpeg'); + module_load_include('inc', 'video', '/transcoders/' . $transcoder); + if(class_exists($transcoder)) { + $this->transcoder = new $transcoder; + } + else { + drupal_set_message(t('The transcoder is not configured properly.'), 'error'); + } + } + + public function generate_thumbnails($video) { + return $this->transcoder->generate_thumbnails($video); + } + + public function convert_video($video, $converted, $dimensions) { + $output = $this->transcoder->convert_video($video, $converted, $dimensions); + // If they are using metadata. + if (variable_get('video_metadata', FALSE)) { + module_load_include('inc', 'video', '/includes/metadata'); + $metadata = new video_metadata; + $metadata->process($converted); + } + return $output; + } + + public function admin_settings() { + $form = array(); + $options = $this->_transcoders(); + $form['vid_convertor'] = array( + '#type' => 'radios', + '#title' => t('Video transcoder'), + '#default_value' => variable_get('vid_convertor', 'video_ffmpeg'), + '#options' => $options['radios'], + '#description' => t('Selecting a video transcoder will help you convert videos and generate thumbnails. !list', array('!list' => theme('item_list', $options['help']))), + '#prefix' => '<div id="transcoder-radios">', + '#suffix' => '</div>', + ); + $form = $form + $options['admin_settings']; + return $form; + } + + private function _transcoders() { + // Lets find our transcoder classes and build our radio options + // We do this by scanning our transcoders folder + $form = array('radios' => array(), 'help' => array(), 'admin_settings' => array()); + $path = drupal_get_path('module', 'video') .'/transcoders'; + $files = file_scan_directory($path, '^.*\.inc$'); + foreach($files as $file) { + module_load_include('inc', 'video', '/transcoders/' . $file->name); + $focus = new $file->name; + $form['radios'][$focus->get_value()] = $focus->get_name(); + $form['help'][] = $focus->get_help(); + $form['admin_settings'] = $form['admin_settings'] + $focus->admin_settings(); + } + //we need to move our video/thumbnail fieldsets to the bottom of our form as they are used for each trancoder + $autothumb = $form['admin_settings']['autothumb']; + $autoconv = $form['admin_settings']['autoconv']; + unset($form['admin_settings']['autothumb'], $form['admin_settings']['autoconv']); + $form['admin_settings']['autothumb'] = $autothumb; + $form['admin_settings']['autoconv'] = $autoconv; + return $form; + } + + public function get_dimensions($video) { + return $this->transcoder->get_dimensions($video); + } + + public function video_converted_extension() { + return $this->transcoder->video_converted_extension(); + } +} + +interface transcoder_interface { + public function run_command($command); + public function generate_thumbnails($video); + public function convert_video($video, $converted, $dimensions); + public function get_playtime($video); + public function get_name(); + public function get_value(); + public function get_help(); + public function admin_settings(); +}
\ No newline at end of file diff --git a/includes/video_helper.inc b/includes/video_helper.inc new file mode 100644 index 0000000..3eaa5d1 --- /dev/null +++ b/includes/video_helper.inc @@ -0,0 +1,116 @@ +<?php +//$Id$ +/* + * @file + * Class file used to create our video and thumbnail objects. + * + */ + +class video_helper { + + public function video_object($element) { + $field = content_fields($element['#field_name'], $element['#type_name']); + //setup our width x height + $dimensions = explode("x", $element['#item']['data']['dimensions']); + $player_dimensions = explode("x", $element['#item']['data']['player_dimensions']); + if(!isset($dimensions[0]) || !isset($dimensions[1])) { + $dimensions = explode("x", $field['widget']['default_dimensions']); + if(!isset($dimensions[0]) || !isset($dimensions[1])) { + drupal_set_message(t('Something is wrong with your dimensions. Make sure you enter dimensions in the form of WxH.'), 'error'); + } + } + if(!isset($player_dimensions[0]) || !isset($player_dimensions[1])) { + $player_dimensions = explode("x", $field['widget']['default_player_dimensions']); + if(!isset($player_dimensions[0]) || !isset($player_dimensions[1])) { + drupal_set_message(t('Something is wrong with your player dimensions. Make sure you enter the player dimensions in the form of WxH.'), 'error'); + } + } + + // Build our video object for all types. + $video = new stdClass(); + $video->fid = $element['#item']['fid']; + $video->original = $element['#item']; + $video->filepath = $element['#item']['filepath']; + $video->url = file_create_url($element['#item']['filepath']); + $video->extension = pathinfo($element['#item']['filename'], PATHINFO_EXTENSION); + $video->width = trim($dimensions[0]); + $video->height = trim($dimensions[1]); + $video->player_width = trim($player_dimensions[0]); + $video->player_height = trim($player_dimensions[1]); + $video->thumbnail = $this->thumbnail_object($element); + $video->formatter = $element['#formatter']; + $video->autoplay = variable_get('video_autoplay', TRUE); + $video->autobuffering = variable_get('video_autobuffering', TRUE); + $video->theora_player = variable_get('video_ogg_player', 'http://theora.org/cortado.jar'); + + // TODO : add hook_video_load API to load videos + // Lets find out if we have pushed this file to the cdn if enabled. + $cdn = false; + if(variable_get('amazon_s3', FALSE)) { + module_load_include('inc', 'video_s3', '/includes/amazon_s3'); + $s3 = new video_amazon_s3; + if($amazon = $s3->get($video->fid)) { + $cdn = true; + // Fix our filepath + $video->filepath = $amazon->filepath; + $video->url = $amazon->filepath; + $video->extension = pathinfo($amazon->filepath, PATHINFO_EXTENSION); + } + } + // If no cdn, lets find out if we have transcoded this file and update our paths. + if(!$cdn) { + //lets find out if we are overriding this video with a converted one. + if (isset($field['widget']['autoconversion']) && $field['widget']['autoconversion'] && !$element['#item']['data']['bypass_autoconversion']) { + module_load_include('inc', 'video', '/includes/conversion'); + $conversion = new video_conversion; + $converted = $conversion->load_converted_video($video->fid); + $video->filepath = $converted->filepath; + $video->url = file_create_url($converted->filepath); + $video->extension = pathinfo($converted->filepath, PATHINFO_EXTENSION); + } + } + // Moved to last to recheck incase we changed our extension above. + $video->flash_player = variable_get('video_extension_'.$video->extension.'_flash_player', ''); + + // Return our object + return $video; + } + + public function thumbnail_object($element) { + $field = content_fields($element['#field_name'], $element['#type_name']); + // Build our thumbnail object + $thumbnail = new stdClass(); + $thumbnail->filepath = ''; + $thumbnail->url = ''; + //@todo future enhancements for our thumbnails + $thumbnail->alt = ''; + $thumbnail->title = ''; + $thumbnail->description = ''; + + // Setup our thumbnail path. + $use_default_img = $element['#item']['data']['use_default_video_thumb']; + if ($use_default_img && !empty($field['widget']['default_video_thumb']['filepath'])) { + $thumbnail->filepath = $field['widget']['default_video_thumb']['filepath']; + } + elseif ($element['#item']['data']['video_thumb']) { + $thumbnail->filepath = $element['#item']['data']['video_thumb']; + } + else { + //need some type of default if nothing is present + //drupal_set_message(t('No thumbnail has been configured for the video.'), 'error'); + } + //lets check for an imagecache preset + if (isset($element['imagecache_preset'])) { + $thumbnail->url = imagecache_create_url($element['imagecache_preset'], $thumbnail->filepath); + $thumbnail->filepath = imagecache_create_path($element['imagecache_preset'], $thumbnail->filepath); + } else { + $thumbnail->url = file_create_url($thumbnail->filepath); + } + + //swftools appends sites/default/files to the front of our path... + //@todo Is this a setting? Need to figure this out. + $thumbnail->swfthumb = $thumbnail->filepath; + // Return our object + return $thumbnail; + } +}
\ No newline at end of file |