Found this useful? Love this post

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

26 Comments

  1. Terri
    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.

    • Dom Sammut
      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

      • Terri
        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!

  2. Levis
    https://www.domsammut.com/?p=1142#comment-1865

    Levis

    Thanks for sharing.
    I worked for me.

  3. Danny
    https://www.domsammut.com/?p=1142#comment-1863

    Danny

    Thank you for this! works like a charm.

  4. sayuj Raghavan
    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 ?

  5. The Doctor
    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?

    • Dom Sammut
      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

  6. rajeev
    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?

  7. David
    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.

    • David
      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() );

  8. Feedback after 75 days of use
    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.

  9. Mario Josifovski
    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.

    • Dom Sammut
      https://www.domsammut.com/?p=1142#comment-1849

      Dom Sammut

      Glad to have helped Mario :)

  10. Shabiq Hazarika
    https://www.domsammut.com/?p=1142#comment-1831

    Shabiq Hazarika

    Hello sir, is it possible to get the video url after uploading the video.??

    • Dom Sammut
      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

  11. Ivan S
    https://www.domsammut.com/?p=1142#comment-1824

    Ivan S

    Hi Dom, thanks for your article.
    I want to download mp4 format, its possible ?

  12. Steven
    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

  13. Bads
    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

  14. Rushikesh
    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.

  15. Top Results
    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?

    • iman
      https://www.domsammut.com/?p=1142#comment-2053

      iman

      Thank you soooooo much it works perfectly (y)

1 3 4 5 6 7 10
css.php