【问题标题】:The File "xxx" couldn’t be opened because there is no such file from directory无法打开文件“xxx”,因为目录中没有这样的文件
【发布时间】:2020-08-05 21:03:09
【问题描述】:

在我的视频录制应用中,我录制了一段视频并将其保存到照片库中。最终目标是获取最近拍摄的视频并将它们与此合并功能合并。

extension AVMutableComposition {

    func mergeVideo(_ urls: [URL], completion: @escaping (_ url: URL?, _ error: Error?) -> Void) {
        guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
            completion(nil, nil)
            return
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateStyle = .long
        dateFormatter.timeStyle = .short
        let date = dateFormatter.string(from: Date())
        let outputURL = documentDirectory.appendingPathComponent("mergedVideo_\(date).mp4")

        // If there is only one video, we dont to touch it to save export time.

        if let url = urls.first, urls.count == 1 {
            do {
                try FileManager().copyItem(at: url, to: outputURL)
                completion(outputURL, nil)
            } catch let error {
                completion(nil, error)
            }
            return
        }

        let maxRenderSize = CGSize(width: 1280.0, height: 720.0)
        var currentTime = CMTime.zero
        var renderSize = CGSize.zero
        // Create empty Layer Instructions, that we will be passing to Video Composition and finally to Exporter.
        var instructions = [AVMutableVideoCompositionInstruction]()

        urls.enumerated().forEach { index, url in
            let asset = AVAsset(url: url)
            print(asset)
            let assetTrack = asset.tracks.first!

            // Create instruction for a video and append it to array.
            let instruction = AVMutableComposition.instruction(assetTrack, asset: asset, time: currentTime, duration: assetTrack.timeRange.duration, maxRenderSize: maxRenderSize)
            instructions.append(instruction.videoCompositionInstruction)

            // Set render size (orientation) according first videro.
            if index == 0 {
                renderSize = instruction.isPortrait ? CGSize(width: maxRenderSize.height, height: maxRenderSize.width) : CGSize(width: maxRenderSize.width, height: maxRenderSize.height)
            }

            do {
                let timeRange = CMTimeRangeMake(start: .zero, duration: assetTrack.timeRange.duration)
                // Insert video to Mutable Composition at right time.
                try insertTimeRange(timeRange, of: asset, at: currentTime)
                currentTime = CMTimeAdd(currentTime, assetTrack.timeRange.duration)
            } catch let error {
                completion(nil, error)
            }
        }

        // Create Video Composition and pass Layer Instructions to it.
        let videoComposition = AVMutableVideoComposition()
        videoComposition.instructions = instructions
        // Do not forget to set frame duration and render size. It will crash if you dont.
        videoComposition.frameDuration = CMTimeMake(value: 1, timescale: 30)
        videoComposition.renderSize = renderSize

        guard let exporter = AVAssetExportSession(asset: self, presetName: AVAssetExportPresetHighestQuality) else {
            completion(nil, nil)
            return
        }
        exporter.outputURL = outputURL
        exporter.outputFileType = .mp4
        // Pass Video Composition to the Exporter.
        exporter.videoComposition = videoComposition
        exporter.shouldOptimizeForNetworkUse = true

        exporter.exportAsynchronously {
            DispatchQueue.main.async {
                completion(exporter.outputURL, nil)
            }
        }
    }

    static func instruction(_ assetTrack: AVAssetTrack, asset: AVAsset, time: CMTime, duration: CMTime, maxRenderSize: CGSize)
        -> (videoCompositionInstruction: AVMutableVideoCompositionInstruction, isPortrait: Bool) {
            let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: assetTrack)

            // Find out orientation from preferred transform.
            let assetInfo = orientationFromTransform(assetTrack.preferredTransform)

            // Calculate scale ratio according orientation.
            var scaleRatio = maxRenderSize.width / assetTrack.naturalSize.width
            if assetInfo.isPortrait {
                scaleRatio = maxRenderSize.height / assetTrack.naturalSize.height
            }

            // Set correct transform.
            var transform = CGAffineTransform(scaleX: scaleRatio, y: scaleRatio)
            transform = assetTrack.preferredTransform.concatenating(transform)
            layerInstruction.setTransform(transform, at: .zero)

            // Create Composition Instruction and pass Layer Instruction to it.
            let videoCompositionInstruction = AVMutableVideoCompositionInstruction()
            videoCompositionInstruction.timeRange = CMTimeRangeMake(start: time, duration: duration)
            videoCompositionInstruction.layerInstructions = [layerInstruction]

            return (videoCompositionInstruction, assetInfo.isPortrait)
    }

    static func orientationFromTransform(_ transform: CGAffineTransform) -> (orientation: UIImage.Orientation, isPortrait: Bool) {
        var assetOrientation = UIImage.Orientation.up
        var isPortrait = false

        switch [transform.a, transform.b, transform.c, transform.d] {
        case [0.0, 1.0, -1.0, 0.0]:
            assetOrientation = .right
            isPortrait = true

        case [0.0, -1.0, 1.0, 0.0]:
            assetOrientation = .left
            isPortrait = true

        case [1.0, 0.0, 0.0, 1.0]:
            assetOrientation = .up

        case [-1.0, 0.0, 0.0, -1.0]:
            assetOrientation = .down

        default:
            break
        }

        return (assetOrientation, isPortrait)
    }

}

调用此函数后,如果 'urls' 数组只有 1 项,则我收到错误消息“无法打开文件(插入文件名),因为没有这样的文件。”否则,如果数组有超过 1 个项目,则应用程序由于强制展开可选值而崩溃,发现为 nil。这就是我格式化 url 并将它们保存到应用程序目录的方式

func tempURL() -> URL? {
        let directory = NSTemporaryDirectory() as NSString

        if directory != "" {
            let path = directory.appendingPathComponent(NSUUID().uuidString + ".mp4")
            return URL(fileURLWithPath: path)
        }

        return nil
    }

关于问题出在哪里或如何解决此问题的任何想法?

【问题讨论】:

  • 错误在哪里
  • 如果数组大小大于1,那么错误就在let assetTrack = asset.tracks.first!这一行的mergeVideo函数中

标签: swift directory uikit avfoundation video-capture


【解决方案1】:
assetTrack = asset.tracks.first!

.首先!是一个强制解包的可选,所以尝试做类似的事情

assetTrack = asset.tracks.first ?? asset.tracks.someValue 

【讨论】:

  • 对不起,这不起作用。我不确定为上面的常量提供默认值是基本问题,而是我无法正确访问最近记录的文件。我可能可以更好地表达我的问题。但例如,这是我要返回的数组[file:///private/var/mobile/Containers/Data/Application/FD6FFB6E-36A8-49A9-8892-15BEAC0BA817/tmp/846F105D-A2F7-4F0A-A512-643B0407B962.mp4],其中包含指向该文件的 url 引用路径。但是,错误是The file “846F105D-A2F7-4F0A-A512-643B0407B962.mp4” couldn’t be opened because there is no such file
  • 好的,感谢您解决问题,我会尽力解决。
猜你喜欢
  • 1970-01-01
  • 2016-02-18
  • 2017-09-29
  • 2014-08-25
  • 2016-07-15
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 2015-09-08
相关资源
最近更新 更多