/**
  * All three save functions, save, saveNonBlocking and getExecutionCommand have common things they 
  * have to do before they are processed. This function contains those execution "warm-up" procedures.
  *
  * @access protected
  * @author Oliver Lillie
  * @param Format $output_format 
  * @param string $save_path 
  * @param string $overwrite 
  * @return void
  */
 protected function _savePreProcess(Format &$output_format = null, &$save_path, $overwrite)
 {
     //          do some processing on the input format
     // $this->_processInputFormat();
     //          if the save path is null then we are overwriting the existing media file.
     if ($save_path === null) {
         $overwrite = self::OVERWRITE_UNIQUE;
         $save_path = $this->_media_file_path;
     }
     //          do some pre processing of the output format
     $this->_processOutputFormat($output_format, $save_path, $overwrite);
     //          check the save path.
     $has_timecode_or_index = false;
     $has_timecode = false;
     $has_index = false;
     $basename = basename($save_path);
     $save_dir = dirname($save_path);
     $save_dir = realpath($save_dir);
     if ($save_dir === false || is_dir($save_dir) === false) {
         throw new \InvalidArgumentException('The directory that the output is to be saved to, "' . $save_dir . '" does not exist.');
     } else {
         if (is_writeable($save_dir) === false || is_readable($save_dir) === false) {
             throw new \RuntimeException('The directory that the output is to be saved to, "' . $save_dir . '" is not read-writeable.');
         } else {
             if (preg_match('/\\%([0-9]*)d/', $save_path) > 0) {
                 throw new \InvalidArgumentException('The output file appears to be using FFmpeg\'s %d notation for multiple file output. The %d notation is depreciated in PHPVideoToolkit in favour of the %index or %timecode notations.');
             } else {
                 if ($has_timecode_or_index = preg_match('/\\%(timecode|[0-9]*(index))/', $save_path, $matches) > 0) {
                     $has_timecode = $matches[1] === 'timecode';
                     $has_index = isset($matches[2]) === true && $matches[2] === 'index';
                 } else {
                     if ($has_timecode_or_index === false && $this->_require_d_in_output === true) {
                         throw new \InvalidArgumentException('It is required that either "%timecode" or "%index" to the save path as more that one file is expected be outputed. When using %index, it is possible to specify a number to be padded with a specific amount of 0s. For example adding %5index.jpg will output files like 00001.jpg, 00002.jpg etc.');
                     } else {
                         if (is_file($save_dir . DIRECTORY_SEPARATOR . $basename) === true && (empty($overwrite) === true || $overwrite === self::OVERWRITE_FAIL)) {
                             throw new \LogicException('The output file already exists and overwriting is disabled.');
                         } else {
                             if (is_file($save_dir . DIRECTORY_SEPARATOR . $basename) === true && $overwrite === self::OVERWRITE_EXISTING && is_writeable($save_dir . DIRECTORY_SEPARATOR . $basename) === false) {
                                 throw new \LogicException('The output file already exists, overwriting is enabled however the file is not writable.');
                             }
                         }
                     }
                 }
             }
         }
     }
     $save_path = $save_dir . DIRECTORY_SEPARATOR . $basename;
     //          check for a recognised output format, and if one is not supplied
     //          then check the a the format has been set in the output format, if not through an error and exit
     $format = false;
     $ext = pathinfo($save_path, PATHINFO_EXTENSION);
     if (empty($ext) === false) {
         //              check we have a format we know about.
         $format = Extensions::toBestGuessFormat($ext);
     }
     //          if we still don't have a format, check from the output format.
     if (!$format) {
         $options = $output_format->getFormatOptions();
         if (isset($options['format']) === false || empty($options['format']) === true) {
             if (empty($ext) === true) {
                 throw new \LogicException('The output path of the file extension has not be given. Please either set a file extension of the output path - or - call setFormat() on the output format to set the format of the output media.');
             }
             throw new \LogicException('Un-recognised file extension. Please call setFormat() on the output format to set the format of the output media.');
         }
     }
     //          process the overwrite status
     switch ($overwrite) {
         case self::OVERWRITE_EXISTING:
             $this->_process->addCommand('-y');
             break;
             //              insert a unique id into the save path
         //              insert a unique id into the save path
         case self::OVERWRITE_UNIQUE:
             $pathinfo = pathinfo($save_path);
             $save_path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $pathinfo['filename'] . '-u_' . String::generateRandomString() . '.' . $pathinfo['extension'];
             break;
             //              this is purely in case the media object is "re-used", as if the command is already been set to overwrite
             //              but the subsequent save is not, we must remove any previous command so we don't get unwanted overwrites.
         //              this is purely in case the media object is "re-used", as if the command is already been set to overwrite
         //              but the subsequent save is not, we must remove any previous command so we don't get unwanted overwrites.
         default:
             $this->_process->removeCommand('-y');
     }
     $this->_output_path = $this->_processing_path = $save_path;
     //          check to see if we are extracting a segment
     //          It is important that we are the extract commands before any segmenting, so that if we are extracting
     //          a segment then spliting the file everything goes as expected.
     if (empty($this->_extract_segment) === false) {
         if (empty($this->_extract_segment['preseek']) === false) {
             $this->_process->addPreInputCommand('-ss', $this->_extract_segment['preseek']->getTimecode('%hh:%mm:%ss.%ms', false));
         }
         if (empty($this->_extract_segment['seek']) === false) {
             $this->_process->addCommand('-ss', $this->_extract_segment['seek']->getTimecode('%hh:%mm:%ss.%ms', false));
         }
         if (empty($this->_extract_segment['length']) === false) {
             $this->_process->addCommand('-t', $this->_extract_segment['length']);
         }
     }
     //          if we are splitting the output
     if (empty($this->_split_options) === false) {
         //              if so check that a timecode or index has been set
         if ($has_timecode_or_index === false) {
             $pathinfo = pathinfo($save_path);
             $save_path = $this->_output_path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $pathinfo['filename'] . '-%timecode.' . $pathinfo['extension'];
             $has_timecode_or_index = true;
         }
         //              if we are splitting we need to add certain commands to make it work.
         //              one of those is -map 0. Also note that video and audio objects additionally set their own
         //              codecs if not supplied, in their related class function _savePreProcess
         // TODO this may need to be changed dependant on the number of mappings.
         $this->_process->addCommand('-map', '0');
         // -acodec copy
         // -vcodec copy
         //              we must do this via add command rather than setFormat as it rejects the segment format.
         $this->_process->addCommand('-f', 'segment');
         foreach ($this->_split_options as $command => $arg) {
             $this->_process->addCommand('-' . $command, $arg);
         }
         //              get the output commands and augment with the final output options.
         $options = $output_format->getFormatOptions();
         //              set the split format if an output format has already been set. and remove from the output format so that multiple "-f" formats are not given to the buffer
         if (empty($options['format']) === false) {
             $this->_ignore_format = true;
             $this->_process->addCommand('-segment_format', $options['format']);
         }
         // TODO add time delta and segment_list
     }
     //          check to see if we have any global meta
     if (empty($this->_metadata) === false) {
         $meta_string = array();
         foreach ($this->_metadata as $key => $value) {
             $this->_process->addCommand('-metadata:g', $key . '=' . $value . '', true);
         }
     }
     //          if we have a timecode or index based path we then have to supply a temporary processing path so that
     //          we can perform the rename to timecode and index after they items have been transcoded by ffmpeg.
     if ($has_timecode_or_index === true) {
         $processing_path = $this->_output_path;
         if ($has_timecode === true) {
             //                  we build the timecode and frame rate data into the output if we use %timecode
             //                  that way we can always reconstruct the timecode even from another script or process.
             //                  get the frame rate of the export. we give priority to "-r" as this is the output of the object if already set somewhere,
             //                  otherwise we revert to the output format setting,
             //                  then fallback to the to the framerate of the current video component
             if (($frame_rate = $this->_process->getCommand('-r')) === false) {
                 $options = $output_format->getFormatOptions();
                 if (empty($options['video_frame_rate']) === false) {
                     $frame_rate = $options['video_frame_rate'];
                 } else {
                     $data = $this->readVideoComponent();
                     if (isset($data['frames']) === true && isset($data['frames']['rate']) === true) {
                         $frame_rate = $data['frames']['rate'];
                     }
                 }
             }
             if ($frame_rate <= 0) {
                 throw new \RuntimeException('Unable to access the output frame rate value and as a result we cannot generate a timecode based filename output.');
             } else {
                 if (preg_match('/[0-9]+\\/[0-9]+/', $frame_rate) > 0) {
                     $frame_rate = explode('/', $frame_rate);
                     $frame_rate = $frame_rate[0] / $frame_rate[1];
                 }
             }
             //                  get the starting offset of the export
             $offset = '0';
             $stream_seek_input = $this->_process->getPreInputCommand('-ss');
             if ($stream_seek_input !== false) {
                 $offset += Timecode::parseTimecode($stream_seek_input, '%hh:%mm:%ss.%ms');
             }
             $stream_seek_output = $this->_process->getCommand('-ss');
             if ($stream_seek_output !== false) {
                 $offset += Timecode::parseTimecode($stream_seek_output, '%hh:%mm:%ss.%ms');
             }
             //                  apply rounding to get a float of precise length
             $offset = round($offset, 2);
             $processing_path = preg_replace('/%timecode/', '.%12d.' . $frame_rate . '_' . $offset . '._t.', $processing_path);
         }
         if ($has_index === true) {
             $processing_path = preg_replace('/%([0-9]*)index/', '.%$1d._i.', $processing_path);
         }
         //              add a unique identifier to the processing path to prevent overwrites.
         $pathinfo = pathinfo($processing_path);
         $this->_processing_path = $pathinfo['dirname'] . DIRECTORY_SEPARATOR . $pathinfo['filename'] . '._u.' . String::generateRandomString() . '.u_.' . $pathinfo['extension'];
     }
 }
 /**
  * Renames any output from ffmpeg that would have been outputted in a sequence, ie using %d. Typically used with imagery.
  *
  * @access public
  * @author Oliver Lillie
  * @param  string $output_path The string notation for the output path.
  * @return array Returns an array of modified file paths.
  */
 protected function _renamePercentDOutput($output_path)
 {
     $output = array();
     //          we have the output path but we now need to treat differently dependant on if we have multiple file output.
     if (preg_match('/\\.(\\%([0-9]*)d)\\.([0-9\\.]+_[0-9\\.]+\\.)?_(i|t)\\./', $output_path, $matches) > 0) {
         //              determine what we have to rename all the files to.
         $convert_back_to = $matches[4] === 't' ? 'timecode' : (int) $matches[2];
         //              get the glob path and then find all the files from this output
         $output_glob_path = str_replace($matches[0], '.*.' . $matches[3] . '_' . $matches[4] . '.', $output_path);
         $outputted_files = glob($output_glob_path);
         //              sort the output naturally so that if there is no index padding that we get the frames in the correct order.
         natsort($outputted_files);
         //              loop to rename the file and then create each output object.
         $timecode = null;
         foreach ($outputted_files as $path) {
             if ($convert_back_to === 'timecode') {
                 //                      if the start timecode has not been generated then find the required from the path string.
                 if ($timecode === null) {
                     $matches[3] = rtrim($matches[3], '.');
                     $matches[3] = explode('_', $matches[3]);
                     $timecode = new Timecode($matches[3][1], Timecode::INPUT_FORMAT_SECONDS, $matches[3][0]);
                 } else {
                     $timecode->frame += 1;
                 }
                 $actual_path = preg_replace('/\\.[0-9]{12}\\.[0-9\\.]+_[0-9\\.]+\\._t\\./', $timecode->getTimecode('%hh_%mm_%ss_%ms', false), $path);
             } else {
                 $actual_path = preg_replace('/\\.([0-9]+)\\._i\\./', '$1', $path);
             }
             $actual_path = preg_replace('/\\._u\\.[0-9]{5}_[a-z0-9]{5}_[0-9]+\\.u_\\./', '.', $actual_path);
             rename($path, $actual_path);
             array_push($output, $actual_path);
         }
         unset($outputted_files);
         // TODO create the multiple image output
     }
     return $output;
 }
 /**
  * Once the process has been completed this function can be called to return the output
  * of the process. Depending on what the process is outputting depends on what is returned.
  * If a single video or audio is being outputted then the related PHPVideoToolkit media object
  * will be returned. However if multiple files are being outputed then an array of the associated
  * objects are returned. Typically speaking an array will be returned when %index or %timecode
  * are within the output path.
  *
  * @access public
  * @author Oliver Lillie
  * @return mixed
  */
 public function getOutput($post_process_callback = null)
 {
     if ($this->isCompleted() === false) {
         throw new FfmpegProcessOutputException('Encoding has not yet started.');
     }
     //			check for an error.
     if ($this->hasError() === true) {
         //				check for specific recieved signal errors.
         $last_split = $this->getLastSplit();
         if (preg_match('/Received signal ([0-9]+): terminating\\./', $last_split, $matches) > 0) {
             $kill_signals = array(1 => 'Hang up detected on controlling terminal or death of controlling process.', 2 => 'User sent an interrupt signal.', 3 => 'User sent a quit signal.', 4 => 'Illegal instruction.', 6 => 'Abort signal from abort(3).', 8 => 'Floating point exception.', 9 => 'Kill signal sent.', 11 => 'Invalid memory reference', 13 => 'Broken pipe: write to pipe with no readers', 14 => 'Timer signal from alarm(2)', 15 => 'Termination signal sent.', 24 => 'Imposed time limit ({length} seconds) exceeded.');
             // TODO add more signals.
             $kill_int = (int) $matches[1];
             if (isset($kill_signals[$kill_int]) === true) {
                 $message = $kill_signals[$kill_int];
                 if ($kill_int == 24) {
                     $length = $this->getCommand('-timelimit');
                     $length = !$length ? 'unknown' : $length;
                     $message = str_replace('{length}', $length, $message);
                 }
                 throw new FfmpegProcessOutputException('Process was aborted. ' . $message);
             } else {
                 throw new FfmpegProcessOutputException('Termination signal received and the process aborted. Signal was ' . $matches[1]);
             }
         }
         throw new FfmpegProcessOutputException('Encoding failed and an error was returned from ffmpeg. Error code ' . $this->getErrorCode() . ' was returned the message (if any) was: ' . $last_split);
     }
     if ($post_process_callback !== null) {
         if (is_callable($post_process_callback) === false) {
             throw new Exception('The supplied post proces scallback is not callable.');
         }
     }
     //			get the output of the process
     $output_path = $this->getOutputPath();
     //			we have the output path but we now need to treat differently dependant on if we have multiple file output.
     if (preg_match('/\\.(\\%([0-9]*)d)\\.([0-9\\.]+_[0-9\\.]+\\.)?_(i|t)\\./', $output_path, $matches) > 0) {
         //				determine what we have to rename all the files to.
         $convert_back_to = $matches[4] === 't' ? 'timecode' : (int) $matches[2];
         //				get the glob path and then find all the files from this output
         $output_glob_path = str_replace($matches[0], '.*.' . $matches[3] . '_' . $matches[4] . '.', $output_path);
         $outputted_files = glob($output_glob_path);
         //				sort the output naturally so that if there is no index padding that we get the frames in the correct order.
         natsort($outputted_files);
         //				loop to rename the file and then create each output object.
         $output = array();
         $timecode = null;
         foreach ($outputted_files as $path) {
             $actual_path = preg_replace('/\\._u\\.[0-9]{5}_[a-z0-9]{5}_[0-9]+\\.u_\\./', '.', $path);
             if ($convert_back_to === 'timecode') {
                 //						if the start timecode has not been generated then find the required from the path string.
                 if ($timecode === null) {
                     $matches[3] = rtrim($matches[3], '.');
                     $matches[3] = explode('_', $matches[3]);
                     $timecode = new Timecode($matches[3][1], Timecode::INPUT_FORMAT_SECONDS, $matches[3][0]);
                 } else {
                     $timecode->frame += 1;
                 }
                 $actual_path = preg_replace('/\\.[0-9]{12}\\.[0-9\\.]+_[0-9\\.]+\\._t\\./', $timecode->getTimecode('%hh_%mm_%ss_%ms', false), $actual_path);
             } else {
                 $actual_path = preg_replace('/\\.([0-9]+)\\._i\\./', '$1', $actual_path);
             }
             rename($path, $actual_path);
             $media_class = $this->_findMediaClass($actual_path);
             $output_object = new $media_class($actual_path, $this->_config, null, false);
             array_push($output, $output_object);
             unset($output_object);
         }
         unset($outputted_files);
         // TODO create the multiple image output
     } else {
         //				check for a none multiple file existence
         if (empty($output_path) === true) {
             throw new FfmpegProcessOutputException('Unable to find output for the process as it was not set.');
         } else {
             if (is_file($output_path) === false) {
                 throw new FfmpegProcessOutputException('The output "' . $output_path . '", of the Ffmpeg process does not exist.');
             } else {
                 if (filesize($output_path) <= 0) {
                     throw new FfmpegProcessOutputException('The output "' . $output_path . '", of the Ffmpeg process is a 0 byte file. Something must have gone wrong however it wasn\'t reported as an error by FFmpeg.');
                 }
             }
         }
         //				get the media class from the output.
         //				create the object from the class name and return the new object.
         $media_class = $this->_findMediaClass($output_path);
         $output = new $media_class($output, $this->_config, null, false);
     }
     //			do any post processing callbacks
     if ($post_process_callback !== null) {
         $output = call_user_func($post_process_callback, $output, $this);
     }
     //			finally return the output to the user.
     return $output;
 }
Exemple #4
0
 /**
  * @param Timecode $timecode
  *
  * @return Timecode
  */
 public function subtract(Timecode $timecode) : Timecode
 {
     $this->fromSeconds($this->getSeconds() - $timecode->getSeconds());
     return $this;
 }
 echo 'new Timecode(102.34); = ' . $timecode . '<br />';
 $timecode = new Timecode(102.34, Timecode::INPUT_FORMAT_SECONDS);
 echo 'new Timecode(102.34, Timecode::INPUT_FORMAT_SECONDS); = ' . $timecode . '<br />';
 $timecode = new Timecode(1.705666667, Timecode::INPUT_FORMAT_MINUTES);
 echo 'new Timecode(1.705666667, Timecode::INPUT_FORMAT_MINUTES); = ' . $timecode . '<br />';
 $timecode = new Timecode(0.028427778, Timecode::INPUT_FORMAT_HOURS);
 echo 'new Timecode(.028427778, Timecode::INPUT_FORMAT_HOURS); = ' . $timecode . '<br />';
 $timecode = new Timecode('00:01:42.34', Timecode::INPUT_FORMAT_TIMECODE);
 echo 'new Timecode(\'00:01:42.34\', Timecode::INPUT_FORMAT_TIMECODE); = ' . $timecode . '<br />';
 $timecode = new Timecode(60);
 echo 'new Timecode(60); = ' . $timecode . '<br />';
 $timecode = new Timecode(360);
 echo 'new Timecode(360); = ' . $timecode . '<br />';
 echo '<hr />';
 echo '<h2>Adjusting timecode values</h2>';
 $timecode = new Timecode('00:01:42.34', Timecode::INPUT_FORMAT_TIMECODE, 24);
 echo '$timecode = new Timecode(\'00:01:42.34\', Timecode::INPUT_FORMAT_TIMECODE); = ' . $timecode . '<br />';
 $adjustments = array(array(15, 'hours', true), array(-54102.34, 'seconds', true), array(-99, 'milliseconds', true), array(59, 'seconds', true), array(1, 'seconds', false), array(59, 'seconds', true), array(999, 'milliseconds', true), array(1, 'milliseconds', true), array(48, 'frames', false), array(-15, 'frames', true), array(-1, 'seconds', true), array(-375, 'milliseconds', true));
 foreach ($adjustments as $value) {
     if ($value[2] === true) {
         $timecode->{$value[1]} += $value[0];
         echo '$timecode->' . $value[1] . ' += ' . $value[0] . '; // = ' . $timecode->getTimecode('%hh:%mm:%ss:%ms') . '<br />';
     } else {
         echo '<Br />$timecode->reset();<br />';
         $timecode->reset();
         $timecode->{$value[1]} = $value[0];
         echo '$timecode->' . $value[1] . ' = ' . $value[0] . '; // = ' . $timecode->getTimecode('%hh:%mm:%ss:%ms') . '<br />';
     }
 }
 echo '<hr />';
 echo '<h2>Setting a timecode value</h2>';