PageRenderTime 59ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/youtube-dl.class.php

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