Created
June 15, 2020 08:16
-
-
Save firecentaur/a6b1b56f41030a484165173a2c0864d3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Extensions; | |
class YoutubeHelper | |
{ | |
public $youtube; | |
public $client; | |
public $appName = 'globworker'; | |
public $apiKey; | |
public function __construct($apiKey) | |
{ | |
$this->apiKey = $apiKey; | |
$this->client = new Google_Client(); | |
$this->client->setDeveloperKey($this->apiKey); | |
$this->client->setApplicationName($this->appName); | |
$this->youtube = new Google_Service_YouTube($this->client); | |
} | |
public function getYouTubeIdByUrl($url) | |
{ | |
$video_id = null; | |
if (preg_match('%(?:youtube(?:-nocookie)?\.com/(?:[^/]+/.+/|(?:v|e(?:mbed)?)/|.*[?&]v=)|youtu\.be/)([^"&?/ ]{11})%i', $url, $match)) { | |
$video_id = $match[1]; | |
} | |
return $video_id; | |
} | |
public function getYouTubeChannelIdByUrl($url) | |
{ | |
$video_id = null; | |
$str = '/((http|https):\/\/)?(www\.|)youtube\.com\/channel\/([a-zA-Z0-9_\-]{1,})/'; | |
if (strpos($url,'user')){ | |
return 'user'; | |
}else{ | |
if (preg_match($str, $url, $match)) { | |
$video_id = $match[4]; | |
} | |
} | |
return $video_id; | |
} | |
public function getYouTubeChannelIdByUserUrl($url) | |
{ | |
$userId = null; | |
$str = '/(?:http(?:s)?:\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:(?:watch)?\?(?:.*&)?v(?:i)?=|(?:embed|v|vi|user)\/))([^\?&\"\'<> #]+)/'; | |
if (preg_match($str, $url, $match)) { | |
$userId = $match[1]; | |
} | |
return $userId; | |
} | |
public function getVideoViews($vid = null) | |
{ | |
if (!$vid || !isset($this->apiKey) || !$this->apiKey || empty($this->apiKey)) return false; | |
$parts = 'statistics'; | |
$json_data = json_decode(YoutubeHelper::videos($vid, $parts), true); | |
if (!isset($json_data['items'][0]['statistics']['viewCount']) || empty($json_data['items'][0]['statistics']['viewCount'])) { | |
return false; | |
} | |
return $json_data['items'][0]['statistics']['viewCount']; | |
} | |
static public function getDuration($youTubeId) | |
{ | |
$parts = 'contentDetails'; | |
$arr = json_decode(YoutubeHelper::videos($youTubeId, $parts)); | |
if (isset($arr['items'])) { | |
if (count($arr['items']) > 0) { | |
if (isset($arr['items'][0]['contentDetails']['duration'])) { | |
return YoutubeHelper::convertISO8600ToSeconds($arr['items'][0]['contentDetails']['duration']); | |
} | |
} | |
} else { | |
return false; | |
} | |
} | |
public function ISO8601ToSeconds($ISO8601) | |
{ | |
$interval = new \DateInterval($ISO8601); | |
return ($interval->d * 24 * 60 * 60) + | |
($interval->h * 60 * 60) + | |
($interval->i * 60) + | |
$interval->s; | |
} | |
/** | |
* Searches YouTube | |
* @param $query - the query movie name, actor etc | |
* @param null $relatedToVideoId - the YouTube Id for which we want the results to be related to | |
* @param null $channelId - YouTube channelId - if set, only searches channel | |
* @param int $maxResults - default is 10 | |
* @param string $order - rating, date, relevance, title, videoCount, viewCount | |
* @param null $pageToken - nextPage, previousPage tokens are in API response | |
* @param null $publishedAfter - datetime | |
* @param null $publishedBefore - datetime | |
* @return bool|mixed | |
*/ | |
public function searchYouTube($query, $relatedToVideoId = null, $channelId = null, $maxResults = 10, $order = 'rating', $pageToken = null, $publishedAfter = null, $publishedBefore = null) | |
{ | |
ini_set("allow_url_fopen", true); | |
if (strlen($query) === 0 || !isset($this->apiKey) || !$this->apiKey || strlen($this->apiKey) === 0) return false; | |
$query = urlencode($query); | |
$parts = 'snippet'; | |
$queryString = "https://www.googleapis.com/youtube/v3/search"; | |
$queryString .= "?q=$query"; | |
$queryString .= "&part=$parts"; | |
$queryString .= "&key={$this->apiKey}"; | |
$queryString .= "&maxResults=$maxResults"; | |
$queryString .= "&order=$order"; | |
$queryString .= "&type=video"; | |
if ($publishedAfter !== null) { | |
$queryString .= "&publishedAfter=$publishedAfter"; | |
} | |
if ($publishedBefore !== null) { | |
$queryString .= "&publishedBefore=$publishedBefore"; | |
} | |
if ($relatedToVideoId !== null) { | |
$queryString .= "&relatedToVideoId=$relatedToVideoId&type=video"; | |
} | |
if ($channelId !== null) { | |
$queryString .= "&channelId=$channelId&type=video"; | |
} | |
if ($pageToken !== null) { | |
$queryString .= "&pageToken=$pageToken"; | |
} | |
//check cache | |
$jsonResponse = @file_get_contents($queryString); | |
if ($jsonResponse === false) { | |
echo 'error'; | |
return false; | |
} | |
$json_data = json_decode($jsonResponse); | |
return $json_data; | |
} | |
static public function convertISO8600ToSeconds($iso8600String) | |
{ | |
$convertedISO = new DateInterval($iso8600String); | |
$formated = $convertedISO->format("%H,%I,%S"); | |
$timeArr = str_getcsv($formated); | |
$hours = $timeArr[0]; | |
$minutes = $timeArr[1]; | |
$seconds = $timeArr[2]; | |
$totalSeconds = $hours * 60 * 60 + $minutes * 60 + $seconds; | |
return $totalSeconds; | |
} | |
/** | |
* get video status will query the google data api to determine if this video | |
* is no longer accessible. | |
* | |
* It will no longer be accessible to MEP if any of the following conditions have been set: | |
* 1) it has been rejected by YouTube - this can happen for a number of reasons - copywrite etc | |
* if it has been rejected, the "rejectionReason" attribute will be set | |
* 2) its status has been set to private - in this case the video will not be displayed to mep | |
* 3) the video is no longer embeddable | |
* 4) its upload status is not "processed" | |
* | |
* @see https://developers.google.com/youtube/v3/docs/videos | |
* @param null $youTubeId | |
* @return bool|string | |
*/ | |
public function getVideoStatus($youTubeId = null) | |
{ | |
return 'success'; | |
if (!$youTubeId || !isset($this->apiKey) || !$this->apiKey || empty($this->apiKey)) return false; | |
$parts = 'status,contentDetails'; | |
$json_data = json_decode(YoutubeHelper::videos($youTubeId, $parts)); | |
if (empty($json_data['items']) || | |
isset($json_data['items'][0]['status']['rejectionReason']) || | |
$json_data['items'][0]['status']['uploadStatus'] === 'fail' || | |
$json_data['items'][0]['status']['uploadStatus'] === 'deleted' || | |
$json_data['items'][0]['status']['uploadStatus'] === 'rejected' || | |
$json_data['items'][0]['status']['embeddable'] === false || | |
$json_data['items'][0]['status']['privacyStatus'] === 'private' || count($json_data['items']) === 0) { | |
return 'fail'; | |
} else { | |
return 'success'; | |
} | |
} | |
public function getRawStatus($youTubeId = null) | |
{ | |
if (!$$youTubeId || !isset($this->apiKey) || !$this->apiKey || empty($this->apiKey)) return false; | |
$parts = 'status,contentDetails'; | |
return json_decode(YoutubeHelper::videos($youTubeId, $parts)); | |
} | |
/** | |
* @see https://developers.google.com/youtube/v3/docs/ | |
* @param null $id | |
* @return bool|mixed | |
*/ | |
public function getYouTubeData($youTubeId = null) | |
{ | |
$parts = 'status'; | |
return json_decode(YoutubeHelper::videos($youTubeId, $parts)); | |
} | |
/** | |
* @see https://developers.google.com/youtube/v3/docs/channels/list | |
* @param $type | |
* @param $channelOrUserInfo | |
* @param $parts | |
* @param $maxResults | |
* @return bool|false|string | |
*/ | |
static public function listChannels($type, $channelOrUserInfo, $parts, $maxResults) | |
{ | |
$ytApiKey = YouTubeHelper::apiKey; | |
/** | |
* order info: @see https://developers.google.com/youtube/v3/docs/search/list | |
*/ | |
//"id" => $ytChannelUrl | |
$queryString = "https://www.googleapis.com/youtube/v3/channels"; | |
$queryString .= "?part=$parts"; | |
if ($type === 'channel') { | |
$queryString .= "&id=$channelOrUserInfo"; | |
} else | |
if ($type === 'user') { | |
$queryString .= "&forUsername=$channelOrUserInfo"; | |
} | |
$queryString .= "&key={$ytApiKey}"; | |
$queryString .= "&maxResults=$maxResults"; | |
//check cache | |
//https://www.googleapis.com/youtube/v3/channels?part=snippet%2CcontentDetails%2Cstatistics&id=UCqFzWxSCi39LnW1JKFR3efg | |
$jsonResponse = @file_get_contents($queryString); | |
if ($jsonResponse === false) { | |
echo 'error'; | |
return false; | |
} | |
return $jsonResponse; | |
} | |
/** | |
* @see https://developers.google.com/youtube/v3/docs/videos | |
* @param $type | |
* @param $channelOrUserInfo | |
* @param $parts | |
* @param $maxResults | |
* @return bool|false|string | |
*/ | |
static public function videos($vid, $parts) | |
{ | |
$ytApiKey = YouTubeHelper::apiKey; | |
/** | |
* order info: @see https://developers.google.com/youtube/v3/docs/search/list | |
*/ | |
//"id" => $ytChannelUrl | |
$queryString = "https://www.googleapis.com/youtube/v3/videos"; | |
//$queryString =?part=$parts&id={$id}&key={$this->apiKey}"; | |
$queryString .= "?part=$parts"; | |
$queryString .= "&key={$ytApiKey}"; | |
$queryString .= "&id=$vid"; | |
//check cache | |
$jsonResponse = @file_get_contents($queryString); | |
if ($jsonResponse === false) { | |
echo 'error'; | |
return false; | |
} | |
return $jsonResponse; | |
} | |
public function channelInfo($channelId,$parts,$userId = null) | |
{ | |
$ytApiKey = $this->apiKey; | |
$queryString = "https://www.googleapis.com/youtube/v3/channels/"; | |
$queryString .= "?part=$parts"; | |
$queryString .= "&key={$ytApiKey}"; | |
if ($userId!==null){ | |
$queryString .= "&forUsername=$userId"; | |
}else{ | |
$queryString .= "&id=$channelId"; | |
} | |
//check cache | |
$jsonResponse = @file_get_contents($queryString); | |
if ($jsonResponse === false) { | |
echo 'error'; | |
return false; | |
} | |
return $jsonResponse; | |
} | |
/** | |
* @see https://developers.google.com/youtube/v3/docs/playlistItems/list | |
* @param $playlistId | |
* @param $parts | |
* @param $maxResults | |
* @return bool|false|string | |
*/ | |
public function listPlaylistItems($playlistId, $parts, $maxResults, $pageToken = null) | |
{ | |
$ytApiKey = $this->apiKey; | |
/** | |
* order info: @see https://developers.google.com/youtube/v3/docs/playlistItems/list | |
*/ | |
//"id" => $ytChannelUrl | |
$queryString = "https://www.googleapis.com/youtube/v3/playlistItems"; | |
$queryString .= "?part=$parts"; | |
$queryString .= "&playlistId=$playlistId"; | |
if ($pageToken !== null) { | |
$queryString .= "&pageToken=$pageToken"; | |
} | |
$queryString .= "&key={$ytApiKey}"; | |
$queryString .= "&maxResults=$maxResults"; | |
//check cache | |
$jsonResponse = @file_get_contents($queryString); | |
if ($jsonResponse === false) { | |
echo 'error'; | |
return false; | |
} | |
return $jsonResponse; | |
} | |
public static function getVideoIdFromUrl($url) | |
{ | |
$parsedUrl = parse_url($url); | |
parse_str($parsedUrl['query'], $queryParams); | |
if (isset($queryParams['v'])) { | |
$videoId = $queryParams['v']; | |
} else { | |
$videoId = false; | |
} | |
return $videoId; | |
} | |
public static function getSubs($videoUrl, $tryNonAsr = true, $fromType = "xml", $lang = 'en') | |
{ | |
$videoConfig = self::getVideoConfig($videoUrl); | |
if (!isset($videoConfig['args'])) { | |
return false; | |
} | |
if (isset($videoConfig['args']['caption_translation_languages'])) { | |
$tracks = $videoConfig['args']['caption_tracks']; | |
parse_str($tracks, $tracksParams); | |
$captionUrl = $tracksParams['u']; | |
$captionListParams = array( | |
'type' => 'list' | |
); | |
$captionListUrl = $captionUrl . "&" . http_build_query($captionListParams); | |
$captionList = self::getUrlContent($captionListUrl); | |
$captionList = self::parseXml($captionList); | |
$isTranslated = self::isCaptionsTranslated($captionList, $lang); | |
} else if (isset($videoConfig['args']['player_response'])) { | |
$videoConfigDecode = json_decode($videoConfig['args']['player_response'], true); | |
if (!isset ($videoConfigDecode['captions']) || !isset ($videoConfigDecode['captions']['playerCaptionsTracklistRenderer']) || !isset($videoConfigDecode['captions']['playerCaptionsTracklistRenderer']['captionTracks'])) { | |
return false; | |
} | |
$captionTrackLength = sizeof($videoConfigDecode['captions']['playerCaptionsTracklistRenderer']['captionTracks']); | |
$tracks = $videoConfigDecode['captions']['playerCaptionsTracklistRenderer']['captionTracks'][0]; | |
if ($captionTrackLength > 1) { | |
for ($i = 0; $i < $captionTrackLength; $i++) { | |
if ($videoConfigDecode['captions']['playerCaptionsTracklistRenderer']['captionTracks'][$i]['vssId'] === '.' . $lang) { | |
$tracks = $videoConfigDecode['captions']['playerCaptionsTracklistRenderer']['captionTracks'][$i]; | |
} | |
} | |
} | |
$tracksParams = $tracks; | |
$captionUrl = $tracksParams['baseUrl']; | |
$captionListParams = array( | |
'type' => 'list' | |
); | |
$captionListUrl = $captionUrl . "&" . http_build_query($captionListParams); | |
$captionList = self::getUrlContent($captionListUrl); | |
$captionList = self::parseXml($captionList); | |
$isTranslated = self::isCaptionsTranslated($captionList, $lang); | |
} | |
if ($tryNonAsr == false) { | |
$isAsr = self::isCaptionsAsr($captionList, $lang); | |
} else { | |
$isAsr = false; | |
} | |
$captionParams = array(); | |
if ($isAsr) { | |
$captionParams['kind'] = 'asr'; | |
} | |
if ($isTranslated) { | |
$captionParams['tlang'] = $lang; | |
} else { | |
$captionParams['lang'] = $lang; | |
} | |
if ($fromType == 'srv3') { | |
$captionParams['fmt'] = 'srv3'; | |
} elseif ($fromType == 'vtt') { | |
$captionParams['fmt'] = 'vtt'; | |
} | |
$url = parse_url($captionUrl); | |
$query = array(); | |
parse_str($url['query'], $query); | |
$query = array_replace($query, $captionParams); | |
$captionQuery = http_build_query($query); | |
$captionUrl = $url['scheme'] . '://' . $url['host'] . $url['path'] . '?' . $captionQuery; | |
$captions = self::getUrlContent($captionUrl); | |
if (empty($captions)) { | |
return false; | |
} | |
if ($fromType == 'srv3') { | |
$captions = self::parseXml($captions); | |
$captionsSrt = self::convertSrv3ToSrt($captions); | |
} elseif ($fromType == 'vtt') { | |
$captionsSrt = self::convertVttToSrt($captions); | |
} elseif ($fromType == 'xml') { | |
$captions = self::parseXml($captions); | |
if ($captions) { | |
$captionsSrt = self::convertXmlToSrt($captions); | |
} else { | |
return false; | |
} | |
} | |
return $captionsSrt; | |
} | |
public static function isCaptionsTranslated($captionList, $lang) | |
{ | |
$isTranslated = true; | |
foreach ($captionList->track as $track) { | |
$trackLang = (string)$track->attributes()->lang_code; | |
if ($trackLang === $lang) { | |
$isTranslated = false; | |
break; | |
} | |
} | |
return $isTranslated; | |
} | |
public static function isCaptionsAsr($captionList, $lang) | |
{ | |
$isAsr = false; | |
foreach ($captionList->track as $track) { | |
$trackLang = (string)$track->attributes()->lang_code; | |
$kind = (string)$track->attributes()->kind; | |
if ($trackLang === $lang && !empty($kind) && $kind === 'asr') { | |
$isAsr = true; | |
break; | |
} | |
} | |
return $isAsr; | |
} | |
public static function sanitizeFilename($name) | |
{ | |
$str = $name; | |
$str = strip_tags($str); | |
$str = preg_replace('/[\r\n\t ]+/', ' ', $str); | |
$str = preg_replace('/[\"\*\/\:\<\>\?\'\|]+/', ' ', $str); | |
$str = strtolower($str); | |
$str = html_entity_decode($str, ENT_QUOTES, "utf-8"); | |
$str = htmlentities($str, ENT_QUOTES, "utf-8"); | |
$str = preg_replace("/(&)([a-z])([a-z]+;)/i", '$2', $str); | |
$str = str_replace(' ', '-', $str); | |
$str = rawurlencode($str); | |
$str = str_replace('%', '-', $str); | |
return $str; | |
} | |
public static function escapeHtmlSymbols($text) | |
{ | |
return preg_replace_callback( | |
"/(&#[0-9]+;)/", | |
function ($m) { | |
return mb_convert_encoding($m[1], "UTF-8", "HTML-ENTITIES"); | |
}, | |
$text); | |
} | |
public function readline() | |
{ | |
return rtrim(fgets(STDIN)); | |
} | |
protected static function convertSrv3ToSrt($xml) | |
{ | |
$captions = $xml->body; | |
$result = ""; | |
$i = 0; | |
foreach ($captions->p as $p) { | |
$t = $p->attributes()->t; | |
$d = $p->attributes()->d; | |
if (is_null($p->s) || !count($p->s)) { | |
continue; | |
} | |
$caption = ""; | |
foreach ($p->s as $s) { | |
$caption .= (string)$s . " "; | |
} | |
$start = number_format(($t) / 1000.0, 3); | |
$end = number_format(($t + $d) / 1000.0, 3); | |
$start = self::convertSecondsToSrtTime($start); | |
$end = self::convertSecondsToSrtTime($end); | |
$result .= ++$i . PHP_EOL; | |
$result .= $start . " --> " . $end . PHP_EOL; | |
$result .= $caption . PHP_EOL . PHP_EOL; | |
} | |
return $result; | |
} | |
protected static function convertXmlToSrt($xml) | |
{ | |
$captions = $xml; | |
$result = ""; | |
$i = 0; | |
foreach ($captions->text as $text) { | |
$start = (string)$text->attributes()->start; | |
$duration = (string)$text->attributes()->dur; | |
$end = (float)$start + (float)$duration; | |
$start = self::convertSecondsToSrtTime($start); | |
$end = self::convertSecondsToSrtTime($end); | |
$text = self::escapeHtmlSymbols($text); | |
if (trim($text) !== "") { | |
// check here if the text is empty when creating the srt file. If the text is empty, discard the subtitle | |
$result .= ++$i . PHP_EOL; | |
$result .= $start . " --> " . $end . PHP_EOL; | |
$result .= (string)$text . PHP_EOL . PHP_EOL; | |
} | |
} | |
return $result; | |
} | |
protected static function convertSecondsToSrtTime($value) | |
{ | |
$originalTimezone = date_default_timezone_get(); | |
date_default_timezone_set("GMT"); | |
$valueDecimal = explode('.', $value); | |
if (isset($valueDecimal[1])) { | |
$decimal = $valueDecimal[1]; | |
switch (strlen($decimal)) { | |
case 1: | |
$decimal *= 100; | |
break; | |
case 2: | |
$decimal *= 10; | |
break; | |
} | |
$valueDecimal = $decimal; | |
} else { | |
$valueDecimal = 000; | |
} | |
$value = date('H:i:s', $value); | |
$value .= "," . $valueDecimal; | |
date_default_timezone_set($originalTimezone); | |
return $value; | |
} | |
protected static function convertVttToSrt($contents) | |
{ | |
$firstLines = true; | |
$lines = explode("\n", $contents); | |
if (count($lines) === 1) { | |
$lines = explode("\r\n", $contents); | |
if (count($lines) === 1) { | |
$lines = explode("\r", $contents); | |
} | |
} | |
array_shift($lines); // removes the WEBVTT header | |
$output = ''; | |
$i = 0; | |
foreach ($lines as $line) { | |
/* | |
* at last version subtitle numbers are not working | |
* as you can see that way is trustful than older | |
* | |
* | |
* */ | |
$line = preg_replace('/<[^>]*>/i', "", $line); | |
$pattern1 = '#(\d{2}):(\d{2}):(\d{2})\.(\d{3})#'; // '01:52:52.554' | |
$pattern2 = '#(\d{2}):(\d{2})\.(\d{3})#'; // '00:08.301' | |
$m1 = preg_match($pattern1, $line); | |
if (is_numeric($m1) && $m1 > 0) { | |
$firstLines = false; | |
$i++; | |
$output .= $i; | |
$output .= PHP_EOL; | |
$line = preg_replace($pattern1, '$1:$2:$3,$4', $line); | |
} else { | |
$m2 = preg_match($pattern2, $line); | |
if (is_numeric($m2) && $m2 > 0) { | |
$firstLines = false; | |
$i++; | |
$output .= $i; | |
$output .= PHP_EOL; | |
$line = preg_replace($pattern2, '00:$1:$2,$3', $line); | |
} else { | |
if ($firstLines) { | |
continue; | |
} | |
} | |
} | |
// remove with <[^>]*> | |
// remove if end with | |
// align:start position:0% | |
$removePositionPattern = " align:start position:0%"; | |
if (self::stringEndsWith($line, $removePositionPattern)) { | |
$line = substr($line, 0, -strlen($removePositionPattern)); | |
} | |
$line = stripslashes($line); | |
$output .= $line . PHP_EOL; | |
} | |
return $output; | |
} | |
protected static function stringEndsWith($string, $needle) | |
{ | |
$length = strlen($needle); | |
if ($length == 0) { | |
return true; | |
} | |
return (substr($string, -$length) === $needle); | |
} | |
protected static function parseXml($xml) | |
{ | |
$p = xml_parser_create(); | |
xml_parse_into_struct($p, $xml, $values); | |
$parsedXml = new SimpleXMLElement($xml); | |
return $parsedXml; | |
} | |
protected static function getUrlContent($url) | |
{ | |
$ch = curl_init($url); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
$result = curl_exec($ch); | |
curl_close($ch); | |
return $result; | |
} | |
protected static function getVideoConfig($url) | |
{ | |
$ch = curl_init($url); | |
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); | |
$result = curl_exec($ch); | |
curl_close($ch); | |
preg_match('/;ytplayer\.config\s*=\s*({.+?});/', $result, $match); | |
if (isset($match[1])) { | |
$config = json_decode($match[1], true); | |
} else { | |
$config = false; | |
} | |
return $config; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment