I am running a WordPress site with ffmpeg where I allow users to create videos using a form. The videos work fine and are saved in their own directories per post.
I am now trying to allow users to upload the videos they created, to their own youtube channel.
Using PHP code from youtube api, and my app from google console, I am able to successfully upload a video to my own account. And it only asks for me to authenticate once.
PHP Code --
<?php
require_once $_SERVER['DOCUMENT_ROOT'] . '/gap/google-api-php-client/vendor/autoload.php';
$key = file_get_contents('the_key.txt');
set_include_path($_SERVER['DOCUMENT_ROOT'] . '/gap/google-api-php-client/');
require_once 'src/Google/Client.php';
require_once 'src/Google/Service/YouTube.php';
$application_name = 'youtube4true';
$OAUTH2_CLIENT_ID = 'REMOVED FOR STACK';
$OAUTH2_CLIENT_SECRET = 'REMOVED FOR STACK';
$client = new Google_Client();
$client->setClientId($OAUTH2_CLIENT_ID);
$client->setClientSecret($OAUTH2_CLIENT_SECRET);
$client->setScopes('https://www.googleapis.com/auth/youtube');
$redirect = filter_var('https://' . $_SERVER['HTTP_HOST'] . $_SERVER['PHP_SELF'],
FILTER_SANITIZE_URL);
$client->setRedirectUri($redirect);
$client->setAccessType("offline");
$client->setApprovalPrompt("force");
// Define an object that will be used to make all API requests.
$youtube = new Google_Service_YouTube($client);
// Check if an auth token exists for the required scopes
$tokenSessionKey = 'token-' . $client->prepareScopes();
if (isset($_GET['code'])) {
if (strval($_SESSION['state']) !== strval($_GET['state'])) {
die('The session state did not match.');
}
$client->authenticate($_GET['code']);
$_SESSION[$tokenSessionKey] = $client->getAccessToken();
header('Location: ' . $redirect);
}
if (isset($_SESSION[$tokenSessionKey])) {
$client->setAccessToken($_SESSION[$tokenSessionKey]);
$refresh_token = $_SESSION[$tokenSessionKey]['refresh_token'];
file_put_contents('the_key.txt', $refresh_token);
} elseif(file_exists('the_key.txt')) {
$refresh_token = file_get_contents('the_key.txt');
$client->refreshToken($refresh_token);
$_SESSION['token'] = $client->getAccessToken();
$access_token = $_SESSION['token']['access_token'];
$client->setAccessToken($access_token);
$refresh_token = $_SESSION['token']['refresh_token'];
file_put_contents('the_key.txt', $refresh_token);
}
// Check to ensure that the access token was successfully acquired.
if ($client->getAccessToken()) {
$htmlBody = '';
try{
// REPLACE this value with the path to the file you are uploading.
$videoPath = "HERE I WILL USE AJAX TO PASS THE PATH TO THE VIDEO FILE";
// 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("Test title");
$snippet->setDescription("Test description");
$snippet->setTags(array("tag1", "tag2"));
// Numeric video category. See
// https://developers.google.com/youtube/v3/docs/videoCategories/list
$snippet->setCategoryId("22");
// Set the video's status to "public". Valid statuses are "public",
// "private" and "unlisted".
$status = new Google_Service_YouTube_VideoStatus();
$status->privacyStatus = "public";
// 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);
// 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>Video Uploaded</title>
</head>
<body>
<?=$htmlBody?>
</body>
</html>
I've also successfully been able to follow to javascript example to upload from my computer, but the files are stored on the server so that's not what I want.
With javascript, getting a new token for users seems to work, except the file upload issue and that users have to go into their Google accounts to revoke access/log out from my site app.
JS Code -
/*
Copyright 2015 Google Inc. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and limitations under the License.
*/
var signinCallback = function (result){
if(result.access_token) {
var uploadVideo = new UploadVideo();
uploadVideo.ready(result.access_token);
}
};
var STATUS_POLLING_INTERVAL_MILLIS = 60 * 1000; // One minute.
/**
* YouTube video uploader class
*
* @constructor
*/
var UploadVideo = function() {
/**
* The array of tags for the new YouTube video.
*
* @attribute tags
* @type Array.<string>
* @default ['google-cors-upload']
*/
this.tags = ['test'];
/**
* The numeric YouTube
* [category id](https://developers.google.com/apis-explorer/#p/youtube/v3/youtube.videoCategories.list?part=snippet®ionCode=us).
*
* @attribute categoryId
* @type number
* @default 22
*/
this.categoryId = 22;
/**
* The id of the new video.
*
* @attribute videoId
* @type string
* @default ''
*/
this.videoId = '';
this.uploadStartTime = 0;
};
UploadVideo.prototype.ready = function(accessToken) {
this.accessToken = accessToken;
this.gapi = gapi;
this.authenticated = true;
this.gapi.client.request({
path: '/youtube/v3/channels',
params: {
part: 'snippet',
mine: true
},
callback: function(response) {
if (response.error) {
console.log(response.error.message);
} else {
$('#channel-name').text(response.items[0].snippet.title);
$('#channel-thumbnail').attr('src', response.items[0].snippet.thumbnails.default.url);
$('.pre-sign-in').hide();
$('.post-sign-in').show();
}
}.bind(this)
});
$('#button').on("click", this.handleUploadClicked.bind(this));
};
/**
* Uploads a video file to YouTube.
*
* @method uploadFile
* @param {object} file File object corresponding to the video to upload.
*/
UploadVideo.prototype.uploadFile = function(file) {
var metadata = {
snippet: {
title: $('#title').val(),
description: $('#description').text(),
tags: this.tags,
categoryId: this.categoryId
},
status: {
privacyStatus: $('#privacy-status option:selected').text()
}
};
var uploader = new MediaUploader({
baseUrl: 'https://www.googleapis.com/upload/youtube/v3/videos',
file: file,
token: this.accessToken,
metadata: metadata,
params: {
part: Object.keys(metadata).join(',')
},
onError: function(data) {
var message = data;
// Assuming the error is raised by the YouTube API, data will be
// a JSON string with error.message set. That may not be the
// only time onError will be raised, though.
try {
var errorResponse = JSON.parse(data);
message = errorResponse.error.message;
} finally {
alert(message);
}
}.bind(this),
onProgress: function(data) {
var currentTime = Date.now();
var bytesUploaded = data.loaded;
var totalBytes = data.total;
// The times are in millis, so we need to divide by 1000 to get seconds.
var bytesPerSecond = bytesUploaded / ((currentTime - this.uploadStartTime) / 1000);
var estimatedSecondsRemaining = (totalBytes - bytesUploaded) / bytesPerSecond;
var percentageComplete = (bytesUploaded * 100) / totalBytes;
$('#upload-progress').attr({
value: bytesUploaded,
max: totalBytes
});
$('#percent-transferred').text(percentageComplete);
$('#bytes-transferred').text(bytesUploaded);
$('#total-bytes').text(totalBytes);
$('.during-upload').show();
}.bind(this),
onComplete: function(data) {
var uploadResponse = JSON.parse(data);
this.videoId = uploadResponse.id;
$('#video-id').text(this.videoId);
$('.post-upload').show();
this.pollForVideoStatus();
}.bind(this)
});
// This won't correspond to the *exact* start of the upload, but it should be close enough.
this.uploadStartTime = Date.now();
uploader.upload();
};
UploadVideo.prototype.handleUploadClicked = function() {
$('#button').attr('disabled', true);
this.uploadFile($('#file').get(0).files[0]);
};
UploadVideo.prototype.pollForVideoStatus = function() {
this.gapi.client.request({
path: '/youtube/v3/videos',
params: {
part: 'status,player',
id: this.videoId
},
callback: function(response) {
if (response.error) {
// The status polling failed.
console.log(response.error.message);
setTimeout(this.pollForVideoStatus.bind(this), STATUS_POLLING_INTERVAL_MILLIS);
} else {
var uploadStatus = response.items[0].status.uploadStatus;
switch (uploadStatus) {
// This is a non-final status, so we need to poll again.
case 'uploaded':
$('#post-upload-status').append('<li>Upload status: ' + uploadStatus + '</li>');
setTimeout(this.pollForVideoStatus.bind(this), STATUS_POLLING_INTERVAL_MILLIS);
break;
// The video was successfully transcoded and is available.
case 'processed':
$('#player').append(response.items[0].player.embedHtml);
$('#post-upload-status').append('<li>Final status.</li>');
break;
// All other statuses indicate a permanent transcoding failure.
default:
$('#post-upload-status').append('<li>Transcoding failed.</li>');
break;
}
}
}.bind(this)
});
};
I'm pretty sure it has to do with the refresh or access token. So how can I create an access token for each user instead of only allowing my account on the app?