Monday, September 30, 2013

YouTube Browser-based Uploading with OAuth

YouTube has changed over to OAuth for using their data API (API 3.0)
https://developers.google.com/youtube/v3/

Which is fine but it seems they haven't move all of their code over to the new API or haven't finished the docs for it yet, so this is sort of a missing manual for implementing the old API 2.0 Browser-based Uploading seen here:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading


Step 1- register your app with Google

If you are creating this for one of your brands you need to login/create a Google account for them. After you logged in, you'll need to register your application by following the instructions found here:
https://developers.google.com/youtube/registering_an_application

- when you create a client ID, set application type to web application
- redirect url can be this: http://localhost/oauth2callback, you only need it once

You will also need to get a developer key from here:
https://code.google.com/apis/youtube/dashboard/

The developer_key will not change so make sure you add this to your config file.


Step 2 - get a refresh_token

https://developers.google.com/youtube/v3/guides/authentication

Now that you have your application registered, you should have an oAuth Client ID and Client Secret. Go ahead and save them into your project as a config variable since they don't change, but you need them to get an access_token. You now need to get an authorization code from the API. This is a one time use token that can be exchanged for an access_token and refresh_token. They call it a refresh_token in the docs, but it's really a long term token that doesn't expire, which is what we want.

The code url will be this:
$url = 'https://accounts.google.com/o/oauth2/auth?';  
$url .= 'client_id='   . {{insert_your_client_id_here}}  
$url .= '&redirect_uri=' . urlencode('http://localhost/oauth2callback'); //this is whatever url you set in the "create client id dialog"  
$url .= '&scope='    . urlencode('https://gdata.youtube.com');  
$url .= '&response_type=code';  
$url .= '&access_type=offline';  
$url .= '&approval_prompt=force';  


After you call this url you will be taken to a page and asked if your application is allowed to access this Google account. Click accept and you will be redirected to the url you specified earlier with the get variable code=... added to the end of the url. This is your one time use token so save it into your text editor while you create the next request.

To make a request to get the refresh_token, which is another one time thing, you can use PHP cURL code below or the command line.

 //using PHP cUrl  
 $curl = curl_init( 'https://accounts.google.com/o/oauth2/token' );  
 $post_fields = array(  
   'code'     => '{{insert_the_code_you_just_got_here}}',  
   'client_id'   => '{{insert_your_client_id_here}}',  
   'client_secret' => '{{insert_your_client_secret_here}}',  
   'redirect_uri' => 'http://localhost/oauth2callback', //this doesn't do anything, but it's validated so i needs to match what you've been using  
   'grant_type'  => 'authorization_code'  
 );  

 curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);  
 curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);  
 curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);  
 curl_setopt($curl, CURLOPT_POST, 1);  
 curl_setopt($curl, CURLOPT_HEADER, 0);  
 curl_setopt($curl, CURLOPT_POSTFIELDS, http_build_query($post_fields));
  
 curl_setopt($curl, CURLOPT_HTTPHEADER, array(  
   'Content-Type: application/x-www-form-urlencoded'  
 )); 
 
 //send request  
 $response = curl_exec($curl); 
 
 print_r($response);  


If everything worked, you should get something like this back:

{  
  "access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",  
  "token_type" : "Bearer",  
  "expires_in" : 3600,  
  "refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"  
 }  

Bingo!, copy and pasted the refresh_token into your config file. Like I said before, it doesn't expire like the access_token will. You will use it to get a new access_token each time your form loads. Using a token to get a token seems strange but it's Google so I guess they know what they're doing.


Step 3 - post url and upload token

We are back to this guide:
https://developers.google.com/youtube/2.0/developers_guide_protocol_browser_based_uploading

You need to create a video object on the YouTube server to upload a video to. To do this, you'll need to get an upload_token and a post_url from the YouTube API.
http://gdata.youtube.com/action/GetUploadToken

To create a video object, a video title and description along with some keywords and a category are required before can you get the post_url so, in my case, I just created temporary ones and updated them later.

The category definitions can be found here:
http://gdata.youtube.com/schemas/2007/categories.cat

public function get_video_upload_info()  
{  
   //get youtube access token  
   $access_token = $this->get_access_token();  

   //create a video obj with temp info  
   $video_title  = 'Video Temp Title ' . rand(1, 9999);  
   $video_desc   = 'Temp Desc';  
   $video_keywords = 'facebook, contest';  

   //setup request body as xml  
   $xml_str = implode('', array(  
     '<?xml version="1.0"?>',  
     '<entry xmlns="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" xmlns:yt="http://gdata.youtube.com/schemas/2007">',  
       '<media:group>',  
         '<media:title type="plain">' . $video_title . '</media:title>',  
         '<media:description type="plain">' . $video_desc . '</media:description>',  
         '<media:category scheme="http://gdata.youtube.com/schemas/2007/categories.cat">Animals</media:category>',  
         '<media:keywords>' . $video_keywords . '</media:keywords>',  
         //'<yt:private/>',  
         '<yt:accessControl action="list" permission="denied"/>', //causes the video to be unlisted  
       '</media:group>',  
     '</entry>'));
  
   //use curl to call youtube api  
   $ch = curl_init( 'http://gdata.youtube.com/action/GetUploadToken' );  
   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);  
   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);  
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
   curl_setopt($ch, CURLOPT_HEADER, 0);
  
   curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
     'Authorization: Bearer ' . $access_token,  
     'GData-Version: 2',  
     'X-GData-Key: key=' . {{your_developer_key}}],  
     'Content-Type: application/atom+xml; charset=UTF-8'  
   ));
  
   curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_str);  

   //send request  
   $xml_response = curl_exec($ch);  

   //close connection  
   curl_close($ch); 
  
   $result = simplexml_load_string( $xml_response );  
   if( $result->getName() === 'errors' )  
   {  
     return array('post_url' => 'broke', 'upload_token' => 'not_a_real_token');     
   }  
   return array('post_url' => (string) $result->url, 'upload_token' => (string) $result->token);  
 }  


In the code above, the method '''get_access_token();''' uses the refreash_token to make a cURL request to the YouTube API to get a valid access_token.

private function get_access_token()  
 {  
   $ch = curl_init( 'https://accounts.google.com/o/oauth2/token' );  
   $post_fields = array(  
     'client_id'   => {{insert_your_client_id_here}},  
     'client_secret' => {{insert_your_client_secret_here}},  
     'refresh_token' => {{insert_your_refresh_token_here}},  
     'grant_type'  => 'refresh_token'  
   );  

   curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);  
   curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);  
   curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
   curl_setopt($ch, CURLOPT_POST, 1);  
   curl_setopt($ch, CURLOPT_HEADER, 0);  
   curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($post_fields)); 
 
   curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
     'Content-Type: application/x-www-form-urlencoded'  
   ));  

   //send request  
   $response = json_decode( curl_exec($ch) );  

   curl_close($ch);  

   return $response->access_token;  
 }  



Step 4 - upload the video to YouTube

Using the post_url we can now display the form to complete this whole process. The post_url you will use in your form will need to have the GET variable next=... added to it. This will be the url that the form will redirect to after the video has finished uploading.

The form needs to have the 2 fields 'token' and 'file', which the API is expecting:

<form method="post" action="{{post_url}}?next={{next_url}}" enctype="multipart/form-data">  
   <input name="token" type="hidden" value="{{upload_token}}"/>   
   <input type='file' id='file' name='file' accept="video/*" />  
 </form>   

When video is finished uploading, the form will redirect to the url you set for next=... along with some extra GET variables from YouTube. The variable status=... is always returned, so you can use that to check if it was successful, which the value would be 200.

If the upload was a success, you will also get the variable id=... which will have the video's new id ( http://www.youtube.com/watch?v={{id}} ).

If the status wasn't 200, then an error occured and you will get the variable code=... which will describe the error.



That should be it. Easy huh? after 3 days I finally got this working so I figured it needed documenting and like i said, it's probably going to change soon so who knows how long this will work.

Google also has some code libraries you can use:
https://developers.google.com/youtube/v3/code_samples/php

I looked into them, but was having trouble fitting the code into my project, so maybe if I started with all this in mind, I would've been able to use them.

No comments:

Post a Comment