You are here

public function PHPVideoToolkit::execute in Video 7

Same name and namespace in other branches
  1. 7.2 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.: * @param $log boolean Determines if a log file of the results should be generated. * @return 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 * } * else if($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(); * } * else if($result === PHPVideoToolkit::RESULT_OK) * { * // everything is ok. * }

File

libraries/phpvideotoolkit/phpvideotoolkit.php5.php, line 3018

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();

  // 			we have multiple inputs that require joining so convert them to a joinable format and join
  if (is_array($this->_input_file)) {
    $this
      ->_joinInput($log);
  }

  // 			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 = strtolower(array_pop(explode('.', $this->_input_file)));
    if ($extension === 'gif') {
      $this
        ->addCommand('-pix_fmt', 'rgb24');
    }
  }
  else {
    if ($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));
      if (($boundry = strpos($ratio, ':')) !== false) {
        $ratio = substr($ratio, 1, $boundry - 1) / substr($ratio, $boundry + 1, -1);
        $new_width = round($dim[1] * $ratio);

        // 						make sure new width is an even number
        $ceiled = ceil($new_width);
        $new_width = $ceiled % 2 !== 0 ? floor($new_width) : $ceiled;
        if ($new_width != $dim[0]) {
          $this
            ->setVideoDimensions($new_width, $dim[1]);
        }
      }
      else {
        if (strpos($ratio, '.') !== false) {
          $ratio = floatval($ratio);
          $new_width = $dim[1] * $ratio;

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

  //			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 = self::_captureExecBuffer($exec_string, $this->_tmp_directory);

  // 			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 = self::_captureExecBuffer($pass2_exc_string, $this->_tmp_directory);

    // 				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;
      }
      else {
        if ($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);
    }
    else {
      if ($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);
    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
      }
      else {
        if ($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)) {
        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;
      }
    }
    else {
      if ($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;
}