├── .gitignore ├── README.md ├── autoLoad.php ├── config.php ├── core ├── .gitignore ├── Cache │ └── FileCache.php ├── Command │ ├── CliProgressBar.php │ └── Console.php ├── Common │ ├── ArrayHelper.php │ ├── Downloader.php │ ├── FFmpeg.php │ ├── File.php │ ├── M3u8.php │ └── StringHelper.php ├── Config │ └── Config.php ├── Http │ ├── .gitignore │ ├── Client.php │ ├── Curl.php │ ├── Domain.php │ └── MessageInterface │ │ ├── Message.php │ │ ├── Request.php │ │ ├── Response.php │ │ ├── ServerRequest.php │ │ ├── Stream.php │ │ ├── UploadFile.php │ │ └── Uri.php ├── Platform │ ├── Douyu │ │ └── Douyu.php │ ├── Iqiyi │ │ └── Iqiyi.php │ ├── Krcom │ │ └── Krcom.php │ ├── Miaopai │ │ └── Miaopai.php │ ├── Porn │ │ └── Porn.php │ ├── Pornhub │ │ └── Pornhub.php │ ├── Qq │ │ └── Qq.php │ ├── Twitter │ │ └── Twitter.php │ ├── Weibo │ │ └── Weibo.php │ ├── Xvideos │ │ └── Xvideos.php │ └── Youku │ │ └── Youku.php └── Style │ └── Color.php └── start.php /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project files 2 | .idea 3 | .DS_Store 4 | downloadTest.php 5 | command.php 6 | test.php 7 | pornhub.html 8 | Runtime 9 | Videos 10 | JS 11 | config.php -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # phpVideos 2 | php 写的视频下载工具: 3 | 4 | | 站点 | 视频 | 5 | | :-----: | :---: | 6 | | 优酷 | :white_check_mark: | 7 | | 秒拍 | :white_check_mark: | 8 | | 腾讯 | :white_check_mark: | 9 | | XVideos | :white_check_mark: | 10 | | Pornhub | :white_check_mark: | 11 | | 91(不提供地址) | :white_check_mark: | 12 | | 微博酷燃 | :white_check_mark: | 13 | | 斗鱼 | :white_check_mark: | 14 | | 爱奇艺 | :white_check_mark: | 15 | | Weibo | :white_check_mark: | 16 | | Twitter | :white_check_mark: | 17 | # 环境依赖 18 | * PHP >= 7.1.3 19 | * OpenSSL PHP Extension 20 | * cURL PHP Extension 21 | * FFmpeg: 22 | * Mac:brew install ffmpeg 23 | * Linux: [Download](http://ffmpeg.org/download.html) 24 | * cURL >= 7.37.0 25 | 26 | # 使用交流 27 | Discord地址:https://discord.gg/xvNQPaT 28 | 29 | # 代理配置 30 | * config.php 31 | 32 |
33 |     return [
34 |         //http 代理配置
35 |         'http_proxy' => 'http://127.0.0.1:1087',
36 |     ];
37 |     
38 | 39 | # 使用方法 40 | php start.php 'link_address' 41 | 42 | ![image](https://image.ibb.co/mysKyd/Jul_21_2018_21_38_34.gif) 43 | 44 | # 其他 45 | Centos7 默认的 curl 版本是 7.29 的,需要升级。升级方法: 46 |
47 | rpm -ivh http://mirror.city-fan.org/ftp/contrib/yum-repo/city-fan.org-release-2-1.rhel7.noarch.rpm
48 | yum --enablerepo=city-fan.org update curl
49 | 
50 | curl 升级之后, php 需要重新编译,不能单独编译 php-curl 扩展 51 | -------------------------------------------------------------------------------- /autoLoad.php: -------------------------------------------------------------------------------- 1 | '', 11 | ]; -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | AbstractInterface 2 | Style -------------------------------------------------------------------------------- /core/Cache/FileCache.php: -------------------------------------------------------------------------------- 1 | rootPath = $directory; 24 | $this->flags = $flags; 25 | } 26 | 27 | /** 28 | * @param $key 29 | * @param $value 30 | * @param int $expire second 31 | * @return bool 32 | * @throws \ErrorException 33 | */ 34 | public function set($key, $value,int $expire=0):bool 35 | { 36 | if(!is_string($key)){ 37 | throw new \ErrorException("Cache key name must be string"); 38 | } 39 | 40 | if(empty($value)){ 41 | return false; 42 | } 43 | 44 | $expireTime = $expire; 45 | if($expire > 0){ 46 | $expireTime = time() + $expire; 47 | } 48 | 49 | $data = serialize([$value, $expireTime]); 50 | 51 | $fileName = md5($key); 52 | $saveFilename = $this->rootPath . $fileName; 53 | 54 | $this->checkDirectory(); 55 | 56 | file_put_contents($saveFilename, $data, $this->flags); 57 | 58 | return true; 59 | } 60 | 61 | /** 62 | * @param string $dir 63 | * @return FileCache 64 | */ 65 | public function setFileDir(string $dir):self 66 | { 67 | $dir = ltrim($dir, '/'); 68 | $dir = ltrim($dir, '\\'); 69 | $this->fileDir = $dir; 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * @param string $key 76 | * @param string $data 77 | * @param string $suffix 78 | * @return null|string 79 | * @throws \ErrorException 80 | */ 81 | public function setFile(string $key,string $data, string $suffix='.html'):?string 82 | { 83 | if(!is_string($key)){ 84 | throw new \ErrorException("Cache key name must be string"); 85 | } 86 | 87 | if(empty($this->fileDir)){ 88 | throw new \ErrorException('fileDir must set'); 89 | } 90 | 91 | $fileName = md5($key); 92 | 93 | $this->rootPath = $this->rootPath . $this->fileDir; 94 | $saveFilename = $this->rootPath . $fileName . $suffix; 95 | 96 | $this->checkDirectory(); 97 | 98 | file_put_contents($saveFilename, $data, $this->flags); 99 | 100 | return $saveFilename; 101 | } 102 | 103 | public function deleteFile() 104 | { 105 | 106 | } 107 | 108 | /** 109 | * @param string $key 110 | * @param null $default 111 | * @return array|null|string 112 | */ 113 | public function get(string $key, $default=null) 114 | { 115 | $fileName = md5($key); 116 | $saveFile = $this->rootPath . $fileName; 117 | if(!file_exists($saveFile)){ 118 | return false; 119 | } 120 | 121 | $data = file_get_contents($saveFile); 122 | $data = unserialize($data); 123 | 124 | $expireTime = $data[1] ?? null; 125 | $time = time(); 126 | if(!empty($expireTime) && $expireTime < $time){ 127 | $this->delete($key); 128 | return $default; 129 | } 130 | 131 | return $data[0]; 132 | } 133 | 134 | /** 135 | * @param string $key 136 | * @return bool 137 | */ 138 | public function delete(string $key):bool 139 | { 140 | $fileName = md5($key); 141 | $saveFile = $this->rootPath . $fileName; 142 | 143 | return unlink($saveFile); 144 | } 145 | 146 | public function checkDirectory():void 147 | { 148 | if(!is_dir($this->rootPath)){ 149 | mkdir($this->rootPath, 0777, true); 150 | } 151 | } 152 | 153 | } -------------------------------------------------------------------------------- /core/Command/CliProgressBar.php: -------------------------------------------------------------------------------- 1 | '; 20 | public $defaultChar = '='; 21 | public $barLength = 40; 22 | public $step = 40; 23 | public $currentStep = 0; 24 | public $network = 0; 25 | 26 | /** 27 | * CliProgressBar constructor. 28 | * @param int $barLength 29 | */ 30 | public function __construct(int $barLength=0) 31 | { 32 | if($barLength > 0){ 33 | $this->barLength = $barLength; 34 | } 35 | $this->setColor(); 36 | $this->output(); 37 | } 38 | 39 | public function __get($name) 40 | { 41 | return $this->$name; 42 | } 43 | 44 | /** 45 | * @param $step 46 | * @return $this 47 | */ 48 | public function setStep(int $step):self 49 | { 50 | $this->step = $step; 51 | return $this; 52 | } 53 | 54 | public function getStep():int 55 | { 56 | return $this->step; 57 | } 58 | 59 | public function setCurrentStep(int $currentStep):self 60 | { 61 | $this->currentStep = $currentStep; 62 | 63 | if($this->currentStep > $this->step){ 64 | $this->currentStep = $this->step; 65 | } 66 | 67 | return $this; 68 | } 69 | 70 | public function getCurrentStep():int 71 | { 72 | return $this->currentStep; 73 | } 74 | 75 | /** 76 | * @param int $value 77 | * @return CliProgressBar 78 | */ 79 | public function setNetwork(int $value):self 80 | { 81 | $this->network = $value; 82 | return $this; 83 | } 84 | 85 | /** 86 | * @return int 87 | */ 88 | public function getNetwork():int 89 | { 90 | $network = $this->network < 1 ? 1024*8 : $this->network; 91 | return $network; 92 | } 93 | 94 | /** 95 | * @param int $currentStep 96 | * @param bool $initStep 97 | * @return $this 98 | */ 99 | public function progress(int $currentStep=1,bool $initStep=true) 100 | { 101 | if(!$initStep){ 102 | $step = $this->getCurrentStep() + $currentStep; 103 | $this->setCurrentStep($step); 104 | } else { 105 | $this->setCurrentStep($currentStep); 106 | } 107 | 108 | $this->output(); 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * @param int $startColor 115 | * @param int $endColor 0 重置颜色 116 | * @return $this 117 | */ 118 | public function setColor(int $startColor=32, int $endColor=0):self 119 | { 120 | $this->colors = [ 121 | sprintf(self::COLOR_FORMAT, $startColor), 122 | sprintf(self::COLOR_FORMAT, $endColor), 123 | ]; 124 | 125 | return $this; 126 | } 127 | 128 | /** 129 | * @return $this 130 | */ 131 | public function colorToGreen():self 132 | { 133 | $this->setColor(32); 134 | return $this; 135 | } 136 | 137 | /** 138 | * @return $this 139 | */ 140 | public function colorToRed():self 141 | { 142 | $this->setColor(31); 143 | return $this; 144 | } 145 | 146 | /** 147 | * @param string $char 148 | * @return CliProgressBar 149 | */ 150 | public function setDefaultChar(string $char):self 151 | { 152 | $this->defaultChar = $char; 153 | return $this; 154 | } 155 | 156 | /** 157 | * @return string 158 | */ 159 | public function getDefaultChar():?string 160 | { 161 | return $this->defaultChar; 162 | } 163 | 164 | /** 165 | * @return string 166 | */ 167 | public function draw():string 168 | { 169 | $step = $this->getStep(); 170 | $currentStep = $this->getCurrentStep(); 171 | 172 | $currentStepLength = ($currentStep / $step) * $this->barLength; 173 | $defaultFillCharLength = $this->barLength - $currentStepLength; 174 | 175 | $proStepNumber = ($currentStep / $this->step) * 100; 176 | $proStepNumber = number_format($proStepNumber, 1, '.', ' '); 177 | 178 | $startColor = $this->colors[0]; 179 | $endColor = $this->colors[1]; 180 | 181 | $defaultFillChar = str_repeat($this->defaultChar, $defaultFillCharLength); 182 | 183 | $defaultFillChar = "\033[0;91m{$defaultFillChar}\033[0m"; 184 | $currentStepFormat = $this->fileSizeToMb($currentStep); 185 | $stepFormat = $this->fileSizeToMb($step); 186 | $networkFormat = $this->fileSizeToMb($this->getNetwork()); 187 | 188 | $stepFillChar = str_repeat($this->currentChar, $currentStepLength); 189 | $bar = sprintf('%s%s %.1f%%(%s/%s)%s/s ', $stepFillChar, $defaultFillChar, $proStepNumber, $currentStepFormat, $stepFormat, $networkFormat); 190 | 191 | return sprintf("\r%s%s%s", $startColor, $bar, $endColor); 192 | } 193 | 194 | public function output() 195 | { 196 | echo $this->draw(); 197 | } 198 | 199 | /** 200 | * @param string $title 201 | */ 202 | public function setHeaderTitle(string $title='test') 203 | { 204 | $title = sprintf("\nTitle: \033[0;92m%s\033[0m\n\n", $title); 205 | echo $title; 206 | } 207 | 208 | /** 209 | * @param string $type 210 | */ 211 | public function setHeaderType(string $type='MP4') 212 | { 213 | $type = sprintf("\nType: \033[0;92m%s\033[0m\n\n", $type); 214 | echo $type; 215 | } 216 | 217 | /** 218 | * @param int $size 219 | * @param int $powValue 220 | * @return string 221 | */ 222 | public function fileSizeToMb(int $size, int $powValue=2):string 223 | { 224 | $sizeToMb = $size / pow(1024, $powValue); 225 | $sizeToMb = $sizeToMb > 0 ? $sizeToMb : '0.01'; 226 | return round($sizeToMb, 2) . 'Mb'; 227 | } 228 | 229 | public function end() 230 | { 231 | echo PHP_EOL; 232 | } 233 | 234 | /** 235 | * @return string 236 | */ 237 | public function __toString() 238 | { 239 | return $this->draw(); 240 | } 241 | 242 | } -------------------------------------------------------------------------------- /core/Command/Console.php: -------------------------------------------------------------------------------- 1 | 'yes', 'n' => 'no' 34 | * ] 35 | * return: y(yes)/n(no) 36 | * @param string $question 37 | * @param array $options 38 | * @param int $colors 39 | * @return null|string 40 | */ 41 | public static function selected(string $question, array $options,int $colors=32):?string 42 | { 43 | $temp = []; 44 | foreach($options as $key => $value){ 45 | $temp[] = "\033[{$colors}m$key\033[0m" . '(' . $value . ')'; 46 | } 47 | 48 | gotoSelected: 49 | $outQuestion = $question . implode('/ ', $temp) . ':'; 50 | self::stdout($outQuestion); 51 | 52 | $stdin = self::stdin(); 53 | if(!array_key_exists($stdin, $options)){ 54 | goto gotoSelected; 55 | } 56 | 57 | return $stdin; 58 | } 59 | 60 | } -------------------------------------------------------------------------------- /core/Common/ArrayHelper.php: -------------------------------------------------------------------------------- 1 | 'xxx'], ['test' => 'bbb']] 16 | * multisort($array, 'test') 17 | * @param array $array 18 | * @param $sortKey 19 | * @param int $sort 20 | * @return array|bool 21 | */ 22 | public static function multisort(array $array, $sortKey,int $sort=SORT_ASC):?array 23 | { 24 | $tempArray = []; 25 | foreach($array as $value){ 26 | if(is_array($value)){ 27 | $tempArray[] = $value[$sortKey]; 28 | } 29 | } 30 | if(empty($tempArray)){ 31 | return null; 32 | } 33 | 34 | array_multisort($tempArray, $sort, $array); 35 | return $array; 36 | } 37 | 38 | public static function group(array $array, $groupKey):array 39 | { 40 | $result = []; 41 | foreach($array as $value){ 42 | $result[$value[$groupKey]][] = $value; 43 | } 44 | 45 | return $result; 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /core/Common/Downloader.php: -------------------------------------------------------------------------------- 1 | 0, 'list' => []] 47 | * @param array $options 48 | * @param array $fileOptions 49 | * @return mixed 50 | */ 51 | public function downloadFile(array $fileSizeArray=[], array $options=[], $fileOptions=[]) 52 | { 53 | $this->outputVideosTitle(); 54 | $this->outputVideoQuality(); 55 | 56 | $check = $this->checkFileExists(); 57 | if($check){ 58 | echo "\033[0;32mThis folder already contains a file named\033[0m".PHP_EOL; 59 | exit(0); 60 | } 61 | 62 | $this->cliProgressBar = new CliProgressBar(); 63 | if(isset($fileOptions['fileSize'])){ 64 | $this->fileSize = $fileOptions['fileSize']; 65 | $this->cliProgressBar->setStep($fileOptions['fileSize']); 66 | } 67 | 68 | $this->checkDirectory(); 69 | 70 | $fileSize = 0; 71 | if(!empty($this->downloadUrls)){ 72 | $fSize = []; 73 | 74 | //file size 75 | if(!isset($fileSizeArray['totalSize'])){ 76 | $this->cliProgressBar->setStep(count($this->downloadUrls)); 77 | foreach($this->downloadUrls as $uKey => $urlRow){ 78 | $contentLength = get_headers($urlRow, 1)['Content-Length']; 79 | if(is_array($contentLength) && count($contentLength) > 1){ 80 | $contentLength = $contentLength[1]; 81 | } 82 | $fileSize += $contentLength; 83 | $fSize[$uKey] = $contentLength; 84 | $this->fileSize = $fileSize; 85 | $this->cliProgressBar->progress($uKey+1); 86 | } 87 | } else { 88 | $fileSize = $fileSizeArray['totalSize']; 89 | $fSize = $fileSizeArray['list']; 90 | $this->fileSize = $fileSize; 91 | } 92 | 93 | if(empty($fileSize)){ 94 | $fileSize = self::DEFAULT_FILESIZE; 95 | } 96 | 97 | //download file 98 | $this->cliProgressBar->setStep($fileSize); 99 | $urlCount = count($this->downloadUrls); 100 | 101 | foreach($this->downloadUrls as $uKey => $urlRow){ 102 | $this->fileSize = isset($fSize[$uKey]) ? $fSize[$uKey] : self::DEFAULT_FILESIZE; 103 | 104 | switch ($urlCount){ 105 | case 1: 106 | $fileName = $this->videosTitle; 107 | break; 108 | default: 109 | $fileName = $this->videosTitle . '-' . $uKey; 110 | break; 111 | } 112 | 113 | $file = $this->rootPath . $fileName . $this->fileExt; 114 | $defaultOptions = self::defaultOptions($file); 115 | if($options){ 116 | $defaultOptions += $options; 117 | } 118 | 119 | $ch = curl_init($urlRow); 120 | curl_setopt_array($ch, $defaultOptions); 121 | curl_exec($ch); 122 | curl_close($ch); 123 | 124 | $this->writeFileLog($fileName.$this->fileExt); 125 | array_push($this->tempSaveFiles, $file); 126 | } 127 | 128 | $this->cliProgressBar->end(); 129 | } 130 | 131 | return [ 132 | 'fileSize' => $fileSize, 133 | 'info' => [], 134 | ]; 135 | } 136 | 137 | /** 138 | * 检查目录是否存在,如不存在则创建 139 | */ 140 | public function checkDirectory():void 141 | { 142 | if(!is_dir($this->rootPath)){ 143 | mkdir($this->rootPath, 0777, true); 144 | } 145 | } 146 | 147 | /** 148 | * @return bool|null 149 | */ 150 | public function checkFileExists():?bool 151 | { 152 | if($this->videosTitle){ 153 | $filePath = $this->rootPath . $this->videosTitle . $this->fileExt; 154 | if(file_exists($filePath)){ 155 | 156 | $stdin = Console::selected("\033[31m文件已存在,覆盖文件?\033[0m", ['y' => 'yes', 'n' => 'no']); 157 | if($stdin == 'y'){ 158 | return false; 159 | } 160 | 161 | if(is_file($this->ffmpFileListTxt)){ 162 | unlink($this->ffmpFileListTxt); 163 | } 164 | 165 | $fileSize = filesize($filePath); 166 | if($fileSize > 0){ 167 | return true; 168 | } 169 | } 170 | } 171 | 172 | return null; 173 | } 174 | 175 | /** 176 | * @param string $videosTitle 177 | * @return Downloader 178 | */ 179 | public function setVideosTitle(string $videosTitle):self 180 | { 181 | $this->videosTitle = str_replace([' ', '\\', '/', '\''], '',$videosTitle); 182 | 183 | return $this; 184 | } 185 | 186 | /** 187 | * @param string $head 188 | * @param string $titleName 189 | */ 190 | public function outputVideosTitle(string $head='Videos Title',string $titleName='') 191 | { 192 | if(empty($titleName)){ 193 | $titleName = $this->videosTitle; 194 | } 195 | 196 | echo PHP_EOL . "{$head}: \e[0;32m{$titleName}\e[0m". PHP_EOL; 197 | } 198 | 199 | public function outputVideoQuality() 200 | { 201 | echo PHP_EOL . "Video Quality: \033[0;32m{$this->videoQuality}\033[0m ". PHP_EOL . PHP_EOL; 202 | } 203 | 204 | /** 205 | * @return string 206 | */ 207 | public static function randIp():string 208 | { 209 | return rand(50,250).".".rand(50,250).".".rand(50,250).".".rand(50,250); 210 | } 211 | 212 | /** 213 | * @param $fileName 214 | * @param bool $noProgress 215 | * @return array 216 | */ 217 | final static function defaultOptions($fileName, $noProgress=false):array 218 | { 219 | $fp = fopen($fileName, 'w+'); 220 | $ip = self::randIp(); 221 | 222 | $curlOption = [ 223 | CURLOPT_FOLLOWLOCATION => true, 224 | CURLOPT_HEADER => false, 225 | CURLOPT_RETURNTRANSFER => true, 226 | CURLOPT_AUTOREFERER => true, 227 | CURLOPT_HTTPHEADER => [ 228 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 229 | // "Accept-Encoding: gzip, deflate, br", 230 | "Accept-Language: zh-CN,en-US;q=0.7,en;q=0.3", 231 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", 232 | "HTTP_X_FORWARDED_FOR: {$ip}" 233 | ], 234 | CURLOPT_FILE => $fp, 235 | ]; 236 | 237 | if(!$noProgress){ 238 | $curlOption += [ 239 | CURLOPT_NOPROGRESS => false, 240 | CURLOPT_PROGRESSFUNCTION => [self::class, 'progress'], 241 | ]; 242 | } 243 | 244 | return $curlOption; 245 | } 246 | 247 | /** 248 | * @param $ch 249 | * @param $expectedDownloadByte 250 | * @param $currentDownloadByte 251 | * @param $expectedUploadFileSize 252 | * @param $currentUploadFileSize 253 | * @return bool|int 254 | */ 255 | final function progress($ch, $expectedDownloadByte, $currentDownloadByte, $expectedUploadFileSize, $currentUploadFileSize) 256 | { 257 | $cuInfo = curl_getinfo($ch, CURLINFO_SPEED_DOWNLOAD); 258 | 259 | $cuDo = $this->currentSize + $currentDownloadByte; 260 | $this->cliProgressBar->progress($cuDo); 261 | $this->cliProgressBar->setNetwork($cuInfo); 262 | 263 | if(empty($expectedDownloadByte)){ 264 | $this->fileSize = self::DEFAULT_FILESIZE; 265 | } 266 | 267 | if($this->fileSize < $expectedDownloadByte && $expectedDownloadByte > 0){ 268 | if($this->cliProgressBar->getStep() == self::DEFAULT_FILESIZE){ 269 | $this->cliProgressBar->setStep($expectedDownloadByte); 270 | } 271 | $this->fileSize = $expectedDownloadByte; 272 | } 273 | 274 | if($this->fileSize === $currentDownloadByte){ 275 | $this->currentSize += $currentDownloadByte; 276 | return 1; 277 | } 278 | 279 | return false; 280 | } 281 | 282 | /** 283 | * @param string $fileName 284 | * @param string $path 285 | * @return $this 286 | */ 287 | public function writeFileLog(string $fileName, $path='./'):self 288 | { 289 | $fileContents = "file ./Videos/{$fileName}".PHP_EOL; 290 | $file = $path . md5($this->requestUrl) . '-filelist.txt'; 291 | 292 | $this->ffmpFileListTxt = $file; 293 | file_put_contents($file, $fileContents,FILE_APPEND | LOCK_EX); 294 | 295 | return $this; 296 | } 297 | 298 | /** 299 | * 删除临时下载文件 300 | */ 301 | public function deleteTempSaveFiles():void 302 | { 303 | if(!empty($this->tempSaveFiles)){ 304 | foreach($this->tempSaveFiles as $value){ 305 | $tr = PHP_EOL; 306 | printf("{$tr}\033[0;34mUnlink file:{$value}\033[0m{$tr}"); 307 | unlink($value); 308 | } 309 | } 310 | } 311 | 312 | /** 313 | * @param string $fileListText 314 | */ 315 | public function success(string $fileListText=null) 316 | { 317 | //清空文件内容 318 | if(is_file($fileListText)){ 319 | unlink($fileListText); 320 | } 321 | 322 | $tr = PHP_EOL; 323 | printf("{$tr}\033[0;32mDownload Done\033[0m{$tr}"); 324 | } 325 | 326 | /** 327 | * @param string $contents 328 | */ 329 | public function error(string $contents='Errors:The video address resolution failed'):void 330 | { 331 | $errors = PHP_EOL ."\033[31m{$contents}\033[0m".PHP_EOL; 332 | echo $errors; 333 | exit(0); 334 | } 335 | 336 | public function __destruct() 337 | { 338 | unset($this->cliProgressBar); 339 | } 340 | 341 | } -------------------------------------------------------------------------------- /core/Common/FFmpeg.php: -------------------------------------------------------------------------------- 1 | conf = new \ArrayObject($config); 22 | } 23 | 24 | /** 25 | * @return Config 26 | */ 27 | public static function instance():self 28 | { 29 | if(null === self::$instance){ 30 | self::$instance = new static(); 31 | } 32 | 33 | return self::$instance; 34 | } 35 | 36 | /** 37 | * @return array|null 38 | */ 39 | public static function includeConfig():?array 40 | { 41 | $baseDir = realpath(getcwd()); 42 | $configFile = $baseDir . '/config.php'; 43 | if(file_exists($configFile)){ 44 | $config = include_once $configFile; 45 | return $config; 46 | } 47 | 48 | return null; 49 | } 50 | 51 | public function get($key) 52 | { 53 | if(!$this->conf->offsetExists($key)){ 54 | return false; 55 | } 56 | 57 | return $this->conf->offsetGet($key); 58 | } 59 | } -------------------------------------------------------------------------------- /core/Http/.gitignore: -------------------------------------------------------------------------------- 1 | MessageInterface 2 | Client.php -------------------------------------------------------------------------------- /core/Http/Client.php: -------------------------------------------------------------------------------- 1 | get($url); 26 | 27 | if($cache){ 28 | $contents = $cache; 29 | $curlInfo = [ 30 | 'http_code' => 200, 31 | ]; 32 | } else { 33 | $ch = curl_init(); 34 | $defaultOptions = self::defaultOptions($url, $httpReferer, $ip); 35 | if($options){ 36 | $defaultOptions = $options + $defaultOptions; 37 | } 38 | 39 | curl_setopt_array($ch, $defaultOptions); 40 | $chContents = curl_exec($ch); 41 | $curlInfo = curl_getinfo($ch); 42 | 43 | curl_close($ch); 44 | 45 | if($curlInfo['http_code'] != 200){ 46 | $contents = null; 47 | $curlInfo['message'] = $chContents; 48 | } else { 49 | $contents = $chContents; 50 | } 51 | 52 | if($resCache){ 53 | (new FileCache())->set($url, $contents); 54 | } 55 | } 56 | 57 | return [ 58 | $contents, 59 | $curlInfo, 60 | ]; 61 | } 62 | 63 | public static function post() 64 | { 65 | 66 | } 67 | 68 | public static function randIp() 69 | { 70 | return mt_rand(20,250).".".mt_rand(20,250).".".mt_rand(20,250).".".mt_rand(20,250); 71 | } 72 | 73 | /** 74 | * @param $url 75 | * @param $httpReferer 76 | * @param bool|string $ip 77 | * @return array 78 | */ 79 | public static function defaultOptions($url, $httpReferer, $ip=false) 80 | { 81 | if(!$ip){ 82 | $ip = self::randIp(); 83 | } 84 | 85 | return [ 86 | CURLOPT_URL => $url, 87 | CURLOPT_FOLLOWLOCATION => true, 88 | CURLOPT_RETURNTRANSFER => true, 89 | CURLOPT_REFERER => $httpReferer, 90 | CURLOPT_HTTPHEADER => [ 91 | "Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 92 | // "Accept-Encoding:gzip, deflate, br", 93 | "Accept-Language:zh-CN,en-US;q=0.7,en;q=0.3", 94 | "User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", 95 | "HTTP_X_FORWARDED_FOR:{$ip}", 96 | "CLIENT-IP:{$ip}" 97 | ] 98 | ]; 99 | } 100 | 101 | } -------------------------------------------------------------------------------- /core/Http/Domain.php: -------------------------------------------------------------------------------- 1 | 1){ 19 | if(!empty($matches[$index])){ 20 | $domain = $matches[$index][0]; 21 | $domain = ucwords($domain); 22 | return $domain; 23 | } 24 | } 25 | 26 | return null; 27 | } 28 | } -------------------------------------------------------------------------------- /core/Http/MessageInterface/Message.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 21 | } 22 | 23 | /** 24 | * PC:https://v.douyu.com/api/swf/getStreamUrl POST params:[did,tt(时间),sign(未知加密),vid] 25 | * 26 | * @return string 27 | * @throws \ErrorException 28 | */ 29 | public function getDid():string 30 | { 31 | $defaultDid = '10000000000000000000000000001501'; 32 | //client_id=1|6 33 | $didApiUrl = 'https://passport.douyu.com/lapi/did/api/get?client_id=1'; 34 | 35 | $didCache = (new FileCache())->get($didApiUrl); 36 | if($didCache){ 37 | return $didCache; 38 | } 39 | 40 | $getInfo = Curl::get($didApiUrl, 'https://v.douyu.com', [], false); 41 | if($getInfo){ 42 | $json = json_decode($getInfo[0], true); 43 | if(!isset($json['data']['did'])){ 44 | throw new \ErrorException('Could not get did'); 45 | } 46 | 47 | $defaultDid = $json['data']['did']; 48 | (new FileCache())->set($didApiUrl, $defaultDid, 7200); 49 | } 50 | 51 | return $defaultDid; 52 | } 53 | 54 | /** 55 | * @param string $vid 56 | * @return string 57 | * @throws \ErrorException 58 | */ 59 | public function getMobileInfo(string $vid):string 60 | { 61 | $getInfoUrl = 'https://vmobile.douyu.com/video/getInfo?vid=' . $vid; 62 | $videoUrl = (new FileCache())->get($getInfoUrl); 63 | 64 | if(!$videoUrl){ 65 | $getInfo = Curl::get($getInfoUrl, 'https://v.douyu.com/show/'.$vid); 66 | if(!$getInfo){ 67 | $this->error('Errors:not found url info'); 68 | } 69 | 70 | if($getInfo[0]){ 71 | $json = json_decode($getInfo[0], true); 72 | $videoUrl = $json['data']['video_url']; 73 | if(empty($videoUrl)){ 74 | $this->error('Errors:not found video url'); 75 | } 76 | 77 | (new FileCache())->set($getInfoUrl, $videoUrl, 7200); 78 | } 79 | } 80 | 81 | return $videoUrl; 82 | } 83 | 84 | /** 85 | * @param string $vid 86 | * @return array|null|string 87 | * @throws \ErrorException 88 | */ 89 | public function getVideoTitle(string $vid):?string 90 | { 91 | $pcInfoUrl = 'https://v.douyu.com/video/video/getVideoUrl?vid=' . $vid; 92 | 93 | $videoTitle = (new FileCache())->get($pcInfoUrl); 94 | if(!$videoTitle){ 95 | $jsonInfo = Curl::get($pcInfoUrl, 'https://vswf.douyucdn.cn/player/vplayer.swf?ver=v6.671'); 96 | 97 | if(!$jsonInfo){ 98 | $this->error('Errors:get pc video info'); 99 | } 100 | 101 | if(!$jsonInfo[0]){ 102 | $this->error('Errors:json empty'); 103 | } 104 | 105 | $json = json_decode($jsonInfo[0], true); 106 | if($json['error'] != 0){ 107 | $this->error('Errors:'.$json['error']); 108 | } 109 | 110 | $videoTitle = $json['data']['title']; 111 | (new FileCache())->set($pcInfoUrl, $videoTitle, 7200); 112 | } 113 | 114 | 115 | return $videoTitle; 116 | } 117 | 118 | /** 119 | * @param string $url 120 | * @return string 121 | * @throws \ErrorException 122 | */ 123 | public function getM3u8(string $url):string 124 | { 125 | $m3u8Info = (new FileCache())->get($this->requestUrl.'m3u8'); 126 | 127 | if(!$m3u8Info){ 128 | $playListInfo = Curl::get($url, $this->requestUrl); 129 | if(!$playListInfo){ 130 | $this->error('Errors:get m3u8 list'); 131 | } 132 | if(empty($playListInfo[0])){ 133 | $this->error('Errors:m3u8 is empty'); 134 | } 135 | $m3u8Info = $playListInfo[0]; 136 | 137 | (new FileCache())->set($this->requestUrl.'m3u8', $m3u8Info); 138 | } 139 | 140 | return $m3u8Info; 141 | } 142 | 143 | /** 144 | * @throws \ErrorException 145 | */ 146 | public function download():void 147 | { 148 | $urlInfo = parse_url($this->requestUrl, PHP_URL_PATH); 149 | $urlArray = explode('show/', $urlInfo); 150 | if(!isset($urlArray[1])){ 151 | $this->error('not found vid'); 152 | } 153 | $vid = $urlArray[1]; 154 | 155 | $videosTitle = $this->getVideoTitle($vid); 156 | 157 | $videoUrl = $this->getMobileInfo($vid); 158 | 159 | $playerHost = pathinfo($videoUrl, PHP_URL_PATH); 160 | $contents = $this->getM3u8($videoUrl); 161 | 162 | $this->setVideosTitle($videosTitle); 163 | 164 | $mUrls = M3u8::getUrls($contents); 165 | 166 | if(!$mUrls){ 167 | $this->error('Errors:m3u8 urls empty'); 168 | } 169 | 170 | $urls = []; 171 | $playerHost = str_replace('http', 'https', $playerHost); 172 | foreach($mUrls as $val){ 173 | $pa = trim($val); 174 | $url = $playerHost . '/' .$pa; 175 | array_push($urls, $url); 176 | } 177 | 178 | $this->downloadUrls = $urls; 179 | 180 | $downloadFileInfo = $this->downloadFile(); 181 | if($downloadFileInfo < 1024){ 182 | printf("\n\e[41m%s\033[0m\n", 'Errors:download file 0'); 183 | } else { 184 | FFmpeg::concatToMp4($this->videosTitle, $this->ffmpFileListTxt, './Videos/'); 185 | } 186 | 187 | $this->deleteTempSaveFiles(); 188 | $this->success($this->ffmpFileListTxt); 189 | } 190 | 191 | } -------------------------------------------------------------------------------- /core/Platform/Iqiyi/Iqiyi.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 24 | } 25 | 26 | /** 27 | * @param int $key 28 | * @return string 29 | */ 30 | public function getQu(int $key):string 31 | { 32 | $vd = [ 33 | 4 => '720P', 34 | 96 => '极速', 35 | 1 => '流畅', 36 | 2 => '高清', 37 | 5 => '1080P', 38 | 10 => '4K', 39 | 6 => '2K', 40 | 3 => '超清', 41 | 19 => '4K', 42 | 17 => '720P', 43 | 14 => '720P', 44 | 21 => '504P' 45 | ]; 46 | 47 | return isset($vd[$key]) ? $vd[$key] : 'Unknown'; 48 | } 49 | 50 | public function getClientTs():float 51 | { 52 | $microTime = microtime(true) * 1000; 53 | $time = round($microTime); 54 | return $time; 55 | } 56 | 57 | /** 58 | * @param string $tvid 59 | * @param string $vid 60 | * @return array|mixed|null|string 61 | * @throws \ErrorException 62 | */ 63 | public function getTmts(string $tvid, string $vid):array 64 | { 65 | $tmtsCache = (new FileCache())->get($tvid); 66 | if(!$tmtsCache){ 67 | $tmtsUrl = 'http://cache.m.iqiyi.com/jp/tmts/'.$tvid.'/'.$vid.'/?'; 68 | $t = $this->getClientTs(); 69 | $src = '76f90cbd92f94a2e925d83e8ccd22cb7'; 70 | $key = 'd5fb4bd9d50c4be6948c97edd7254b0e'; 71 | $sc = md5($t.$key.$vid); 72 | $tmtsUrl .= 't='.$t.'&sc='. $sc .'&src='.$src; 73 | 74 | $tmtsInfo = Curl::get($tmtsUrl, 'https://wwww.iqiyi.com'); 75 | if(!$tmtsInfo){ 76 | $this->error('Errors:get tmts'); 77 | } 78 | if(count($tmtsInfo[0]) < 1){ 79 | $this->error('Errors:tmts info'); 80 | } 81 | 82 | $videoInfo = ltrim($tmtsInfo[0], 'var tvInfoJs='); 83 | $tmtsCache = json_decode($videoInfo, true); 84 | (new FileCache())->set($tvid, $tmtsCache, 60); 85 | } 86 | 87 | return $tmtsCache; 88 | } 89 | 90 | /** 91 | * @return array 92 | * @throws \ErrorException 93 | */ 94 | public function getVideosInfo():array 95 | { 96 | $videoInfo = (new FileCache())->get($this->requestUrl.'video-info'); 97 | if(!$videoInfo){ 98 | $html = Curl::get($this->requestUrl, $this->requestUrl); 99 | if(!$html){ 100 | $this->error('Errors:request error'); 101 | } 102 | if(empty($html[0])){ 103 | $this->error('Errors:html empty'); 104 | } 105 | 106 | $libErrors = libxml_use_internal_errors(true); 107 | 108 | $dom = new \DOMDocument(); 109 | $dom->loadHTML($html[0]); 110 | $element = $dom->documentElement; 111 | $titleItem = $element->getElementsByTagName('title'); 112 | $div = $dom->getElementById('flashbox'); 113 | if($titleItem->length < 1){ 114 | $this->error('Errors:not found title'); 115 | } 116 | $title = $titleItem->item(0)->nodeValue; 117 | 118 | if(empty($title)){ 119 | $this->error('Errors:title is empty'); 120 | } 121 | 122 | libxml_use_internal_errors($libErrors); 123 | 124 | $tvid = $div->getAttribute('data-player-tvid'); 125 | $vid = $div->getAttribute('data-player-videoid'); 126 | 127 | if(empty($tvid)){ 128 | $iqiyiMain = $dom->getElementById('iqiyi-main'); 129 | $pageInfo = $this->getPageInfo($iqiyiMain); 130 | 131 | if(empty($pageInfo)){ 132 | $this->error('Errors:tvid empty'); 133 | } 134 | 135 | $tvid = $pageInfo['tvId']; 136 | $vid = $pageInfo['vid']; 137 | } 138 | if(empty($vid)){ 139 | $this->error('Errors:vid empty'); 140 | } 141 | 142 | $videoInfo = [ 143 | 'title' => $title, 144 | 'tvid' => $tvid, 145 | 'vid' => $vid, 146 | ]; 147 | (new FileCache())->set($this->requestUrl.'video-info', $videoInfo, 120); 148 | } 149 | 150 | return $videoInfo; 151 | } 152 | 153 | /** 154 | * @param \DOMElement $dom 155 | * @return array 156 | */ 157 | public function getPageInfo(\DOMElement $dom):array 158 | { 159 | $iqiyiMainDiv = $dom->getElementsByTagName('div'); 160 | $pageInfo = $iqiyiMainDiv->item(0)->getAttribute(':page-info'); 161 | 162 | if(!$pageInfo){ 163 | $this->error('Error:div page info is empty'); 164 | } 165 | $pageInfo = json_decode($pageInfo, true); 166 | 167 | return $pageInfo; 168 | } 169 | 170 | /** 171 | * @throws \ErrorException 172 | */ 173 | public function download(): void 174 | { 175 | $videoInfo = $this->getVideosInfo(); 176 | $tmtsInfo = $this->getTmts($videoInfo['tvid'], $videoInfo['vid']); 177 | if($tmtsInfo['code'] != 'A00000'){ 178 | $this->error('Errors:get mus error -》'.$tmtsInfo['code']); 179 | } 180 | 181 | $vidl = ArrayHelper::multisort($tmtsInfo['data']['vidl'], 'screenSize', SORT_ASC); 182 | 183 | $m3utxCache = (new FileCache())->get($videoInfo['tvid'].'m3utx'); 184 | if(!$m3utxCache){ 185 | $m3utx = Curl::get($vidl[0]['m3u'], $this->requestUrl); 186 | if(!$m3utx){ 187 | $this->error('Errors:get m3utx error'); 188 | } 189 | 190 | $m3utxCache = $m3utx[0]; 191 | (new FileCache())->set($videoInfo['tvid'].'m3utx', $m3utxCache); 192 | } 193 | 194 | $m3u8Urls = M3u8::getUrls($m3utxCache); 195 | if(!$m3u8Urls){ 196 | $this->error('Errors:m3u8Urls error'); 197 | } 198 | 199 | $tsUrl = []; 200 | foreach($m3u8Urls as $row){ 201 | $tempUrl = ltrim($row); 202 | array_push($tsUrl, $tempUrl); 203 | } 204 | 205 | $this->downloadUrls = $tsUrl; 206 | $this->setVideosTitle($videoInfo['title']); 207 | 208 | $this->videoQuality = $this->getQu($vidl[0]['vd']); 209 | 210 | $this->fileExt = '.ts'; 211 | $downloadFileInfo = $this->downloadFile(); 212 | 213 | if($downloadFileInfo < 1024){ 214 | printf("\n\e[41m%s\033[0m\n", 'Errors:download file 0'); 215 | } else { 216 | FFmpeg::concatToMp4($this->videosTitle, $this->ffmpFileListTxt, './Videos/'); 217 | } 218 | 219 | $this->deleteTempSaveFiles(); 220 | $this->success($this->ffmpFileListTxt); 221 | } 222 | 223 | } -------------------------------------------------------------------------------- /core/Platform/Krcom/Krcom.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 22 | } 23 | 24 | /** 25 | * @throws \ErrorException 26 | */ 27 | public function download():void 28 | { 29 | $fileKey = md5($this->requestUrl); 30 | 31 | $htmlFile = false; 32 | if(file_exists('./Runtime/Cache/Html/'.$fileKey.'.html')){ 33 | $htmlFile = './Runtime/Cache/Html/'.$fileKey.'.html'; 34 | } 35 | 36 | if(!$htmlFile){ 37 | $c = Curl::get($this->requestUrl, $this->requestUrl, [], false); 38 | if(!$c){ 39 | throw new \ErrorException('无法获取 HTML 内容'); 40 | } 41 | $htmlFile = (new FileCache())->setFileDir('Html/')->setFile($this->requestUrl, $c[0]); 42 | } 43 | 44 | $libxmlErros = libxml_use_internal_errors(true); 45 | $dom = new \DOMDocument(); 46 | 47 | $dom->loadHTMLFile($htmlFile); 48 | 49 | $videosTitle = $dom->getElementsByTagName('title')->item(0)->nodeValue; 50 | $el = $dom->documentElement->getElementsByTagName('script'); 51 | 52 | libxml_use_internal_errors($libxmlErros); 53 | 54 | if($el->length < 17){ 55 | throw new \ErrorException('无法解析内容'); 56 | } 57 | 58 | 59 | $javaScript = $el->item(12)->textContent; 60 | 61 | $patter = "/video-sources=(.*?)action-data=/is"; 62 | preg_match_all($patter, $javaScript, $matches); 63 | 64 | $videosUrlList = str_replace(['\n', '\"'], '', $matches[1][0]); 65 | 66 | $videosUrlList = explode('video&', $videosUrlList); 67 | 68 | $tempArray = []; 69 | foreach($videosUrlList as $row){ 70 | $temp = explode('=', $row); 71 | if(is_numeric($temp[0])){ 72 | $tempUrl = rtrim(str_replace('video', '', $temp[1])); 73 | 74 | $tempArray[] = [ 75 | 'plate' => $temp[0], 76 | 'url' => 'http:' . urldecode($tempUrl) . 'video', 77 | ]; 78 | } 79 | } 80 | 81 | if(!$tempArray){ 82 | throw new \ErrorException('获取视频地址失败'); 83 | } 84 | 85 | $tempArray = ArrayHelper::multisort($tempArray, 'plate', SORT_DESC); 86 | $this->setVideosTitle($videosTitle); 87 | $this->videoQuality = $tempArray[0]['plate']; 88 | $this->downloadUrls[0] = $tempArray[0]['url']; 89 | 90 | $this->downloadFile(); 91 | $this->success($this->ffmpFileListTxt); 92 | 93 | unlink($htmlFile); 94 | 95 | } 96 | } -------------------------------------------------------------------------------- /core/Platform/Miaopai/Miaopai.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 20 | } 21 | 22 | /** 23 | * @throws \ErrorException 24 | */ 25 | public function download():void 26 | { 27 | $parseUrlPath = parse_url($this->requestUrl, PHP_URL_PATH); 28 | $pathInfoExtension = pathinfo($parseUrlPath, PATHINFO_EXTENSION); 29 | 30 | if($pathInfoExtension == 'mp4'){ 31 | $this->videosTitle = 'MP-' . md5($this->requestUrl); 32 | 33 | $this->downloadUrls[0] = $this->requestUrl; 34 | 35 | $this->outputVideosTitle(); 36 | $this->downloadFile(); 37 | $this->success($this->ffmpFileListTxt); 38 | exit(0); 39 | } 40 | 41 | $res = Curl::get($this->requestUrl, $this->requestUrl); 42 | 43 | if($res){ 44 | preg_match_all('/"videoSrc":"(.*?)",/i', $res[0], $matches); 45 | if(!isset($matches[1]) && !is_array($matches[1])){ 46 | (new FileCache())->delete($this->requestUrl); 47 | 48 | throw new \ErrorException('无法解析该地址'); 49 | } 50 | 51 | $errors = libxml_use_internal_errors(true); 52 | 53 | $dom = new \DOMDocument('1.0', 'UTF-8'); 54 | $dom->recover = true; 55 | $dom->strictErrorChecking = false; 56 | $dom->loadHTML($res[0]); 57 | 58 | $element = $dom->documentElement; 59 | $titleItem = $element->getElementsByTagName('title'); 60 | 61 | if($titleItem->length < 1 || !isset($matches[1][0])){ 62 | (new FileCache())->delete($this->requestUrl); 63 | throw new \ErrorException('无法解析该地址'); 64 | } 65 | 66 | $videosTitle = $titleItem->item(0)->textContent; 67 | 68 | $this->setVideosTitle($videosTitle); 69 | 70 | $videosUrl = $matches[1][0]; 71 | 72 | //https://kscdn.miaopai.com/stream/xBghjLxNWzMYqIcEH0D5FDmMttMmBejfSo-nRw__.mp4?ssig=e52e308ef953d7b90898f1aa044555af&time_stamp=1531901900853 73 | 74 | $gotoN = 1; 75 | gotoVideosDownload: 76 | $this->downloadUrls[0] = $videosUrl; 77 | $vi = $this->downloadFile(); //下载 78 | 79 | if($vi['fileSize'] == 1024 && $gotoN < 2){ 80 | $videosUrl = str_replace(['txycdn'], 'kscdn', $vi['info']['url']); 81 | $gotoN++; 82 | goto gotoVideosDownload; 83 | } 84 | 85 | $this->success($this->ffmpFileListTxt); 86 | 87 | libxml_use_internal_errors($errors); 88 | 89 | } else { 90 | throw new \ErrorException('not found page'); 91 | } 92 | 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /core/Platform/Porn/Porn.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 20 | } 21 | 22 | /** 23 | * @param array $curlProxy 24 | * @return array|bool|null 25 | * @throws \ErrorException 26 | */ 27 | public function getVideosUrl(array $curlProxy=[]):?array 28 | { 29 | $videosUrlCache = (new FileCache())->get($this->requestUrl); 30 | if($videosUrlCache){ 31 | return $videosUrlCache; 32 | } 33 | 34 | $html = Curl::get($this->requestUrl, $this->requestUrl, $curlProxy, false); 35 | 36 | if(!$html || empty($html[0])){ 37 | $this->error('Error:not found html'); 38 | } 39 | 40 | $libxmlErrors = libxml_use_internal_errors(true); 41 | $dom = new \DOMDocument(); 42 | $dom->loadHTML($html[0]); 43 | $vidSource = $dom->getElementById('vid'); 44 | 45 | if(empty($vidSource)){ 46 | $this->error('Error:vid is empty'); 47 | } 48 | 49 | $title = $dom->getElementsByTagName('title')->item(0)->nodeValue; 50 | $title = str_replace(PHP_EOL, '', $title); 51 | 52 | $this->setVideosTitle($title); 53 | 54 | libxml_use_internal_errors($libxmlErrors); 55 | 56 | $videosUrl = $vidSource->getElementsByTagName('source')->item(0)->getAttribute('src'); 57 | 58 | $videosInfo = [ 59 | 'url' => $videosUrl, 60 | 'title' => $this->videosTitle, 61 | ]; 62 | 63 | if(!$videosUrl){ 64 | $this->error('Error:videos url is empty'); 65 | } 66 | 67 | (new FileCache())->set($this->requestUrl, $videosInfo); 68 | 69 | return $videosInfo; 70 | 71 | } 72 | 73 | /** 74 | * @throws \ErrorException 75 | */ 76 | public function download():void 77 | { 78 | $httpProxy = Config::instance()->get('http_proxy'); 79 | $curlProxy = []; 80 | if($httpProxy){ 81 | $curlProxy = [ 82 | CURLOPT_PROXY => $httpProxy, 83 | ]; 84 | } 85 | 86 | $videosUrl = $this->getVideosUrl($curlProxy); 87 | 88 | $this->videosTitle = $videosUrl['title']; 89 | $this->downloadUrls[0] = $videosUrl['url']; 90 | 91 | $this->downloadFile(['totalSize' => self::DEFAULT_FILESIZE, 'list' => [self::DEFAULT_FILESIZE]], $curlProxy); 92 | $this->success($this->ffmpFileListTxt); 93 | } 94 | 95 | } -------------------------------------------------------------------------------- /core/Platform/Pornhub/Pornhub.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 23 | } 24 | 25 | /** 26 | * @return bool|null|string 27 | * @throws \ErrorException 28 | */ 29 | public function getVideosJson():?string 30 | { 31 | $videosJsonCache = (new FileCache())->get($this->requestUrl); 32 | if($videosJsonCache){ 33 | return $videosJsonCache; 34 | } 35 | //PHP cUrl 不做代理 ,直接走本地 36 | exec("curl {$this->requestUrl} > pornhub.html", $curlHtml); 37 | 38 | $htmlErrors = libxml_use_internal_errors(true); 39 | $dom = new \DOMDocument(); 40 | $dom->loadHTMLFile($this->pornhubHtmlFile); 41 | $player = $dom->getElementById('player'); 42 | $videosId = $player->getAttribute('data-video-id'); 43 | 44 | libxml_use_internal_errors($htmlErrors); 45 | 46 | $javaScript = $player->nodeValue; 47 | 48 | if(!$videosId){ 49 | throw new \ErrorException('无法解析该视频'); 50 | } 51 | 52 | $patter = "/flashvars_{$videosId} = (.*?)};/is"; 53 | preg_match_all($patter, $javaScript, $matches); 54 | 55 | 56 | if(!isset($matches[1][0])){ 57 | throw new \ErrorException('无法解析该视频真实地址'); 58 | } 59 | 60 | unset($dom); 61 | unlink($this->pornhubHtmlFile); 62 | (new FileCache())->set($this->requestUrl, $matches[1][0] . '}'); 63 | 64 | return $matches[1][0] . '}'; 65 | } 66 | 67 | /** 68 | * @param $videosJson 69 | * @return array 70 | */ 71 | public function getVideosList($videosJson):array 72 | { 73 | $videosLists = json_decode($videosJson, true); 74 | 75 | $this->setVideosTitle($videosLists['video_title']); 76 | 77 | $videosList = array_filter($videosLists['mediaDefinitions'], function($var){ 78 | if(!empty($var['videoUrl'])){ 79 | return $var; 80 | } 81 | }); 82 | 83 | return $videosList; 84 | } 85 | 86 | 87 | /** 88 | * @throws \ErrorException 89 | */ 90 | public function download():void 91 | { 92 | $httpProxy = Config::instance()->get('http_proxy'); 93 | $curlProxy = []; 94 | if($httpProxy){ 95 | $curlProxy = [ 96 | CURLOPT_PROXY => $httpProxy, 97 | ]; 98 | } 99 | 100 | $videosJson = $this->getVideosJson(); 101 | $videosList = $this->getVideosList($videosJson); 102 | 103 | if(!$videosList){ 104 | echo PHP_EOL . 'No video found'. PHP_EOL; 105 | exit(0); 106 | } 107 | 108 | $videosList = ArrayHelper::multisort($videosList, 'quality', SORT_DESC); 109 | 110 | $this->videoQuality = $videosList[0]['quality']; 111 | $this->downloadUrls[0] = $videosList[0]['videoUrl']; 112 | 113 | $fileSizeArray = [ 114 | 'totalSize' => self::DEFAULT_FILESIZE, 115 | 'list' => [self::DEFAULT_FILESIZE], 116 | ]; 117 | 118 | $this->downloadFile($fileSizeArray, $curlProxy); 119 | $this->success($this->ffmpFileListTxt); 120 | } 121 | } -------------------------------------------------------------------------------- /core/Platform/Qq/Qq.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 24 | } 25 | 26 | /** 27 | * @throws \ErrorException 28 | */ 29 | public static function getGuid() 30 | { 31 | $getJsonUrl = 'http://ncgi.video.qq.com/fcgi-bin/get_guid_http_to_jce?'; 32 | $guidCache = (new FileCache())->get($getJsonUrl); 33 | if($guidCache){ 34 | return $guidCache; 35 | } 36 | 37 | $jsonInfo = Curl::get($getJsonUrl, $getJsonUrl); 38 | if($jsonInfo){ 39 | $jsonInfo = json_decode($jsonInfo[0], true); 40 | if($jsonInfo['err_code'] == 0){ 41 | (new FileCache())->set($getJsonUrl, $jsonInfo['guid'], 300); 42 | return $jsonInfo['guid']; 43 | } 44 | } 45 | 46 | throw new \ErrorException('无法获取 Guid'); 47 | } 48 | 49 | /** 50 | * @return bool|string 51 | * @throws \ErrorException 52 | */ 53 | public function getVid():?string 54 | { 55 | $html = Curl::get($this->requestUrl, $this->requestUrl); 56 | preg_match_all("/url=(.*)?&ptag/i", $html[0], $matches); 57 | 58 | $baseName = pathinfo($matches[1][0], PATHINFO_BASENAME); 59 | parse_str($baseName, $parseArray); 60 | 61 | if(!isset($parseArray['vid'])){ 62 | $this->error('Error:无法匹配 vid'); 63 | } 64 | 65 | //https://v.qq.com/x/cover/m441e3rjq9kwpsc/h00251u5sdp.html? 66 | /*$baseName = pathinfo($this->requestUrl, PATHINFO_BASENAME); 67 | if(!$baseName){ 68 | $this->error('Error:无法匹配 vid'); 69 | } 70 | 71 | $vid = substr($baseName, 0, 11);*/ 72 | 73 | return $parseArray['vid']; 74 | } 75 | 76 | /** 77 | * @param string $vid 78 | * @return mixed 79 | * @throws \ErrorException 80 | */ 81 | public static function getVideosInfo(string $vid) 82 | { 83 | $guid = self::getGuid(); 84 | $getVideosInfoUrl = 'https://h5vv.video.qq.com/getinfo?isHLS=false&vid='. $vid .'&otype=ojson&guid='. $guid .'&platform=11&sdtfrom=v1010&host=v.qq.com&_qv_rmt=sNk0sWZTA17002uQa%3D&_qv_rmt2=0Qs65I9%2B149182HOQ%3D'; 85 | 86 | $videosInfo = Curl::get($getVideosInfoUrl, $getVideosInfoUrl); 87 | 88 | $videosJsonInfo = false; 89 | if($videosInfo){ 90 | if(!is_array($videosInfo[0])){ 91 | $json = json_decode($videosInfo[0], true); 92 | $videosInfo[0] = $json; 93 | (new FileCache())->set($getVideosInfoUrl, $json); 94 | } 95 | 96 | $videosJsonInfo = $videosInfo[0]; 97 | } 98 | 99 | 100 | return $videosJsonInfo; 101 | } 102 | 103 | /** 104 | * @param $format 105 | * @param string $fileName 106 | * @return mixed 107 | * @throws \ErrorException 108 | */ 109 | public function getKey($format,string $fileName):array 110 | { 111 | //https://h5vv.video.qq.com/getkey?otype=json&vid=t0195b4eoyw&format=10701&filename=t0195b4eoyw.p701.mp4&platform=1 112 | $getKeyUrl = 'https://h5vv.video.qq.com/getkey?otype=ojson&vid='. $this->getVid() .'&format='. $format .'&filename='. $fileName . '&platform=2'; 113 | 114 | $keyInfo = Curl::get($getKeyUrl, $this->requestUrl); 115 | if($keyInfo){ 116 | if(!is_array($keyInfo[0])){ 117 | $json = json_decode($keyInfo[0], true); 118 | $keyInfo[0] = $json; 119 | (new FileCache())->set($getKeyUrl, $json); 120 | } 121 | 122 | return $keyInfo[0]; 123 | } 124 | 125 | $this->error('Error:无法获取 key'); 126 | } 127 | 128 | /** 129 | * @param string $keyId 130 | * @return string 131 | */ 132 | public function getKeyId(string $keyId):string 133 | { 134 | $exKeyId = explode('.', $keyId); 135 | 136 | if($exKeyId[1] > 100000){ 137 | $newKeyId = $exKeyId[0] . '.m' . substr($exKeyId[1], 3) . self::FILE_EXTENSION; 138 | } elseif($exKeyId[1] > 10000){ 139 | $newKeyId = $exKeyId[0] . '.p' . substr($exKeyId[1], 2) . '.' . $exKeyId[2] . self::FILE_EXTENSION; 140 | } else { 141 | switch ($exKeyId[1]){ 142 | case 2: 143 | $newKeyId = $exKeyId[0] . self::FILE_EXTENSION; 144 | break; 145 | default: 146 | $newKeyId = $keyId . self::FILE_EXTENSION; 147 | break; 148 | } 149 | } 150 | 151 | return $newKeyId; 152 | } 153 | 154 | /** 155 | * @throws \ErrorException 156 | */ 157 | public function download():void 158 | { 159 | $vid = $this->getVid(); 160 | $videosListInfo = self::getVideosInfo($vid); 161 | 162 | if(!isset($videosListInfo['vl']['vi'][0]['ti'])){ 163 | $this->error("Error:videos title not found"); 164 | } 165 | 166 | $videosTitle = $videosListInfo['vl']['vi'][0]['ti']; 167 | 168 | if(empty($videosTitle)){ 169 | $videosTitle = md5($this->requestUrl); 170 | } 171 | 172 | if(!isset($videosListInfo['fl']['fi'])){ 173 | $this->error('Error:videos info not found fi'); 174 | } 175 | $videosArrayCount = count($videosListInfo['fl']['fi']); 176 | if($videosArrayCount < 1){ 177 | $this->error('Error:videos info not array'); 178 | } 179 | 180 | $this->setVideosTitle($videosTitle); 181 | 182 | $videoFi = ArrayHelper::multisort($videosListInfo['fl']['fi'], 'br', SORT_DESC); 183 | 184 | $videosFileIdPrefix = ''; 185 | $videoFileFormat = $videoFi[0]['id']; 186 | $this->videoQuality = $videoFi[0]['cname']; 187 | 188 | if($videoFi[0]['id'] > 100000){ 189 | $videosFileIdPrefix = '.m' . substr($videoFi[0]['id'], 3); 190 | } elseif($videoFi[0]['id'] > 10000){ 191 | $videosFileIdPrefix = '.p'. substr($videoFileFormat, 2); 192 | } 193 | 194 | $videoFileName = $vid . $videosFileIdPrefix . self::FILE_EXTENSION; 195 | 196 | $videosKey = $this->getKey($videoFileFormat, $videoFileName); 197 | 198 | $uiLen = count($videosListInfo['vl']['vi'][0]['ul']['ui']); 199 | 200 | $videosUrl = $videosListInfo['vl']['vi'][0]['ul']['ui'][$uiLen-1]['url']; 201 | $fileKeyId = $this->getKeyId($videosKey['keyid']); 202 | 203 | $requestVideosUrl = $videosUrl . $fileKeyId . '?vkey='.$videosKey['key']; 204 | 205 | $this->downloadUrls[0] = $requestVideosUrl; 206 | 207 | $downInfo = $this->downloadFile(); 208 | 209 | if($downInfo['fileSize'] < 1024){ 210 | unlink($this->rootPath . $this->videosTitle . $this->fileExt); 211 | $this->error(); 212 | } 213 | 214 | $this->success($this->ffmpFileListTxt); 215 | 216 | } 217 | 218 | } -------------------------------------------------------------------------------- /core/Platform/Twitter/Twitter.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 24 | } 25 | 26 | /** 27 | * @return null|string 28 | */ 29 | public function getVid():?string 30 | { 31 | $urlPath = parse_url($this->requestUrl, PHP_URL_PATH); 32 | $vid = pathinfo($urlPath, PATHINFO_BASENAME); 33 | 34 | if(!is_numeric($vid)){ 35 | $this->error('Error:vid not number'); 36 | } 37 | 38 | return $vid; 39 | } 40 | 41 | /** 42 | * @param string $vid 43 | * @return array 44 | * @throws \ErrorException 45 | */ 46 | public function getVideos(string $vid):array 47 | { 48 | $authorization = 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA'; 49 | 50 | $api = 'https://api.twitter.com/1.1/statuses/show.json?include_profile_interstitial_type=1&include_blocking=1&include_blocked_by=1&include_followed_by=1&include_want_retweets=1&include_mute_edge=1&include_can_dm=1&skip_status=1&cards_platform=Web-12&include_cards=1&include_ext_alt_text=true&include_reply_count=1&tweet_mode=extended&trim_user=false&include_ext_media_color=true&id='. $vid .'&ext=mediaStats,highlightedLabel'; 51 | 52 | $curlHeader = $this->httpHeader($authorization); 53 | $getInfo = Curl::get($api, $this->requestUrl, $curlHeader); 54 | if(empty($getInfo[0])){ 55 | $this->error('Error:get videos info '); 56 | } 57 | 58 | $json = json_decode($getInfo[0], true); 59 | if(isset($json['errors'])){ 60 | $this->error('Error:' . $json[0]['message']); 61 | } 62 | 63 | $userName = str_replace([' ', '\\', '/', '\''], '', $json['user']['name']); 64 | 65 | $title = $userName . '-' . $vid; 66 | $videoInfo = $json['extended_entities']['media'][0]['video_info']['variants']; 67 | 68 | return [ 69 | 'title' => $title, 70 | 'list' => $videoInfo, 71 | ]; 72 | } 73 | 74 | public function httpHeader(string $authorization=''):array 75 | { 76 | $httpProxy = Config::instance()->get('http_proxy'); 77 | $ip = Curl::randIp(); 78 | $curlHeader = [ 79 | CURLOPT_HTTPHEADER => [ 80 | "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 81 | "Accept-Language: zh-CN,en-US;q=0.7,en;q=0.3", 82 | "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36", 83 | "HTTP_X_FORWARDED_FOR: {$ip}", 84 | "CLIENT-IP: {$ip}", 85 | "Authorization: {$authorization}", 86 | ], 87 | CURLOPT_PROXY => $httpProxy, 88 | ]; 89 | 90 | return $curlHeader; 91 | } 92 | 93 | /** 94 | * @param array $videosInfo 95 | * @return array 96 | * @throws \ErrorException 97 | */ 98 | public function getM3u8(array $videosInfo):array 99 | { 100 | $curlHeader = $this->httpHeader(); 101 | $baseHost = parse_url($videosInfo['application/x-mpegURL'][0]['url'], PHP_URL_HOST); 102 | $m3u8Urls = Curl::get($videosInfo['application/x-mpegURL'][0]['url'], $this->requestUrl, $curlHeader); 103 | 104 | $result = []; 105 | if($m3u8Urls[0]){ 106 | $m3u8Urls = M3u8::getUrls($m3u8Urls[0]); 107 | $url = array_pop($m3u8Urls); 108 | $m3u8Url = $baseHost . trim($url); 109 | 110 | $m3u8Urls = Curl::get($m3u8Url, $this->requestUrl, $curlHeader); 111 | if(empty($m3u8Urls[0])){ 112 | $this->error('Error:m3u8 empty'); 113 | } 114 | 115 | $m3u8Url = M3u8::getUrls($m3u8Urls[0]); 116 | if(!empty($m3u8Url)){ 117 | foreach($m3u8Url as $row){ 118 | $result[] = 'https://' . $baseHost . trim($row); 119 | } 120 | } else { 121 | $this->error('Error:m3u8 empty'); 122 | } 123 | } 124 | 125 | return $result; 126 | } 127 | 128 | /** 129 | * @throws \ErrorException 130 | */ 131 | public function download(): void 132 | { 133 | $vid = $this->getVid(); 134 | 135 | $videoInfo = $this->getVideos($vid); 136 | 137 | $videosInfoGroup = ArrayHelper::group($videoInfo['list'], 'content_type'); 138 | 139 | if(isset($videosInfoGroup['video/mp4'])){ 140 | $videoUrlInfo = ArrayHelper::multisort($videosInfoGroup['video/mp4'], 'bitrate', SORT_DESC); 141 | 142 | $mp4Info = array_shift($videoUrlInfo); 143 | 144 | $this->downloadUrls[0] = $mp4Info['url']; 145 | $this->videoQuality = $mp4Info['content_type']; 146 | } else { 147 | $m3u8Urls = $this->getM3u8($videosInfoGroup); 148 | 149 | $this->downloadUrls = $m3u8Urls; 150 | $this->fileExt = '.ts'; 151 | } 152 | 153 | $fileSizeArray = [ 154 | 'totalSize' => self::DEFAULT_FILESIZE, 155 | 'list' => [self::DEFAULT_FILESIZE], 156 | ]; 157 | $httpProxy = Config::instance()->get('http_proxy'); 158 | $curlProxy = []; 159 | if($httpProxy){ 160 | $curlProxy = [ 161 | CURLOPT_PROXY => $httpProxy, 162 | ]; 163 | } 164 | 165 | $this->setVideosTitle($videoInfo['title']); 166 | $downloadFileInfo = $this->downloadFile($fileSizeArray, $curlProxy); 167 | 168 | if($downloadFileInfo < 1024){ 169 | printf("\n\e[41m%s\033[0m\n", 'Errors:download file 0'); 170 | } else { 171 | if($this->fileExt == '.ts'){ 172 | FFmpeg::concatToMp4($this->videosTitle, $this->ffmpFileListTxt, './Videos/'); 173 | $this->deleteTempSaveFiles(); 174 | } 175 | } 176 | 177 | $this->success($this->ffmpFileListTxt); 178 | } 179 | 180 | } -------------------------------------------------------------------------------- /core/Platform/Weibo/Weibo.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 20 | } 21 | 22 | /** 23 | * @return string 24 | */ 25 | public function getVid():string 26 | { 27 | $vid = ''; 28 | $url = parse_url($this->requestUrl, PHP_URL_PATH); 29 | 30 | $urls = explode('v/', $url); 31 | if(isset($urls[2])){ 32 | $vid = $urls[2]; 33 | } 34 | 35 | if(empty($vid)){ 36 | $urls = explode('/', $url); 37 | if(isset($urls[2])){ 38 | $vid = $urls[2]; 39 | } 40 | } 41 | 42 | return $vid; 43 | } 44 | 45 | /** 46 | * url:https://m.weibo.cn/statuses/show?id=Gte2peqo6 47 | * @param string $vid 48 | * @return array|null 49 | * @throws \ErrorException 50 | */ 51 | public function getVideosInfo(string $vid):?array 52 | { 53 | $getJsonUrl = 'https://m.weibo.cn/statuses/show?id=' . $vid; 54 | $getInfo = Curl::get($getJsonUrl, $this->requestUrl); 55 | $videosInfo = []; 56 | 57 | if(!empty($getInfo[0])){ 58 | $json = json_decode($getInfo[0], true); 59 | if(1 != $json['ok']){ 60 | $this->error('Error:json is error / html must login'); 61 | } 62 | 63 | if(empty($json['data']['page_info']['media_info'])){ 64 | $this->error('Error:media_info is empty'); 65 | } 66 | 67 | $mediaInfo = array_values($json['data']['page_info']['media_info']); 68 | $mediaInfo = array_slice($mediaInfo, 0, count($mediaInfo)-1); 69 | $streamInfo = array_keys($json['data']['page_info']['media_info']); 70 | $streamInfo = array_slice($streamInfo, 0, count($streamInfo)-1); 71 | 72 | $videoTitle = md5($this->requestUrl); 73 | if(!empty($json['data']['page_info']['title'])){ 74 | $videoTitle = $json['data']['page_info']['title']; 75 | } 76 | 77 | $videosInfo = [ 78 | 'title' => $videoTitle, 79 | 'url' => $mediaInfo, 80 | 'size' => $json['data']['page_info']['video_details']['size'], 81 | 'stream' => $streamInfo, 82 | ]; 83 | } 84 | 85 | return $videosInfo; 86 | } 87 | 88 | /** 89 | * html5 Url: https://m.weibo.cn/status/Gte2peqo6?fid=1034%3A4269653577684456&jumpfrom=weibocom 90 | * @throws \ErrorException 91 | */ 92 | public function download(): void 93 | { 94 | $vid = $this->getVid(); 95 | 96 | if(empty($vid)){ 97 | $this->error('Error:vid is empty'); 98 | } 99 | 100 | $videosInfo = $this->getVideosInfo($vid); 101 | 102 | if(empty($videosInfo)){ 103 | $this->error('Error:VideosInfo is empty'); 104 | } 105 | 106 | $this->setVideosTitle($videosInfo['title']); 107 | $this->videoQuality = array_pop($videosInfo['stream']); 108 | $this->downloadUrls[0] = array_pop($videosInfo['url']); 109 | if(empty($this->downloadUrls[0])){ 110 | $this->downloadUrls[0] = array_shift($videosInfo['url']); 111 | $this->videoQuality = array_shift($videosInfo['stream']); 112 | } 113 | 114 | $downloadFileInfo = $this->downloadFile(); 115 | 116 | if($downloadFileInfo < 1024){ 117 | printf("\n\e[41m%s\033[0m\n", 'Errors:download file 0'); 118 | } 119 | 120 | $this->success($this->ffmpFileListTxt); 121 | } 122 | 123 | } -------------------------------------------------------------------------------- /core/Platform/Xvideos/Xvideos.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 24 | } 25 | 26 | /** 27 | * @param $videoId 28 | * @param array $curlProxy 29 | * @return array|bool|null 30 | * @throws \ErrorException 31 | */ 32 | public function getVideosInfo(string $videoId, array $curlProxy=[]):?array 33 | { 34 | $jsonUrl = 'https://www.xvideos.com/html5player/getvideo/'. $videoId .'/2'; 35 | 36 | $urlCache = (new FileCache())->get($jsonUrl); 37 | if(!$urlCache){ 38 | $getJson = Curl::get($jsonUrl, $jsonUrl, $curlProxy); 39 | if($getJson){ 40 | $json = json_decode($getJson[0], true); 41 | if(false === $json['exist']){ 42 | throw new \ErrorException('获取json 失败'); 43 | } 44 | if(!empty($json['mp4_high'])){ 45 | $urlCache = [ 46 | 'url' => $json['mp4_high'], 47 | 'type' => 'mp4_high', 48 | ]; 49 | } elseif (!empty($json['mp4_low'])){ 50 | $urlCache = [ 51 | 'url' => $json['mp4_low'], 52 | 'type' => 'mp4_low', 53 | ]; 54 | } else { 55 | $this->error('未找到视频地址:'.$jsonUrl); 56 | } 57 | } 58 | 59 | (new FileCache())->set($jsonUrl, $urlCache); 60 | } 61 | 62 | return $urlCache; 63 | } 64 | 65 | /** 66 | * @throws \ErrorException 67 | */ 68 | public function download():void 69 | { 70 | $urlInfo = parse_url($this->requestUrl, PHP_URL_PATH); 71 | $urlInfo = explode('/', $urlInfo); 72 | 73 | $videoId = substr($urlInfo[1], 5); 74 | 75 | $httpProxy = Config::instance()->get('http_proxy'); 76 | $curlProxy = []; 77 | if($httpProxy){ 78 | $curlProxy = [ 79 | CURLOPT_PROXY => $httpProxy, 80 | ]; 81 | } 82 | 83 | $this->videosTitle = $urlInfo[2]; 84 | $videosInfo = $this->getVideosInfo($videoId, $curlProxy); 85 | 86 | $this->videoQuality = $videosInfo['type']; 87 | $this->downloadUrls[0] = $videosInfo['url']; 88 | 89 | $this->downloadFile([], $curlProxy); 90 | $this->success($this->ffmpFileListTxt); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /core/Platform/Youku/Youku.php: -------------------------------------------------------------------------------- 1 | requestUrl = $url; 23 | } 24 | 25 | public function getClientTs():float 26 | { 27 | $microTime = microtime(true); 28 | $time = round($microTime, 3); 29 | return $time; 30 | } 31 | 32 | /** 33 | * @return string 34 | * @throws \ErrorException 35 | */ 36 | public function getUtid():string 37 | { 38 | $utidCache = (new FileCache())->get('youkuEtag'); 39 | 40 | if($utidCache){ 41 | $utid = $utidCache; 42 | } else { 43 | $logEgJs = file_get_contents('https://log.mmstat.com/eg.js'); 44 | 45 | //goldlog.Etag="字符" 46 | $rule = '/Etag="(.+?)"/'; 47 | preg_match_all($rule, $logEgJs, $matches); 48 | if(!$matches){ 49 | throw new \ErrorException('must be utid'); 50 | } 51 | 52 | $utid = urlencode($matches[1][0]); 53 | (new FileCache())->set('youkuEtag', $utid, 1800); 54 | } 55 | 56 | return $utid; 57 | } 58 | 59 | public function getCKey():string 60 | { 61 | $ckey = 'DIl58SLFxFNndSV1GFNnMQVYkx1PP5tKe1siZu/86PR1u/Wh1Ptd+WOZsHHWxysSfAOhNJpdVWsdVJNsfJ8Sxd8WKVvNfAS8aS8fAOzYARzPyPc3JvtnPHjTdKfESTdnuTW6ZPvk2pNDh4uFzotgdMEFkzQ5wZVXl2Pf1/Y6hLK0OnCNxBj3+nb0v72gZ6b0td+WOZsHHWxysSo/0y9D2K42SaB8Y/+aD2K42SaB8Y/+ahU+WOZsHcrxysooUeND'; 62 | 63 | return urlencode($ckey); 64 | } 65 | 66 | public function getVid():?string 67 | { 68 | if(empty($this->requestUrl)){ 69 | return ''; 70 | } 71 | $rule = '/id_(.+?).html/'; 72 | preg_match_all($rule, $this->requestUrl, $matches); 73 | if($matches){ 74 | return $matches[1][0]; 75 | } 76 | 77 | return ''; 78 | } 79 | 80 | /** 81 | * @throws \ErrorException 82 | */ 83 | public function download():void 84 | { 85 | $vid = $this->getVid(); 86 | $ckey = $this->getCKey(); 87 | $clientTs = $this->getClientTs(); 88 | $utid = $this->getUtid(); 89 | $httpReferer = 'https://tv.youku.com'; 90 | $ccode = '0518'; 91 | 92 | $youkuVideoUrl = 'https://ups.youku.com/ups/get.json?vid='. $vid .'&ccode=' . $ccode . '&client_ip=192.168.1.1&client_ts=' . $clientTs .'&utid=' . $utid .'&ckey=' . $ckey; 93 | 94 | $json = self::get($youkuVideoUrl, $httpReferer, $vid); 95 | 96 | if(isset($json['data']['stream'])){ 97 | $videosTitle = $json['data']['video']['title']; 98 | 99 | $this->setVideosTitle($videosTitle); 100 | 101 | $videosInfo = ArrayHelper::multisort($json['data']['stream'], 'width', SORT_DESC); 102 | 103 | $this->videoQuality = $videosInfo[0]['stream_type']; 104 | 105 | $fileTotalSize = 0; 106 | $fileSizeArr = []; 107 | foreach($videosInfo[0]['segs'] as $key => $value){ 108 | $fileTotalSize += $value['size']; 109 | 110 | $fileSizeArr[$key] = $value['size']; 111 | $this->downloadUrls[$key] = $value['cdn_url']; 112 | } 113 | 114 | $fileSizeArray = [ 115 | 'totalSize' => $fileTotalSize, 116 | 'list' => $fileSizeArr, 117 | ]; 118 | 119 | $downloadFileInfo = $this->downloadFile($fileSizeArray); 120 | 121 | if($downloadFileInfo < 1024){ 122 | printf("\n\e[41m%s\033[0m\n", 'Errors:download file 0'); 123 | } else { 124 | if(count($this->downloadUrls) > 1){ 125 | FFmpeg::concatToMp4($this->videosTitle, $this->ffmpFileListTxt, './Videos/'); 126 | $this->deleteTempSaveFiles(); 127 | } 128 | } 129 | 130 | $this->success($this->ffmpFileListTxt); 131 | 132 | } else { 133 | printf("\n\e[41m%s\033[0m\n", $json); 134 | exit(0); 135 | } 136 | } 137 | 138 | /** 139 | * @param string $url 140 | * @param string $httpReferer 141 | * @param string $cacheFileName 142 | * @return bool|mixed|null 143 | * @throws \ErrorException 144 | */ 145 | public static function get(string $url,string $httpReferer,string $cacheFileName) 146 | { 147 | $ch = curl_init(); 148 | $defaultOptions = Curl::defaultOptions($url, $httpReferer); 149 | 150 | curl_setopt_array($ch, $defaultOptions); 151 | $contents = curl_exec($ch); 152 | curl_close($ch); 153 | 154 | $contents = json_decode($contents, true); 155 | 156 | if(isset($contents['data']['error']['note'])){ 157 | $contentsCache = (new FileCache())->get($cacheFileName); 158 | if($contentsCache){ 159 | $contents = $contentsCache; 160 | } else { 161 | $contents = $contents['data']['error']['note']; 162 | } 163 | } else if(is_array($contents['data']['stream'])) { 164 | (new FileCache())->set($cacheFileName, $contents, 1800); 165 | } 166 | 167 | return $contents; 168 | } 169 | 170 | } -------------------------------------------------------------------------------- /core/Style/Color.php: -------------------------------------------------------------------------------- 1 | ='); 12 | 13 | if(('7.37.0' > $curlVersion) && !$phpVersion){ 14 | die("PHP version\e[31m must >= 7.1 and curl version >= 7.37.0\e[0m\n"); 15 | } 16 | } 17 | 18 | checkVersion(); 19 | 20 | $args = array_slice($argv, 1); 21 | if(empty($args)){ 22 | printf("请传入参数 \n"); 23 | die; 24 | } 25 | 26 | $url = $args[0]; 27 | $domain = parse_url($url, PHP_URL_HOST); 28 | 29 | $domain = \core\Http\Domain::match($url); 30 | 31 | if($domain){ 32 | if(in_array($domain, ['91p25', '91porn'])){ 33 | $domain = 'Porn'; 34 | } 35 | 36 | $className = "\core\Platform\\$domain\\" . $domain; 37 | 38 | $classFile = __DIR__ . str_replace('\\', DIRECTORY_SEPARATOR, $className); 39 | if(!file_exists($classFile.'.php')){ 40 | echo("\033[31m No Support \033[0m".PHP_EOL); 41 | exit(0); 42 | } 43 | 44 | $class = new $className($url); 45 | $class->download(); 46 | } 47 | 48 | echo "\n"; --------------------------------------------------------------------------------