Search code examples
iosswiftvideocamerasave

Swift: error while saving video data from url


I have the following code for saving a recorded video:

func saveVideo() -> Void {
        guard let url = self.videoUrl else {
            Logger.shared.log(.video, .error, "error video url invalid")
            return
        }
        
        let homeDirectory = URL.init(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
        let fileUrl = homeDirectory.appendingPathComponent(self.adId.toString()).appendingPathComponent("video-clip").appendingPathComponent(UUID.init().uuidString, isDirectory: false).appendingPathExtension("mov")
        
        Logger.shared.log(.video, .debug, "saving video to: \(fileUrl)")
        
        self.savedVideoUrl = fileUrl
        
        do {
            let data = try Data(contentsOf: url)
            try data.write(to: fileUrl, options: .atomicWrite)
        } catch {
            Logger.shared.log(.video, .error, "error saving video: \(error.localizedDescription)")
        }
        
    }

In here self.videoUrl is the url of the recorded video from camera and is initialized from the delegate method: func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])

The problem is that when the saveVideo() method executes, it gives an error:

[878     ] 13:22:16.045 | video | Main thread | error saving video: The folder “4A601A28-C4EB-405E-8110-6D01965D5920.mov” doesn’t exist.

I'm not sure what is wrong with this code. If the file does not exist then how do we have to create the file first and then write the data to it?


Solution

  • Okay, I had a similar task days ago (ie. downloading a video, regardless of the url source (local or remote) and then saving it locally).

    I did an experiment using your function.

    See github repo, with working sample and applied fix to your function https://github.com/glennposadas/AVFoundation-SaveLocal

    What you're doing wrong is the way you provide the URL path for the new file.

    EDIT: I think you need first to create your folder path. My fileUrl works because the path exists.

    This way, it works (.mp4 or .mov, either way):

    let fileUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
                .first!
                .appendingPathComponent("\(UUID.init().uuidString).mp4")
    

    And it has a path (if you're going to print it):

    /Users/myusername/Library/Developer/CoreSimulator/Devices/812428A5-0893-43BE-B343-B23E9F13D4AA/data/Containers/Data/Application/E278E093-BE29-410B-9C5D-810BD0F968F8/Documents/F227C8B5-5D8D-42B0-8205-3ADAD0DD38F5.mp4
    

    Meanwhile, your fileUrl:

        let homeDirectory = URL.init(fileURLWithPath: NSHomeDirectory(), isDirectory: true)
        let fileUrl = homeDirectory
            .appendingPathComponent("someId")
            .appendingPathComponent("video-clip")
            .appendingPathComponent(UUID.init().uuidString, isDirectory: false)
            .appendingPathExtension("mov")
    

    gives the path:

    /Users/myusername/Library/Developer/CoreSimulator/Devices/812428A5-0893-43BE-B343-B23E9F13D4AA/data/Containers/Data/Application/570D3CFA-3C44-41A5-A642-C79E5590D16E/someId/video-clip/5B1C9C98-C9EA-40D2-805C-B326466837E6.mov
    

    EDIT: To fix the issue of missing file in your document every time you build your project, then see the changes in the repo I provided above. I added a fix for retrieving the video file from the document folder.

    So basically how you should retrieve the file is you only save the fileName, because every run of the build produces a different filePath.