Mobile Zone is brought to you in partnership with:

Den is a DZone Zone Leader and has posted 460 posts at DZone. You can read more from them at their website. View Full User Profile

Flickr API for Windows Phone 7 – Part 13 – Uploading photos

10.19.2010
| 12065 views |
  • submit to reddit

I talked a lot about web requests and retrieving a lot of information related to Flickr photos, groups, contacts and what not. There are still two categories from the standard Flickr API that I need to cover to build the fundamental set of implemented methods – people and photos, but I will talk about those in separate articles later on. Now, please let me digress here and talk about photo uploads.

The main photo upload method is not a component of the basic Flickr API framework because it involves binary data, and if you’ve read my previous articles on the subject, you probably noticed that there is no binary data sent across the wire – only plain string parameters that request a JSON-formatted document. Uploading works a bit differently.

The URL where the request should be executed is

http://api.flickr.com/services/upload/

A POST request should be executed against this URL – you can specify a set of parameters, but those are optional. These parameters are:

title - The title of the photo.
description - A description of the photo. May contain some limited HTML.
tags - A space-seperated list of tags to apply to the photo.
is_public, is_friend, is_family - Set to 0 for no, 1 for yes. Specifies who can view the photo.
safety_level - Set to 1 for Safe, 2 for Moderate, or 3 for Restricted.
content_type - Set to 1 for Photo, 2 for Screenshot, or 3 for Other.
hidden - Set to 1 to keep the photo in global search results, 2 to hide from public searches.

Unlike regular Flickr API method calls, uploading doesn’t happen with parameters specified through the URL, but rather directly via a HTTP POST request. And that is yet another difference – I am no longer using WebClient to upload an image – HttpWebRequest is what I am looking at.

This method still requires user authentication, therefore an API key,  auth token and signature should be passed as HTTP POST parameters. I should mention that the returned result will be an XML-formatted document and it cannot be received as JSON – simply because the format parameter is not supported for this specific method.

To start, create a class named Uploader. It won’t be a static class – the user might want to start multiple upload procedures at once. Inside the class I will create the Upload method that has the basic set of parameters defined in the API documentation:

public void Upload(string apiKey, string authToken, string signature, Stream photo, HelperDelegate helperDelegate, string title = "", string description = "", string[] tags = null, SafetyLevel safetyLevel = SafetyLevel.Undefined, ContentType contentType = ContentType.Undefined, Hidden hidden = Hidden.Undefined, IsPublic isPublic = IsPublic.Undefined, IsFriend isFriend = IsFriend.Undefined, IsFamily isFamily = IsFamily.Undefined)
{

}

Now this is one large method declaration, you might think. Indeed, but if you look at it you can see that the majority of parameters are optional. One important thing to mention here is that the photo is passed as a Stream instance. This might be a bit unusual and you might ask – why not a byte array?

At the root of this decision stands the way photos are selected in Windows Phone 7. Since I will only be working with pictures that are stored locally (on the device itself), I will be using PhotoChooserTask to get a picture from an existing album (if you are interested how to select and take new pictures in a Windows Phone 7 application, read this article). The result (chosen photo) is returned as a Stream object, therefore I will be able to pass that object directly to this method call.

The rest are enums:

public enum SafetyLevel
{
    Undefined = 0,
    Safe = 1,
    Moderate = 2,
    Restricted = 3
}

public enum ContentType
{
    Undefined = 0,
    Photo = 1,
    Screenshot = 2,
    Other = 3
}

public enum Hidden
{
    Undefined = 0,
    NotHidden = 1,
    IsHidden = 2
}

public enum IsPublic
{
    Undefined = 2,
    No = 0,
    Yes = 1
}

public enum IsFriend
{
    Undefined = 2,
    No = 0,
    Yes = 1
}

public enum IsFamily
{
    Undefined = 2,
    No = 0,
    Yes = 1
}

Each of these enums specifies an integer value for one of the optional parameters – since that’s what’s required by the call. I could request an integer from the user, but that might eventually create confusion on correct values, so to avoid passing illegal numbers (other than those defined in the documentation), I used enums to restrict the subset.

Inside the Uploader class I need to add three more things:

HelperDelegate helperDelegateInstance;
 
public string UploadResult;
byte[] contents;

You are probably familiar with the HelperDelegate instance – it will allow me to execute a specific action when the photo is uploaded. UploadResult will be the XML-formatted response and contents will be the byte array that will be written to the HTTP POST request stream.

Since I am using a stream for the photo, I will eventually need to convert that to a byte array. For this, I will need a helper method that will do this:

byte[] ConvertToByte(Stream source)
{
    MemoryStream memStream = new MemoryStream();
    byte[] buffer = new byte[1024];
    int bytes;

    while ((bytes = source.Read(buffer,0,buffer.Length)) > 0)
    {
        memStream.Write(buffer, 0, buffer.Length);
    }

    return memStream.ToArray();
}

This method can also be placed inside the Uploader class since I don’t plan on using it anywhere outside of it. MemoryStream has the role of a third-party converter – I am putting the stream in the MemoryStream with chucks defined by the length of the byte array and then I am getting the complete “image” by using the built-in ToArray() method of the MemoryStream object to get the byte array.

Inside the upload method, the first thing you should do is set the helperDelegateInstance to the delegate passed to the method:

helperDelegateInstance = helperDelegate;

Now you need to convert the image stream to a byte array. That’s where I am going to use the helper method I’ve just shown:

byte[] imageBytes = ConvertToByte(photo);

Now we’ll be working on the actual HTTP request. When passing distinct parameters to a HTTP POST request, you have to define a boundary that will define the limits between each separate parameter. This boundary should be a unique identifier used across the entire request for parameter headers and as a part of the footer.

I defined the boundary as a GUID and then the header and the footer:

string boundary = Guid.NewGuid().ToString();
string header = "--" + boundary;
string footer = "--" + boundary + "--";

As you can see, both of them contain the boundary string – however, the header separates parameters and the footer completes the entire parameter set, basically being the end line.

Now I can actually create a HTTP web request to the URL defined by Flickr that accepts an image via a POST request:

HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://api.flickr.com/services/upload/");
request.ContentType = "multipart/form-data; boundary=" + boundary;

Notice that the boundary is defined inside the request when I am specifying the content type. Now, the actual headers are built with the help of a StringBuilder instance:

StringBuilder headers = new StringBuilder();
headers.AppendLine(header);
headers.AppendLine("Content-Disposition: file; name=\"photo\"; filename=\"image1.jpg\"");
headers.AppendLine("Content-Type: image/jpeg");
headers.AppendLine();
headers.AppendLine(Encoding.GetEncoding("iso-8859-1").GetString(imageBytes,0,imageBytes.Length));
headers.AppendLine(header);

headers.AppendLine("Content-Disposition: form-data; name=\"api_key\";");
headers.AppendLine();
headers.AppendLine(apiKey);
headers.AppendLine(header);

headers.AppendLine("Content-Disposition: form-data; name=\"auth_token\";");
headers.AppendLine();
headers.AppendLine(authToken);
headers.AppendLine(header);

headers.AppendLine("Content-Disposition: form-data; name=\"api_sig\";");
headers.AppendLine();
headers.AppendLine(signature);
headers.AppendLine(header);

if (title != "")
{
    headers.AppendLine("Content-Disposition: form-data; name=\"title\";");
    headers.AppendLine();
    headers.AppendLine(title);
    headers.AppendLine(header);
}

if (description != "")
{
    headers.AppendLine("Content-Disposition: form-data; name=\"description\";");
    headers.AppendLine();
    headers.AppendLine(description);
    headers.AppendLine(header);
}

if (tags != null)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"tags\";");
    headers.AppendLine();
    foreach (string tag in tags)
        headers.Append(tag + " ");
    headers.AppendLine(header);
}

if (safetyLevel != SafetyLevel.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"safety_level\";");
    headers.AppendLine();
    headers.Append((int)safetyLevel);
    headers.AppendLine(header);
}

if (contentType != ContentType.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"content_type\";");
    headers.AppendLine();
    headers.Append((int)contentType);
    headers.AppendLine(header);
}

if (hidden != Hidden.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"hidden\";");
    headers.AppendLine();
    headers.Append((int)hidden);
    headers.AppendLine(header);
}

if (isPublic != IsPublic.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"is_public\";");
    headers.AppendLine();
    headers.Append((int)isPublic);
    headers.AppendLine(header);
}

if (isFamily != IsFamily.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"is_family\";");
    headers.AppendLine();
    headers.Append((int)isFamily);
    headers.AppendLine(header);
}

if (isFriend != IsFriend.Undefined)
{
    headers.AppendLine("Content-Disposition: form-data; name=\"is_friend\";");
    headers.AppendLine();
    headers.Append((int)isFriend);
    headers.AppendLine(header);
}

headers.AppendLine(footer);

Since there are some optional parameters, I have to check for their presence before adding the header, hence, the presence of the “if” statements.

Take a look at the parameters I am passing. The image is passed as JPEG – that’s the default format used in Windows Phone 7 to store images, so it is not a good idea to use another format unless you have a custom converter present in your application. The image name here is completely unimportant – you can use whatever filename you want – in the absence of a title parameter, it will be used as the image title.

Notice that when I am creating the tag parameter, I have a “for each” loop inside – following the guidelines, tags passed via a parameter should be space-separated string values, and that’s exactly what’s happening there.

The rest of the parameters are plain strings. The selected enum values (where applied) are converted to an integer that is defined inside the enum and appended to the header, that later on is automatically converted to a string and appended as a parameter value.

Now I can actually convert the headers to a byte array that will be written to the request stream:

contents = Encoding.GetEncoding("iso-8859-1").GetBytes(headers.ToString());

The encoding format defines that the set of characters abides the “8-bit single-byte coded graphic character sets - Part 1: Latin alphabet No. 1” standard.

I should also set the method for the request to POST:

request.Method = "POST";

Time to write to the request stream, but before this I need to actually get it. This is done via an asynchronous operation:

request.BeginGetRequestStream(new AsyncCallback(GetReqCallback), request);

The callback I am referring to here is this:

void GetReqCallback(IAsyncResult asyncResult)
{
    HttpWebRequest req = (HttpWebRequest)asyncResult.AsyncState;
    Stream postStream = req.EndGetRequestStream(asyncResult);
    postStream.Write(contents, 0, contents.Length);
    postStream.Close();
    req.BeginGetResponse(new AsyncCallback(GetRespCallback), req);
}

This is the place where I am writing the byte contents to the stream. Getting the response is also initiated via an asynchronous call, and the callback I am referencing is:

void GetRespCallback(IAsyncResult asyncResult)
{
    HttpWebRequest r = (HttpWebRequest)asyncResult.AsyncState;
    HttpWebResponse response = (HttpWebResponse)r.EndGetResponse(asyncResult);
    using (StreamReader reader = new StreamReader(response.GetResponseStream()))
    {
        UploadResult = reader.ReadToEnd();
    }
    helperDelegateInstance();
}

Notice that I am setting the UploadResult to the response string and only then I am calling the helper delegate – that’s how I can create a secondary action that will be triggered once the response is completely received.

Now let’s try this method by using the following snippet:

PhotoChooserTask task = new PhotoChooserTask();
task.Completed +=new EventHandler<PhotoResult>(task_Completed);
task.Show();

This will trigger the photo chooser.

Once the photo is selected, task_Completed takes control of the situation, and here is what I do there:

param = new Dictionary<string, string>();
param.Add("api_key", apiKey);
param.Add("auth_token", "TOKEN");

Core.Authentication.GetSignature(param, "SECRET", () =>
{
    Core.Uploader uploader = new Core.Uploader();
    uploader.Upload(apiKey, "TOKEN", Core.Authentication.Signature, e.ChosenPhoto,
        () =>
        {
            Debug.WriteLine(uploader.UploadResult);
        }
    );
});

Once this method completes, you should see the XML response in the Output window:

<?xml version="1.0" encoding="utf-8" ?>
<rsp stat="ok">
<photoid>5097295680</photoid>
</rsp>

And of course, the photo will show up in your Flickr photo stream:

You can download the Uploader class here.

Comments

Harvinder Singh replied on Tue, 2010/10/26 - 9:08am

I want to convert byte to image I have converted image to bytes FileStream fileStream = new FileStream("/0057.jpg", FileMode.Open, FileAccess.Read); byte[] buffer = new byte[fileStream.Length]; fileStream.Read(buffer, 0, (int)fileStream.Length); fileStream.Close(); /////////////////////////now convert bytes to image///////////////////// MemoryStream memStream = new MemoryStream(); memStream.Write(buffer, 0, buffer.Length); return Image.FromStream(memStream); /////this does not work How to convert Bytes to Image in Windows Phone 7? Please tell, I need to do this urgently.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.