Skip to content

Instantly share code, notes, and snippets.

@firecentaur
Created June 15, 2020 08:16
Show Gist options
  • Save firecentaur/a6b1b56f41030a484165173a2c0864d3 to your computer and use it in GitHub Desktop.
Save firecentaur/a6b1b56f41030a484165173a2c0864d3 to your computer and use it in GitHub Desktop.
<?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