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-1868
#Terri
Hi Dom,
I want my app upload videos to different youtube accounts.
Save access/refresh tokens for each user after going through a consent page, and then retrieve and use them for uploading to switch between users –> I’ve tried this scenario, and this doesn’t seem to work.
Is there a way to upload videos to different youtube accounts without going through a consent page each time?
Thank you.
https://www.domsammut.com/?p=1142#comment-1869
#Dom Sammut
Hi Terri,
You should be able to switch between accounts as long as you’ve stored all the information for each one locally on your server.
Are these different YouTube accounts on the same Google Account or different?
I haven’t done this FYI, but I would hazard a guess and say it would be possible and just be an extension of what I’ve detailed here.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1871
#Terri
Dom,
You were right!
I was retrieving access-refresh tokens only, but specifying “TokenType” made it possible switching between accounts!
Thanks for giving me the hint to save *all* the information.
Thank you, thank you!
https://www.domsammut.com/?p=1142#comment-1865
#Levis
Thanks for sharing.
I worked for me.
https://www.domsammut.com/?p=1142#comment-1863
#Danny
Thank you for this! works like a charm.
https://www.domsammut.com/?p=1142#comment-1861
#sayuj Raghavan
JavaScript origin and redirect URI u mentioned in 5th step . How did you set the local host address ? I am also setting tht way to test the app through localhost server. And what will JavaScript origin look like ? The Javascript origin is mandatory ?
https://www.domsammut.com/?p=1142#comment-1859
#The Doctor
Here’s a new issue that I haven’t seen in any other comments here…
I have the script working fine for some videos, but I just tried another video and the upload failed. It returns the following:
Caught Google service Exception 400 message is Error calling PUT https://www.googleapis.com/upload/youtube/v3/videos?part=status%2Csnippet&uploadType=resumable&upload_id=AEnB2UraAYkTXgflIlHIDhEs1pyEEEbLlnG3LjJ4u5nEmLedq4zfaeAF4biSZdn0CCYf2KzEN61LAIIwYimJU4R3qT0_uyWqqQ: (400) Failed to parse Content-Range header.Stack trace is #0 C:\stuff\htdocs\common\google-api-php-client-1.0.5-beta\src\Google\Http\MediaFileUpload.php(185): Google_Http_REST::decodeHttpResponse(Object(Google_Http_Request))
#1 C:\stuff\htdocs\uniblab\episode\youtube\upload.php(292): Google_Http_MediaFileUpload->nextChunk('??? ftypisom???...')
#2 {main}
Yes, I’m using the correct 1.0.5 version of the API library. Ever seen this error before?
https://www.domsammut.com/?p=1142#comment-1860
#Dom Sammut
I haven’t seen this issue before, are you able to upload the same video file without the error occurring if you retry? What’s the size / format of the video? If you can’t get it to upload via the API and there are no meaningful errors, are you able to successfully upload the video via YouTube directly? Whats your limit execution time limit for PHP? Perhaps making it a greater value, I’ve based this on
Failed to parse Content-Range header
and this question on StackOverflow.Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1855
#rajeev
we are facing error like this:
Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 400: global, Uploads must be sent to the upload URL. Re-send this request to https://www.googleapis.com/upload/youtube/v3/videos?part=status,snippet&uploadType=resumable)Stack trace is #0 /home/outsourc/public_html/google/google-api-php-client-master/src/Google/Http/MediaFileUpload.php(136): Google_Http_MediaFileUpload->getResumeUri() #1 /home/outsourc/public_html/google/file1.php(87): Google_Http_MediaFileUpload->nextChunk(‘\x00\x00\x00\x18ftypmp42\x00\x00\x00…’) #2 {main}
Can you help me?
https://www.domsammut.com/?p=1142#comment-1854
#David
Hello ! I would have like to find this page earlier. :)
I’m runnig the API well but i have a last case unthreated and which provides this exeption : “Failed to start the resumable upload (HTTP 401: youtube.header, Unauthorized) ”
The reason is that the user has a google account but he didn’t linked it to a channel (he just needs to create a channel…) but in the cas he didn’t create a channel, the exeption happens. Is some one knows how to threat this case ? Is it possible to test if a user has created a channel or not ? The best would be to be able to force the creation of the channel but don’t think it is possible :p. Or other …
Anyway thanks for the share.
https://www.domsammut.com/?p=1142#comment-1856
#David
Hi,
Found it, i work on symfony and missed to put “use Google_Exception;” in my controller, that’s why the Google_Exception was not catched…
And just, there is a google method to get the refresh token, so instead of using json_decode :
$newToken = json_decode($client->getAccessToken());
$client->refreshToken($newToken->refresh_token);
You can do :
$client->refreshToken( $client->getRefreshToken() );
https://www.domsammut.com/?p=1142#comment-1850
#Feedback after 75 days of use
It is almost 75 days since I left my last comment in this very useful post and on this amazing blog. In conclusion, the script works totally fine without any errors. No problems reported by users who upload their videos via my uploading page since April 2015 at presentationtube.com. Also, no errors are reported in my Console Application. The tokens are updated and saved automatically every 1-3 days. I can get the video key and check and update the video status right after uploading easier than API 2.0.
https://www.domsammut.com/?p=1142#comment-1848
#Mario Josifovski
I just want to thank you for the awesome explanation, got a job done in much shorter that what it would have taken.
https://www.domsammut.com/?p=1142#comment-1849
#Dom Sammut
Glad to have helped Mario :)
https://www.domsammut.com/?p=1142#comment-1831
#Shabiq Hazarika
Hello sir, is it possible to get the video url after uploading the video.??
https://www.domsammut.com/?p=1142#comment-1832
#Dom Sammut
Hi Shabiq,
You will get the documented dataset in response to a successful video upload. The example is a JSON response but the response will be php object when using this tutorial.
An example in PHP is
$response->status['uploadStatus']
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1824
#Ivan S
Hi Dom, thanks for your article.
I want to download mp4 format, its possible ?
https://www.domsammut.com/?p=1142#comment-1825
#Dom Sammut
Hi Ivan,
Please check the YouTube documentation to see if this is possible: https://developers.google.com/youtube/v3/
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1823
#Steven
Thanks for this example… the expired token was the main problem which you solved simple to understand. thanks
https://www.domsammut.com/?p=1142#comment-1819
#Bads
Hi Dom,
On my website I used to call a very basic PHP script to get an XML feed containing a list of my publisehd videos on youtube. This call just returns an XML content I can parse.
Unfortunately, it stopped working some days ago and I’m wondering if I realy need to setup all this OAuth 2.0 stuff in order to get acces to the expected info (list of my vids)?
I already prepared all definition on Developer’s console, I can run your sample script but I always need to clic on the “authorise access” link when I call it!
If I realy need to define/use OAuth 2.0, just to retrieve the list of my videos, could you let me know what I have to put into the file http://www.mywebsite.com/oauth2callback.php?
Hope you can help me about this issue.
Regards.
Bads
https://www.domsammut.com/?p=1142#comment-1816
#Rushikesh
When i try to upload youtube video for another youtube channel like
$videoPath = “https://www.youtube.com/watch?v=sHQp4lmTaZs”;
it gives me error like
Error calling PUT https://www.googleapis.com/upload/youtube/v3/videos?part=status%2Csnippet&uploadType=resumable&upload_id=AEnB2Ur9Js7ALE4_RT29-dwlHhWOStCo6PBkevAaQec-oUz9Rl8IhNzv7dfF9M9U8xvdH2qKuGdad_5AiclKBBQc7PXrR1rQfw: (400) Failed to parse Content-Range header.
https://www.domsammut.com/?p=1142#comment-1817
#Dom Sammut
Hi Rushikesh,
The $videoPath variable needs to be to an actual video file e.g. (.mp4, .avi). You cannot not upload existing YouTube videos to your own channel like you’re attempting to do. You’ll need to have the original file.
Cheers
Dom
https://www.domsammut.com/?p=1142#comment-1818
#Rushikesh
Hi Dom,
Thank you for reply, Is there any way to share youtube video on our channel with php?
https://www.domsammut.com/?p=1142#comment-1920
#Sanchita Dey
Hi,
My video is in .mp4 format. When trying to upload the video it giving me error “A service error occurred: Error calling PUT https://www.googleapis.com/upload/youtube/v3/videos?part=status%2Csnippet&uploadType=resumable&upload_id=AEnB2Uo6jpp2Fz1QLglzj7xxLG34WqBgqKLUW9JryFsrPp2DVLZcdQTWtRoZPsYVZ4JwYdcgAcMYZEukKWzNRPq8R8SUUgX06w: (400) Failed to parse Content-Range header.”… I’m not using any youtube video. In youtube video manager, video title is showing but video uploading is stuck in “preparing upload”….
https://www.domsammut.com/?p=1142#comment-1815
#Top Results
I am to get the first portion to work, along with updating of the refresh token. It has been connecting successfully for well over a week now. I am just having issues with uploading a video.
I have posted the scripting and my results here.
https://github.com/google/google-api-php-client/issues/557
It was suggested to download the latest version which I have and the same results. Would there be any reasons why a video would like post to YouTube?
https://www.domsammut.com/?p=1142#comment-2053
#iman
Thank you soooooo much it works perfectly (y)