AVFoundationErrorDomain代码=-11800“操作无法完成”

问题描述 投票:0回答:1

我正在尝试从照片应用程序中修剪视频。下面是我的代码。错误似乎出在

exportSession.exportAsynchronously
,我无法理解为什么

import UIKit
import AVFoundation
import AVKit
import Photos
import PhotosUI

class ViewController: UIViewController, PHPickerViewControllerDelegate {
    
    func loadVideo(url: URL) -> AVAsset? {
        return AVAsset(url: url)
    }
    
    func trimVideo(asset: AVAsset, startTime: CMTime, endTime: CMTime, completion: @escaping (URL?, Error?) -> Void) {
        // Create an export session with the desired output URL and preset.
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
            completion(nil, NSError(domain: "com.yourapp.trimVideo", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to create AVAssetExportSession."]))
            return
        }
        
        let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
        let outputURL = documentsDirectory.appendingPathComponent("trimmedVideo.mp4")
        
        // Remove the existing file if it exists
        try? FileManager.default.removeItem(at: outputURL)
        
        exportSession.outputURL = outputURL
        exportSession.outputFileType = AVFileType.mp4
        exportSession.shouldOptimizeForNetworkUse = true
        
        // Set the time range for trimming
        let timeRange = CMTimeRangeFromTimeToTime(start: startTime, end: endTime)
        exportSession.timeRange = timeRange
        
        // Perform the export
        exportSession.exportAsynchronously {
            switch exportSession.status {
            case .completed:
                completion(exportSession.outputURL, nil)
            case .failed:
                completion(nil, exportSession.error)
            case .cancelled:
                completion(nil, NSError(domain: "com.yourapp.trimVideo", code: 2, userInfo: [NSLocalizedDescriptionKey: "Export cancelled"]))
            default:
                break
            }
        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        debugPrint("1")
        picker.dismiss(animated: true,completion: nil)
        debugPrint("2")
        guard let provider = results.first?.itemProvider else { return }
        debugPrint("3")
        if provider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
            debugPrint("4")
            provider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { [self] (videoURL, error) in
                debugPrint("5")
                cropVideo(sourceURL1: videoURL as! URL, statTime: 10, endTime: 20)
            }
        }
        
        
        
        
    }
    
    
    
    @IBAction func btnClicked(_ sender: Any) {
        
        var config = PHPickerConfiguration(photoLibrary: .shared())
        config.selectionLimit = 1
        config.filter = .videos
        let vc = PHPickerViewController(configuration: config)
        vc.delegate = self
        present(vc, animated: true)
    }
    
    func cropVideo(sourceURL1: URL, statTime:Float, endTime:Float)
    {
        debugPrint("21")
        let manager = FileManager.default
        debugPrint("22")
        guard let documentDirectory = try? manager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) else {return}
        debugPrint("23")
        let mediaType = "mp4"
        debugPrint("24")
        if mediaType == UTType.movie.identifier || mediaType == "mp4" as String {
            debugPrint("25")
            let asset = AVAsset(url: sourceURL1 as URL)
            let length = Float(asset.duration.value) / Float(asset.duration.timescale)
            print("video length: \(length) seconds")
            debugPrint("26")
            let start = statTime
            let end = endTime
            debugPrint("27")
            var outputURL = documentDirectory.appendingPathComponent("output")
            do {
                debugPrint("28")
                try manager.createDirectory(at: outputURL, withIntermediateDirectories: true, attributes: nil)
                outputURL = outputURL.appendingPathComponent("\(UUID().uuidString).\(mediaType)")
            }catch let error {
                debugPrint("29")
                print(error)
            }
            
            //Remove existing file
            _ = try? manager.removeItem(at: outputURL)
            
            debugPrint("30")
            guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {return}
            exportSession.outputURL = outputURL
            exportSession.outputFileType = .mp4
            debugPrint("31")
            let startTime = CMTime(seconds: Double(start ), preferredTimescale: 1000)
            let endTime = CMTime(seconds: Double(end ), preferredTimescale: 1000)
            let timeRange = CMTimeRange(start: startTime, end: endTime)
            debugPrint("32")
            exportSession.timeRange = timeRange
            exportSession.exportAsynchronously{
                switch exportSession.status {
                case .completed:
                    print("exported at \(outputURL)")
                case .failed:
                    print("failed \(String(describing: exportSession.error))")
                    
                case .cancelled:
                    print("cancelled \(String(describing: exportSession.error))")
                    
                default: break
                }
            }
        }
    } 
}

以下是整个错误

Optional(Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x2818544b0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSURL=file:///private/var/mobile/Containers/Shared/AppGroup/35D28117-572C-484E-969C-A9515EF42CDF/File%20Provider%20Storage/photospicker/version=1&uuid=6C291C6C-F254-44F5-83C2-C15980750530&mode=compatible&noloc=0.mp4, NSLocalizedDescription=The operation could not be completed})
ios swift avfoundation
1个回答
0
投票

我可以确认您遇到的

-16979
错误通常与 iOS 中的沙箱导致的文件访问问题有关。

这里的主要问题是,当您从

PHPickerViewController
获取文件 URL 时,您会收到一个安全范围的 URL。为了访问 URL 后面的文件,您需要首先使用 URL 上的
startAccessingSecurityScopedResource()
方法请求许可。如果此方法返回
true
,则意味着您已被授予访问该资源的权限。

您的代码应如下所示:

provider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { [self] (url, error) in
    debugPrint("5")
    
    guard let url = url else {
        print("Could not get video URL: \(error?.localizedDescription ?? "Unknown error")")
        return
    }
    
    let canAccessResource = url.startAccessingSecurityScopedResource()
    
    if canAccessResource {
        defer { url.stopAccessingSecurityScopedResource() }
        cropVideo(sourceURL1: url, statTime: 10, endTime: 20)
    } else {
        print("Could not access security scoped resource")
    }
}

其他一些改进和注意事项:

  1. 切勿强制强制转换 (

    as!
    ) 可选值,因为如果可选值是
    nil
    ,这可能会导致运行时崩溃。相反,请正确处理
    nil
    的情况。在您的代码中,
    (videoURL as! URL)
    是有风险的。通过使用
    guard let url = url else {...}
    ,我们可以确保在继续之前拥有有效的 URL。

  2. 使用完安全范围的 URL 后,请务必调用

    stopAccessingSecurityScopedResource()
    。正确管理这些 URL 以避免文件访问和内存方面的潜在问题非常重要。

  3. 始终检查

    startAccessingSecurityScopedResource()
    的结果。如果是
    false
    ,则意味着您无权访问该资源,您应该在代码中适当处理这种情况。

考虑到这些修改和要点,您的代码现在应该可以正确处理文件访问,并有望解决您遇到的问题。如果您仍然看到相同的错误,请分享更多详细信息,我很乐意为您提供帮助。

© www.soinside.com 2019 - 2024. All rights reserved.