Found this useful? Love this post
295

PHP server-side YouTube V3 OAuth API video upload guide

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

  1. You’ll need to head over to https://console.developers.google.com/ and create a new project.
  2. Then go and enable the YouTube Data API – (Your Project > APIS & AUTH > APIs)
  3. We’ll now need to create some credentials. Go to Your Project > APIS & AUTH > Credentials
  4. Click “Create new Client ID” and then under “Application Type” select “Web Application”.
  5. 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.
  6. 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).
  7. 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 :)

Subscribe

You'll only get 1 email per month containing new posts (I hate spam as much as you!). You can opt out at anytime.

Categories

Leave a Reply

Your email address will not be published. Required fields are marked *

Preview Comment

25 Comments

  1. Bastien
    http://bit.ly/1uyGLyO#comment-2074

    Bastien

    Hi Dom,

    Thank you for this post.
    Do you know if your code / logic is still possible with the V3 API if we wish to upload a video ?

    What “localhost” redirect URL you’ve decided to put when you’re in a server ?
    Thank you,

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2075

      Dom Sammut

      Hey Bastien,

      I haven’t tested with the latest release of the V3 API but the core principals should be same considering they’re still using OAuth. You might just need to update some of the Google API methods to reflect any new changes.

      In regards to the localhost URL, it needs to be something that will resolve on your computer / server of execution. You shouldn’t need the first half of the script after you’ve attained the refresh token and saved it to file.

      Cheers
      Dom

      • Bastien
        http://bit.ly/1uyGLyO#comment-2076

        Bastien

        Hi Dom

        Thank you for your quick response.
        Regarding your 2nd point, could you please tell me a bit more, or some example on it ? I’m bit confuse on it.

        Thank you again

        • Dom Sammut
          http://bit.ly/1uyGLyO#comment-2077

          Dom Sammut

          Hey Bastien,

          The first part of the tutorial Getting your refresh token is what you’ll need to run once to get your refresh token that can be used in all subsequent requests to attain an access token. The tutorial outlines this pretty clearly in terms of what you need to do. Have you completed this step?

          Cheers
          Dom

  2. parag
    http://bit.ly/1uyGLyO#comment-2068

    parag

    Hi Dom,

    I have set-up a cron for regererate the token in every 30 min, but after couple of hours the file has no value (I’m only saving the refresh token in my file) what could be the issue. that the refresh token is return blank.

    I have now used snippet from your article for refresh token

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2069

      Dom Sammut

      Hey Parag,

      Are you logging the requests? What status code do you get when you’re attempting to refresh the token?

      Cheers
      Dom

      • Parag
        http://bit.ly/1uyGLyO#comment-2071

        Parag

        I was uploading the file while uploading the video to YouTube and at that time the token is not refreshed, I have added the condition while writing the file. I’m pretty sure that issue is resolved, the file is not blank now.
        And albe to upload the multiple video one after anather.

        Also, I only use the refresh_token to save in the file, is that a good practice?.

        Thanks,
        Parag.

        • Dom Sammut
          http://bit.ly/1uyGLyO#comment-2072

          Dom Sammut

          Hey Parag,

          Correct, you’ll need to perform the token refresh (and ensure it is successful) before any other API actions are done.

          In regards to good practice, without knowing the circumstances of your environment it’s hard to know what’s best. In saying that, what you’ve suggested seems perfectly reasonable.

          Cheers
          Dom

  3. Emmanuel
    http://bit.ly/1uyGLyO#comment-2062

    Emmanuel

    I’m getting this error: “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” when I execute the second script (‘upload.php’).

    I think that I have an error in the line: set_include_path($_SERVER[‘DOCUMENT_ROOT’ . ‘google-api-php’]); because I’m not getting a new token.

    Thanks for the post and the help.

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2063

      Dom Sammut

      Hey Emmanuel,

      Can you confirm that you have a refresh token text file generated from the first script?

      Cheers
      Dom

  4. Tihomir
    http://bit.ly/1uyGLyO#comment-2043

    Tihomir

    Hi Dom, friends,

    I use this guide in last 2 yeas with no problems. Now i build new instance and have problem with refresh token.

    Caught Google service Exception 400 message is Error refreshing the OAuth2 token, message: ‘{
    “error” : “invalid_request”,
    “error_description” : “Missing required parameter: refresh_token”
    }

    Any working solution ?

  5. Marcelo
    http://bit.ly/1uyGLyO#comment-2041

    Marcelo

    Hi Dom, thanks for the post.

    Do you know if is it possible to upload the video, directly to Youtube, instead of having it locally ?
    Example: User uploads a video using my server, which uses the API to send it to Youtube.

    Thanks!

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2042

      Dom Sammut

      Hey Marcelo,

      Yes this is possible, this post outlines how to implement a server-side upload like your example scenario.

      Cheers
      Dom

  6. Tran Binh
    http://bit.ly/1uyGLyO#comment-2038

    Tran Binh

    i converted it to a function.
    it working fine in session time (one hour)
    But, after one hour, it’s cant be refresh token, error:

    Caught Google service Exception 400 message is Error refreshing the OAuth2 token, message: ‘{
    “error” : “invalid_request”,
    “error_description” : “Could not determine client ID from request.”
    }’Stack trace is #0 C:\AppServ\www\yt\Google\Auth\OAuth2.php(272): Google_Auth_OAuth2->refreshTokenRequest(Array)
    #1 C:\AppServ\www\yt\Google\Client.php(421): Google_Auth_OAuth2->refreshToken(‘1/cIxhHOXDN6hVL…’)
    #2 C:\AppServ\www\yt\testupload.php(36): Google_Client->refreshToken(‘1/cIxhHOXDN6hVL…’)
    #3 C:\AppServ\www\yt\testupload.php(127): uploadYoutube(‘token/DMGG0c24e…’, ‘file.mp4’, ‘test title’, ‘description her…’, ’22’, Array)
    #4 {main}

    Can you help me..! Please

  7. Javier
    http://bit.ly/1uyGLyO#comment-2036

    Javier

    Hi Dom

    I have the following error trying to get the token on your first script:

    Fatal error: Uncaught exception ‘Google_Auth_Exception’ with message ‘Could not json decode the token’

    I read all comments and try everything but I still didn’t solve it.
    Can you please tell me what is wrong?

    My code is the same than you only replacing ID client, secret, redirect and app name.

    Cheers

  8. Dennis
    http://bit.ly/1uyGLyO#comment-2032

    Dennis

    Hello. Thanx for the tutorial. I’ve made it as well.
    But my goal is a use of multiple accounts since they’ve limited Uploads count per day.
    So I wonder how to make it done without prompting each time new credentials on the consent screen?

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2033

      Dom Sammut

      Hey Dennis,

      You’d either need to grant access to all the different YouTube accounts to a single account or manually go through and store a refresh token for each account you’re looking to upload to. Not ideal, but not much else you can do at this point.

      Cheers
      Dom

  9. Developer
    http://bit.ly/1uyGLyO#comment-2014

    Developer

    How is one able to specify $videopath to a html file input in your script?

  10. Developer
    http://bit.ly/1uyGLyO#comment-2013

    Developer

    Does this allow users to upload videos to the owner of the application’s youtube channel? If not how would you do this?

  11. Den Zubr
    http://bit.ly/1uyGLyO#comment-2012

    Den Zubr

    Good day, Dom. Your post was very useful to me, but I have a question: did you ever stack a problem with PHP upload photo to Instagram the same way, that you here discribed.

    • Dom Sammut
      http://bit.ly/1uyGLyO#comment-2021

      Dom Sammut

      Hey Den,

      At this point in time Instagram doesn’t not have API support for creating content / posts.

      Cheers
      Dom

  12. MikeW
    http://bit.ly/1uyGLyO#comment-2010

    MikeW

    Hey,
    When I tried the code, it worked but it did not return any token though. It only printed a word “array” and followed my video list. The response url has state=xxxx&code=xxxx# with the # sign at the end. I think somehow the token got dropped. Where do you see them? This is happening to me for facebook php code too. a # sign in there so the server don’t see token response.

    • Rich
      http://bit.ly/1uyGLyO#comment-2097

      Rich

      I’m having the same issue. Did you ever get it figured out?

1 7 8 9
css.php