Found this useful? Love this post
312

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 to Dom Sammut Cancel reply

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

Preview Comment

25 Comments

  1. Thanks,
    https://www.domsammut.com/?p=1142#comment-1967

    Thanks,

    Hi Dom,

    Is it possible to use this script for uploading video to youtube and the file is coming from google storage bucket, if so what is the videpath I should use for upload.

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

      Dom Sammut

      Hi,

      I haven’t worked with Google Storage Bucket, you’d need to look at their API though and see if you are able to pull a file path.

      Cheers
      Dom

  2. Eddsters
    https://www.domsammut.com/?p=1142#comment-1961

    Eddsters

    Hey Dom,

    How can I allow the user to upload only to my channel and not prompt them for their own credentials? Does it have to do with saving the refresh token?? Or saving a token that can be used forever that was initialized with my account?

  3. Mohammad Asim Zamim
    https://www.domsammut.com/?p=1142#comment-1955

    Mohammad Asim Zamim

    Hey Dom,

    I have applied everything you told in this tutorial but still i am receiving error

    Fatal error: Maximum execution time of 120 seconds exceeded in C:wampwwwyoutube-uploadGoogleIOCurl.php on line 79

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

      Dom Sammut

      Hi Mohammad,

      It would appear you’re hitting PHP’s maximum execution time. You increase this time by modifying the following:

      ini_set('max_execution_time', 300); //300 seconds = 5 minutes

      More information on ini_set.

      Cheers
      Dom

  4. Mohammad Asim Zamim
    https://www.domsammut.com/?p=1142#comment-1954

    Mohammad Asim Zamim

    Dear dom
    I am adding this script but recieved this error when open first file please help me dom.

    Parse error: syntax error, unexpected ‘set_include_path’ (T_STRING) in C:xampphtdocsyoutube-uploadscript.php on line 4

    • Ahmed Samir
      https://www.domsammut.com/?p=1142#comment-1981

      Ahmed Samir

      same with me …

      did u solve it ??

  5. Alaa Sadik
    https://www.domsammut.com/?p=1142#comment-1953

    Alaa Sadik

    Another suggestion, to say Hello Dom, and remind those who are worry about loading the “key.txt” in the browser by others. Please create .htaccess and save it in the same directory of “the_key.txt”. Add the following info the .htacces file:
    ===
    SecFilterEngine Off

    deny from all

    ===
    but you still able to read the file using file_get_contents

  6. Hassan Shahbaz
    https://www.domsammut.com/?p=1142#comment-1941

    Hassan Shahbaz

    Hi Dom,
    Great tutorial i just want to know that can i hit this script from terminal in ubuntu.Actually i don’t want to run it on browser is it possible to run from terminal by simply typing this command (php sript.php).

    Thank You.

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

      Dom Sammut

      Hey Hassan,

      Thanks for your feedback!

      Interesting question, I’m assuming you already have the refresh token and are just looking to upload videos. You could call the script via the command line and have it look in a particular directory and upload any new videos. That was actually my implementation except it ran as a cronjob every hour. I can’t provide that section of code as it’s proprietary, but the main basis is here if you were looking to do that.

      Cheers
      Dom

      • Hassan Shahbaz
        https://www.domsammut.com/?p=1142#comment-1944

        Hassan Shahbaz

        Thanks Dom i also wanted to set the Cron job know i have set thta.

        But lastly i want to know that is there any way to upload video to specific playlist like if i have a playlist and want to add the video to that playlist.

          • Hassan Shahbaz
            https://www.domsammut.com/?p=1142#comment-1946

            Hassan Shahbaz

            Thanks Dom,

            Now i have successfully created playlist and upload video in that playlist but what if i have videos in folder and want to upload these videos to particular playlist of particular channel this is my actual concern.Please can you help me for that.

            Thanks in Advance.

  7. Rahul
    https://www.domsammut.com/?p=1142#comment-1939

    Rahul

    Hello Dom,

    Can you pls help me in creating folders because i am not able to follow the folder hierarchy.

    Thanks

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

      Dom Sammut

      Hi Rahul,

      The hierarchy can be fluid depending on where you’ve installed the Google PHP API Client on your server. You’ll just need to update the set_include_path and require_once to the appropriate locations. If you can ensure you have the oauth2callback.php at the root of your public web server directory you’ll be fine.

      I’d just make sure you’ve set all the variables if you’re running into include / require issues (which it sounds like you are).

      Cheers
      Dom

  8. Brett
    https://www.domsammut.com/?p=1142#comment-1930

    Brett

    Dom, thank you so much for this guide! Google really screwed a lot of people over by cutting support for direct uploads in v3, but your work saved me a lot of headache!

    When uploading large videos, we were noticing truncation, but I think I’ve identified and eliminated the issue. Would it make sense to you that each chunk being sent up would be authenticated individually? Would it also make sense that if the upload time exceeded the token expiration time, that it would result in truncation? If so, would you agree that the 2 solutions would be to check and re-authenticate in the while loop for uploading chunks or just raising the chunk size to greater than the video size?

    Thanks!

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

      Dom Sammut

      Hey Brett,

      Thanks for your comment!

      Yeah the cut direct upload removal in V3 was not ideal, but I can understand where Google / Youtube were coming from.

      In terms of your questions, authentication happens on every chunk, so yes, checking the token while in the loop to ensure it’s still valid (and re-authenticating if necessary) would be the right way to go.

      Let me know if you have any other questions / feedback as this can be helpful to other people looking to implement a solution :)

      Cheers
      Dom

  9. Dima
    https://www.domsammut.com/?p=1142#comment-1925

    Dima

    Hello, Dom!

    Thank you for your work. Tell me what you have in the file http: //localhost/oauth2callback.php

    I found the file, whether it is true?

    setAuthConfigFile(‘client_secrets.json’);
    $client->setRedirectUri(‘http://’ . $_SERVER[‘HTTP_HOST’] . ‘/oauth2callback.php’);
    $client->addScope(Google_Service_Drive::DRIVE_METADATA_READONLY);

    if (! isset($_GET[‘code’])) {
    $auth_url = $client->createAuthUrl();
    header(‘Location: ‘ . filter_var($auth_url, FILTER_SANITIZE_URL));
    } else {
    $client->authenticate($_GET[‘code’]);
    $_SESSION[‘access_token’] = $client->getAccessToken();
    $redirect_uri = ‘http://’ . $_SERVER[‘HTTP_HOST’] . ‘/’;
    header(‘Location: ‘ . filter_var($redirect_uri, FILTER_SANITIZE_URL));

    I use a remote hosting, authorization was, but I can not get a token

    I know that this matter has already been, but I could not understand, do not speak English, use a translator. I would appreciate your answer.

  10. Mike
    https://www.domsammut.com/?p=1142#comment-1923

    Mike

    Great tutorial Dom, thanks for sharing how to get started with what can be a rather tricky API. It certainly helped me!

    To anyone who is getting the error with a missing/undefined refresh token
    i.e.
    PHP Notice: Undefined property: stdClass::$refresh_token in …..

    and a

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

    The problem is not because of Dom’s code actually it’s because you did not save the initial app authorisation response in the_key.txt
    The refresh_token parameter is only returned the first time ever you authorise your app. If like me during the course of developing your app you needed to re-authenticate – it will save a version of the access token WITHOUT refresh_token.

    To get the refresh_token you need to go into your Google account and revoke access to your app. Then re-authenticate and the response you get this time will have the refresh_token. Make sure that your app saves this response correctly into the_key.txt or wherever (I’m using a db personally).

    Doing the above will ensure that your app “keeps alive”. You should only ever have to authenticate once using Dom’s “dummy script”

    Hope that helps!

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

      Dom Sammut

      Thanks Mike! Thanks for providing additional info for people who might be getting stuck, every little bit helps :)

  11. Dam
    https://www.domsammut.com/?p=1142#comment-1921

    Dam

    Hi Dom

    Thanks a lot for your detailed tutorial.

    I’ve followed your instructions, but I don’t succeed to get the token
    In fact, I fail on the last step. When oauth2callback.php redirects to index.php to show the token details…

    I have one error in my apache logs.

    [Fri Dec 11 12:37:53.079005 2015] [:error] [pid 2747] [client 127.0.0.1:42857] PHP Fatal error: Uncaught exception ‘Google_Service_Exception’ with message ‘{\n “error”: {\n “errors”: [\n {\n “domain”: “global”,\n “reason”: “insufficientPermissions”,\n “message”: “Insufficient Permission”\n }\n ],\n “code”: 403,\n “message”: “Insufficient Permission”\n }\n}\n’ in /var/www/html/vendor/google/apiclient/src/Google/Http/REST.php:120\nStack trace:\n#0 /var/www/html/vendor/google/apiclient/src/Google/Http/REST.php(80): Google_Http_REST::decodeHttpResponse(Object(GuzzleHttp\\Message\\Response), Object(GuzzleHttp\\Message\\Request))\n#1 [internal function]: Google_Http_REST::doExecute(Object(GuzzleHttp\\Client), Object(GuzzleHttp\\Message\\Request))\n#2 /var/www/html/vendor/google/apiclient/src/Google/Task/Runner.php(181): call_user_func_array(Array, Array)\n#3 /var/www/html/vendor/google/apiclient/src/Google/Http/REST.php(56): Google_Task_Runner->run()\n#4 /var/www/html/vendor/google/apiclient/src/Google/Client.php(779): Google_Http_REST::execute(Object(GuzzleHttp\\Client), Object(GuzzleHttp\\Message\\Request), Array)\n#5 / in /var/www/html/vendor/google/apiclient/src/Google/Http/REST.php on line 120

    When I try to debug this is the value of body variable in REST.php file
    body:{ “error”: { “errors”: [ { “domain”: “global”, “reason”: “insufficientPermissions”, “message”: “Insufficient Permission” } ], “code”: 403, “message”: “Insufficient Permission” } }

    I seems that Google reject the token creation.

    I have checked, everything, and my API configuration looks fine:
    -Project created
    -YouTube Data API enabled
    -credentials created
    -JSON file OK
    -JavaScript origin and authorized redirect working fine

    So if you have any idea?

    Thanks in advance for your help

    Dam

  12. divyanshu
    https://www.domsammut.com/?p=1142#comment-1919

    divyanshu

    Dom your article was very helpful me.first of all i would like to thanks and i have some another query to ask you i hope you will help. plz tell me how i will implement your code in case of youtube analytics api if i want to know analytics data of youtube video. i did some changes instead of Google_Service_Youtube class but this is giving error like –

    $youtube = new Google_Service_YouTube($client);
    $channelsResponse = $youtube->channels->listChannels(‘id,snippet,contentDetails,statistics,status’, array(
    ‘mine’ => ‘true’,
    ));

    this is giving error as shown below.

    $youtube = new Google_Service_YouTube($client);
    $channelsResponse = $youtube->channels->listChannels(‘id,snippet,contentDetails,statistics,status’, array(
    ‘mine’ => ‘true’,
    ));

  13. irfan shaikh
    https://www.domsammut.com/?p=1142#comment-1917

    irfan shaikh

    is it possible any user upload video using my portal it directly upload in youtube without asking any id to user it will my take my id which i all ready config…is it possible?

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

      Dom Sammut

      It sure is :)

css.php