【问题标题】:How to convert a local Video to base64 in swift?如何快速将本地视频转换为 base64?
【发布时间】:2022-01-28 04:43:08
【问题描述】:

我目前正在研究一种将视频短视频(10-30 秒)上传到我的数据库的方法,并且正在质疑是否可以将视频从本地画廊转换为 base64,目前我使用如您在此代码中看到的 imagePickerController:

func imagePickerController(_ picker: UIImagePickerController,
                           didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        //Here is save the video URL
        let url = info[.mediaURL] as? URL

        //Here goes the code to convert this video URL to base64...
    
        self.dismiss(animated: true)
}

我还质疑将视频保存到 base64 并将其发送到我的帖子请求正文中是否可行,或者我应该使用其他方式将我的视频上传到服务器中吗? 我愿意接受任何建议,谢谢

【问题讨论】:

    标签: ios swift base64 urlsession video-gallery


    【解决方案1】:
    1. 从文件url获取数据
    2. 从数据中获取 Base64 字符串
    guard let url = info[.mediaURL] as? URL else { return }
    let data = Data(contentsOf: url)
    let base64String = data.base64EncodedString()
    

    上传文件到服务器使用Multipart/form-data,因为Base64有4/3的原始文件大小

    【讨论】:

    • @Rob 1) 是的,是原始大小的 4/3 2) CryptoSwift 是最流行的快速加密算法库,但您可以自己实现 Base64 编码
    • 是的,你是对的。 CryptoSwift 有几个 Base64 扩展,常规任务不需要这些扩展。我更正了答案中的代码,谢谢!
    【解决方案2】:

    我建议不要对视频进行 base64 编码。

    资产已经如此之大,以至于:

    • 您希望防止 base64 使资产变得更大(因此上传速度更慢);和

    • 无论如何,您可能希望避免在任何给定时间将整个资产加载到内存中(即避免在构建此上传请求的过程中使用Data)。标准 base-64 编码 Data 方法实际上要求您在内存中拥有整个资产以执行 base-64 编码,同时您还将在内存中拥有 base-64 字符串。

      例如,对 50 mb 视频使用标准 base-64 编码 Data 方法可能会使内存至少达到 116 mb。

    multipart/form-data 请求是标准方法(允许嵌入二进制有效负载并发送附加字段)。不过要小心,因为大多数你会在网上找到的例子是构建一个Data,然后它会发送它,这可能是不谨慎的。将其写入文件,而无需在任何给定时间尝试将整个资产加载到 RAM 中。然后执行基于文件的上传任务,将其发送到您的服务器。

    例如,如果您想自己创建这个多部分请求,您可以执行以下操作:

    // MARK: - Public interface
    
    extension URLSession {
        /// Delegate-based upload task
    
        @discardableResult
        func uploadTask(
            from url: URL,
            headers: [String: String]? = nil,
            parameters: [String: String]? = nil,
            filePathKey: String,
            fileURLs: [URL]
        ) throws -> URLSessionUploadTask {
            let (request, fileURL) = try uploadRequestFile(from: url, headers: headers, parameters: parameters, filePathKey: filePathKey, fileURLs: fileURLs)
            return uploadTask(with: request, fromFile: fileURL)
        }
    
        /// Completion-handler-based upload task
    
        @discardableResult
        func uploadTask(
            from url: URL,
            headers: [String: String]? = nil,
            parameters: [String: String]? = nil,
            filePathKey: String,
            fileURLs: [URL],
            completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void
        ) -> URLSessionUploadTask? {
            do {
                let (request, fileURL) = try uploadRequestFile(
                    from: url,
                    headers: headers,
                    parameters: parameters,
                    filePathKey: filePathKey,
                    fileURLs: fileURLs
                )
                return uploadTask(with: request, fromFile: fileURL, completionHandler: completionHandler)
            } catch {
                completionHandler(nil, nil, error)
                return nil
            }
        }
    
        /// Async-await-based upload task
    
        @available(iOS 15.0, *)
        func upload(
            from url: URL,
            headers: [String: String]? = nil,
            parameters: [String: String]? = nil,
            filePathKey: String,
            fileURLs: [URL],
            delegate: URLSessionTaskDelegate? = nil
        ) async throws -> (Data, URLResponse) {
            let (request, fileURL) = try uploadRequestFile(
                from: url,
                headers: headers,
                parameters: parameters,
                filePathKey: filePathKey,
                fileURLs: fileURLs
            )
            return try await upload(for: request, fromFile: fileURL, delegate: delegate)
        }
    }
    
    // MARK: - Private implementation
    
    private extension URLSession {
        private func uploadRequestFile(
            from url: URL,
            headers: [String: String]? = nil,
            parameters: [String: String]? = nil,
            filePathKey: String,
            fileURLs: [URL]
        ) throws -> (URLRequest, URL) {
            let boundary = "Boundary-" + UUID().uuidString
    
            var request = URLRequest(url: url)
            request.httpMethod = "POST"
            request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    
            headers?.forEach { (key, value) in
                request.addValue(value, forHTTPHeaderField: key)
            }
    
            let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
                .appendingPathComponent(UUID().uuidString)
    
            guard let stream = OutputStream(url: fileURL, append: false) else {
                throw OutputStreamError.unableToCreateFile
            }
    
            stream.open()
            
            try parameters?.forEach { (key, value) in
                try stream.write("--\(boundary)\r\n")
                try stream.write("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
                try stream.write("\(value)\r\n")
            }
    
            for fileURL in fileURLs {
                let filename = fileURL.lastPathComponent
    
                try stream.write("--\(boundary)\r\n")
                try stream.write("Content-Disposition: form-data; name=\"\(filePathKey)\"; filename=\"\(filename)\"\r\n")
                try stream.write("Content-Type: \(fileURL.mimeType)\r\n\r\n")
                try stream.write(from: fileURL)
                try stream.write("\r\n")
            }
    
            try stream.write("--\(boundary)--\r\n")
    
            stream.close()
    
            return (request, fileURL)
        }
    }
    

    extension URL {
        /// Mime type for the URL
        ///
        /// Requires `import UniformTypeIdentifiers` for iOS 14 solution.
        /// Requires `import MobileCoreServices` for pre-iOS 14 solution
    
        var mimeType: String {
            if #available(iOS 14.0, *) {
                return UTType(filenameExtension: pathExtension)?.preferredMIMEType ?? "application/octet-stream"
            } else {
                guard
                    let identifier = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension as CFString, nil)?.takeRetainedValue(),
                    let mimeType = UTTypeCopyPreferredTagWithClass(identifier, kUTTagClassMIMEType)?.takeRetainedValue() as String?
                else {
                    return "application/octet-stream"
                }
    
                return mimeType
            }
        }
    }
    

    enum OutputStreamError: Error {
        case stringConversionFailure
        case bufferFailure
        case writeFailure
        case unableToCreateFile
        case unableToReadFile
    }
    
    extension OutputStream {
    
        /// Write `String` to `OutputStream`
        ///
        /// - parameter string:                The `String` to write.
        /// - parameter encoding:              The `String.Encoding` to use when writing the string. This will default to `.utf8`.
        /// - parameter allowLossyConversion:  Whether to permit lossy conversion when writing the string. Defaults to `false`.
    
        func write(_ string: String, encoding: String.Encoding = .utf8, allowLossyConversion: Bool = false) throws {
            guard let data = string.data(using: encoding, allowLossyConversion: allowLossyConversion) else {
                throw OutputStreamError.stringConversionFailure
            }
            try write(data)
        }
    
        /// Write `Data` to `OutputStream`
        ///
        /// - parameter data:                  The `Data` to write.
    
        func write(_ data: Data) throws {
            try data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) throws in
                guard var pointer = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
                    throw OutputStreamError.bufferFailure
                }
    
                var bytesRemaining = buffer.count
    
                while bytesRemaining > 0 {
                    let bytesWritten = write(pointer, maxLength: bytesRemaining)
                    if bytesWritten < 0 {
                        throw OutputStreamError.writeFailure
                    }
    
                    bytesRemaining -= bytesWritten
                    pointer += bytesWritten
                }
            }
        }
    
        /// Write `Data` to `OutputStream`
        ///
        /// - parameter data:                  The `Data` to write.
    
        func write(from url: URL) throws {
            guard let input = InputStream(url: url) else {
                throw OutputStreamError.unableToReadFile
            }
    
            input.open()
            defer { input.close() }
    
            let bufferSize = 65_536
    
            var data = Data(repeating: 0, count: bufferSize)
    
            try data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) throws in
                guard let buffer = buffer.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
                    throw OutputStreamError.bufferFailure
                }
    
                while input.hasBytesAvailable {
                    var remainingCount = input.read(buffer, maxLength: bufferSize)
                    if remainingCount < 0 { throw OutputStreamError.unableToReadFile }
    
                    var pointer = buffer
                    while remainingCount > 0 {
                        let countWritten = write(pointer, maxLength: remainingCount)
                        if countWritten < 0 { throw OutputStreamError.writeFailure }
                        remainingCount -= countWritten
                        pointer += countWritten
                    }
                }
            }
        }
    }
    

    然后您可以执行以下操作(在 iOS 15 中):

    extension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            dismiss(animated: true)
        }
    
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let fileURL = info[.mediaURL] as? URL else {
                print("no media URL")
                return
            }
    
            Task {
                do {
                    let (data, response) = try await URLSession.shared.upload(from: url, filePathKey: "file", fileURLs: [fileURL])
                    try? FileManager.default.removeItem(at: fileURL)
    
                    // check `data` and `response` here
                } catch {
                    print(error)
                }
            }
    
            dismiss(animated: true)
        }
    }
    

    或者,在早期的 Swift 版本中:

    extension ViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
        func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
            dismiss(animated: true)
        }
    
        func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
            guard let fileURL = info[.mediaURL] as? URL else {
                print("no media URL")
                return
            }
    
            URLSession.shared.uploadTask(from: url, filePathKey: "file", fileURLs: [fileURL]) { data, _, error in
                try? FileManager.default.removeItem(at: fileURL)
    
                guard let data = data, error == nil else {
                    print(error!)
                    return
                }
    
                // check `data` and `response` here
            }?.resume()
    
            dismiss(animated: true)
        }
    }
    

    在这里,虽然我上传了两个 55mb 的视频,但总分配量从未超过 8mb(其中一些似乎是图像选择器本身缓存的内存)。我重复了两次,以说明每次后续上传的内存都不会继续增长。

    (绿色间隔是在图像/视频选择器中花费的时间以及相关的视频压缩时间。红色间隔是实际上传的时间。这样您可以将过程与内存使用情况相关联。)

    【讨论】:

      猜你喜欢
      • 2017-03-16
      • 2017-05-21
      • 1970-01-01
      • 2014-04-05
      • 2013-06-11
      • 1970-01-01
      • 1970-01-01
      • 2021-03-18
      • 2017-07-05
      相关资源
      最近更新 更多