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