Created
December 10, 2018 21:57
-
-
Save abiusx/a3e5b2ef6d54866a5a1eba71021f8c2a to your computer and use it in GitHub Desktop.
Youtube Video Updater in Pure PHP
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 | |
/** | |
* This script can be used to modify videos on Youtube programatically. | |
* It does not need Google API libraries, it merely uses PHP's CURL. | |
* | |
* Before starting to work with this, read the comments below for oAuth_token() | |
* function. It requires you do 3 steps (twice run this code while uncommenting, | |
* once login on your browser). Once you have done that and updated the constants | |
* below, you're good to go! | |
* @author abiusx | |
*/ | |
/** | |
* API KEY is needed for quota management | |
*/ | |
const API_KEY='...TODO...'; | |
/** | |
* Create a pair by visiting https://console.developers.google.com/apis/credentials | |
*/ | |
const CLIENT_ID='...TODO...'; | |
const CLIENT_SECRET='...TODO...'; | |
/** | |
* Update these as necessary, based on the process listed in the comments of oAuth_token() | |
*/ | |
const AUTH_CODE='...TODO...'; | |
const REFRESH_TOKEN="...TODO..."; | |
/** | |
* This function is used as a callback send to update_videos below. | |
* to modify a single video snippet and use it to update youtube | |
* @param object $video youtube video snippet | |
* @return object modified or unmodified snippet | |
*/ | |
function modify_video($video){ | |
$str="...TODO..."; | |
if (strpos($video->description,$str)===false) | |
$video->description=$str.PHP_EOL.PHP_EOL.$video->description; | |
return $video; | |
} | |
// Update all videos using the modify function | |
update_videos("modify_video"); | |
/** | |
* Returns an oAuth access token. | |
* | |
* If it's not working, uncomment the first die command, it will generate a URL. | |
* Use that URL in the browser to login with the owning account (currently has youtube scopes in it, | |
* add other scopes if needed). | |
* Once logged in, will give you an AUTH_CODE. Use that AUTH_CODE to update the constant above. | |
* | |
* Uncomment the second request and die command, it will use the AUTH_CODE to generate a REFRESH_TOKEN | |
* Use that REFRESH_TOKEN to update the const above, and you should be golden. | |
* | |
* This function will use the REFRESH TOKEN to generate ACCESS_TOKENs on every app launch. | |
* @return string | |
*/ | |
function oAuth_token() | |
{ | |
/// Generates a url, visit that to login and get token | |
// die("https://accounts.google.com/o/oauth2/auth?".http_build_query(["client_id"=>CLIENT_ID,"redirect_uri"=>"urn:ietf:wg:oauth:2.0:oob","response_type"=>"code","scope"=>"https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.force-ssl https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtubepartner https://www.googleapis.com/auth/youtubepartner-channel-audit"])); | |
static $access_token; | |
if ($access_token===null) | |
{ | |
///Only once, to get the refresh token et. al. | |
// $r=request("https://accounts.google.com/o/oauth2/token","POST",[ | |
// "code"=>AUTH_CODE, | |
// "client_id"=>CLIENT_ID, | |
// "client_secret"=>CLIENT_SECRET, | |
// "grant_type"=>"authorization_code", | |
// "redirect_uri"=>"urn:ietf:wg:oauth:2.0:oob", | |
// ]); | |
// print_r($r);die(); | |
$r=request("https://accounts.google.com/o/oauth2/token","POST",[ | |
"refresh_token"=>REFRESH_TOKEN, | |
"client_id"=>CLIENT_ID, | |
"client_secret"=>CLIENT_SECRET, | |
"grant_type"=>"refresh_token", | |
"redirect_uri"=>"urn:ietf:wg:oauth:2.0:oob", | |
]); | |
$obj=json($r->body); | |
$access_token=$obj->access_token; | |
} | |
return $access_token; | |
} | |
/** | |
* Returns playlistId of my default upload channel | |
* @return string | |
*/ | |
function getMyChannel() | |
{ | |
$r=request("https://www.googleapis.com/youtube/v3/channels","GET",[ | |
"part"=>"contentDetails", | |
"access_token" => oAuth_token(), | |
"mine"=>"true", | |
"key"=>API_KEY, | |
]); | |
$channels=json($r->body); | |
// Find the 'uploads' channel which includes all videos | |
return $channels->items[0]->contentDetails->relatedPlaylists->uploads; | |
} | |
/** | |
* Returns videos in a playlist | |
* | |
* By default returns 50 at a time | |
* @param string $playlistId | |
* @param string $pageToken optional: if provided returns a certain page | |
* @return object | |
*/ | |
function getVideos($playlistId,$pageToken=null) | |
{ | |
$r=request("https://www.googleapis.com/youtube/v3/playlistItems","GET",[ | |
"key"=>API_KEY, | |
"part"=>"snippet", | |
"playlistId"=>$playlistId, | |
"maxResults"=>50, // Max supported | |
"pageToken"=>$pageToken | |
]); | |
if ($r->status!=200) | |
return false; | |
return json($r->body); | |
} | |
/** | |
* Loops through all my videos and updates them | |
* based on the modify function (which receives each video snippet) | |
* @param callable $modify_video receives one video snippet input | |
*/ | |
function update_videos(callable $modify_video) | |
{ | |
$pageIndex=$videoIndex=0; | |
$pageToken=""; | |
$channel=getMyChannel(); | |
while(true) | |
{ | |
$page=getVideos($channel,$pageToken); | |
$total=$page->pageInfo->totalResults; | |
if (!$page) | |
{ | |
echo "Failed on page {$pageIndex}. Retrying...",PHP_EOL; | |
continue; | |
} | |
foreach ($page->items as $video) | |
{ | |
$videoIndex++; | |
$id=$video->snippet->resourceId->videoId; | |
/// Use the following to get CategoryID | |
// $r=request("https://www.googleapis.com/youtube/v3/videos","GET",[ | |
// "key"=>API_KEY, | |
// "part"=>"snippet", | |
// "id"=>$id, | |
// ]); | |
// print_r($video); | |
$categoryId=1; | |
$snippet=$video->snippet; | |
$snippet->categoryId=$categoryId; | |
$new_snippet=$modify_video(clone $snippet); | |
if ($new_snippet!=$snippet) // Update | |
{ | |
$r=request("https://www.googleapis.com/youtube/v3/videos?part=snippet","PUT",json([ | |
"id"=>$id, | |
"snippet"=>$new_snippet, | |
]), | |
[ | |
"Authorization"=> "Bearer ".oAuth_token(), | |
"Content-Type" => "application/json", | |
]); | |
$success=($r->status==200); | |
if ($success) | |
$status="updated"; | |
else | |
$status="failed to update"; | |
} | |
else | |
$status="not touched"; | |
echo "({$videoIndex}/{$total}) {$status}. \thttps://www.youtube.com/watch?v={$id}",PHP_EOL; | |
} | |
$pageIndex++; | |
$pageToken=$page->nextPageToken; | |
} | |
} | |
function json($in) | |
{ | |
$flags=JSON_PRETTY_PRINT; | |
$encode=false; | |
if (is_string($in)) | |
{ | |
$out=json_decode($in); | |
$err=json_last_error(); | |
if ($err!==JSON_ERROR_NONE) | |
$encode=true; | |
} | |
else | |
$encode=true; | |
if ($encode) | |
{ | |
$out=json_encode($in,$flags); | |
$err=json_last_error(); | |
if ($err!==JSON_ERROR_NONE) | |
throw new Exception(json_last_error_msg()); | |
} | |
return $out; | |
} | |
function request($url,$method="GET",$data=[],$headers=[],$additional_opts=[]) | |
{ | |
$response_headers=[]; | |
$opts=[ | |
CURLOPT_FOLLOWLOCATION => false, | |
CURLOPT_COOKIEJAR => 'stdlib.cookie', | |
CURLOPT_COOKIEFILE => 'stdlib.cookie', | |
CURLOPT_RETURNTRANSFER => 1, | |
CURLOPT_URL => $url, | |
CURLOPT_CUSTOMREQUEST => $method, | |
CURLOPT_TIMEOUT => 5, | |
CURLOPT_CONNECTTIMEOUT => 2, | |
CURLOPT_USERAGENT => 'cURL', | |
CURLOPT_HEADERFUNCTION => | |
function($curl, $header) use (&$response_headers) | |
{ | |
$len = strlen($header); | |
$header = explode(':', $header, 2); | |
if (count($header) < 2) // ignore invalid headers | |
return $len; | |
$name = strtolower(trim($header[0])); | |
if (!array_key_exists($name, $response_headers)) | |
$response_headers[$name] = trim($header[1]); | |
elseif (is_array($response_headers[$name])) | |
$response_headers[$name][] = trim($header[1]); | |
else | |
$response_headers[$name] = [$response_headers[$name], trim($header[1])]; | |
return $len; | |
} | |
]; | |
$final_headers=[]; | |
if (!empty($headers)) | |
foreach ($headers as $key=>$value) | |
if (is_numeric($key)) | |
$final_headers[]=$value; | |
else | |
$final_headers[]="{$key}: {$value}"; | |
$opts[CURLOPT_HTTPHEADER]=$final_headers; | |
if (is_array($additional_opts)) | |
foreach ($additional_opts as $k=>$v) | |
$opts[$k]=$v; | |
if ($data) | |
{ | |
if (!is_string($data)) | |
$data=http_build_query($data); | |
if ($method=="GET") | |
{ | |
$url=$opts[CURLOPT_URL]; | |
if (strpos($url,"?")===false) | |
$url.="?{$data}"; | |
else | |
$url.="&{$data}"; | |
$opts[CURLOPT_URL]=$url; | |
} | |
else | |
$opts[CURLOPT_POSTFIELDS]=$data; | |
} | |
$curl = curl_init(); | |
curl_setopt_array($curl, $opts); | |
$res = curl_exec($curl); | |
$httpcode = curl_getinfo($curl, CURLINFO_HTTP_CODE); | |
curl_close($curl); | |
$out=(object)["status"=>$httpcode, "body"=>$res, "header"=>$response_headers]; | |
// $out->status_code=&$out->status; | |
// $out->code=&$out->status; | |
// $out->content=&$out->body; | |
// $out->headers=&$out->header; | |
return $out; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment