18

I am creating a app in which i need to record videos and upload it to a server. Now my project has a android version too. To support android version i have to record the videos in mp4 format. I followed this tutorial to set the UIImagePicker media type to movie format imagePicker.mediaTypes = [kUTTypeMovie as String]

The UIImagePickerController is perfect for my requirement and the only thing that i need to change is its saving format to mp4. I tried kUTTypeMPEG4 in mediaTypes but it throws error at the run time with no error description.

This is my video Capture function

func startCameraFromViewController() {

        if UIImagePickerController.isSourceTypeAvailable(.Camera) == false {
            return
        }
        viewBlack.hidden = false
        presentViewController(cameraController, animated: false, completion: nil)

        cameraController.sourceType = .Camera

        cameraController.mediaTypes = [kUTTypeMovie as String]
        //cameraController.mediaTypes = [kUTTypeMPEG4 as String]
        cameraController.cameraCaptureMode = .Video
        cameraController.videoQuality = .TypeMedium
        if(getPurchaseId() as! Int == 0)
        {
            if(txtBenchMark.text?.isEmpty == false)
            {
                cameraController.videoMaximumDuration = NSTimeInterval(300.0)
            }else{
                cameraController.videoMaximumDuration = NSTimeInterval(60.0)
            }
        }else{
            cameraController.videoMaximumDuration = NSTimeInterval(600.0)
        }
        cameraController.allowsEditing = false
    }

I am using Swift 2.2 and Xcode 8 with Use Legacy swift Language version = Yes

Any Alternative Solutions are also appreciated. Thanks in advance.

EDIT: I found out that there is no method to directly record videos in mp4 format in swift. only can be converted to required format from apple's quicktime mov format.

Kautham Krishna
  • 889
  • 1
  • 13
  • 31

6 Answers6

13

Here is some code that you can use to convert the recorded video into MP4:

func encodeVideo(videoURL: NSURL)  {
let avAsset = AVURLAsset(URL: videoURL, options: nil)

var startDate = NSDate()

//Create Export session
exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)

// exportSession = AVAssetExportSession(asset: composition, presetName: mp4Quality)
//Creating temp path to save the converted video


let documentsDirectory = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let myDocumentPath = NSURL(fileURLWithPath: documentsDirectory).URLByAppendingPathComponent("temp.mp4").absoluteString
let url = NSURL(fileURLWithPath: myDocumentPath)

let documentsDirectory2 = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0] as NSURL

let filePath = documentsDirectory2.URLByAppendingPathComponent("rendered-Video.mp4")
deleteFile(filePath)

//Check if the file already exists then remove the previous file
if NSFileManager.defaultManager().fileExistsAtPath(myDocumentPath) {
    do {
        try NSFileManager.defaultManager().removeItemAtPath(myDocumentPath)
    }
    catch let error {
        print(error)
    }
}

 url

exportSession!.outputURL = filePath
exportSession!.outputFileType = AVFileTypeMPEG4
exportSession!.shouldOptimizeForNetworkUse = true
var start = CMTimeMakeWithSeconds(0.0, 0)
var range = CMTimeRangeMake(start, avAsset.duration)
exportSession.timeRange = range

exportSession!.exportAsynchronouslyWithCompletionHandler({() -> Void in
    switch self.exportSession!.status {
    case .Failed:
        print("%@",self.exportSession?.error)
    case .Cancelled:
        print("Export canceled")
    case .Completed:
        //Video conversion finished
        var endDate = NSDate()

        var time = endDate.timeIntervalSinceDate(startDate)
        print(time)
        print("Successful!")
        print(self.exportSession.outputURL)

    default:
        break
    }

})


}

func deleteFile(filePath:NSURL) {
guard NSFileManager.defaultManager().fileExistsAtPath(filePath.path!) else {
    return
}

do {
    try NSFileManager.defaultManager().removeItemAtPath(filePath.path!)
}catch{
    fatalError("Unable to delete file: \(error) : \(__FUNCTION__).")
}
}

Source: https://stackoverflow.com/a/39329155/4786204

Community
  • 1
  • 1
William Taylor
  • 595
  • 9
  • 20
13

I made some modifications to the following 2 answers to make it compatible with Swift 5:
https://stackoverflow.com/a/40354948/2470084
https://stackoverflow.com/a/39329155/2470084

import AVFoundation

func encodeVideo(videoURL: URL){
    let avAsset = AVURLAsset(url: videoURL)
    let startDate = Date()
    let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)
    
    let docDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
    let myDocPath = NSURL(fileURLWithPath: docDir).appendingPathComponent("temp.mp4")?.absoluteString
    
    let docDir2 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL
    
    let filePath = docDir2.appendingPathComponent("rendered-Video.mp4")
    deleteFile(filePath!)
    
    if FileManager.default.fileExists(atPath: myDocPath!){
        do{
            try FileManager.default.removeItem(atPath: myDocPath!)
        }catch let error{
            print(error)
        }
    }
    
    exportSession?.outputURL = filePath
    exportSession?.outputFileType = AVFileType.mp4
    exportSession?.shouldOptimizeForNetworkUse = true
    
    let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
    let range = CMTimeRange(start: start, duration: avAsset.duration)
    exportSession?.timeRange = range
    
    exportSession!.exportAsynchronously{() -> Void in
        switch exportSession!.status{
        case .failed:
            print("\(exportSession!.error!)")
        case .cancelled:
            print("Export cancelled")
        case .completed:
            let endDate = Date()
            let time = endDate.timeIntervalSince(startDate)
            print(time)
            print("Successful")
            print(exportSession?.outputURL ?? "")
        default:
            break
        }
        
    }
}

func deleteFile(_ filePath:URL) {
    guard FileManager.default.fileExists(atPath: filePath.path) else{
        return
    }
    do {
        try FileManager.default.removeItem(atPath: filePath.path)
    }catch{
        fatalError("Unable to delete file: \(error) : \(#function).")
    }
}
Kai-jie Ke
  • 399
  • 3
  • 11
9

A quick swift 4 update to the previous answers:

func encodeVideo(videoUrl: URL, outputUrl: URL? = nil, resultClosure: @escaping (URL?) -> Void ) {

    var finalOutputUrl: URL? = outputUrl

    if finalOutputUrl == nil {
        var url = videoUrl
        url.deletePathExtension()
        url.appendPathExtension(".mp4")
        finalOutputUrl = url
    }

    if FileManager.default.fileExists(atPath: finalOutputUrl!.path) {
        print("Converted file already exists \(finalOutputUrl!.path)")
        resultClosure(finalOutputUrl)
        return
    }

    let asset = AVURLAsset(url: videoUrl)
    if let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetPassthrough) {
        exportSession.outputURL = finalOutputUrl!
        exportSession.outputFileType = AVFileType.mp4
        let start = CMTimeMakeWithSeconds(0.0, 0)
        let range = CMTimeRangeMake(start, asset.duration)
        exportSession.timeRange = range
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.exportAsynchronously() {

            switch exportSession.status {
            case .failed:
                print("Export failed: \(exportSession.error != nil ? exportSession.error!.localizedDescription : "No Error Info")")
            case .cancelled:
                print("Export canceled")
            case .completed:
                resultClosure(finalOutputUrl!)
            default:
                break
            }
        }
    } else {
        resultClosure(nil)
    }
}
Robert Nissl
  • 111
  • 1
  • 2
  • Works GREAT thanks ... but the video can not be played on Chrome with HTML5. Does anyone have a solution ? – Fox5150 Jul 18 '18 at 18:04
  • This code works for smaller videos though when I try to compress a 791mb file I get this error. **videoMentorThreadCreateSampleBuffer signalled err=-12848 (err) (FigSampleGeneratorCreateSampleBufferAtCursor failed) at /BuildRoot/Library/Caches/com.apple.xbs/Sources/CoreMedia/CoreMedia-2501.27.4.3/Prototypes/FormatHandlers/VideoMentor.c:3798**. The error occurs around 78 -88% when compressing. Anyone else come across this error? – uplearnedu.com Oct 06 '19 at 01:31
  • I keep getting failed the operation could not be completed – nivla360 Oct 29 '19 at 14:52
5

Swift 5.2 Update Solution

// Don't forget to import AVKit

func encodeVideo(at videoURL: URL, completionHandler: ((URL?, Error?) -> Void)?)  {
    let avAsset = AVURLAsset(url: videoURL, options: nil)

    let startDate = Date()

    //Create Export session
    guard let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) else {
        completionHandler?(nil, nil)
        return
    }

    //Creating temp path to save the converted video
    let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as URL
    let filePath = documentsDirectory.appendingPathComponent("rendered-Video.mp4")

    //Check if the file already exists then remove the previous file
    if FileManager.default.fileExists(atPath: filePath.path) {
        do {
            try FileManager.default.removeItem(at: filePath)
        } catch {
            completionHandler?(nil, error)
        }
    }

    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(completionHandler: {() -> Void in
        switch exportSession.status {
        case .failed:
            print(exportSession.error ?? "NO ERROR")
            completionHandler?(nil, exportSession.error)
        case .cancelled:
            print("Export canceled")
            completionHandler?(nil, nil)
        case .completed:
            //Video conversion finished
            let endDate = Date()

            let time = endDate.timeIntervalSince(startDate)
            print(time)
            print("Successful!")
            print(exportSession.outputURL ?? "NO OUTPUT URL")
            completionHandler?(exportSession.outputURL, nil)

            default: break
        }

    })
}
Sourabh Sharma
  • 7,426
  • 4
  • 62
  • 75
1

Run on iOS11, we will always received the nil value for the AVAssetExportSession. Do we have any solution for this case?

if let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough) {
    //work on iOS 9 and 10
} else {
    //always on iOS 11
}
Tram Nguyen
  • 347
  • 2
  • 7
1

Minor refactoring of previous examples:

import AVFoundation

extension AVURLAsset {
    func exportVideo(presetName: String = AVAssetExportPresetHighestQuality,
                     outputFileType: AVFileType = .mp4,
                     fileExtension: String = "mp4",
                     then completion: @escaping (URL?) -> Void)
    {
        let filename = url.deletingPathExtension().appendingPathExtension(fileExtension).lastPathComponent
        let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent(filename)

        if let session = AVAssetExportSession(asset: self, presetName: presetName) {
            session.outputURL = outputURL
            session.outputFileType = outputFileType
            let start = CMTimeMakeWithSeconds(0.0, 0)
            let range = CMTimeRangeMake(start, duration)
            session.timeRange = range
            session.shouldOptimizeForNetworkUse = true
            session.exportAsynchronously {
                switch session.status {
                case .completed:
                    completion(outputURL)
                case .cancelled:
                    debugPrint("Video export cancelled.")
                    completion(nil)
                case .failed:
                    let errorMessage = session.error?.localizedDescription ?? "n/a"
                    debugPrint("Video export failed with error: \(errorMessage)")
                    completion(nil)
                default:
                    break
                }
            }
        } else {
            completion(nil)
        }
    }
}

Also: AVAssetExportPresetHighestQuality preset works when video is played on Android / Chrome.

P.S. Be aware that the completion handler of exportVideo method might not be returned on the main thread.

tadija
  • 2,279
  • 20
  • 28