2

I have two channels under my Google account. I can upload the videos to my main channel using the Youtube API for PHP. I want to upload the videos to my second channel (Not the main channel). I have searched a lot, but could not find a solution for that. What I just figured is, that I may need to use the Google_Service_YouTube_ChannelContentOwnerDetails() class, but I don't know how to specify this detail to $youtube->videos->insert("status,snippet", $video) method specified at https://github.com/youtube/api-samples/blob/master/php/resumable_upload.php.

I have also found that there is onBehalfOfContentOwner and onBehalfOfContentOwnerChannel attributes which I may need to use, but I think these parameters are for the accounts which have given access to other channels to manage their channels on behalf of them, and I don't need to use them because I own my two channels under one Google account. (Maybe I am wrong).

I just want to upload the videos to my second account by authorizing from my Main channel.

Any help will be highly appreciated in this regard.

2 Answers2

3

There's no way for one to specify a channel ID to the Videos.insert API endpoint. But bear with me for a while...

The first time one issues the OAuth 2.0 authorization flow, his/her app is receiving something as the following JSON object:

{
  "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
  "expires_in": 3920,
  "token_type": "Bearer",
  "scope": "https://www.googleapis.com/auth/youtube.force-ssl",
  "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}

The access token is short-lived, while the refresh token is long-lived (but can be revoked at will). This kind of JSON object gets cached in a local file, for to be used later when the app needs a fresh access token (that is obtained from the API based on the stored refresh token). The access token needs to be passed on to each API endpoint that requires authorization.

The above two tokens are relative to the Google account that one has been logged into during the authorization process. Under most common circumstances, each Google account is uniquely associated to a certain YouTube account (i.e. channel). But there can be Google accounts that are associated with multiple YouTube channels (see this doc, the section Use Brand Accounts managed by your Google Account).

Now my conjecture: I do think that it's possible for one to have N such JSON objects (relative to the same scope; e.g. https://www.googleapis.com/auth/youtube.upload), each being associated with a different Youtube channel -- all under the umbrella of a single Google account --, all being stored locally in separate files (or even all in one file), such that, when issuing API calls that need to target a given channel, to choose programmatically the associated pair of tokens out of the whole set of N objects.


The second part of my answer contains source code illustrating my conjecture above. It's not my intention to present here a complete and/or an all-encompassing solution, but only to set forth an application frame that would fit the various ways a concrete PHP app may function (e.g. single desktop apps or apps that run autonomously on remote servers that have no browser installed).

The app manages a set of JSON files -- all stored in the same directory ($auth_conf_path). Each file contains the required credentials relative to a certain channel, such that to be able to create from it a proper instance of class Google_Client. The names of these JSON files are of form CHANNEL_ID.json, where CHANNEL_ID is the ID of the channel to which this file is referring to.

The app is split into two parts: one creating these credentials JSON files upon initiating OAuth authorization flows; the other making API calls relative to a given channel for which a credentials JSON file already exists.

The first part of the app, by using initChannelCredentials within an usual PHP OAuth flow, produces IDs of and credentials JSON files relative to YouTube channels to which the app was granted access to.

The second part of the app, upon obtaining an instance of Google_Client class from makeChannelClient, makes actual API endpoint calls relative to the channel identified by the ID passed to that function.

function initChannelCredentials(
  $auth_conf_path, $scopes, $redirect_uri, $client_code)
{
  if (!is_dir($auth_conf_path))
    throw new InvalidArgumentException(sprintf(
      'Auth config path "%s" does not exist', $auth_conf_path));

  $client = new Google_Client();
  $client->setAuthConfigFile(
    $auth_conf_path . DIRECTORY_SEPARATOR . 'client_secrets.json');
  $client->setRedirectUri($redirect_uri);
  $client->setScopes($scopes);

  $cred = $client->fetchAccessTokenWithAuthCode($client_code);

  $youtube = new Google_Service_YouTube($client);
  $response = $youtube->channels->listChannels('id', array(
    'mine' => 'true'
  ));

  $channel_id = $response[0]['id'];
  $cred_file = $auth_conf_path . DIRECTORY_SEPARATOR . $channel_id . '.json';
  if (file_exists($cred_file))
    throw new InvalidArgumentException(sprintf(
      'Credentials file for channel "%s" already exists', $channel_id));

  file_put_contents($cred_file, json_encode($cred));

  return $channel_id;
}
function makeChannelClient($auth_conf_path, $channel_id)
{
  if (!is_dir($auth_conf_path))
    throw new InvalidArgumentException(sprintf(
      'Auth config path "%s" does not exist', $auth_conf_path));

  $cred_file = $auth_conf_path . DIRECTORY_SEPARATOR . $channel_id . '.json';
  if (!file_exists($cred_file))
    throw new InvalidArgumentException(sprintf(
      'Credentials file for channel "%s" does not exist', $channel_id));

  if (!$cred = json_decode(file_get_contents($cred_file), true))
    throw new LogicException(sprintf(
      'Invalid content of credentials file for channel "%s"', $channel_id));

  $client = new Google_Client();
  $client->setAccessType('offline');
  $client->setScopes($cred['scope']);
  $client->setAccessToken($cred);

  if ($client->isAccessTokenExpired()) {
    $client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
    file_put_contents($cred_file, json_encode($client->getAccessToken()));
  }

  return $client;
}

Note that an app as described above is a bit more general than the one subsumed to the original post and to my conjecture above. The credentials JSON files -- equally the YouTube channels -- managed by the app are not required to work all under the umbrella of a single Google account (as was prescribed by my conjecture). These channels may well be relative to different Google accounts, as long as the concrete incarnation of such a PHP app is able to handle properly multiple Google accounts.

stvar
  • 6,551
  • 2
  • 13
  • 28
  • 1
    Thanks for the detailed reply. Can you give an example for obtaining the N JSON objects by authorizing with the main channel? – Kamal Ashraf Gill Jul 19 '20 at 11:28
  • Yes, this would by the next part of my answer, which I'll formulate in a little while. As far as I know, there's no public code sample that tackles the issue at hand -- regardless of implementation language (PHP, Python, Java, etc.). I also need you to stay tuned and help along the way, since you already have a Google account that is associated with two YouTube channels. – stvar Jul 19 '20 at 12:56
  • 1
    Please read carefully [this question](https://stackoverflow.com/q/45350308/8327971). The OP says that, during the OAuth flow, he is presented with a screen that let's him choose between two YouTube channels (after he have chosen one of his Google account, thus upon already being authenticated against a certain Google account). I need you to confirm that you're also presented with that kind of screen that allows you pick from one of the two of your associated YouTube channels -- this happening after you've logged in your Google account. – stvar Jul 19 '20 at 13:06
  • 1
    Yes, I am presented with both YouTube channels, and I select the Main channel from the list. – Kamal Ashraf Gill Jul 19 '20 at 16:29
  • That's very good news! Now, I want you to test the following thing: do select the second channel (not the main one) and proceed to the flow completion. Then initiate an upload (of a small video) just to verify that this newly added video appears on the secondary channel (and, of course, does not on the main one). Note that I did played (and you could also play even better) with [OAuth Playground](https://developers.google.com/oauthplayground) (assuming that you're by now quite familiar with the OAuth 2.0 flow) and things look quite promising. – stvar Jul 19 '20 at 17:52
  • Thanks for suggesting the OAuth Playground. Please tell me, if I get the Refresh token from the OAuth playground **once**, and **programmatically refresh the access token**, then can I upload the videos without prompting the Google signing page (**forever**)? – Kamal Ashraf Gill Jul 21 '20 at 11:30
  • Have you proceeded to what I was asking you (uploading a small video to the second channel)? Does that succeeded? (If your answer is yes -- i.e. successfully uploaded on the second channel, and that new video is not present on the first channel --, then I have the green light to update my answer above.) – stvar Jul 21 '20 at 12:05
  • The short answer to *forever* is yes. However the longer one is a bit more intricate: see the doc [Using OAuth 2.0 to Access Google APIs](https://developers.google.com/identity/protocols/oauth2), specifically the section [Refresh token expiration](https://developers.google.com/identity/protocols/oauth2#expiration). – stvar Jul 21 '20 at 12:11
  • Yes, I have successfully uploaded the video to the second channel, and the video is available in my second channel and not on the main channel. – Kamal Ashraf Gill Jul 21 '20 at 12:47
  • One more caveat to your proposal (getting the refresh token via OAuth Playground): if you'll log in to your Google account and then visit [the account's security permissions page](https://myaccount.google.com/permissions) and you'll see listed something like `Google OAuth 2.0 Playground: Has access to YouTube`. I deem that to be *less of a right solution for production stage* of an app (indeed OK when testing things around). – stvar Jul 21 '20 at 12:53
  • The proper way would be to have that permissions list contain *the real name of the app* that's been given access to the account/channel. This means that *your app* should (better say: must) be able to handle both the initialization flow of OAuth (the *once* part of your comment above) and the day to day function of whatever that would be (in your case: uploading videos), including the transparent (automated) logic of refreshing expired access tokens. – stvar Jul 21 '20 at 12:54
  • Thanks for you second confirmation. That's very good! – stvar Jul 21 '20 at 12:56
  • Thanks for your help, I will try to use all the information in my project and will let you know in this thread. – Kamal Ashraf Gill Jul 21 '20 at 13:04
  • Stay tuned. I'll update my answer soon (I need to compose it): a solution along the lines prescribed by my conjecture above that will (hopefully) handle OAuth flow smoothly. – stvar Jul 21 '20 at 13:07
  • Posted part two of my answer. – stvar Jul 22 '20 at 11:56
  • Thankyou so much. I have marked it as answer. Will try it and let you know. – Kamal Ashraf Gill Jul 23 '20 at 07:47
-1

I tried setting '--channelId' in snippet object but that doesn't seem to work. The solution is to generate different oauth2.json for different sub-channels.

Dhruv Kaushal
  • 632
  • 8
  • 17