问题
Still a noob, so bear with me. I am using SWIFT 3 and the V3 YouTube Data API with REST. I can pull a list of my videos so my connection and authorization is working just fine.
I can't seem to figure out how to upload though. I found an old post that was very similar to mine (Setting snippet data for youtube upload via REST API using Swift).
I'm confused where they are getting that token variable from and how they pass it into this function. Also, not sure how to set the upload variable that is right before the post. Any help is appreciated!
func uploadVideo(token: String, callback: @escaping (Bool) -> Void){
let headers = ["Authorization": "Bearer \(token)"]
let path = Bundle.main.path(forResource: "intro", ofType: "mov")
let videodata: NSData = NSData.dataWithContentsOfMappedFile(path!)! as! NSData
upload(
.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: "intro", fileName: "intro.mov", 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)
}
})
}
回答1:
Updated code from https://github.com/mfriedl89/Developer5/blob/master/Conari/Conari/. It's just a poc mostly, refactoring required (e.g: Use Codable
for JSON parsing instead of force-unwrapping). And a proper OAuth2 flow should be implemented using SFSafariViewController
/SFAuthenticationSession
(depending on the targeted iOS version) or Google SignIn
import Foundation
class YoutubeTokenProvider {
// TODO: Store token, evaluate ttl, retry request if needed
static func getAccessToken(callback: @escaping (String?) -> Void){
/* Remark (Security)
Storing the data inside here is not secure, but still better than requesting it from a custom request.
Two possibilities:
1. Storing all data here
2. Storing all data on an external server and only requesting the access token from this server
Option 1 was chosen based on the assumption that decompiling the app should be more difficult than just
monitoring the request and getting the access token (which allows the attacker to do anything with our
YouTube account). The disadvantage is that an attacker gets everything after a successful decompilation
and not only the access token.
*/
let client_secret = "..."
let grant_type = "refresh_token"
let refresh_token = "..."
let client_id = "..."
var request = URLRequest(url: URL(string: "https://accounts.google.com/o/oauth2/token")!)
request.httpMethod = "POST"
let postString = "client_secret=" + client_secret +
"&grant_type=" + grant_type +
"&refresh_token=" + refresh_token +
"&client_id=" + client_id
request.httpBody = postString.data(using: .utf8)
URLSession.shared.dataTask(with: request, completionHandler: {data, response, error in
guard let data = data, error == nil else {
callback(nil)
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
callback(nil)
return
}
do {
let jsonData = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! YouTubeManager.JSON
let accessToken = jsonData["access_token"]! as? String
callback(accessToken)
} catch {
callback(nil)
}
return
}).resume()
}
}
import Foundation
struct YoutubeVideo {
let title, thumbnailUrl, videoId: String
}
import Foundation
import Alamofire
/// This file will act as our YouTube manager.
class YouTubeManager {
typealias JSON = [String:AnyObject]
typealias SearchByTitleCallback = (_ response: [YoutubeVideo], _ success:Bool, _ message:String) -> Void
typealias PostVideoCallback = (String, Bool) -> Void
/** Singletone instance. */
static let sharedManager = YouTubeManager()
let apiKey = "..."
let channelID = "..."
let searchApiUrl = "https://www.googleapis.com/youtube/v3/search"
let identifier = "videoID: "
func parseIdentifier(input: String) -> String? {
let seperator = "videoID: "
if input.contains(self.identifier) {
let videoID = input.components(separatedBy: seperator)
return videoID.last
}
return nil
}
func searchVideoByTitle(title: String, completionHandler: @escaping SearchByTitleCallback) -> Void {
let eTitle = title.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
let urlString = searchApiUrl + "?part=snippet&q=" + eTitle + "&type=video&key=" + apiKey
let targetURL = URL(string: urlString)!
var returnArray = [YoutubeVideo]()
let task = URLSession.shared.dataTask(with: targetURL) { (data, response, error) in
guard error == nil else {
completionHandler(returnArray, false, "error = \(error!)")
return
}
guard let data = data else {
completionHandler(returnArray, false, "error = data is nil")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
completionHandler(returnArray, false, "response = \(response!)")
return
}
do {
let resultsDict = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as! JSON
let items = resultsDict["items"] as! [JSON]
returnArray = items.map { item in
let snippetDict = item["snippet"] as! JSON
let title = snippetDict["title"] as! String
let thumbnail = ((snippetDict["thumbnails"] as! JSON)["default"] as! JSON)["url"] as! String
let videoid = (item["id"] as! JSON)["videoId"] as! String
return YoutubeVideo(title: title, thumbnailUrl: thumbnail, videoId: videoid)
}
completionHandler(returnArray, true, "")
} catch {
completionHandler(returnArray, false, "error serializing JSON: \(error)")
}
}
task.resume()
}
func postVideoToYouTube(uploadUrl: String, videoData: Data, title: String, callback: @escaping PostVideoCallback){
YoutubeTokenProvider.getAccessToken { (accessToken) in
guard let accessToken = accessToken
else { return }
let headers: HTTPHeaders = ["Authorization": "Bearer \(accessToken)"]
Alamofire.upload(
multipartFormData: { multipartFormData in
let metadata = "{'snippet':{'title' : '\(title)', 'description': 'This video was uploaded using Mr Tutor.'}}".data(using: .utf8, allowLossyConversion: false)!
multipartFormData.append(metadata, withName: "snippet", mimeType: "application/json")
multipartFormData.append(videoData, withName: "video", fileName: "sample.mp4", mimeType: "application/octet-stream")
},
to: "https://www.googleapis.com/upload/youtube/v3/videos?part=snippet",
headers: headers,
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { response in
do {
let jsonData = try JSONSerialization.jsonObject(with: response.data!, options: .allowFragments) as! JSON
let videoID = jsonData["id"] as! String
let identifierFinal = self.identifier + videoID
callback(identifierFinal, true)
} catch {
print("error serializing JSON: \(error)")
callback("", false)
}
print("Success")
}
case .failure(_):
print("Failure")
callback("", false)
}
})
}
}
}
来源:https://stackoverflow.com/questions/45800480/uploading-video-to-youtube-using-its-rest-api-without-the-ios-sdk