From 0c8e7ae689eed6291bb2061c9c75e3057b230339 Mon Sep 17 00:00:00 2001
From: Mohamed Mujahid <muja_dd@494418.no-reply.drupal.org>
Date: Tue, 6 Jul 2010 17:04:02 +0000
Subject: merging changes from DRUPAL-6--4

---
 includes/conversion.inc   | 262 ++++++++++++++++++++++++++++++++++++++++++++++
 includes/metadata.inc     |  57 ++++++++++
 includes/transcoder.inc   |  96 +++++++++++++++++
 includes/video_helper.inc | 116 ++++++++++++++++++++
 4 files changed, 531 insertions(+)
 create mode 100644 includes/conversion.inc
 create mode 100644 includes/metadata.inc
 create mode 100644 includes/transcoder.inc
 create mode 100644 includes/video_helper.inc

(limited to 'includes')

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
-- 
cgit v1.2.3