<?php

/*
 * @author : Aqib
 * @name: Youtube Automated
 * @website : https://codemsi.com
 *
 */

function curl_get_file_contents($URL)
    {
        $c = curl_init();
        curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($c, CURLOPT_URL, $URL);
        $contents = curl_exec($c);
        curl_close($c);

        if ($contents) return $contents;
        else return FALSE;
    }  

class YTDownloader {
    private $cache_dir;
    private $cookie_dir;
    public $error = false;
    public $length_error = false;
    public $play = null;
    public $id;
    public $yt_title;
    public $duration;
    public $links;
    public $url;
    private $itag_info = array(
        43 =>   ["type" => "WEBM", "quality" => "360p"],
        247 =>  ["type" => "WEBM", "quality" => "720p"],
        22 =>   ["type" => "MP4", "quality" => "720p"],
        18 =>   ["type" => "MP4", "quality" => "360p"],
        36 =>   ["type" => "3GP", "quality" => "240p"],
        17 =>   ["type" => "3GP", "quality" => "144p"],
    );
    
    function __construct($url='') {
        $this->cache_dir  = dirname(__FILE__) . '/.cache';
        $this->cookie_dir = sys_get_temp_dir();
        $this->url        = $url;
        if (!file_exists($this->cache_dir)) {
            mkdir($this->cache_dir, 0755);
        }
    }


    public function get_id() {
        $url     = $this->url;
        $pattern = '#^(?:https?://)?(?:www\.)?(?:youtu\.be/|youtube\.com(?:/embed/|/v/|';
        $pattern .= '/watch\?v=|/watch\?.+&v=))([\w-]{11})(?:.+)?$#x';
        preg_match($pattern, $url, $matches);
        return (isset($matches[1])) ? $matches[1] : false;
    }


    public function getDownloadLinks() {
        $id       = $this->get_id();
        $videoID  = $id;
        $this->id = $id;
        $webPage  = $this->curlGet('https://www.youtube.com/watch?v=' . $videoID);
        $sts      = null;
        if (preg_match('/player_response":"(.*?)\"}};/', $webPage, $matches)) {
            $match           = stripslashes($matches[1]);
            $player_response = json_decode($match, true);
        }elseif (preg_match('/ytInitialPlayerResponse = (.*?);<\/script>/', $webPage, $matches)) {
            $match           = $matches[1];
            $player_response = json_decode($match, true);
        }else{
            $this->error = true;
            return false;
        }
        $url = $this->playerURI($webPage);
        $js = @curl_get_file_contents($url);
        $result = $this->getLinks($player_response, $js);
        if(count($result)==0){
            $this->error = true;
            return false;
        }
        return $result;
    }


    private function playerURI($video_html) {
        $player_url = null;
        if (preg_match('@<script\s*src="([^"]+player[^"]+js)@', $video_html, $matches)) {
            $player_url = $matches[1];
            if (strpos($player_url, '//') === 0) {
                $player_url = 'http://' . substr($player_url, 2);
            } elseif (strpos($player_url, '/') === 0) {
                $player_url = 'http://www.youtube.com' . $player_url;
            }
        }
        return $player_url;
    }

    private function getLinks($player_response, $js_code) {
            $formats = $player_response['streamingData']['formats'];
            $adaptiveFormats = $player_response['streamingData']['adaptiveFormats'];

            $this->yt_title = $player_response['videoDetails']['title'];
            $this->duration = gmdate("H:i:s", $player_response['videoDetails']['lengthSeconds']);

            if (!is_array($formats)) {
                $formats = array();
            }

            if (!is_array($adaptiveFormats)) {
                $adaptiveFormats = array();
            }

            $formats_combined = array_merge($formats, $adaptiveFormats);

            // final response
            $return = array();

            foreach ($formats_combined as $item) {
				if(isset($item['cipher'])) {
					$cipher = $item['cipher'];
				}elseif(isset($item['signatureCipher'])) {
					$cipher = $item['signatureCipher'];
				}else{
					$cipher = [];
				}
                $itag = $item['itag'];

                if (!isset($this->itag_info[$itag])) {
                    continue;
                }

                if (isset($item['url'])) {

                    $return[] = array(
                        'url' => $item['url'].'&title='.$this->clean_name($this->yt_title),
                        'size' => $this->size($item),
                        'type' => $this->itag_info[$itag]['type'],
                        'quality' => $this->itag_info[$itag]['quality']
                    );

                    continue;
                }

                parse_str($cipher, $result);

                $url = $result['url'];
                $sp = $result['sp']; // typically 'sig'
                $signature = $result['s'];

                $params = compact('url');
                if (isset($item['contentLength'])) 
                    $params = array_merge($params, ['contentLength' => $item['contentLength']]);

                $decoded_signature = $this->decodeSig($signature, $js_code);

                // redirector.googlevideo.com
                //$url = preg_replace('@(\/\/)[^\.]+(\.googlevideo\.com)@', '$1redirector$2', $url);
                $return[] = array(
                    'url' => $url . '&' . $sp . '=' . $decoded_signature.'&title='.$this->clean_name($this->yt_title),
                    'size' => $this->size($params),
                    'type' => $this->itag_info[$itag]['type'],
                    'quality' => $this->itag_info[$itag]['quality']
                );
            }

            return $return;
    }

    private function decodeSig($signature, $js_code) {
        $func_name = '';
        if (preg_match('@,\s*encodeURIComponent\((\w{2})@is', $js_code, $matches)) {
            $func_name = $matches[1];
            $func_name = preg_quote($func_name);
        }elseif(preg_match('@\b([a-zA-Z0-9$]{2})\s*=\s*function\(\s*a\s*\)\s*{\s*a\s*=\s*a\.split\(\s*""\s*\)@is', $js_code, $matches)) {
            $func_name = preg_quote($matches[1]);
        }

        // PHP instructions
        $instructions = (array)$this->sigCode($func_name, $js_code);

        foreach ($instructions as $opt) {

            $command = $opt[0];
            $value = $opt[1];

            if ($command == 'swap') {

                $temp = $signature[0];
                $signature[0] = $signature[$value % strlen($signature)];
                $signature[$value] = $temp;

            } elseif ($command == 'splice') {
                $signature = substr($signature, $value);
            } elseif ($command == 'reverse') {
                $signature = strrev($signature);
            }
        }

        return trim($signature);

    }

    public function sigCode($func_name, $html) {
        if (preg_match('/' . $func_name . '=function\([a-z]+\){(.*?)}/', $html, $matches)) {
            $js_code = $matches[1];

            if (preg_match_all('/([a-z0-9]{2})\.([a-z0-9]{2})\([^,]+,(\d+)\)/i', $js_code, $matches) != false) {

                $obj_list = $matches[1];
                $func_list = $matches[2];

                preg_match_all('/(' . implode('|', $func_list) . '):function(.*?)\}/m', $html, $matches2, PREG_SET_ORDER);

                $functions = array();

                foreach ($matches2 as $m) {

                    if (strpos($m[2], 'splice') !== false) {
                        $functions[$m[1]] = 'splice';
                    } elseif (strpos($m[2], 'a.length') !== false) {
                        $functions[$m[1]] = 'swap';
                    } elseif (strpos($m[2], 'reverse') !== false) {
                        $functions[$m[1]] = 'reverse';
                    }
                }

                $instructions = array();

                foreach ($matches[2] as $index => $name) {
                    $instructions[] = array($functions[$name], $matches[3][$index]);
                }

                return $instructions;
            }
        }

        return null;
    }

    public function curlGet($url) {
        $tmpfname = $this->cookie_dir . '/cookie.txt';
        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_HEADER, 0);

        curl_setopt($ch, CURLOPT_COOKIEJAR, $tmpfname);
        curl_setopt($ch, CURLOPT_COOKIEFILE, $tmpfname);
		
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);

        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

        $result = curl_exec($ch);
        curl_close($ch);

        return $result;
    }

    private function clean_name($name) {
        $special_chars = array("?", "[", "]", "/", "\\", "=", "<", ">", ":", ";", ",", "'", "\"", "&", "$", "#", "*", "(", ")", "|", "~", "`", "!", "{", "}", "%", "+", chr(0));
        $filename = str_replace($special_chars,' ',$name);
        $filename = preg_replace( "#\x{00a0}#siu", ' ', $filename );
        $filename = str_replace( array( '%20', '+', ' '), '-', $filename );
        $filename = preg_replace( '/[\r\n\t -]+/', '-', $filename );
        $filename = trim( $filename, '.-_' );

        return $filename;
    }

    function size($item) {
        if (isset($item['contentLength'])) {
            return $this->format_size($item['contentLength']);
        }

        $url = $item['url'];
        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
        curl_setopt($ch, CURLOPT_HEADER, TRUE);
        curl_setopt($ch, CURLOPT_NOBODY, TRUE);
        curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);

        $data = curl_exec($ch);
        $clen = curl_getinfo($ch, CURLINFO_CONTENT_LENGTH_DOWNLOAD);
        curl_close($ch);
        $size = 'Unknown';
        return $this->format_size($clen);
    }

    function format_size($clen) {
        $size = 'Unknown';

        switch ($clen) {
            case $clen < 1024:
                $size = $clen . ' B';
                break;
            case $clen < 1048576:
                $size = round($clen / 1024, 2) . ' KB';
                break;
            case $clen < 1073741824:
                $size = round($clen / 1048576, 2) . ' MB';
                break;
            case $clen < 1099511627776:
                $size = round($clen / 1073741824, 2) . ' GB';
                break;
        }
        return $size;
	}

}