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>
.
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.