You are here

public function PHPVideoToolkit::execute in Video 7.2

Same name and namespace in other branches
  1. 7 libraries/phpvideotoolkit/phpvideotoolkit.php5.php \PHPVideoToolkit::execute()

Commits all the commands and executes the ffmpeg procedure. This will also attempt to validate any outputted files in order to provide some level of stop and check system.

@access public

Parameters

$multi_pass_encode boolean Determines if multi (2) pass encoding should be used.:

$log boolean Determines if a log file of the results should be generated.:

Return value

mixed - FALSE On error encountered. - PHPVideoToolkit::RESULT_OK (bool TRUE) If the file has successfully been processed and moved ok to the output address - PHPVideoToolkit::RESULT_OK_BUT_UNWRITABLE (int -1) If the file has successfully been processed but was not able to be moved correctly to the output address If this is the case you will manually need to move the processed file from the temp directory. You can get around this by settings the third argument from PHPVideoToolkit::setOutput(), $overwrite to TRUE. - n (int) A positive integer is only returned when outputting a series of frame grabs from a movie. It dictates the total number of frames grabbed from the input video. You should also not however, that if a conflict exists with one of the filenames then this return value will not be returned, but PHPVideoToolkit::RESULT_OK_BUT_UNWRITABLE will be returned instead. Because of the mixed return value you should always go a strict evaluation of the returned value. ie

$result = $toolkit->excecute(); if($result === FALSE) { // error } elseif($result === PHPVideoToolkit::RESULT_OK_BUT_UNWRITABLE) { // ok but a manual move is required. The file to move can be it can be retrieved by $toolkit->getLastOutput(); } elseif($result === PHPVideoToolkit::RESULT_OK) { // everything is ok. }

File

libraries/phpvideotoolkit/phpvideotoolkit.php5.php, line 2778
Libary to access FFmpeg

Class

PHPVideoToolkit

Code

public function execute($multi_pass_encode = FALSE, $log = FALSE) {

  // 			check for inut and output params
  $has_placeholder = preg_match('/\\%([0-9]+)index/', $this->_process_address) || strpos($this->_process_address, '%index') === FALSE && strpos($this->_process_address, '%timecode') === FALSE;
  if ($this->_input_file === NULL && !$has_placeholder) {
    return $this
      ->_raiseError('execute_input_404');

    // <-			exits
  }

  //			check to see if the output address has been set
  if ($this->_process_address === NULL) {
    return $this
      ->_raiseError('execute_output_not_set');

    // <-			exits
  }

  // 			check if temp dir is required and is writable
  if (($multi_pass_encode || $log) && !is_writable($this->_tmp_directory)) {
    return $this
      ->_raiseError('execute_temp_unwritable');

    // <-			exits
  }
  if (($this->_overwrite_mode == self::OVERWRITE_PRESERVE || $this->_overwrite_mode == self::OVERWRITE_FAIL) && is_file($this->_process_address)) {
    return $this
      ->_raiseError('execute_overwrite_process');

    // <-			exits
  }

  // 			carry out some overwrite checks if required
  $overwrite = '';
  switch ($this->_overwrite_mode) {
    case self::OVERWRITE_UNIQUE:

      // 					insert a unique id into the output address (the process address already has one)
      $unique = $this
        ->unique();
      $last_index = strrpos($this->_output_address, DS);
      $this->_output_address = substr($this->_output_address, 0, $last_index + 1) . $unique . '-' . substr($this->_output_address, $last_index + 1);
      break;
    case self::OVERWRITE_EXISTING:

      // 					add an overwrite command to ffmpeg execution call
      $overwrite = '-y ';
      break;
    case self::OVERWRITE_PRESERVE:

      // 					do nothing as the preservation comes later
      break;
    case self::OVERWRITE_FAIL:
    default:

      // 					if the file should fail
      if (!$has_placeholder && is_file($this->_output_address)) {
        return $this
          ->_raiseError('execute_overwrite_fail');

        // <-					exits
      }
      break;
  }
  $this->_timer_start = self::microtimeFloat();

  // 			check to see if the format has been set and if it hasn't been set and the extension is a gif
  // 			we need to add an extra argument to set the pix format.
  $format = $this
    ->hasCommand('-f');
  if ($format === FALSE) {
    $extension = pathinfo($this->_input_file, PATHINFO_EXTENSION);
    if ($extension === 'gif') {
      $this
        ->addCommand('-pix_fmt', 'rgb24');
    }
  }
  elseif ($format === self::FORMAT_GIF) {
    $this
      ->addCommand('-pix_fmt', 'rgb24');
  }

  // 			check to see if an aspect ratio is set, if it is correct the width and heights to reflect that aspect ratio.
  // 			This isn't strictly needed it is purely for informational purposes that this is done, because if the width is not
  // 			inline with what is should be according to the aspect ratio ffmpeg will report the wrong final width and height
  // 			when using it to lookup information about the file.
  $ratio = $this
    ->hasCommand('-aspect');
  if ($ratio !== FALSE) {
    $size = $this
      ->hasCommand('-s');
    if ($size === FALSE) {
      $info = $this
        ->getFileInfo();
      if (isset($info['video']) === TRUE && isset($info['video']['dimensions']) === TRUE) {
        $size = $info['video']['dimensions']['width'] . 'x' . $info['video']['dimensions']['height'];
      }
    }
    if ($size !== FALSE) {
      $dim = explode('x', substr($size, 1, -1));
      $floatratio = NULL;
      if (($boundry = strpos($ratio, ':')) !== FALSE) {
        $floatratio = substr($ratio, 1, $boundry - 1) / substr($ratio, $boundry + 1, -1);
      }
      elseif (strpos($ratio, '.') !== FALSE) {
        $floatratio = floatval(trim($ratio, '\''));
      }
      if ($floatratio !== NULL) {
        $new_height = $dim[0] / $floatratio;

        // 						make sure new height is an even number
        $ceiled = ceil($new_height);
        $new_height = $ceiled % 2 !== 0 ? floor($new_height) : $ceiled;
        if ($new_height != $dim[1]) {
          $this
            ->setVideoDimensions($dim[0], $new_height);
        }
      }
    }
  }

  //			add the input file command to the mix
  $this
    ->addCommand('-i', $this->_input_file);

  // 			if multi pass encoding is enabled add the commands and logfile
  if ($multi_pass_encode) {
    $multi_pass_file = $this->_tmp_directory . $this
      ->unique() . '-multipass';
    $this
      ->addCommand('-pass', 1);
    $this
      ->addCommand('-passlogfile', $multi_pass_file);
  }

  //			combine all the output commands
  $command_string = $this
    ->_combineCommands();

  //			prepare the command suitable for exec
  //			the input and overwrite commands have specific places to be set so they have to be added outside of the combineCommands function
  $exec_string = $this
    ->_prepareCommand($this->_ffmpeg_binary, $command_string, $overwrite . $this->_process_address);

  // 			$exec_string = $this->_prepareCommand(PHPVIDEOTOOLKIT_FFMPEG_BINARY, '-i '.$this->_commands['-i'].' '.$command_string, $overwrite.escapeshellcmd($this->_process_address));
  if ($log) {
    $this->_log_file = $this->_tmp_directory . $this
      ->unique() . '.info';
    array_push($this->_unlink_files, $this->_log_file);
  }

  //			execute the command
  // 			$exec_string = $exec_string.' 2>&1';// &> '.$this->_log_file;
  $buffer = $this
    ->_captureExecBuffer($exec_string);

  // 			exec($exec_string, $buffer);
  if ($log) {
    $this
      ->_addToLog($buffer, 'a+');
  }

  //			track the processed command by adding it to the class
  array_unshift($this->_processed, $exec_string);

  // 			scan buffer for any errors
  $last_line = $buffer[count($buffer) - 1];
  if (preg_match('/(.*)(Unsupported codec|Error while opening)(.*)/s', $last_line, $error_matches) > 0) {
    $type = $error_matches[2];
    switch ($error_matches[2]) {
      case 'Unsupported codec':
        break;
      case 'Error while opening':
        break;
    }
    $stream = 'could be with either the audio or video codec';
    if (preg_match('/#0.(0|1)/', $last_line, $stream_matches) > 0) {
      $stream = $stream_matches[1] === '0' ? 'is with the video codec' : 'is with the audio codec';
    }

    // 						add the error to the log file
    if ($log) {
      $this
        ->_logResult('execute_ffmpeg_return_error', array(
        'input' => $this->_input_file,
        'type' => $type,
        'message' => $error_matches[0],
        'stream' => $stream,
      ));
    }
    return $this
      ->_raiseError('execute_ffmpeg_return_error', array(
      'input' => $this->_input_file,
      'type' => $type,
      'message' => $error_matches[0],
      'stream' => $stream,
    ));
  }

  // 			create the multiple pass encode
  if ($multi_pass_encode) {
    $pass2_exc_string = str_replace('-pass ' . escapeshellarg(1), '-pass ' . escapeshellarg(2), $exec_string);
    $buffer = $this
      ->_captureExecBuffer($pass2_exc_string);

    // 				exec($pass2_exc_string, $buffer);
    if ($log) {
      $this
        ->_addToLog($buffer, 'a+');
    }
    $this->_processed[0] = array(
      $this->_processed[0],
      $pass2_exc_string,
    );

    // 				tidy up the multipass log file
    array_push($this->_unlink_files, $multi_pass_file . '-0.log');

    // 				scan buffer for any errors
    $last_line = $buffer[count($buffer) - 1];
    if (preg_match('/(.*)(Unsupported codec|Error while opening)(.*)/s', $last_line, $error_matches) > 0) {
      $type = $error_matches[2];
      switch ($error_matches[2]) {
        case 'Unsupported codec':
          break;
        case 'Error while opening':
          break;
      }
      $stream = 'could be with either the audio or video codec';
      if (preg_match('/#0.(0|1)/', $last_line, $stream_matches) > 0) {
        $stream = $stream_matches[1] === '0' ? 'is with the video codec' : 'is with the audio codec';
      }

      // 						add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_ffmpeg_return_error_multipass', array(
          'input' => $this->_input_file,
          'type' => $type,
          'message' => $error_matches[0],
          'stream' => $stream,
        ));
      }
      return $this
        ->_raiseError('execute_ffmpeg_return_error_multipass', array(
        'input' => $this->_input_file,
        'type' => $type,
        'message' => $error_matches[0],
        'stream' => $stream,
      ));
    }
  }

  // 			keep track of the time taken
  $execution_time = self::microtimeFloat() - $this->_timer_start;
  array_unshift($this->_timers, $execution_time);

  // 			add the exec string to the log file
  if ($log) {
    $lines = $this->_processed[0];
    if (!is_array($lines)) {
      $lines = array(
        $lines,
      );
    }

    // 				array_unshift($lines, $exec_string);
    array_unshift($lines, $this
      ->_getMessage('ffmpeg_log_separator'), $this
      ->_getMessage('ffmpeg_log_ffmpeg_command'), $this
      ->_getMessage('ffmpeg_log_separator'));

    // 				if($multi_pass_encode)
    // 				{
    // 					array_unshift($lines, $pass2_exc_string);
    // 				}
    array_unshift($lines, $this
      ->_getMessage('ffmpeg_log_separator'), $this
      ->_getMessage('ffmpeg_log_ffmpeg_gunk'), $this
      ->_getMessage('ffmpeg_log_separator'));
    $this
      ->_addToLog($lines, 'a+');
  }

  //			must validate a series of outputed items
  //			detect if the output address is a sequence output
  if (preg_match('/\\%([0-9]+)d/', $this->_process_address, $d_matches) || strpos($this->_process_address, '%d') !== FALSE) {

    //				get the path details
    $process_info = pathinfo($this->_process_address);
    $output_info = pathinfo($this->_output_address);
    $pad_amount = intval($d_matches[1]);

    // 				print_r(array($process_info, $output_info));
    // 				get the %index padd amounts
    $has_preg_index = preg_match('/\\%([0-9]+)index/', $output_info['basename'], $index_matches);
    $output_index_pad_amount = isset($index_matches[1]) === TRUE ? intval($index_matches[1], 1) : 0;

    // 				var_dump($index_matches);
    //				init the iteration values
    $num = 1;
    $files = array();
    $produced = array();
    $error = FALSE;
    $name_conflict = FALSE;
    $file_exists = FALSE;

    // 				get the first files name
    $filename = $process_info['dirname'] . DS . str_replace($d_matches[0], str_pad($num, $pad_amount, '0', STR_PAD_LEFT), $process_info['basename']);
    $use_timecode = strpos($output_info['basename'], '%timecode') !== FALSE;
    $use_index = $has_preg_index || strpos($output_info['basename'], '%index') !== FALSE;

    // 				start the timecode pattern replacement values
    if ($use_timecode) {
      $secs_start = $this
        ->formatTimecode($this->_image_output_timecode_start, '%hh:%mm:%ss.%ms', '%mt', $this->_image_output_timecode_fps);
      $fps_inc = 1 / $this->_image_output_timecode_fps;
      $fps_current_sec = 0;
      $fps_current_frame = 0;
    }

    //				loop checking for file existence
    while (@is_file($filename)) {

      //					check for empty file
      $size = filesize($filename);
      if ($size == 0) {
        $error = TRUE;
      }
      array_push($produced, $filename);

      // 					create the substitution arrays
      $searches = array();
      $replacements = array();
      if ($use_index) {
        array_push($searches, isset($index_matches[0]) === TRUE ? $index_matches[0] : '%index');
        array_push($replacements, str_pad($num, $output_index_pad_amount, '0', STR_PAD_LEFT));
      }

      // 					check if timecode is in the output name, no need to use it if not
      if ($use_timecode) {
        $fps_current_sec += $fps_inc;
        $fps_current_frame += 1;
        if ($fps_current_sec >= 1) {
          $fps_current_sec = $fps_inc;
          $secs_start += 1;
          $fps_current_frame = 1;
        }
        $timecode = $this
          ->formatSeconds($secs_start, $this->image_output_timecode_format, $this->_image_output_timecode_fps);
        $timecode = str_replace(array(
          ':',
          '.',
        ), $this->timecode_seperator_output, $timecode);

        // 						add to the substitution array
        array_push($searches, '%timecode');
        array_push($replacements, $timecode);
      }

      // 					check if the file exists already and if it does check that it can be overriden
      $old_filename = $filename;

      // 					print_r(array($searches, $replacements, $output_info['basename']));
      $new_file = str_replace($searches, $replacements, $output_info['basename']);
      $new_filename = $output_info['dirname'] . DS . $new_file;

      // 					var_dump($filename, $new_filename);
      if (!is_file($new_filename) || $this->_overwrite_mode == self::OVERWRITE_EXISTING) {
        if (is_file($new_filename)) {
          unlink($new_filename);
        }
        rename($filename, $new_filename);
        $filename = $new_filename;
      }
      elseif ($this->_overwrite_mode == self::OVERWRITE_PRESERVE) {
        $new_filename = $process_info['dirname'] . DS . 'tbm-' . $this
          ->unique() . '-' . $new_file;
        rename($filename, $new_filename);
        $filename = $new_filename;

        // 						add the error to the log file
        if ($log) {
          $this
            ->_logResult('execute_image_file_exists', array(
            'file' => $new_filename,
          ));
        }

        // 						flag the conflict
        $file_exists = TRUE;
      }
      else {

        // 						add the error to the log file
        if ($log) {
          $this
            ->_logResult('execute_overwrite_fail');
        }

        // 						tidy up the produced files
        array_merge($this->_unlink_files, $produced);
        return $this
          ->_raiseError('execute_overwrite_fail');
      }

      //					process the name change if the %d is to be replaced with the timecode
      $num += 1;
      $files[$filename] = $size > 0 ? basename($filename) : FALSE;

      // 					print_r("\r\n\r\n".is_file($old_filename)." - ".$old_filename.' => '.$new_filename);
      // 					print_r($files);
      // 					get the next incremented filename to check for existance
      $filename = $process_info['dirname'] . DS . str_replace($d_matches[0], str_pad($num, $pad_amount, '0', STR_PAD_LEFT), $process_info['basename']);
    }

    //				de-increment the last num as it wasn't found
    $num -= 1;

    //				if the file was detected but were empty then display a different error
    if ($error === TRUE) {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_partial_error', array(
          'input' => $this->_input_file,
        ));
      }
      return $this
        ->_raiseError('execute_partial_error', array(
        'input' => $this->_input_file,
      ));

      // <-				exits
    }

    // 				post process any files
    // 				print_r($files);
    $post_process_result = $this
      ->_postProcess($log, $files);

    // 				print_r($files);
    if (is_array($post_process_result)) {

      // 					post process has occurred and everything is fine
      $num = count($files);
    }
    elseif ($post_process_result !== FALSE) {

      // 					the file has encountered an error in the post processing of the files
      return $post_process_result;
    }

    // 				var_dump("\r\n\r\n", $files, __LINE__, __FILE__, "\r\n\r\n"); exit;
    $this->_process_file_count = $num;

    //				no files were generated in this sequence
    if ($num == 0) {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_image_error', array(
          'input' => $this->_input_file,
        ));
      }
      return $this
        ->_raiseError('execute_image_error', array(
        'input' => $this->_input_file,
      ));

      // <-				exits
    }

    //				add the files the the class a record of what has been generated
    array_unshift($this->_files, $files);
    if ($log) {
      array_push($lines, $this
        ->_getMessage('ffmpeg_log_separator'), $this
        ->_getMessage('ffmpeg_log_ffmpeg_output'), $this
        ->_getMessage('ffmpeg_log_separator'), implode("\n", $files));
      $this
        ->_addToLog($lines, 'a+');
    }
    return $file_exists ? self::RESULT_OK_BUT_UNWRITABLE : self::RESULT_OK;
  }
  else {

    //				check that it is a file
    if (!is_file($this->_process_address)) {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_output_404', array(
          'input' => $this->_input_file,
        ));
      }
      return $this
        ->_raiseError('execute_output_404', array(
        'input' => $this->_input_file,
      ));

      // <-				exits
    }

    //				the file does exist but is it empty?
    if (filesize($this->_process_address) == 0) {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_output_empty', array(
          'input' => $this->_input_file,
        ));
      }
      return $this
        ->_raiseError('execute_output_empty', array(
        'input' => $this->_input_file,
      ));

      // <-				exits
    }

    // 				the file is ok so move to output address
    if (!is_file($this->_output_address) || $this->_overwrite_mode == self::OVERWRITE_EXISTING) {

      // 					post process any files
      $post_process_result = $this
        ->_postProcess($log, array(
        $this->_process_address,
      ));
      if (is_array($post_process_result) || $post_process_result === TRUE) {

        // 						post process has occurred and everything is fine
      }
      elseif ($post_process_result !== FALSE) {
        return $post_process_result;
      }

      // 					if the result is FALSE then no post process has taken place
      if (is_file($this->_output_address)) {
        unlink($this->_output_address);
      }

      // 					rename the file to the final destination and check it went ok
      if (rename($this->_process_address, $this->_output_address)) {
        if ($log) {
          array_push($lines, $this
            ->_getMessage('ffmpeg_log_separator'), $this
            ->_getMessage('ffmpeg_log_ffmpeg_output'), $this
            ->_getMessage('ffmpeg_log_separator'), $this->_output_address);
          $this
            ->_addToLog($lines, 'a+');
        }

        // 						the file has been renamed ok
        // 						add the error to the log file
        if ($log) {
          $this
            ->_logResult('execute_result_ok', array(
            'output' => $this->_output_address,
          ));
        }
        $this->_process_file_count = 1;

        //						add the file the the class a record of what has been generated
        array_unshift($this->_files, array(
          $this->_output_address,
        ));
        return self::RESULT_OK;
      }
      else {

        // 						add the error to the log file
        if ($log) {
          $this
            ->_logResult('execute_result_ok_but_unwritable', array(
            'process' => $this->_process_address,
            'output' => $this->_output_address,
          ));
        }

        //						add the file the the class a record of what has been generated
        array_unshift($this->_files, array(
          $this->_process_address,
        ));
        array_push($lines, $this
          ->_getMessage('ffmpeg_log_separator'), $this
          ->_getMessage('ffmpeg_log_ffmpeg_output'), $this
          ->_getMessage('ffmpeg_log_separator'), $this->_process_address);
        $this
          ->_addToLog($lines, 'a+');
        return self::RESULT_OK_BUT_UNWRITABLE;
      }
    }
    elseif ($this->_overwrite_mode == self::OVERWRITE_PRESERVE) {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_result_ok_but_unwritable', array(
          'process' => $this->_process_address,
          'output' => $this->_output_address,
        ));
      }

      //					add the file the the class a record of what has been generated
      array_unshift($this->_files, array(
        $this->_process_address,
      ));
      return self::RESULT_OK_BUT_UNWRITABLE;
    }
    else {

      // 					add the error to the log file
      if ($log) {
        $this
          ->_logResult('execute_overwrite_fail');
      }

      // 					tidy up the produced files
      array_push($this->_unlink_files, $this->_process_address);
      return $this
        ->_raiseError('execute_overwrite_fail');
    }
  }
  return NULL;
}