Video Uploads with YouTube API and PHP Code Flow

With Persistent Authorization

As YouTube’s documentation says

"The server-side web apps flow supports web applications that can securely store persistent information."

In this tutorial, we’ll demonstrate an end-to-end application for uploading videos to YouTube while maintaining persistent authorization. Such a system allows for a web-based interface where videos can be uploaded to a YouTube channel without authorization from the channel owner for each upload instance.

Installing Google API Client

While it is an option to upload the Google API Client file to your project’s domain, we will be using the Composer option instead. See the instructions for installing composer at this GitHub repository before going any further in this tutorial.

File Structure

First we’ll create a file structure for our project:

  • youtube_upload_project
    • config.php
    • Database.php
    • index.php
    • upload.php
    • youtube_video_sync.php
    • videos/
    • vendor/
      • autoload.php

Create Google Project

Next, we'll create a Google project. Go to the Google API Console:

  • Click New Project or select an existing project
  • Type the project name. The project’s ID will be created automatically. Your project’s name should be unique.
  • Click the Create button
  • Click New Project or select an existing project

Select the new project and enable the YouTube Data API:

  • Select Library from the sidebar under the APIs and Services section
  • Search for and select YouTube Data API v3
  • Click Enable to make YouTube Data API v3 library available for your project

Next, select Credentials under the APIs & Services section:

  • Select the Oauth2 consent screen tab and specify the settings as they relate to your project
  • Click the Save button

Now, open the Credentials tab and select the Create Credentials dropdown. Select OAuth Client ID:

  • Select Web Application in the application type section
  • In the Authorize Redirect URI field you need to place the redirect URI for our project. In this case, it will look like: https://www.yourproject.com/youtube_upload_project/youtube_video_sync.php (this is one of the files in our file structure that will be important as the callback script later on)
  • Click the Create button

Next, you’ll see a dialog box appear. In that dialog box are the Client ID and the Client Secret. Later, we’ll implement both of these to authorize our access to the Google API call.

Create Tables for Our Project

Let’s make some tables in our database where we can store video data, access tokens and refresh tokens.

For the videos table:

													
CREATE TABLE `VIDEOS` (

  `VIDEO_ID` INT NOT NULL,

  `EMAIL` VARCHAR(100) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `TITLE` VARCHAR(100) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `FIRSTNAME` VARCHAR(50) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `LASTNAME` VARCHAR(50) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `DESCRIPTION` VARCHAR(1000) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI DEFAULT NULL,

  `PRIVACY` VARCHAR(100) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `FILE_NAME` VARCHAR(200) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,

  `YOUTUBE_VIDEO_ID` VARCHAR(100) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI DEFAULT NULL,

  `CREATED` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP

) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_GENERAL_CI;



ALTER TABLE `VIDEOS`

  ADD PRIMARY KEY (`VIDEO_ID`);

ALTER TABLE `VIDEOS`

  MODIFY `VIDEO_ID` INT NOT NULL AUTO_INCREMENT;

COMMIT;


												

For the access_tokens table:

													
CREATE TABLE `ACCESS_TOKENS` (

  `TOKEN_ID` INT NOT NULL,

  `TOKEN` VARCHAR(300) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL

) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_GENERAL_CI;



ALTER TABLE `ACCESS_TOKENS`

  ADD PRIMARY KEY (`TOKEN_ID`);



ALTER TABLE `ACCESS_TOKENS`

  MODIFY `TOKEN_ID` INT NOT NULL AUTO_INCREMENT;

COMMIT;


												

For the refresh_tokens table:

													
CREATE TABLE `REFRESH_TOKENS` (

  `TOKEN_ID` INT NOT NULL,
  
  `TOKEN` VARCHAR(300) CHARACTER SET UTF8MB4 COLLATE UTF8MB4_GENERAL_CI NOT NULL,
  
  `ACTIVE` INT NOT NULL
  
) ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COLLATE=UTF8MB4_GENERAL_CI;



ALTER TABLE `REFRESH_TOKENS`

  ADD PRIMARY KEY (`TOKEN_ID`);



ALTER TABLE `REFRESH_TOKENS`

  MODIFY `TOKEN_ID` INT NOT NULL AUTO_INCREMENT;

COMMIT;


												

Let's get coding

config.php:

The config.php file is where we will instantiate a Google Client object, which we’ll use to access all of the features of the YouTube API.

  • Set [YOUR_CLIENT_ID] to your client ID from your API Manager page in the Google API Console.
  • Set [YOUR_CLIENT_SECRET] to your client secret from your API Manager page in the Google API Console.
  • Set yourproject in the REDIRECT_URL to the URL of your project.

On line 11 we create a copy of the Google Client object, which we are granted access to by the require_once on line 3. We will require config.php in some of our files later on. Whenever we reference $client later in the project, we will be pointing to this original object on line 11.

<?php
require_once __DIR__ . ‘/vendor/autoload.php’;

// Google API configuration 
Define(‘OAUTH_CLIENT_ID’, ‘[YOUR_CLIENT_ID]’); 
Define(‘OAUTH_CLIENT_SECRET’, ‘[YOUR_CLIENT_SECRET]’); 
Define(‘REDIRECT_URL’, ‘https://www.yourproject.com/video_upload_page/youtube_video_sync.php’);

// Initialize Google Client class 
$client = new Google_Client(); 
$client->setClientId(OAUTH_CLIENT_ID); 
$client->setClientSecret(OAUTH_CLIENT_SECRET); 
$client->setScopes(‘https://www.googleapis.com/auth/youtube’); 
$client->setRedirectUri(REDIRECT_URL); 
$client->setAccessType(‘offline’);
$client->setIncludeGrantedScopes(true);
$client->setApprovalPrompt(‘force’);

// Define an object that will be used to make all API requests 
$youtube = new Google_Service_YouTube($client);
?>

The Database.php file:

Database.php is how we will connect to our database. This file will also be included in our subsequent files.

  • Set `dbuser` to your database username.
  • Set `password` to your database password .
  • Set `yourproject` in the REDIRECT_URL to the URL of your project.
<?php
Class Database {
	Public $prefix= ‘’;
    Protected $connection;
	Protected $query;
    Protected $show_errors = TRUE;
    Protected $query_closed = TRUE;
	Public $query_count = 0;
	Public function __construct($dbhost = ‘localhost’, $dbuser = ‘dbuser’, $dbpass = ‘password’, $dbname = ‘dbname’, $charset = ‘utf8’) {
		$this->connection = new mysqli($dbhost, $dbuser, $dbpass, $dbname);
		If ($this->connection->connect_error) {
			$this->error(‘Failed to connect to MySQL – ‘ . $this->connection->connect_error);
		}
		$this->connection->set_charset($charset);
	}
    Public function query($query) {
        If (!$this->query_closed) {
            $this->query->close();
        }
		If ($this->query = $this->connection->prepare($query)) {
            If (func_num_args() > 1) {
                $x = func_get_args();
                $args = array_slice($x, 1);
				$types = ‘’;
                $args_ref = array();
                Foreach ($args as $k => &$arg) {
					If (is_array($args[$k])) {
						Foreach ($args[$k] as $j => &$a) {
							$types .= $this->_gettype($args[$k][$j]);
							$args_ref[] = &$a;
						}
					} else {
	                	$types .= $this->_gettype($args[$k]);
	                    $args_ref[] = &$arg;
					}
                }
				Array_unshift($args_ref, $types);
                Call_user_func_array(array($this->query, ‘bind_param’), $args_ref);
            }
            $this->query->execute();
           	If ($this->query->errno) {
				$this->error(‘Unable to process MySQL query (check your params) – ‘ . $this->query->error);
           	}
            $this->query_closed = FALSE;
			$this->query_count++;
        } else {
            $this->error(‘Unable to prepare MySQL statement (check your syntax) – ‘ . $this->connection->error);
        }
		Return $this;
    }
	Public function fetchAll($callback = null) {
	    $params = array();
        $row = array();
	    $meta = $this->query->result_metadata();
	    While ($field = $meta->fetch_field()) {
	        $params[] = &$row[$field->name];
	    }
	    Call_user_func_array(array($this->query, ‘bind_result’), $params);
        $result = array();
        While ($this->query->fetch()) {
            $r = array();
            Foreach ($row as $key => $val) {
                $r[$key] = $val;
            }
            If ($callback != null && is_callable($callback)) {
                $value = call_user_func($callback, $r);
                If ($value == ‘break’) break;
            } else {
                $result[] = $r;
            }
        }
        $this->query->close();
        $this->query_closed = TRUE;
		Return $result;
	}
	Public function fetchArray() {
	    $params = array();
        $row = array();
	    $meta = $this->query->result_metadata();
	    While ($field = $meta->fetch_field()) {
	        $params[] = &$row[$field->name];
	    }
	    Call_user_func_array(array($this->query, ‘bind_result’), $params);
        $result = array();
		While ($this->query->fetch()) {
			Foreach ($row as $key => $val) {
				$result[$key] = $val;
			}
		}
        $this->query->close();
        $this->query_closed = TRUE;
		Return $result;
	}
	Public function close() {
		Return $this->connection->close();
	}
    Public function numRows() {
		$this->query->store_result();
		Return $this->query->num_rows;
	}
	Public function affectedRows() {
		Return $this->query->affected_rows;
	}
    Public function lastInsertI() {
    	Return $this->connection->insert_id;
    }
    Public function error($error) {
        If ($this->show_errors) {
            Exit($error);
        }
    }
	Private function _gettype($var) {

	    If (is_string($var)) return ‘s’;
	    If (is_float($var)) return ‘d’;
	    If (is_int($var)) return ‘I’;
	    Return ‘b’;
	}
}
?>

The index.php file:

This file is where the upload process begins. We will start with some PHP that will present a message to the user about the status of the upload after they submit the form.

  • Note: we are using the Bootstrap framework to style the upload form.
  • On lines 3 and 4 we require the files we have just created: config.php and Database.php.
  • Next, we check the $_SESSION variable to see if it is populated by anything we will set it to later on.
  • It is important to understand and plan for the fact that Google does not allow for a form of this kind to be submitted asynchronously (e.g. through fetch, Ajax, etc.). Consequently, the form must be submitted synchronously and enctype='multipart/form-data' must be present in the <form> tag.
  • In the <form> tag, the action attribute is set to our upload.php file, which we will handle next.
<?php
require_once WP_CONTENT_DIR . 'upload_config.php';
require_once WP_CONTENT_DIR . 'Database.php';

$postData = array();
if(!empty($_SESSION['postData'])) {
	$postData = $_SESSION['postData'];
	
	unset($_SESSION['postData']);
}
	
$status = $statusMsg = ''; 
if(!empty($_SESSION['status_response'])){ 
    $status_response = $_SESSION['status_response']; 
    $status = $status_response['status']; 
    $statusMsg = $status_response['status_msg']; 
     
    unset($_SESSION['status_response']); 
}
<div class='container-fluid'>
	<div class='row'>
		<div class='col'>
 			<h2 class='m-3 text-uppercase'>Submit Video</h2>
		</div>
	</div>
<?php if(!empty($statusMsg)){ ?>
	<div class='alert alert-<?php echo $status; ?>'><?php echo $statusMsg; ?></div>
<?php } ?>
	 <div class='row m-2'>
		<div class='col'>
		  <div id='submit-video-content'>
			<form id='submitvideo' method='POST' action='upload.php' class='needs-validation' enctype='multipart/form-data' novalidate>
				<div class='form-group row my-3'>
					<div class='col-md-6'>
						<label for='first_name'>First Name</label>
						<input type='text' id='first_name' class='form-control' name='first_name' placeholder='First name' value='<?php echo $postData['first_name']; ?>' required />
						<div class='valid-feedback'>Looks good!</div>
						<div class='invalid-feedback'>This field is required.</div>
					</div>
					<div class='col-md-6'>
						<label for='last_name'>Last Name</label>
						<input type='text' id='last_name' class='form-control' name='last_name' placeholder='Last name' value='<?php echo $postData['last_name']; ?>' required />
						<div class='valid-feedback'>Looks good!</div>
						<div class='invalid-feedback'>This field is required.</div>
					</div>
				</div>
				<div class='form-group row my-3'>
					<div class='col-md-6'>
					    <label for='email'>Email address</label>
					    <input type='email' name='email' class='form-control' id='email' aria-describedby='emailHelp' placeholder='Enter email' value='<?php echo $postData['email']; ?>' required>
						<div id='emailvalid' class='valid-feedback'>Looks good!</div>
						<div class='invalid-feedback'>You must enter a valid email.</div>
					</div>
				</div>
				<div class='form-group row my-3'>
					<div class='col'>
						<label for='description'>Enter Video Description</label>
						<textarea class='form-control' id='description' name='description' rows='5'></textarea>
						<div id='descriptionvalid' class='valid-feedback'>Looks good!</div>
						<div class='invalid-feedback'>This field is required and must contain no more than 1000 characters.</div>
					</div>
				</div>
				<div class='input-group row my-3 justify-content-center'>
					<div class='col-md-8'>
					    <input type='file' class='form-control' id='video' name='file' required>
					    <div class='invalid-feedback' id='videofeedback'></div>
					</div>
				</div>
				<div class='d-grid gap-2 col-6 mx-auto justify-content-center'>
				  <button id='submit' type='submit' name='submit' class='btn btn-lg btn-primary text-uppercase fw-bolder'>Submit Video</button>
				</div>
			</form>
		  </div>
		</div>
	  </div>
</div>
?>

The upload.php file:

In this script, we handle uploading to and temporarily storing the video on the server, creating a record in the database and creating an OAuth URL.

  • In the $allowedTypeArr we specify the video file formats we will allow for upload.
  • We set the $_SESSION['postData'] variable to $POST so that we will have access to the information the user submitted in the form fields in the event we have to send the user back to the form with an error.
  • Next, we validate that the fields from the form were filled in. If any field is empty, we add a field-specific message to the $valErr variable, which we will check for at the top of the index.php file. Any content in the $valErr variable will send the user back to index.php.
  • After moving the video from its temporary storage location to the videos folder we created in our file structure above, we create a record in the database for the video submission. We will leave the youtube_video_id column null but will update it the youtube_video_sync.php script later when YouTube assigns the video an ID.
  • After clearing the $_SESSION['postData'] variable, we set the $_SESSION['last_uploaded_file_id'] so that we can easily search for the video record by its ID in the youtube_video_sync.php script. Once YouTube has given our video an ID, we will use $_SESSION['last_uploaded_file_id'] to update the youtube_video_id column in our videos table.
  • It is time to prepare the OAuth URL. To start, we create a random number and set it to $state. We also set it as a $_SESSION variable. Google uses this number as a way to verify the script receiving the request, youtube_video_sync.php, is in sync with the script originating the request, upload.php. We will reference this $_SESSION['state'] in youtube_video_sync.php.
  • At this point, we begin persistent authorization. We first check the table access_tokens to see if there is an active access token. On our first video upload, there will not be an access token in the table. However, Google will send one in response to our first request. An access token will authorize us to make requests for 3600 seconds. Once it has expired, and so that we don't have to grant authorization again, we can use a refresh token to obtain a new access token. A refresh token is also sent by Google when we grant authorization. We will store the refresh token in our database in our callback script youtube_video_sync.php. For now, we check if there is an active access token. If there is not, we use our refresh token to obtain a new one. If there is no refresh token, we format the request to Google as a completely new request, requiring authorization from the account.
  • At the end of the script, we send the user back to index.php with a success or error message.
<?php
require_once __DIR__ . '/upload_config.php';
require_once __DIR__ . '/Database.php';
session_start();
session_unset();

$statusMsg = $valErr = ''; 
$status = 'danger'; 
 
// If the form is submitted 
if(isset($_POST['submit'])) { 
    // Allowed mime types of the file to upload 
    $allowedTypeArr = array('video/mp4', 
							'video/avi', 
							'video/mpeg', 
							'video/mpg', 
							'video/mov', 
							'video/wmv', 
							'video/rm', 
							'video/quicktime'); 
     
    // Store post data in session 
    $_SESSION['postData'] = $_POST;
    
    $conn = new Database();

    // Get input's value 
	$firstName = $_POST['first_name'];
	$lastName = $_POST['last_name'];
	$email = $_POST['email'];
	$youtubeID = null;
    $title = $firstName . ' ' . $lastName;
    $description = $_POST['description'];
    $privacy = 'unlisted';
    $created = date('Y-m-d H:i:s');
     
    // Validate form input fields 
    if(empty($_FILES['file']['name'])) { 
        $valErr .= 'Please select a video file to upload.<br/>'; 
    } elseif(!in_array($_FILES['file']['type'], $allowedTypeArr)) { 
        $valErr .= 'Sorry, only MP4, AVI, MPEG, MPG, MOV, and WMV files are allowed to upload.<br/>'; 
    } 
         
    if(empty($title)) {
        $valErr .= 'Please enter <b>first name</b>, <b>last name</b>.<br/>'; 
    }
   
	if(empty($email)) {
		$valErr .= 'Please provide an <b>email</b> in email field.<br/>'; 
	}
	
    // Check whether user inputs are empty 
    if(empty($valErr)) { 
        $targetDir = __DIR__ . '/videos/'; 
        $fileName = time() . '_' . basename($_FILES['file']['name']); 
        $targetFilePath = $targetDir . $fileName; 
         
        // Upload file to local server 
        if(move_uploaded_file($_FILES['file']['tmp_name'], $targetFilePath)) {
             
            // Insert data into the database 
            $si = 'INSERT INTO videos (email, title, firstname, lastname, description, privacy, file_name, youtube_video_id, created) 
					VALUES (?,?,?,?,?,?,?,?,?)';
						
			$rsi = $conn->query($si, $email, $title, $firstName, $lastName, $description, $privacy, $fileName, $youtubeID, $created);
             
            if($rsi) { 
                $file_id = $rsi->lastInsertI();
                 
                // Remove post data from session 
                unset($_SESSION['postData']); 
                 
                // Store DB reference ID of file in SESSION 
                $_SESSION['last_uploaded_file_id'] = $file_id; 
                 
                // Get Google OAuth URL
				$state = mt_rand();
				$client->setState($state);
				$_SESSION['state'] = $state;
				
				$ss = 'SELECT * 
						FROM access_tokens 
						ORDER BY token_id 
						DESC LIMIT 1';
								
				$rss = $conn->query($ss);
				
				if($rss1->numRows() > 0) {
					$tokenResp = $rss1->fetchArray();
					$accessToken = $tokenResp['token'];
					if($client->isAccessTokenExpired() == false) {
						$_SESSION['upload_token'] = $accessToken;
						$googleOauthURL = 'https://www.yourproject.com/video_upload_project/youtube_video_sync.php';
					} else {
						unset($_SESSION['upload_token']);
						
						$ss = 'SELECT * 
								FROM refresh_tokens 
								ORDER BY token_id 
								DESC LIMIT 1';
										
						$rss = $conn->query($ss);
						if($rss->numRows() > 0) {
							$refresh = $rss->fetchArray();
							if($refresh['active'] == 1) {
								$_SESSION['refresh_token'] = $refresh['token'];
								$googleOauthURL = 'https://www.yourproject.com/video_upload_project/youtube_video_sync.php';
							} else {
								unset($_SESSION['refresh_token']);
								$googleOauthURL = $client->createAuthUrl();
							}
						} else {
							unset($_SESSION['refresh_token']);
							$googleOauthURL = $client->createAuthUrl();
						}
					}
				} else {
					
					$ss = 'SELECT * 
							FROM refresh_tokens 
							ORDER BY token_id 
							DESC LIMIT 1';
										
					$rss = $conn->query($ss);
					if($rss->numRows() > 0) {
						$refresh = $rss->fetchArray();
						if($refresh['active'] == 1) {
							$_SESSION['refresh_token'] = $refresh['token'];
							$googleOauthURL = 'https://www.yourproject.com/video_upload_project/youtube_video_sync.php';
						} else {
							unset($_SESSION['refresh_token']);
							$googleOauthURL = $client->createAuthUrl();
						}
					} else {
						unset($_SESSION['refresh_token']);
						$googleOauthURL = $client->createAuthUrl();
					}
				}
                
                // Redirect user for Google authentication 
                header('Location: ' . filter_var($googleOauthURL, FILTER_SANITIZE_URL)); 
                exit();
            } else { 
                $statusMsg = 'Something went wrong, please try again after some time.'; 
            }
        } else { 
            $statusMsg = 'File upload failed, please try again after some time.'; 
        }
    } else { 
        $statusMsg = '<p>Please fill in all the mandatory fields:</p>'.trim($valErr, '<br/>'); 
    }
} else { 
    $statusMsg = 'Form submission failed!'; 
}

$result['session'] = $googleOauthURL;
$result['message'] = $statusMsg;

//echo json_encode($result);
 
$_SESSION['status_response'] = array('status' => $status, 'status_msg' => $statusMsg); 
 
header('Location: https://www.yourproject.com/video_upload_project/index.php'); 
exit();
?>

The youtube_video_sync.php file:

In this script, we handle the response from Google and upload our video to YouTube. This script can be broken up into three blocks. The first block of code tests Google's response to determine if it is the first time authorization has been requested for this action and, if not, whether an access token or a refresh token is required to perform the upload. The second block of code prepares the video for upload to YouTube by using a number of Google objects we have access to through inclusion of autoloader.php. We set various settings using the methods of those Google objects. The third and final block of code updates our database video record.

  • If neither an access token nor a refresh token is present in the request sent to Google, the response will contain a key/value pair in $_GET, where $_GET['code'] is the key. If that key is present, and the $_GET['state'] matches the $_SESSION['state'] we set earlier in upload.php, our code will request an access token with the $client->fetchAccessTokenWithAuthCode(urldecode($_GET['code'])). That method will respond with an array containing new access and refresh tokens, which we will write to our database for later use. If $_GET['code'] is not present, it means we have either an access token or a refresh token to use. If we do not have an access token, we use the refresh token we have to get a new access token. If $_SESSION['refresh_token'] is not valid, we use the refresh token sent by Google. In either case, the database needs to be updated with the new tokens.
  • If the youtube_video_id in our videos table is not set, we carry on with preparing our video for upload to YouTube. Through the Google_Service_YouTube_VideoSnippet() object we set the title and description (we also comment out the method for setting tags if your application requires that).

    Through the Google_Service_YouTube_VideoStatus() object we set the privacy status to the privacy column value from the video record in the database. (In this example, we are setting that status to unlisted, which will make the video unavailable to casual YouTube visitors. Set to private, public or unlisted as your application requires.) We also setEmbeddable(true), making our video embeddable in any website or application, and setSelfDeclaredMadeForKids(false), as the videos in this example are not specifically made for kids.

    Through the Google_Service_YouTube_Video() object we set the setSnippet() and setStatus() to $snippet and $status values, respectively, that we prepared with the two previous objects.

    The $youtube variable comes from an instance of the Google_Service_YouTube() object we set in the config.php file.

    With all the options we set using the many Google objects, we upload the video using Google_Http_MediaFileUpload().
  • Finally, with the YouTube video ID now in hand, we update the video record in our videos table.
<?php
// Include database configuration file 
require_once __DIR__ . '/config.php';
require_once __DIR__ . '/Database.php';

session_start();

$conn = new Database();

$statusMsg = ''; 
$status = 'danger'; 
$redirectURL = 'https://www.yourproject.com/video_upload_project/index.php';
 
// Check if an auth token exists for the required scopes 
if (isset($_GET['code'])) { 
    if (strval($_SESSION['state']) !== strval($_GET['state'])) {
        die('The session state did not match.'); 
    }
     
    $auth = $client->fetchAccessTokenWithAuthCode(urldecode($_GET['code']));
    $accessToken = $auth['access_token'];
    $refreshToken = $auth['refresh_token'];
    $active = 1;
    
    $ss = 'INSERT INTO access_tokens(token) 
			VALUES(?)';
					
	$rss = $conn->query($ss, $accessToken);
	
	$ss = 'INSERT INTO refresh_tokens(token, active) 
			VALUES(?,?)';
					
	$rss = $conn->query($ss, $refreshToken, $active);
    
    $_SESSION['upload_token'] = $client->getAccessToken(); 
    header('Location: ' . REDIRECT_URL); 
} else {
	if (isset($_SESSION['upload_token'])) {
    	$client->setAccessToken($_SESSION['upload_token']); 
	} else if(isset($_SESSION['refresh_token'])) {
		$client->refreshToken($_SESSION['refresh_token']);
		$accessArray = $client->getAccessToken();
		$accessToken = $accessArray['access_token'];
		$arrayRefresh = $accessArray['refresh_token'];
		
		if($arrayRefresh !== $_SESSION['refresh_token']) {
			
			$ss = 'INSERT INTO refresh_tokens(token, active) 
					VALUES(?,?)';
					
			$rss = $conn->query($ss, $refreshToken, $active);
		}
		
		$_SESSION['upload_token'] = $accessToken;
		$client->setAccessToken($_SESSION['upload_token']);
		
		$ss = 'INSERT INTO access_tokens(token) 
				VALUES(?)';
					
		$rss = $conn->query($ss, $accessToken);
	}
}
 
// Check to ensure that the access token was successfully acquired. 
if($client->getAccessToken()) { 
    // Get file reference ID from SESSION 
    $file_id = $_SESSION['last_uploaded_file_id']; 
    
    if(!empty($file_id)) { 
        // Fetch video file details from the database 
        
        $ss = 'SELECT * 
				FROM videos 
				WHERE video_id = ?';
						
		$rs = $conn->query($ss, $file_id);
		$videoData = $rs->fetchArray();
     
        if(!empty($videoData)) { 
            $file_name = $videoData['file_name']; 
            $videoPath = __DIR__ . '/videos/' . $file_name;
             
            if(!empty($videoData['youtube_video_id'])) { 
                // Get video info from local database 
                $video_title = $videoData['title']; 
                $video_desc = $videoData['description']; 
                $video_tags = $videoData['tags']; 
                $youtube_video_id = $videoData['youtube_video_id']; 
            } else {
                try {  
                    // Create a snippet with title, description, tags and category ID 
                    // Create an asset resource and set its snippet metadata and type. 
                    // This example sets the video's title, description, keyword tags, and 
                    // video category. 
                    $snippet = new Google_Service_YouTube_VideoSnippet(); 
                    $snippet->setTitle($videoData['title']); 
                    $snippet->setDescription($videoData['description']); 
                    //$snippet->setTags(explode(',', $videoData['tags']));
                    
                    // Numeric video category. See 
                    // https://developers.google.com/youtube/v3/docs/videoCategories/list 
                    $snippet->setCategoryId('10');
                 
                    // Set the video's status to 'unlisted'. Valid statuses are 'public', 
                    // 'private' and 'unlisted'. 
                    $status = new Google_Service_YouTube_VideoStatus(); 
                    $status->setPrivacyStatus($videoData['privacy']);
                    $status->setEmbeddable(true);
                    $status->setSelfDeclaredMadeForKids(false);
                 
                    // Associate the snippet and status objects with a new video resource. 
                    $video = new Google_Service_YouTube_Video(); 
                    $video->setSnippet($snippet); 
                    $video->setStatus($status);
                 
                    // Specify the size of each chunk of data, in bytes. Set a higher value for 
                    // reliable connection as fewer chunks lead to faster uploads. Set a lower 
                    // value for better recovery on less reliable connections.
                    
                    $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); 
                 
                    // If you want to make other calls after the file upload, set setDefer back to false 
                    $client->setDefer(false);
                     
                    if(!empty($status['id'])) { 
                        // Uploaded youtube video info 
                        $video_title = $status['snippet']['title']; 
                        $video_desc = $status['snippet']['description']; 
                        //$video_tags = implode(',',$status['snippet']['tags']); 
                        $youtube_video_id = $status['id']; 
                         
                        // Update youtube video reference id in the database 
                        $su1 = 'UPDATE videos 
								SET youtube_video_id = ? 
								WHERE video_id = ?';
										
						$rsu1 = $conn->query($su1, $youtube_video_id, $file_id);
                         
                        if($rsu1) {
                            // Delete video file from local server 
                            @unlink($videoPath); 
                        }
                        
						unset($_SESSION['last_uploaded_file_id']); 
                         
                        $status = 'success'; 
                        $statusMsg = 'Video has been uploaded successfully! Your submission is awaiting approval. Check your email.'; 
                    }
                } catch (Google_Service_Exception $e) {
                    $statusMsg = 'A service error occurred: <code>' . $e->getMessage() . '</code>'; 
                } catch (Google_Exception $e) { 
                    $statusMsg = 'A client error occurred: <code>' . $e->getMessage() . '</code>';
                    $statusMsg .= '<br/>Please reset session <a href='logout.php'>Logout</a>'; 
                }
            }
        } else { 
            $statusMsg = 'File data not found!'; 
        }
    } else { 
        $statusMsg = 'File reference not found!'; 
    }
     
    $_SESSION[$tokenSessionKey] = $client->getAccessToken(); 
} else { 
    $statusMsg = 'Failed to fetch access token!'; 
}
 
$_SESSION['status_response'] = array('status' => $status, 'status_msg' => $statusMsg);

header('Location: $redirectURL'); 
exit();
?>

Things to note:

  • While the YouTube API is a valuable resource, it is not an unlimited one. YouTube has a quota system that limits the number of calls an account can make. An account is given 10,000 quotas a day. All calls to the API cost at least one quota. Some calls use 10, 50 or 100 quotas. Uploading a video, however, uses 1,600 quotas. While 10,000 quotas a day is more than most projects will require, an application that uploads more than three or four videos a day will need to consider applying for an increase in their account's limit.
  • YouTube's quota application request process is fairly detailed requiring the information related to your API account, a detailed description of your project's use of the API, why your project requires an increase in quota, what a rejection of your application would mean for your project and a host of other details. We recommend reading YouTube's compliance rules to ensure your project is within their guidelines. If there is an aspect of your project that is outside YouTube's guidelines, while you may be able to continue using the standard 10,000 quotas a day, a request for a quota increase could be delayed or rejected. Issues surrounding compliance concern things like storing user data, using the appropriate/designated YouTube and Google product logos, embedding videos (if your project requires that), making sure your project's privacy policy and terms of service contain information informing users about API concerns and that the privacy policy and terms of service are readily available to users. There are many other details of compliance worth considering. We recommend a studied perusal.