PHP API Youtube Uploads, 异常: 启动断点续传失败, upload必须发到upload URL

PHP API Youtube Uploads, exception: Failed to start the resumable upload, upload must be sent to the upload URL

我正在尝试构建用于将视频上传到 YouTube 的服务器应用程序。在我的服务器应用程序中,用户可以将视频直接上传到我的 YouTube 频道,使其成为 public.

  1. 我的应用程序的客户端获取视频并将其上传到我的服务器。
  2. 然后我的服务器使用 YouTube API 将视频上传到我的 YouTube 频道。

为了完成这项工作,我创建了一个可以捕获生成的刷新令牌的虚拟 Web 应用程序,并将其存储在 key.txt 文件中

{"access_token":"MYTOKEN","token_type":"Bearer","expires_in":3600,"created":1435654774}

如果 access_token 已过期,upload_video.php 脚本将自动更新 "key.txt" 文件。这是来自 upload_video.php:

的代码
$key = file_get_contents('key.txt');

$application_name = 'YouTube_Upload'; 
$client_secret    = 'MY_CLIENT_SECRET';
$client_id        = 'MY_CLIENT_ID';
$scope            = array('https://www.googleapis.com/auth/youtube.upload', 'https://www.googleapis.com/auth/youtube', 'https://www.googleapis.com/auth/youtubepartner');

$videoPath        = "Test.f4v";
$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 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('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('unlisted');

        // 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
         */
        if ($status->status['uploadStatus'] == 'uploaded') {
            // Actions to perform for a successful upload
            // ......
        }

        // If 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 "Google_Service_Exception ".$e->getCode(). " message is ".$e->getMessage();
    print "Stack trace is ".$e->getTraceAsString();
}catch (Exception $e) {
    print "Exception ".$e->getCode(). " message is ".$e->getMessage();
    print "Stack trace is ".$e->getTraceAsString();
}

当脚本运行时,会引发此异常:

Exception 0 message is Failed to start the resume-able upload (HTTP 400: global, Uploads must be sent to the upload URL. Re-send this request to https://www.googleapis.com/upload/youtube/v3/videos?part=status,snippet&uploadType=resumable)Stack trace is 
#0 D:\xampp\htdocs\youtube\src\Google\Http\MediaFileUpload.php(136): Google_Http_MediaFileUpload->getResumeUri()
#1 D:\xampp\htdocs\youtube\resumable_upload.php(100): Google_Http_MediaFileUpload->nextChunk('\x00\x00\x00\x1Cftypf4v \x00\x00\x00...')
#2 {main}

getResumeUri()(第 281 行)在 Google_Http_MediaFileUpload 中引发异常,我有 var dump 来自 google

的响应
Google_Http_Request Object
(
    [batchHeaders:Google_Http_Request:private] => Array
        (
            [Content-Type] => application/http
            [Content-Transfer-Encoding] => binary
            [MIME-Version] => 1.0
        )

    [queryParams:protected] => Array
        (
            [part] => status,snippet
            [uploadType] => resumable
        )

    [requestMethod:protected] => POST
    [requestHeaders:protected] => Array
        (
            [content-type] => application/json; charset=UTF-8
            [authorization] => Bearer XXXXXXXXXXXXXXXX
            [content-length] => 187
            [x-upload-content-type] => video/*
            [x-upload-content-length] => 10201286
            [expect] => 
        )

    [baseComponent:protected] => https://www.googleapis.com//upload
    [path:protected] => /youtube/v3/videos
    [postBody:protected] => {"snippet":{"categoryId":"22","description":"A video tutorial on how to upload to YouTube","tags":["youtube","tutorial"],"title":"A tutorial video"},"status":{"privacyStatus":"unlisted"}}
    [userAgent:protected] => 
    [canGzip:protected] => 
    [responseHttpCode:protected] => 400
    [responseHeaders:protected] => Array
        (
            [x-guploader-uploadid] => XXXXXXXXXXXXXXXXXXXXXXXXXX
            [location] => https://www.googleapis.com/upload/youtube/v3/videos?part=status,snippet&uploadType=resumable
            [vary] => Origin
X-Origin
            [content-type] => application/json; charset=UTF-8
            [content-length] => 468
            [date] => Fri, 10 Jul 2015 09:54:30 GMT
            [server] => UploadServer
            [alternate-protocol] => 443:quic,p=1
        )

    [responseBody:protected] => {
 "error": {
  "errors": [
   {
    "domain": "global",
    "reason": "wrongUrlForUpload",
    "message": "Uploads must be sent to the upload URL. Re-send this request to https://www.googleapis.com/upload/youtube/v3/videos?part=status,snippet&uploadType=resumable"
   }
  ],
  "code": 400,
  "message": "Uploads must be sent to the upload URL. Re-send this request to https://www.googleapis.com/upload/youtube/v3/videos?part=status,snippet&uploadType=resumable"
 }
}

    [expectedClass:protected] => Google_Service_YouTube_Video
    [expectedRaw:protected] => 
    [accessKey] => 
)

怎么了? 感谢您的帮助,抱歉英语不好。

希望您需要可续传上传(https://developers.google.com/youtube/v3/guides/using_resumable_upload_protocol)

然后不做任何修改直接使用this

还要检查 https://support.google.com/youtube/troubleshooter/2888402?hl=en

是否支持视频格式

有一个 google 方法来获取刷新令牌,所以不要使用 json_decode :

$newToken = json_decode($client->getAccessToken());
$client->refreshToken($newToken->refresh_token);

你可以这样做:

$client->refreshToken( $client->getRefreshToken() );

这是我到目前为止测试过的并且工作正常。

$key = trim(file_get_contents('key.txt'));
$scope = 'https://www.googleapis.com/auth/youtube';

这似乎是 Google API 的 PHP 客户端库的问题。转到 GOOGLE_LIB_PATH/Http/MediaFileUpload.php 并替换此行:

$this->request->setBaseComponent($base . '/upload'); 

有了这个:

$this->request->setBaseComponent($base . 'upload'); 

重试并分享结果。我遇到了与 Google Pubsub API 类似的问题,其中 API 库设置的路径不正确。