让用户将视频从我的站点服务器上传到他们的 youtube 频道
Posted
技术标签:
【中文标题】让用户将视频从我的站点服务器上传到他们的 youtube 频道【英文标题】:Let users upload videos from my site server to their youtube channel 【发布时间】:2019-02-17 14:14:15 【问题描述】:我正在运行一个带有 ffmpeg 的 WordPress 网站,我允许用户使用表单创建视频。视频运行良好,每个帖子都保存在自己的目录中。
我现在正尝试允许用户将他们创建的视频上传到他们自己的 youtube 频道。
使用来自youtube api 的 php 代码和来自谷歌控制台的我的应用程序,我能够成功地将视频上传到我自己的帐户。它只要求我进行一次身份验证。
PHP 代码 --
<?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>
我也成功地按照 javascript 示例从我的计算机上传,但文件存储在服务器上,所以这不是我想要的。
使用 javascript,为用户获取新令牌似乎可行,除了文件上传问题以及用户必须进入他们的 Google 帐户才能撤销访问/从我的站点应用程序中注销。
JS 代码 -
/*
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)
);
;
我很确定这与刷新或访问令牌有关。那么如何为每个用户创建一个访问令牌,而不是只允许我的帐户在应用程序上呢?
【问题讨论】:
【参考方案1】:通过在我的页面上使用以下表单,我能够让它按照我想要的方式工作 -
<form id="publish-youtube" action="/path/to/upload.php" method="post" target="_blank">
<input type="hidden" id="a_id" name="a_id" value="<?php echo $post_author_id; ?>" />
<input type="hidden" id="vid_id" name="vid_id" value="<?php echo $v_Id; ?>" />
<input type="submit" id="ytu_submit" name="ytu_submit" value="Publish to YouTube">
</form>
然后我按照youtube api docs 创建了我的应用程序和我的upload.php 脚本,该脚本要求用户登录(如果尚未登录)并根据表单中的值上传视频(注意:我使用wordpress) --
<?php
//require_once $_SERVER['DOCUMENT_ROOT'] . '/gap/google-api-php-client/vendor/autoload.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/wp-load.php';
set_include_path($_SERVER['DOCUMENT_ROOT'] . '/gap/google-api-php-client/');
require_once 'src/Google/Client.php';
require_once 'src/Google/Service/YouTube.php';
session_start();
$application_name = 'XXXX';
$OAUTH2_CLIENT_ID = 'XXXX';
$OAUTH2_CLIENT_SECRET = 'XXXX';
$videoTitle = $_POST['titlez'];
$authorID = $_POST['a_id'];
$vidID = $_POST['vid_id'];
$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]);
get_header();
global $current_user, $imic_options; // Use global
get_currentuserinfo(); // Make sure global is set, if not set it.
if ((user_can($current_user, "administrator"))||(user_can($current_user, "edit_others_posts")) ):
// 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 = $_SERVER["DOCUMENT_ROOT"] ."/uploads/".$authorID."/".$vidID."/output-".$vidID.".mp4";
// 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);
$htmlBody .= "<h3>Video Uploaded</h3><ul>";
$htmlBody .= sprintf('<li>%s</li>',
$status['snippet']['title']);
$htmlBody .= sprintf('<li><a href="https://www.youtube.com/watch?v=%s" target="_blank">Video Link</a></li>',
$status['id']);
$htmlBody .= '</ul>';
catch (Google_Service_Exception $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[$tokenSessionKey] = $client->getAccessToken();
elseif ($OAUTH2_CLIENT_ID == '(I never changed this)REPLACE_ME')
$htmlBody = <<<END
<h3>Client Credentials Required</h3>
<p>
You need to set <code>\$OAUTH2_CLIENT_ID</code> and
<code>\$OAUTH2_CLIENT_ID</code> before proceeding.
<p>
END;
else
// If the user hasn't authorized the app, initiate the OAuth flow
$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">authorize access</a> before proceeding.<p>
END;
?>
<div id="ytu-container">
<?=$htmlBody?>
</div>
<?php
else: echo imic_unidentified_agent();
endif;
get_footer();
?>
【讨论】:
【参考方案2】:你需要执行the OAUTH dance
Google 在此过程中有一些 good documentation。以下引用的内容和代码来自该站点。
您的应用程序确定它需要的权限。 您的应用程序将用户连同请求的权限列表一起重定向到 Google。 用户决定是否将权限授予您的应用程序。 您的应用程序会找出用户的决定。 如果用户授予请求的权限,您的应用程序将检索代表用户发出 API 请求所需的令牌。
步骤 1 和 2 的代码:
// setup client (step.1)
$client = new Google_Client();
$client->setAuthConfig('client_secret.json');
$client->addScope(GOOGLE_SERVICE_YOUTUBE::YOUTUBE_UPLOAD);
$client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php');
$client->setAccessType('offline'); // offline access
$client->setIncludeGrantedScopes(true); // incremental auth
// get url and redirect (step.2)
$auth_url = $client->createAuthUrl();
header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
exit();
您需要设置重定向 url 以接受来自他们的回电,格式如下:
[your_callback_url]/oauth2callback?state=state_parameter_passthrough_value&code=[code]&scope=[scope]
然后您从回调中提取详细信息并将它们交换为允许您上传的令牌。
// swap the code for a token (step.5)
// … do client setup first
// then auth with the code
$client->authenticate($_GET['code']);
// retrieve the token
$access_token = $client->getAccessToken();
然后您可以在您的 api 调用中使用该令牌代表用户进行调用。
还有一个complete example on that same page。
【讨论】:
以上是关于让用户将视频从我的站点服务器上传到他们的 youtube 频道的主要内容,如果未能解决你的问题,请参考以下文章