12

TLDR: Skip to the updates. I am looking for a way to compress or lower the quality of video output, preferably after not directly after creating, but if that is the only way then so be it

Also if you know of any good cocoa pods which can accomplish this that would be good.

Update 3:

I am looking for a function which can output the compressed URL, and I should be able to control the compression quality...

Update 2:

After trying to make the function work in its current state it doe not work. Yeilding nil. I think as a result of the following:

                let outputURL = urlToCompress
            assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)

I am trying to compress video in swift. So far all solutions to this have been for use during creation. I am wondering if there is a way to compress after creation? only using the video URL?

If not then how can I make a compression function which compresses the video and returns the compressed URL?

Code I have been working with:

func compressVideo(videoURL: URL) -> URL {
        let data = NSData(contentsOf: videoURL as URL)!
        print("File size before compression: \(Double(data.length / 1048576)) mb")
        let compressedURL = NSURL.fileURL(withPath: NSTemporaryDirectory() + NSUUID().uuidString + ".mov")
        compressVideoHelperMethod(inputURL: videoURL , outputURL: compressedURL) { (exportSession) in

        }
        return compressedURL
    }

    func compressVideoHelperMethod(inputURL: URL, outputURL: URL, handler:@escaping (_ exportSession: AVAssetExportSession?)-> Void) {
        let urlAsset = AVURLAsset(url: inputURL, options: nil)
        guard let exportSession = AVAssetExportSession(asset: urlAsset, presetName: AVAssetExportPresetMediumQuality) else {
            handler(nil)

            return
        }

        exportSession.outputURL = outputURL
        exportSession.outputFileType = AVFileType.mov
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.exportAsynchronously { () -> Void in
            handler(exportSession)
        }
    }

Update:

So I have found the code below. I am yet to test it, but I don't know how I can make it so that I choose the quality of the compression:

var assetWriter:AVAssetWriter?
var assetReader:AVAssetReader?
let bitrate:NSNumber = NSNumber(value:250000)

func compressFile(urlToCompress: URL, outputURL: URL, completion:@escaping (URL)->Void){
    //video file to make the asset

    var audioFinished = false
    var videoFinished = false

    let asset = AVAsset(url: urlToCompress);

    let duration = asset.duration
    let durationTime = CMTimeGetSeconds(duration)

    print("Video Actual Duration -- \(durationTime)")

    //create asset reader
    do{
        assetReader = try AVAssetReader(asset: asset)
    } catch{
        assetReader = nil
    }

    guard let reader = assetReader else{
        fatalError("Could not initalize asset reader probably failed its try catch")
    }

    let videoTrack = asset.tracks(withMediaType: AVMediaType.video).first!
    let audioTrack = asset.tracks(withMediaType: AVMediaType.audio).first!

    let videoReaderSettings: [String:Any] =  [(kCVPixelBufferPixelFormatTypeKey as String?)!:kCVPixelFormatType_32ARGB ]

    // ADJUST BIT RATE OF VIDEO HERE

    if #available(iOS 11.0, *) {
        let videoSettings:[String:Any] = [
            AVVideoCompressionPropertiesKey: [AVVideoAverageBitRateKey:self.bitrate],
            AVVideoCodecKey: AVVideoCodecType.h264,
            AVVideoHeightKey: videoTrack.naturalSize.height,
            AVVideoWidthKey: videoTrack.naturalSize.width
        ]
    } else {
        // Fallback on earlier versions
    }


    let assetReaderVideoOutput = AVAssetReaderTrackOutput(track: videoTrack, outputSettings: videoReaderSettings)
    let assetReaderAudioOutput = AVAssetReaderTrackOutput(track: audioTrack, outputSettings: nil)


    if reader.canAdd(assetReaderVideoOutput){
        reader.add(assetReaderVideoOutput)
    }else{
        fatalError("Couldn't add video output reader")
    }

    if reader.canAdd(assetReaderAudioOutput){
        reader.add(assetReaderAudioOutput)
    }else{
        fatalError("Couldn't add audio output reader")
    }

    let audioInput = AVAssetWriterInput(mediaType: AVMediaType.audio, outputSettings: nil)
    let videoInput = AVAssetWriterInput(mediaType: AVMediaType.video, outputSettings: videoReaderSettings)
    videoInput.transform = videoTrack.preferredTransform
    //we need to add samples to the video input

    let videoInputQueue = DispatchQueue(label: "videoQueue")
    let audioInputQueue = DispatchQueue(label: "audioQueue")

    do{
        assetWriter = try AVAssetWriter(outputURL: outputURL, fileType: AVFileType.mov)
    }catch{
        assetWriter = nil
    }
    guard let writer = assetWriter else{
        fatalError("assetWriter was nil")
    }

    writer.shouldOptimizeForNetworkUse = true
    writer.add(videoInput)
    writer.add(audioInput)


    writer.startWriting()
    reader.startReading()
    writer.startSession(atSourceTime: CMTime.zero)


    let closeWriter:()->Void = {
        if (audioFinished && videoFinished){
            self.assetWriter?.finishWriting(completionHandler: {
                print("------ Finish Video Compressing")                    
                completion((self.assetWriter?.outputURL)!)
            })

            self.assetReader?.cancelReading()
        }
    }


    audioInput.requestMediaDataWhenReady(on: audioInputQueue) {
        while(audioInput.isReadyForMoreMediaData){
            let sample = assetReaderAudioOutput.copyNextSampleBuffer()
            if (sample != nil){
                audioInput.append(sample!)
            }else{
                audioInput.markAsFinished()
                DispatchQueue.main.async {
                    audioFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }

    videoInput.requestMediaDataWhenReady(on: videoInputQueue) {
        //request data here
        while(videoInput.isReadyForMoreMediaData){
            let sample = assetReaderVideoOutput.copyNextSampleBuffer()
            if (sample != nil){
                let timeStamp = CMSampleBufferGetPresentationTimeStamp(sample!)
                let timeSecond = CMTimeGetSeconds(timeStamp)
                let per = timeSecond / durationTime
                print("Duration --- \(per)")
                videoInput.append(sample!)
            }else{
                videoInput.markAsFinished()
                DispatchQueue.main.async {
                    videoFinished = true
                    closeWriter()
                }
                break;
            }
        }
    }
}

How can I change this to be able to set the quality? I am looking for compression of about 0.6

I am now playing around with the following code, the issue is that it keeps printing the error (does not seem to work):

    func convertVideoToLowQuailty(withInputURL inputURL: URL?, outputURL: URL?, handler: @escaping (AVAssetExportSession?) -> Void) {
    do {
        if let outputURL = outputURL {
            try FileManager.default.removeItem(at: outputURL)
        }
    } catch {
    }
    var asset: AVURLAsset? = nil
    if let inputURL = inputURL {
        asset = AVURLAsset(url: inputURL, options: nil)
    }
    var exportSession: AVAssetExportSession? = nil
    if let asset = asset {
        exportSession = AVAssetExportSession(asset: asset, presetName:AVAssetExportPresetMediumQuality)
    }
    exportSession?.outputURL = outputURL
    exportSession?.outputFileType = .mov
    exportSession?.exportAsynchronously(completionHandler: {
        handler(exportSession)
    })
}

func compressVideo(videoURL: URL) -> URL {
    var outputURL = URL(fileURLWithPath: "/Users/alexramirezblonski/Desktop/output.mov")
    convertVideoToLowQuailty(withInputURL: videoURL, outputURL: outputURL, handler: { exportSession in
        print("fdshljfhdlasjkfdhsfsdljk")
        if exportSession?.status == .completed {
            print("completed\n", exportSession!.outputURL!)
            outputURL = exportSession!.outputURL!
        } else {
            print("error\n")
            outputURL = exportSession!.outputURL!//this needs to be fixed and may cause errors
        }
    })
    return outputURL
}
Radeonx
  • 191
  • 2
  • 14
  • 1
    Perhaps, this article helps - https://blog.testfairy.com/fine-tuned-video-compression-in-ios-swift-4-no-dependencies/ – Nina Jun 05 '19 at 11:23
  • @Nina, I have been trying to make that work, but it does not seem to work, I implemented the class and the usage method but it does not seem able to get passed the //compress part –  Jun 05 '19 at 18:34
  • At the end of the article, the author provided the Github link for the source code. The sample itself is not working the way you want? – Nina Jun 06 '19 at 02:04
  • My only issue with this compression code is 2: 1. It's slow, 2. It flips the video horizontaly @Nina –  Jun 06 '19 at 04:48
  • Try this one https://stackoverflow.com/a/42548218/10150796 – Nikunj Kumbhani Jun 08 '19 at 06:49
  • @NikunjKumbhani I have seen that and the after trying to implement it it keeps failing I need something clear cut that is just a stand along function where I put in an input and get an output –  Jun 08 '19 at 18:29
  • @Outsider you just need to pass your selected or recorded view URL and output URL so that function returns you compared file on your passed output URL. – Nikunj Kumbhani Jun 10 '19 at 04:49
  • have you tried this answer the question is same https://stackoverflow.com/a/40470706/6550949 – NickCoder Jun 11 '19 at 11:34

2 Answers2

5

I have looked at your code. Actually, you are compressing video in medium quality which will be around the same as the original video which you have. So, you have to change presetName in export session initialization as follow:

exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetLowQuality)

You can pass AVAssetExportPresetMediumQuality, So it's could be compressed as you expect.

There is the following list of format available to compress video.

1. Available from iOS 11.0

  • AVAssetExportPresetHEVCHighestQuality
  • AVAssetExportPresetHEVC1920x1080
  • AVAssetExportPresetHEVC3840x2160

2. Available from iOS 4.0

  • AVAssetExportPresetLowQuality
  • AVAssetExportPresetMediumQuality
  • AVAssetExportPresetHighestQuality
  • AVAssetExportPreset640x480
  • AVAssetExportPreset960x540
  • AVAssetExportPreset1280x720
  • AVAssetExportPreset1920x1080
  • AVAssetExportPreset3840x2160
  • AVAssetExportPresetAppleM4A

You can use above all format to compress your video based on your requirements. I hope this will help you.

Radeonx
  • 191
  • 2
  • 14
Mayur Karmur
  • 2,119
  • 14
  • 35
1

If you want more customizable compression filters using AVAssetWriter, consider this library that I wrote. You can compress the video with general quality settings or with more detail filters like bitrate, fps, scale, and more.

FYVideoCompressor().compressVideo(yourVideoPath, quality: .lowQuality) { result in
    switch result {
    case .success(let compressedVideoURL):
    case .failure(let error):
    }
 }

or with more custom configuration:

let config = FYVideoCompressor.CompressionConfig(videoBitrate: 1000_000,
                                                videomaxKeyFrameInterval: 10,
                                                fps: 24,
                                                audioSampleRate: 44100,
                                                audioBitrate: 128_000,
                                                fileType: .mp4,
                                                scale: CGSize(width: 640, height: 480))
FYVideoCompressor().compressVideo(yourVideoPath, config: config) { result in
    switch result {
    case .success(let compressedVideoURL):
    case .failure(let error):
    }
}

More: Batch compression is now supported.

manman
  • 1,268
  • 11
  • 14
  • It's so far the most convenient solution, with some extra functionality, however as for today, it's not working for batch video compressing (inside of a loop), for more info check [here](https://github.com/T2Je/FYVideoCompressor/issues/2). Until it's fixed, I went with solution accepted as answer above, if you are interested, you can read on more detailed native implementation, [here](https://stackoverflow.com/a/42548218/18082439) – peetadelic Jun 06 '22 at 13:01
  • Batch video compressing(inside of a loop) is supported now, please have a try @peetadelic – manman Jun 22 '22 at 10:13