10

We have gotten reports of issues with recording slow motion videos in our application. We have tested the issue on iPhone X, iPhone 6, and iPhone SE. The 6 and the X both work fine, but the SE fails when attempting to add the recorded video to Photos.


The video file to be added to Photos:

  • h.264 with recommended settings
  • Quicktime (.mov)
  • 120/200/240 FPS
  • No custom metadata
  • AAC audio with recommended settings

Our code adding the video:

PHPhotoLibrary.shared().performChanges {  
    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)  
}  

The returned error doesn't provide much useful information, which appears to be a recurring issue when working with Photos.

Error Domain=NSCocoaErrorDomain Code=-1 "(null)"  

We apply an aspect ratio to the videos using the encoder setting's clean aperture parameters. Apparently, changing the video aspect ratio affects the result (see the list at the bottom).


We have tried:

  • Because the aspect ratio affected the result, we thought the issue might be related to the amount of data to be stored. Reducing the bitrate/file size did not change anything
  • Perhaps something was still using the file? We waited a few seconds before adding the file, but were awarded the same errors
  • Scoured the docs, dev forum, SO, blogs, and general google to no avail

Once again - everything works fine on iPhone X and 6.

The resolution-fps-ratio combinations and their result:

1080p

  • 120@16:9 - Error
  • 120@2.35 - Error
  • 120@1:1 - OK

720p

  • 240@16:9 - Error
  • 240@2.35 - Error
  • 240@1:1 - OK
  • 200@16:9 - Error
  • 200@2.35 - OK
  • 200@1:1 - OK
  • 120@16:9 - OK
  • 120@2.35 - OK
  • 120@1:1 - OK

Have you got any clue what the issue might be?

halfer
  • 19,824
  • 17
  • 99
  • 186
Oyvindkg
  • 111
  • 10
  • Sounds odd enough (and reproducible enough) to report to Apple? – halfer Jan 15 '18 at 22:49
  • I am getting this problem on all high FPS video files exported. Did you ever find a solution for this? – wsidell Mar 06 '18 at 00:08
  • We're still looking into it. If we consider both confirmed and unconfirmed error reports, it looks like the issue is present on SE, 6, 6+, and 6S+. If we also assume that 6S is affected, it occurs on all models older than 7. We're currently trying to figure out what changed between 6 and 7 to locate potential perpetrators (e.g. new wide colour space). – Oyvindkg Mar 15 '18 at 13:38
  • I am seeing this same function fail to store a video if the call to creationRequestForAssetFromVideo prompts an authorization message. – drewster Mar 25 '18 at 23:02
  • For me this issue occurs on an iPhone 5s Simulator. I tried about 10 different solutions so far. Dispatch, video quality export setting, video length, different video contents, permission wrapper around the block. The same video works on other simulators or real devices - but not reliably. Other videos fail on other devices.... Note, my video is produced by AVExportSession so that adds a whole layer of complexity in regards what could be wrong with it. – FranticRock Jan 17 '19 at 19:12
  • I know this is going to sound insane, but is your path convention to the local asset consistent? I know it's ridiculous, but try appending `.mov` to every URL regardless of the container. Code=-1 "(null)" is now gone, with no other changes besides forcing that extension. – Ryan Romanchuk May 13 '19 at 00:55

4 Answers4

2

EDIT #4

Ok this one came back to bite me again, and I probably spent another two hours trying to figure out how it regressed since the code was not touched and previously confirmed working. I initially thought it had to be device or encoding related. I ended up "fixing" it, but it makes zero sense to me. The issue was resolved after I appended .mov to the local file that was being used to store the remote video after downloading. Why appending an arbitrary extension to the file path makes a difference is beyond me.

let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let temporaryFilename = "\(ProcessInfo().globallyUniqueString).mov"
let fileURL = documentsURL.appendingPathComponent(temporaryFilename)
Ryan Romanchuk
  • 10,819
  • 6
  • 37
  • 41
0

I had this issue as well. I solved it by couching the request inside an authorization block. Once the user has responded to the prompt, your performChanges block will be invoked and it should succeed:

PHPhotoLibrary.requestAuthorization { (status) in 
  PHPhotoLibrary.shared().performChanges {  
    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: url)  
  }
}
dbn
  • 458
  • 5
  • 12
0

I have been battling with this obscure error for a while, and just found the solution.

What fixed it was: I had to make sure that the Width x Height of the exported image I was using to compose the video through: AVAssetExportSession, matched the quality setting which was used. My code was resizing the images to 1200 x 1920 (which is an incorrect resolution for my purposes). The video dimensions being produced were 1080 x 1920. So it the AVAssetExportSession was able to export the images with the wrong resolution just fine, but that video would not be savable to the gallery, resulting in this generic error. As soon as I used the correct resolution images (1080w x 1920h), the export using PHPhotoLibrary.shared().performChanges works just fine on the iPhone 5s and other phones.

FranticRock
  • 3,233
  • 1
  • 31
  • 56
0

I have been fighting with this bug recently and I was able to solve it by exporting the file not with presetName AVAssetExportPresetPassthrough but with AVAssetExportPresetHighestQuality.

This way the file will be exported to the highest quality that the current device can handle (without sacrificing much from the original file) so the import to Cameraroll won't fail

private func encodeVideo(at videoURL: URL, completion: ((Swift.Result<URL, Error>) -> Void)?) {

    let avAsset = AVURLAsset(url: videoURL, options: nil)

    // Create Export session BUT WITH AVAssetExportPresetHighestQuality 
    guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetHighestQuality) else {
        completion?(.failure(VideoEncodingError
            .invalidAVAssetSession))
        return
    }

    // Creating temp path to save the converted video
    let documentsDirectory = (FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL)
    let fileName = "\(UUID().uuidString).\(MCConstants.BGDownload.mp4Extension)"
    let filePath = documentsDirectory.appendingPathComponent(fileName)

    exportSession.outputURL = filePath
    exportSession.outputFileType = AVFileType.mp4
    exportSession.shouldOptimizeForNetworkUse = true
    let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
    let range = CMTimeRangeMake(start: start, duration: avAsset.duration)
    exportSession.timeRange = range

    exportSession.exportAsynchronously {
        switch exportSession.status {
        case .failed: completion?(.failure(exportSession.error ?? VideoEncodingError.unknown))
        case .cancelled: completion?(.failure(VideoEncodingError.cancelled))
        case .completed:

            if let _url = exportSession.outputURL {
                completion?(.success(_url))
            } else {
                completion?(.failure(VideoEncodingError.encodedURLUnavailable))
            }

        default: completion?(.failure(VideoEncodingError.unknown))
        }
    }
}
mtet88
  • 524
  • 6
  • 21