Файл: _system/class/YoutubeDownloader.php
Строк: 276
<?
###################################################
#     BY MAKSAMKA (Mr.MAX) ICQ 154-26-36          #
#       Поиск видео > API v3 Youtube              #
#      Версия сборки V.2.0                        #
###################################################
class YoutubeDownloader
{
    protected $url;
    protected $fullInfo;
    public function __construct($url)
    {
        $this->url = $url;
    }
    public function getFullInfo()
    {
        if (is_null($this->fullInfo)) {
            preg_match("/^.*((youtu.be/)|(v/)|(/u/w/)|(embed/)|(watch?))??v?=?([^#&?]*).*/i", $this->url, $matches);
            $videoId = $matches[7];
            $clearContent = file_get_contents("https://www.youtube.com/watch?v=$videoId");
            preg_match('/ytplayer.config = ({.+});ytplayer/', $clearContent, $mathes);
            $configData = json_decode($mathes[1], true);
            $streamList = explode(',', $configData['args']['url_encoded_fmt_stream_map']);
            $configData['args']['url_encoded_fmt_stream_map'] = array();
            foreach ($streamList AS $stream) {
                parse_str($stream, $res);
                $configData['args']['url_encoded_fmt_stream_map'][] = $res;
            }
            $streamList = explode(',', $configData['args']['adaptive_fmts']);
            $configData['args']['adaptive_fmts'] = array();
            foreach ($streamList AS $stream) {
                parse_str($stream, $res);
                $configData['args']['adaptive_fmts'][] = $res;
            }
            $this->fullInfo = $configData['args'];
            $this->fullInfo['html'] = $clearContent;
            $this->fullInfo['video_id'] = $videoId;
            $jsPath = $configData['assets']['js'];
            $jsPlayerClear = file_get_contents('https://www.youtube.com' . $jsPath);
            preg_match('/set("signature",s*(?:([^(]*).*))/', $jsPlayerClear, $mathes);
            $signParserFnName = $mathes[1];
            preg_match("/$signParserFnName=(function(.+;)/", $jsPlayerClear, $mathes);
            $signFnBody = $mathes[1];
            preg_match("/(w+).w+([wd,]+)/", $signFnBody, $mathes);
            $helperObjName = $mathes[1];
            preg_match_all("/$helperObjName.(w+)(w+,(d+));/", $signFnBody, $matches);
            $startPos = strpos($jsPlayerClear, "$helperObjName={");
            $calcRule = array();
            foreach ($matches[1] AS $i => $fnName) {
                $calcRule[] = array(
                    'function' => $fnName,
                    'param' => $matches[2][$i],
                );
            }
            $endPos = $startPos + strlen($helperObjName) + 2;
            $startPos += strlen($helperObjName) + 1;
            $countOpenBraces = 1;
            while ($countOpenBraces > 0) {
                $char = substr($jsPlayerClear, $endPos, 1);
                $endPos++;
                if ($char == '{') {
                    $countOpenBraces++;
                } elseif ($char == '}') {
                    $countOpenBraces--;
                }
            }
            $helperObjBody = substr($jsPlayerClear, $startPos, $endPos - $startPos);
            preg_match_all('/(w+):function([w,]+){([^}]+)}/', $helperObjBody, $matches);
            $calcFunctions = array();
            foreach ($matches[1] AS $i => $fnName) {
                $fnBody = $matches[2][$i];
                if (strpos($fnBody, 'reverse') !== false) {
                    $calcFunctions[$fnName] = 'reverse';
                    continue;
                }
                if (strpos($fnBody, 'splice') !== false) {
                    $calcFunctions[$fnName] = 'splice';
                    continue;
                }
                $calcFunctions[$fnName] = 'swap';
            }
            foreach ($calcRule AS &$calcItem) {
                $calcItem['function'] = $calcFunctions[$calcItem['function']];
            }
            $this->fullInfo['calcSignatureSteps'] = $calcRule;
            $calcSignExpression = <<<JS
    function calculateSignature(sign){
        var __HELPER_OBJ_NAME = __HELPER_OBJ_BODY__;
        var calcSign = __SIGN_FN_BODY__;
        return calcSign(sign);
    }
JS;
            $calcSignExpression = str_replace('__HELPER_OBJ_NAME', $helperObjName, $calcSignExpression);
            $calcSignExpression = str_replace('__HELPER_OBJ_BODY__', $helperObjBody, $calcSignExpression);
            $calcSignExpression = str_replace('__SIGN_FN_BODY__', $signFnBody, $calcSignExpression);
            $this->fullInfo['jsSignatureGen'] = $calcSignExpression;
        }
        return $this->fullInfo;
    }
    public function getBaseInfo()
    {
        $fullInfo = $this->getFullInfo();
        $baseInfo = array();
        $baseInfo['name'] = $fullInfo['title'];
        $videoId = $fullInfo['video_id'];
        $baseInfo['previewUrl'] = "https://img.youtube.com/vi/$videoId/hqdefault.jpg";
        $html = $fullInfo['html'];
        if (preg_match('/<p id="eow-description"[^>]+>(.+)</p>/', $html, $matches)) {
            $baseInfo['description'] = strip_tags(str_replace('<br />', "n", $matches[1]));
        }
        return $baseInfo;
    }
    public static function getItagInfo()
    {
        return array(
            5 => array('format' => 'flv', 'withVideo' => true, 'withAudio' => true),
            6 => array('format' => 'flv', 'withVideo' => true, 'withAudio' => true),
            13 => array('format' => '3gp', 'withVideo' => true, 'withAudio' => true),
            17 => array('format' => '3gp', 'withVideo' => true, 'withAudio' => true),
            18 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            22 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            34 => array('format' => 'flv', 'withVideo' => true, 'withAudio' => true),
            35 => array('format' => 'flv', 'withVideo' => true, 'withAudio' => true),
            36 => array('format' => '3gp', 'withVideo' => true, 'withAudio' => true),
            37 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            38 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            43 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            44 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            45 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            46 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            82 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            83 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            84 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            85 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            100 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            101 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            102 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => true),
            92 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            93 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            94 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            95 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            96 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            132 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            151 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => true),
            133 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            134 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            135 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            136 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            137 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            138 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            160 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            264 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            298 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            299 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            266 => array('format' => 'mp4', 'withVideo' => true, 'withAudio' => false),
            139 => array('format' => 'm4a', 'withVideo' => false, 'withAudio' => true),
            140 => array('format' => 'm4a', 'withVideo' => false, 'withAudio' => true),
            141 => array('format' => 'm4a', 'withVideo' => false, 'withAudio' => true),
            167 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            168 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            169 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            170 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            218 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            219 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            242 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            243 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            244 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            245 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            246 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            247 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            248 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            271 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            272 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            302 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            303 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            308 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            313 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            315 => array('format' => 'webm', 'withVideo' => true, 'withAudio' => false),
            171 => array('format' => 'webm', 'withVideo' => false, 'withAudio' => true),
            172 => array('format' => 'webm', 'withVideo' => false, 'withAudio' => true),
        );
    }
    static protected function calcSignature($signatureEncoded, $steps)
    {
        $sign = str_split($signatureEncoded);
        foreach ($steps AS $step) {
            $fn = $step['function'];
            $param = (int)$step['param'];
            switch ($fn) {
                case 'reverse':
                    $sign = array_reverse($sign);
                    break;
                case 'splice':
                    $sign = array_slice($sign, $param);
                    break;
                case 'swap':
                    $c = $sign[0];
                    $sign[0] = $sign[$param % count($sign)];
                    $sign[$param] = $c;
                    break;
            }
        }
        return implode('', $sign);
    }
    public static function getResponseHeaders($url)
    {
        $clearHeaders = get_headers($url);
        $headers = array();
        foreach ($clearHeaders AS $header) {
            $header = explode(':', $header);
            if (count($header) === 2) {
                $headers[$header[0]] = trim($header[1]);
            }
        }
        return $headers;
    }
    public function getDownloadInfoOne($fmtsItem)
    {
        $fullInfo = $this->getFullInfo();
        $title = $fullInfo['title'];
        $url = $fmtsItem['url'] . '&title=' . $_SERVER['HTTP_HOST'] .'_' . urlencode($title);
        $signature = false;
        if (isset($fmtsItem['signature'])) {
            $signature = $fmtsItem['signature'];
        }
        if (isset($fmtsItem['sig'])) {
            $signature = self::calcSignature($fmtsItem['sig'], $fullInfo['calcSignatureSteps']);
        }
        if (isset($fmtsItem['s'])) {
            $signature = self::calcSignature($fmtsItem['s'], $fullInfo['calcSignatureSteps']);
        }
        if ($signature) {
            $url .= '&signature=' . $signature;
        }
        $headers = self::getResponseHeaders($url);
        $downloadInfo = array();
          $downloadInfo['url'] = $url;
        $downloadInfo['youtubeItag'] = $fmtsItem['itag'];
        $downloadInfo['fileType'] = explode(';', $fmtsItem['type']);
        $downloadInfo['fileType'] = $downloadInfo['fileType'][0];
        $ext = explode('/', $downloadInfo['fileType']);
        $ext = $ext[1];
        $downloadInfo['name'] = $title . '.' . $ext;
        $downloadInfo['itagInfo'] = self::getItagInfo();
        if (isset($downloadInfo['itagInfo'][$fmtsItem['itag']])) {
            $downloadInfo['itagInfo'] = $downloadInfo['itagInfo'][$fmtsItem['itag']];
        } else {
            $downloadInfo['itagInfo'] = null;
        }
        $downloadInfo[] = $downloadInfo;
        return $downloadInfo;
    }
    /**
     * Return array of download information for video
     *
     * @return VideoDownloadInfo[]
     * @throws VideoDownloaderDownloadException
     */
    public function getDownloadsInfo()
    {
        $fullInfo = $this->getFullInfo();
        $fmts = $fullInfo['url_encoded_fmt_stream_map'];
        $fmts = array_merge($fmts, $fullInfo['adaptive_fmts']);
        $downloadsInfo = array();
        foreach ($fmts AS $item) {
            $downloadsInfo[] = $this->getDownloadInfoOne($item);
        }
        return $downloadsInfo;
    }
}