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

75 Comments

  1. J.J.
    https://www.domsammut.com/?p=1142#comment-1269

    J.J.

    Hello Dom,

    Thanks for taking the time to share your knowledge on this with us!

    I successfully obtained an access_token with your script, however when I try to upload a movie clip, I get the following error message:

    Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 401: youtube.header, Unauthorized)Stack trace is #0 /var/www/html/externals/youtube_v3/src/Google/Http/MediaFileUpload.php(134): Google_Http_MediaFileUpload->getResumeUri() #1 /var/www/html/youtube_test/upload.php(89): Google_Http_MediaFileUpload->nextChunk(‘????ftypisom???…’) #2 {main}

    Any idea what this could be?

    Thank you and kind regards,
    J.J.

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

      Dom Sammut

      Hey J.J.,

      Thanks for the kind words.

      If you haven’t already, can you have a look at this comment and the thread below it.

      If the suggestions in that thread don’t help, can you var_dump() your $media variable after line 80 on my example.

      Cheers
      Dom

      • Patrick
        https://www.domsammut.com/?p=1142#comment-1683

        Patrick

        I got exactly the same error, below is I var dump $media:

        object(Google_Http_MediaFileUpload)[32]
        private ‘mimeType’ => string ‘video/*’ (length=7)
        private ‘data’ => null
        private ‘resumable’ => boolean true
        private ‘chunkSize’ => int 1048576
        private ‘size’ => int 219616
        private ‘resumeUri’ => null
        private ‘progress’ => int 0
        private ‘client’ =>
        object(Google_Client)[1]
        private ‘auth’ =>
        object(Google_Auth_OAuth2)[3]
        private ‘assertionCredentials’ => null
        private ‘state’ => null
        private ‘token’ =>
        array (size=5)

        private ‘client’ =>
        &object(Google_Client)[1]
        private ‘io’ => null
        private ‘cache’ => null
        private ‘config’ =>
        object(Google_Config)[2]
        protected ‘configuration’ =>
        array (size=7)

        private ‘logger’ =>
        object(Google_Logger_Null)[25]
        protected ‘level’ => int 100
        protected ‘logFormat’ => string ‘[%datetime%] %level%: %message% %context%
        ‘ (length=42)
        protected ‘dateFormat’ => string ‘d/M/Y:H:i:s O’ (length=13)
        protected ‘allowNewLines’ => boolean true
        private ‘deferExecution’ => boolean true
        protected ‘requestedScopes’ =>
        array (size=3)
        0 => string ‘https://www.googleapis.com/auth/youtube.upload’ (length=46)
        1 => string ‘https://www.googleapis.com/auth/youtube’ (length=39)
        2 => string ‘https://www.googleapis.com/auth/youtubepartner’ (length=46)
        protected ‘services’ =>
        array (size=0)
        empty
        private ‘authenticated’ => boolean false
        private ‘request’ =>
        object(Google_Http_Request)[41]
        private ‘batchHeaders’ =>
        array (size=3)
        ‘Content-Type’ => string ‘application/http’ (length=16)
        ‘Content-Transfer-Encoding’ => string ‘binary’ (length=6)
        ‘MIME-Version’ => string ‘1.0’ (length=3)
        protected ‘queryParams’ =>
        array (size=2)
        ‘part’ => string ‘status,snippet’ (length=14)
        ‘uploadType’ => string ‘resumable’ (length=9)
        protected ‘requestMethod’ => string ‘POST’ (length=4)
        protected ‘requestHeaders’ =>
        array (size=2)
        ‘content-type’ => string ‘video/*’ (length=7)
        ‘authorization’ => string ‘Bearer ya29.DwFgfwoiKWReevLO2BqIBIHnSZMtixO3IB-0yf3WQbFBow_psWMwFpdH_j8wgdkKd2THJBZsB_uHDA’ (length=90)
        protected ‘baseComponent’ => string ‘https://www.googleapis.com/upload’ (length=33)
        protected ‘path’ => string ‘/youtube/v3/videos’ (length=18)
        protected ‘postBody’ => string ‘{“snippet”:{“categoryId”:”22″,”tags”:[“arefilm”],”title”:”test”},”status”:{“privacyStatus”:”private”}}’ (length=102)
        protected ‘userAgent’ => null
        protected ‘canGzip’ => null
        protected ‘responseHttpCode’ => null
        protected ‘responseHeaders’ => null
        protected ‘responseBody’ => null
        protected ‘expectedClass’ => string ‘Google_Service_YouTube_Video’ (length=28)
        public ‘accessKey’ => null
        private ‘boundary’ => boolean false
        private ‘httpResultCode’ => null

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

          Dom Sammut

          Hi Patrick,

          I assume you’ve received the error in the comment you’ve replied to? I’ve checked the access token you supplied in your dump at it is valid (though it is soon to expire, which is expected).

          Can you try uploading a small video file, something under 10mb? Can I also confirm you’re using version 1.0.5 of the API?

          Cheers
          Dom

          • Patrick
            https://www.domsammut.com/?p=1142#comment-1685

            Patrick

            Hi Dom,

            Just noticed that I am not using the correct version. Now it works! Billion thanks!

            Thanks,
            Patrick

          • Developer
            https://www.domsammut.com/?p=1142#comment-1874

            Developer

            Getting the following errors:

            Caught Google service Exception 0 message is Failed to start the resumable uploadStack trace is #0 C:\xampp\htdocs\youtubev2\google-api-php-client-1.0.5-beta\src\Google\Http\MediaFileUpload.php(142): Google_Http_MediaFileUpload->getResumeUri() #1 C:\xampp\htdocs\youtubev2\upload2.php(102): Google_Http_MediaFileUpload->nextChunk(‘??? ftypisom???…’) #2 {main}

            Note: I have installed a XAMPP on Server. The first part of key creation works fine and it displayed Video Name in the Channel.

            I have changed library version as you mentioned in the comments.

            Checked with media object and video size is also displayed.

            • Developer
              https://www.domsammut.com/?p=1142#comment-1875

              Developer

              I could solve my issue. It was related to my Youtube account.

              I have printed different variables and responses in “src\Google\Http\MediaFileUpload.php -> getResumeUri()”. One of such output contains the text “reason” : “youtubeSignupRequired” Comments already contains the steps to solve this issue. I followed the steps and could solve it.

              To test Video Uploading to Youtube, I created a new gmail account. Then login into Youtube using new Gmail. I created a Channel in Youtube because it doesn’t have any in the beginning. Then I moved back to code and started testing. What I remember is, the first step of generating Key worked fine. And I could see the “Video Name” in the output. Then after getting above error, I logged out from Youtube and login again. This time, a dialog box appears with a question “Use Yutube as “My Gmail xx Name” or “Youtube Profile Name I given as part of Channel creation”. I selected Gmail and now the issue is resolved. So even after gmail Sign In using Youtube, there is still possibility of the above error.

              Thank you for the good article and your efforts to answer all comments.

    • birbal
      https://www.domsammut.com/?p=1142#comment-1910

      birbal

      i think this is outdated. i’m using google api v3. and in responce i get {“access_token”:”ya29.IAJBeu4xafxuf8s8ojtPKwOY5JMlzHXXQGrrWW2X8UvYGszaOv6r-VQIJfdUaWqx3iwQxdvMcg”,”token_type”:”Bearer”,”expires_in”:3599,”created”:1446466871} .
      there is no refresh token which i’m looking for. i’m also trying to upload video on youtube channel from my development server

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

        Dom Sammut

        Hi Birbal,

        No this isn’t outdated, refresh tokens are still used as per Google Documentation. I also re-ran the script and successfully harvested a token.

        Cheers
        Dom

        • Fabio
          https://www.domsammut.com/?p=1142#comment-1913

          Fabio

          hi Dom,

          i have some trovel to ipload videos after token expired. only me use the app. not for other user. is like to upload the recorded videos on my local app to youtube. what im doing wrong?

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

            Dom Sammut

            Hi Fabio,

            You’ll need to implement the refresh token which will in turn allow you to generate a new access token.

            Cheers
            Dom

  2. Cris
    https://www.domsammut.com/?p=1142#comment-1135

    Cris

    Hi,

    Something is causing my token to expire.

    My script is bulk uploading videos, before i was able to upload about 5k videos a day.
    But after some days i’m getting token expired after about 1h of running script.

    Any ideas?

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

      Dom Sammut

      Hey Cris,

      Can you confirm that you are regenerating your access token if it is expired? If so can you provide the snippet of code that currently attempts to do this?

      Cheers
      Dom

      • Cris
        https://www.domsammut.com/?p=1142#comment-1162

        Cris

        I don’t know how to regenerate it, without accesing token.php and copy paste it to my the_key.txt all manually.

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

          Dom Sammut

          Hey Cris,

          Can you confirm the following code is executing?

                  /**
                   * 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());
                  }
          
          • Cris
            https://www.domsammut.com/?p=1142#comment-1226

            Cris

            Yes I have that in my code.

            This what i got from google, when enter token.php

            “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.”

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

              Dom Sammut

              Hi Cris,

              Can you var_dump():

              • $newToken
              • $client->refreshToken

              Cheers
              Dom

            • Cris
              https://www.domsammut.com/?p=1142#comment-1242

              Cris

              /**
               * 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());
                  var_dump($newToken);
                  $client->refreshToken($newToken->refresh_token);
                  var_dump($client->refreshToken);
                  file_put_contents('the_key.txt', $client->getAccessToken());
              }
              
              
              var_dump($newToken):
              
              object(stdClass)#115 (4) {
                ["access_token"]=>
                string(83) "ya29.6AChs7osF1HIW5CLxazYHVaILDUOgu8SXxW6pV-htstXas_cIZjHbNjkrXyo06HdnYEKVsoqUDXXXX"
                ["token_type"]=>
                string(6) "Bearer"
                ["expires_in"]=>
                int(3600)
                ["created"]=>
                int(1419556745)
              }
              
              var_dump($client->refreshToken): no output, it's ok what i'm doing?
              
              

              PHP Notice: Undefined property: stdClass::$refresh_token in /home…./Google/functions.php on line 420

              Notice: Undefined property: stdClass::$refresh_token in /home/…./Google/functions.php on line 420
              Caught Google service Exception 400 message is Error refreshing the OAuth2 token, message: ‘{
              “error” : “invalid_request”,
              “error_description” : “Missing required parameter: refresh_token”

              Thx

            • Cris
              https://www.domsammut.com/?p=1142#comment-1267

              Cris

              Hi, var_dump(), output is blank.
              No refresh token is added to the_key.txt,
              Also it’s not showing EXPIRED or NOT EXPIRED, but it’s giving error on line 196.

              Notice: Undefined property: stdClass::$refresh_token in /home/vhosts/…./functions.php on line 196
              Caught Google service Exception 400 message is Error refreshing the OAuth2 token, message: ‘{
              “error” : “invalid_request”,
              “error_description” : “Missing required parameter: refresh_token”

              ————————————————

              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()) {
              		    echo "EXPIRED";
                          $newToken = json_decode($client->getAccessToken());
                          $client->refreshToken($newToken->refresh_token); //line 196
              			var_dump($client->getAccessToken());
                          file_put_contents('the_key.txt', $client->getAccessToken());
                      }else{
              		
              		echo "NOT EXPIRED!";
              		}
               
                      $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);
               
                      /**
                       * Vidoe 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
                      }
               
                      // 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();
              }
              
              
            • Dom Sammut
              https://www.domsammut.com/?p=1142#comment-1276

              Dom Sammut

              Hi Cris,

              If you don’t have a refresh token in your “the_key.txt”, you’ll need to run the Getting your refresh token part of the guide again as it would appear you’ve lost your refresh token at some point along the way.

              Cheers
              Dom

  3. Luna
    https://www.domsammut.com/?p=1142#comment-1117

    Luna

    Hi. I was able to figure out how to upload a video to my youtube channel with the api thanks to you and this article. Now I’m trying to figure out the api for discogs but I’m completely lost. Basically, I’d like to let users of my website complete a search by artist or song title and the results show up. I signed up at discogs and recieved a consumer key and a consumer secret, however, I don’t know what to do from there. I read all of the developer pages and attempted to interpret the single php oauth example from github, but I was unsuccessful. I mainly know javascript but am learning some php. Could you help me out? Thanks.

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

      Dom Sammut

      Hey Luna,

      I’m glad you’ve found this article useful. In regards to the discogs API. I’ve never even heard of that site until your comment. I won’t be of much help in attempting to find a solution for it. It appears they have good documentation so I’d suggest trying to find some similar examples or continue learning PHP as you’ve said you’re doing as they’re your best bets at this point.

      Cheers
      Dom

      • Luna
        https://www.domsammut.com/?p=1142#comment-1121

        Luna

        Ok, thanks for the reply.

  4. Avinash
    https://www.domsammut.com/?p=1142#comment-1100

    Avinash

    Hi Dom,

    I have got following error while executing your code.
    Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 401: global, Invalid Credentials)Stack trace is #0 /mnt/hgfs/tapes/POC/youtube/google-api/src/Google/Http/MediaFileUpload.php(134): Google_Http_MediaFileUpload->getResumeUri() #1 /mnt/hgfs/tapes/POC/youtube/uploadOnYoutube_1.php(100): Google_Http_MediaFileUpload->nextChunk(‘????ftypisom???…’) #2 {main}

    My vardump($media) object is

    object(Google_Http_MediaFileUpload)#32 (11) {
      ["mimeType":"Google_Http_MediaFileUpload":private]=>
      string(7) "video/*"
      ["data":"Google_Http_MediaFileUpload":private]=>
      NULL
      ["resumable":"Google_Http_MediaFileUpload":private]=>
      bool(true)
      ["chunkSize":"Google_Http_MediaFileUpload":private]=>
      int(1048576)
      ["size":"Google_Http_MediaFileUpload":private]=>
      int(3662871)
      ["resumeUri":"Google_Http_MediaFileUpload":private]=>
      NULL
      ["progress":"Google_Http_MediaFileUpload":private]=>
      int(0)
      ["client":"Google_Http_MediaFileUpload":private]=>
      object(Google_Client)#1 (9) {
        ["auth":"Google_Client":private]=>
        object(Google_Auth_OAuth2)#3 (4) {
          ["assertionCredentials":"Google_Auth_OAuth2":private]=>
          NULL
          ["state":"Google_Auth_OAuth2":private]=>
          NULL
          ["token":"Google_Auth_OAuth2":private]=>
          array(4) {
            ["access_token"]=>
            string(83) "ya29.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-xxxxxxxxxxxxxxxxx"
            ["token_type"]=>
            string(6) "Bearer"
            ["expires_in"]=>
            int(3588)
            ["created"]=>
            int(1418473131)
          }
          ["client":"Google_Auth_OAuth2":private]=>
          *RECURSION*
        }
        ["io":"Google_Client":private]=>
        NULL
        ["cache":"Google_Client":private]=>
        NULL
        ["config":"Google_Client":private]=>
        object(Google_Config)#2 (1) {
          ["configuration":protected]=>
          array(7) {
            ["application_name"]=>
            string(5) "Sepat"
            ["auth_class"]=>
            string(18) "Google_Auth_OAuth2"
            ["io_class"]=>
            string(14) "Google_IO_Curl"
            ["cache_class"]=>
            string(17) "Google_Cache_File"
            ["logger_class"]=>
            string(18) "Google_Logger_Null"
            ["base_path"]=>
            string(26) "https://www.googleapis.com"
            ["classes"]=>
            array(9) {
              ["Google_IO_Abstract"]=>
              array(1) {
                ["request_timeout_seconds"]=>
                int(100)
              }
              ["Google_Logger_Abstract"]=>
              array(4) {
                ["level"]=>
                string(5) "debug"
                ["log_format"]=>
                string(42) "[%datetime%] %level%: %message% %context%
    "
                ["date_format"]=>
                string(13) "d/M/Y:H:i:s O"
                ["allow_newlines"]=>
                bool(true)
              }
              ["Google_Logger_File"]=>
              array(3) {
                ["file"]=>
                string(12) "php://stdout"
                ["mode"]=>
                int(416)
                ["lock"]=>
                bool(false)
              }
              ["Google_Http_Request"]=>
              array(2) {
                ["disable_gzip"]=>
                bool(false)
                ["enable_gzip_for_uploads"]=>
                bool(false)
              }
              ["Google_Auth_OAuth2"]=>
              array(13) {
                ["client_id"]=>
                string(72) "xxxxxxxxx-xxxxxxxxxxxxxxxxx.apps.googleusercontent.com"
                ["client_secret"]=>
                string(24) "xxxxxxxxxxxxxxxxx"
                ["redirect_uri"]=>
                string(0) ""
                ["developer_key"]=>
                string(0) ""
                ["hd"]=>
                string(0) ""
                ["prompt"]=>
                string(0) ""
                ["openid.realm"]=>
                string(0) ""
                ["include_granted_scopes"]=>
                string(0) ""
                ["login_hint"]=>
                string(0) ""
                ["request_visible_actions"]=>
                string(0) ""
                ["access_type"]=>
                string(7) "offline"
                ["approval_prompt"]=>
                string(4) "auto"
                ["federated_signon_certs_url"]=>
                string(42) "https://www.googleapis.com/oauth2/v1/certs"
              }
              ["Google_Task_Runner"]=>
              array(5) {
                ["initial_delay"]=>
                int(1)
                ["max_delay"]=>
                int(60)
                ["factor"]=>
                int(2)
                ["jitter"]=>
                float(0.5)
                ["retries"]=>
                int(0)
              }
              ["Google_Service_Exception"]=>
              array(1) {
                ["retry_map"]=>
                array(4) {
                  [500]=>
                  int(-1)
                  [503]=>
                  int(-1)
                  ["rateLimitExceeded"]=>
                  int(-1)
                  ["userRateLimitExceeded"]=>
                  int(-1)
                }
              }
              ["Google_IO_Exception"]=>
              array(1) {
                ["retry_map"]=>
                array(5) {
                  [6]=>
                  int(-1)
                  [7]=>
                  int(-1)
                  [28]=>
                  int(-1)
                  [35]=>
                  int(-1)
                  [52]=>
                  int(-1)
                }
              }
              ["Google_Cache_File"]=>
              array(1) {
                ["directory"]=>
                string(18) "/tmp/Google_Client"
              }
            }
          }
        }
        ["logger":"Google_Client":private]=>
        object(Google_Logger_Null)#25 (4) {
          ["level":protected]=>
          int(100)
          ["logFormat":protected]=>
          string(42) "[%datetime%] %level%: %message% %context%
    "
          ["dateFormat":protected]=>
          string(13) "d/M/Y:H:i:s O"
          ["allowNewLines":protected]=>
          bool(true)
        }
        ["deferExecution":"Google_Client":private]=>
        bool(true)
        ["requestedScopes":protected]=>
        array(3) {
          [0]=>
          string(46) "https://www.googleapis.com/auth/youtube.upload"
          [1]=>
          string(39) "https://www.googleapis.com/auth/youtube"
          [2]=>
          string(46) "https://www.googleapis.com/auth/youtubepartner"
        }
        ["services":protected]=>
        array(0) {
        }
        ["authenticated":"Google_Client":private]=>
        bool(false)
      }
      ["request":"Google_Http_MediaFileUpload":private]=>
      object(Google_Http_Request)#41 (14) {
        ["batchHeaders":"Google_Http_Request":private]=>
        array(3) {
          ["Content-Type"]=>
          string(16) "application/http"
          ["Content-Transfer-Encoding"]=>
          string(6) "binary"
          ["MIME-Version"]=>
          string(3) "1.0"
        }
        ["queryParams":protected]=>
        array(2) {
          ["part"]=>
          string(14) "status,snippet"
          ["uploadType"]=>
          string(9) "resumable"
        }
        ["requestMethod":protected]=>
        string(4) "POST"
        ["requestHeaders":protected]=>
        array(2) {
          ["content-type"]=>
          string(7) "video/*"
          ["authorization"]=>
          string(90) "Bearer ya29.2wAo4wSxpnAYzNV5A9xyxf0Inwk7LAI_wNS7PvlyK_pi5SFbYsLoQr-xxxxxxxxxxxxxxxxx"
        }
        ["baseComponent":protected]=>
        string(33) "https://www.googleapis.com/upload"
        ["path":protected]=>
        string(18) "/youtube/v3/videos"
        ["postBody":protected]=>
        string(186) "{"snippet":{"categoryId":"22","description":"A video tutorial on how to upload to YouTube","tags":["youtube","tutorial"],"title":"A tutorial video"},"status":{"privacyStatus":"private"}}"
        ["userAgent":protected]=>
        NULL
        ["canGzip":protected]=>
        NULL
        ["responseHttpCode":protected]=>
        NULL
        ["responseHeaders":protected]=>
        NULL
        ["responseBody":protected]=>
        NULL
        ["expectedClass":protected]=>
        string(28) "Google_Service_YouTube_Video"
        ["accessKey"]=>
        NULL
      }
      ["boundary":"Google_Http_MediaFileUpload":private]=>
      bool(false)
      ["httpResultCode":"Google_Http_MediaFileUpload":private]=>
      NULL
    }
    

    Can you please tell me what’s wrong with my code?

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

      Dom Sammut

      Hi Avinash,

      If you’re receiving invalid credentials errors. I’d double check your tokens using the Google API to see exactly what the error is:

      https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=12345

      Just replace the 12345 with the access token used in the request. Note these tokens are time sensitive so you’ll need to check the API within 1 hour of generation.

      Cheers
      Dom

      • Avinash
        https://www.domsammut.com/?p=1142#comment-1129

        Avinash

        I used suggested API and got following response,

        {
         "issued_to": "XXXXXXX.apps.googleusercontent.com",
         "audience": "XXXXXXX.apps.googleusercontent.com",
         "scope": "https://www.googleapis.com/auth/youtube.upload https://www.googleapis.com/auth/youtube",
         "expires_in": 3263,
         "access_type": "offline"
        }
        

        Is my token is valid? but I still got the same error
        Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 401: global, Invalid Credentials)Stack trace is #0 /mnt/hgfs/sepat/POC/youtube/google-api/src/Google/Http/MediaFileUpload.php(134): Google_Http_MediaFileUpload->getResumeUri() #1 /mnt/hgfs/sepat/POC/youtube/uploadOnYoutube_1.php(100): Google_Http_MediaFileUpload->nextChunk(‘????ftypmp42???…’) #2 {main}

  5. Kipyegon Derricks
    https://www.domsammut.com/?p=1142#comment-1048

    Kipyegon Derricks

    This is super, it worked for me perfectly after a few tweaks. Problem is it uploads two videos

  6. warswitcherLukas Vollmer
    https://www.domsammut.com/?p=1142#comment-864

    warswitcherLukas Vollmer

    Hey, Dom.

    As David already say, with the current Google PHP API client the script is no longer working. Google has updatet the folder structure. Could you plese upload your version of your Google PHP API client?

    Thank you

  7. Cris
    https://www.domsammut.com/?p=1142#comment-844

    Cris

    Hi

    Do you see any strange, in my $media dump?

    I have same problem of: “failed to start resumable video”

    =============

    object(Google_Http_MediaFileUpload)#93 (11) {
      ["mimeType":"Google_Http_MediaFileUpload":private]=>
      string(7) "video/*"
      ["data":"Google_Http_MediaFileUpload":private]=>
      NULL
      ["resumable":"Google_Http_MediaFileUpload":private]=>
      bool(true)
      ["chunkSize":"Google_Http_MediaFileUpload":private]=>
      int(1048576)
      ["size":"Google_Http_MediaFileUpload":private]=>
      int(256343)
      ["resumeUri":"Google_Http_MediaFileUpload":private]=>
      NULL
      ["progress":"Google_Http_MediaFileUpload":private]=>
      int(0)
      ["client":"Google_Http_MediaFileUpload":private]=>
      object(Google_Client)#53 (8) {
        ["auth":"Google_Client":private]=>
        object(Google_Auth_OAuth2)#55 (4) {
          ["assertionCredentials":"Google_Auth_OAuth2":private]=>
          NULL
          ["state":"Google_Auth_OAuth2":private]=>
          NULL
          ["token":"Google_Auth_OAuth2":private]=>
          array(4) {
            ["access_token"]=>
            string(83) "ya29.ugD6xaBuGDh-wn8XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
            ["token_type"]=>
            string(6) "Bearer"
            ["expires_in"]=>
            int(3600)
            ["created"]=>
            int(1415663114)
          }
          ["client":"Google_Auth_OAuth2":private]=>
          *RECURSION*
        }
        ["io":"Google_Client":private]=>
        NULL
        ["cache":"Google_Client":private]=>
        NULL
        ["config":"Google_Client":private]=>
        object(Google_Config)#54 (1) {
          ["configuration":protected]=>
          array(6) {
            ["application_name"]=>
            string(7) "youtube"
            ["auth_class"]=>
            string(18) "Google_Auth_OAuth2"
            ["io_class"]=>
            string(14) "Google_IO_Curl"
            ["cache_class"]=>
            string(17) "Google_Cache_File"
            ["base_path"]=>
            string(26) "https://www.googleapis.com"
            ["classes"]=>
            array(4) {
              ["Google_IO_Abstract"]=>
              array(1) {
                ["request_timeout_seconds"]=>
                int(100)
              }
              ["Google_Http_Request"]=>
              array(2) {
                ["disable_gzip"]=>
                bool(false)
                ["enable_gzip_for_uploads"]=>
                bool(false)
              }
              ["Google_Auth_OAuth2"]=>
              array(13) {
                ["client_id"]=>
                string(72) "29293403XXXXXXXXXXXXX.apps.googleusercontent.com"
                ["client_secret"]=>
                string(24) "cMpWyh47ZTPXXXXXXXX"
                ["redirect_uri"]=>
                string(0) ""
                ["developer_key"]=>
                string(0) ""
                ["hd"]=>
                string(0) ""
                ["prompt"]=>
                string(0) ""
                ["openid.realm"]=>
                string(0) ""
                ["include_granted_scopes"]=>
                string(0) ""
                ["login_hint"]=>
                string(0) ""
                ["request_visible_actions"]=>
                string(0) ""
                ["access_type"]=>
                string(7) "offline"
                ["approval_prompt"]=>
                string(4) "auto"
                ["federated_signon_certs_url"]=>
                string(42) "https://www.googleapis.com/oauth2/v1/certs"
              }
              ["Google_Cache_File"]=>
              array(1) {
                ["directory"]=>
                string(18) "/tmp/Google_Client"
              }
            }
          }
        }
        ["deferExecution":"Google_Client":private]=>
        bool(true)
        ["requestedScopes":protected]=>
        array(3) {
          [0]=>
          string(46) "https://www.googleapis.com/auth/youtube.upload"
          [1]=>
          string(39) "https://www.googleapis.com/auth/youtube"
          [2]=>
          string(46) "https://www.googleapis.com/auth/youtubepartner"
        }
        ["services":protected]=>
        array(0) {
        }
        ["authenticated":"Google_Client":private]=>
        bool(false)
      }
      ["request":"Google_Http_MediaFileUpload":private]=>
      object(Google_Http_Request)#77 (14) {
        ["batchHeaders":"Google_Http_Request":private]=>
        array(3) {
          ["Content-Type"]=>
          string(16) "application/http"
          ["Content-Transfer-Encoding"]=>
          string(6) "binary"
          ["MIME-Version"]=>
          string(3) "1.0"
        }
        ["queryParams":protected]=>
        array(2) {
          ["part"]=>
          string(14) "status,snippet"
          ["uploadType"]=>
          string(9) "resumable"
        }
        ["requestMethod":protected]=>
        string(4) "POST"
        ["requestHeaders":protected]=>
        array(2) {
          ["content-type"]=>
          string(7) "video/*"
          ["authorization"]=>
          string(90) "Bearer ya29.ugD6xaBuGDh-wn8eqSrTvYiEXXXXXXXXXXXXXXXX"
        }
        ["baseComponent":protected]=>
        string(33) "https://www.googleapis.com/upload"
        ["path":protected]=>
        string(18) "/youtube/v3/videos"
        ["postBody":protected]=>
        string(3359) "{"snippet":{"categoryId":"22","description":"\r\n\r\n\r\nApple MacBook Air - Ordenador port\u00e1til de 13\" (Dual Core i5, 1.4 GHz, 4 GB de memoria, 128 GB de disco duro)\r\n\r\nPrecio rebajado desde 955 euros\r\nPrecio final del producto 929.00 euros.\r\nObtendr\u00e1 un ahorro de 26 euros, 3%.\r\n\r\nCOMPRAR: http:\/\/bit.ly\/1xdUg88\r\n\r\nPort\u00e1til ligero de 13\"Autonom\u00eda de hasta 12 horasEntorno a 1.3 Kg de pesoAlmacenamiento Flash ultravelozProcesadores Intel Core de cuarta generaci\u00f3n\r\nMacBook Air Toda la potencia que quieres. Todo el d\u00eda.  Hasta 12 horas de autonom\u00eda. El d\u00eda se acaba cuando t\u00fa lo digas. Ya puedes usar el MacBook Air desde que sales de casa hasta que vuelves sin pasar por un enchufe, porque la bater\u00eda del modelo de 13 pulgadas te dura nada menos que hasta 12 horas, y la del modelo de 11 pulgadas hasta 9 horas. Y si lo que quieres es darte un atrac\u00f3n de cine viendo pel\u00edculas de iTunes, tienes hasta 9 horas en el modelo de 11 pulgadas y hasta 12 horas en el de 13. Adem\u00e1s aguanta hasta un mes en reposo, as\u00ed que puedes pasarte semanas sin usarlo y estar\u00e1 listo cuando t\u00fa quieras. Fino. Ligero. Potente. Y preparado para Io que le eches. Con los procesadores Intel Core de cuarta generaci\u00f3n y los gr\u00e1ficos HD Graphics 5000 de Intel vas sobrado para hacer de todo y a todo trapo, desde retocar fotos hasta navegar por Internet. Y consumir\u00e1s poqu\u00edsima energ\u00eda. Toda esta potencia cabe en un dise\u00f1o Unibody de 1,7 cm de grosor y 1,08 kg de peso. Wi-Fi 802.11ac. La pr\u00f3xima generaci\u00f3n de conexi\u00f3n inal\u00e1mbrica. Con la nueva tecnolog\u00eda 802.11ac la conexi\u00f3n Wi-Fi del MacBook Air alcanza velocidades supers\u00f3nicas. Con\u00e9ctalo a una estaci\u00f3n base 802.11ac ?como la AirPort Extreme o AirPort Time Capsule? y la conexi\u00f3n te ir\u00e1 hasta el triple de r\u00e1pido que con la tecnolog\u00eda Wi?Fi anterior. Adem\u00e1s la se\u00f1al llega a\u00fan m\u00e1s lejos para que te muevas a tus anchas. Y a tus largas. Almacenamiento flash ultraveloz. Preparado. Listo. Hecho. El almacenamiento flash del MacBook Air es hasta 9 veces m\u00e1s r\u00e1pido que un disco duro de 5.400 rpm, por eso responde en un abrir y cerrar de ojos. Adem\u00e1s se activa al instante gracias al almacenamiento flash y a los procesadores Intel Core de cuarta generaci\u00f3n. Incluye apps geniales. Hora de trabajar. O no. Todos los Mac nuevos vienen de serie con iPhoto, iMovie, GarageBand, Pages, Numbers y Keynote, para que trabajes mejor y hagas aut\u00e9nticas virguer\u00edas desde el pr\r\n\r\nProducto disponible para envio inmediato. \r\nComprando ahora, recibir\u00e1 el pedido, pasado ma\u00f1ana.\r\nSe puede envolver para regalo.\r\n\r\nPara COMPRAR AHORA y ver MAS DETALLES:\r\n\r\nPincha este enlace: http:\/\/bit.ly\/1xdUg88\r\n\r\n\r\n\r\n\r\n","tags":["Apple MacBook Air","- Ordenador port","til de Dual","Core i GHz","GB de memoria","GB de disco","duro video ofertas","amazon comprar Ordenadores","personales Apple MD","Y B"],"title":"Comprar: Apple MacBook Air - Ordenador port\u00e1til de 13\" (Dual Core i5, 1.4 GHz, 4 GB de memoria, 128 GB de disco duro) Video - Ofertas"},"status":{"privacyStatus":"public"}}"
        ["userAgent":protected]=>
        NULL
        ["canGzip":protected]=>
        NULL
        ["responseHttpCode":protected]=>
        NULL
        ["responseHeaders":protected]=>
        NULL
        ["responseBody":protected]=>
        NULL
        ["expectedClass":protected]=>
        string(28) "Google_Service_YouTube_Video"
        ["accessKey"]=>
        NULL
      }
      ["boundary":"Google_Http_MediaFileUpload":private]=>
      bool(false)
      ["httpResultCode":"Google_Http_MediaFileUpload":private]=>
      NULL
    }
    

    Thanks
    Cris

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

      Dom Sammut

      Hey Cris,

      Having a very quick look, it would appear your title is too long. You can only have a maximum of 100 characters. You appear to have 139 characters. Just double check the limits for the snippet fields in the YouTube V3 API.

      Can you provide the stack trace dump as well if you’re still running into issues?

      Cheers
      Dom

      • Cris
        https://www.domsammut.com/?p=1142#comment-850

        Cris

        Hi,

        Yes title is limited to 100 chars and description to 5000 bytes, also they can’t include chars like “” .

        Thanks

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

          Dom Sammut

          Hey Cris,

          Once you made these changes where you able to successfully upload your video?

          Cheers
          Dom

          • Cris
            https://www.domsammut.com/?p=1142#comment-861

            Cris

            Yes it’s working.

            Not allowed chars are

    • minionsvevo
      https://www.domsammut.com/?p=1142#comment-854

      minionsvevo

      Thank You Thank You Thank You :) I’m very happy…

    • Abuzar
      https://www.domsammut.com/?p=1142#comment-1762

      Abuzar

      HI,
      I’ve just used google apis with help of your tutorial, and I must say it’s awesome. I’ve a question to ask.
      Is there any way through which I can upload all videos to a specific channel?
      currently videos are being uploaded on logged in user’s channel. I just want that whenever any user upload a video, it must be uploaded on my channel as user is using my platform.
      Thanks in advance.

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

        Dom Sammut

        Hi Abuzar,

        Thank you for the kind words.

        In regards to your question, based on this tutorial, your users don’t have to be authenticate with YouTube, they should just be interacting with your web server. Your web server then handles the upload of those videos so they should only be uploading to the account you have authenticated server-side.

        Cheers
        Dom

  8. Filip
    https://www.domsammut.com/?p=1142#comment-786

    Filip

    Hi, thanks for this tutorial. its help a lot.
    But do you know how to implement the browser-based upload using only v3 api php without zend framework and without curl too?? Just youtube v3 api php.
    I was searching for weeks for this implementation.
    Thanks in advance!! it will help me…

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

      Dom Sammut

      Hi Filip,

      I haven’t had the need to build this, but it wouldn’t be too hard based off the existing code above. If you’re using the Google PHP API it’s underlying code utilises CURL to upload just so you’re aware.

      I would also ensure that you don’t have the upload ability on a public-facing website as this potentially anyone can upload to your account (which could lead to your account getting banned). Ensure that there is authentication required before they are able to upload video(s).

      As for the line-for-line implementation of this, I don’t have the time (or need) to build something like this. If you do come up with a solution, please share the final product or work in progress.

      Cheers
      Dom

      • Filip
        https://www.domsammut.com/?p=1142#comment-809

        Filip

        Hi, thanks for your fast response.
        I didnt know about that v3 uses curl inside the api.
        However, I still searching the solution because
        I have all my code organized to use the api methods.
        Thanks for the advice…

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

          Dom Sammut

          Hi Filip,

          I’m not exactly sure what you’re issue is then?

          If you are able to use the Google PHP API you can follow my example in this post and implement a solution.

          If your server has CURL disabled, I haven’t developed an alternative that runs without CURL and you won’t be able to use the Google API either, so you’d most likely have to start from scratch or write an extension of API for the upload function that utilizes an alternative method.

          Cheers
          Dom

  9. Webbho
    https://www.domsammut.com/?p=1142#comment-746

    Webbho

    How come I do not have the value ” refresh_token “: ” XXXXXXX ” in my output ?

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

      Dom Sammut

      Hey Webbho,

      Can you let me know what part of the script you’re up to?

      Cheers
      Dom

    • Mario
      https://www.domsammut.com/?p=1142#comment-2039

      Mario

      I also did not get the refresh_token value in first attempt.

      I added
      $client->setApprovalPrompt('force');
      in the line 34 of oauth2callback.php and its works now.

  10. Webbho
    https://www.domsammut.com/?p=1142#comment-745

    Webbho

    I get this error, ideas ?

    Notice: Undefined property: stdClass::$refresh_token in /var/www/vhosts/…youtube.php on line 39 Fatal error: Uncaught exception ‘Google_Auth_Exception’ with message ‘Error refreshing the OAuth2 token, message: ‘{ “error” : “invalid_request”, “error_description” : “Missing required parameter: refresh_token” }” in /var/www/vhosts/…google-api-php-client-master/src/Google/Auth/OAuth2.php:345 Stack trace: #0 /var/www/vhosts/…google-api-php-client-master/src/Google/Auth/OAuth2.php(269): Google_Auth_OAuth2->refreshTokenRequest(Array) #1 /var/www/vhosts/…google-api-php-client-master/src/Google/Client.php(412): Google_Auth_OAuth2->refreshToken(NULL) #2 /var/www/vhosts/…youtube.php(39): Google_Client->refreshToken(NULL) #3 {main} thrown in /var/www/vhosts/…google-api-php-client-master/src/Google/Auth/OAuth2.php on line 345

    • Fabb
      https://www.domsammut.com/?p=1142#comment-890

      Fabb

      Hi,
      I have the same problem, how did you solve it?

  11. Jakub
    https://www.domsammut.com/?p=1142#comment-670

    Jakub

    Hello Dom,
    I fixed the problem before was my categoryId I wrote it wrong like you said. A question about that the categoryID can be what numbers I want or there are specific id’s I didn’t understand this.
    Second question I have to create a similar system to this but for videos in a database. I have to send a selected video from the DB from the customer to the youtube Chanel. Do you have any suggestion how to make this or something I can look it?
    Thank you a lot for your help.

    Cheers,
    Jakub

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

      Dom Sammut

      That’s great to hear. I think category numbers are region specific. To be honest, I just went to youtube and uploaded a video and inspected the category select box and picked the id from there.

      In regards to creating a system, I can’t imagine it would be too difficult to create something basic. As to examples I don’t have any off the top of my head. You should be able to integrate the upload method relatively easily into an existing system.

      Cheers
      Dom

  12. sameer
    https://www.domsammut.com/?p=1142#comment-651

    sameer

    Fatal error: Maximum execution time of 30 seconds exceeded in E:\xampp\htdocs\googleapi\src\Google\IO\Curl.php on line 80

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

      Dom Sammut

      Hi Sameer,

      You’ll need to provide a little more information than that.

      Cheers
      Dom

      • Jakub
        https://www.domsammut.com/?p=1142#comment-654

        Jakub

        Hello,
        I’m Jakub and I’m trying your script but when I arrived at that moment when there is the auth2callback I received object don’t found. I tried to create a file auth2callback.php but when open in the browser is empty without the refresh token,.
        This is my code:

        // Call to Google API
        set_include_path($_SERVER['DOCUMENT_ROOT'] . '/YoutubeKTF/Google/src/Google');
        require_once './Google/src/Google/Client.php';
        require_once './Google/src/Google/Service/YouTube.php';
        session_start();
        
        /*
         * Google APP credential OAUTH2
         *
         *
         */
        $OAUTH2_CLIENT_ID = 'xxxx.apps.googleusercontent.com';
        $OAUTH2_CLIENT_SECRET = 'xxxx';
        $REDIRECT = 'http://localhost/youtubeKTF/oauth2callback.php';
        $APPNAME = "digital_souvenir";
        

        Thanks for help

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

          Dom Sammut

          Hey Jakub,

          I have a number of steps to check / get back to me with answers:

          1. Check your PHP error.log to see if that provides you with any further insight into what is occurring
          2. Can I also confirm that you have all of the code from my first example (all 107 lines) in your file?
          3. When you first load your oauth2callback.php file in your web browser it shows “Authorisation Required”?

          Cheers
          Dom

          • Jakub
            https://www.domsammut.com/?p=1142#comment-657

            Jakub

            Hello Dom,
            The first part I fixed. I changed the callback to my directory in localhost and then works. Now I have this problem when I try to upload

            Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 400: youtube.video, Bad Request)Stack trace is #0 /Applications/XAMPP/xamppfiles/htdocs/YoutubeKTF/Google/src/Google/Http/MediaFileUpload.php(134): Google_Http_MediaFileUpload->getResumeUri() #1 /Applications/XAMPP/xamppfiles/htdocs/YoutubeKTF/upload.php(95): Google_Http_MediaFileUpload->nextChunk('\x00\x00\x00\x18ftypmp42\x00\x00\x00...') #2 {main}

            Can you explain the meaning of this?

            Cheers
            Jakub

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

              Dom Sammut

              Hey Jakub,

              Glad to hear you sorted out the first part.

              Can you confirm that your script is reading in the $key file correctly. Can you also var_dump your $media variable after line 80 on my example.

              Also maybe try uploading a different video file to see if that makes any difference.

              It might also be handy to have a look at code in the Google PHP API that threw the error:

              Cheers
              Dom

            • Jakub
              https://www.domsammut.com/?p=1142#comment-659

              Jakub

              Hey Dom,
              I corrected the permission on that file and the var_dump output of $media is:

              object(Google_Http_MediaFileUpload)#41 (11) {
                  ["mimeType":"Google_Http_MediaFileUpload":private]=>
                  string(7) "video/*"
                  ["data":"Google_Http_MediaFileUpload":private]=>
                  NULL
                  ["resumable":"Google_Http_MediaFileUpload":private]=>
                  bool(true)
                  ["chunkSize":"Google_Http_MediaFileUpload":private]=>
                  int(1048576)
                  ["size":"Google_Http_MediaFileUpload":private]=> int(0)
                  ["resumeUri":"Google_Http_MediaFileUpload":private]=>
                  NULL
                  ["progress":"Google_Http_MediaFileUpload":private]=> int(0)
                  ["client":"Google_Http_MediaFileUpload":private]=>
                  object(Google_Client)#1 (8) {
                      ["auth":"Google_Client":private]=>
                      object(Google_Auth_OAuth2)#3 (4) {
                          ["assertionCredentials":"Google_Auth_OAuth2":private]=>
                          NULL
                          ["state":"Google_Auth_OAuth2":private]=>
                          NULL
                          ["token":"Google_Auth_OAuth2":private]=>
                          array(5) {
                              ["access_token"]=>
                              string(83) "ya29.XXXXXXXXXXXXXXXXXXXXXXXXXXX"
                              ["token_type"]=>
                              string(6) "Bearer"
                              ["expires_in"]=>
                              int(3600)
                              ["refresh_token"]=>
                              string(45) "1/XXXXXXXXXXXXXXXXXXXXXXXXXXX"
                              ["created"]=>
                              int(1414071926)
                          }
                          ["client":"Google_Auth_OAuth2":private]=>
                          *RECURSION*
                      }
                      ["io":"Google_Client":private]=>
                      NULL
                      ["cache":"Google_Client":private]=>
                      NULL
                      ["config":"Google_Client":private]=>
                      object(Google_Config)#2 (1) {
                          ["configuration":protected]=>
                          array(6) {
                              ["application_name"]=>
                              string(16) "digital souvenir"
                              ["auth_class"]=>
                              string(18) "Google_Auth_OAuth2"
                              ["io_class"]=> string(14) "Google_IO_Curl"
                              ["cache_class"]=>
                              string(17) "Google_Cache_File"
                              ["base_path"]=>
                              string(26) "https://www.googleapis.com"
                              ["classes"]=>
                              array(4) {
                                  ["Google_IO_Abstract"]=>
                                  array(1) {
                                      ["request_timeout_seconds"]=>
                                      int(100)
                                  }
                                  ["Google_Http_Request"]=>
                                  array(2) {
                                      ["disable_gzip"]=>
                                      bool(false)
                                      ["enable_gzip_for_uploads"]=>
                                      bool(false)
                                  }
                                  ["Google_Auth_OAuth2"]=>
                                  array(13) {
                                      ["client_id"]=>
                                      string(72) "XXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com"
                                      ["client_secret"]=>
                                      string(24) "XXXXXXXXXXXXXXXXXXXXXXXXXXX"
                                      ["redirect_uri"]=>
                                      string(0) ""
                                      ["developer_key"]=>
                                      string(0) ""
                                      ["hd"]=>
                                      string(0) ""
                                      ["prompt"]=>
                                      string(0) ""
                                      ["openid.realm"]=>
                                      string(0) ""
                                      ["include_granted_scopes"]=>
                                      string(0) ""
                                      ["login_hint"]=>
                                      string(0) ""
                                      ["request_visible_actions"]=>
                                      string(0) ""
                                      ["access_type"]=>
                                      string(7) "offline"
                                      ["approval_prompt"]=>
                                      string(4) "auto"
                                      ["federated_signon_certs_url"]=>
                                      string(42) "https://www.googleapis.com/oauth2/v1/certs"
                                  }
                                  ["Google_Cache_File"]=>
                                  array(1) {
                                      ["directory"]=>
                                      string(62) "/var/folders/6n/8gmbsz5j711f6qs_bhy6mjn40000gn/T/Google_Client"
                                  }
                              }
                          }
                      }
                      ["deferExecution":"Google_Client":private]=>
                      bool(true)
                      ["requestedScopes":protected]=>
                      array(3) {
                          [0]=>
                          string(46) "https://www.googleapis.com/auth/youtube.upload"
                          [1]=>
                          string(39) "https://www.googleapis.com/auth/youtube"
                          [2]=>
                          string(46) "https://www.googleapis.com/auth/youtubepartner"
                      }
                      ["services":protected]=>
                      array(0) {
                      }
                      ["authenticated":"Google_Client":private]=>
                      bool(false)
                  }
                  ["request":"Google_Http_MediaFileUpload":private]=>
                  object(Google_Http_Request)#25 (14) {
                      ["batchHeaders":"Google_Http_Request":private]=>
                      array(3) {
                          ["Content-Type"]=>
                          string(16) "application/http"
                          ["Content-Transfer-Encoding"]=>
                          string(6) "binary"
                          ["MIME-Version"]=>
                          string(3) "1.0"
                      }
                      ["queryParams":protected]=>
                      array(2) {
                          ["part"]=>
                          string(14) "status,snippet"
                          ["uploadType"]=>
                          string(9) "resumable"
                      }
                      ["requestMethod":protected]=>
                      string(4) "POST"
                      ["requestHeaders":protected]=>
                      array(2) {
                          ["content-type"]=>
                          string(7) "video/*"
                          ["authorization"]=>
                          string(90) "Bearer ya29.XXXXXXXXXXXXXXXXXXXXXXXXXXX"
                      }
                      ["baseComponent":protected]=>
                      string(33) "https://www.googleapis.com/upload"
                      ["path":protected]=>
                      string(18) "/youtube/v3/videos"
                      ["postBody":protected]=>
                      string(157) "{"snippet":{"categoryId":"testing","description":"Test upload to youtube","tags":["youtube","tutorial"],"title":"Test"},"status":{"privacyStatus":"private"}}"
                      ["userAgent":protected]=>
                      NULL
                      ["canGzip":protected]=>
                      NULL
                      ["responseHttpCode":protected]=>
                      NULL
                      ["responseHeaders":protected]=>
                      NULL
                      ["responseBody":protected]=>
                      NULL
                      ["expectedClass":protected]=>
                      string(28) "Google_Service_YouTube_Video"
                      ["accessKey"]=> NULL
                  }
                  ["boundary":"Google_Http_MediaFileUpload":private]=>
                  bool(false)
                  ["httpResultCode":"Google_Http_MediaFileUpload":private]=>
                  NULL
              }
              


              Caught Google service Exception 0 message is Failed to start the resumable upload (HTTP 400: youtube.video, Bad Request)Stack trace is #0 /Applications/XAMPP/xamppfiles/htdocs/YoutubeKTF/Google/src/Google/Http/MediaFileUpload.php(134): Google_Http_MediaFileUpload->getResumeUri() #1 /Applications/XAMPP/xamppfiles/htdocs/YoutubeKTF/upload.php(96): Google_Http_MediaFileUpload->nextChunk('\x00\x00\x00\x18ftypmp42\x00\x00\x00...') #2 {main}

              Cheers
              Jakub

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

              Dom Sammut

              Hi Jakub,

              It would appear from that dump, that your $videoPath isn’t referencing a video file. I’m basing this off the size parameter in the dump that has a value of 0.

              If you check $media->setFileSize(filesize($videoPath)); on line 80 and ensure that it is outputting the correct file size.

              Let me know how that goes.

              Cheers
              Dom

            • Jakub
              https://www.domsammut.com/?p=1142#comment-661

              Jakub

              Hey, really thanks for your help. I’m checked what you suggest and I received NULL. I have no idea how to fix it. I must put this video in youtube because is for a project at my company. The video is 8.4MB mp4 HD and the company wants the videos in this hight quality version so I need a solution to process files of this dimension to youtube if this is the main issue.
              Thanks a lot.

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

              Dom Sammut

              Hey Jakub,

              8.4MB for a video isn’t a large size, so it shouldn’t be an issue related to that. I’d double check that you have specified a valid path ($videoPath) to your file if you’re receiving NULL. Also when you var_dump(filesize($videoPath)); it should return a value around 8808038 (based on 8.4MB conversion to bytes).

              Cheers
              Dom

            • Jakub
              https://www.domsammut.com/?p=1142#comment-668

              Jakub

              Hello Dom,

              The output of the $videoPath is the path of my video string(13) “video/1mb.mp4 and the other output like you said is the size int(1780768). I var_dump($media->setFileSize(filesize($videoPath)); and output is null.
              I’m sending you my scripts so you can take a look what is wrong. Maybe I did something no correct.
              Thanks a lot.

              Cheers,
              Jakub

              http://laravel.io/bin/yLyk7 Index.php
              http://laravel.io/bin/5a0eX Upload.php
              http://laravel.io/bin/klG2e LogIn.php

            • Burak
              https://www.domsammut.com/?p=1142#comment-1088

              Burak

              Hi Dom

              Thanks for the great doc! I have trouble creating the oauth2callback.php. Can you please give the code example for this?

              I have uploaded the files to my server. Google says that “http://example.com” but you have written localhost, which one can I use? I do not use this at my own PC, I will use this in my remote webserver.

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

              Dom Sammut

              Hi Burak,

              Thanks for the kind feedback.

              In regards to creating the oauth2callback.php, the code is in this post. It is the first code example. If you read through the code, you’ll see this line (18):

              $REDIRECT = 'http://localhost/oauth2callback.php'
              

              This should be the URL that you’ll be accessing the oauth2callback.php file in your web browser (note, you might run into problems using file://).

              You should only need to use the oauth2callback.php file once to generate your access token that will also provide you with a refresh token. This can be done on your local machine or a web server.

              You can then save these values and generate new access tokens off your refresh token if you have an expired access token as demonstrated in the second code example (a crude implementation of a server-side automated YouTube video upload method).

              Cheers
              Dom

            • Cris
              https://www.domsammut.com/?p=1142#comment-1094

              Cris

              Hi,

              How can I prevent user token expiration ?

              Thanks

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

              Dom Sammut

              Hey Cris,

              Are you referring to the Access Token? If so, the example provided checks to see if the access token is still valid, and if not, uses the refresh token we harvested to request a new one.

              Cheers
              Dom

      • Master Icang
        https://www.domsammut.com/?p=1142#comment-1907

        Master Icang

        Hello Dom, i got the same error like sammer, but the information is provide just it, would you please help me?

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

          Dom Sammut

          Hi,

          That is a PHP setting you’ll need to modify to increase the execution timeout. An example of this would be:

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

          Cheers
          Dom

  13. David
    https://www.domsammut.com/?p=1142#comment-634

    David

    I downloaded the library here: http://developers.google.com/api-client-library/php

    Unfortunately I get the error Class ‘ Google_Service ‘ not found

    It ‘ a problem of version of php ? I have the 5.3 and the 5.5

    how can I fix it?

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

      Dom Sammut

      Hey David,

      It could be a couple things, ensure that you have PHP 5.3 or higher for the cryptographic algorithm requirements and class calls.

      Have you double checked that the set_include_path() is pointing to the correct directory?

      Alternatively can you pastebin your code or put it in a comment?

      Cheers
      Dom

  14. kcode
    https://www.domsammut.com/?p=1142#comment-491

    kcode

    Hi, today i try more example and i have an error, after the access token expired when i use the refresh token the response is like this
    ” Google_Auth_Exception: Error refreshing the OAuth2 token, message: ‘{ “error” : “invalid_grant” }'”

    here is the peace of code i use to refresh the token, my refresh token is saved when i authenticate the app at first time

    if($client->isAccessTokenExpired()) {
      echo 'expired';
    
      $client->refreshToken('1/cbDvCHW-kuwMZ_a2ugLj-T956X1_IhJ5lZ_d8BYSvsU');
    
    } 
    

    what the cause of the problem do i need to re-authenticate the app? or the refresh token is wrong?

    Best Regards
    Karim,

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

      Dom Sammut

      Hey Karim,

      You don’t need to re-authentic the app like you did when you harvested the refresh token. It doesn’t appear your using the demo script above. Have you ensured that you have correctly initialised the $client and passed in all the required parameters.

      Has your script worked in the past after 3600 seconds of getting a valid access token?

      Also double check your server clock is synced to the atomic clock. If your server time is out be even 30 seconds you can run into issues.

      Cheers
      Dom

    • l
      https://www.domsammut.com/?p=1142#comment-1979

      l

      Hi Kcode,

      i get the invalid grant error as well. Did you happen to fix it ?
      $client->refreshToken($newToken->refresh_token);

      this part is the problem eventhough im 100% sure my refresh token works because for 3600 seconds or an hour I was able to upload.

  15. kcode
    https://www.domsammut.com/?p=1142#comment-457

    kcode

    thanks you for your helpful tutorial, i have some questions related to the access tokens,
    i have created an application and run it from chrome and firefox, both of them give me a different refresh token,
    witch one to use to generate my next token? can i use any of them?

    does the access token related to the google account that authenticate my app?

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

      Dom Sammut

      Hey kcode,

      Yes running it from two different browsers will give you a different set of tokens, this is normal and either set can be used. In regards to your access relating to the google account that authenticated your app, yes the access / refresh tokens generated are for your google account.

      Let me know if you have any further questions!

      Cheers
      Dom

      • kcode
        https://www.domsammut.com/?p=1142#comment-478

        kcode

        Thank you a lot, i run some example to understand how it works, in my test i capture two refresh tokens and both of them works well. i will try more examples

        Thanks

1 2 3 10
css.php