Dec 2014 – Please note this post is based on the 1.0.5 version of the API. If you are using the latest version you’ll need to update file paths as the file/folder structure has changed significantly.
Many bothans died to bring you this information. And when I say bothans, I refer of course to brain cells that have died as result of me smashing my head against the YouTube api.
To make this work, you’ll need to create a dummy web application that can capture the refresh token generated when you authorise your app. You can read a detailed explanation of what we’ll be doing and how the OAuth process works here.
This implementation has no requirements for any PHP frameworks (such as Zend). It is a barebones implementation based off the documentation for the YouTube V3 API utilising the Google PHP API client.
Creating your project
- You’ll need to head over to https://console.developers.google.com/ and create a new project.
- Then go and enable the YouTube Data API – (Your Project > APIS & AUTH > APIs)
- We’ll now need to create some credentials. Go to Your Project > APIS & AUTH > Credentials
- Click “Create new Client ID” and then under “Application Type” select “Web Application”.
- You’ll then need to enter your JavaScript origin and authorised redirect URI. I just set this to localhost, but you can set it to where ever you want.
- We’ve now created our client ID, we just need to fill in the Consent Screen. We can do this by navigating to Your Project > APIS & AUTH > Consent Screen. Once you’re on this page, ensure you fill out all the required fields (Email Address and Product Name).
- Hurray, we’ve created our project.
Getting your refresh token
I used the following PHP script as my dummy application to generate the OAuth token.
This script utilises Google APIs Client Library for PHP. You’ll need to download and install it a directory that can be read using the below script.
<?php // Call set_include_path() as needed to point to your client library. set_include_path($_SERVER['DOCUMENT_ROOT'] . '/directory/to/google/api/'); require_once 'Google/Client.php'; require_once 'Google/Service/YouTube.php'; session_start(); /* * You can acquire an OAuth 2.0 client ID and client secret from the * {{ Google Cloud Console }} <{{ https://cloud.google.com/console }}> * For more information about using OAuth 2.0 to access Google APIs, please see: * <https://developers.google.com/youtube/v3/guides/authentication> * Please ensure that you have enabled the YouTube Data API for your project. */ $OAUTH2_CLIENT_ID = 'XXXXXXX.apps.googleusercontent.com'; $OAUTH2_CLIENT_SECRET = 'XXXXXXXXXX'; $REDIRECT = 'http://localhost/oauth2callback.php'; $APPNAME = "XXXXXXXXX"; $client = new Google_Client(); $client->setClientId($OAUTH2_CLIENT_ID); $client->setClientSecret($OAUTH2_CLIENT_SECRET); $client->setScopes('https://www.googleapis.com/auth/youtube'); $client->setRedirectUri($REDIRECT); $client->setApplicationName($APPNAME); $client->setAccessType('offline'); // Define an object that will be used to make all API requests. $youtube = new Google_Service_YouTube($client); if (isset($_GET['code'])) { if (strval($_SESSION['state']) !== strval($_GET['state'])) { die('The session state did not match.'); } $client->authenticate($_GET['code']); $_SESSION['token'] = $client->getAccessToken(); } if (isset($_SESSION['token'])) { $client->setAccessToken($_SESSION['token']); echo '<code>' . $_SESSION['token'] . '</code>'; } // Check to ensure that the access token was successfully acquired. if ($client->getAccessToken()) { try { // Call the channels.list method to retrieve information about the // currently authenticated user's channel. $channelsResponse = $youtube->channels->listChannels('contentDetails', array( 'mine' => 'true', )); $htmlBody = ''; foreach ($channelsResponse['items'] as $channel) { // Extract the unique playlist ID that identifies the list of videos // uploaded to the channel, and then call the playlistItems.list method // to retrieve that list. $uploadsListId = $channel['contentDetails']['relatedPlaylists']['uploads']; $playlistItemsResponse = $youtube->playlistItems->listPlaylistItems('snippet', array( 'playlistId' => $uploadsListId, 'maxResults' => 50 )); $htmlBody .= "<h3>Videos in list $uploadsListId</h3><ul>"; foreach ($playlistItemsResponse['items'] as $playlistItem) { $htmlBody .= sprintf('<li>%s (%s)</li>', $playlistItem['snippet']['title'], $playlistItem['snippet']['resourceId']['videoId']); } $htmlBody .= '</ul>'; } } catch (Google_ServiceException $e) { $htmlBody .= sprintf('<p>A service error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } catch (Google_Exception $e) { $htmlBody .= sprintf('<p>An client error occurred: <code>%s</code></p>', htmlspecialchars($e->getMessage())); } $_SESSION['token'] = $client->getAccessToken(); } else { $state = mt_rand(); $client->setState($state); $_SESSION['state'] = $state; $authUrl = $client->createAuthUrl(); $htmlBody = <<<END <h3>Authorization Required</h3> <p>You need to <a href="$authUrl">authorise access</a> before proceeding.<p> END; } ?> <!doctype html> <html> <head> <title>My Uploads</title> </head> <body> <?php echo $htmlBody?> </body> </html>
Once you’ve got the script above and the Google Client Library API in place, load the script in your browser. You should be prompted to Authorise access before you can continue. Once you click on this link you should be taken through the steps to authorise your currently logged in Google account to access your new application.
Once it’s all done you should be redirected back to the localhost page, except now you will see a JSON string along the top of the page. Save this string somewhere for use later on. You might also notice if you’ve already uploaded videos to YouTube, a list of your videos on the page – just to confirm the access token works!
Saving your credentials
Now that you’ve received your refresh tokens create a text file called “the_key.txt” with the following information you’ve just received, it should look something similar to the format below:
{"access_token":"XXXXXXXXX","token_type":"Bearer", "expires_in":3600, "refresh_token":"XXXXXXX", "created":000000}
I suggest you write your refresh token down somewhere for safe keeping.
Creating the upload script
The script below is a very basic implementation. It requires the following file structure:
- Google/
- upload.php (the script below)
- the_key.txt (the file we created in the previous section)
- tutorial.mp4 (the video to upload)
The script will automatically update your “the_key.txt” file if your access_token is out of date.
Set the include path to ensure the Google API works correctly (line 3).
Replace the following variables with your own information:
- $application_name
- $client_secrect
- $client_id
- $videoPath
- $videoTitle
- $videoDescription
- $videoCategory
- $videoTags
$key = file_get_contents('the_key.txt'); set_include_path($_SERVER['DOCUMENT_ROOT'] . '/path-to-your-director/'); require_once 'Google/Client.php'; require_once 'Google/Service/YouTube.php'; $application_name = 'XXXXXX'; $client_secret = 'XXXXXXX'; $client_id = 'XXXXXXX.apps.googleusercontent.com'; $scope = array('https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/auth/youtube', 'https://www.googleapis.com/auth/youtubepartner'); $videoPath = "tutorial.mp4"; $videoTitle = "A tutorial video"; $videoDescription = "A video tutorial on how to upload to YouTube"; $videoCategory = "22"; $videoTags = array("youtube", "tutorial"); try{ // Client init $client = new Google_Client(); $client->setApplicationName($application_name); $client->setClientId($client_id); $client->setAccessType('offline'); $client->setAccessToken($key); $client->setScopes($scope); $client->setClientSecret($client_secret); if ($client->getAccessToken()) { /** * Check to see if our access token has expired. If so, get a new one and save it to file for future use. */ if($client->isAccessTokenExpired()) { $newToken = json_decode($client->getAccessToken()); $client->refreshToken($newToken->refresh_token); file_put_contents('the_key.txt', $client->getAccessToken()); } $youtube = new Google_Service_YouTube($client); // Create a snipet with title, description, tags and category id $snippet = new Google_Service_YouTube_VideoSnippet(); $snippet->setTitle($videoTitle); $snippet->setDescription($videoDescription); $snippet->setCategoryId($videoCategory); $snippet->setTags($videoTags); // Create a video status with privacy status. Options are "public", "private" and "unlisted". $status = new Google_Service_YouTube_VideoStatus(); $status->setPrivacyStatus('private'); // Create a YouTube video with snippet and status $video = new Google_Service_YouTube_Video(); $video->setSnippet($snippet); $video->setStatus($status); // Size of each chunk of data in bytes. Setting it higher leads faster upload (less chunks, // for reliable connections). Setting it lower leads better recovery (fine-grained chunks) $chunkSizeBytes = 1 * 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($videoPath)); // Read the media file and upload it chunk by chunk. $status = false; $handle = fopen($videoPath, "rb"); while (!$status && !feof($handle)) { $chunk = fread($handle, $chunkSizeBytes); $status = $media->nextChunk($chunk); } fclose($handle); /** * Video has successfully been upload, now lets perform some cleanup functions for this video */ if ($status->status['uploadStatus'] == 'uploaded') { // Actions to perform for a successful upload // $uploaded_video_id = $status['id']; } // If you want to make other calls after the file upload, set setDefer back to false $client->setDefer(true); } else{ // @TODO Log error echo 'Problems creating the client'; } } catch(Google_Service_Exception $e) { print "Caught Google service Exception ".$e->getCode(). " message is ".$e->getMessage(); print "Stack trace is ".$e->getTraceAsString(); }catch (Exception $e) { print "Caught Google service Exception ".$e->getCode(). " message is ".$e->getMessage(); print "Stack trace is ".$e->getTraceAsString(); }
Suggestions
As you can see, the script is very primitive, among other things, this script should not be located in a publicly accessible directory due to the obvious abuse that could occur. In my situation, I created a queue of files to upload and setup a cron to execute the script once an hour that would then check a directory for any files of a certain type (mp4) and then loop through and upload to YouTube. The method I described hasn’t been tested on files more than 10 x 400mb in size so I’m not sure what the impacts would be on your server for more files / larger sizes etc.
Hopefully that’s enough to get your started, if you have any questions feel free to ask :)
https://www.domsammut.com/?p=1142#comment-1903
#Nahapet
Hi Dom, could you please tell me how to get the video ID/url after it was uploaded?
https://www.domsammut.com/?p=1142#comment-1904
#Dom Sammut
Hi Nahapet,
Please look through the comments on this post as there is a detailed response regarding this question (basically it’s returned as part of the
$status
variable).Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1901
#Keyur
An client error occurred: The OAuth 2.0 access token has expired, and a refresh token is not available. Refresh tokens are not returned for responses that were auto-approved.
how can i solve this???
https://www.domsammut.com/?p=1142#comment-1905
#Dom Sammut
Hi Keyur,
This tutorial outlines how to harvest, store and use a refresh token to enable you to use it to generate a valid access token. If you have completed this tutorial successfully you won’t encounter the error you’ve detailed above.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1899
#Alvin
why i got this error, Failed to load resource: the server responded with a status of 503 (Service Unavailable
but, the video is successfully uploaded, and the video 2 times uploaded
https://www.domsammut.com/?p=1142#comment-1898
#Alvin
Hello Dom, i got this error when uploading video, but video is uploaded, and why the video is uploaded 2 times? thanks for your article
https://www.domsammut.com/?p=1142#comment-1897
#Weidong Pan
Hello Dom,
Thanks for sharing your code and the discussion with us! Not sure why I am not able to get your first script work. After I load the script, I am prompted to Authorise access. Then I enter my login details. After that, I am redirected to the localhost page (I sent redirect as ‘localhost’), but the URL is:
https://localhost/?state=508697803&code=4/IYi7y6LWFfYRHlQ3Mbp_RNBBUJcqiITyU07BJ65-MRg#
As you see, no token has returned. Either, the list of videos I’ve already uploaded into my channel does not appear on the page.
I have no idea for why the script is not working as I’ve used the same code and same version. The only changes I’ve made are the tow variables: $OAUTH2_CLIENT_ID and $OAUTH2_CLIENT_SECRET (set them to my ones). In addition, I’ve changed the ‘set_include_path’ statement.
Can you please give me some ideas to let the script work? Thank you in advanced!
https://www.domsammut.com/?p=1142#comment-1900
#Dom Sammut
Hi Weidong,
The token will be returned in page as per Line 46
echo '
' . $_SESSION['token'] . '
';.The token does not get appended to the URL.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1893
#Clement
Hi,
Tanks very much for the code. I already use it on 2 of my websites.
In order to go a bit further, I would like to know if you (or anyone reading this comment) would have an idea on how to set a thumbnail from a remote file on my server. (also, is it possible to put the video unlisted by default?)
Thanks in advance.
https://www.domsammut.com/?p=1142#comment-1894
#Dom Sammut
Hi Clement,
In regards to setting a thumbnail for the video from your remote server automatically, I’m not sure. In terms of setting your video to unlisted / private that is one of the options when uploading the video. If you look through all the comments (theres a lot, sorry), you should find some more detail regarding this aspect (link to documentation with parameters).
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1889
#Ritu Raj
Nice tutorial
https://www.domsammut.com/?p=1142#comment-1885
#Tajinder
Hi Dom,
Thanks for your help. But my case is different. I am working on it from last many days but didn’t get success.
I want to give access to the users of my website to upload video through a form to my youtube channel and no authentication should be required for them.
For this I authenticate once with my account and got the token and state value from session. Then I statically passed it in the code.
With this I am able to do what I want. No authentication required when I am uploading video from another system or browser.
But the issue is, it works for a little time and then it starts throwing error of token expired. How can I handle it ?
Your help will be so much valuable for me.
Thanks,
Tajinder
https://www.domsammut.com/?p=1142#comment-1886
#Dom Sammut
Hi Tajinder,
You need to be harvesting a refresh token. This will allow you to generate new access tokens upon expiration. This is detailed in the second part of my tutorial.
Cheers
Doms
https://www.domsammut.com/?p=1142#comment-1882
#Mohan
i am getting below error??
Fatal error: Class ‘Google_Service’ not found in C:\xampp\htdocs\webservice\YouTube.php on line 32
https://www.domsammut.com/?p=1142#comment-1884
#Dom Sammut
Hi Mohan,
Have a look through the comments as I believe this issue has come up before with other people with a number of different solutions provided.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1878
#Anders Borg
Do you know whether the access token ever expires? In my case there will not be any user granting permission. My app must work silently on behalf of the user, so I had hoped I could sample the access token and rely on that for a long time (read: forever).
I also noted that the Google_Service class is not found. Any ideas where that might be hiding?
Thanks in advance,
Anders
https://www.domsammut.com/?p=1142#comment-1881
#Dom Sammut
Hi Anders,
As you’ve noted, access tokens expire. That’s why you need to harvest a refresh token so you can generate a new access token silently without user interaction, this tutorial covers how to complete the process.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1877
#Sergio
Hallo, just found this page. Thanks, it’s helpful… but I am stucked on the generation of the OAuth token. The callback works fine but I get this error:
Fatal error: Uncaught exception ‘Google_IO_Exception’ with message ‘HTTP Error: Unable to connect: ‘fopen(https://accounts.google.com/o/oauth2/token): failed to open stream: Invalid argument” in C:\wamp\www\youtubeupload\google-api-php-client-1.0.5-beta\src\Google\IO\Stream.php:112 Stack trace: #0 C:\wamp\www\youtubeupload\google-api-php-client-1.0.5-beta\src\Google\IO\Abstract.php(125): Google_IO_Stream->executeRequest(Object(Google_Http_Request)) #1 C:\wamp\www\youtubeupload\google-api-php-client-1.0.5-beta\src\Google\Auth\OAuth2.php(113): Google_IO_Abstract->makeRequest(Object(Google_Http_Request)) #2 C:\wamp\www\youtubeupload\google-api-php-client-1.0.5-beta\src\Google\Client.php(130): Google_Auth_OAuth2->authenticate(‘4/CZV3nR_ugV6W-…’) #3 C:\wamp\www\youtubeupload\index.php(43): Google_Client->authenticate(‘4/CZV3nR_ugV6W-…’) #4 {main} thrown in C:\wamp\www\youtubeupload\google-api-php-client-1.0.5-beta\src\Google\IO\Stream.php on line 112
The returning URL is http://localhost/youtubeupload/index.php?state=1330971980&code=4/CZV3nR_ugV6W-66RIADkqYZ40rODYj0Cy47hD6gnh88#
CURL and Https wrappers are enabled, GZIP is disable, I am using API version 1.0.5 as suggested. I wonder what to try more.
Thanks in advance.
https://www.domsammut.com/?p=1142#comment-1876
#Developer
Two error cases:
1. I selected a wrong file type (mp3 or image), the Youtube API code still returns “uploaded” status (ie same as of a successful upload) and returned new video id.
2. I selected a video which already uploaded then Youtube returns status as “uploaded” and returned new Video Id.
Both cases, if we try to open the video using the id received, it shows the error message “Duplication or something like that.”.
Some points come to my mind:
1. I know case #1 can be handled by checking “video” type during File Upload. But still there is possibility of uploading video file which is not supported by Youtube.
2. On the first part of this article, there is code to retrieve the list of videos from Channel. We could check the id received after upload against this list will
resolve both cases. But still it is not easy to show proper error message.
Does anyone resolved these issues ?
https://www.domsammut.com/?p=1142#comment-1872
#JT
Thanks for this! Worked perfectly!
https://www.domsammut.com/?p=1142#comment-1873
#Dom Sammut
Thanks for the feedback JT, much appreciated. Glad to have helped you out :)
https://www.domsammut.com/?p=1142#comment-1870
#priyal
Hello, I am using above code..I get response of my channelList … but not getting playlistingitems and getting video details by passind videoID, but cant delete this video… giving : Error calling DELETE https://www.googleapis.com/youtube/v3/videos?id=FchdirmxC5c: (403) The video that you are trying to delete cannot be deleted. The request might not be properly authorized.