<?php
if (!defined('RAPIDLEECH')) {
require_once('index.html');
exit();
}
class youtube_com extends DownloadClass {
private $page, $cookie, $fmtmaps, $vid, $sts = -1, $js, $playerJs, $sigJs, $jsVars, $cookieFile,
$fmts = array(22 => '0|720|0|192', 45 => '1|720|1|192', 44 => '1|480|1|192', 35 => '2|480|0|128', 43 => '1|360|1|128', 34 => '2|360|0|128', 18 => '0|360|0|96', 6 => '2|270|3|64', 5 => '2|240|3|64', 36 => '3|240|0|36', 17 => '3|144|0|24');
public function Download($link) {
$this->cookieFile = DOWNLOAD_DIR.'YT_cookie.txt';
$this->cookie = isset($_POST['step']) && !empty($_POST['cookie']) ? StrToCookies(decrypt(urldecode($_POST['cookie']))) : $this->loadCookie();
$url = parse_url($link);
$this->vid = array();
if (host_matches('youtu.be', $url['host'])) preg_match('@/([\w\-\.]{11})@i', $url['path'], $this->vid);
else if (empty($url['query']) || ($this->vid[1] = cut_str('&'.$url['query'].'&', '&v=', '&')) === false || !preg_match('@^[\w\-\.]{11}$@i', $this->vid[1])) preg_match('@/(?:v|(?:embed))/([\w\-\.]{11})@i', $url['path'], $this->vid);
if (empty($this->vid[1])) html_error('Video ID not found.');
$this->vid = $this->vid[1];
$this->link = 'https://www.youtube.com/watch?v='.$this->vid;
if (empty($_POST['step'])) $this->getFmtMaps();
else $this->captcha();
$yt_fmt = empty($_REQUEST['yt_fmt']) ? '' : $_REQUEST['yt_fmt'];
if (empty($yt_fmt) && !isset($_GET['audl'])) return $this->QSelector();
else if (isset($_REQUEST['ytube_mp4']) && $_REQUEST['ytube_mp4'] == 'on' && !empty($yt_fmt)) {
//look for and download the highest quality we can find?
if ($yt_fmt == 'highest') {
foreach (array_keys($this->fmts) as $itag) if (!empty($this->fmtmaps[$itag])) break;
} else if (!empty($this->fmtmaps[$yt_fmt])) {
$itag = $yt_fmt;
} else html_error('Specified video format not found');
} else { //just get the one Youtube plays by default (in some cases it could also be the highest quality format)
$itag = key($this->fmtmaps);
}
$fmt = $this->fmtmaps[$itag];
$is_dash = empty($this->fmts[$itag]);
if (empty($this->response['title'])) {
if (empty($this->response['player_response']['videoDetails']['title'])) html_error('No video title found! Download halted.');
else $this->response['title'] = $this->response['player_response']['videoDetails']['title'];
}
$filename = str_replace(str_split('\\\:*?"<>|=;'."\t\r\n\f"), '_', html_entity_decode(trim($this->response['title']), ENT_QUOTES));
if (!empty($_REQUEST['cleanname'])) $filename = preg_replace('@[^ A-Za-z_\-\d\.,\(\)\[\]\{\}&\!\'\@\%\#]@u', '_', $filename);
if (!empty($fmt['qualityLabel'])) $filename .= " [YT-{$fmt['qualityLabel']}]";
else if (!empty($fmt['quality_label'])) $filename .= " [YT-{$fmt['quality_label']}]";
else if (!$is_dash) {
$v = explode('|', $this->fmts[$itag]);
$filename .= " [YT-{$v[1]}p]";
} else $filename .= " [YT-Audio]";
$filename .= sprintf('[%s].%s', $this->vid, ($is_dash ? 'dash.' : '') . $fmt['_codecs']['container']);
$this->RedirectDownload($fmt['url'], $filename, $this->cookie, 0, ($is_dash ? $this->link . "\r\nRange: bytes=0-" : 0), $filename);
}
private function FormToArr($content, $v1 = '&', $v2 = '=') {
$rply = array();
if (strpos($content, $v1) === false || strpos($content, $v2) === false) return $rply;
foreach (array_filter(array_map('trim', explode($v1, $content))) as $v) {
$v = array_map('trim', explode($v2, $v, 2));
if ($v[0] != '') $rply[$v[0]] = $v[1];
}
return $rply;
}
private function captcha() {
$url = 'https://www.youtube.com/das_captcha';
if (!empty($_POST['step']) && $_POST['step'] == '1') {
$post = $this->verifyReCaptchav2();
$post['action_recaptcha_verify2'] = '1';
$post['session_token'] = $_POST['session_token'];
$page = $this->GetPage($url, $this->cookie, $post, $url);
is_present($page, 'The verification code was invalid', 'The verification code was invalid or has timed out, please try again.');
is_present($page, "\r\n\r\nAuthorization Error.", 'Error sending captcha.');
is_notpresent($page, 'Set-Cookie: goojf=', 'Cannot get captcha cookie.');
$this->cookie = GetCookiesArr($page, $this->cookie);
unset($_POST['step']);
$this->getFmtMaps();
} else {
$page = $this->GetPage($url, $this->cookie);
$this->cookie = GetCookiesArr($page, $this->cookie);
if (!preg_match('@class="g-recaptcha" data-sitekey="([\w\.\-]+)"@i', $page, $pid)) html_error('Error: reCAPTCHA2 not found.');
$data = $this->DefaultParamArr($this->link, $this->cookie, 1, 1);
$data['session_token'] = urlencode(cut_str($page, 'name="session_token" value="', '"'));
if (isset($_REQUEST['ytube_mp4'])) $data['ytube_mp4'] = $_REQUEST['ytube_mp4'];
if (isset($_REQUEST['ytdirect'])) $data['ytdirect'] = $_REQUEST['ytdirect'];
if (isset($_REQUEST['yt_fmt'])) $data['yt_fmt'] = $_REQUEST['yt_fmt'];
$data['step'] = '1';
$this->reCAPTCHAv2($pid[1], $data);
}
}
private function loadCookie() {
if (@file_exists($this->cookieFile) && ($file = file_get_contents($this->cookieFile)) && ($saved = @unserialize($file)) && is_array($saved) && !empty($saved['hash']) && !empty($saved['cookie']) && ($saved['cookie'] = decrypt(base64_decode($saved['cookie']))) && sha1($saved['cookie']) == $saved['hash']) {
return StrToCookies($saved['cookie']);
}
return array();
}
private function saveCookie() {
if (!empty($this->cookie)) {
$data = array('cookie' => CookiesToStr($this->cookie));
$data['hash'] = sha1($data['cookie']);
$data['cookie'] = base64_encode(encrypt($data['cookie']));
file_put_contents($this->cookieFile, serialize($data));
}
}
// Special Function Called by verifyReCaptchav2 When Captcha Is Incorrect, To Allow Retry. - Required
protected function retryReCaptchav2() {
$data = $this->DefaultParamArr($this->link, $this->cookie, 1, 1);
foreach (array('step', 'action_recaptcha_verify2', 'session_token', 'ytube_mp4', 'ytdirect', 'yt_fmt') as $name) {
if (!empty($_POST[$name])) $data[$name] = $_POST[$name];
}
return $this->reCAPTCHAv2($_POST['recaptcha2_public_key'], $data);
}
private function queryVideo() {
$this->page = $this->GetPage('https://www.youtube.com/get_video_info?hl=en_US&video_id=' . $this->vid . '&eurl=https%3A%2F%2Fgoogle.com%2F&html5=1' . ($this->sts > 0 ? '&sts=' . $this->sts : ''), $this->cookie);
$this->cookie = GetCookiesArr($this->page, $this->cookie);
$this->response = array_map('urldecode', $this->FormToArr(substr($this->page, strpos($this->page, "\r\n\r\n") + 4)));
if (!empty($this->response['requires_purchase'])) html_error('[Unsupported Video] This Video or Channel Requires a Payment to Watch.');
$this->response['player_response'] = json_decode($this->response['player_response'], true);
}
private function getFmtMaps() {
$this->queryVideo();
if (!empty($this->response['reason'])) html_error('['.htmlspecialchars($this->response['errorcode']).'] '.htmlspecialchars($this->response['reason']));
if (!empty($this->response['player_response']['playabilityStatus']['reason'])) html_error('['.htmlspecialchars($this->response['player_response']['playabilityStatus']['reason']).'] '.htmlspecialchars($this->response['player_response']['playabilityStatus']['errorScreen']['playerErrorMessageRenderer']['subreason']['runs'][0]['text']));
if (!empty($this->response['player_response']['streamingData']['formats'])) {
$this->response['_formats'] = $this->response['player_response']['streamingData']['formats'];
}
if (!empty($this->response['player_response']['streamingData']['adaptiveFormats'])) {
$this->response['_adaptiveFormats'] = $this->response['player_response']['streamingData']['adaptiveFormats'];
}
if (in_array(substr($this->page, 9, 3), array('402', '429')) || preg_match('@Location: https?://(www\.)?youtube\.com/das_captcha@i', $this->page)) return $this->captcha();
if (!empty($this->cookie['goojf'])) $this->saveCookie();
$this->fmtmaps = array();
$keys2Merge = array('cipher', 'signatureCipher');
foreach (array('url_encoded_fmt_stream_map', 'adaptive_fmts', '_formats', '_adaptiveFormats') as $map) {
if (empty($this->response[$map])) continue;
if (!is_array($this->response[$map])) $this->response[$map] = explode(',', $this->response[$map]);
foreach ($this->response[$map] as $fmt) {
if (!is_array($fmt)) $fmt = array_map('urldecode', $this->FormToArr($fmt));
foreach($keys2Merge as $key) if (!empty($fmt[$key])) {
$fmt += array_map('urldecode', $this->FormToArr($fmt[$key]));
}
if (empty($fmt['itag']) || empty($fmt['url'])) continue;
if (!empty($fmt['s']) && empty($this->encS)) {
if ($this->sts < 1) return $this->getCipher();
else html_error('[' . $this->sts . '] No decoded steps');
}
$fmt['url'] = parse_url($fmt['url']);
$fmt['url']['query'] = array_map('urldecode', $this->FormToArr($fmt['url']['query']));
if (!empty($fmt['s']) && !empty($fmt['sp']))
{
$fmt['url']['query']["{$fmt['sp']}"] = $this->sigDecode($fmt['s']);
}
else if (empty($fmt['s']) && !empty($fmt['sig']))
{
if (empty($fmt['url']['query']['signature'])) $fmt['url']['query']['signature'] = $fmt['sig'];
}
else if (empty($fmt['url']['query']['signature']) && empty($fmt['url']['query']['sig'])) html_error("Cannot get signature key name");
if (empty($fmt['url']['query']['ratebypass'])) $fmt['url']['query']['ratebypass'] = 'yes'; // Fix for Slow Downloads of DASH Formats
ksort($fmt['url']['query']);
$fmt['url']['query'] = http_build_query($fmt['url']['query']);
$fmt['url'] = rebuild_url($fmt['url']);
if (!empty($fmt['mimeType']) && preg_match('@(audio|video)/(?:x-)?(\w+); codecs="(\w+)(?:\.\w+)*(?:, (\w+)(?:\.\w+)*)?"@', $fmt['mimeType'], $mimeType)) {
$fmt['_codecs'] = array();
$fmt['_codecs']['type'] = $mimeType[1];
$fmt['_codecs']['container'] = $mimeType[2];
if (!empty($mimeType[4])) {
$fmt['_codecs']['type'] = 'dual';
$fmt['_codecs']['video'] = $mimeType[3];
$fmt['_codecs']['audio'] = $mimeType[4];
} else if ($mimeType[1] == 'video') {
$fmt['_codecs']['video'] = $mimeType[3];
} else {
$fmt['_codecs']['container'] = str_replace(array('mp4a', 'vorbis'), array('m4a', 'ogg'), $mimeType[3]);
$fmt['_codecs']['audio'] = $mimeType[3];
}
if (!empty($fmt['_codecs']['video'])) $fmt['_codecs']['video'] = strtoupper(str_replace(array('av01', 'avc1'), array('AV1', 'H264'), $fmt['_codecs']['video']));
if (!empty($fmt['_codecs']['audio'])) $fmt['_codecs']['audio'] = ucfirst(str_replace('mp4a', 'AAC', $fmt['_codecs']['audio']));
}
$this->fmtmaps[$fmt['itag']] = $fmt;
}
}
if (empty($this->fmtmaps)) html_error('[' . $this->sts . '] Video links not found.');
}
private function decError($msg) {
html_error("Error while decoding [{$this->sts}][{$this->js[2]}]: $msg");
}
private function findFunction($fName, $num) {
if ($fName == 'T8') return "w$num";
$obj = explode('.', $fName, 3);
if (count($obj) > 2) $this->decError("Cannot search function: '$fName'");
if (count($obj) > 1) {
$fName = $obj[1];
$obj = $obj[0];
if (empty($this->jsVars[$obj]['src'])) {
if (($spos = strpos($this->playerJs, "var $obj={")) === false || ($epos = strpos($this->playerJs, '};', $spos)) === false) $this->decError("Cannot find object '$obj'");
$spos += strlen("var $obj={");
$this->jsVars[$obj] = array('src' => substr($this->playerJs, $spos, $epos - $spos), 'fn' => array());
}
if (empty($this->jsVars[$obj]['fn'][$fName]['step'])) {
$v = '[\$_A-Za-z][\$\w]*';
if (!preg_match("@(?<=^|,)\s*$fName:function\($v(?:,($v))?\)\{([^}]+)\}\s*(?=,|$)@", $this->jsVars[$obj]['src'], $src)) $this->decError("Cannot find function '$obj.$fName'");
$src[0] = trim($src[0]);
$this->jsVars[$obj]['fn'][$fName] = array('src' => $src);
if (empty($src[1])) return $this->jsVars[$obj]['fn'][$fName]['step'] = 'r';
else if (preg_match("@var\s+($v)=($v)\[0\];\\2\[0\]=\\2\[{$src[1]}%\\2(?:\.length|\[$v\])\];\\2\[{$src[1]}(?:%\\2(?:\.length|\[$v\]))?\]=\\1@", $src[2])) {
$this->jsVars[$obj]['fn'][$fName]['step'] = 'w%d';
return "w$num";
} else if (preg_match("@(?:$v=)?$v(?:\.s(p)?lice|\[$v\])\((?(1)0,){$src[1]}\)@", $src[2])) {
$this->jsVars[$obj]['fn'][$fName]['step'] = 's%d';
return "s$num";
} else if (preg_match("@(?:$v=)?$v(?:\.reverse|\[$v\])\(\)@", $src[2])) return $this->jsVars[$obj]['fn'][$fName]['step'] = 'r';
else $this->decError("Error parsing function '$obj.$fName'");
} else return sprintf($this->jsVars[$obj]['fn'][$fName]['step'], $num);
}
if (empty($this->jsVars[$fName]['step'])) {
if (($spos = strpos($this->playerJs, "function $fName(")) === false || ($epos = strpos($this->playerJs, '};', $spos)) === false) $this->decError("Cannot find function '$fName'");
$this->jsVars[$fName] = array('src' => substr($this->playerJs, $spos, $epos - $spos));
$v = '[\$_A-Za-z][\$\w]*';
if (!preg_match("@^function\s+$fName\($v(?:,($v))?\)\{(.*)$@", $this->jsVars[$fName]['src'], $pars)) $this->decError("Cannot parse function '$fName'");
if (empty($pars[1])) return $this->jsVars[$fName]['step'] = 'r';
else if (preg_match("@var\s+($v)=($v)\[0\];\\2\[0\]=\\2\[{$pars[1]}%\\2(?:\.length|\[$v\])\];\\2\[{$pars[1]}(?:%\\2(?:\.length|\[$v\]))?\]=\\1@", $src[2])) {
$this->jsVars[$fName]['step'] = 'w%d';
return "w$num";
} else if (preg_match("@(?:$v=)?$v(?:\.s(p)?lice|\[$v\])\((?(1)0,){$src[1]}\)@", $src[2])) {
$this->jsVars[$fName]['step'] = 's%d';
return "s$num";
} else if (preg_match("@(?:$v=)?$v(?:\.reverse|\[$v\])\(\)@", $src[2])) return $this->jsVars[$fName]['step'] = 'r';
else $this->decError("Error parsing function '$fName'");
} else return sprintf($this->jsVars[$fName]['step'], $num);
}
// getCipher & sigDecode are based on jwz's youtubedown code.
private function getCipher() {
$this->changeMesg('<br />Video with ciphered signature, trying to decode it.', 1);
$page = $this->GetPage('https://www.youtube.com/embed/'.$this->vid, $this->cookie);
$this->cookie = GetCookiesArr($page, $this->cookie);
if (preg_match('@"sts"\s*:\s*(\d+)@i', $page, $this->sts) && intval($this->sts[1])) {
$this->sts = intval($this->sts[1]);
}
$savefile = DOWNLOAD_DIR.'YT_lastjs.txt';
if (!preg_match('@(?<=[\"\'])(?:(?:https?:?)?//((?:[\w\-]+\.)+[\w\-]+(?:\:\d+)?))?(/(?:[^\"\'/]+/)+?(?:html5)?player[-_][\w\-\.]+(?:(?:/\w+)?/[\w\-\.]+)?)\.js@i', str_replace('\\/', '/', $page), $this->js)) html_error('YT\'s player javascript not found.');
$this->changeMesg(sprintf('<br />Found player: %s.js', $this->js[2]), 1);
if (@file_exists($savefile) && ($file = file_get_contents($savefile, false, NULL, 0, 822)) && ($saved = @unserialize($file)) && is_array($saved) && !empty($saved['js']) && !empty($saved['sts']) && !empty($saved['steps']) && ((!$this->sts && $saved['js'] == $this->js[2]) || $saved['sts'] == $this->sts) && preg_match('@^\s*([ws]\d+|r)( ([ws]\d+|r))*\s*$@', $saved['steps'])) {
$this->changeMesg(sprintf('<br />Using cached decoder: [%d] %s.', $saved['sts'], $saved['steps']), 1);
$this->encS = explode(' ', trim($saved['steps']));
if (empty($this->sts)) $this->sts = $saved['sts'];
} else {
$this->changeMesg('<br />Loading video player data.', 1);
$this->playerJs = $this->GetPage('https://' . (!empty($this->js[1]) ? $this->js[1] : 'www.youtube.com') . $this->js[2] . '.js', $this->cookie, 0, 'https://www.youtube.com/embed/'.$this->vid);
$v = '[\$_A-Za-z][\$\w]*';
$v3 = '[\$_A-Za-z][\$\w]{3,}';
if (empty($this->sts)) {
if (preg_match('@\b(?:sts|signatureTimestamp)\s*[:=]\s*(\d+)@i', $this->playerJs, $sts)) {
$this->sts = intval($sts[1]);
} else if (preg_match("@[:=]($v+(?:\.$v+)?\(\s*\"STS\"[^()?:]+?\))\s*\?\s*\1\s*:\s*(\d\d+)\b@i", $this->playerJs, $sts)) {
$this->sts = intval($sts[2]);
} else if (preg_match("@\b(?:sts|signatureTimestamp)\s*[:=]\s*($v)@", $this->playerJs, $sts, PREG_OFFSET_CAPTURE) && preg_match("@(?:var\s+|[,{};])\s*{$sts[1][0]}\s*[=:]\s*(\d\d+)@", $this->playerJs, $sts2, 0, strrpos($this->playerJs, "\n", $sts[0][1] - strlen($this->playerJs)))) {
$this->sts = intval($sts2[1]);
}
if (empty($this->sts)) html_error('Signature TimeStamp not found.');
}
//if (($spos = strpos($this->playerJs, '.sig||')) === false) $this->decError('Not found (".sig||")');
//if (($cut1 = cut_str(substr($this->playerJs, $spos), '{', '}')) == false) $this->decError('Cannot get inner content of "if(X.sig||X.s)"');
if (!preg_match("@(?:\.sig\|\||\.set\(\"signature\",|\|\"signature\",|$v\.sp,|$v=)(?:\(0,$v(?:\.$v)*\)\(|$v3\()?($v)\((?:\(0,$v(?:\.$v)*\)\(|$v3\()?$v\.s\)@", $this->playerJs, $fn)) $this->decError('Cannot get decoder function name');
$fn = preg_quote($fn[1], '@');
if (!preg_match("@(?:function\s+$fn\s*\(|var\s+$fn\s*=\s*function\s*\(|(?<=(?:{|,|;))\s*$fn\s*=\s*function\s*\()@", $this->playerJs, $fpos, PREG_OFFSET_CAPTURE)) $this->decError('Cannot find decoder function');
$fpos = $fpos[0][1];
if (($cut2 = cut_str(substr($this->playerJs, $fpos), '{', '}')) == false) $this->decError('Cannot get decoder function contents');
$this->sigJs = preg_replace("@var $v=$v\[0\];$v\[0\]=($v)\[(\d+)%$v(?:\.length|\[$v\])\];$v\[\d+\]=$v;@", '$1=T8($1,$2);', trim($cut2));
$this->encS = array();
foreach (array_map('trim', explode(';', '{'.$this->sigJs.'}')) as $step) {
if (($step[0] == '{' || substr($step, strlen($step) - 1, 1) == '}') && (preg_match("@^\{(?:var\s+)?$v=$v(?:\.split|\[$v\])\(\"\"\)$@", $step) || preg_match("@^return\s+$v(?:\.join|\[$v\])\(\"\"\);?\}$@", $step))) continue;
else if (preg_match("@^(?:$v=)?((?:$v.)*$v)\($v\,(\d+)\)$@", $step, $s)) $this->encS[] = $this->findFunction($s[1], $s[2]);
else if (preg_match("@^(?:$v=)?$v(?:\.s(p)?lice|\[$v\])\((?(1)0,)(\d+)\)$@", $step, $s)) $this->encS[] = 's'.$s[2];
else if (preg_match("@^(?:$v=)?$v(?:\.reverse|\[$v\])\(\)$@", $step)) $this->encS[] = 'r';
else $this->decError($step.' | Unknown step on decoder function.');
}
if (empty($this->encS)) $this->decError('Empty decoded result');
$this->changeMesg(sprintf('<br />Decoder: [%d] %s.', $this->sts, implode(' ', $this->encS)), 1);
file_put_contents($savefile, serialize(array('js' => $this->js[2], 'sts' => $this->sts, 'steps' => implode(' ', $this->encS))));
}
// Request video fmts with the current sts
$this->getFmtMaps();
}
private function sigDecode($sig) {
if (empty($this->encS)) $this->decError('sigDecode() can\'t be called before getCipher()');
$_sig = $sig;
$sig = str_split($sig);
foreach ($this->encS as $_step) {
if (!preg_match('@^\s*([wrs])(\d*)\s*$@', $_step, $step) || ($step[1] != 'r' && !array_key_exists(2, $step))) $this->decError("Unknown decoding step \"$_step\"");
switch ($step[1]) {
case 'w': $step[2] = (int)$step[2];$x = $sig[0];$sig[0] = $sig[$step[2] % count($sig)];$sig[$step[2]] = $x; break;
case 's': $step[2] = (int)$step[2];$sig = array_slice($sig, $step[2]); break;
case 'r': $sig = array_reverse($sig); break;
}
}
return implode($sig);
}
private function bitrate2KMG($bitrate) {
if (!is_numeric($bitrate)) return 'Unknown';
$s = array('', 'K', 'M', 'G');
$e = min(floor(log($bitrate) / log(1000)), count($s) - 1);
return sprintf("%.2f {$s[$e]}bps", ($bitrate / pow(1000, $e)));
}
private function QSelector() {
$C = array('V' => array('MP4', 'WebM', 'FLV', '3GP'), 'A' => array('AAC', 'Vorbis', 'Opus', 'MP3'));
$sizes = array();
/* Add a // at the start of this line for enable this code.
if (extension_loaded('curl') && function_exists('curl_init') && function_exists('curl_exec') && !trim(ini_get('open_basedir'))) {
$sizes = array();
$opt = array(CURLOPT_FOLLOWLOCATION => true, CURLOPT_MAXREDIRS => 5, CURLOPT_NOBODY => true);
foreach ($this->fmtmaps as $itag => $fmt) {
if (empty($this->fmts[$itag]) || !empty($fmt['contentLength']) || !empty($fmt['clen'])) continue;
$headers = explode("\r\n\r\n", cURL($fmt['url'], $this->cookie, 0, 0, 0, $opt));
$headers = ((count($headers) > 2) ? $headers[count($headers) - 2] : $headers[0]) . "\r\n\r\n";
if (substr($headers, 9, 3) == '200' && ($CL = cut_str($headers, "\nContent-Length: ", "\n")) && $CL > 1024) $sizes[$itag] = bytesToKbOrMbOrGb(trim($CL));
if (substr($headers, 9, 3) == '403') {
$this->changeMesg('<br />Cannot Ask Video Filesize of (' . $itag . ') (Signature Error?)');
break;
}
}
unset($headers, $CL);
} //*/
echo "\n<br /><br /><h3 style='text-align: center;'>".lang(216).".</h4>";
echo "\n<center><form name='YT_QS' action='{$_SERVER['SCRIPT_NAME']}' method='POST'>\n";
echo "<input type='hidden' name='yt_QS' value='on' />\n";
echo '<label><input type="checkbox" name="cleanname" checked="checked" value="1" /><small> Remove non-supported characters from filename</small></label><br />';
echo "<select name='yt_fmt' id='QS_fmt'>\n";
foreach ($this->fmtmaps as $itag => $fmt) {
if (!empty($this->fmts[$itag])) {
// Classic Formats
$size = (!empty($fmt['contentLength']) ? ' ('.bytesToKbOrMbOrGb($fmt['contentLength']).')' : (!empty($fmt['clen']) ? ' ('.bytesToKbOrMbOrGb($fmt['clen']).')' : (!empty($sizes[$itag]) ? ' ('.$sizes[$itag].')' : '')));
if (($I = explode('|', $this->fmts[$itag]))) printf('<option value="%d"' . ($itag == '22' ? ' selected="selected"' : '') . ">[%1\$d] Video: %s %dp | Audio: %s ~%d Kbps%s</option>\n", $itag, $C['V'][$I[0]], $I[1], $C['A'][$I[2]], $I[3], $size);
} else if (!empty($fmt['_codecs'])) {
// DASH Streams
$size = (!empty($fmt['contentLength']) ? ' ('.bytesToKbOrMbOrGb($fmt['contentLength']).')' : (!empty($fmt['clen']) ? ' ('.bytesToKbOrMbOrGb($fmt['clen']).')' : ''));
switch ($fmt['_codecs']['type']) {
case 'video':
printf("<option value='%d'>[%1\$d] Video only: %s (%s) @ %s%s</option>\n", $itag, str_replace('WEBM', 'WebM', strtoupper($fmt['_codecs']['container'])), $fmt['_codecs']['video'], (!empty($fmt['qualityLabel']) ? $fmt['qualityLabel'] : $fmt['quality_label']), $size);
break;
case 'audio':
printf("<option value='%d'>[%1\$d] Audio only: %s @ ~%s%s</option>\n", $itag, $fmt['_codecs']['audio'], $this->bitrate2KMG((!empty($fmt['averageBitrate']) ? $fmt['averageBitrate'] : $fmt['bitrate'])), $size);
break;
}
}
}
echo "</select>\n";
$data = $this->DefaultParamArr($this->link);
$data['ytube_mp4'] = 'on';
foreach ($data as $n => $v) echo("<input type='hidden' name='$n' id='QS_$n' value='$v' />\n");
echo "<input type='submit' name='Th3-822' value='".lang(209)."' />\n";
echo "</form></center>\n</body>\n</html>";
exit;
}
}
//re-written by szal based on original plugin by eqbal
//updated 07 June 2010
// [28-03-2011] Fixed (!$video_id) regex. - Th3-822
// [29-03-2011] Added support for captcha. - Th3-822
// [02-04-2011] Fixed redirect error. [26-04-2011] Added error msgs. - Th3-822
// [04-8-2011] Fixed for recent changes in fmt_stream_map content & some edits maded for work fine. (Redirect is needed yet) - Th3-822
// [12-8-2011] Added support for videos that need login for verify age & Changed fmt order by quality & Fixed regexps for fileext. - Th3-822
// [13-8-2011] Some fixes & removed not working code & fixed verify_age function. - Th3-822
// [17-9-2011] Added function for skip 'verify_controversy' on youtube && Fixed cookies after captcha && Little changes. - Th3-822
// [26-1-2012] Fixed regexp for get title, added a quality selector (if the one in template is removed) and some changes in the code. - Th3-822
// [17-5-2012] Fixed captcha (Now uses reCaptcha). - Th3-822
// [14-9-2012] Fixed Download links & small changes. - Th3-822
// [07-10-2012] Fixed for redirect at link. - Th3-822
// [02-1-2013] Using new way for getting links and video info, now it doesn't need login for restricted videos. - Th3-822
// [02-10-2013] Fixed issues with videos with ciphered signature & Rewritten quality selector (Now it doesn't use lang) & Remove direct-link option & Added option for sanitize filenames & small changes. - Th3-822
// [04-3-2014] Re-Added Support for 3GP quality. - Th3-822
// [16-3-2014] Added functions for decoding ciphered signatures. - Th3-822
// [28-7-2014] Fixed signature decoding functions. - Th3-822
// [17-12-2014] Forced https on all the requests for avoid redirect errors. - Th3-822
// [14-1-2015] Fixed Age Restrictions. (Please, do not annoy my inbox when a plugin fails, go to the forum) - Th3-822
// [21-1-2015] Fixed backslash in filename when cleanname is off. - Th3-822
// [13-4-2015] Fixed captcha detection. - Th3-822
// [05-2-2016] Fixed captcha (Now uses reCaptcha2) & Added cookie storage for it. - Th3-822
// [08-6-2016] Added support to download DASH formats & Revised video formats handling. - Th3-822
// [30-8-2016] Fixed slow speed while downloading DASH streams. - Th3-822
// [30-4-2017] Fixed signature decoding functions. - Th3-822
// [25-1-2018] Fixed get_video_info. - Th3-822
// [03-2-2019] Fixed signature decoding functions. - Th3-822
// [25-5-2019] Fixed embed JS regex. - Th3-822
// [19-6-2019] Fixed signature key name. - Th3-822
// [10-7-2019] Fixed signature search and related functions. - Th3-822
// [27-8-2019] Fixed video title code. - Th3-822
// [04-1-2020] Fixed fmts handling & Fixed signature search. - Th3-822
// [02-2-2020] Fixed signature search. - Th3-822
// [15-3-2020] Fixed embed JS regex. - Th3-822
// [31-5-2020] Fixed signature search & Rewrote quality selector to parse and show all available formats. - Th3-822
// [17-8-2020] Fixed signature timestamp search. - Th3-822
// [30-5-2021] Fixed get_video_info & Now MP4 @ 720p is default (if available) on the selector. - Th3-822
Comments