youtube-apigdata-apiyoutube.net-api

YouTube GData API for C# - Where do I find an example of a working batch query?


I have scoured the net for days, unsuccessfully just looking for a working example of a YouTube batch request using the C# API. So much of the documentation is out of date.

These tell me what the XML should look like:

https://developers.google.com/gdata/docs/batch https://developers.google.com/youtube/2.0/developers_guide_protocol_batch_processing

and this is an example with Google Spreadsheets:

https://developers.google.com/google-apps/spreadsheets/#updating_multiple_cells_with_a_batch_request

Though I'm frightfully close time and again, I keep falling short of the mark with that gloriously descriptive GDataRequestException "Execution of request batch failed".

Help!


Solution

  • Finally cracked it!

    Now, so you don't have to go through the same blind trial & terror experience I endured:

    using Google.GData.Client;
    using Google.GData.YouTube;
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Text.RegularExpressions;
    using System.Threading;
    using System.Windows.Forms;
    using System.Xml;
    ...
    YouTubeService service = new YouTubeService("YouTube API Client", Properties.Settings.Default.YouTubeDeveloperKey);
    const String searchFields = "entry(id,title,link[@rel='alternate'],author(name,yt:userId),media:group(media:category(@label),media:credit,yt:videoid,yt:uploaderId),yt:statistics,yt:rating,gd:rating(@average),gd:comments/gd:feedLink(@countHint))";
    String urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos", (testing ? "stage." : "")); 
    String url = String.Format("{0}?v=2&fields={1}", urlBase, searchFields);
    Debug.Print(url);
    YouTubeQuery searchQuery = new YouTubeQuery(url);
    searchQuery.Query = keyword;
    searchQuery.NumberToRetrieve = Properties.Settings.Default.NumVideosToRetrieve;
    searchQuery.OrderBy = "relevance";
    lblTagStats.Text = "Retrieving Top " + searchQuery.NumberToRetrieve + " Videos...";
    YouTubeFeed searchResults = service.Query(searchQuery);
    DebugSaveToXml(searchResults, "searchResults");
    foreach (YouTubeEntry entry in searchResults.Entries)
    {
        titles.Add(entry.Title.Text);
        if (entry.Statistics != null)
            viewCounts.Add(long.Parse(entry.Statistics.ViewCount));
        if (entry.Comments != null)
            commentCounts.Add(long.Parse(entry.Comments.FeedLink.CountHint.ToString()));
        if (entry.Rating != null)
            ratings.Add(entry.Rating.Average);
        if (entry.YtRating != null)
        {
            likeCounts.Add(long.Parse(entry.YtRating.NumLikes));
            dislikeCounts.Add(long.Parse(entry.YtRating.NumDislikes));
        }
        videoURLs.Add(new Uri(entry.AlternateUri.ToString()));
        uploaders.Add(entry.Authors[0].Name);
        foreach (var category in entry.Media.Categories)
            if (category.Attributes["label"] != null)
                categories.Add(category.Attributes["label"].ToString());
    }
    
    lblTagStats.Text = "Retrieving Subscriber Counts...";
    AtomFeed batchRequest = new AtomFeed(searchResults);
    batchRequest.BatchData = new GDataBatchFeedData();
    batchRequest.BatchData.Type = GDataBatchOperationType.query;
    foreach (YouTubeEntry entry in searchResults.Entries)
    {
        AtomEntry batchEntry = searchResults.CreateFeedEntry();
        String uploaderId = entry.Media.ChildNodes.Find(x => x.Name == "yt:uploaderId").InnerText;
        batchEntry.Id = new AtomId("tag:youtube.com,2008:user:" + uploaderId);  //not returned in search, so reconstruct it
        batchEntry.BatchData = new GDataBatchEntryData();
        urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/users", (testing ? "stage." : ""));
        url = String.Format("{0}/{1}?v=2", urlBase, entry.Media.Credit.Value);
        Debug.Print(url);
        batchEntry.EditUri = url;
        batchRequest.Entries.Add(batchEntry);
    }
    DebugSaveToXml(batchRequest, "batchRequest_Profiles");
    
    const String profileFields = ""; // "&fields=id,yt:username,yt:statistics(@subscriberCount)"; // YouTube API bug: cant handle colons in batch request fields
    urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/users/batch", (testing ? "stage." : ""));
    url = String.Format("{0}?v=2{1}", urlBase, profileFields);
    Debug.Print(url);
    YouTubeFeed profilesFeed = (YouTubeFeed)service.Batch(batchRequest, new Uri(url));
    DebugSaveToXml(profilesFeed,"profilesFeed");
    foreach (ProfileEntry entry in profilesFeed.Entries)
        if (entry.BatchData.Status.Code == 200)
            subscriberCounts.Add(long.Parse(entry.Statistics.SubscriberCount));
    
    
    
    lblTagStats.Text = "Retrieving Full Descriptions...";
    batchRequest = new AtomFeed(searchResults);
    batchRequest.BatchData = new GDataBatchFeedData();
    batchRequest.BatchData.Type = GDataBatchOperationType.query;
    foreach (YouTubeEntry entry in searchResults.Entries)
    {
        AtomEntry batchEntry = searchResults.CreateFeedEntry();
        batchEntry.Id = entry.Id;
        batchEntry.BatchData = new GDataBatchEntryData();
        urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos", (testing ? "stage." : ""));
        url = String.Format("{0}/{1}?v=2", urlBase, entry.VideoId);
        Debug.Print(url);
        batchEntry.EditUri = url;
        batchRequest.Entries.Add(batchEntry);
    }
    DebugSaveToXml(batchRequest, "batchRequest_Descriptions");
    
    const String descriptionFields = ""; // "&fields=media:group/media:description"; // YouTube API bug: cant handle colons in batch request fields
    urlBase = String.Format("https://{0}gdata.youtube.com/feeds/api/videos/batch", (testing ? "stage.":""));
    url = String.Format("{0}?v=2{1}", urlBase, descriptionFields);
    Debug.Print(url);
    YouTubeFeed descriptionsFeed = (YouTubeFeed)service.Batch(batchRequest, new Uri(url));
    DebugSaveToXml(descriptionsFeed,"descriptionsFeed");
    
    foreach (YouTubeEntry entry in descriptionsFeed.Entries)
        if (entry.BatchData.Status.Code == 200)
            descriptions.Add(entry.Media.Description.Value);
    

    And FYI, this posts the following batch request XML for the profiles, which differs considerably from other samples, but still works like a charm:

    <feed xmlns="http://www.w3.org/2005/Atom" xmlns:batch="http://schemas.google.com/gdata/batch">
      <link rel="http://schemas.google.com/g/2005#batch" type="application/atom+xml" />
      <link rel="http://schemas.google.com/g/2005#post" type="application/atom+xml" />
      <link rel="self" type="application/atom+xml" />
      <link rel="http://schemas.google.com/g/2005#feed" type="application/atom+xml" />
      <batch:operation type="query" />
      <entry xmlns:gd="http://schemas.google.com/g/2005">
        <id>tag:youtube.com,2008:user:UC7UgD2HIyslGofSyX811fsQ</id>
        <link href="https://stage.gdata.youtube.com/feeds/api/users/inspiredtennis?v=2" rel="edit" type="application/atom+xml" />
      </entry>
      <entry xmlns:gd="http://schemas.google.com/g/2005">
        <id>tag:youtube.com,2008:user:UClXdxsQW4AqykIq2Fu4YXvQ</id>
        <link href="https://stage.gdata.youtube.com/feeds/api/users/azishertanto?v=2" rel="edit" type="application/atom+xml" />
      </entry>
      <entry xmlns:gd="http://schemas.google.com/g/2005">
        <id>tag:youtube.com,2008:user:UC1Diq4duvGuTUaGxk61-LOQ</id>
        <link href="https://stage.gdata.youtube.com/feeds/api/users/atamaiidotcom?v=2" rel="edit" type="application/atom+xml" />
      </entry>
      ...
    </feed>