├── .gitignore ├── README.md ├── comparisonfunctions.usort.php ├── examples ├── example-audio-1.php ├── example-audio-2.php ├── example-audio-3.php ├── example-video-1.php ├── example-video-2.php └── example-video-3.php ├── videos └── .gitignore ├── youtube-dl.class.php └── youtube-dl.config.php /.gitignore: -------------------------------------------------------------------------------- 1 | gh-pages/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # yt_downloader - PHP class to download videos from YouTube and/or convert them to mp3 audio files. 2 | 3 | ## Introduction 4 | 5 | This PHP class takes a YouTube URL (or YouTube Video-ID) and downloads the video to your computer. 6 | Optionally, you can convert any YouTube video to an MP3 Audio file (requires ffmpeg to be installed!). 7 | 8 | ## UI Demo 9 | For an example integration, try the demo: 10 | http://eyecatchup.github.com/php-yt_downloader/index.html?vid=http://www.youtube.com/watch?v=cMMpLEGlFWE 11 | 12 | ## Download 13 | 14 | The latest stable version can be downloaded from the downloads tab, or using the following link: 15 | https://github.com/downloads/eyecatchup/php-yt_downloader/eyecatchup-php-yt_downloader-403c053.zip 16 | 17 | The UI demo code is available here: 18 | https://github.com/eyecatchup/php-yt_downloader/tree/gh-pages 19 | 20 | ## Basic Usage 21 | 22 | Usage is pretty straight forward: 23 | 24 | ```php 25 | getMessage()); 36 | } 37 | ``` 38 | 39 | You can provide either a YouTube URL (as used in the example), or a Youtube Video-ID. The class will check whether the given input value is a YouTube URL, or a YouTube Video-ID . If it's a URL, the ID will be extracted automatically. So, 40 | 41 | `new yt_downloader("http://www.youtube.com/watch?v=aahOEZKTCzU");` is identical to `new yt_downloader("https://www.youtube.com/watch?feature=related&v=aahOEZKTCzU");` is identical to `new yt_downloader("aahOEZKTCzU");`. 42 | 43 | For more (advanced) examples see the example-*.php files. 44 | 45 | ## Configuration 46 | 47 | Use the youtube-dl.config.php file to set your download preferences. 48 | 49 | URL: https://github.com/eyecatchup/php-yt_downloader/ 50 | License: http://eyecatchup.mit-license.org/ 51 | (c) 2012, Stephan Schmitz 52 | -------------------------------------------------------------------------------- /comparisonfunctions.usort.php: -------------------------------------------------------------------------------- 1 | $b) ? -1 : +1; 21 | } 22 | -------------------------------------------------------------------------------- /examples/example-audio-1.php: -------------------------------------------------------------------------------- 1 | getMessage()); 8 | } 9 | -------------------------------------------------------------------------------- /examples/example-audio-2.php: -------------------------------------------------------------------------------- 1 | get_audio(); 7 | $path_dl = $mytube->get_downloads_dir(); 8 | 9 | clearstatcache(); 10 | if($audio !== FALSE && file_exists($path_dl . $audio) !== FALSE) 11 | { 12 | print "Click, to open downloaded audio file."; 13 | } else { 14 | print "Oups. Something went wrong."; 15 | } 16 | 17 | $log = $mytube->get_ffmpeg_Logfile(); 18 | if($log !== FALSE) { 19 | print "
Click, to view the Ffmpeg file."; 20 | } 21 | } 22 | catch (Exception $e) { 23 | die($e->getMessage()); 24 | } 25 | -------------------------------------------------------------------------------- /examples/example-audio-3.php: -------------------------------------------------------------------------------- 1 | set_audio_format("wav"); # Change default audio output filetype. 8 | $mytube->set_ffmpegLogs_active(FALSE); # Disable Ffmpeg process logging. 9 | 10 | $mytube->download_audio(); 11 | } 12 | catch (Exception $e) { 13 | die($e->getMessage()); 14 | } 15 | -------------------------------------------------------------------------------- /examples/example-video-1.php: -------------------------------------------------------------------------------- 1 | getMessage()); 8 | } 9 | -------------------------------------------------------------------------------- /examples/example-video-2.php: -------------------------------------------------------------------------------- 1 | get_video(); 7 | $path_dl = $mytube->get_downloads_dir(); 8 | 9 | clearstatcache(); 10 | if($video !== FALSE && file_exists($path_dl . $video)) 11 | { 12 | print "Click, to open downloaded video file."; 13 | } else { 14 | print "Oups. Something went wrong."; 15 | } 16 | } 17 | catch (Exception $e) { 18 | die($e->getMessage()); 19 | } 20 | -------------------------------------------------------------------------------- /examples/example-video-3.php: -------------------------------------------------------------------------------- 1 | set_youtube($_GET["vid"]); # YouTube URL (or ID) of the video to download. 10 | $mytube->set_video_quality(1); # Change default output video file quality. 11 | $mytube->set_thumb_size('s'); # Change default video preview image size. 12 | 13 | $download = $mytube->download_video(); 14 | 15 | if($download == 0 || $download == 1) 16 | { 17 | $video = $mytube->get_video(); 18 | 19 | if($download == 0) { 20 | print "

$video
succesfully downloaded into your Downloads Folder.

"; 21 | } 22 | else if($download == 1) { 23 | print "

$video
already exists in your your Downloads Folder.

"; 24 | } 25 | 26 | $filestats = $mytube->video_stats(); 27 | if($filestats !== FALSE) { 28 | print "

File statistics for $video

"; 29 | print "Filesize: " . $filestats["size"] . "
"; 30 | print "Created: " . $filestats["created"] . "
"; 31 | print "Last modified: " . $filestats["modified"] . "
"; 32 | } 33 | 34 | $path = $mytube->get_downloads_dir(); 35 | print "
Click, to open downloaded video file."; 36 | 37 | $thumb = $mytube->get_thumb(); 38 | clearstatcache(); 39 | if($thumb !== FALSE && file_exists($path . $thumb)) { 40 | print "

"; 41 | } 42 | } 43 | } 44 | catch (Exception $e) { 45 | die($e->getMessage()); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /videos/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eyecatchup/php-yt_downloader/fd201e23970ad1bba82444fb3614c59780e2f72a/videos/.gitignore -------------------------------------------------------------------------------- /youtube-dl.class.php: -------------------------------------------------------------------------------- 1 | 11 | * @copyright 2012 - present, Stephan Schmitz 12 | * @license http://eyecatchup.mit-license.org 13 | * @link https://github.com/eyecatchup/php-yt_downloader/ 14 | * ================================================================================ 15 | * LICENSE: Permission is hereby granted, free of charge, to any person obtaining 16 | * a copy of this software and associated documentation files (the "Software"), 17 | * to deal in the Software without restriction, including without limitation the 18 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | * copies of the Software, and to permit persons to whom the Software is furnished 20 | * to do so, subject to the following conditions: 21 | * 22 | * The above copyright notice and this permission notice shall be included in all 23 | * copies or substantial portions of the Software. 24 | * 25 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY 29 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 30 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 31 | * ================================================================================ 32 | */ 33 | 34 | // Ensure the PHP extensions CURL and JSON are installed. 35 | if (!function_exists('curl_init')) { 36 | throw new Exception('Script requires the PHP CURL extension.'); 37 | exit(0); 38 | } 39 | if (!function_exists('json_decode')) { 40 | throw new Exception('Script requires the PHP JSON extension.'); 41 | exit(0); 42 | } 43 | 44 | // Downloading HD Videos may take some time. 45 | ini_set('max_execution_time', 0); 46 | // Writing HD Videos to your disk, may need some extra resources. 47 | ini_set('memory_limit', '64M'); 48 | 49 | // Include the config interface. 50 | require('youtube-dl.config.php'); 51 | // Include helper functions for usort. 52 | require('comparisonfunctions.usort.php'); 53 | 54 | /** 55 | * yt_downloader Class 56 | */ 57 | class yt_downloader implements cnfg 58 | { 59 | /** 60 | * Class constructor method. 61 | * @access public 62 | * @return void 63 | */ 64 | public function __construct($str=NULL, $instant=FALSE, $out=NULL) 65 | { 66 | // Required YouTube URLs. 67 | $this->YT_BASE_URL = "http://www.youtube.com/"; 68 | $this->YT_INFO_URL = $this->YT_BASE_URL . "get_video_info?video_id=%s&el=embedded&ps=default&eurl=&hl=en_US"; 69 | $this->YT_INFO_ALT = $this->YT_BASE_URL . "oembed?url=%s&format=json"; 70 | $this->YT_THUMB_URL = "http://img.youtube.com/vi/%s/%s.jpg"; 71 | $this->YT_THUMB_ALT = "http://i1.ytimg.com/vi/%s/%s.jpg"; 72 | 73 | $this->CURL_UA = "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:11.0) Gecko Firefox/11.0"; 74 | 75 | // Set default parameters for the download instance. 76 | $this->audio = FALSE; # Formatted output filename(.ext) of the object's audio output. 77 | $this->video = FALSE; # Formatted output filename(.ext) of the object's video output. 78 | $this->thumb = FALSE; # Formatted output filename(.ext) of the object's preview image. 79 | $this->videoID = FALSE; # YouTube video id. 80 | $this->videoExt = FALSE; # Filetype of the object's video ouput file. 81 | $this->videoTitle = FALSE; # Formatted output filename by original YouTube video title /wo ext. 82 | $this->videoThumb = FALSE; # YouTube URL of the object's preview image. 83 | $this->videoQuality = FALSE; # Whether downloading videos at the best quality available. 84 | $this->audioQuality = FALSE; # Default audio output sample rate (in kbits). 85 | $this->audioFormat = FALSE; # Default audio output filetype. 86 | $this->videoThumbSize = FALSE; # Whether default thumbnail size is 120*90, or 480*360 px. 87 | $this->defaultDownload = FALSE; # Whether downloading video, or audio is default. 88 | $this->downloadThumbs = TRUE; # Whether to download thumnails for downloads (Boolean TRUE/FALSE). 89 | $this->downloadsDir = FALSE; # Relative path to the directory to save downloads to. 90 | $this->downloadsArray = FALSE; # Array, containing the YouTube URLs of all videos available. 91 | $this->FfmpegLogsDir = FALSE; # Absolute path to the directory to save ffmpeg logs to. 92 | $this->ffmpegLogfile = FALSE; # Absolute path to the directory to save ffmpeg logs to. 93 | $this->FfmpegLogsActive = TRUE; # Whether to log ffmpeg processes (Boolean TRUE/FALSE). 94 | 95 | self::set_downloads_dir(cnfg::Download_Folder); 96 | self::set_ffmpegLogs_dir(cnfg::Ffmpeg_LogsDir); 97 | self::set_ffmpegLogs_active(cnfg::Ffmpeg_LogsActive); 98 | self::set_default_download(cnfg::Default_Download); 99 | self::set_download_thumbnail(cnfg::Download_Thumbnail); 100 | self::set_thumb_size(cnfg::Default_Thumbsize); 101 | self::set_video_quality(cnfg::Default_Videoquality); 102 | self::set_audio_quality(cnfg::Default_Audioquality); 103 | self::set_audio_format(cnfg::Default_Audioformat); 104 | 105 | // Optional constructor argument #1 will be used as YouTube Video-ID. 106 | if($str != NULL) { 107 | self::set_youtube($str); 108 | // Optional constructor argument #2 will start an instant download. 109 | if($instant === TRUE) { 110 | // Optional constructor argument #3 defines the media type to be saved. 111 | self::do_download($out); 112 | } 113 | } 114 | } 115 | 116 | public function do_download($out) 117 | { 118 | $action = ($out == "audio" || $out == "video") ? $out : self::get_default_download(); 119 | return ($action == "audio" ) ? self::download_audio() : self::download_video(); 120 | } 121 | 122 | /** 123 | * Set the YouTube Video that shall be downloaded. 124 | * @access public 125 | * @return void 126 | */ 127 | public function set_youtube($str) 128 | { 129 | /** 130 | * Parse input string to determine if it's a Youtube URL, 131 | * or an ID. If it's a URL, extract the ID from it. 132 | */ 133 | $tmp_id = self::parse_yturl($str); 134 | $vid_id = ($tmp_id !== FALSE) ? $tmp_id : $str; 135 | 136 | /** 137 | * Check the public video info feed to check if we got a 138 | * valid Youtube ID. If not, throw an exception and exit. 139 | */ 140 | $url = sprintf($this->YT_BASE_URL . "watch?v=%s", $vid_id); 141 | $url = sprintf($this->YT_INFO_ALT, urlencode($url)); 142 | if(self::curl_httpstatus($url) !== 200) { 143 | throw new Exception("Invalid Youtube video ID: $vid_id"); 144 | exit(); } 145 | else { self::set_video_id($vid_id); } 146 | } 147 | 148 | /** 149 | * Get the direct links to the YouTube Video. 150 | * @access public 151 | * @return integer Returns (int)0 if download succeded, or (int)1 if 152 | * the video already exists on the download directory. 153 | */ 154 | public function get_downloads() 155 | { 156 | /** 157 | * If we have a valid Youtube Video Id, try to get the real location 158 | * and download the video. If not, throw an exception and exit. 159 | */ 160 | $id = self::get_video_id(); 161 | if($id === FALSE) { 162 | throw new Exception("Missing video id. Use set_youtube() and try again."); 163 | exit(); 164 | } 165 | else { 166 | /** 167 | * Try to parse the YouTube Video-Info file to get the video URL map, 168 | * that holds the locations on the YouTube media servers. 169 | */ 170 | $v_info = self::get_yt_info(); 171 | if(self::get_videodata($v_info) === TRUE) 172 | { 173 | $vids = self::get_url_map($v_info); 174 | /** 175 | * If extracting the URL map failed, throw an exception 176 | * and exit. Try to include the original YouTube error 177 | * - eg "forbidden by country"-message. 178 | */ 179 | if(!is_array($vids) || sizeof($vids) == 0) { 180 | $err_msg = ""; 181 | if(strpos($v_info, "status=fail") !== FALSE) { 182 | preg_match_all('#reason=(.*?)$#si', $v_info, $err_matches); 183 | if(isset($err_matches[1][0])) { 184 | $err_msg = urldecode($err_matches[1][0]); 185 | $err_msg = str_replace("Watch on YouTube", "", strip_tags($err_msg)); 186 | $err_msg = "Youtube error message: ". $err_msg; 187 | } 188 | } 189 | return $err_msg; 190 | } 191 | else { 192 | $quality = self::get_video_quality(); 193 | if($quality == 1) { 194 | usort($vids, 'asc_by_quality'); 195 | } else if($quality == 0) { 196 | usort($vids, 'desc_by_quality'); 197 | } 198 | self::set_yt_url_map($vids); 199 | return $vids; 200 | } 201 | } 202 | } 203 | } 204 | 205 | /** 206 | * Try to download the defined YouTube Video. 207 | * @access public 208 | * @return integer Returns (int)0 if download succeded, or (int)1 if 209 | * the video already exists on the download directory. 210 | */ 211 | public function download_video($c=NULL) 212 | { 213 | $c = ($c !== NULL) ? $c : 0; 214 | /** 215 | * If we have a valid Youtube Video Id, try to get the real location 216 | * and download the video. If not, throw an exception and exit. 217 | */ 218 | $id = self::get_video_id(); 219 | if($id === FALSE) { 220 | throw new Exception("Missing video id. Use set_youtube() and try again."); 221 | exit(); 222 | } 223 | else { 224 | $yt_url_map = self::get_yt_url_map(); 225 | if($yt_url_map === FALSE) { 226 | $vids = self::get_downloads(); 227 | self::set_yt_url_map($vids); 228 | } else { 229 | $vids = $yt_url_map; 230 | } 231 | if(!is_array($vids)) { 232 | throw new Exception("Grabbing original file location(s) failed. $vids"); 233 | exit(); 234 | } 235 | else { 236 | /** 237 | * Format video title and set download and file preferences. 238 | */ 239 | $title = self::get_video_title(); 240 | $path = self::get_downloads_dir(); 241 | 242 | $YT_Video_URL = $vids[$c]["url"]; 243 | $res = $vids[$c]["type"]; 244 | $ext = $vids[$c]["ext"]; 245 | 246 | $videoTitle = $title . "_-_" . $res ."_-_youtubeid-$id"; 247 | $videoFilename = "$videoTitle.$ext"; 248 | $thumbFilename = "$videoTitle.jpg"; 249 | $video = $path . $videoFilename; 250 | 251 | self::set_video($videoFilename); 252 | self::set_thumb($thumbFilename); 253 | 254 | /** 255 | * PHP doesn't cache information about non-existent files. 256 | * So, if you call file_exists() on a file that doesn't exist, 257 | * it will return FALSE until you create the file. The problem is, 258 | * that once you've created a file, file_exists() will return TRUE - 259 | * even if you've deleted the file meanwhile and the cache haven't 260 | * been cleared! Even though unlink() clears the cache automatically, 261 | * since we don't know which way a file may have been deleted (if it existed), 262 | * we clear the file status cache to ensure a valid file_exists result. 263 | */ 264 | clearstatcache(); 265 | 266 | /** 267 | * If the video does not already exist in the download directory, 268 | * try to download the video and the video preview image. 269 | */ 270 | if(!file_exists($video)) 271 | { 272 | $download_thumbs = self::get_download_thumbnail(); 273 | if($download_thumbs === TRUE) { 274 | self::check_thumbs($id); 275 | } 276 | touch($video); 277 | chmod($video, 0775); 278 | 279 | // Download the video. 280 | $download = self::curl_get_file($YT_Video_URL, $video); 281 | 282 | if($download === FALSE) { 283 | throw new Exception("Saving $videoFilename to $path failed."); 284 | exit(); 285 | } 286 | else { 287 | if($download_thumbs === TRUE) { 288 | // Download the video preview image. 289 | $thumb = self::get_video_thumb(); 290 | if($thumb !== FALSE) 291 | { 292 | $thumbnail = $path . $thumbFilename; 293 | self::curl_get_file($thumb, $thumbnail); 294 | chmod($thumbnail, 0775); 295 | } 296 | } 297 | return 0; 298 | } 299 | } 300 | else { 301 | return 1; 302 | } 303 | } 304 | } 305 | } 306 | 307 | public function download_audio() 308 | { 309 | $ffmpeg = self::has_ffmpeg(); 310 | if($ffmpeg === FALSE) { 311 | throw new Exception("You must have Ffmpeg installed in order to use this function."); 312 | exit(); 313 | } 314 | else if($ffmpeg === TRUE) { 315 | self::set_video_quality(1); 316 | $dl = self::download_video(); 317 | if($dl == 0 || $dl == 1) 318 | { 319 | $title = self::get_video_title(); 320 | $path = self::get_downloads_dir(); 321 | $ext = self::get_audio_format(); 322 | $ffmpeg_infile = $path . self::get_video(); 323 | $ffmpeg_outfile = $path . $title .".". $ext; 324 | 325 | if(!file_exists($ffmpeg_outfile)) 326 | { 327 | // Whether to log the ffmpeg process. 328 | $logging = self::get_ffmpegLogs_active(); 329 | 330 | // Ffmpeg command to convert the input video file into an audio file. 331 | $ab = self::get_audio_quality() . "k"; 332 | $cmd = "ffmpeg -i \"$ffmpeg_infile\" -ar 44100 -ab $ab -ac 2 \"$ffmpeg_outfile\""; 333 | 334 | if($logging !== FALSE) 335 | { 336 | // Create a unique log file name per process. 337 | $ffmpeg_logspath = self::get_ffmpegLogs_dir(); 338 | $ffmpeg_logfile = "ffmpeg." . date("Ymdhis") . ".log"; 339 | $logfile = "./" . $ffmpeg_logspath . $ffmpeg_logfile; 340 | self::set_ffmpeg_Logfile($logfile); 341 | 342 | // Create new log file (via command line). 343 | exec("touch $logfile"); 344 | exec("chmod 777 $logfile"); 345 | } 346 | 347 | // Execute Ffmpeg command line command. 348 | $Ffmpeg = exec($cmd); 349 | $log_it = "echo \"$Ffmpeg\" > \"$logfile\""; 350 | $lg = `$log_it`; 351 | 352 | // If the video did not already existed, delete it (since we probably wanted the soundtrack only). 353 | if($dl == 0) { 354 | unlink($ffmpeg_infile); 355 | } 356 | 357 | clearstatcache(); 358 | if(file_exists($ffmpeg_outfile) !== FALSE) { 359 | self::set_audio($title .".". $ext); 360 | return 0; 361 | } else { 362 | throw new Exception("Something went wrong while converting the video into $ext format, sorry!"); 363 | exit(); 364 | } 365 | } 366 | else { 367 | self::set_audio($title .".". $ext); 368 | return 1; 369 | } 370 | } 371 | } else { 372 | throw new Exception("Cannot locate your Ffmpeg installation?! Thus, cannot convert the video into $ext format."); 373 | exit(); 374 | } 375 | } 376 | 377 | /** 378 | * Get filestats for the downloaded audio/video file. 379 | * @access public 380 | * @return mixed Returns an array containing formatted filestats, 381 | * or (boolean) FALSE if file doesn't exist. 382 | */ 383 | public function video_stats() 384 | { 385 | $file = self::get_video(); 386 | $path = self::get_downloads_dir(); 387 | 388 | clearstatcache(); 389 | $filestats = stat($path . $file); 390 | if($filestats !== FALSE) { 391 | return array( 392 | "size" => self::human_bytes($filestats["size"]), 393 | "created" => date ("d.m.Y H:i:s.", $filestats["ctime"]), 394 | "modified" => date ("d.m.Y H:i:s.", $filestats["mtime"]) 395 | ); 396 | } 397 | else { return FALSE; } 398 | } 399 | 400 | /** 401 | * Check if input string is a valid YouTube URL 402 | * and try to extract the YouTube Video ID from it. 403 | * @access private 404 | * @return mixed Returns YouTube Video ID, or (boolean) FALSE. 405 | */ 406 | private function parse_yturl($url) 407 | { 408 | $pattern = '#^(?:https?://)?'; # Optional URL scheme. Either http or https. 409 | $pattern .= '(?:www\.)?'; # Optional www subdomain. 410 | $pattern .= '(?:'; # Group host alternatives: 411 | $pattern .= 'youtu\.be/'; # Either youtu.be, 412 | $pattern .= '|youtube\.com'; # or youtube.com 413 | $pattern .= '(?:'; # Group path alternatives: 414 | $pattern .= '/embed/'; # Either /embed/, 415 | $pattern .= '|/v/'; # or /v/, 416 | $pattern .= '|/watch\?v='; # or /watch?v=, 417 | $pattern .= '|/watch\?.+&v='; # or /watch?other_param&v= 418 | $pattern .= ')'; # End path alternatives. 419 | $pattern .= ')'; # End host alternatives. 420 | $pattern .= '([\w-]{11})'; # 11 characters (Length of Youtube video ids). 421 | $pattern .= '(?:.+)?$#x'; # Optional other ending URL parameters. 422 | preg_match($pattern, $url, $matches); 423 | return (isset($matches[1])) ? $matches[1] : FALSE; 424 | } 425 | 426 | /** 427 | * Get internal YouTube info for a Video. 428 | * @access private 429 | * @return string Returns video info as string. 430 | */ 431 | private function get_yt_info() 432 | { 433 | $url = sprintf($this->YT_INFO_URL, self::get_video_id()); 434 | return self::curl_get($url); 435 | } 436 | 437 | /** 438 | * Get the public YouTube Info-Feed for a Video. 439 | * @access private 440 | * @return mixed Returns array, containing the YouTube Video-Title 441 | * and preview image URL, or (boolean) FALSE 442 | * if parsing the feed failed. 443 | */ 444 | private function get_public_info() 445 | { 446 | $url = sprintf($this->YT_BASE_URL . "watch?v=%s", self::get_video_id()); 447 | $url = sprintf($this->YT_INFO_ALT, urlencode($url)); 448 | $info = json_decode(self::curl_get($url), TRUE); 449 | 450 | if(is_array($info) && sizeof($info) > 0) { 451 | return array( 452 | "title" => $info["title"], 453 | "thumb" => $info["thumbnail_url"] 454 | ); 455 | } 456 | else { return FALSE; } 457 | } 458 | 459 | /** 460 | * Get formatted video data from the public YouTube Info-Feed. 461 | * @access private 462 | * @param string $str Info-File contents for a YouTube Video. 463 | * @return mixed Returns the formatted YouTube Video-Title as string, 464 | * or (boolean) FALSE if extracting failed. 465 | */ 466 | private function get_videodata($str) 467 | { 468 | $yt_info = $str; 469 | $pb_info = self::get_public_info(); 470 | 471 | if($pb_info !== FALSE) { 472 | $htmlTitle = htmlentities(utf8_decode($pb_info["title"])); 473 | $videoTitle = self::canonicalize($htmlTitle); 474 | } 475 | else { 476 | $videoTitle = self::formattedVideoTitle($yt_info); 477 | } 478 | 479 | if(is_string($videoTitle) && strlen($videoTitle) > 0) { 480 | self::set_video_title($videoTitle); 481 | return TRUE; 482 | } 483 | else { return FALSE; } 484 | } 485 | 486 | /** 487 | * Get the URL map for a YouTube Video. 488 | * @access private 489 | * @param string $data Info-File contents for a YouTube Video. 490 | * @return mixed Returns an array, containg the Video URL map, 491 | * or (boolean) FALSE if extracting failed. 492 | */ 493 | private function get_url_map($data) 494 | { 495 | preg_match('/stream_map=(.[^&]*?)&/i',$data,$match); 496 | if(!isset($match[1])) { 497 | return FALSE; 498 | } 499 | else { 500 | $fmt_url = urldecode($match[1]); 501 | if(preg_match('/^(.*?)\\\\u0026/',$fmt_url,$match2)) { 502 | $fmt_url = $match2[1]; 503 | } 504 | 505 | $urls = explode(',',$fmt_url); 506 | $tmp = array(); 507 | 508 | foreach($urls as $url) { 509 | if(preg_match('/itag=([0-9]+)&url=(.*?)&.*?/si',$url,$um)) 510 | { 511 | $u = urldecode($um[2]); 512 | $tmp[$um[1]] = $u; 513 | } 514 | } 515 | 516 | $formats = array( 517 | '13' => array('3gp', '240p', '10'), 518 | '17' => array('3gp', '240p', '9'), 519 | '36' => array('3gp', '320p', '8'), 520 | '5' => array('flv', '240p', '7'), 521 | '6' => array('flv', '240p', '6'), 522 | '34' => array('flv', '320p', '5'), 523 | '35' => array('flv', '480p', '4'), 524 | '18' => array('mp4', '480p', '3'), 525 | '22' => array('mp4', '720p', '2'), 526 | '37' => array('mp4', '1080p', '1') 527 | ); 528 | 529 | foreach ($formats as $format => $meta) { 530 | if (isset($tmp[$format])) { 531 | $videos[] = array('pref' => $meta[2], 'ext' => $meta[0], 'type' => $meta[1], 'url' => $tmp[$format]); 532 | } 533 | } 534 | return $videos; 535 | } 536 | } 537 | 538 | /** 539 | * Get the preview image for a YouTube Video. 540 | * @access private 541 | * @param string $id Valid YouTube Video-ID. 542 | * @return mixed Returns the image URL as string, 543 | * or (boolean) FALSE if no image found. 544 | */ 545 | private function check_thumbs($id) 546 | { 547 | $thumbsize = self::get_thumb_size(); 548 | $thumb_uri = sprintf($this->YT_THUMB_URL, $id, $thumbsize); 549 | 550 | if(self::curl_httpstatus($thumb_uri) == 200) { 551 | $th = $thumb_uri; 552 | } 553 | else { 554 | $thumb_uri = sprintf($this->YT_THUMB_ALT, $id, $thumbsize); 555 | 556 | if(self::curl_httpstatus($thumb_uri) == 200) { 557 | $th = $thumb_uri; 558 | } 559 | else { $th = FALSE; } 560 | } 561 | self::set_video_thumb($th); 562 | } 563 | 564 | /** 565 | * Get the YouTube Video Title and format it. 566 | * @access private 567 | * @param string $str Input string. 568 | * @return string Returns cleaned input string. 569 | */ 570 | private function formattedVideoTitle($str) 571 | { 572 | preg_match_all('#title=(.*?)$#si', urldecode($str), $matches); 573 | 574 | $title = explode("&", $matches[1][0]); 575 | $title = $title[0]; 576 | $title = htmlentities(utf8_decode($title)); 577 | 578 | return self::canonicalize($title); 579 | } 580 | 581 | /** 582 | * Format the YouTube Video Title into a valid filename. 583 | * @access private 584 | * @param string $str Input string. 585 | * @return string Returns cleaned input string. 586 | */ 587 | private function canonicalize($str) 588 | { 589 | $str = trim($str); # Strip unnecessary characters from the beginning and the end of string. 590 | $str = str_replace(""", "", $str); # Strip quotes. 591 | $str = self::strynonym($str); # Replace special character vowels by their equivalent ASCII letter. 592 | $str = preg_replace("/[[:blank:]]+/", "_", $str); # Replace all blanks by an underscore. 593 | $str = preg_replace('/[^\x9\xA\xD\x20-\x7F]/', '', $str); # Strip everything what is not valid ASCII. 594 | $str = preg_replace('/[^\w\d_-]/si', '', $str); # Strip everything what is not a word, a number, "_", or "-". 595 | $str = str_replace('__', '_', $str); # Fix duplicated underscores. 596 | $str = str_replace('--', '-', $str); # Fix duplicated minus signs. 597 | if(substr($str, -1) == "_" OR substr($str, -1) == "-") { 598 | $str = substr($str, 0, -1); # Remove last character, if it's an underscore, or minus sign. 599 | } 600 | return trim($str); 601 | } 602 | 603 | /** 604 | * Replace common special entity codes for special character 605 | * vowels by their equivalent ASCII letter. 606 | * @access private 607 | * @param string $str Input string. 608 | * @return string Returns cleaned input string. 609 | */ 610 | private function strynonym($str) 611 | { 612 | $SpecialVowels = array( 613 | 'À'=>'A', 'à'=>'a', 'È'=>'E', 'è'=>'e', 'Ì'=>'I', 'ì'=>'i', 'Ò'=>'O', 'ò'=>'o', 'Ù'=>'U', 'ù'=>'u', 614 | 'Á'=>'A', 'á'=>'a', 'É'=>'E', 'é'=>'e', 'Í'=>'I', 'í'=>'i', 'Ó'=>'O', 'ó'=>'o', 'Ú'=>'U', 'ú'=>'u', 'Ý'=>'Y', 'ý'=>'y', 615 | 'Â'=>'A', 'â'=>'a', 'Ê'=>'E', 'ê'=>'e', 'Î'=>'I', 'î'=>'i', 'Ô'=>'O', 'ô'=>'o', 'Û'=>'U', 'û'=>'u', 616 | 'Ã'=>'A', 'ã'=>'a', 'Ñ'=>'N', 'ñ'=>'n', 'Õ'=>'O', 'õ'=>'o', 617 | 'Ä'=>'Ae', 'ä'=>'ae', 'Ë'=>'E', 'ë'=>'e', 'Ï'=>'I', 'ï'=>'i', 'Ö'=>'Oe', 'ö'=>'oe', 'Ü'=>'Ue', 'ü'=>'ue', 'Ÿ'=>'Y', 'ÿ'=>'y', 618 | 'Å'=>'A', 'å'=>'a', 'Æ'=>'Ae', 'æ'=>'ae', 'Ç'=>'C', 'ç'=>'c', 'Œ'=>'OE', 'œ'=>'oe', 'ß'=>'ss', 'Ø'=>'O', 'ø'=>'o' 619 | ); 620 | return strtr($str, $SpecialVowels); 621 | } 622 | 623 | /** 624 | * Check if given directory exists. If not, try to create it. 625 | * @access private 626 | * @param string $dir Path to the directory. 627 | * @return boolean Returns (boolean) TRUE if directory exists, 628 | * or was created, or FALSE if creating non-existing 629 | * directory failed. 630 | */ 631 | private function valid_dir($dir) 632 | { 633 | if(is_dir($dir) !== FALSE) { 634 | chmod($dir, 0777); # Ensure permissions. Otherwise CURLOPT_FILE will fail! 635 | return TRUE; 636 | } 637 | else { 638 | return (bool) ! @mkdir($dir, 0777); 639 | } 640 | } 641 | 642 | /** 643 | * Check on the command line if we can find an Ffmpeg installation on the script host. 644 | * @access private 645 | * @return boolean Returns (boolean) TRUE if Ffmpeg is installed on the server, 646 | * or FALSE if not. 647 | */ 648 | private function has_ffmpeg() 649 | { 650 | $sh = `which ffmpeg`; 651 | return (bool) (strlen(trim($sh)) > 0); 652 | } 653 | 654 | /** 655 | * HTTP HEAD request with curl. 656 | * @access private 657 | * @param string $url String, containing the URL to curl. 658 | * @return intval Returns a HTTP status code. 659 | */ 660 | private function curl_httpstatus($url) 661 | { 662 | $ch = curl_init($url); 663 | curl_setopt($ch, CURLOPT_USERAGENT, $this->CURL_UA); 664 | curl_setopt($ch, CURLOPT_REFERER, $this->YT_BASE_URL); 665 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 666 | curl_setopt($ch, CURLOPT_NOBODY, 1); 667 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); 668 | $str = curl_exec($ch); 669 | $int = curl_getinfo($ch, CURLINFO_HTTP_CODE); 670 | curl_close($ch); 671 | return intval($int); 672 | } 673 | 674 | /** 675 | * HTTP GET request with curl. 676 | * @access private 677 | * @param string $url String, containing the URL to curl. 678 | * @return string Returns string, containing the curl result. 679 | */ 680 | private function curl_get($url) 681 | { 682 | $ch = curl_init($url); 683 | curl_setopt($ch, CURLOPT_USERAGENT, $this->CURL_UA); 684 | curl_setopt($ch, CURLOPT_REFERER, $this->YT_BASE_URL); 685 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 686 | $contents = curl_exec ($ch); 687 | curl_close ($ch); 688 | return $contents; 689 | } 690 | 691 | /** 692 | * HTTP GET request with curl that writes the curl result into a local file. 693 | * @access private 694 | * @param string $remote_file String, containing the remote file URL to curl. 695 | * @param string $local_file String, containing the path to the file to save 696 | * the curl result in to. 697 | * @return void 698 | */ 699 | private function curl_get_file($remote_file, $local_file) 700 | { 701 | $ch = curl_init($remote_file); 702 | curl_setopt($ch, CURLOPT_USERAGENT, $this->CURL_UA); 703 | curl_setopt($ch, CURLOPT_REFERER, $this->YT_BASE_URL); 704 | $fp = fopen($local_file, 'w'); 705 | curl_setopt($ch, CURLOPT_FILE, $fp); 706 | curl_exec ($ch); 707 | curl_close ($ch); 708 | fclose($fp); 709 | } 710 | 711 | // Getter and Setter for the downloaded audio file. 712 | public function get_audio() { 713 | return $this->audio; } 714 | private function set_audio($audio) { 715 | $this->audio = $audio; } 716 | 717 | // Getter and Setter for the downloaded video file. 718 | public function get_video() { 719 | return $this->video; } 720 | private function set_video($video) { 721 | $this->video = $video; } 722 | 723 | // Getter and Setter for the downloaded video preview image. 724 | public function get_thumb() { 725 | return $this->thumb; } 726 | private function set_thumb($img) { 727 | if(is_string($img)) { 728 | $this->thumb = $img; 729 | } else { 730 | throw new Exception("Invalid thumbnail given: $img"); } 731 | } 732 | 733 | // Getter and Setter whether to download the video, or convert to audio by default. 734 | public function get_default_download() { 735 | return $this->defaultDownload; } 736 | public function set_default_download($action) { 737 | if($action == "audio" || $action == "video") { 738 | $this->defaultDownload = $action; 739 | } else { 740 | throw new Exception("Invalid download type. Must be either 'audio', or 'video'."); } 741 | } 742 | 743 | // Getter and Setter for the video quality. 744 | public function get_video_quality() { 745 | return $this->videoQuality; } 746 | public function set_video_quality($q) { 747 | if(in_array($q, array(0,1))) { 748 | $this->videoQuality = $q; 749 | } else { 750 | throw new Exception("Invalid video quality."); } 751 | } 752 | 753 | // Getter and Setter for the audio quality. 754 | public function get_audio_quality() { 755 | return $this->audioQuality; } 756 | public function set_audio_quality($q) { 757 | if($q >= 128 && $q <= 320) { 758 | $this->audioQuality = $q; 759 | } else { 760 | throw new Exception("Audio sample rate must be between 128 and 320."); } 761 | } 762 | 763 | // Getter and Setter for the audio output filetype. 764 | public function get_audio_format() { 765 | return $this->audioFormat; } 766 | public function set_audio_format($ext) { 767 | $valid_exts = array("mp3", "wav", "ogg", "mp4"); 768 | if(in_array($ext, $valid_exts)) { 769 | $this->audioFormat = $ext; 770 | } else { 771 | throw new Exception("Invalid audio filetype '$ext' defined. 772 | Valid filetypes are: " . implode(", ", $valid_exts) ); } 773 | } 774 | 775 | // Getter and Setter for the download directory. 776 | public function get_downloads_dir() { 777 | return $this->downloadsDir; } 778 | public function set_downloads_dir($dir) { 779 | if(self::valid_dir($dir) !== FALSE) { 780 | $this->downloadsDir = $dir; 781 | } else { 782 | throw new Exception("Can neither find, nor create download folder: $dir"); } 783 | } 784 | 785 | // Getter and Setter whether to log Ffmpeg processes. 786 | public function get_ffmpegLogs_active() { 787 | return $this->FfmpegLogsDir; } 788 | public function set_ffmpegLogs_active($b) { 789 | $this->FfmpegLogsActive = (bool) ($b !== FALSE); } 790 | 791 | // Getter and Setter for the YouTube URL map. 792 | public function get_yt_url_map() { 793 | return $this->downloadsArray; } 794 | private function set_yt_url_map($videos) { 795 | $this->downloadsArray = $videos; } 796 | 797 | // Getter and Setter for the YouTube Video-ID. 798 | public function get_video_id() { 799 | return $this->videoID; } 800 | public function set_video_id($id) { 801 | if(strlen($id) == 11) { 802 | $this->videoID = $id; 803 | } else { 804 | throw new Exception("$id is not a valid Youtube Video ID."); } 805 | } 806 | 807 | // Getter and Setter for the formatted video title. 808 | public function get_video_title() { 809 | return $this->videoTitle; } 810 | public function set_video_title($str) { 811 | if(is_string($str)) { 812 | $this->videoTitle = $str; 813 | } else { 814 | throw new Exception("Invalid title given: $str"); } 815 | } 816 | 817 | // Getter and Setter for thumbnail preferences. 818 | public function get_download_thumbnail() { 819 | return $this->downloadThumbs; } 820 | public function set_download_thumbnail($q) { 821 | if($q == TRUE || $q == FALSE) { 822 | $this->downloadThumbs = (bool) $q; 823 | } else { 824 | throw new Exception("Invalid argument given to set_download_thumbnail."); } 825 | } 826 | 827 | // Getter and Setter for the video preview image size. 828 | public function get_thumb_size() { 829 | return $this->videoThumbSize; } 830 | public function set_thumb_size($s) { 831 | if($s == "s") { 832 | $this->videoThumbSize = "default"; } 833 | else if($s == "l") { 834 | $this->videoThumbSize = "hqdefault"; } 835 | else { 836 | throw new Exception("Invalid thumbnail size specified."); } 837 | } 838 | 839 | // Getter and Setter for the object's Ffmpeg log file. 840 | public function get_ffmpeg_Logfile() { 841 | return $this->ffmpegLogfile; } 842 | private function set_ffmpeg_Logfile($str) { 843 | $this->ffmpegLogfile = $str; } 844 | 845 | 846 | // Getter and Setter for the remote video preview image. 847 | public function get_video_thumb() { 848 | return $this->videoThumb; } 849 | private function set_video_thumb($img) { 850 | $this->videoThumb = $img; } 851 | 852 | // Getter and Setter for the Ffmpeg-Logs directory. 853 | public function get_ffmpegLogs_dir() { 854 | return $this->FfmpegLogsDir; } 855 | public function set_ffmpegLogs_dir($dir) { 856 | if(self::valid_dir($dir) !== FALSE) { 857 | $this->FfmpegLogsDir = $dir; 858 | } else { 859 | throw new Exception("Can neither find, nor create ffmpeg log directory '$dir', but logging is enabled."); } 860 | } 861 | 862 | /** 863 | * Format file size in bytes into human-readable string. 864 | * @access public 865 | * @param string $bytes Filesize in bytes. 866 | * @return string Returns human-readable formatted filesize. 867 | */ 868 | public function human_bytes($bytes) 869 | { 870 | $fsize = $bytes; 871 | switch ($bytes): 872 | case $bytes < 1024: 873 | $fsize = $bytes .' B'; break; 874 | case $bytes < 1048576: 875 | $fsize = round($bytes / 1024, 2) .' KiB'; break; 876 | case $bytes < 1073741824: 877 | $fsize = round($bytes / 1048576, 2) . ' MiB'; break; 878 | case $bytes < 1099511627776: 879 | $fsize = round($bytes / 1073741824, 2) . ' GiB'; break; 880 | endswitch; 881 | return $fsize; 882 | } 883 | } 884 | -------------------------------------------------------------------------------- /youtube-dl.config.php: -------------------------------------------------------------------------------- 1 | 7 | * @updated 2012/05/30 8 | */ 9 | 10 | interface cnfg 11 | { 12 | /** 13 | * Set the directory (relative path to class file) 14 | * where to save the downloads to. 15 | */ 16 | const Download_Folder = 'videos/'; 17 | 18 | /** 19 | * Set the default download type. 20 | * Choose one of 'video', or 'audio'. 21 | * 22 | * Defines whether to download the video files, or to extract 23 | * and convert the soundtrack into an audio file by default. 24 | * 25 | * NOTE: To extract the soundtrack from the video file and convert 26 | * it into an audio file, you must have Ffmpeg and, depending on 27 | * the video filetype, additional media libraries installed on the 28 | * server that hosts the scripts! 29 | */ 30 | const Default_Download = "video"; 31 | 32 | /** 33 | * Set the default video output quality. 34 | * Choose integer '1' (One) to download videos in the best quality available, 35 | * or integer '0' (Zero) for the lowest quality (,thus smallest file size). 36 | */ 37 | const Default_Videoquality = 1; 38 | 39 | /** 40 | * Set the default audio quality (sample rate in kbits). 41 | * Choose any integer value between 128 (low quality) and 320 (CD quality). 42 | * 43 | * Note: Max. output quality depends on the video input file. Thus, the 44 | * converted mp3 output file may be worse, than expected from the value set. 45 | */ 46 | const Default_Audioquality = 320; 47 | 48 | /** 49 | * Set the default audio output filetype. 50 | * Choose one of "mp3", "wav", "ogg", or "mp4". 51 | */ 52 | const Default_Audioformat = "mp3"; 53 | 54 | /** 55 | * Set the video preview image preference. 56 | * Choose '1' (nummeric One) to download a preview image for the video, 57 | * or '0' (nummeric Zero) to download only the video itself. 58 | */ 59 | const Download_Thumbnail = TRUE; 60 | 61 | /** 62 | * Set the video preview image size. 63 | * Choose 'l' (small letter "L") for a size of 480*360px, 64 | * or 's' for a size of 120*90px. 65 | */ 66 | const Default_Thumbsize = 'l'; 67 | 68 | /** 69 | * Set the directory (absolute path, trailing slash!) 70 | * where to save Ffmpeg log files to. 71 | */ 72 | const Ffmpeg_LogsActive = FALSE; 73 | 74 | /** 75 | * Set the directory (absolute path, trailing slash!) 76 | * where to save Ffmpeg log files to. 77 | */ 78 | const Ffmpeg_LogsDir = 'logs/'; 79 | } 80 | --------------------------------------------------------------------------------