Warning: chmod(): Operation not permitted in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8 [0] in function chmod in /var/www/hopeinstoughton_www/wp-content/plugins/simple-universal-google-analytics/main.php on line 8 [1] in function include_once in /var/www/hopeinstoughton_www/wp-settings.php on line 560 [2] in function require_once in /var/www/hopeinstoughton_www/wp-config.php on line 85 [3] in function require_once in /var/www/hopeinstoughton_www/wp-load.php on line 50 [4] in function require_once in /var/www/hopeinstoughton_www/wp-blog-header.php on line 13 [5] in function require in /var/www/hopeinstoughton_www/index.php on line 17
| Server IP : 94.177.8.99 / Your IP : 216.73.217.165 Web Server : Apache/2.4.58 (Ubuntu) System : Linux aries 6.8.0-134-generic #134-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 26 18:43:11 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.1.34 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : OFF Directory : /var/www/hopeinstoughton_www/wp-content/plugins/media-manager/ |
Upload File : |
<?php
class Youtube_Model {
private $debug = 0;
private $url;
public $caption;
private $captionpresent;
public $poster;
public $title;
private $body;
public $debug_output;
// cookies exported using "Get cookies.txt LOCALLY" plugin from firefox
const RUNTIME = ' --js-runtimes node:/var/www/.nvm/versions/node/v24.12.0/bin/node --remote-components ejs:github --cookies /var/www/.cookies.txt ';
// const RUNTIME = ' --js-runtimes node:/var/www/.nvm/versions/node/v24.12.0/bin/node --remote-components ejs:github ';
public function Url() {
if (func_num_args()) {
$this->url = func_get_arg(0);
$this->_downloadPage($this->url);
return $this;
}
return $this->url;
}
private function _downloadPage($url) {
if (class_exists('WP_Http_Curl', FALSE)) { // wordpress
$curl = new WP_Http_Curl;
$args = [
'redirection' => 5,
'user-agent' => '',
'stream' => FALSE,
'filename' => '',
'decompress' => TRUE,
'_redirection' => 5,
];
$response = $curl->request($url, $args);
if (is_a($response, 'WP_Error')) {
var_dump($url);
var_dump($response);
exit;
}
$this->body = $response['body'];
} else { // pkp
$n = new \CurlObject();
$n ->Url($url)
->Redirect(1)
->Execute();
$this->body = $n->Body();
}
$this->Load();
}
public function Debug() {
if (func_num_args()) {
$this->debug = func_get_arg(0);
return $this;
}
return $this->debug;
}
public function hasCaption() {
return !empty($this->caption);
}
public function Caption() {
if (empty($this->caption)) return '';
return $this->loadAutoCaption($this->caption);
}
public function CaptionPresent() {
return $this->captionpresent;
}
public function Load(){
$this->caption = FALSE;
$this->poster = [];
$this->title = FALSE;
$this->captionpresent = 0;
preg_match_all('~<script.*?>(.*?)</script>~u', $this->body, $scripts);
foreach($scripts[1] as $index => $block) {
if (preg_match('~var ytInitialPlayerResponse = (.*?);$~', $block, $match)) {
$content = $this->scanForEndOfJson($match[1]);
$content = json_decode($content, TRUE);
if ($content == NULL) {
echo 'JSON decode error: ', json_last_error_msg() . "\n\n";
break;
}
if (isset($content['captions'])) {
$temp = $content['captions']['playerCaptionsTracklistRenderer'];
foreach($temp['captionTracks'] as $caption) {
if (($caption['kind'] ?? '') == 'asr') {
// 2025.06.29 we can no longer download captions. extra security fields required: use yt-dlp instead
// $this->caption = $caption['baseUrl'] . '&fmt=json3&xorb=2&xobt=3&xovt=3';
$this->captionpresent = 1;
break;
}
}
if ($this->caption === FALSE) {
foreach($temp['captionTracks'] as $caption) {
if (substr($caption['languageCode'], 0, 2) == 'en') {
// $this->caption = $caption['baseUrl'] . '&fmt=json3&xorb=2&xobt=3&xovt=3';
$this->captionpresent = 1;
break;
}
}
}
}
if (isset($content['videoDetails'])) {
$node = $content['videoDetails'];
$this->title = $node['title'];
$this->poster = $node['thumbnail']['thumbnails'];
}
}
}
usort($this->poster, function($a, $b) {
return $b['height'] - $a['height'];
});
}
// 19680 to '00:00:19.680'
function msToTime($x) {
$ms = substr($x . '', -3);
$x = intval($x / 1000);
$ss = $x % 60;
$x = intval($x / 60);
$mm = $x % 60;
$hh = intval($x / 60);
return sprintf('%02d:%02d:%02d.%s', $hh, $mm, $ss, $ms);
}
// return smallest thumbnail that is at least this height
public function GetThumbnail($height = -1) {
$result = FALSE;
$foundheight = FALSE;
foreach($this->poster as $item) {
if ($height == -1) {
if ($foundheight === FALSE || $item['height'] > $foundheight) {
$result = $item['url'];
$foundheight = $item['height'];
}
} elseif ($item['height'] >= $height) {
$result = $item['url'];
$foundheight = $item['height'];
}
}
if ($result !== FALSE) {
if (class_exists('WP_Http_Curl', FALSE)) { // wordpress
$curl = new WP_Http_Curl;
$args = [
'user-agent' => '',
'stream' => FALSE,
'filename' => '',
'redirection' => 5,
'decompress' => TRUE,
'_redirection' => 5,
];
$response = $curl->request($result, $args);
$result = $response['body'];
} else { // pkp
$n = new \CurlObject();
$n ->Url($result)
->Redirect(1)
->Execute();
$result = $n->Body();
}
}
return [$foundheight, $result];
}
protected function loadAutoCaption($url) {
if (class_exists('WP_Http_Curl', FALSE)) { // wordpress
$curl = new WP_Http_Curl;
$args = [
'user-agent' => '',
'stream' => FALSE,
'filename' => '',
'redirection' => 5,
'decompress' => TRUE,
'_redirection' => 5,
];
$response = $curl->request($url, $args);
$body = $response['body'];
} else { // pkp
$n = new \CurlObject();
$n ->Url($url)
->Redirect(1)
->Execute();
$body = $n->Body();
}
if ($body == '') die("Unable to download captions");
// file_put_contents('/mnt/x/caps.txt', $body);
// $body = file_get_contents('/mnt/x/caps.txt');
$json = json_decode($body, FALSE);
$json = $json->events;
$output = '';
$count = count($json);
foreach($json as $index => $entry) {
$dstart = $this->msToTime($entry->tStartMs);
$duration = $entry->dDurationMs ?? 0;
if (empty($entry->aAppend) && isset($entry->segs)) {
if ($index < $count - 1 && !empty($json[$index+1]->aAppend)) { // the silence is included in the current duration
$silence = $json[$index+1]->dDurationMs ?? 0;
if ($duration > $silence) {
$duration -= $silence;
}
}
$dfinish = $this->msToTime($entry->tStartMs + $duration); // length includes the subsequent silent period [$entry->aAppend == 1]
$output .= "{$dstart} --> {$dfinish} align:start position:0%\nx\n";
$output .= $this->_asText($entry->tStartMs, $entry->segs) . "\n\n";
} elseif (!empty($entry->aAppend)) { // this is a period of silence
$dfinish = $this->msToTime($entry->tStartMs + $duration);
$output .= "{$dstart} --> {$dfinish} align:start position:0%\n";
$output .= "x\n\n\n";
}
}
return <<<BLOCK
WEBVTT
Kind: captions
Language: en
$output
BLOCK;
}
public function _asText($ms, $segs) {
$result = '';
foreach($segs as $seg) {
$pos = $seg->tOffsetMs ?? 0;
$text = htmlspecialchars($seg->utf8);
if ($pos) {
$t = $this->msToTime($ms + $pos);
$text = "<$t><c>$text</c>";
}
$result .= $text;
}
return $result;
}
private function scanForEndOfJson($source) {
$inquote = FALSE;
$p = 0;
$ln = strlen($source);
while ($p < $ln) {
$ch = $source[$p]; $p++;
if ($ch == '"' && $inquote) {
$inquote = FALSE;
} elseif ($ch == '"' && !$inquote) {
$inquote = TRUE;
} elseif ($ch == ';' && !$inquote) {
return substr($source, 0, $p - 1);
}
}
return $source;
}
# admin@his: https://console.cloud.google.com/iam-admin/quotas?authuser=5&project=noted-door-322222
# list videos: 1 unit
# update video 50 units.
// requires: composer require google/apiclient:^2.0
// you get 10,000 quota units per day and each upload costs 1600 units.
// so this is not worthwhile
public function UploadVideo($client, $localfilename, $ident) {
try {
// Define an object that will be used to make all API requests
$youtube = new Google_Service_YouTube($client);
// Create a snippet with title, description, tags and category ID
// Create an asset resource and set its snippet metadata and type.
// This example sets the video's title, description, keyword tags, and
// video category.
$snippet = new Google_Service_YouTube_VideoSnippet();
$snippet->setTitle($ident);
$snippet->setDescription('');
$snippet->setTags([]);
// Numeric video category. See
// https://developers.google.com/youtube/v3/docs/videoCategories/list
$snippet->setCategoryId('22'); // People & Blogs
// Set the video's status to "public". Valid statuses are "public",
// "private" and "unlisted".
$status = new Google_Service_YouTube_VideoStatus();
$status->privacyStatus = 'unlisted';
// Associate the snippet and status objects with a new video resource.
$video = new Google_Service_YouTube_Video();
$video->setSnippet($snippet);
$video->setStatus($status);
// Specify the size of each chunk of data, in bytes. Set a higher value for
// reliable connection as fewer chunks lead to faster uploads. Set a lower
// value for better recovery on less reliable connections.
$chunkSizeBytes = 2 * 1024 * 1024;
// Setting the defer flag to true tells the client to return a request which can be called
// with ->execute(); instead of making the API call immediately.
$client->setDefer(true);
// Create a request for the API's videos.insert method to create and upload the video.
$insertRequest = $youtube->videos->insert("status,snippet", $video);
// Create a MediaFileUpload object for resumable uploads.
$media = new Google_Http_MediaFileUpload(
$client,
$insertRequest,
'video/*',
null,
true,
$chunkSizeBytes
);
$media->setFileSize(filesize($localfilename));
// Read the media file and upload it chunk by chunk.
$status = false;
$handle = fopen($localfilename, 'rb');
while (!$status && !feof($handle)) {
$chunk = fread($handle, $chunkSizeBytes);
$status = $media->nextChunk($chunk);
}
fclose($handle);
// If you want to make other calls after the file upload, set setDefer back to false
$client->setDefer(false);
if (!empty($status) && !empty($status->id)) {
return $status->id;
}
} catch (Google_Service_Exception $e) {
$msg = json_decode($e->getMessage(), TRUE);
return [
'result' => 19401,
'message' => sprintf('A service error occurred: %s', $msg['error']['message']),
];
} catch (Google_Exception $e) {
$msg = json_decode($e->getMessage(), TRUE);
return [
'result' => 19402,
'message' => sprintf('A client error occurred: %s', $msg['error']['message']),
];
}
return FALSE;
}
// gets the videoid and title for uploaded videos
// https://stackoverflow.com/questions/18953499/youtube-api-to-fetch-all-videos-on-a-channel/27872244#27872244
public function ListVideos($client) {
$youtube = new Google_Service_YouTube($client);
$yt_channels = $youtube->channels->listChannels('contentDetails', ['mine' => TRUE] );
$playlist = $yt_channels[0]->contentDetails->relatedPlaylists->uploads;
$yt_results = $youtube->playlistItems->listPlaylistItems('snippet', [
'playlistId' => $playlist,
'maxResults' => 50,
]);
$result = [];
foreach ($yt_results['items'] as $playlistItem) {
$hash = preg_match('~([0-9a-f]{8})~', $playlistItem['snippet']['title'], $match) ? $match[1] : '';
$result[] = [
'title' => $playlistItem['snippet']['title'],
'id' => $playlistItem['snippet']['resourceId']['videoId'],
'hash' => $hash,
];
}
return $result;
}
private function getYoutubeTemp($prefix = 'mp4') {
$path = ABSPATH . '/wp-content/uploads/youtube';
@mkdir($path);
$temp = tempnam($path, $prefix);
return $temp;
}
// download the video, return name of temp file
public function DownloadVideo() {
$temp = $this->getYoutubeTemp('mp4');
$filename_temp = $temp . '.mp4';
$safe = escapeshellarg($temp);
$url = escapeshellarg($this->url);
$command = 'yt-dlp ' . static::RUNTIME . " -4 -o $safe --force-overwrites --no-write-subs -N 32 -S \"height:720\" -t mp4 $url"; // could be mkv format
$this->debug_output = $command . "\n\n" . `$command 2>&1`;
if (file_exists($filename_temp) && filesize($filename_temp)) return $filename_temp;
// @unlink($temp);
// return $filename_temp;
return $temp;
}
// download the captions, return name of temp file
public function DownloadVTT() {
$temp = $this->getYoutubeTemp('vtt');
$safe = escapeshellarg($temp);
$url = escapeshellarg($this->url);
// not using web player: web player requires potc/pot hashes. other players do not.
$command = 'yt-dlp ' . static::RUNTIME . " -o $safe --force-overwrites --no-download --sub-lang \"en.*\" --write-auto-subs --extractor-args \"youtube:player_client=default,-web\" $url";
echo "$command\n";
$this->debug_output = `$command 2>&1`;
@unlink($temp);
return "{$temp}.en.vtt";
}
}
/*
https://github.com/yt-dlp/yt-dlp?tab=readme-ov-file#installation
Download video and captions using yt-dlp:
1. Video: // this should handle live video
yt-dlp --trim-filenames 16 --restrict-filenames --no-write-subs -N 32 -S "height:720" 'https://www.youtube.com/watch?v=soNrKb-hFhk'
2. Captions: // this doesn't handle live video
yt-dlp --trim-filenames 16 --restrict-filenames --no-download --write-auto-subs --extractor-args "youtube:player_client=default,-web" 'https://www.youtube.com/watch?v=soNrKb-hFhk'
*/