PHP で YouTube API を操作する

PHP の SimpleXML 拡張機能を使って YouTube のデータを処理し、PHP アプリケーションに統合する方法

YouTube 動画共有サイトでは、Web アプリケーション開発者が REST をベースとした開発者向け API を使って公開コンテンツにアクセスできるようになっています。この API によって生成された XML フィードを処理し、カスタマイズした PHP アプリケーションを構築するために使うには、PHP の SimpleXML 拡張機能が最適です。この記事では YouTube Data API を紹介し、この API を使用してユーザーが生成した動画のコンテンツをブラウズする方法、動画のメタデータや動画に対するコメントとレスポンスにアクセスする方法、そしてキーワード検索を行う方法を具体的に説明します。

はじめに

オンライン動画共有サイトのなかで群を抜いて人気を集めているのは、毎日数十億件ものページ・ビューがあり、数十万ものビデオが追加されている YouTube です。YouTube にあるのはホーム・ビデオだけではありません。今では音楽ビデオ、TV 番組の場面、映画の予告編、アニメーション・クリップなど、さまざまな内容を提供しています。さらに、YouTube のサービスでは、ユーザーが動画にキーワードで「タグ」を付けたり、その時点で最も人気の高い動画を表示することもできます。

よく使われる頭字語

  • API: application pogramming iterface
  • HTTP: Hypertext Transfer Protocol
  • PEAR: PHP Extension and Application Repository
  • PHP: PHP Hypertext Preprocessor
  • REST: Representational State Transfer
  • RSS: Really Simple Syndication
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language

YouTube の特に優れた機能の 1 つとして挙げられるのは、YouTube Data API です。この REST ベースの API では、開発者が YouTube の動画データにアクセスし、検索したデータを独自の XML 対応アプリケーションに統合することができます。これは特に難しい話ではなく、REST リクエストを送信し、レスポンスを解析してデコードし、デコードしたデータをアプリケーション・インターフェースに統合するアプリケーション・レベルのコードを作成するという作業になります。PHP を使用すれば、これらのタスクを YouTube の PHP Client Library によって実行することも、手動で REST リクエストに対する XML レスポンスを構文解析することで実行することもできます。

この記事では後者の方法を取り上げ、YouTube API を介して公開コンテンツにアクセスし、このコンテンツを SimpleXML を使用して PHP アプリケーションに統合する方法を説明します。例として記載しているのは、特定のカテゴリーの動画リストを取得する方法、キーワードで動画を検索する方法、サムネールと統計値を含む動画のメタデータを取得する方法、そしてユーザー・プロフィール情報にアクセスする方法です。


YouTube データ・フォーマットについて

PHP コードの詳細を探る前に、YouTube Data API について少々説明しておきます。あらゆる REST ベースのサービスと同様、すべては指定したリソースへの HTTP リクエストから始まります。この HTTP リクエストには 1 つ以上の入力パラメーターを指定したクエリーが含まれます。サーバーはこのクエリーに対するレスポンスを、XML 対応のクライアントでの構文解析に適した Atom または RSS 形式で返します。

この仕組みを確かめるには、お好みの Web ブラウザーで URL、http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed にアクセスしてください。この REST メソッドは、現在 YouTube で最も再生回数が多い動画のリストを返します。このメソッドに対する XML レスポンス (結果ページのソース・コードで確認できます) には、リストアップされた動画の詳細情報が含まれます。その一例をリスト 1 に示します。

リスト 1. YouTube API によって生成されたフィードの例
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' 
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 
xmlns:gml='http://www.opengis.net/gml' xmlns:georss='http://www.georss.org/georss' 
xmlns:media='http://search.yahoo.com/mrss/' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' 
xmlns:gd='http://schemas.google.com/g/2005'>
  <id>http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed</id>
  <updated>2008-03-06T14:43:27.000-08:00</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#video'/>
  <title type='text'>Most Viewed</title>
  <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
  <link rel='alternate' type='text/html' href='http://www.youtube.com/browse?s=mp'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed
  ?start-index=1&max-results=5'/>
  <link rel='next' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed
  ?start-index=6&max-results=5'/>
  <author>
    <name>YouTube</name>
    <uri>http://www.youtube.com/</uri>
  </author>
  <generator version='beta' uri='http://gdata.youtube.com/'>
  YouTube data API</generator>
  <openSearch:totalResults>94</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>5</openSearch:itemsPerPage>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg</id>
    <published>2006-04-06T14:30:53.000-07:00</published>
    <updated>2008-03-12T00:22:25.000-07:00</updated>
    <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' 
    term='Dancing'/>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://gdata.youtube.com/schemas/2007#video'/>
    <category scheme='http://gdata.youtube.com/schemas/2007/keywords.cat' 
    term='comedy'/>
    <category scheme='http://gdata.youtube.com/schemas/2007/categories.cat' 
    term='Comedy' label='Comedy'/>
    <title type='text'>Evolution of Dance</title>
    <content type='text'>The funniest 6 minutes you will ever see! 
    Remember how many of these you have done!
Judson Laipply is dancing -
http://www.evolutionofdance.com -
for more info including song list!</content>
    <link rel='alternate' type='text/html' 
    href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.responses' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/responses'/>
    <link rel='http://gdata.youtube.com/schemas/2007#video.related' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/related'/>
    <link rel='self' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed/dMH0bHeiRNg'/>
    <author>
      <name>judsonlaipply</name>
      <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
    </author>
    <media:group>
      <media:title type='plain'>Evolution of Dance</media:title>
      <media:description type='plain'>The funniest 6 minutes you will ever see!
       Remember how many of these you have done!
Judson Laipply is dancing -
http://www.evolutionofdance.com -
for more info including song list!</media:description>
      <media:keywords>comedy, Dancing</media:keywords>
      <yt:duration seconds='360'/>
      <media:category label='Comedy' 
      scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>Comedy
      </media:category>
      <media:content 
      url='http://www.youtube.com/v/dMH0bHeiRNg' type='application/x-shockwave-flash' 
      medium='video' isDefault='true' expression='full' duration='360' yt:format='5'/>
      <media:content 
      url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQnYRKJ3bPTBdBMYDSANFEgGDA==
      /0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='360'
       yt:format='1'/>
      <media:content 
      url='rtsp://rtsp2.youtube.com/ChoLENy73wIaEQnYRKJ3bPTBdBMYESARFEgGDA==
      /0/0/0/video.3gp' type='video/3gpp' medium='video' expression='full' duration='360'
       yt:format='6'/>
      <media:player url='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/2.jpg' height='97' width='130' 
      time='00:03:00'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/1.jpg' height='97' width='130' 
      time='00:01:30'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/3.jpg' height='97' width='130' 
      time='00:04:30'/>
      <media:thumbnail 
      url='http://img.youtube.com/vi/dMH0bHeiRNg/0.jpg' height='240' width='320' 
      time='00:03:00'/>
    </media:group>
    <yt:statistics viewCount='78060679' favoriteCount='400468'/>
    <gd:rating min='1' max='5' numRaters='276123' average='4.65'/>
    <gd:comments>
      <gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg
      /comments' countHint='124130'/>
    </gd:comments>
  </entry>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/cQ25-glGRzI</id>
    <published>2007-02-27T15:08:01.000-08:00</published>
    <updated>2008-03-12T00:46:25.000-07:00</updated>
    ...
  </entry>
</feed>

この出力にざっと目を通して、主要な要素を把握してください。

  • YouTube Data API は REST リクエストに対し、要求されたデータが含まれるフィードで応答します。これらのフィードには、動画のフィード、動画に関するコメントのフィード、ユーザー再生リストのフィード、ユーザー連絡先のフィードなど、さまざまなものがあります。そのため大抵の場合、XML レスポンスにはルート要素として <feed> 要素が含まれます。この <feed> 要素のなかに、結果セットの現在のページ、次のページ、前のページの URL が含まれる <link> 要素、そして検索の要約統計情報が含まれる <openSearch:> があります。
  • 最も外側の <feed> 要素の内側にはクエリーに一致する動画を表す 1 つ以上の <entry> 要素があり、<entry> 要素のそれぞれには、その要素が表す動画の詳細情報 (カテゴリー、タイトル、説明、公開日、投稿者、再生時間など) が含まれます。さらに各 <entry> 要素のなかに含まれる <link> 要素には、動画、動画レスポンス、そして YouTube Web サイトにある他の関連動画を表示するための URL リンクが示されます。
  • <entry> 要素ごとに含まれる <media:group> 要素には、動画に関する詳細情報としてタイトル、説明、各種フォーマットでの提供、サムネール・リンク、そして動画プレイヤーのリンクがあります。
  • <entry> 要素ごとに含まれる <yt:statistics> 要素には動画の再生回数の統計が示され、<gd:rating> 要素にはビデオの平均評価ならびに評価を付けたユーザー数の合計が示されます。

このように、大量の情報がありますが、おそらく通常期待する以上の情報量でしょう。YouTube Data API での作業を面白いものにしているのは、まさに、この広範な情報ベースです。この情報ベースのおかげで、開発者は自由自在に新しいアプリケーションの発想を得ることができます。

YouTube Data API 関数のなかには、一般に対してアクセス可能になっていないものもあります。特定の関数には開発者キーと認証トークンがなければアクセスできません。これに該当するのは、具体的には動画をアップロードするための関数、コンテンツまたはユーザー・プロフィール情報を編集するための関数、評価およびコメントを追加するための関数など、サイトのデータを変更するための関数です。このような API 関数へのアクセス権を取得するには、開発者として YouTube に登録する必要があります (「参考文献」に記載したリンクにアクセスしてください)。この記事で説明するのは開発者キーを必要としない API 関数についてのみですが、この後すぐにわかるように、開発者キーが要らない API 関数でも、しばらく熱中できるぐらいの有り余るほどの数があります。


動画リストを取得する方法

ここからは、PHP を使用して YouTube Data API フィードを処理する具体的な例に話を進めます。リスト 2 では、リスト 1 のフィードを取得し、SimpleXML を使って抽出した関連するデータ・フラグメントを Web ページのフォーマットになるように設定しています。

リスト 2. 再生回数の多い YouTube 動画をリストアップする PHP スクリプト
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing most viewed videos</title>
    <style type="text/css">
    div.item {
      border-top: solid black 1px;      
      margin: 10px; 
      padding: 2px; 
      width: auto;
      padding-bottom: 20px;
    }
    span.thumbnail {
      float: left;
      margin-right: 20px;
      padding: 2px;
      border: solid silver 1px;  
      font-size: x-small; 
      text-align: center
    }    
    span.attr {
      font-weight: bolder;  
    }
    span.title {
      font-weight: bolder;  
      font-size: x-large
    }
    img {
      border: 0px;  
    }    
    a {
      color: brown; 
      text-decoration: none;  
    }
    </style>
  </head>
  <body>
    <?php
    // set feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed';
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    ?>
      <h1><?php echo $sxml->title; ?></h1>
    <?php
    // iterate over entries in feed
    foreach ($sxml->entry as $entry) {
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $watch = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $thumbnail = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) {
        $attrs = $gd->rating->attributes();
        $rating = $attrs['average']; 
      } else {
        $rating = 0; 
      } 
      ?>
      <div class="item">
        <span class="title">
          <a href="<?php echo $watch; ?>"><?php echo $media->group->title; ?></a>
        </span>
        <p><?php echo $media->group->description; ?></p>
        <p>
          <span class="thumbnail">
            <a href="<?php echo $watch; ?>"><img src="<?php echo $thumbnail;?>" /></a>
            <br/>click to view
          </span> 
          <span class="attr">By:</span> <?php echo $entry->author->name; ?> <br/>
          <span class="attr">Duration:</span> <?php printf('%0.2f', $length/60); ?> 
          min. <br/>
          <span class="attr">Views:</span> <?php echo $viewCount; ?> <br/>
          <span class="attr">Rating:</span> <?php echo $rating; ?> 
        </p>
      </div>      
    <?php
    }
    ?>
  </body>
</html>

図 1 は、上記のスクリプトの出力例です。

図 1. 再生回数の多い動画のリスト
再生回数の多い動画のリスト

リスト 2 ではまず、simplexml_load_file() オブジェクトを使ってフィード URL にリクエストを送信し、レスポンスを SimpleXML オブジェクトに変換しています。続いてレスポンスに含まれる <entry> 要素の繰り返し処理により、各要素を foreach() ループによって処理して図 1 に表示された情報を取得しています。それぞれの値は、以下のようにして取得しました。

  • ビデオのメタデータは <media:group> ノード・コレクションの各 <entry> ノードの下に保存されています。media: 名前空間を指定して SimpleXML の children() メソッドを実行することで、このノード・コレクションを $media として返してから、続いて動画のタイトルと説明にそれぞれ $media->group->title$media->group->description としてアクセスします。
  • さまざまな <media:thumbnail> 要素の url 属性には、動画のサムネールの URL が保存されています。この値にアクセスするため、$media->group->thumbnail[0]->attributes() を呼び出し、その結果得られた属性配列の url キーにアクセスします。
  • <media:player> 要素の url 属性に保存されているのは、動画プレイヤーの URL です。この値にアクセスするため、$media->group->player->attributes() を呼び出し、その結果得られた属性配列の url キーにアクセスします。
  • <media:group> 要素に含まれる <yt:duration> 要素には、動画の再生時間が秒単位で保存されています。yt: 名前空間を指定して $media->children() メソッドを実行することで、このノードを SimpleXML オブジェクトとして返し、このオブジェクトの attributes() メソッドが seconds 属性の値を取得します。分単位の再生時間を取得する場合には、この値を 60 で割ってください。
  • 同じように、動画の再生回数の統計は <yt:stats> 要素で入手することができます。この場合も、名前空間によってこの要素にアクセスし、結果オブジェクトの attributes() メソッドで viewCount 属性の値を取得します。
  • 動画のユーザー評価は、各 <entry> の下にある <gd:ratings> 要素にあります。この要素にアクセスするには、その名前空間を使って SimpleXML の children() メソッドを呼び出し、結果オブジェクトの average 属性にアクセスして平均評価を取得します。

以上の情報をすべて取得したら、後は情報にフォーマット設定をして Web ページに表示するだけの話です。この部分は、リスト 2 の先頭にある単純な CSS ルールが引き受けます。

リスト 2 で使用したフィード URL は、YouTube が開発者のために用意しているさまざまな標準フィードの 1 つに過ぎません。他にもよく使われるフィードとしては以下のものがあります。

  • 評価の高い動画: http://gdata.youtube.com/feeds/api/standardfeeds/top_rated
  • 新着動画: http://gdata.youtube.com/feeds/api/standardfeeds/most_recent
  • 人気の動画: http://gdata.youtube.com/feeds/api/standardfeeds/most_linked

標準フィードの全リストについては、「参考文献」に記載された YouTube Data API Developer's Guide を参照してください。


動画のカテゴリーを操作する方法

前のセクションで説明した標準フィードの他、YouTube ではカテゴリー別に動画を取得することもできます。カテゴリーを基準に動画を取得するには、動画フィードの URL にカテゴリー名 (http://gdata.youtube.com/feeds/api/videos/-/Travel/http://gdata.youtube.com/feeds/api/videos/-/Tech/ など) を追加します。

リスト 3 は、Travel カテゴリーの動画を取得してリストにする例です。

リスト 3. Travel カテゴリーに属する YouTube の動画をリストにする PHP スクリプト
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing videos in a category</title>
  </head>
  <body>
    <?php
    // set feed URL
    // add category
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/-/Travel/';
    
    // read feed into SimpleXML object
    $sxml = simplexml_load_file($feedURL);
    
    // get summary counts from opensearch: namespace
    $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
    $total = $counts->totalResults;  
    ?>
    <h1><?php echo $sxml->title; ?></h1>
    <?php echo $total; ?> items found.
    <p/>
    <ol>
    <?php    
    // iterate over entries in category
    // print each entry's details
    foreach ($sxml->entry as $entry) {
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $watch = $attrs['url']; 
      
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $length = $attrs['seconds']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) {
        $attrs = $gd->rating->attributes();
        $rating = $attrs['average']; 
      } else {
        $rating = 0; 
      } 
            
      // print record
      echo "<li>\n";
      echo "<a href=\"{$watch}\">{$media->group->title}</a>
      <br/>\n";
      echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating
      <br/>\n";
      echo "{$media->group->description}<p/>\n";
      echo "<p/></li>\n";
    }
    ?>
    </ol>
  </body>
</html>

リスト 2 の場合と同じく、リスト 3 でもまずは Travel カテゴリーのフィード URL に対する GET リクエストを送信し、そのレスポンスをSimpleXML オブジェクトに変換しています。続いてコレクション内の <entry> 要素を繰り返し処理することによって、各要素のタイトル、説明、再生時間、そしてユーザーの評価を取得し、この情報を番号付きリストのフォーマットにします。リスト 3 ではさらにもう 1 つの新しい機能も追加しています。それは、XML レスポンス内の <openSearch:> 要素に含まれる要約データを調べ、当該カテゴリーで見つかった動画の総数を出力するという機能です。

このスクリプトの出力は図 2 のようになります。

図 2. Travel カテゴリーの動画リスト
Travel カテゴリーの動画リスト

YouTube の全カテゴリーのリストは URL、http://gdata.youtube.com/schemas/2007/categories.cat から入手することができます。リスト 4 は、このファイルの一例です。

リスト 4. YouTube カテゴリー・リストの例
<?xml version='1.0' encoding='UTF-8'?>
<app:categories xmlns:app='http://www.w3.org/2007/app' 
xmlns:atom='http://www.w3.org/2005/Atom' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' fixed='yes' 
scheme='http://gdata.youtube.com/schemas/2007/categories.cat'>
  <atom:category term='Film' label='Film & Animation' xml:lang='en-US'>
    <yt:browsable/>
    <yt:assignable/>
  </atom:category>
  <atom:category term='Autos' label='Autos & Vehicles' xml:lang='en-US'>
    <yt:browsable/>
    <yt:assignable/>
  </atom:category>
  ...
</app:categories>

上記のファイルを取得して構文解析し、それから YouTube API を使って各カテゴリーで人気の高い上位 5 つの動画を取得する PHP スクリプトを作成するのはそれほど難しい作業ではありません。例えば、このスクリプトはリスト 5 のようになります。

リスト 5. カテゴリー別に上位 5 位の YouTube 動画をリストアップする PHP スクリプト
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Listing videos in different categories</title>
  </head>
  <body>
    <?php
    // set URL for XML feed containing category list
    $catURL = 'http://gdata.youtube.com/schemas/2007/categories.cat';
    
    // retrieve category list using atom: namespace
    // note: you can cache this list to improve performance, 
    // as it doesn't change very often!
    $cxml = simplexml_load_file($catURL);
    $cxml->registerXPathNamespace('atom', 'http://www.w3.org/2005/Atom');
    $categories = $cxml->xpath('//atom:category');
    
    // iterate over category list
    foreach ($categories as $c) {
      // for each category    
      // set feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos/-/{$c['term']}
      ?max-results=5&orderby=viewCount";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      ?>
      
      <h1><?php echo $c['label']; ?></h1>
      <?php echo $total; ?> items found.
      <p/>
      
      <?php    
      echo "<ol>\n";
      // iterate over entries in category
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // print record
        echo "<li>\n";
        echo "<a href=\"{$watch}\">{$media->group->title}</a>
        <br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating
        <br/>\n";
        echo substr($media->group->description,0,50) . "...<p/>\n";
        echo "<p/></li>\n";
      }
      echo "</ol>\n";
    }
    ?>
  </body>
</html>

リスト 5 ではまず http://gdata.youtube.com/schemas/2007/categories.cat で、XML にエンコードされたカテゴリー・リストを取得します。次に SimpleXML の xpath() 関数を使って、このリストから YouTube カテゴリー名を取得し、foreach ループを実行してカテゴリーごとにフィード URL を動的に作成することによって、各カテゴリーで再生回数の多い上位 5 つの動画を取得します。foreach() ループ内で行われていることは、リスト 3 のステップとほとんど同じなのでもう驚くようなものではありません。ただし、フィード URL に以下の 2 つのパラメーターが追加されていることは指摘しておく価値があります。

  • max-results パラメーターは、フィードに返す結果の数を指定します。
  • orderby パラメーターは、結果のソート基準を人気 (viewCount)、言語、ユーザー評価 (rating)、または公開日時 (published) のどれにするかを指定します。

それでは、リスト 5 によってどんな結果が生成されるか、その出力例 (図 3) を見てください。

図 3. 各種カテゴリーの動画を表示する Web ページ
各種カテゴリーの動画を表示する Web ページ

キーワードで検索する方法

YouTube では、動画を特定しやすくするためにユーザーが動画にキーワードでタグを付けることができます。さらに YouTube API では、開発者がカテゴリー別検索と同じように、キーワードによっても動画を検索できるようになっています。例えば「boat」というキーワードでタグが付けられた動画を検索するには、http://gdata.youtube.com/feeds/api/videos/-/boat に GET リクエストを送信します。同様に、「westminster」と「london」というキーワードでタグが付けられた動画を検索するには、http://gdata.youtube.com/feeds/api/videos/-/westminster/london に GET リクエストを送信します。このようなリクエストに対する XML レスポンスは、リスト 1 に示したレスポンスと同じく、<feed> に <entry> が含まれ、それぞれの <entry> が一致する動画を表すことになります。

言うまでもなく、キーワードを使用すれば、ユーザーが指定したキーワードに一致する動画を YouTube データベースで探し出すという単純な検索エンジンを PHP で至って簡単に構築することができます。リスト 6 は、そのような検索エンジンの一例です。

リスト 6. YouTube 動画を対象とした単純なキーワード検索エンジン
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Searching for videos by keyword</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    td.line {
      border-bottom: solid 1px black;  
    }
    </style>
  </head>
  <body>
    <?php
    // if form not submitted
    // display search box
    if (!isset($_POST['submit'])) {
    ?>
    <h1>Keyword search</h1>  
    <form method="post" action="<?php echo 
      htmlentities($_SERVER['PHP_SELF']); ?>">
      Keywords: <br/>
      <input type="text" name="q" />
      <p/>
      Items to display: <br/>
      <select name="i">
        <option value="10">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
      <p/>
      <input type="submit" name="submit" value="Search"/>  
    </form>
    <?php      
    // if form submitted
    } else {
      // check for search keywords
      // trim whitespace
      // separate multiple keywords with /
      if (!isset($_POST['q']) || empty($_POST['q'])) {
        die ('ERROR: Please enter one or more search keywords');
      } else {
        $q = $_POST['q'];
        $q = ereg_replace('[[:space:]]+', '/', trim($q));
      }
      
      // set max results
      if (!isset($_POST['i']) || empty($_POST['i'])) {
        $i = 25;
      } else {
        $i = $_POST['i'];
      }
      
      // generate feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos/-/{$q}
      ?orderby=viewCount&max-results={$i}";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      $startOffset = $counts->startIndex; 
      $endOffset = ($startOffset-1) + $counts->itemsPerPage;       
      ?>
      
      <h1>Search results</h1>
      <?php echo $total; ?> items found. Showing items 
      <?php echo $startOffset; ?> to <?php echo $endOffset; ?>:
      <p/>
      
      <table>
      <?php    
      // iterate over entries in resultset
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get video thumbnail
        $attrs = $media->group->thumbnail[0]->attributes();
        $thumbnail = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // print record
        echo "<tr><td colspan=\"2\" class=\"line\"></td>
        </tr>\n";
        echo "<tr>\n";
        echo "<td><a href=\"{$watch}\"><img src=\"$thumbnail\"/></a></td>\n";
        echo "<td><a href=\"{$watch}\">
        {$media->group->title}</a><br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user 
        rating<br/>\n";
        echo $media->group->description . "</td>\n";
        echo "</tr>\n";
      }
    }
    ?>
    </table>
  </body>
</html>

リスト 6 に記載した 2 つのセグメント (フォームとその結果ページ) は条件付きテストによって分離されています。最初のセグメントは極めて単純な内容で、ユーザーが検索キーワードを入力して出力ページに表示する結果件数を指定するためのフォーム (図 4) を生成するだけです。

図 4. キーワード検索フォーム
キーワード検索フォーム

ユーザーがこのフォームを送信すると、スクリプトはまず始めに 1 つ以上の検索キーワードが入力されているかどうかをチェックし、1 つも入力されていない場合にはスクリプトの処理を停止してエラー・メッセージを表示します。検索キーワードが入力されていれば、スクリプトは YouTube フィード URL に検索キーワードを挿入して YouTube API に対する REST リクエストを生成します。その後は前のリスト 23、および 5 で概説した手法を使用して、リクエストに対する結果の XML 文書を SimpleXML オブジェクトに変換し、繰り返し処理によって結果ページを生成します。

リスト 6 には注目すべき下記 2 点があります。

  • ユーザーが検索フォームに入力したキーワードは、フィード URL で使用できるようにフォーマットし直されます。再フォーマットの方法は、まず trim() によってストリングの末尾からすべての空白文字を削除し、次に PHP の ereg_replace() 関数によってキーワードの間にある空白文字を詰めてスラッシュ (/) に置き換えるというものです。
  • リスト 6 では検出された結果の総数を表示するだけでなく、<openSearch:startIndex> および <openSearch:itemsPerPage> 要素を利用して表示された結果セットの開始オフセットと終了オフセットも表示しています。

図 5 に、リスト 6 によって生成された検索結果ページの一例を記載します。

図 5. 検索結果ページ
検索結果ページ

追加検索パラメーターを使用する方法

より正確な検索を行うには、以下のパラメーターを検索クエリーに追加することを検討してください。

  • start-index パラメーター。フィードの開始オフセットを指定します。
  • vq パラメーター。URL にエンコードされた検索語を指定します。
  • format パラメーター。動画のフィードを特定の形式に限定します。
  • lr パラメーター。フィードを特定の言語で説明されている動画に限定します。
  • time パラメーター。フィード (一部の標準フィードのみ) をフィルタリングして特定の期間内に公開された動画のみが含まれるようにします。

リスト 7 では、上記の追加パラメーターによってリスト 6 の検索エンジンのスクリプトを拡張し、さらに PEAR の Pager クラスによるページング・サポートを追加しています。

リスト 7. YouTube 動画を対象とする、改良されたキーワード検索エンジン
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Searching for videos by keyword</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    td.line {
      border-bottom: solid 1px black;  
    }
    </style>
  </head>
  <body>
    <?php
    // if form not submitted
    // display search box
    if (!isset($_GET['submit'])) {
    ?>
    <h1>Keyword search</h1>  
    <form method="get" action="<?php echo 
     htmlentities($_SERVER['PHP_SELF']); ?>">
      Keywords: <br/>
      <input type="text" name="vq" />
      <p/>
      Items sorted by: <br/>
      <select name="s">
        <option value="viewCount">User views</option>
        <option value="rating">User rating</option>
        <option value="published">Publication time</option>
      </select>
      <p/>
      Items per page: <br/>
      <select name="i">
        <option value="10">10</option>
        <option value="25">25</option>
        <option value="50">50</option>
        <option value="100">100</option>
      </select>
      <p/>
      <input type="submit" name="submit" value="Search"/>  
    </form>
    <?php      
    // if form submitted
    } else {
      // check for search keywords
      // trim whitespace
      // encode search string
      if (!isset($_GET['vq']) || empty($_GET['vq'])) {
        die ('ERROR: Please enter one or more search keywords');
      } else {
        $vq = $_GET['vq'];
        $vq = ereg_replace('[[:space:]]+', ' ', trim($vq));
        $vq = urlencode($vq);
      }
      
      // set max results per page
      if (!isset($_GET['i']) || empty($_GET['i'])) {
        $i = 25;
      } else {
        $i = htmlentities($_GET['i']);
      }
      
      // set sort critera
      if (!isset($_GET['s']) || empty($_GET['s'])) {
        $s = 'viewCount';
      } else {
        $s = htmlentities($_GET['s']);
      }
      
      // set start index
      if (!isset($_GET['pageID']) || $_GET['pageID'] <= 0) {
        $o = 1;  
      } else {        
        $pageID = htmlentities($_GET['pageID']);
        $o = (($pageID-1) * $i)+1;  
      }
      
      // generate feed URL
      $feedURL = "http://gdata.youtube.com/feeds/api/videos
      ?vq={$vq}&orderby={$s}&max-results={$i}&start-index={$o}";
      
      // read feed into SimpleXML object
      $sxml = simplexml_load_file($feedURL);
      
      // get summary counts from opensearch: namespace
      $counts = $sxml->children('http://a9.com/-/spec/opensearchrss/1.0/');
      $total = $counts->totalResults; 
      $startOffset = $counts->startIndex; 
      $endOffset = ($startOffset-1) + $counts->itemsPerPage;       
      
      // include Pager class
      require_once 'Pager/Pager.php';
      $params = array(
          'mode'       => 'Jumping',
          'perPage'    => $i,
          'delta'      => 5,
          'totalItems' => $total,
      );
      $pager = & Pager::factory($params);
      $links = $pager->getLinks();     
      ?>
      
      <h1>Search results</h1>
      <?php echo $total; ?> items found. 
      Showing items <?php echo $startOffset; ?> to 
      <?php echo $endOffset; ?>:
      <p/>
      
      <?php
      // print page links
      echo $links['all'];
      ?>
      
      <table>
      <?php    
      // iterate over entries in resultset
      // print each entry's details
      foreach ($sxml->entry as $entry) {
        // get nodes in media: namespace for media information
        $media = $entry->children('http://search.yahoo.com/mrss/');
        
        // get video player URL
        $attrs = $media->group->player->attributes();
        $watch = $attrs['url']; 
        
        // get video thumbnail
        $attrs = $media->group->thumbnail[0]->attributes();
        $thumbnail = $attrs['url']; 
        
        // get <yt:duration> node for video length
        $yt = $media->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->duration->attributes();
        $length = $attrs['seconds']; 
        
        // get <yt:stats> node for viewer statistics
        $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
        $attrs = $yt->statistics->attributes();
        $viewCount = $attrs['viewCount']; 
      
        // get <gd:rating> node for video ratings
        $gd = $entry->children('http://schemas.google.com/g/2005'); 
        if ($gd->rating) {
          $attrs = $gd->rating->attributes();
          $rating = $attrs['average']; 
        } else {
          $rating = 0; 
        }

        // get video ID
        $arr = explode('/',$entry->id);
        $id = $arr[count($arr)-1];
             
        // print record
        echo "<tr><td colspan=\"2\" class=\"line\"></td></tr>\n";
        echo "<tr>\n";
        echo "<td><a href=\"{$watch}\">
        <img src=\"$thumbnail\"/></a></td>\n";
        echo "<td><a href=\"{$watch}\">
        {$media->group->title}</a><br/>\n";
        echo sprintf("%0.2f", $length/60) . " min. | {$rating} user rating | 
        {$viewCount} views<br/>\n";
        echo $media->group->description . "<br/>\n";
        echo "<a href=\"details.php?id=$id\">More information</a>
        </td>\n"; 
        echo "</tr>\n";
      }
    }
    ?>
    </table>
  </body>
</html>

改良されたフォームには、図 6 に表示されているようにソート用のパラメーターも含まれています。

図 6. 改良されたキーワード検索フォーム
改良されたキーワード検索フォーム

ユーザーが上記のフォームを送信すると、ユーザーによって入力されたパラメーターがフィード URL に挿入された上で XML 結果セットが処理され、表示されます。ページングを処理する PEAR の Pager クラスは、<openSearch:totalResults> 要素の値と $_GET['pageID'] 変数を使用して自動的に結果セットの前のページ、次のページへのリンクを生成します。ユーザーがこれらのページ・リンクを選択すると、$_GET['pageID'] 変数の値を使用して開始インデックスが再計算され、その開始インデックスがフィード URL に挿入し直されて次の結果セットが生成されます。

リスト 7 の改良された出力例には、ページ・リンクが表示されています。

図 7. 検索結果ページ
検索結果ページ

各エントリーの横にある More information リンクに注目してください。このリンクが指定する別の PHP スクリプトは、コメント、動画レスポンス、投稿者のプロフィール、そして関連動画を含む動画の詳細を表示します。次のセクションで、これらの情報を取得する方法を説明します。


コメント、動画レスポンス、関連動画を操作する方法

リスト 1 のサンプル動画のエントリーをよく見てみると、動画のメタデータに加え、各動画の <entry> にコメントのフィード、動画レスポンスのフィード、そして関連動画のフィードへのリンクも含まれていることに気付くはずです。このそれぞれの情報について、詳しく検討していきましょう。

コメントとは、他の YouTube ユーザーが動画に関して投稿するフィードバックです。動画のコメントには、動画のコメント・フィードからアクセスすることができます。フィードの URL は <gd:comments> 要素のなかにあります。以下はその一例です。

<gd:comments>
  <gd:feedLink href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments' 
  countHint='124130'/>
</gd:comments>

このリンクを GET リクエストによって取得すると、新しい XML フィードが生成されます。リスト 8 はそのようなフィードの一例で、ここに、ユーザーが動画について投稿したコメントが含まれます。

リスト 8. コメント・フィードの例
<?xml version='1.0' encoding='UTF-8'?>
<feed xmlns='http://www.w3.org/2005/Atom' 
xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/'>
  <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments</id>
  <updated>2008-03-13T11:58:12.729Z</updated>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#comment'/>
  <title type='text'>Comments on 'Evolution of Dance'</title>
  <logo>http://www.youtube.com/img/pic_youtubelogo_123x63.gif</logo>
  <link rel='related' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg'/>
  <link rel='alternate' type='text/html' 
  href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
  <link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/
  comments?start-index=1&max-results=25'/>
  <link rel='next' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/
  comments?start-index=26&max-results=25'/>
  <author>
    <name>YouTube</name>
    <uri>http://www.youtube.com/</uri>
  </author>
  <generator version='beta' uri='http://gdata.youtube.com/'>
  YouTube data API</generator>
  <openSearch:totalResults>124353</openSearch:totalResults>
  <openSearch:startIndex>1</openSearch:startIndex>
  <openSearch:itemsPerPage>25</openSearch:itemsPerPage>
  <entry>
    <id>http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    206D45A8B1191817</id>
    <published>2008-03-13T04:57:22.000-07:00</published>
    <updated>2008-03-13T04:57:22.000-07:00</updated>
    <category scheme='http://schemas.google.com/g/2005#kind' 
    term='http://gdata.youtube.com/schemas/2007#comment'/>
    <title type='text'>how can you say ...</title>
    <content type='text'>how can you say that????!??!!!</content>
    <link rel='related' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg'/>
    <link rel='http://gdata.youtube.com/schemas/2007#in-reply-to' 
    type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    1DA6D946967C23A6'/>
    <link rel='alternate' type='text/html' 
    href='http://www.youtube.com/watch?v=dMH0bHeiRNg'/>
    <link rel='self' type='application/atom+xml' 
    href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/comments/
    206D45A8B1191817'/>
    <author>
      <name>stez407</name>
      <uri>http://gdata.youtube.com/feeds/api/users/stez407</uri>
    </author>
  </entry>
  <entry>
  ...
  </entry>
</feed>

動画レスポンスとは、YouTube ユーザーが動画に応えて投稿する別の動画です。動画レスポンスにアクセスするには、<link rel='http://gdata.youtube.com/schemas/2007#video.responses' ...> 要素の href 属性を使用します。以下はその一例です。

<link rel='http://gdata.youtube.com/schemas/2007#video.responses' 
type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/responses'/>

関連動画にアクセスする場合も同じように、<link rel='http://gdata.youtube.com/schemas/2007#video.related' ...> 要素の href 属性を使用します。以下にその例を記載します。

<link rel='http://gdata.youtube.com/schemas/2007#video.related' 
type='application/atom+xml' 
href='http://gdata.youtube.com/feeds/api/videos/dMH0bHeiRNg/related'/>

以上のすべての情報が手に入ったら、詳細な動画情報 Web ページを作成するのは簡単です。リスト 9 にその方法を示します。

リスト 9. 動画の詳細情報を取得する PHP スクリプト
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Retrieving video details</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    </style>
  </head>
  <body>
    <?php
    // function to parse a video <entry>
    function parseVideoEntry($entry) {      
      $obj= new stdClass;
      
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      $obj->title = $media->group->title;
      $obj->description = $media->group->description;
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $obj->watchURL = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $obj->thumbnailURL = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $obj->length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $obj->viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) { 
        $attrs = $gd->rating->attributes();
        $obj->rating = $attrs['average']; 
      } else {
        $obj->rating = 0;         
      }
        
      // get <gd:comments> node for video comments
      $gd = $entry->children('http://schemas.google.com/g/2005');
      if ($gd->comments->feedLink) { 
        $attrs = $gd->comments->feedLink->attributes();
        $obj->commentsURL = $attrs['href']; 
        $obj->commentsCount = $attrs['countHint']; 
      }
      
      // get feed URL for video responses
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/schemas/
      2007#video.responses']"); 
      if (count($nodeset) > 0) {
        $obj->responsesURL = $nodeset[0]['href'];      
      }
         
      // get feed URL for related videos
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/schemas/
      2007#video.related']"); 
      if (count($nodeset) > 0) {
        $obj->relatedURL = $nodeset[0]['href'];      
      }
    
      // return object to caller  
      return $obj;      
    }   
    
    // get video ID from $_GET 
    if (!isset($_GET['id'])) {
      die ('ERROR: Missing video ID');  
    } else {
      $vid = $_GET['id'];
    }
    
    // set video data feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/' . $vid;

    // read feed into SimpleXML object
    $entry = simplexml_load_file($feedURL);
    
    // parse video entry
    $video = parseVideoEntry($entry);
       
    // display main video record
    echo "<table>\n";
    echo "<tr>\n";
    echo "<td><a href=\"{$video->watchURL}\">
    <img src=\"$video->thumbnailURL\"/></a></td>\n";
    echo "<td><a href=\"{$video->watchURL}\">{$video->title}</a>
    <br/>\n";
    echo sprintf("%0.2f", $video->length/60) . " min. | {$video->rating} 
    user rating | {$video->viewCount} views<br/>\n";
    echo $video->description . "</td>\n";
    echo "</tr>\n";
    
    // read 'video comments' feed into SimpleXML object
    // parse and display each comment
    if ($video->commentsURL && $video->commentsCount > 0) {
      $commentsFeed = simplexml_load_file($video->commentsURL);    
      echo "<tr><td colspan=\"2\"><h3>" . $commentsFeed->title . 
      "</h3></td></tr>\n";
      echo "<tr><td colspan=\"2\"><ol>\n";
      foreach ($commentsFeed->entry as $comment) {
        echo "<li>" . $comment->content . "</li>\n";
      }
      echo "</ol></td></tr>\n";
    }
    
    // read 'video responses' feed into SimpleXML object
    // parse and display each video entry
    if ($video->responsesURL) {
      $responseFeed = simplexml_load_file($video->responsesURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $responseFeed->title . "</h3></td></tr>\n";
      foreach ($responseFeed->entry as $response) {
        $responseVideo = parseVideoEntry($response);
        echo "<tr>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        <img src=\"$responseVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\"
        >{$responseVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $responseVideo->length/60) . " min. | 
        {$responseVideo->rating} user rating | {$responseVideo->viewCount} 
        views<br/>\n";
        echo $responseVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    
    // read 'related videos' feed into SimpleXML object
    // parse and display each video entry
    if ($video->relatedURL) {
      $relatedFeed = simplexml_load_file($video->relatedURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $relatedFeed->title . "</h3></td></tr>\n";
      foreach ($relatedFeed->entry as $related) {
        $relatedVideo = parseVideoEntry($related);
        echo "<tr>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        <img src=\"$relatedVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        {$relatedVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $relatedVideo->length/60) . " min. | 
        {$relatedVideo->rating} user rating | {$relatedVideo->viewCount} 
        views<br/>\n";
        echo $relatedVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    echo "</table>\n";    
    ?>
  </body>
</html>

リスト 9 は少し複雑そうに見えるかもしれませんが、実際はそうではありません。まず始めに、このリストでは新しく parseVideoEntry() 関数を定義しています。この関数は SimpleXML <entry> オブジェクトを入力として受け取り、このオブジェクトから (前のリスト 236 に記載した手法で) データを抽出して、動画のキー属性に対応するプロパティーを設定した新規 stdClass オブジェクトを返します。このコードを関数にカプセル化すると、理解するのも再利用するにも容易になるだけでなく、スクリプトが読みやすくなります。

リストを辿っていくと、コードは動画 ID (More information リンクからリスト 7 の $_GET 配列によって受信した動画 ID) をキーとして使用して動画フィードの GET リクエストを送信するところから始まっています。これによって取得されたフィードの <entry> 要素が parseVideoEntry() に渡され、この関数によって動画に関する基本情報が含まれる stdClass オブジェクトが返されます。

しかし、これですべてではありません。parseVideoEntry() はサムネール、タイトル、説明などの基本的な動画情報を取得するだけでなく、さらに動画のコメント、レスポンス、関連動画それぞれのフィード URL を抽出します。するとスクリプトはフィードごとに GET リクエストを生成し、結果の <entry> 要素を構文解析して最終的な情報 (コメント、関連動画のサムネールとメタデータ、動画レスポンスを含む) を出力ページに組み込みます。この出力ページは図 8 のようになります。

図 8. 動画のコメント、レスポンス、関連動画を表示する Web ページ
動画のコメント、レスポンス、関連動画を表示する Web ページ

投稿者のプロフィールにアクセスする方法

開発者は YouTube API を使って、特定の動画をアップロードしたユーザーに関する情報にアクセスすることもできます。リスト 1 をよく見ください。このリストのなかにある <author> 要素に、動画所有者のユーザー名とそのユーザーのプロフィール・フィードへの URL が示されています。以下に例を記載します。

<author>
  <name>judsonlaipply</name>
  <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
</author>

この URL に対する GET リクエストでは、リスト 10 のような XML フィードが作成されます。

リスト 10. 投稿者のフィード例
<?xml version='1.0' encoding='UTF-8'?>
<entry xmlns='http://www.w3.org/2005/Atom' 
xmlns:media='http://search.yahoo.com/mrss/' 
xmlns:yt='http://gdata.youtube.com/schemas/2007' 
xmlns:gd='http://schemas.google.com/g/2005'>
  <id>http://gdata.youtube.com/feeds/api/users/judsonlaipply</id>
  <published>2006-03-23T07:20:45.000-08:00</published>
  <updated>2008-03-13T09:33:36.000-07:00</updated>
  <category scheme='http://gdata.youtube.com/schemas/2007/channeltypes.cat' 
  term='Standard'/>
  <category scheme='http://schemas.google.com/g/2005#kind' 
  term='http://gdata.youtube.com/schemas/2007#userProfile'/>
  <title type='text'>judsonlaipply Channel</title>
  <link rel='http://gdata.youtube.com/schemas/2007#featured-video' 
  type='application/atom+xml' href='http://gdata.youtube.com/feeds/api/
  videos/61heClTFc5w'/>
  <link rel='alternate' type='text/html' 
  href='http://www.youtube.com/profile?user=judsonlaipply'/>
  <link rel='self' type='application/atom+xml' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply'/>
  <author>
    <name>judsonlaipply</name>
    <uri>http://gdata.youtube.com/feeds/api/users/judsonlaipply</uri>
  </author>
  <yt:age>31</yt:age>
  <yt:username>judsonlaipply</yt:username>
  <yt:gender>m</yt:gender>
  <yt:location>US</yt:location>
  <media:thumbnail url='http://i.ytimg.com/vi/61heClTFc5w/default.jpg'/>
  <yt:statistics viewCount='1120502' videoWatchCount='858' 
  subscriberCount='21800' lastWebAccess='2008-03-11T15:36:18.000-07:00'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.favorites' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/favorites' 
  countHint='0'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.contacts' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/contacts' 
  countHint='2331'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.inbox' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/inbox' 
  countHint='20143'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.playlists' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/playlists'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.subscriptions' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/subscriptions' 
  countHint='1'/>
  <gd:feedLink rel='http://gdata.youtube.com/schemas/2007#user.uploads' 
  href='http://gdata.youtube.com/feeds/api/users/judsonlaipply/uploads' countHint='2'/>
</entry>

ご覧のように、リスト 10 にはユーザー名、国、年齢、性別といった基本的なユーザー・プロフィールだけではなく、そのユーザーのお気に入りの動画への URL、チャンネル登録、連絡先、受信ボックス (認証が必要)、そしてアップロードした動画のリストも示されています。したがって、リスト 10 のデータを構文解析してこの情報の一部をリスト 9 で生成されたページに追加するのはわけありません。この変更内容をリスト 11 に記載します。

リスト 11. 投稿者の詳細情報を取得する PHP スクリプト
<!DOCTYPE html 
  PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
  <head>
    <title>Retrieving video details</title>
    <style>
    img {
      padding: 2px; 
      margin-bottom: 15px;
      border: solid 1px silver; 
    }
    td {
      vertical-align: top;
    }
    </style>
  </head>
  <body>
    <?php
    // function to parse a video <entry>
    function parseVideoEntry($entry) {      
      $obj= new stdClass;
      
      // get author name and feed URL
      $obj->author = $entry->author->name;
      $obj->authorURL = $entry->author->uri;
      
      // get nodes in media: namespace for media information
      $media = $entry->children('http://search.yahoo.com/mrss/');
      $obj->title = $media->group->title;
      $obj->description = $media->group->description;
      
      // get video player URL
      $attrs = $media->group->player->attributes();
      $obj->watchURL = $attrs['url']; 
      
      // get video thumbnail
      $attrs = $media->group->thumbnail[0]->attributes();
      $obj->thumbnailURL = $attrs['url']; 
            
      // get <yt:duration> node for video length
      $yt = $media->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->duration->attributes();
      $obj->length = $attrs['seconds']; 
      
      // get <yt:stats> node for viewer statistics
      $yt = $entry->children('http://gdata.youtube.com/schemas/2007');
      $attrs = $yt->statistics->attributes();
      $obj->viewCount = $attrs['viewCount']; 
      
      // get <gd:rating> node for video ratings
      $gd = $entry->children('http://schemas.google.com/g/2005'); 
      if ($gd->rating) { 
        $attrs = $gd->rating->attributes();
        $obj->rating = $attrs['average']; 
      } else {
        $obj->rating = 0;         
      }
        
      // get <gd:comments> node for video comments
      $gd = $entry->children('http://schemas.google.com/g/2005');
      if ($gd->comments->feedLink) { 
        $attrs = $gd->comments->feedLink->attributes();
        $obj->commentsURL = $attrs['href']; 
        $obj->commentsCount = $attrs['countHint']; 
      }
      
      // get feed URL for video responses
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/
      schemas/2007#video.responses']"); 
      if (count($nodeset) > 0) {
        $obj->responsesURL = $nodeset[0]['href'];      
      }
         
      // get feed URL for related videos
      $entry->registerXPathNamespace('feed', 'http://www.w3.org/2005/Atom');
      $nodeset = $entry->xpath("feed:link[@rel='http://gdata.youtube.com/
      schemas/2007#video.related']"); 
      if (count($nodeset) > 0) {
        $obj->relatedURL = $nodeset[0]['href'];      
      }
    
      // return object to caller  
      return $obj;      
    }   
    
    // get video ID from $_GET 
    if (!isset($_GET['id'])) {
      die ('ERROR: Missing video ID');  
    } else {
      $vid = $_GET['id'];
    }

    // set video data feed URL
    $feedURL = 'http://gdata.youtube.com/feeds/api/videos/' . $vid;

    // read feed into SimpleXML object
    $entry = simplexml_load_file($feedURL);
    
    // parse video entry
    $video = parseVideoEntry($entry);
       
    // display main video record
    echo "<table>\n";
    echo "<tr>\n";
    echo "<td><a href=\"{$video->watchURL}\">
    <img src=\"$video->thumbnailURL\"/></a></td>\n";
    echo "<td><a href=\"{$video->watchURL}\">{$video->title}</a>
    <br/>\n";
    echo sprintf("%0.2f", $video->length/60) . " min. 
    | {$video->rating} user rating | {$video->viewCount} views<br/>\n";
    echo $video->description . "</td>\n";
    echo "</tr>\n";
    
    // read 'author profile feed' into SimpleXML object
    // parse and display author bio
    $authorFeed = simplexml_load_file($video->authorURL);    
    echo "<tr><td colspan=\"2\"><h3>Author information</h3>
    </td></tr>\n";
    $authorData = $authorFeed->children('http://gdata.youtube.com/schemas/2007');
    echo "<tr><td>Username:</td><td>" . $video->author . 
    "</td></tr>\n";
    echo "<tr><td>Age:</td><td>" . $authorData->age . 
    "</td></tr>\n";
    echo "<tr><td>Gender:</td><td>" . 
    strtoupper($authorData->gender) . "</td></tr>\n";
    echo "<tr><td>Location:</td><td>" . $authorData->location
     . "</td></tr>\n";    
    
    // read 'video comments' feed into SimpleXML object
    // parse and display each comment
    if ($video->commentsURL && $video->commentsCount > 0) {
      $commentsFeed = simplexml_load_file($video->commentsURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $commentsFeed->title . "</h3></td></tr>\n";
      echo "<tr><td colspan=\"2\"><ol>\n";
      foreach ($commentsFeed->entry as $comment) {
        echo "<li>" . $comment->content . "</li>\n";
      }
      echo "</ol></td></tr>\n";
    }
    
    // read 'video responses' feed into SimpleXML object
    // parse and display each video entry
    if ($video->responsesURL) {
      $responseFeed = simplexml_load_file($video->responsesURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $responseFeed->title . "</h3></td></tr>\n";
      foreach ($responseFeed->entry as $response) {
        $responseVideo = parseVideoEntry($response);
        echo "<tr>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        <img src=\"$responseVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$responseVideo->watchURL}\">
        {$responseVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $responseVideo->length/60) . " min. |
         {$responseVideo->rating} user rating | {$responseVideo->viewCount} 
         views<br/>\n";
        echo $responseVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    
    // read 'related videos' feed into SimpleXML object
    // parse and display each video entry
    if ($video->relatedURL) {
      $relatedFeed = simplexml_load_file($video->relatedURL);    
      echo "<tr><td colspan=\"2\"><h3>" . 
      $relatedFeed->title . "</h3></td></tr>\n";
      foreach ($relatedFeed->entry as $related) {
        $relatedVideo = parseVideoEntry($related);
        echo "<tr>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        <img src=\"$relatedVideo->thumbnailURL\"/></a></td>\n";
        echo "<td><a href=\"{$relatedVideo->watchURL}\">
        {$relatedVideo->title}</a><br/>\n";
        echo sprintf("%0.2f", $relatedVideo->length/60) . " min. | 
        {$relatedVideo->rating} user rating | {$relatedVideo->viewCount} 
        views<br/>\n";
        echo $relatedVideo->description . "</td>\n";
        echo "</tr>\n";      
      }
    }
    echo "</table>\n";    
    ?>
  </body>
</html>

リスト 11parseVideoEntry() 関数は、投稿者の名前と、投稿者フィード URL を出力オブジェクトに含めるように変更されています。メインのスクリプト内にもスクリプトのブロックが追加され、フィード URL に GET リクエストを送信し、レスポンスを構文解析して動画所有者のユーザー名、年齢、国、性別を抽出し、表示するようになっています。このスクリプトでは実際に行ってはいませんが、ユーザーのお気に入りの動画、連絡先、そしてチャンネル登録に関する情報を辿って取得するのも、それほど難しいことではありません。それにはメインの投稿者フィードに含まれる <link> を使用すればいいだけの話です。

改良された出力は、図 9 のようになります。

図 9. 投稿者の情報を表示する Web ページ
投稿者の情報を表示する Web ページ

まとめ

これで、SimpleXMLを使って YouTube サービスのデータを PHP アプリケーションに統合する方法についての記事と短期集中コースはおしまいです。この記事に記載した例では、以下の内容について説明しました。

  • YouTube のフィードの概要
  • カテゴリー、キーワードを基準に動画を検索する方法
  • 動画のメタデータを抽出する方法
  • コメント、関連動画、動画レスポンス、投稿者のプロフィールなどの補助的情報を取得する方法

これらの例が示すように、開発者が新しい Web アプリケーションを作成する際に大幅な柔軟性と自由をもたらしてくれる YouTube REST API は、YouTube のデータを他の Web サービスのデータとマッシュアップするときにも、あるいはただ単に YouTube コミュニティー向けにカスタマイズしたインターフェースを作成するときにも大活躍します。


ダウンロード

内容ファイル名サイズ
Code sample for articlex-youtubeapi-code.zip8KB

参考文献

学ぶために

製品や技術を入手するために

  • PEAR Pager パッケージ: 配列からのデータを入力として追加し、ページに分割するパラメーターを指定するには、このツールをダウンロードしてください。
  • IBM トライアル・ソフトウェア: developerWorks から直接ダウンロードできるトライアル・ソフトウェアで、次の開発プロジェクトを構築してください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source
ArticleID=309129
ArticleTitle=PHP で YouTube API を操作する
publish-date=04182008