You are here

Transcoder.inc in Video 7.2

Same filename and directory in other branches
  1. 6.4 includes/transcoder.inc
  2. 7 includes/transcoder.inc

Class file used to wrap the transcoder helper functions.

File

includes/Transcoder.inc
View source
<?php

/**
 * @file
 * Class file used to wrap the transcoder helper functions.
 */
class Transcoder {

  /**
   * @var TranscoderFactoryInterface
   */
  private $transcoder;
  public function __construct() {
    $transcoder = variable_get('video_convertor', 'TranscoderAbstractionFactoryFfmpeg');
    $this->transcoder = self::createTranscoder($transcoder);
  }
  public function hasTranscoder() {
    return $this->transcoder != NULL;
  }

  /**
   * Returns the current transcoder implementation.
   *
   * @return
   *   TranscoderFactoryInterface
   */
  public function getTranscoder() {
    return $this->transcoder;
  }

  /**
   * Extract frames from the video file. This helper function will interact with
   * only the database and it will save all the thumbnail file reference in to
   * the database.
   *
   * @return
   *   array of file objects, or false on failure
   */
  public function extractFrames(array $video, array $field) {
    global $user;
    $thumbnails = db_query('SELECT f.* FROM {file_managed} f INNER JOIN {video_thumbnails} tn ON tn.thumbnailfid = f.fid WHERE tn.videofid = :fid ORDER BY f.fid', array(
      ':fid' => $video['fid'],
    ))
      ->fetchAllAssoc('fid');
    if (!empty($thumbnails)) {
      return $thumbnails;
    }
    if ($this->transcoder == NULL) {
      return array();
    }
    $scheme = !empty($field['settings']['uri_scheme_thumbnails']) ? $field['settings']['uri_scheme_thumbnails'] : 'public';
    $format = !empty($field['settings']['thumbnail_format']) ? $field['settings']['thumbnail_format'] : 'png';
    $this->transcoder
      ->setInput($video);
    $thumbnails = $this->transcoder
      ->extractFrames($scheme, $format);
    $this->transcoder
      ->reset();
    if ($thumbnails === FALSE) {
      return FALSE;
    }
    foreach ($thumbnails as $thumb) {

      // Determine whether there is an existing thumbnail
      $thumb->fid = (int) db_query('SELECT fid FROM {file_managed} WHERE uri = :uri', array(
        ':uri' => $thumb->uri,
      ))
        ->fetchField(0);
      $thumb->uid = (int) $user->uid;

      // For the media module
      $thumb->type = 'image';
      file_save($thumb);
      db_merge('video_thumbnails')
        ->key(array(
        'videofid' => $video['fid'],
        'thumbnailfid' => $thumb->fid,
      ))
        ->execute();
    }
    return $thumbnails;
  }

  /**
   * Processes up to 'video_ffmpeg_instances' jobs in the current thread.
   *
   * @see video_jobs::loadQueue()
   */
  public function runQueue() {
    if ($this
      ->hasTranscoder() && ($videos = video_jobs::loadQueue())) {
      foreach ($videos as $video) {
        $this
          ->executeConversion($video);
      }
    }
  }

  /**
   * This helper function will help to execute video conversion job by loading
   * job from the database and once it completed saving its data in to the
   * database.
   *
   * @param $video
   * @return
   *   TRUE on success, FALSE on failure. Also check $video->video_status.
   */
  public function executeConversion(stdClass $video) {
    global $user;

    // Check the video state
    if ($video->video_status != VIDEO_RENDERING_INQUEUE && $video->video_status != VIDEO_RENDERING_PENDING) {
      $status = array(
        VIDEO_RENDERING_ACTIVE => 'activated previously',
        VIDEO_RENDERING_COMPLETE => 'completed',
        VIDEO_RENDERING_FAILED => 'failed',
      );
      watchdog('transcoder', 'Video conversion has been @status. You should add video to the queue. Please check the re-queue to enable the video conversion.', array(
        '@status' => $status[$video->video_status],
      ), WATCHDOG_WARNING);
      return FALSE;
    }
    if ($this->transcoder == NULL) {
      return FALSE;
    }

    // update the video conversion start time and status
    $video->statusupdated = time();
    $video->started = $video->statusupdated;
    $video->video_status = VIDEO_RENDERING_ACTIVE;
    video_jobs::update($video);
    $fieldname = !empty($video->data['field_name']) ? $video->data['field_name'] : NULL;
    $presets = Preset::getEnabledPresets($fieldname);
    $converted_scheme = file_uri_scheme($video->uri);
    $thumbnail_format = 'png';
    $thumbnail_number = intval(variable_get('video_thumbnail_count', 5));

    // Apply field-specific settings
    if ($fieldname != NULL) {
      $field = field_info_field($video->data['field_name']);

      // Find the scheme and thumbnail format for the converted videos
      if (!empty($field['settings']['uri_scheme_converted'])) {
        $converted_scheme = $field['settings']['uri_scheme_converted'];
      }
      if (!empty($field['settings']['thumbnail_format'])) {
        $thumbnail_format = $field['settings']['thumbnail_format'];
      }

      // If no automatic thumbnail generation, set thumbnail number to 0
      if ($field['settings']['autothumbnail'] != 'auto') {
        $thumbnail_number = 0;
      }
    }
    $this->transcoder
      ->setInput((array) $video);
    $transcodingsuccess = TRUE;
    $output = array();

    // Set output directory for converted files
    // for following operations, we need 'file_directory' prefix from current bundle
    $entity = entity_load('node', array(
      "{$video->entity_id}",
    ));
    $entity = reset($entity);
    list(, , $bundle) = entity_extract_ids('node', $entity);
    $instance = field_info_instance('node', $video->data['field_name'], $bundle);
    $fdirectory = $instance['settings']['file_directory'];

    // if path not contains 'file_directory' string (probably because original video has been not copied to local file system)
    if (!empty($fdirectory) && strpos(drupal_dirname($video->uri), $fdirectory) === false) {

      // replace last path level from file_directory with '/imported' or add '/imported' if only one level found ...
      if (strrpos($fdirectory, '/') !== false) {
        $fdirectory = substr($fdirectory, 0, strrpos($fdirectory, '/')) . '/imported';
      }

      //... and append source path and fid
      $output_directory = $fdirectory . '/' . file_uri_target(drupal_dirname($video->uri)) . '/' . $video->fid;
      $output_directory = $converted_scheme . '://' . $output_directory;
    }
    else {

      // original behaviour for videos with original files in local file system
      $output_directory = str_replace('original', 'converted', drupal_dirname($video->uri)) . '/' . $video->fid;
      $output_directory = $converted_scheme . '://' . file_uri_target($output_directory);
    }
    if (!file_prepare_directory($output_directory, FILE_CREATE_DIRECTORY)) {
      watchdog('transcoder', 'Video conversion failed. Could not create the directory: %dir', array(
        '%dir' => $output_directory,
      ), WATCHDOG_ERROR);
      $transcodingsuccess = FALSE;
    }
    elseif (empty($presets)) {
      watchdog('transcoder', 'No preset enabled. Please !presets_message.', array(
        '!presets_message' => l(t('enable or create a preset'), 'admin/config/media/video/presets'),
      ), WATCHDOG_ERROR, 'admin/config/media/video/presets');
      $transcodingsuccess = FALSE;
    }
    else {
      foreach ($presets as $name => $preset) {

        // override the widthXheight if enabled
        $preset['settings']['wxh'] = variable_get('video_use_preset_wxh', FALSE) ? $preset['settings']['wxh'] : $video->dimensions;
        $preset['settings']['thumbnails']['format'] = $thumbnail_format;
        $preset['settings']['thumbnails']['number'] = $thumbnail_number;

        // Only create thumbnails for the first preset
        $thumbnail_number = 0;

        // set transcoder options
        if (!$this->transcoder
          ->setOptions($preset['settings'])) {

          // setOptions should write to the watchdog log.
          $transcodingsuccess = FALSE;
          break;
        }
        $output_name = file_munge_filename(str_replace(' ', '_', pathinfo($video->filename, PATHINFO_FILENAME) . ' ' . strtolower($name)) . '_' . time() . '.' . $preset['settings']['video_extension'], '');
        $this->transcoder
          ->setOutput($output_directory, $output_name);

        // Close the database connection since encodding operation may take a lot of time
        // and mysql connection will timed out
        db_close(array(
          'target' => 'default',
        ));
        if ($output_file = $this->transcoder
          ->execute()) {
          $output[] = $output_file;
        }
        else {
          $transcodingsuccess = FALSE;
          break;
        }
        $this->transcoder
          ->reset(TRUE);
      }
    }
    if (!$transcodingsuccess) {
      video_jobs::setFailed($video);
    }
    else {

      // add files to file_managed table and add reference to the file_usage table.
      $offsite = $this->transcoder
        ->isOffSite();
      $this
        ->cleanConverted($video->fid);
      foreach ($output as $file) {
        $file->filemime = video_utility::getMimeType($file->uri);
        $file->status = FILE_STATUS_PERMANENT;
        $file->uid = $video->uid;
        $file->type = 'video';

        // For the media module
        // Empty file to prevent 'Warning: filesize(): stat failed' error.
        if ($offsite && $file->filesize == 0) {
          file_put_contents($file->uri, '');
        }
        file_save($file);
        file_usage_add($file, 'file', $video->entity_type, $video->entity_id);
        $output_vid = array(
          'vid' => $video->vid,
          'original_fid' => $video->fid,
          'output_fid' => $file->fid,
          'job_id' => !empty($file->jobid) ? $file->jobid : NULL,
        );
        drupal_write_record('video_output', $output_vid);
      }

      // add duration to the video_queue table
      $video->duration = round($file->duration);

      // Change the status if the file exists for onsite transcoders.
      if (!$offsite && file_exists($file->uri)) {
        video_jobs::setCompleted($video);
      }
      else {

        // Else: just update the video.
        video_jobs::update($video);
      }
    }
    $this->transcoder
      ->reset();
    return $transcodingsuccess;
  }

  /**
   * This helper function clean the database records if exist for current job.
   */
  protected function cleanConverted($fid) {
    $output_fids = db_select('video_output', 'vo')
      ->fields('vo', array(
      'output_fid',
    ))
      ->condition('original_fid', $fid)
      ->execute()
      ->fetchCol();
    if (empty($output_fids)) {
      return;
    }
    $output_files = file_load_multiple($output_fids);

    // Delete for all output files file_usage and file if not used anymore.
    foreach ($output_files as $output_file) {
      file_usage_delete($output_file, 'file');
      file_delete($output_file);
    }

    // Delete original_fid and all output_fid's from video_output table.
    db_delete('video_output')
      ->condition('original_fid', $fid)
      ->execute();
  }

  /**
   * Retuns all transcoders implemented to work with the video module.
   */
  public function getAllTranscoders() {

    // Lets find our transcoder classes and build our radio options
    // We do this by scanning our transcoders folder
    $form = array(
      'radios' => array(
        '' => t('No transcoder'),
      ),
      'help' => array(),
      'admin_settings' => array(),
    );

    // check inside sub modules
    $modules = module_list();
    $files = array();
    foreach ($modules as $module) {
      $module_files = array();
      $module_path = drupal_get_path('module', $module) . '/transcoders';
      foreach (file_scan_directory($module_path, '/.*\\.inc/') as $filekey => $file) {
        $file->module = $module;

        // Get filename to retrieve transcoder class name
        $filename = explode(".", $file->name);
        $file->name = $filename[0];
        $module_files[] = $file;
      }
      $files = array_merge($files, $module_files);
    }
    foreach ($files as $file) {
      module_load_include('inc', $file->module, '/transcoders/' . $file->name);
      $focus = new $file->name();
      $errorMessage = '';
      if (!$focus
        ->isAvailable($errorMessage)) {
        $form['help'][] = t('@name is unavailable: !errormessage', array(
          '@name' => $focus
            ->getName(),
          '!errormessage' => $errorMessage,
        ));
      }
      else {
        $form['radios'][$file->name] = check_plain($focus
          ->getName());
        $form['admin_settings'] = $form['admin_settings'] + $focus
          ->adminSettings();
      }
    }
    return $form;
  }

  /**
   * Create a new instance of the transcoder implementation.
   *
   * @return
   *   TranscoderFactoryInterface
   */
  public static function createTranscoder($transcoder) {
    if ($transcoder == '') {
      return NULL;
    }
    if (!class_exists($transcoder, TRUE)) {
      drupal_set_message(t('The transcoder %transcoder is not configured properly.', array(
        '%transcoder' => $transcoder,
      )), 'error');
    }
    return new $transcoder();
  }

}

Classes

Namesort descending Description
Transcoder @file Class file used to wrap the transcoder helper functions.