Sdílet prostřednictvím


Slideshare API Upload

I've been experimenting a little with Slideshare as a mechanism to make our slidedecks more broadly available. I spent a fun morning last week emailing 15 slide decks to the Slideshare site. When they didn't appear by the next day I was a little surprised. When they hadn't appeared a week later I thought I'd better check what was going on.

The response I got was "upload by email and by url are disabled at the moment while we fix a few issues in our upload process". Fair enough. It might have been nice to put a message to that effect on the site or disable the "Email Upload" link or perhaps an auto-reply. Any of those might have saved me a few hours of effort. Anyway, what to do?

Slideshare does have an API and they've even built a .NET API Kit. A quick check of the documentation revealed an upload_slideshow resource. Great news! Unfortunately it's not exposed in the .NET API Kit. Booo! So I set about making some changes to the API Kit to allow me to do a bulk upload with titles, descriptions, tags etc (the big advantage over using the Bulk upload facility on the site).

It wasn't as easy as I thought as you have to manually craft the elements of the multipart/form-data request. In the end I came across this article on Code Project which gave me most of what I needed. After that it was a question of sniffing requests with Fiddler comparing the Slideshare Upload sample with my code. In the end I arrived at something that works. It's not terribly pretty but it works.

This is based on the .NET API Kit so download and unpack that first as a start point. In there you'll find sampleprogram.cs. I've added a simple class to represent the Slidedeck metadata and modified the sampleprogram class to drive the uploads from an XML file:

 class Slidedeck
{
    public string Title { get; set; }
    public string Description { get; set; }
    public string Tags { get; set; }
    public string Filepath { get; set; }
}

class sampleprogram
{
    static void Main(string[] args)
    {
        slideshare ss = new slideshare();
        ss.initializeKeys(key, secret);

        XDocument xdoc = XDocument.Load("UploadManifest.xml");

        var query = from x in xdoc.Descendants("Slidedeck")
                    select new Slidedeck
                    {
                        Title = (string)x.Element("Title"),
                        Description = (string)x.Element("Description"),
                        Tags = (string)x.Element("Tags"),
                        Filepath = (string)x.Element("Filepath")
                    };

        foreach (var item in query.ToList())
        {
            using (FileStream fs = File.Open(item.Filepath, FileMode.Open))
            {
                Console.WriteLine("Uploading {0}...", item.Title);
                Console.WriteLine(
                    ss.uploadSlidedeck(username, password,
                    item.Title,
                    item.Description,
                    item.Tags,
                    fs));
                Console.WriteLine("Finished Uploading {0}...", item.Title);
                Console.WriteLine();
            }
        }

        Console.ReadLine();

Then in the slideshare class in slideshare.cs I added a new uploadSlidedeck() method:

 public string uploadSlidedeck(
    string Username, 
    string Password, 
    string Title, 
    string Description, 
    string Tags, 
    FileStream Sourcefile)
{
    Dictionary<string, string> parameters = new Dictionary<string, string>()
    {
        { "api_key", api_key },
        {"sharedsecret", shared_secret },
        {"ts", timestamp},
        {"hash", CalculateSHA1(shared_secret + timestamp, Encoding.UTF8)},
        {"username", Username},
        {"password", Password},
        {"slideshow_title", Title},
        {"slideshow_description", Description},
        {"slideshow_tags", Tags}
    };

    return executePostCommand(
        "https://www.slideshare.net/api/1/upload_slideshow", 
        parameters, 
        Sourcefile);
}

and in turn a modified version of the executePostCommand() method:

 private string executePostCommand(
    string uri,
    Dictionary<string, string> parameters,
    Stream contentStream)
{
    if (api_key == null || shared_secret == null || timestamp == null)
        return "Keys must be initialized first";

    string boundary = "----------" + DateTime.Now.Ticks.ToString("x");

    HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri);

    webRequest.ContentType = "multipart/form-data; boundary=" + boundary;
    webRequest.Method = "POST";
    webRequest.Timeout = 600000;

    Stream os = null;

    try
    {
        StringBuilder sb = new StringBuilder();

        // Add each of the parameters to the request
        foreach (var item in parameters)
        {
            sb.Append("--");
            sb.Append(boundary);
            sb.Append("\r\n");
            sb.Append("Content-Disposition: form-data; name=\"");
            sb.Append(item.Key);
            sb.Append("\"");
            sb.Append("\r\n\r\n");
            sb.Append(item.Value);
            sb.Append("\r\n");
        }

        // Add the file upload itself to the request
        sb.Append("--");
        sb.Append(boundary);
        sb.Append("\r\n");
        sb.Append("Content-Disposition: form-data; name=\"");
        sb.Append("slideshow_srcfile");
        sb.Append("\"; filename=\"");
        sb.Append("Test.ppt");
        sb.Append("\"");
        sb.Append("\r\n");
        sb.Append("Content-Type: ");
        sb.Append("application/vnd.ms-powerpoint");
        sb.Append("\r\n");
        sb.Append("\r\n");

        string postHeader = sb.ToString();
        byte[] postHeaderBytes = Encoding.UTF8.GetBytes(postHeader);
        byte[] boundaryBytes = Encoding.ASCII.GetBytes("\r\n--" + boundary + "--\r\n");

        // request will be postHeader (parameters) + contentStream (file) + boundary
        webRequest.ContentLength = postHeaderBytes.Length +
                                   contentStream.Length +
                                   boundaryBytes.Length;

        os = webRequest.GetRequestStream();

        // Write the parameters to the request stream
        os.Write(postHeaderBytes, 0, postHeaderBytes.Length);


        // Copy from the file to the request stream
        byte[] buffer = new byte[32768];
        while (true)
        {
            int read = contentStream.Read(buffer, 0, buffer.Length);
            if (read <= 0)
                break;

            os.Write(buffer, 0, read);
        }

        // Write the final boundary to the request stream
        os.Write(boundaryBytes, 0, boundaryBytes.Length);

    }
    catch (WebException ex)
    {
        return ex.ToString();
    }

    finally
    {
        if (os != null)
        {
            os.Close();
        }
    }

    try
    {
        WebResponse webResponse = webRequest.GetResponse();
        if (webResponse == null)
        { return null; }
        StreamReader sr = new StreamReader(webResponse.GetResponseStream());
        return sr.ReadToEnd().Trim();
    }
    catch (WebException ex)
    {
        return ex.ToString();
    }
}

And finally you need an XML file which I named UploadManifest.xml which describes the slidedecks you want to upload:

 <?xml version="1.0" encoding="utf-8"?>
<Slidedecks>

  <Slidedeck>
    <Title></Title>
    <Description></Description>
    <Tags></Tags>
    <Filepath></Filepath>
  </Slidedeck>
  
</Slidedecks>

And believe it or not, it looks as though - just as I post this - my emailed slide decks (mailed on 2/12) have started to come through. At exactly the same time as my API uploads (bit of a coincidence). So I now have duplicates of everything. Argh...

Technorati Tags: slideshare,api,multipart/form-data,.net,upload

Comments

  • Anonymous
    December 08, 2008
    PingBack from http://blogs.msdn.com/mikeormond/archive/2008/12/09/uploading-to-slideshare.aspx

  • Anonymous
    December 09, 2008
    Sorry about the duplication. All the emailed slideshows were held in a queue and once we fixed the email upload earlier today, the queue of files sent to us was processed. But great to hear that you've been using the API too! We'd love any feedback on that too.

  • Anonymous
    December 11, 2008
    Thanks Ashwan. It was just a case of Sods Law at work. It'd be nice to see the upload capability incorporated in the .NET API Kit. Mike

  • Anonymous
    December 11, 2008
    Very creative solution to what must have been an annoying problem! Sorry we let you down ... we're making big efforts to beef up our handling up uploads (within a few weeks, they'll be going directly to EC2, which will make it faster as well as more scalable). Anyway, nice writeup.

  • Anonymous
    December 15, 2008
    If only I'd had this a week or so ago ! Nice add-in that supports (amongst other things) uploading, has