/** * Extracts a segment of the media object. * * @access public * @author Oliver Lillie * @param Timecode $from_timecode * @param Timecode $to_timecode * @param boolean $accurate If true then accuracy is prefered over performance. * @return Media */ public function extractSegment(Timecode $from_timecode = null, Timecode $to_timecode = null, $accurate = false) { // check that a segment extract has not already been set if (empty($this->_extract_segment) === false) { throw new \LogicException('Extract segment options have already been set. You cannot call extractSegment more than once on a ' . get_class($this) . ' object.'); } // check that a split has already been set as if it has we can't extract a segment // however we can extract a segment, then split it. if (empty($this->_split_options) === false) { throw new \LogicException('You cannot extract a segment once ' . get_class($this) . '::split has been called. You can however extract a segment, the call ' . get_class($this) . '::split.'); } // check the timecodes against the duration $duration = $this->readDuration(); if ($from_timecode !== null && $duration->total_seconds < $from_timecode->total_seconds) { throw new \InvalidArgumentException('The duration of the media is less than the starting timecode specified.'); } else { if ($to_timecode !== null && $duration->total_seconds < $to_timecode->total_seconds) { throw new \InvalidArgumentException('The duration of the media is less than the end timecode specified.'); } } $this->_extract_segment = array('preseek' => null, 'seek' => null, 'length' => null); // if the from timecode is greater than say 15 seconds, we will stream seek to 15 seconds before the // required extracted segment before the input to improve extract performance. // See http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg $pre_input_stream_seek_offset = 0; $pre_input_stream_seek_adjustment = 15; if ($from_timecode !== null) { if ($accurate === false) { if ($from_timecode->total_seconds > $pre_input_stream_seek_adjustment) { $pre_input_stream_seek_offset = $from_timecode->total_seconds - $pre_input_stream_seek_adjustment; $seek_timecode = new Timecode($pre_input_stream_seek_offset, Timecode::INPUT_FORMAT_SECONDS); $this->_extract_segment['preseek'] = $seek_timecode; } // if we have a pre input stream seek then input video is then offset by that ammount if ($pre_input_stream_seek_offset > 0) { $from_timecode = new Timecode($pre_input_stream_seek_adjustment, Timecode::INPUT_FORMAT_SECONDS); } } // then seek the exact position after input $begin_position = $from_timecode->getTimecode('%hh:%mm:%ss.%ms', false); $this->_extract_segment['seek'] = $from_timecode; } else { $from_timecode = new Timecode(0, Timecode::INPUT_FORMAT_SECONDS); } // then add the number of seconds to export for if there is an end timecode. if ($to_timecode !== null) { // if we have a pre input stream seek then input video is then offset by that ammount if ($pre_input_stream_seek_offset > 0) { $to_timecode = new Timecode($to_timecode->total_seconds - $pre_input_stream_seek_offset, Timecode::INPUT_FORMAT_SECONDS); } $this->_extract_segment['length'] = $to_timecode->total_seconds - $from_timecode->total_seconds; } return $this; }
/** * 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; }
/** * 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; }
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>'; $timecode->setSeconds(193.7); echo '$timecode->setSeconds(193.7); = ' . $timecode . '<br />'; $timecode->setTimecode('12:45:39.01'); echo '<br /><strong>IMPORTANT: Notice the difference between total_seconds and seconds</strong><br />$timecode->setTimecode(\'12:45:39.01\'); <br />'; echo '$timecode->total_seconds = ' . $timecode->total_seconds . '<br />'; echo '$timecode->seconds = ' . $timecode->seconds . '<br />';