movieFile = $moviePath; $this->frameNumber = 0; $this->ffmpegBinary = $ffmpegBinary; if ($outputProvider === null) { $outputProvider = new FFmpegOutputProvider($ffmpegBinary); } $this->setProvider($outputProvider); } /** * Setting provider implementation * * @param OutputProvider $outputProvider */ public function setProvider(OutputProvider $outputProvider) { $this->provider = $outputProvider; $this->provider->setMovieFile($this->movieFile); $this->output = $this->provider->getOutput(); } /** * Getting current provider implementation * * @return OutputProvider */ public function getProvider() { return $this->provider; } /** * Return the duration of a movie or audio file in seconds. * * @return float movie duration in seconds */ public function getDuration() { if ($this->duration === null) { $match = array(); preg_match(self::$REGEX_DURATION, $this->output, $match); if (array_key_exists(1, $match) && array_key_exists(2, $match) && array_key_exists(3, $match)) { $hours = (int) $match[1]; $minutes = (int) $match[2]; $seconds = (int) $match[3]; $fractions = (float) ((array_key_exists(5, $match)) ? "0.$match[5]" : 0.0); $this->duration = (($hours * (3600)) + ($minutes * 60) + $seconds + $fractions); } else { $this->duration = 0.0; } return $this->duration; } return $this->duration; } /** * Return the number of frames in a movie or audio file. * * @return int */ public function getFrameCount() { if ($this->frameCount === null) { $this->frameCount = (int) ($this->getDuration() * $this->getFrameRate()); } return $this->frameCount; } /** * Return the frame rate of a movie in fps. * * @return float */ public function getFrameRate() { if ($this->frameRate === null) { $match = array(); preg_match(self::$REGEX_FRAME_RATE, $this->output, $match); $this->frameRate = (float) ((array_key_exists(1, $match)) ? $match[1] : 0.0); } return $this->frameRate; } /** * Return the path and name of the movie file or audio file. * * @return string */ public function getFilename() { return $this->movieFile; } /** * Return the comment field from the movie or audio file. * * @return string */ public function getComment() { if ($this->comment === null) { $match = array(); preg_match(self::$REGEX_COMMENT, $this->output, $match); $this->comment = (array_key_exists(2, $match)) ? trim($match[2]) : ''; } return $this->comment; } /** * Return the title field from the movie or audio file. * * @return string */ public function getTitle() { if ($this->title === null) { $match = array(); preg_match(self::$REGEX_TITLE, $this->output, $match); $this->title = (array_key_exists(2, $match)) ? trim($match[2]) : ''; } return $this->title; } /** * Return the author field from the movie or the artist ID3 field from an mp3 file; alias $movie->getArtist() * * @return string */ public function getArtist() { if ($this->artist === null) { $match = array(); preg_match(self::$REGEX_ARTIST, $this->output, $match); $this->artist = (array_key_exists(3, $match)) ? trim($match[3]) : ''; } return $this->artist; } /** * Return the author field from the movie or the artist ID3 field from an mp3 file. * * @return string */ public function getAuthor() { return $this->getArtist(); } /** * Return the copyright field from the movie or audio file. * * @return string */ public function getCopyright() { if ($this->copyright === null) { $match = array(); preg_match(self::$REGEX_COPYRIGHT, $this->output, $match); $this->copyright = (array_key_exists(2, $match)) ? trim($match[2]) : ''; } return $this->copyright; } /** * Return the genre ID3 field from an mp3 file. * * @return string */ public function getGenre() { if ($this->genre === null) { $match = array(); preg_match(self::$REGEX_GENRE, $this->output, $match); $this->genre = (array_key_exists(2, $match)) ? trim($match[2]) : ''; } return $this->genre; } /** * Return the track ID3 field from an mp3 file. * * @return int */ public function getTrackNumber() { if ($this->trackNumber === null) { $match = array(); preg_match(self::$REGEX_TRACK_NUMBER, $this->output, $match); $this->trackNumber = (int) ((array_key_exists(2, $match)) ? $match[2] : 0); } return $this->trackNumber; } /** * Return the year ID3 field from an mp3 file. * * @return int */ public function getYear() { if ($this->year === null) { $match = array(); preg_match(self::$REGEX_YEAR, $this->output, $match); $this->year = (int) ((array_key_exists(2, $match)) ? $match[2] : 0); } return $this->year; } /** * Return the height of the movie in pixels. * * @return int */ public function getFrameHeight() { if ($this->frameHeight == null) { $match = array(); preg_match(self::$REGEX_FRAME_WH, $this->output, $match); if (array_key_exists(1, $match) && array_key_exists(2, $match)) { $this->frameWidth = (int) $match[1]; $this->frameHeight = (int) $match[2]; } else { $this->frameWidth = 0; $this->frameHeight = 0; } } return $this->frameHeight; } /** * Return the width of the movie in pixels. * * @return int */ public function getFrameWidth() { if ($this->frameWidth === null) { $this->getFrameHeight(); } return $this->frameWidth; } /** * Return the pixel format of the movie. * * @return string */ public function getPixelFormat() { if ($this->pixelFormat === null) { $match = array(); preg_match(self::$REGEX_PIXEL_FORMAT, $this->output, $match); $this->pixelFormat = (array_key_exists(1, $match)) ? trim($match[1]) : ''; } return $this->pixelFormat; } /** * Return the bit rate of the movie or audio file in bits per second. * * @return int */ public function getBitRate() { if ($this->bitRate === null) { $match = array(); preg_match(self::$REGEX_BITRATE, $this->output, $match); $this->bitRate = (int) ((array_key_exists(1, $match)) ? ($match[1] * 1000) : 0); } return $this->bitRate; } /** * Return the bit rate of the video in bits per second. * * NOTE: This only works for files with constant bit rate. * * @return int */ public function getVideoBitRate() { if ($this->videoBitRate === null) { $match = array(); preg_match(self::$REGEX_VIDEO_BITRATE, $this->output, $match); $this->videoBitRate = (int) ((array_key_exists(1, $match)) ? ($match[1] * 1000) : 0); } return $this->videoBitRate; } /** * Return the audio bit rate of the media file in bits per second. * * @return int */ public function getAudioBitRate() { if ($this->audioBitRate === null) { $match = array(); preg_match(self::$REGEX_AUDIO_BITRATE, $this->output, $match); $this->audioBitRate = (int) ((array_key_exists(1, $match)) ? ($match[1] * 1000) : 0); } return $this->audioBitRate; } /** * Return the audio sample rate of the media file in bits per second. * * @return int */ public function getAudioSampleRate() { if ($this->audioSampleRate === null) { $match = array(); preg_match(self::$REGEX_AUDIO_SAMPLE_RATE, $this->output, $match); $this->audioSampleRate = (int) ((array_key_exists(1, $match)) ? $match[1] : 0); } return $this->audioSampleRate; } /** * Return the current frame index. * * @return int */ public function getFrameNumber() { return ($this->frameNumber == 0) ? 1 : $this->frameNumber; } /** * Return the name of the video codec used to encode this movie as a string. * * @return string */ public function getVideoCodec() { if ($this->videoCodec === null) { $match = array(); preg_match(self::$REGEX_VIDEO_CODEC, $this->output, $match); $this->videoCodec = (array_key_exists(1, $match)) ? trim($match[1]) : ''; } return $this->videoCodec; } /** * Return the name of the audio codec used to encode this movie as a string. * * @return string */ public function getAudioCodec() { if ($this->audioCodec === null) { $match = array(); preg_match(self::$REGEX_AUDIO_CODEC, $this->output, $match); $this->audioCodec = (array_key_exists(1, $match)) ? trim($match[1]) : ''; } return $this->audioCodec; } /** * Return the number of audio channels in this movie as an integer. * * @return int */ public function getAudioChannels() { if ($this->audioChannels === null) { $match = array(); preg_match(self::$REGEX_AUDIO_CHANNELS, $this->output, $match); if (array_key_exists(1, $match)) { switch (trim($match[1])) { case 'mono': $this->audioChannels = 1; break; case 'stereo': $this->audioChannels = 2; break; case '5.1': $this->audioChannels = 6; break; case '5:1': $this->audioChannels = 6; break; default: $this->audioChannels = (int) $match[1]; } } else { $this->audioChannels = 0; } } return $this->audioChannels; } /** * Return boolean value indicating whether the movie has an audio stream. * * @return boolean */ public function hasAudio() { return (boolean) preg_match(self::$REGEX_HAS_AUDIO, $this->output); } /** * Return boolean value indicating whether the movie has a video stream. * * @return boolean */ public function hasVideo() { return (boolean) preg_match(self::$REGEX_HAS_VIDEO, $this->output); } /** * Returns a frame from the movie as an FFmpegFrame object. Returns false if the frame was not found. * * * framenumber - Frame from the movie to return. If no framenumber is specified, returns the next frame of the movie. * * @param int $framenumber * @param int $height * @param int $width * @param int $quality * @return FFmpegFrame|boolean */ public function getFrame($framenumber = null, $height = null, $width = null, $quality = null) { $framePos = ($framenumber === null) ? $this->frameNumber : (((int) $framenumber) - 1); // Frame position out of range if (!is_numeric($framePos) || $framePos < 0 || $framePos > $this->getFrameCount()) { return false; } $frameTime = round((($framePos / $this->getFrameCount()) * $this->getDuration()), 4); $frame = $this->getFrameAtTime($frameTime, $height, $width, $quality); // Increment internal frame number if ($framenumber === null) { ++$this->frameNumber; } return $frame; } /** * Returns a frame from the movie as an FFmpegFrame object. Returns false if the frame was not found. * * @param float $seconds * @param int $width * @param int $height * @param int $quality * @param string $frameFilePath * @param array $output * * @throws Exception * * @return FFmpegFrame|boolean * */ public function getFrameAtTime($seconds = null, $width = null, $height = null, $quality = null, $frameFilePath = null, &$output = null) { // Set frame position for frame extraction $frameTime = ($seconds === null) ? 0 : $seconds; // time out of range if (!is_numeric($frameTime) || $frameTime < 0 || $frameTime > $this->getDuration()) { throw(new Exception('Frame time is not in range '.$frameTime.'/'.$this->getDuration().' '.$this->getFilename())); } if(is_numeric($height) && is_numeric($width)) { $image_size = ' -s '.$width.'x'.$height; } else { $image_size = ''; } if(is_numeric($quality)) { $quality = ' -qscale '.$quality; } else { $quality = ''; } $deleteTmp = false; if ($frameFilePath === null) { $frameFilePath = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('frame', true).'.jpg'; $deleteTmp = true; } $output = array(); // Fast and accurate way to seek. First quick-seek before input up to // a point just before the frame, and then accurately seek after input // to the exact point. // See: http://ffmpeg.org/trac/ffmpeg/wiki/Seeking%20with%20FFmpeg if ($frameTime > 30) { $seek1 = $frameTime - 30; $seek2 = 30; } else { $seek1 = 0; $seek2 = $frameTime; } exec(implode(' ', array( $this->ffmpegBinary, '-ss '.$seek1, '-i '.escapeshellarg($this->movieFile), '-f image2', '-ss '.$seek2, '-vframes 1', $image_size, $quality, escapeshellarg($frameFilePath), '2>&1', )), $output, $retVar); $output = join(PHP_EOL, $output); // Cannot write frame to the data storage if (!file_exists($frameFilePath)) { // Find error in output preg_match(self::$REGEX_ERRORS, $output, $errors); if ($errors) { throw new Exception($errors[0]); } // Default file not found error throw new Exception('TMP image not found/written '. $frameFilePath); } // Create gdimage and delete temporary image $gdImage = imagecreatefromjpeg($frameFilePath); if ($deleteTmp && is_writable($frameFilePath)) { unlink($frameFilePath); } $frame = new FFmpegFrame($gdImage, $frameTime); imagedestroy($gdImage); return $frame; } /** * Returns the next key frame from the movie as an FFmpegFrame object. Returns false if the frame was not found. * * @return FFmpegFrame|boolean */ public function getNextKeyFrame() { return $this->getFrame(); } public function __clone() { $this->provider = clone $this->provider; } public function serialize() { $data = serialize(array( $this->ffmpegBinary, $this->movieFile, $this->output, $this->frameNumber, $this->provider )); return $data; } public function unserialize($serialized) { list($this->ffmpegBinary, $this->movieFile, $this->output, $this->frameNumber, $this->provider ) = unserialize($serialized); } }