Calling the InnerTubeService Class

Now that we have our classes designed, let's show how we can convert the XML from Figure 4-4 into the objects represented in Figure 4-5. To do this, we'll use another class, our InnerTubeService class. As you can see in Figure 4-6, the common feeds—Top Rated, Most Viewed, etc.—from YouTube's API are all available as methods. The return type for all of the methods in InnerTubeService is an ObservableCollection<Inner TubeVideo>.

IntelliSense showing the available methods for retrieving YouTube data

Figure 4-6. IntelliSense showing the available methods for retrieving YouTube data

Converting YouTube XML into .NET Objects

The InnerTubeService class actually has only one real method that does the work of converting XML into objects, and that's the ConvertYouTubeXmlIntoObjects method. The reason for this is that each YouTube XML API call has an identical structure, whether it is for search results or top-rated videos, so we can use the same code to parse the XML but still maintain some "convenience" methods, such as Search(string query), to make it easier to reuse the library.

As you can see in Example 4-1 and Example 4-2, the ConvertYouTubeXmlToObjects method takes two parameters, the Uri for a valid YouTube API, and a Setting object, which we must pass in for configuring the directories on your PC that we will save video files (thumbnail, video, etc.). We will cover the Setting class in depth later, in the Application Settings section.

Example 4-1.  C# code for ConvertYouTubeXmlToObjects method signature

public static ObservableCollection<InnerTubeVideo> ConvertYouTubeXmlToObjects(
  Uri youTubeUrl, Setting setting)

Example 4-2.  Visual Basic code for ConvertYouTubeXmlToObjects method signature

Public Shared Function ConvertYouTubeXmlToObjects(ByVal youTubeUrl As Uri, _
  ByVal setting As Setting) As ObservableCollection(Of InnerTubeVideo)

Inside the ConvertYouTubeXmlToObjects method, we declare a number of XNamespace variables, which are required because the XML we receive back from YouTube makes heavy use of XML namespaces (see Example 4-3 and Example 4-4).

Example 4-3.  C# code to declare XML namespaces

XNamespace nsBase = @"http://www.w3.org/2005/Atom";
XNamespace nsGData = @"http://schemas.google.com/g/2005";
XNamespace nsYouTube = @"http://gdata.youtube.com/schemas/2007";

Example 4-4.  Visual Basic code to declare XML namespaces

Dim nsBase As XNamespace = "http://www.w3.org/2005/Atom"
Dim nsGData As XNamespace = "http://schemas.google.com/g/2005"
Dim nsYouTube As XNamespace = "http://gdata.youtube.com/schemas/2007"

To get the actual data "over-the-wire," we'll call the YouTube service using the WebClient class in the System.Net namespace by passing in the URL for the service and calling the OpenRead() method with YouTube's schema URL. This is equivalent to how we previously opened our browser to peruse the XML from Figure 4-5. We'll then use the XDocument class to load the XML with the XmlTextReader as shown in Example 4-5 and Example 4-6.

Example 4-5.  C# code to make a web request to YouTube's API

//Use to call service
WebClient wc = new WebClient();

//Get Data
XmlTextReader xr = new XmlTextReader(wc.OpenRead(youTubeUrl));
XDocument rawData = XDocument.Load(xr);

Example 4-6.  Visual Basic code to make a web request to YouTube API

'Use to call service
Dim wc As New WebClient()

'Get Data
Dim xr As New XmlTextReader(wc.OpenRead(youTubeUrl))
Dim rawData As XDocument = XDocument.Load(xr)

At this point, we have the raw XML results with a listing of videos in memory, with each <entry> element (a YouTube video) being an XElement object. Next, we want to use LINQ for XML to loop through every entry element and create a new InnerTube Video object, setting properties such as the video author, categories, title, and more, as shown in Example 4-7 and Example 4-8.

In the first line of Example 4-7 and Example 4-8, you'll notice that we pass in the nsBase + "entry" as a parameter to the Descendents method. This is required because the entry element is in the nsBase namespace, and without this namespace, your LINQ query would throw an exception saying the element attribute does not exist. Also note that we are not concatenating strings, but instead using the overloaded + operator for the XNamespace class. This is why the Visual Basic code uses the + operator instead of the concatenation operator (&).

Example 4-7.  C# code to loop through entry elements and assign properties

var query = from entry in rawData.Descendants(nsBase + "entry")
  select new InnerTubeVideo
  {
    Author = entry.Element(nsBase + "author").Element(nsBase + "name").Value,
    Categories = ParseCategories(entry.Elements(nsBase + "category")),
    Id = ParseID(entry.Element(nsBase + "id").Value),
    Published = DateTime.Parse(entry.Element(nsBase + "published").Value),
    Updated = DateTime.Parse(entry.Element(nsBase + "updated").Value),
    Title = entry.Element(nsBase + "title").Value,
    Description = entry.Element(nsBase + "content").Value,
...

Example 4-8.  Visual Basic code to loop through entry elements and assign properties

Dim query = From entry In rawData.Descendants(nsBase + "entry") _
  Select New InnerTubeVideo With _
  { _
    .Author = entry.Element(nsBase + "author").Element(nsBase + "name").Value, _
    .Categories = ParseCategories(entry.Elements(nsBase + "category")), _
    .Id = ParseID(entry.Element(nsBase + "id").Value), _
    .Published = DateTime.Parse(entry.Element(nsBase + "published").Value), _
    .Updated = DateTime.Parse(entry.Element(nsBase + "updated").Value), _
    .Title = entry.Element(nsBase + "title").Value, _
    .Description = entry.Element(nsBase + "content").Value, _
...

As mentioned previously, some fields that we want to retrieve are in different namespaces, so we have to, in code, swap out the namespace to read that field. To show exactly what this means, Example 4-9 contains the raw XML received from YouTube's API for the total number of views for a video.

Example 4-9. Snippet of XML for the total number of views for a YouTube video

<yt:statistics viewCount="130534" favoriteCount="1210" />

Since the viewCount attribute lives in the <yt> (YouTube) XML namespace, we must explicitly add the nsYouTube namespace variable to get the value. In Example 4-10 and Example 4-11, we start at the root entry element and get the statistics element, and then pull the viewCount attribute.

Example 4-10.  C# code to parse the viewCount attribute from XML

Views = int.Parse(entry.Element(nsYouTube +
        "statistics").Attribute("viewCount").Value),

Example 4-11.  Visual Basic code to parse the viewCount attribute from XML

.Views = Integer.Parse(entry.Element(nsYouTube + _
         "statistics").Attribute("viewCount").Value), _

To keep the parsing relatively compact, one nice thing you can do with LINQ for XML is pass in a chunk of XML to a function. For example, Johnny Lee's head-tracking video (http://www.youtube.com/watch?v=cI1AwZN4ZYg) includes 15 different YouTube video categories, like the listing in Example 4-12.

Example 4-12. Snippet of XML for a YouTube video's categories

<category scheme="..." term="wiimote" />
  <category scheme="..." term="display" />
  <category scheme="..." term="3D" />
  <category scheme="..." term="reality" />
  <category scheme="..." term="nintendo"/>
...

What we want to do is convert the term attributes in the <category> elements from Example 4-12 into a nice Collection<string> variable, with each term stored as a string. To do this, we'll call ParseCategories, as shown in Example 4-13 and Example 4-14.

Example 4-13.  C# code to send XML to the ParseCategories method

Categories = ParseCategories(entry.Elements(nsBase + "category")),

Example 4-14.  Visual Basic code to send XML to the ParseCategories method

.Categories = ParseCategories(entry.Elements(nsBase + "category")), _

ParseCategories receives an IEnumerable<Xelement>, which will be formatted exactly like the <category> elements shown in Example 4-12. We can then use LINQ to pull the value of the <term> attribute from each category and return it all as a Collection<string> class, as shown in Example 4-15 and Example 4-16.

Example 4-15.  C# code to parse the value of the term attributes

private static Collection<string> ParseCategories(IEnumerable<XElement> Categories)
{
  var vals = from c in Categories.Attributes("term")
    select c.Value;
  return (Collection<string>)vals;
}

Example 4-16.  Visual Basic code to parse the value of the term attributes

Private Shared Function ParseCategories(ByVal Categories As IEnumerable(Of XElement)) As Collection(Of String)
  Dim vals = From c In Categories.Attributes("term") _
  Select c.Value
  Return vals.ToCollection()
End Function

Example 4-17 and Example 4-18 show the ConvertYouTubeXmlToObjects method in its entirety.

Example 4-17.  C# code for the ConvertYouTubeXmlToObjects method

public static ObservableCollection<InnerTubeVideo> ConvertYouTubeXmlToObjects(
  Uri youTubeUrl, Setting setting)
{
  XNamespace nsBase = @"http://www.w3.org/2005/Atom";
  XNamespace nsGData = @"http://schemas.google.com/g/2005";
  XNamespace nsYouTube = @"http://gdata.youtube.com/schemas/2007";

  //Use to call service
  WebClient wc = new WebClient();

  //Get Data
  XmlTextReader xr = new XmlTextReader(wc.OpenRead(youTubeUrl));
  XDocument rawData = XDocument.Load(xr);
  var query = from entry in rawData.Descendants(nsBase + "entry")
  select new InnerTubeVideo
  {
    Author = entry.Element(nsBase + "author").Element(nsBase + "name").Value,
    Categories = ParseCategories(entry.Elements(nsBase + "category")),
    Id = ParseID(entry.Element(nsBase + "id").Value),
    Published = DateTime.Parse(entry.Element(nsBase + "published").Value),
    Updated = DateTime.Parse(entry.Element(nsBase + "updated").Value),
    Title = entry.Element(nsBase + "title").Value,
    Description = entry.Element(nsBase + "content").Value,
    ThumbnailLink = _BaseThumbnailUrl + ParseID(entry.Element(nsBase + "id").Value) +
      @"/0.jpg",
    Link = _BasewatchUrl + ParseID(entry.Element(nsBase + "id").Value),
    EmbedLink = _baseEmbedUrl + ParseID(entry.Element(nsBase + "id").Value),
    DownloadLink = _BaseDownloadUrl + ParseID(entry.Element(nsBase + "id").Value),
    Views = int.Parse(entry.Element(nsYouTube +
            "statistics").Attribute("viewCount").Value),
    AvgRating = float.Parse(entry.Element(nsGData +
                "rating").Attribute("average").Value),
    NumRaters = int.Parse(entry.Element(nsGData +
                "rating").Attribute("numRaters").Value),

    //set download locations
    DownloadedImage = FileHelper.BuildFileName(setting.SubPath,
      ParseID(entry.Element(nsBase + "id").Value), FileType.Image),
    DownloadedFlv = FileHelper.BuildFileName(setting.SubPath,
      entry.Element(nsBase + "title").Value, FileType.Flv),
    DownloadedMp4 = FileHelper.BuildFileName(setting.VideoPath,
      entry.Element(nsBase + "title").Value, FileType.Mp4),
    DownloadedWmv = FileHelper.BuildFileName(setting.VideoPath,
     entry.Element(nsBase + "title").Value, FileType.Wmv)
  };

  return query.ToObservableCollection<InnerTubeVideo>();
}

Example 4-18.  Visual Basic code for the ConvertYouTubeXmlToObjects method

Public Shared Function ConvertYouTubeXmlToObjects(ByVal youTubeUrl As Uri, _
  ByVal setting  As Setting) As ObservableCollection(Of InnerTubeVideo)

  Dim nsBase As XNamespace = "http://www.w3.org/2005/Atom"
  Dim nsGData As XNamespace = "http://schemas.google.com/g/2005"
  Dim nsYouTube As XNamespace = "http://gdata.youtube.com/schemas/2007"

  'Use to call service
  Dim wc As New WebClient()

  'Get Data
  Dim xr As New XmlTextReader(wc.OpenRead(youTubeUrl))
  Dim rawData As XDocument = XDocument.Load(xr)
  Dim query = From entry In rawData.Descendants(nsBase + "entry") _

  Select New InnerTubeVideo With _
  { _
    .Author = entry.Element(nsBase + "author").Element(nsBase + "name").Value, _
    .Categories = ParseCategories(entry.Elements(nsBase + "category")), _
    .Id = ParseID(entry.Element(nsBase + "id").Value), _
    .Published = DateTime.Parse(entry.Element(nsBase + "published").Value), _
    .Updated = DateTime.Parse(entry.Element(nsBase + "updated").Value), _
    .Title = entry.Element(nsBase + "title").Value, _
    .Description = entry.Element(nsBase + "content").Value, _
    .ThumbnailLink = _BaseThumbnailUrl & ParseID(entry.Element(nsBase + _
      "id").Value)& "/0.jpg", _
    .Link = _BasewatchUrl & ParseID(entry.Element(nsBase + "id").Value), _
    .EmbedLink = _baseEmbedUrl & ParseID(entry.Element(nsBase + "id").Value), _
    .DownloadLink = _BaseDownloadUrl & ParseID(entry.Element(nsBase + "id").Value), _
    .Views = Integer.Parse(entry.Element(nsYouTube + _
      "statistics").Attribute("viewCount").Value), _
    .AvgRating = Single.Parse(entry.Element(nsGData + _
      "rating").Attribute("average").Value), _
    .NumRaters = Integer.Parse(entry.Element(nsGData + _
      "rating").Attribute("numRaters").Value), _
    .DownloadedImage = FileHelper.BuildFileName(setting.SubPath, _
      ParseID(entry.Element(nsBase + "id").Value), FileType.Image), _
    .DownloadedFlv = FileHelper.BuildFileName(setting.SubPath,
      entry.Element(nsBase + "title").Value, FileType.Flv), _
    .DownloadedMp4 = FileHelper.BuildFileName(setting.VideoPath, _
      entry.Element(nsBase + "title").Value, FileType.Mp4), _
    .DownloadedWmv = FileHelper.BuildFileName(setting.VideoPath, _
      entry.Element(nsBase + "title").Value, FileType.Wmv) _
  }

  Return query.ToObservableCollection()

End Function

Now that we know how to retrieve information about YouTube videos, let's see how we can use this data to programmatically download a YouTube video.

Get Coding4Fun now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.