Use the YouTube API with PHP

Process and integrate data from YouTube into your PHP application with PHP's SimpleXML extension

The YouTube video sharing site allows Web application developers to access public content through its REST-based developer API. The SimpleXML extension in PHP is ideal for processing the XML feeds generated by this API and using them to build customized PHP applications. This article introduces the YouTube Data API, demonstrates how you can use it to browse user-generated video content; access video metadata, comments and responses; and perform keyword searches.

Share:

Vikram Vaswani, Founder, Melonfire

Vikram Vaswani is the founder and CEO of Melonfire, a consulting services firm with special expertise in open-source tools and technologies. He is also the author of the books PHP Programming Solutions and How to do Everything with PHP and MySQL.



18 April 2008 (First published 15 April 2008)

Also available in Chinese Japanese

Introduction

When it comes to online video sharing, the most popular site by far is YouTube, with billions of page views and hundreds of thousands of videos added daily. It's not just home videos either: today, YouTube offers music videos, clips from TV shows, trailers of upcoming movies, animation clips, and much more. The service also allows users to tag videos with keywords, and view the most popular videos at any given time.

Frequently used acronyms

  • API: application programming interface
  • 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

One of YouTube's cooler features is its YouTube Data API, which allows developers to access and search YouTube video data through a REST-based API, and integrate this data into their own XML-aware application. This isn't particularly difficult to do—you write application-level code to send REST requests, parse and decode responses, and integrate the resulting data into the application interface. If you use PHP, you can perform these tasks either through YouTube's PHP Client Library, or manually parse the XML responses to your REST requests.

This article discusses the latter method, showing you how to access public content through the YouTube API and integrate this content into your PHP application using SimpleXML. It includes examples that retrieve video listings in specific categories; search for videos by keyword; retrieve video metadata, including thumbnails and statistics; and access user profile information.


Understanding the YouTube data format

Before diving into the PHP code, a few words about the YouTube Data API are in order. As with all REST-based services, things start with an HTTP request to a designated resource. This HTTP request contains a query with one or more input parameters; the server replies to the query with an Atom- or RSS-formatted response, suitable for parsing in any XML-aware client.

To see how this works, try to access the URL http://gdata.youtube.com/feeds/api/standardfeeds/most_viewed in your favourite Web browser. This REST method returns a list of the most frequently viewed videos on YouTube at the current time. The raw XML response to this method (which you can view in the source code of the resulting page) contains detailed information on these videos, and might look something like Listing 1:

Listing 1. An example feed generated by the 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>

Take a quick glance through this output to familiarize yourself with its main elements:

  • The YouTube Data API responds to a REST request with a feed that contains the requested data. Various feeds are available: video feeds, video comments feeds, user playlist feeds, user contact feeds, and so on. In most cases, therefore, the XML response contains a <feed> element as the root element. The <feed> element contains <link> elements, which contain URLs for the current, next, and previous pages of the result set, and <openSearch:> elements, which contain summary statistics for the search.
  • The outermost <feed> element encloses one or more <entry> elements, each representing a video matching the query. Each <entry> contains further information on the video it represents, including the category, title, description, publication date, author, and duration. Each <entry> also contains <link> elements, which provide URL links to view the video, its responses, and other related videos on the YouTube Web site.
  • A <media:group> element within each <entry> contains detailed information on the video: its title, description, availability in various formats, thumbnail links, and video player links.
  • A <yt:statistics> element within each <entry> provides viewer statistics for the video, while a <gd:rating> element supplies the video's average rating, as well as a count of the total number of users who have rated it.

You'll agree that this is a lot of information—probably more than you would normally expect. It's precisely this extensive information base that makes the YouTube Data API so interesting to work with, as it allows developers the freedom to come up with creative new applications.

Not all YouTube Data API functions are publicly accessible in this manner. Certain functions—specifically, functions that modify data on the site, including functions for uploading videos, editing content or user profile information, or adding ratings and comments—are only accessible with a developer key and authentication token. To get access to these API functions, register with YouTube as a developer (look in Resources for a link). The discussion on this article is limited to those API functions which don't require a developer key; as you'll shortly see, there are more than enough of these to keep you occupied for a while!


Retrieving video listings

Let's now proceed to an example of processing a YouTube Data API feed using PHP. Listing 2 takes the feed from Listing 1 and uses SimpleXML to extract relevant fragments of data from it and format it into a Web page:

Listing 2. A PHP script to list most frequently viewed YouTube videos
<!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>

Figure 1 demonstrates the output you might see:

Figure 1. A list of frequently-viewed videos
A list of frequently-viewed videos

Listing 2 begins by using the simplexml_load_file() object to send a request to the feed URL and convert the response into a SimpleXML object. It then iterates over the <entry> elements in the response, processing each one using a foreach() loop and retrieving the information shown in Figure 1. Here's how each of the values was obtained:

  • Video meta-data is stored in the <media:group> node collection, under each <entry> node. SimpleXML's children() method in conjunction with the media: namespace returns this node collection as $media, and then accesses the video title and description as $media->group->title and $media->group->description respectively.
  • The url attribute of the various <media:thumbnail> elements stores a URL for the video thumbnail. To access this value, call $media->group->thumbnail[0]->attributes(), and access the url key of the resulting attribute array.
  • The url attribute of the <media:player> elements stores a URL for the video player. To access this value, call $media->group->player->attributes(), and access the url key of the resulting attribute array.
  • The <yt:duration> element under the <media:group> element stores the length of the video, in seconds. The $media->children() method in conjunction with the yt: namespace returns this node as a SimpleXML object, and the object's attributes() method retrieves the value of the seconds attribute. To obtain the length in minutes, you divide this value by 60.
  • In a similar vein, viewer statistics for the video are available in the <yt:stats> element. Again, access this element through namespace, and the resulting object's attributes() method retrieves the value of the viewCount attribute.
  • User ratings for a video are found in the <gd:ratings> element under each <entry>. To access this element, call SimpleXML's children() method using its namespace, and access the resulting object's average attribute to obtain the average rating.

Once you obtain all this information, it's a simple matter to format and display it on a Web page; the simple CSS rules at the top of Listing 2 take care of this.

Note that the feed URL used in Listing 2 is one of various standard feeds provided by YouTube to developers. Some other commonly-used ones are:

  • Highly rated videos: http://gdata.youtube.com/feeds/api/standardfeeds/top_rated
  • Most recently-added videos: http://gdata.youtube.com/feeds/api/standardfeeds/most_recent
  • Most linked videos: http://gdata.youtube.com/feeds/api/standardfeeds/most_linked

For a complete list of standard feeds, refer to the YouTube Data API Developer's Guide in Resources.


Working with video categories

In addition to the standard feeds discussed in the previous section, YouTube also allows developers to retrieve videos by category. This is done by appending the category name to the URL for the video feed, such as http://gdata.youtube.com/feeds/api/videos/-/Travel/ or http://gdata.youtube.com/feeds/api/videos/-/Tech/.

Listing 3 illustrates, by retrieving and listing videos in the Travel category:

Listing 3. A PHP script to list YouTube videos in the Travel category
<!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>

As in Listing 2, Listing 3 begins by sending a GET request for the feed URL for the Travel category, and turning the response into a SimpleXML object. It then iterates over the <entry> elements in the collection to retrieve the title, description, duration and user rating for each and formats this information as an ordered list. Listing 3 also adds one new feature: it looks through the summary data included in the <openSearch:> elements in the XML response and prints the total number of videos found the category.

Figure 2 illustrates what the output looks like:

Figure 2. A list of videos from Travel category
A list of videos from Travel category

You can get a complete list of YouTube categories from the URL http://gdata.youtube.com/schemas/2007/categories.cat. Listing 4 illustrates a sample of this file:

Listing 4. An example YouTube category list
<?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>

It's not too difficult to write a PHP script that retrieves this file, parses it, and then uses the YouTube API to retrieve the five most frequently viewed videos in each category. Listing 5 illustrates such a script:

Listing 5. A PHP script to list the top five YouTube videos in different categories
<!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>

Listing 5 first retrieves the XML-encoded category list at http://gdata.youtube.com/schemas/2007/categories.cat. It then uses SimpleXML's xpath() function to retrieve the YouTube category names from this list, and executes a foreach loop to retrieve the five most frequently viewed videos in each category, by dynamically creating the feed URL for each category. What happens inside the foreach() loop should come as no surprise to you by now, as it's almost identical to the steps in Listing 3; however, it's worth pointing out the two extra parameters added to the feed URL:

  • The max-results parameter specifies the number of results to return in the feed.
  • The orderby parameter specifies how to sort these results: by popularity (viewCount), language, user rating (rating) or publication time (published).

Now, feast your eyes on Figure 3, which contains a sample of the output generated by Listing 5:

Figure 3. A Web page displaying videos from various categories
A Web page displaying videos from various categories

Performing keyword search

YouTube allows users to tag videos with keywords, to make them easier to identify. And the YouTube API allows developers to search for videos by keyword, in a manner similar to that used for categories. For example, to search for videos tagged with the keyword '"boat", send a GET request to http://gdata.youtube.com/feeds/api/videos/-/boat. Similarly, to find all videos tagged with the keywords 'westminster' and 'london', send a GET request to http://gdata.youtube.com/feeds/api/videos/-/westminster/london. The XML response to these requests will be similar to that shown in Listing 1: a <feed> containing <entry>s, with each <entry> representing a matching video.

Needless to say, this makes it quite easy to build a simple search engine in PHP that mines the YouTube database for videos matching user-supplied keywords. Listing 6 illustrates one such search engine:

Listing 6. A simple keyword search engine for YouTube videos
<!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>

Listing 6 contains two segments, a form and its result page, separated by a conditional test. The first segment is quite simple. All it does is generate a form (Figure 4) for the user to enter search keywords and specify the number of results to display in the output page.

Figure 4. A keyword search form
A keyword search form

Once the user submits this form, the script first checks whether one or more search keywords were entered, and halts script processing with an error message if it finds none. If search keywords are present, the script interpolates them into a YouTube feed URL and generates a REST request to the YouTube API. It then converts the resulting XML document into a SimpleXML object and iterates over to generate a result page, using techniques outlined in previous Listings 2, 3, and 5.

Two things are noteworthy about Listing 6:

  • The keywords entered by the user into the search form are reformatted for use in the feed URL. This is done by first trimming all whitespace from the ends of the string with trim(), and then compressing and replacing whitespace between the keywords with forward slashes (/) using PHP's ereg_replace() function.
  • In addition to displaying the total number of results found, Listing 6 also makes use of the <openSearch:startIndex> and <openSearch:itemsPerPage> elements to display the start and end offset of the result set being displayed.

Figure 5 provides an example of the search results page generated by Listing 6:

Figure 5. A search results page
A search results page

Using additional search parameters

For a more precise search, consider adding some of these parameters to your search query:

  • The start-index parameter, which specifies the start offset for the feed
  • The vq parameter, which specifies a URL-encoded search term
  • The format parameter, which restricts the feed to videos in a specific format
  • The lr parameter, which restricts the feed to videos described in a specific language
  • The time parameter, which filters the feed (some standard feeds only) to only contain videos published within a certain time frame

Listing 7 enhances the search engine script from Listing 6 with these additional parameters, and adds paging support with the Pager class in PEAR:

Listing 7. A better keyword search engine for YouTube videos
<!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>

The revised form now includes an additional sort parameter, and can be seen in Figure 6:

Figure 6. A revised keyword search form
A revised keyword search form

When a user submits this form, the parameters entered by the user are interpolated into a feed URL, and the XML result set is processed and displayed. Paging is taken care of by the Pager class in PEAR, which uses the values of the <openSearch:totalResults> element and $_GET['pageID'] variable to automatically generate links to previous and next pages of the result set. When a user selects these page links, the value of the $_GET['pageID'] variable is used to recalculate the start index, which is re-interpolated into the feed URL to generate the next set of results.

Figure 7 illustrates the revised output, with page links:

Figure 7. A search results page
A search results page

Notice the More information link that appears next to each entry. This link points to another PHP script that displays additional information about the video, including its comments, video responses, author profile and related videos. The next section discusses how to go about obtaining this information.


Working with comments, video responses, and related videos

If you look closely at the sample video entry in Listing 1, you'll see that in addition to video meta-data, each video <entry> also includes links to feeds for comments, video responses, and related videos. Let's look at each of these in detail.

A comment is feedback on a video posted by other YouTube users. You can access comments for a video through the video's comments feed. The URL to the feed is in the <gd:comments> element. Here's an example:

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

Retrieving this link through a GET request generates a new XML feed. In Listing 8, you see one such feed that contains user-posted comments on the video.

Listing 8. An example comments feed
<?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>

A video response is a video posted in response to another video by a YouTube user. You can access the video responses for a video through the href attribute of the <link rel='http://gdata.youtube.com/schemas/2007#video.responses' ...> element. Here's an example:

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

In a similar vein, you can access a list of related videos through the href attribute of the <link rel='http://gdata.youtube.com/schemas/2007#video.related' ...> element. Here's an example:

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

With all this information at hand, it's not very difficult to produce a more detailed video information Web page. Listing 9 illustrates:

Listing 9. A PHP script to retrieve detailed video information
<!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>

Listing 9 might look a little complex, but it really isn't. To begin, it defines a new parseVideoEntry() function, which accepts a SimpleXML <entry> object as input, extracts data from this object (using techniques shown in previous Listings 2, 3, and 6) and returns a new stdClass object with properties corresponding to key attributes of the video. Encapsulating this code into a function makes it easier to understand and reuse, and also makes the script easier to read.

On to the listing proper, then. The code begins by sending a GET request for a video feed, using the video ID as key (this is the video ID received through the $_GET array from Listing 7 through the More information link). The <entry> element from the resulting feed is then passed to parseVideoEntry(), which returns a stdClass object containing basic information about the video.

That's not all, though. In addition to retrieving basic video information, such as the thumbnail, title and description, parseVideoEntry() also extracts the feed URLs for the video's comments, responses, and related videos. The script then generates GET requests for each of these feeds, parses the resulting <entry> elements, and interpolates the resulting information (including comments, thumbnails and meta-data of related videos, and video responses) into the output page. Figure 8 illustrates what the output might look like:

Figure 8. A Web page displaying video comments, responses, and related videos
A Web page displaying video comments, responses, and related videos

Accessing author profiles

The YouTube API also lets developers access information on the user who uploaded a particular video. Look closely at Listing 1, and you'll see an <author> element. This element provides the username of the video owner, and the URL to the user's profile feed. Here's an example:

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

A GET request to this URL will produce an XML feed like Listing 10:

Listing 10. An example author feed
<?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>

As you can see, Listing 10 provides a basic user profile—the user's name, location, age and sex—as well as URLs for the user's favourite videos, subscriptions, contacts, inbox (requires authentication), and uploaded video list. So it's a simple matter to parse the data in Listing 10 and add some of this information to the page generated by Listing 9. Listing 11 shows the changes:

Listing 11. A PHP script to retrieve detailed author information
<!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>

In Listing 11, the parseVideoEntry() function changed to include the author name and author feed URL in the output object. Within the main script too, an additional script block sends a GET request for the feed URL and parses the response to extract and display the video owner's username, age, location, and gender. Although this script doesn't do it, it's not very difficult to also follow and retrieve information about the user's favourite videos, contacts, and subscriptions, by using the <link>s included in the main author feed.

Figure 9 displays the revised output:

Figure 9. A Web page displaying author information
A Web page displaying author information

Summary

And that brings you to the end of this article and its crash course in how to integrate data from the YouTube service into a PHP application using SimpleXML. The examples in this article:

  • Introduced you to YouTube's feeds
  • Showed you how to search for videos by category and keyword
  • Explained how to extract video meta-data.
  • Illustrated how to obtain ancillary information such as comments, related videos, video responses, and author profiles

As these examples prove, the YouTube REST API offers developers a great deal of flexibility and freedom when it comes to creating new Web applications. It's very useful when you try to mash up YouTube data with data from other Web services, or simply build a customized interface for the YouTube community.


Download

DescriptionNameSize
Code sample for articlex-youtubeapi-code.zip8KB

Resources

Learn

Get products and technologies

  • The PEAR Pager package: Download this tool to add data from array as input and specify the parameters that divide it into pages.
  • IBM trial software: Build your next development project with trial software available for download directly from developerWorks.

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into XML on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=XML, Open source
ArticleID=299640
ArticleTitle=Use the YouTube API with PHP
publish-date=04182008