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 \RuntimeException(
                'Frame time is not in range '.$frameTime.'/'.$this->getDuration().' '.$this->getFilename()
            );
        }

        $image_size = '';
        if (is_numeric($height) && is_numeric($width)) {
            $image_size = ' -s '.$width.'x'.$height;
        }

        $qualityCommand = '';
        if (is_numeric($quality)) {
            $qualityCommand = ' -qscale '.$quality;
        }

        $deleteTmp = false;
        if ($frameFilePath === null) {
            $frameFilePath = sys_get_temp_dir().DIRECTORY_SEPARATOR.uniqid('frame', true).'.jpg';
            $deleteTmp = true;
        }

        $output = [];

        // 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(
                ' ',
                [
                    $this->ffmpegBinary,
                    '-ss '.$seek1,
                    '-i '.escapeshellarg($this->movieFile),
                    '-f image2',
                    '-ss '.$seek2,
                    '-vframes 1',
                    $image_size,
                    $qualityCommand,
                    escapeshellarg($frameFilePath),
                    '2>&1',
                ]
            ),
            $output,
            $retVar
        );
        /** @noinspection ReferenceMismatchInspection */
        $stringOutput = implode(PHP_EOL, $output);

        // Cannot write frame to the data storage
        if (!file_exists($frameFilePath)) {
            // Find error in output
            preg_match(self::$REGEX_ERRORS, $stringOutput, $errors);
            if ($errors) {
                throw new \RuntimeException($errors[0]);
            }
            // Default file not found error
            throw new \RuntimeException('TMP image not found/written '.$frameFilePath);
        }

        // Create gdimage and delete temporary image
        $imageSize = getimagesize($frameFilePath);
        switch ($imageSize[2]) {
            case IMAGETYPE_GIF:
                $gdImage = imagecreatefromgif($frameFilePath);
                break;
            case IMAGETYPE_PNG:
                $gdImage = imagecreatefrompng($frameFilePath);
                break;
            default:
                $gdImage = imagecreatefromjpeg($frameFilePath);
        }

        if ($deleteTmp && is_writable($frameFilePath)) {
            unlink($frameFilePath);
        }

        $frame = new Frame($gdImage, $frameTime);
        imagedestroy($gdImage);

        return $frame;
    }