Twitch API v5 を利用してユーザー情報を取得する

Twitch API v5はすでにdeprecatedであるが、v5でユーザー情報を取得する方法を記載する。あえてv5でユーザー情報を取得する理由は、New Twitch APIで取得できるユーザー情報には、アカウント作成日時や更新日時が含まれていないが、Twitch API v5ではそれが取得可能なため。

下記はv5を利用してユーザー情報を取得するコード。


class TwitchApi_v5_Client
{
  private $endpoint = 'https://api.twitch.tv/kraken/';

  private $oauthClientIdHeader;

  public function __construct($CLIENT_ID, $ACCESS_TOKEN = "")
  {
    $this->oauthClientIdHeader = "Client-ID: $CLIENT_ID";
  }

  public function get($resource, array $queryParams)
  {
    $url = $this->endpoint . $resource;

    if (!empty($queryParams))
      $url .= '?' . http_build_query($queryParams);

    $response = $this->execCurlGET($url);
    sleep(1);

    return json_decode($response, true);
  }

  private function execCurlGET($url)
  {
    $cmd = 'curl' .
           " -H 'Accept: application/vnd.twitchtv.v5+json'" .
           " -H '{$this->oauthClientIdHeader}'" .
           " -X GET '$url'";

    echo "# $cmd\n";

    return exec($cmd);
  }
}


$CLIENT_ID = '_CLIENT_ID_';
$client = new TwitchApi_v5_Client($CLIENT_ID);

$params = [
  'login' => 'ninja',
];

$response = $client->get('users', $params);
var_dump($response);

Twitch API v5でユーザー情報を取得するには、

https://api.twitch.tv/kraken/users

に対し、クエリパラメータとして、login パラメータにユーザー名を指定してGETリクエストを行えば良い。
※公式ドキュメントでは リクエストURLに user IDs と記載されていて紛らわしいが、ユーザーIDではなくユーザー名を指定する。

上記のコードを実行するとAPIレスポンスとして、下記のようなユーザー情報を取得することできる。
※実際はAPIレスポンスとしてJSONデータが返されるが、json_decode() を使って配列データに変換している。

レスポンスデータに含まれる created_atupdated_at がNew Twitch APIでは取得できない情報で、それぞれアカウントの作成日時、更新日時にあたる情報となる。


array(2) {
  ["_total"]=>
  int(1)
  ["users"]=>
  array(1) {
    [0]=>
    array(8) {
      ["display_name"]=>
      string(5) "Ninja"
      ["_id"]=>
      string(8) "19571641"
      ["name"]=>
      string(5) "ninja"
      ["type"]=>
      string(4) "user"
      ["bio"]=>
      NULL
      ["created_at"]=>
      string(27) "2011-01-16T04:31:20.024666Z"
      ["updated_at"]=>
      string(27) "2020-08-26T11:23:23.750014Z"
      ["logo"]=>
      string(109) "https://static-cdn.jtvnw.net/jtv_user_pictures/cef31105-8a6e-4211-a74b-2f0bbd9791fb-profile_image-300x300.png"
    }
  }
}

New Twitch API を利用してユーザー(チャンネル)のフォロワー数を取得する

New Twitch APIを利用してユーザー(チャンネル)のフォロワー数を取得する。Twitch API v5ではフォロワー数はチャンネルオブジェクトのプロパティの一つであったが、New Twitch APIではデータモデルが見直され、チャンネルオブジェクトはユーザーオブジェクトに統合された。よって、New Twitch APIでは、ユーザーリソースに対してリクエストを行うことによって、フォロワー数を取得することができる。

下記は、New Twitch APIでユーザーリソースに対してリクエストを行い、フォロワー数を取得するコードの例。


class TwitchApiClient
{
  private $endpoint = 'https://api.twitch.tv/helix/';

  private $oauthClientIdHeader;
  private $oauthTokenHeader;

  public function __construct($CLIENT_ID, $ACCESS_TOKEN)
  {
    $this->oauthClientIdHeader = "Client-ID: $CLIENT_ID";
    $this->oauthTokenHeader = "Authorization: Bearer $ACCESS_TOKEN";
  }

  public function get($resource, array $queryParams)
  {
    $url = $this->endpoint . $resource;

    if (!empty($queryParams))
      $url .= '?' . http_build_query($queryParams);

    $response = $this->execCurlGET($url);
    sleep(1);

    return json_decode($response, true);
  }

  private function execCurlGET($url)
  {
    $cmd = 'curl' .
           " -H '{$this->oauthClientIdHeader}'" .
           " -H '{$this->oauthTokenHeader}'" .
           " -X GET '$url'";

    echo "# $cmd\n";

    return exec($cmd);
  }
}


$CLIENT_ID = '_CLIENT_ID_';
$ACCESS_TOKEN = '_ACCESS_TOKEN_';

$client = new TwitchApiClient($CLIENT_ID, $ACCESS_TOKEN);

$params = [
  'to_id' => '19571641', // ninja
  'first' => 1,
];

$response = $client->get('users/follows', $params);
var_dump($response);

フォロワー数は、

https://api.twitch.tv/helix/users/follows

に対し、クエリパラメータとして to_id パラメータを付加してGETリクエストを行うことによって取得することができる。

to_id にはユーザーIDを指定する。上記のコードの例では、ユーザー名 ninja のユーザーIDを指定している。

APIレスポンスとして返ってくるデータは、totaldatapagination フィールドから構成され、total の値が取得したいフォロワー数に該当する。

data フィールドには、フォロワーのユーザー情報が含まれている。デフォルトでは20件のユーザー情報が含まれる。この件数は、APIリクエストを行う際のクエリパラメータとして first パラメータに値を設定することで変更できる。設定できる最大値は 100 まで。上記のコードの例では、フォロワー数を取得するのが主な目的なため、first1 を指定し、レスポンスに含めるデータ件数を抑制している。

pagination フィールドのデータは、結果のページネーションに利用するためのもの。data フィールドに含まれるユーザー情報の次の結果セットを取得したい場合、APIリクエスト時のクエリパラメータとして after パラメータに pagination フィールドの cursor の値を指定すれば良い。

Twitch API v5 を利用して人気の動画情報を取得する

Twitch API v5を利用して人気の動画情報を取得することができる。これは特定の期間における視聴数(再生回数)に基づいたもので、視聴数が多い順にソートされた動画情報がAPIレスポンスとして返ってくる。

Twitch API v5はすでにdeprecatedで、New Twitch APIを利用することが推奨されているが、New Twitch APIには現状、人気の動画情報をリクエストする手段が提供されていないので、v5を利用する。

下記は、v5を利用して人気の動画情報を取得するコード。


class TwitchApi_v5_Client
{
  private $endpoint = 'https://api.twitch.tv/kraken/';

  private $oauthClientIdHeader;

  public function __construct($CLIENT_ID, $ACCESS_TOKEN = "")
  {
    $this->oauthClientIdHeader = "Client-ID: $CLIENT_ID";
  }

  public function get($resource, array $queryParams)
  {
    $url = $this->endpoint . $resource;

    if (!empty($queryParams))
      $url .= '?' . http_build_query($queryParams);

    $response = $this->execCurlGET($url);
    sleep(1);

    return json_decode($response, true);
  }

  private function execCurlGET($url)
  {
    $cmd = 'curl' .
           " -H 'Accept: application/vnd.twitchtv.v5+json'" .
           " -H '{$this->oauthClientIdHeader}'" .
           " -X GET '$url'";

    echo "# $cmd\n";

    return exec($cmd);
  }
}


$CLIENT_ID = '_CLIENT_ID_';
$client = new TwitchApi_v5_Client($CLIENT_ID);

$params = [
  'language' => 'en,ja',
  'period' => 'all',
  'limit' => 20,
  'offset' => 0,
];

do
{
  $response = $client->get('videos/top', $params);
  var_dump($response);
  $params['offset']++;

  if ($params['offset'] >= 2)
    break;

} while($response);

Twitch API v5を利用して人気の動画情報を取得するには下記のURLに対してGETリクエストを送信すれば良い。

https://api.twitch.tv/kraken/videos/top

APIリクエストを送る際のクエリパラメータとして、上記のコードの例では、languageperiodlimitoffset パラメータを設定している。

language パラメータに言語コードを指定すると、APIレスポンスに含める結果を指定した言語に紐付いたものに限定させることができる。上記の例では、en,ja を指定し英語と日本語の動画に限定している。

period パラメータでは、人気の期間を指定することができる。この値には、weekmonthall のいずれかを指定できる。上記の例では、all を指定し全期間における累計の視聴数に基づいた結果を取得するようにしている。

limit パラメータは、APIレスポンスに含める動画件数を指定するもので、デフォルト値は 10、最大値として 100 を設定できる。今回の例では、20 を指定し、1回のリクエストあたり20件の結果を取得するようにしている。

offset パラメータは、結果のページネーションのためのもので、デフォルトでは 0 が設定される。このパラメータの値をインクリメントしていけば、次の結果を順に取得していくことができる。

Google APIs Client Library for PHP における YouTube サービスの実装の概要を確認する

YouTube Data API v3において定義されているリソースとPHPクライアントライブラリ (Google APIs Client Library for PHP) における実装がどのように対応しているか見ていきたい。

対応関係の概要だけでも知っておくと、APIリファレンスを読むとき、ドキュメント上での記述とPHPでアプリケーションを実装する際に必要なコードを頭の中で対応付けられるようになる。

APIリファレンスに記載されている通り、APIを利用して操作できるリソースには下記のものがある。

  • activities リソース
  • captions リソース
  • channelBanners リソース
  • channelSections リソース
  • channels リソース
  • commentThreads リソース
  • comments リソース
  • guideCategories リソース
  • i18nLanguages リソース
  • i18nRegions リソース
  • liveBroadcasts リソース(APIレファレンスに記載なし)
  • liveChatBans リソース(APIレファレンスに記載なし)
  • liveChatMessages リソース(APIレファレンスに記載なし)
  • liveChatModerators リソース(APIレファレンスに記載なし)
  • liveStreams リソース(APIレファレンスに記載なし)
  • members リソース
  • membershipsLevels リソース
  • playlistItems リソース
  • playlists リソース
  • search リソース
  • sponsors リソース(APIレファレンスに記載なし)
  • subscriptions リソース
  • superChatEvents リソース(APIレファレンスに記載なし)
  • thumbnails リソース
  • videoAbuseReportReasons リソース
  • videoCategories リソース
  • videos リソース
  • watermarks リソース

これらのリソースとPHPクライアントライブラリにおける実装の対応関係を見ていくために、簡単なコードを使って結果を確認していく。


まず、YouTube Data APIのリクエスト操作を行うには、下記のように Google_Service_YouTube オブジェクトを作成する。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

ここで下記のコードを実行すると、$youtube (Google_Service_YouTube オブジェクト) が持つプロパティと、そのクラスを確認することができる。


$propertyNames = [
  'activities',
  'captions',
  'channelBanners',
  'channelSections',
  'channels',
  'commentThreads',
  'comments',
  'guideCategories',
  'i18nLanguages',
  'i18nRegions',
  'liveBroadcasts',
  'liveChatBans',
  'liveChatMessages',
  'liveChatModerators',
  'liveStreams',
  'members',
  'membershipsLevels',
  'playlistItems',
  'playlists',
  'search',
  'sponsors',
  'subscriptions',
  'superChatEvents',
  'thumbnails',
  'videoAbuseReportReasons',
  'videoCategories',
  'videos',
  'watermarks',
];

foreach ($propertyNames as $propertyName)
{
  echo "property: ".'$youtube->'."$propertyName\n";

  if (property_exists($youtube, $propertyName))
  {
    echo "  ==> class       : " . get_class($youtube->$propertyName) . "\n";
    echo "  ==> parent class: " . get_parent_class($youtube->$propertyName) . "\n\n";
  }
  else
  {
    echo "  ==> not exists\n\n";
  }
}
[実行結果]

property: $youtube->activities
  ==> class       : Google_Service_YouTube_Resource_Activities
  ==> parent class: Google_Service_Resource

property: $youtube->captions
  ==> class       : Google_Service_YouTube_Resource_Captions
  ==> parent class: Google_Service_Resource

property: $youtube->channelBanners
  ==> class       : Google_Service_YouTube_Resource_ChannelBanners
  ==> parent class: Google_Service_Resource

property: $youtube->channelSections
  ==> class       : Google_Service_YouTube_Resource_ChannelSections
  ==> parent class: Google_Service_Resource

property: $youtube->channels
  ==> class       : Google_Service_YouTube_Resource_Channels
  ==> parent class: Google_Service_Resource

property: $youtube->commentThreads
  ==> class       : Google_Service_YouTube_Resource_CommentThreads
  ==> parent class: Google_Service_Resource

property: $youtube->comments
  ==> class       : Google_Service_YouTube_Resource_Comments
  ==> parent class: Google_Service_Resource

property: $youtube->guideCategories
  ==> class       : Google_Service_YouTube_Resource_GuideCategories
  ==> parent class: Google_Service_Resource

property: $youtube->i18nLanguages
  ==> class       : Google_Service_YouTube_Resource_I18nLanguages
  ==> parent class: Google_Service_Resource

property: $youtube->i18nRegions
  ==> class       : Google_Service_YouTube_Resource_I18nRegions
  ==> parent class: Google_Service_Resource

property: $youtube->liveBroadcasts
  ==> class       : Google_Service_YouTube_Resource_LiveBroadcasts
  ==> parent class: Google_Service_Resource

property: $youtube->liveChatBans
  ==> class       : Google_Service_YouTube_Resource_LiveChatBans
  ==> parent class: Google_Service_Resource

property: $youtube->liveChatMessages
  ==> class       : Google_Service_YouTube_Resource_LiveChatMessages
  ==> parent class: Google_Service_Resource

property: $youtube->liveChatModerators
  ==> class       : Google_Service_YouTube_Resource_LiveChatModerators
  ==> parent class: Google_Service_Resource

property: $youtube->liveStreams
  ==> class       : Google_Service_YouTube_Resource_LiveStreams
  ==> parent class: Google_Service_Resource

property: $youtube->members
  ==> class       : Google_Service_YouTube_Resource_Members
  ==> parent class: Google_Service_Resource

property: $youtube->membershipsLevels
  ==> class       : Google_Service_YouTube_Resource_MembershipsLevels
  ==> parent class: Google_Service_Resource

property: $youtube->playlistItems
  ==> class       : Google_Service_YouTube_Resource_PlaylistItems
  ==> parent class: Google_Service_Resource

property: $youtube->playlists
  ==> class       : Google_Service_YouTube_Resource_Playlists
  ==> parent class: Google_Service_Resource

property: $youtube->search
  ==> class       : Google_Service_YouTube_Resource_Search
  ==> parent class: Google_Service_Resource

property: $youtube->sponsors
  ==> class       : Google_Service_YouTube_Resource_Sponsors
  ==> parent class: Google_Service_Resource

property: $youtube->subscriptions
  ==> class       : Google_Service_YouTube_Resource_Subscriptions
  ==> parent class: Google_Service_Resource

property: $youtube->superChatEvents
  ==> class       : Google_Service_YouTube_Resource_SuperChatEvents
  ==> parent class: Google_Service_Resource

property: $youtube->thumbnails
  ==> class       : Google_Service_YouTube_Resource_Thumbnails
  ==> parent class: Google_Service_Resource

property: $youtube->videoAbuseReportReasons
  ==> class       : Google_Service_YouTube_Resource_VideoAbuseReportReasons
  ==> parent class: Google_Service_Resource

property: $youtube->videoCategories
  ==> class       : Google_Service_YouTube_Resource_VideoCategories
  ==> parent class: Google_Service_Resource

property: $youtube->videos
  ==> class       : Google_Service_YouTube_Resource_Videos
  ==> parent class: Google_Service_Resource

property: $youtube->watermarks
  ==> class       : Google_Service_YouTube_Resource_Watermarks
  ==> parent class: Google_Service_Resource

実行結果を見ると、例えば $youtube オブジェクトは、videos リソースに対応する videos プロパティを持ち、そのプロパティは Google_Service_YouTube_Resource_Videos クラスのオブジェクトであることが分かる。また、各プロパティに対応するクラスは、全て Google_Service_Resource クラスを親に持つことが分かる。

このことから、APIリファレンスに記載された videos リソースに関する説明は、PHPでは $youtube (Google_Service_YouTube オブジェクト) の videos プロパティに関する説明と解釈することができる。


次に、プロパティが持つメソッドを見ていきたい。下記のコードを実行すると、各プロパティが持つメソッドを確認することができる。


$methodNames = [
 'list',
 'insert',
 'update',
 'download',
 'delete',
 'markAsSpam',
 'setModerationStatus',
 'rate',
 'getRating',
 'reportAbuse',
 'set',
 'unset',
];

foreach ($propertyNames as $propertyName)
{
  echo "property: ".'$youtube->'."$propertyName\n";

  foreach ($methodNames as $methodName)
  {
    if (method_exists($youtube->$propertyName, $methodName))
    {
      echo "  ==> {$methodName}()\n";
    }
    else
    {
      $PropertyName = $propertyName;
      $PropertyName[0] = strtoupper($PropertyName[0]);

      $methodNamePropertyName = $methodName . $PropertyName;

      if (method_exists($youtube->$propertyName, $methodNamePropertyName))
      {
        echo "  ==> {$methodNamePropertyName}()\n";
      }
    }
  }
  echo "\n";
}
[実行結果]

property: $youtube->activities
  ==> listActivities()
  ==> insert()

property: $youtube->captions
  ==> listCaptions()
  ==> insert()
  ==> update()
  ==> download()
  ==> delete()

property: $youtube->channelBanners
  ==> insert()

property: $youtube->channelSections
  ==> listChannelSections()
  ==> insert()
  ==> update()
  ==> delete()

property: $youtube->channels
  ==> listChannels()
  ==> update()

property: $youtube->commentThreads
  ==> listCommentThreads()
  ==> insert()
  ==> update()

property: $youtube->comments
  ==> listComments()
  ==> insert()
  ==> update()
  ==> delete()
  ==> markAsSpam()
  ==> setModerationStatus()

property: $youtube->guideCategories
  ==> listGuideCategories()

property: $youtube->i18nLanguages
  ==> listI18nLanguages()

property: $youtube->i18nRegions
  ==> listI18nRegions()

property: $youtube->liveBroadcasts
  ==> listLiveBroadcasts()
  ==> insert()
  ==> update()
  ==> delete()

property: $youtube->liveChatBans
  ==> insert()
  ==> delete()

property: $youtube->liveChatMessages
  ==> listLiveChatMessages()
  ==> insert()
  ==> delete()

property: $youtube->liveChatModerators
  ==> listLiveChatModerators()
  ==> insert()
  ==> delete()

property: $youtube->liveStreams
  ==> listLiveStreams()
  ==> insert()
  ==> update()
  ==> delete()

property: $youtube->members
  ==> listMembers()

property: $youtube->membershipsLevels
  ==> listMembershipsLevels()

property: $youtube->playlistItems
  ==> listPlaylistItems()
  ==> insert()
  ==> update()
  ==> delete()

property: $youtube->playlists
  ==> listPlaylists()
  ==> insert()
  ==> update()
  ==> delete()

property: $youtube->search
  ==> listSearch()

property: $youtube->sponsors
  ==> listSponsors()

property: $youtube->subscriptions
  ==> listSubscriptions()
  ==> insert()
  ==> delete()

property: $youtube->superChatEvents
  ==> listSuperChatEvents()

property: $youtube->thumbnails
  ==> set()

property: $youtube->videoAbuseReportReasons
  ==> listVideoAbuseReportReasons()

property: $youtube->videoCategories
  ==> listVideoCategories()

property: $youtube->videos
  ==> listVideos()
  ==> insert()
  ==> update()
  ==> delete()
  ==> rate()
  ==> getRating()
  ==> reportAbuse()

property: $youtube->watermarks
  ==> set()
  ==> unsetWatermarks()

実行結果を見ると、list メソッド以外は、APIリファレンスに記載された通りの名前でメソッドが用意されている。list メソッドについては、listVideos のようにリソース名が付いた名前となっている。おそらく、これは、list() がPHPにおいて特別な意味を持つキーワードとして登録されているので、混乱を避けるためにこのようなメソッド名を採用したと考えられる。


最後にAPIレスポンスについて見ていきたい。下記のコードを実行するとレスポンスに含まれるプロパティと、そのクラスを確認することができる。


$channelId = "UCrXUsMBcfTVqwAS7DKg9C0Q"; // YouTube Japan

$params = [
  'id' => $channelId
];

$response = $youtube->channels->listChannels('snippet', $params);

echo "response\n";
echo "  ==> class              : " . get_class($response) . "\n";
echo "  ==> parent class       : " . get_parent_class($response) . "\n";
echo "  ==> parent parent class: " . get_parent_class(get_parent_class($response)) . "\n\n";

echo "response.pageInfo\n";
echo "  ==> class       : " . get_class($response['pageInfo']) . "\n";
echo "  ==> parent class: " . get_parent_class($response['pageInfo']) . "\n\n";

echo "// access as array\n";

foreach ($response['items'] as $item)
{
  echo "item\n";
  echo "  ==> class       : " . get_class($item) . "\n";
  echo "  ==> parent class: " . get_parent_class($item) . "\n\n";

  echo "item.snippet\n";
  echo "  ==> class       : " . get_class($item['snippet']) . "\n";
  echo "  ==> parent class: " . get_parent_class($item['snippet']) . "\n\n";
}

echo "// access with arrow operator\n";

foreach ($response->items as $item)
{
  echo "item.snippet\n";
  echo "  ==> class       : " . get_class($item->snippet) . "\n";
  echo "  ==> parent class: " . get_parent_class($item->snippet) . "\n\n";
}

echo "// just iterate over the object\n";

foreach ($response as $item)
{
  echo "item.snippet\n";
  echo "  ==> class       : " . get_class($item['snippet']) . "\n";
  echo "  ==> parent class: " . get_parent_class($item['snippet']) . "\n\n";
}
[実行結果]

response
  ==> class              : Google_Service_YouTube_ChannelListResponse
  ==> parent class       : Google_Collection
  ==> parent parent class: Google_Model

response.pageInfo
  ==> class       : Google_Service_YouTube_PageInfo
  ==> parent class: Google_Model

// access as array
item
  ==> class       : Google_Service_YouTube_Channel
  ==> parent class: Google_Model

item.snippet
  ==> class       : Google_Service_YouTube_ChannelSnippet
  ==> parent class: Google_Model

// access with arrow operator
item.snippet
  ==> class       : Google_Service_YouTube_ChannelSnippet
  ==> parent class: Google_Model

// just iterate over the object
item.snippet
  ==> class       : Google_Service_YouTube_ChannelSnippet
  ==> parent class: Google_Model

実行結果からAPIレスポンスに含まれるプロパティは、全て Google_Model を継承したクラスのオブジェクトであることが分かる。

Google_Model クラスは、ArrayAccess インターフェイスを実装したクラスとなっているため、アロー演算子を使わず配列としてオブジェクトのプロパティにアクセスすることが可能となっている。

Google_Collection クラスは Iterator インターフェイスを実装したクラスで、反復処理では自動的に items 配列を参照するようになっているので、例えば、foreach を使うとき、

foreach ($response['items'] as $item)

と明示的に items 配列を書かなくても、

foreach ($response as $item)

というように、単に $response オブジェクトに対して foreach を書くことができる。

YouTube Data API v3を利用してチャンネルのプロフィール情報を取得する

独自にYouTubeチャンネルのプロフィール情報ページを作成することを想定し、プロフィール情報ページは下記のデータから構成されるものと定義し、APIでこれらのデータを取得することを考えてみる。

  • チャンネルアイコン画像
  • チャンネルアート画像
  • チャンネル名
  • アップロード動画数
  • 登録者数
  • 動画再生回数
  • チャンネルに関連付けられた国
  • チャンネル作成日

これらのデータは、YouTube Data APIの channels リソースに含まれるプロパティと下記のように対応する。

  • snippet
    • snippet.thumbnails:チャンネルアイコン画像
    • snippet.title:チャンネル名
    • snippet.country:チャンネルに関連付けられた国
    • snippet.publishedAt:チャンネル作成日
  • statistics
    • statistics.videoCount:アップロード動画数
    • statistics.subscriberCount:登録者数
    • statistics.viewCount:動画再生回数
  • brandingSettings

これを踏まえて、APIを利用して上記のプロパティを取得するコードの例が下記。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

$json = file_get_contents("channelIds.json");
$channelIds = json_decode($json, true);

foreach (array_chunk($channelIds, 50) as $ids)
{
  $params = [
    'id' => implode(",", $ids),
    'maxResults' => 50,
  ];

  $response = $youtube->channels->listChannels('snippet,statistics,brandingSettings', $params);

  foreach ($response['items'] as $i => $item)
  {
    echo "#$i\n";
    echo "id         : {$item['id']}\n";
    echo "title      : {$item['snippet']['title']}\n";
    echo "country    : {$item['snippet']['country']}\n";
    echo "publishedAt: {$item['snippet']['publishedAt']}\n";

    echo "viewCount      : {$item['statistics']['viewCount']}\n";
    echo "subscriberCount: {$item['statistics']['subscriberCount']}\n";
    echo "videoCount     : {$item['statistics']['videoCount']}\n";

    foreach ($item['snippet']['thumbnails'] as $key => $img)
    {
      echo "$key: {$img['url']}\n";
    }

    foreach ($item['brandingSettings']['image'] as $key => $imgUrl)
    {
      if (!is_null($imgUrl))
      {
        echo "$key: $imgUrl\n";
      }
    }
  }
  sleep(1);
}

前述のプロフィール情報に必要なプロパティをリクエストするには、listChannels() メソッドの part 引数に snippetstatisticsbrandingSettings を指定すれば良い。

フィルタのパラメータとして、maxResults パラメータには、APIレスポンスに含める結果の件数を指定する。デフォルト値は 5、最大値は 50 となる。上記コードでは最大値 50 を指定している。また、id パラメータにチャンネルIDを指定する。カンマ区切りで複数のチャンネルIDを指定できる。上記のコードでは、maxResults パラメータに合わせて、カンマ区切りで一度に最大50のチャンネルIDを指定するようにしている。

YouTube Data API v3を利用してチャンネルの国情報を取得する

なぜか日本語版の公式ドキュメントには記載されていないが、英語版を確認すると、APIを利用してYouTubeチャンネルに関連付けられた国情報を取得できることが分かる。

channels リソースには brandingSettings プロパティがある。このプロパティ内の brandingSettings.channel.country プロパティにはチャンネルに関連付けられた国名コードが入っている。
※後述するが、snippet.country プロパティを参照しても良い。ただし、この値は brandingSettings.channel.country プロパティにセットした値が入っているだけのもの。

APIリクエストを行って、このプロパティを取得するコードの例が下記。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

$channelIds = [
  "UC_x5XG1OV2P6uZZ5FSM9Ttw", // Google Developers
  "UCrXUsMBcfTVqwAS7DKg9C0Q", // YouTube Japan
];

$params = [
  'id' => implode(",", $channelIds)
];

$response = $youtube->channels->listChannels('brandingSettings', $params);

foreach ($response['items'] as $i => $item)
{
  echo "#$i\n";
  echo "id     : {$item['id']}\n";
  echo "title  : {$item['brandingSettings']['channel']['title']}\n";
  echo "country: {$item['brandingSettings']['channel']['country']}\n\n";
}

listChannels() メソッドを使ってAPIリクエストを行う。part 引数には brandingSettings を指定し、フィルタのパラメータには、id パラメータに取得したいチャンネルIDを指定する。カンマ区切りで複数のIDを指定することができる。

そして、APIレスポンスとして返ってきたデータの中にある brandingSettings.channel.country プロパティの値を取得する。値は文字列で国名コードを表すものが入っている。

ただし、チャンネルによっては、brandingSettings.channel.country プロパティの値がセットされていないようで、NULL 値の場合がある。


次に、snippet.country プロパティを参照するバージョンのコードが下記。snippet.country プロパティの値には brandingSettings.channel.country プロパティにセットした値と同じものが入る。コードも、プロパティ名を変更するだけで本質的に違いはない。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

$channelIds = [
  "UC_x5XG1OV2P6uZZ5FSM9Ttw", // Google Developers
  "UCrXUsMBcfTVqwAS7DKg9C0Q", // YouTube Japan
];

$params = [
  'id' => implode(",", $channelIds)
];

$response = $youtube->channels->listChannels('snippet', $params);

foreach ($response['items'] as $i => $item)
{
  echo "#$i\n";
  echo "id     : {$item['id']}\n";
  echo "title  : {$item['snippet']['title']}\n";
  echo "country: {$item['snippet']['country']}\n\n";
}

YouTube Data API v3を利用してチャンネルの画像情報を取得する

APIを利用してYouTubeチャンネルに関連した画像情報を取得することができる。チャンネルに関連する主な画像として下記の2つがある。

  • チャンネルアイコン
  • チャンネルアート

チャンネルアイコンは、チャンネルのプロフィール画像で、縦横のサイズが等しい正方形または円形の画像となる。小、中、大の3種類の画像がある。

チャンネルアートは、チャンネルページ上部の背景/バナー画像として表示されるもので、各デバイス向けアプリケーション/画面解像度に合わせて複数の画像がある。

下記のコードは、これらの画像をAPIを利用してリクエストするもの。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

$channelId = "UCrXUsMBcfTVqwAS7DKg9C0Q"; // YouTube Japan

$params = [
  'id' => $channelId,
];

$response = $youtube->channels->listChannels('snippet,brandingSettings', $params);

foreach ($response['items'] as $i => $item)
{
  echo "channelId: {$item['id']}\n";

  foreach ($item['snippet']['thumbnails'] as $key => $img)
  {
    echo "$key ({$img['width']}px x {$img['height']}px):\n";
    echo "{$img['url']}\n";
  }

  foreach ($item['brandingSettings']['image'] as $key => $imgUrl)
  {
    if (!is_null($imgUrl))
    {
      // APIと関係ない、単に出力を見やすくするための処理、読み飛ばして良い
      $parts = preg_split('/(?=[A-Z])/', $key, -1, PREG_SPLIT_NO_EMPTY);
      array_pop($parts);
      array_pop($parts);
      $width = preg_replace('/^.+=w([0-9]+).+$/', '$1', $imgUrl);

      echo "$key (".implode(' ', $parts).", width={$width}px):\n";
      echo "$imgUrl\n";
    }
  }
}

チャンネルアイコンの画像情報は、snippet.thumbnails プロパティに格納されている。また、チャンネルアートの画像情報は、brandingSettings.image プロパティに格納されている。

※追記:brandingSettings.image プロパティは2020年9月9日に deprecated となっている。
https://developers.google.com/youtube/v3/revision_history#september-9,-2020

APIリクエストは listChannels() メソッドを使って行い、part引数に上記 snippetbrandingSettings の2つを指定する。フィルタのパラメータ指定では、id パラメータにチャンネルIDを指定すると、そのチャンネルの画像情報を取得できる。カンマ区切りでチャンネルIDを複数指定できる。

snippet.thumbnails プロパティには、defaultmediumhigh の3つキーがあり、それぞれに urlwidthheight の値が保持されている。

  • default
    • url: 小サイズ(デフォルト)の画像URL, width: 88, height: 88
  • medium
    • url: 中サイズの画像URL, width: 240, height: 240
  • high
    • url: 大サイズの画像URL, width: 800, height: 800

brandingSettings.image プロパティに格納されているデータには、各デバイス向けアプリケーション/画面解像度の画像URLが保持されている。代表的なものを挙げると下記となる。

  • bannerImageUrl
    • ウェブサイトのチャンネルページに表示される画像URL(サイズ 1060px x 175px)
  • bannerMobileImageUrl
    • モバイルアプリケーションのチャンネルページに表示される画像URL(サイズ 640px x 175px)
  • bannerTabletImageUrl
    • タブレットアプリケーションのチャンネルページに表示される画像URL(サイズ 1707px x 283px)
  • bannerTvImageUrl
    • テレビアプリケーションのチャンネルページに表示される画像URL(サイズ 2120px x 1192px)

また、画面解像度別には、モバイルアプリケーション向けを例にすると、bannerMobileMediumHdImageUrlbannerMobileHdImageUrl のように区分されて画像URLが保持されている。

YouTube Data API v3を利用して最も人気のある動画を取得する

APIを使ってYouTubeで最も人気のある動画を取得することができる。

地域や動画カテゴリを指定して取得することが可能で、下記は日本の音楽カテゴリで最も人気のある動画を取得する例。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

$client = new Google_Client();
$client->setDeveloperKey("_API_KEY_");
$youtube = new Google_Service_YouTube($client);

$params = [
  'chart' => 'mostPopular',
  'regionCode' => 'JP',
  'videoCategoryId' => '10', // 音楽
  'maxResults' => 50,
];

do {
  $response = $youtube->videos->listVideos('snippet', $params);

  foreach ($response['items'] as $i => $item)
  {
    echo "#$i\n";
    echo "id         : {$item['id']}\n";
    echo "channelTitle: {$item['snippet']['channelTitle']}\n";
    echo "title      : {$item['snippet']['title']}\n";
  }

  echo "\n";
  echo "# nextPageToken: {$response['nextPageToken']}\n";
  echo "# prevPageToken: {$response['prevPageToken']}\n";
  echo "# totalResults: {$response['pageInfo']['totalResults']}\n";
  echo "# resultsPerPage: {$response['pageInfo']['resultsPerPage']}\n\n";

  $pageToken = $response['nextPageToken'];
  $params['pageToken'] = $pageToken;
  sleep(1);

} while (!empty($pageToken));

listVideos() を呼ぶときのフィルタのパラメータとして chart パラメータに mostPopular を指定すると最も人気のある動画が返ってくる。上記のコードでは、その他に regionCodeJPvideoCategoryId10(音楽)を指定して、リクエスト対象が日本の音楽カテゴリになるようにしている。maxResults は、APIレスポンスに含める結果の動画件数を指定する。

動画タイトルなどの基本情報は listVideos()part 引数に snippet を指定すると得られる。APIレスポンスとして返ってくる snippet プロパティには下記の動画・基本情報が格納されている。

  • snippet.publishedAt:動画のアップロード日時
  • snippet.channelId:動画のチャンネルID
  • snippet.title:動画のタイトル
  • snippet.description:動画の説明
  • snippet.thumbnails:動画のサムネイル画像情報
  • snippet.channelTitle:動画のチャンネル名
  • snippet.tags:動画に付けられたタグ
    • 配列データ(動画にタグが付けられていない場合は NULL
  • snippet.categoryId:動画のカテゴリID

上記コードを実行すると、日本の音楽カテゴリで最も人気のある動画が人気上位から順に出力される。

なお、2ページ目以降の結果を取得するには、フィルタに pageToken パラメータを追加し、その値に nextPageToken の値(上記の実行結果の例では CDIQAA)を指定すると得られる。nextPageToken は、次のページ結果がある場合、APIレスポンスに含まれている。

YouTube Data API v3を利用したプレイリストのエクスポート/インポート機能のクォータ使用量

Googleは、APIによる各リクエストにコストを割り当ててクォータの使用量を計算している。コストは操作の種類ごとに下記のように設定されている。

※追記:Googleがクォータ・コストの割り当て方法の簡略化を行い、part パラメータに伴う追加コストが撤廃された。
https://developers.google.com/youtube/v3/revision_history#july-29,-2020

簡略化後は、part引数によらず、単純に各メソッドに割り当てられたコストがかかるだけになる。


※注意:これ以降の内容は、以前のpartパラメータにかかる追加コストを考慮に入れて、コストを計算したもの
  • リソースのIDを取得するだけの単純な読み取り操作のコストは 1 ユニット
  • リソースのパーツ(snippetstatus など、ただし id は除く)を取得する読み取り操作のコストはパーツ毎に 2 ユニット
  • リソースの書き込み操作のコストは 50 ユニット
  • 動画のアップロードのコストは 1600 ユニット

ここで、下記のプレイリストのエクスポート機能を想定したAPIリクエストのコストを計算してみる。

$youtube->playlistItems->listPlaylistItems('snippet', $params)

※フィルタのパラメータ $paramsmaxResults の値に 50 を設定、1回のリクエストで最大50件のプレイリストアイテム(プレイリスト内の動画情報)を取得

読み取り操作には、1 ユニットのコストがかかる。引数 partsnippet だけ指定しているので、読み取るパーツ数は1となる。パーツの読み取り操作は、パーツ毎に 2 ユニットなので、読み取り操作全体のコストは、

1 + 2 * 1
= 3

となり、上記APIリクエストは1回で 3 ユニットのコストがかかることになる。

1日あたりのクォータが 10,000、含まれる動画が50個以内のプレイリストを対象にすると、1日あたり 3,333 (=10,000/3) のプレイリスト情報を取得できる計算になる。


次に、下記のプレイリストのインポート機能を想定したAPIリクエストのコストを計算してみる。

プレイリストのインポートでは、下記2つのAPIリクエストが行われる。

  1. $youtube->playlists->insert('snippet,status', $youTubePlaylist, [])
  2. $youtube->playlistItems->insert('snippet', $playlistItem, [])

まず1番目のリクエストのコストから。

これはプレイリストを作成するためのもので、書き込み操作に該当する。よって、playlists->insert() は、書き込み操作として 50 ユニットのコストがかかる。

また、playlists->insert() の書き込み操作は、書き込みだけでなく、APIレスポンスとしてリソースを返す処理も実行される。つまり、リソースを返す処理に伴い読み取り操作のコストもかかることになる。読み取り操作には、1 ユニットのコストがかかる。引数 partsnippet,status を指定しているので、読み取るパーツ数は2となる。よって、playlists->insert() は、読み取り操作として、

1 + 2 * 2
= 5

のコストがかかる。

よって、playlists->insert() にかかる書き込みと読み取り操作のコストを合わせると、

50 + 5
= 55

これが1番目のAPIリクエストのコストとなる。

続いて、2番目のリクエストのコストを計算してみる。

これは作成したプレイリストにプレイリストアイテムを追加していく操作で、書き込み操作として、playlistItems->insert()50 ユニットのコストがかかる。これも同様にAPIレスポンスを返す処理に伴い読み取り操作が行われる。引数 partsnippet を指定しているので、読み取るパーツ数は1となることから、playlistItems->insert() は、読み取り操作として、

1 + 2 * 1
= 3

のコストがかかる。playlistItems->insert() にかかる書き込みと読み取り操作のコストを合わせると、

50 + 3
= 53

のコストがかかることになる。これはプレイリストアイテム1個あたりにかかるコストとなる。

そこで、プレイリストに15個のプレイリストアイテムを追加する場合を想定すると、

53 * 15
= 795

これが2番目のAPIリクエストのコストとなる。

プレイリストの作成、プレイリストアイテムの追加(15個)のコストを合わせると

55 + 795
= 850

これが1番目と2番目のAPIリクエストを合わせた総コストとなる。

1日あたりのクォータが 10,000、含まれる動画が10個のプレイリストをインポート対象とすると、1日あたり 11 (=10,000/850) のプレイリストをインポートできる計算になる。


エクスポートとインポートのコストを比較してみると、1日あたりの可能なエクスポート数は 3,333、インポート数は 11 となることから、書き込み操作のコストはかなり高いことがわかる。

クォータの使用量は、Google Cloud Platformの "IAM と管理" → "割り当て" から確認できる。

最後に、クォータ制限に引っかかると下記のようなエラーが返ってくる。これはプレイリスト作成で playlists->insert() による書き込み操作をリクエストしたときのもの。
※クォータ上限は 10,000 のはずなのだが、7700 ぐらいに達したぐらいで書き込み操作を伴うAPIリクエストが一切行えなくなった

Fatal error: Uncaught Google_Service_Exception: {
  "error": {
    "code": 500,
    "message": "Internal error encountered.",
    "errors": [
      {
        "domain": "youtube.api.v3.PlaylistInsertResponse.Error",
        "reason": "RATE_LIMIT_EXCEEDED"
      }
    ],
    "status": "INTERNAL"
  }
}

YouTube Data API v3を利用してプレイリストのインポート機能を実装する

YouTubeにはプレイリスト(再生リスト)のインポート機能がないので、YouTube Data API v3を利用して簡単なものを自作した。

プレイリストのインポート機能を実装するには、APIで挿入 (insert) 操作をリクエストする必要がある。

このAPIリクエストを実行するには、OAuth 2.0認証を行い、アプリケーション(今回の場合は自作インポート機能プログラム)によるユーザーアカウントへのアクセスが許可されなければならない。

また、このアクセス許可は、ユーザーのリソース操作全てを許可するわけではなく、スコープという仕組みを導入して許可する範囲を限定している。

プレイリストのインポート機能に必要な挿入 (insert) 操作を行う場合は、スコープ https://www.googleapis.com/auth/youtube が許可される必要がある。

OAuth 2.0認証のやり取りを行い、承認されたクライアントオブジェクトをセットアップするためのコードが下記になる。


require_once '/path/to/google-api-php-client/vendor/autoload.php';

require_once 'OAuthClient.php';

$CLIENT_ID = '_CLIENT_ID_';
$CLIENT_SECRET = '_CLIENT_SECRET_';
$REDIRECT_URI = 'urn:ietf:wg:oauth:2.0:oob';
$TOKEN_FILENAME = "token.json";
$CODE = '_CODE_';

try
{
  $gclient = new Google_Client();
  $oauth = new OAuthClient($gclient, $CLIENT_ID, $CLIENT_SECRET, $REDIRECT_URI, $TOKEN_FILENAME, $CODE);
  $oauth->setScopes('https://www.googleapis.com/auth/youtube');
  $client = $oauth->getAuthorizedClient();
}
catch(Exception $e)
{
  switch($e->getCode())
  {
    case OAuthClient::EMPTY_CODE:
    case OAuthClient::INVALID_CODE:
      echo $e->getMessage() . "\n";
      echo $oauth->getAuthUrl() . "\n";
      exit;
    case OAuthClient::INVALID_TOKEN_FILE:
      echo $e->getMessage() . "\n";
      exit;
    default:
      throw $e;
  }
}

$youtube = new Google_Service_YouTube($client);

OAuth 2.0認証の一連の処理は OAuthClient クラスにまとめてある。このクラスの getAuthorizedClient() を呼べば、承認されたクライアントオブジェクトが返ってくる。詳細は前の記事で説明したので、それを参照のこと。
OAuth 2.0 認証で YouTube Data API v3 を利用する


ここからは本題のインポート機能について説明する。

インポート機能は次の3つの処理から成る。

  1. インポートファイルの読み込み
  2. プレイリスト作成
  3. プレイリストに動画を追加

まず、下記はインポートファイルの読み込みとプレイリストの作成を行う部分。


$importFileName = "import.tsv";

$videoIds = extractVideoIds($importFileName);

$playlistName = preg_replace('/^(.+)\.tsv$/', '$1', $importFileName);
echo "creating a playlist \"$playlistName\"...\n";

$playlistSnippet = new Google_Service_YouTube_PlaylistSnippet();
$playlistSnippet->setTitle($playlistName);
$playlistSnippet->setDescription("created with the YouTube Data API v3 [" . date('Y-m-d H:i:s') . "]");

$playlistStatus = new Google_Service_YouTube_PlaylistStatus();
$playlistStatus->setPrivacyStatus('private');

$youTubePlaylist = new Google_Service_YouTube_Playlist();
$youTubePlaylist->setSnippet($playlistSnippet);
$youTubePlaylist->setStatus($playlistStatus);

$playlistResponse = $youtube->playlists->insert('snippet,status', $youTubePlaylist, []);
$playlistId = $playlistResponse['id'];

echo "done.\n\n";


function extractVideoIds($filename)
{
  $videoIds = [];

  $file = file_get_contents($filename);

  if (!$file)
    exit("Error: Can't open file: $filename\n");

  echo "reading \"$filename\"...\n";

  foreach (str_getcsv($file, "\n") as $row)
  {
    if (empty($row))
      continue;

    $cols = str_getcsv($row, "\t");

    $videoId = preg_replace('|^https://www.youtube.com/watch\?v=(.+)$|', '$1', $cols[0]);
    $videoIds[] = $videoId;
  }

  echo "done.\n\n";

  return $videoIds;
}

インポートできるファイルは、下記のようなYouTubeの動画URLとタイトルが TSV(タブ区切り)形式で保存されたファイルを想定している。


YouTube動画URL1	動画タイトル1
YouTube動画URL2	動画タイトル2
YouTube動画URL3	動画タイトル3
...

このファイルを読み込んで動画IDだけ抽出する。

続いてプレイリストの作成では、まず、snippet プロパティと status プロパティの値を保持する PlaylistSnippetPlaylistStatus オブジェクトをそれぞれ作成する。

PlaylistSnippet オブジェクトの作成では、snippet プロパティ内の snippet.title プロパティと snippet.description(プレイリストの説明)の値を setTitle()setDescription() でそれぞれセットする。上記のコードの例では、title にはインポートファイルのファイル名、description には文字列 created with the YouTube Data API v3 とそれにタイムスタンプを付加したものをセットしている。

PlaylistStatus オブジェクトの作成では、status プロパティ内の status.privacyStatus プロパティの値をセットする。有効な値は、publicprivateunlisted の3つで、デフォルトは public となる。上記のコードの例では、private をセットし、作成するプレイリストは非公開のものとして設定している。

PlaylistSnippetPlaylistStatus オブジェクトを作成したら、それらを Playlist オブジェクト $youTubePlaylistsetSnippet()setStatus() でそれぞれセットする。そして、$youtube->playlists->insert('snippet,status', $youTubePlaylist, []) でAPIリクエストを行い、プレイリストの作成が完了する。insert() の引数 'snippet,status' は、書き込み操作対象となるプロパティを特定するものである。

APIレスポンスに作成されたプレイリストIDが含まれているのでそれを取得する。

次に、このIDを使って、プレイリストに動画をインポートしてプレイリストを完成させていく。


foreach ($videoIds as $i => $videoId)
{
  echo "$i: videoId: $videoId ";

  $resourceId = new Google_Service_YouTube_ResourceId();
  $resourceId->setVideoId($videoId);
  $resourceId->setKind('youtube#video');

  $playlistItemSnippet = new Google_Service_YouTube_PlaylistItemSnippet();
  $playlistItemSnippet->setPlaylistId($playlistId);
  $playlistItemSnippet->setResourceId($resourceId);

  $playlistItem = new Google_Service_YouTube_PlaylistItem();
  $playlistItem->setSnippet($playlistItemSnippet);
  $playlistItemResponse = $youtube->playlistItems->insert('snippet', $playlistItem, []);

  echo "inserted.\n";
}

echo "\ndone.\n";

プレイリストはプレイリストアイテムから構成され、インポートする動画一つ一つに対応するプレイリストアイテムを作成して、それらをプレイリストに挿入していくのが主な処理となる。

プレイリストアイテムの作成手順は前述のプレイリスト作成と同様で、プレイリストアイテムの snippet プロパティの値を保持する PlaylistItemSnippet オブジェクトを作成し、それを PlaylistItem オブジェクトの setSnippet() に渡してセットし、PlaylistItem オブジェクト $playlistItem をセットアップする。そして、$youtube->playlistItems->insert('snippet', $playlistItem, []) でAPIリクエストを行い、プレイリストにプレイリストアイテムを挿入し、動画一つのインポートが完了する。

違う点は、プレイリストアイテムとプレイリストの紐付け、また、プレイリストアイテムと動画IDを紐付けるために ResourceId オブジェクトを作成する必要があるというところ。

プレイリストアイテムとプレイリストの紐付けは、PlaylistItemSnippet オブジェクトの setPlaylistId() にプレイリストIDを渡して行う。

プレイリストアイテムと動画IDの紐付けは、ResourceId オブジェクトを作成し、setVideoId() で動画IDをセット、setKind() でリソースタイプをセットする。リソースタイプは動画の場合、youtube#video となる。そして、ResourceId オブジェクトを PlaylistItemSnippet オブジェクトの setResourceId() に渡し、プレイリストアイテムと動画IDが紐付けられる。