/** * 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 * @param $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. * } */ 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 == PHPVIDEOTOOLKIT_OVERWRITE_PRESERVE || $this->_overwrite_mode == PHPVIDEOTOOLKIT_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 PHPVIDEOTOOLKIT_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 PHPVIDEOTOOLKIT_OVERWRITE_EXISTING : // add an overwrite command to ffmpeg execution call $overwrite = '-y '; break; case PHPVIDEOTOOLKIT_OVERWRITE_PRESERVE : // do nothing as the preservation comes later break; case PHPVIDEOTOOLKIT_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 = PHPVideoToolkit::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); } // 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); } // 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 === PHPVIDEOTOOLKIT_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']) && isset($info['video']['dimensions'])) { $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]); } } } } // 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(PHPVIDEOTOOLKIT_FFMPEG_BINARY, $command_string, $overwrite.escapeshellcmd($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); $exec_string = $exec_string.' &> '.$this->_log_file; } // execute the command exec($exec_string); // track the processed command by adding it to the class array_unshift($this->_processed, $exec_string); // create the multiple pass encode if($multi_pass_encode) { $pass2_exc_string = str_replace('-pass '.escapeshellarg(1), '-pass '.escapeshellarg(2), $exec_string); exec($pass2_exc_string); $this->_processed[0] = array($this->_processed[0], $pass2_exc_string); // remove the multipass log file unlink($multi_pass_file.'-0.log'); } // keep track of the time taken $execution_time = PHPVideoToolkit::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, $this->_getMessage('ffmpeg_log_separator'), $this->_getMessage('ffmpeg_log_ffmpeg_command'), $this->_getMessage('ffmpeg_log_separator')); array_unshift($lines, $this->_getMessage('ffmpeg_log_separator'), $this->_getMessage('ffmpeg_log_ffmpeg_gunk'), $this->_getMessage('ffmpeg_log_separator')); $this->_addToLog($lines, 'r+'); } // exit // 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]) ? 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; // if(!$use_timecode && $use_index) // { // if($log) // { // $this->_logResult('execute_overwrite_fail'); // } // return $this->_raiseError('execute_overwrite_fail'); // } // 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]) ? $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 == PHPVIDEOTOOLKIT_OVERWRITE_EXISTING) { rename($filename, $new_filename); $filename = $new_filename; } // the file exists and is not allowed to be overriden so just rename in the temp directory using the timecode else if($this->_overwrite_mode == PHPVIDEOTOOLKIT_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; } // the file exists so the process must fail 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; // if the result is false then no post process has taken place $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, 'r+'); return $file_exists ? PHPVIDEOTOOLKIT_RESULT_OK_BUT_UNWRITABLE : PHPVIDEOTOOLKIT_RESULT_OK; } // must validate one file 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 == PHPVIDEOTOOLKIT_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 // 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, 'r+'); // 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 PHPVIDEOTOOLKIT_RESULT_OK; } // renaming failed so return ok but erro 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, 'r+'); return PHPVIDEOTOOLKIT_RESULT_OK_BUT_UNWRITABLE; } } // if it is not we signal that it has been created but has not been moved. else if($this->_overwrite_mode == PHPVIDEOTOOLKIT_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 PHPVIDEOTOOLKIT_RESULT_OK_BUT_UNWRITABLE; } // the file exists so the process must fail 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; }