2

I'm interested in uploading a video to youtube along with title, description, and keywords. The code below uploads a video to youtube without any properties:

func postVideoToYouTube(token: String, callback: Bool -> Void){

let headers = ["Authorization": "Bearer \(token)"]
let urlYoutube = "https://www.googleapis.com/upload/youtube/v3/videos?part=id"

let path = NSBundle.mainBundle().pathForResource("video", ofType: "mp4")
let videodata: NSData = NSData.dataWithContentsOfMappedFile(path!)! as! NSData
upload(
    .POST,
    urlYoutube,
    headers: headers,
    multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(data: videodata, name: "video", fileName: "video.mp4", mimeType: "application/octet-stream")
    },
    encodingCompletion: { encodingResult in
        switch encodingResult {
        case .Success(let upload, _, _):
            upload.responseJSON { request, response, error in
                print(response)
                callback(true)
            }
        case .Failure(_):
            callback(false)
        }
    })

}

I've been trying to modify urlYoutube to include the necessary snippet information to no avail:

let snippetTitle = "The Best Video Ever"
let snippetDesc = "first video upload with title"
let snippetTags = "best,video,ever,snoopy,monkey,charlie"
let urlYoutube = "https://www.googleapis.com/upload/youtube/v3/videos?part=id&snippet.title=%@&snippet.description=%@&snippet.keywords=%@", snippetTitle, snippetDesc, snippetTags)"

The other approach I tried (thanks to @adjuremods suggestion) was to use Request Body based on the Youtube-API to EDIT a previously uploaded video. So, first, I defined a video resource:

let parms = [
  "kind": "youtube#video",
  "id" : returnedId,
  "snippet.title" : "summer vacation cali",
  "snippet.description" : "had fun in the sun",
  "snippet.tags" : ["surf","fun","sun"],
  "snippet.categoryId" : "1"
]

and sent it as a PUT request like so:

do {
  let parmsJson = try NSJSONSerialization.dataWithJSONObject(parameters, options: .PrettyPrinted)

  let putURL = NSURL(string: "https://www.googleapis.com/upload/youtube/v3/videos")!

  let request = NSMutableURLRequest(URL: putURL)
  request.HTTPMethod = "PUT"
  request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
  request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
  request.HTTPBody = parmsJson

  let task = NSURLSession.sharedSession().dataTaskWithRequest(request){ data, response, error in
    if error != nil{
      print("Error -> \(error)")
      return
    }

    do {
      let result = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? [String:AnyObject]

      print("Result -> \(result)")

    } catch {
      print("Error -> \(error)")
    }
  }

  task.resume()
  //return task


} catch {
  print ("...could not accomplish put request")
}   

Unfortunately, the result I get is always the same, regardless of how I modify the video resource:

Result -> Optional(["error": {
code = 400;
errors =     (
            {
        domain = global;
        message = "Unsupported content with type: application/json; charset=UTF-8";
        reason = badContent;
    }
);
message = "Unsupported content with type: application/json; charset=UTF-8";

Could someone advice where I might be going wrong? I don't have a very clear understanding of how to set these parameters defined by the API:

https://developers.google.com/youtube/v3/docs/videos/insert#parameters

Plutovman
  • 677
  • 5
  • 22
  • 1
    Did you figure this out? Currently dealing with the same issue. My video upload works fine but it never actually sets my snippet title or description. – user3344977 Mar 23 '16 at 19:28
  • No. Still actively, desperately trying. It seems that the JSON "params" data I'm sending is not being deciphered by the youtube server....So I've been trying to encode it differently, but so far nothing... – Plutovman Mar 23 '16 at 20:53
  • Thanks for the update, please do ping me in the comments if you figure this out, and I'll post an answer as well if I do. This is super frustrating. – user3344977 Mar 23 '16 at 21:16
  • Agreed on both accounts...I'm continuing my search. From the variety of posts that I see out there with similar issues, it seems like this is a feature of the api that is not being looked after or has been broken for quite some time. I'm sure you came across [this](http://stackoverflow.com/questions/13274173/upload-video-on-youtube-using-curl-and-api-v3) recipe...Well, it doesn't seem to work for me, but let me know if you have better luck. – Plutovman Mar 23 '16 at 22:25
  • @user3344977: See answer below. Also, I included more info in your similar [post](http://stackoverflow.com/questions/36190397/setting-snippet-data-for-youtube-upload-via-rest-api-using-swift/36207960#36207960) – Plutovman Mar 24 '16 at 19:23
  • where exactly can I find the token? I've been looking in my plist file and only have a client ID – Yu Chen Jul 02 '17 at 22:26

3 Answers3

5

Add a line to the multipartFormData block for the parameter values as follows (place the code before the video item -and- add any additional snippet property values per the implied structure):

multipartFormData.appendBodyPart(data:"{'snippet':{'title' : 'TITLE_TEXT', 'description': 'DESCRIPTION_TEXT'}}".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"snippet", mimeType: "application/json")

the post url should also be changed to part=snippet

https://www.googleapis.com/upload/youtube/v3/videos?part=snippet

i.e.

 .POST,
    "https://www.googleapis.com/upload/youtube/v3/videos?part=snippet",
    headers: headers,
    multipartFormData: { multipartFormData in
        multipartFormData.appendBodyPart(data:"{'snippet':{'title' : 'TITLE_TEXT', 'description': 'DESCRIPTION_TEXT'}}".dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!, name :"snippet", mimeType: "application/json")
        multipartFormData.appendBodyPart(data: videodata, name: "video", fileName: "video.mp4", mimeType: "application/octet-stream")
},
mrkbxt
  • 481
  • 4
  • 6
  • 1
    @mrkbtx: That, my friend, is a work of art. A++! You would not believe how much pain I've gone through researching this....You wouldn't by any chance happen to have similar magic sauce for vimeo uploads? – Plutovman Mar 24 '16 at 21:26
  • @mrkbtx:Please see my [vimeo](http://stackoverflow.com/questions/36210781/restful-mechanism-to-upload-video-and-properties-to-vimeo) question. Thanks you brother! – Plutovman Mar 24 '16 at 22:07
  • @mrkbtx: I wonder if you might have an insight into uploading a file with properties to Google Drive. One would think that it would be a similar process. Here is a [question](http://stackoverflow.com/questions/38450245/googledrive-alamofire-uploading-a-file-with-properties) I posted. Many thanks in advance! – Plutovman Jul 20 '16 at 18:03
  • @mrkbtx: hi.. I am facing the same issue or u can say unable to understand, coz there is no proper documentation, Plz help if u can share a whole working class or demo code. – Deepak Chaudhary Mar 14 '17 at 07:43
0

Setting up Titles, Descriptions, Tags, etc. will require you to use Request Body based on the API. Check out this issue raised here on how to set up a request body in Swift.

let json = [ "title":"ABC" , "dict": mapDict ]
let jsonData = NSJSONSerialization.dataWithJSONObject(json, options: .PrettyPrinted, error: nil)
// insert json data to the request
request.HTTPBody = jsonData

Remember that the Request Body should be a resource representation of Video

Community
  • 1
  • 1
adjuremods
  • 2,938
  • 2
  • 12
  • 17
  • Thanks @adjuremods for some very valuable input. Using this information, what I tackled next was setting title,descriptions,tags, etc to an already uploaded video. – Plutovman Mar 23 '16 at 01:40
  • I updated the question above to include my latest attempt. Youtube is being very finicky about how I'm sending the request, and I'm not sure where I might be going wrong. – Plutovman Mar 23 '16 at 01:57
  • hmmm... it seems like [this](http://stackoverflow.com/questions/32869525/400-bad-request-on-youtube-api-update-using-angularjs) is a very similar issue with javascript – Plutovman Mar 23 '16 at 02:09
0

Oh boy....This turned out to be quite a puzzle. From previous youtube answers, it had been hinted that the only way to upload a video with snippet metadata was through a combination of POST and PUT requests...However, getting those to work was quite the challenge. One very useful thing I learned along the way was that Alamofire returns an object when making a request that can be used to troubleshoot a cURL session:

let putRequest = request(.PUT, "https://www.googleapis.com/youtube/v3/videos?part=snippet&key=\(ios_key)", parameters: dictionaryParameters, encoding: .JSON , headers: headers)
          debugPrint(putRequest)

This returns output like this:

$ curl -i \
-X PUT \
-H "Authorization: Bearer ##########################" \
-H "Content-Type: application/json" \
-H "Accept-Language: en-US;q=1.0" \
-H "Accept-Encoding: gzip;q=1.0, compress;q=0.5" \
-H "User-Agent: testVideoApp/com.thinkforward.testVideoApp (1; OS Version 9.2 (Build 13C75))" \
-d "{\"id\":\"###########\",\"snippet\":{\"title\":\"something in the way\",\"tags\":[\"whisky\",\"tango\",\"fox\"],\"description\":\"is this finally gonna work?\"}}" \

Aha! Now we have a cURL command we can test with independent of Alamofire. Once I got this to work, the rest was a matter of reverse-engineering the command to ensure that I was passing the correct parameters to Alamofire...This is how I found out that Alamofire wants parameters to be passed in a very specific way:

 parameters: <[String : AnyObject]?>

With this intel, I rebuilt my dictionaries as follows:

let dictionarySnippet :Dictionary<String, AnyObject>  = [
  "title" : "something in the way",
  "description" : "is this finally gonna work?",
  "tags" : ["whisky","tango","fox"],
  "categoryId" : "1"
]
let dictionaryParameters :Dictionary<String, AnyObject> = [
            "id" : "\(returnedId)",
            "snippet" : dictionarySnippet,
          ]

Next, I found out that making a PUT request, actually requires a different scope from the one I was using for POST. Since I needed to make both types of requests, I had to update my scope variable as follows:

let scope = "https://www.googleapis.com/auth/youtube+https://www.googleapis.com/auth/youtube.upload"

Similarly, POST and PUT requests need separate urls:

https://www.googleapis.com/upload/youtube/v3/videos?part=snippet for POST
https://www.googleapis.com/youtube/v3/videos?part=snippet&key=\(ios_key) for PUT

With these changes in place, the code posted in my question works like a charm. It's not very elegant, but it gets the job done.

Plutovman
  • 677
  • 5
  • 22