reduce video size in swift/iOS to upload to server

15,522

Solution 1

Try this answer for compress video. According to jojaba's answer:

If you are wanting to compress the video for remote sharing and to keep the original quality for local storage on the iPhone, you should look into AVAssetExportSession or AVAssetWriter.

Compress Video Without Low Quality

This approach is as per Objective-C though.

You should also consider reading on how iOS manages Assets.

Solution 2

//use SDAVAssetExportSession library with sprcifica bitrate as per requirement
// video file size ~10-15 MB apporox

func aVodzLatestVideoCompressor(inputURL: URL, aOutputURL: URL, aStartTime:Float, aEndTime:Float)   {
          
            let startTime = CMTime(seconds: Double(aStartTime), preferredTimescale: 1000)
            let endTime = CMTime(seconds: Double(aEndTime), preferredTimescale: 1000)
            let timeRange = CMTimeRange(start: startTime, end: endTime)
            
            let anAsset = AVURLAsset(url: inputURL, options: nil)
            guard let videoTrack = anAsset.tracks(withMediaType: AVMediaType.video).first else { return }
            var aQuality:Float = 0.0
            var duration = anAsset.duration
            let totalSeconds = Int(CMTimeGetSeconds(duration))
            print("duration -\(duration) - totalSeconds -\(totalSeconds)")
            
            var bitrate = min(aQuality, videoTrack.estimatedDataRate)
            let landscap = self.isLandScapVideo(afileURL: inputURL )
            var originalWidth = videoTrack.naturalSize.width
            var originalHeight  = videoTrack.naturalSize.height
            print("originalWidth -\(originalWidth) originalHeight- \(originalHeight) ")
            while (originalWidth >= 1920 || originalHeight >= 1920) {
                originalWidth = originalWidth / 2
                originalHeight = originalHeight / 2
            }
    
            var setWidth = Int(originalWidth)
            var setlHeight = Int(originalHeight)
            
            if  sizeVideo < 10.0 {
                // COMPRESS_QUALITY_HIGH:
                setWidth = Int(originalWidth)
                setlHeight = Int(originalHeight)
                aQuality = Float(setWidth * setlHeight *  20)
                bitrate = min(aQuality, videoTrack.estimatedDataRate)
            }else if sizeVideo < 20.0 {
                //COMPRESS_QUALITY_MEDIUM:
                if totalSeconds > 35{
                    setWidth = Int(originalWidth /  2.7)
                    setlHeight = Int(originalHeight / 2.7)
                }else if totalSeconds > 25 {
                    setWidth = Int(originalWidth / 2.3)
                    setlHeight = Int(originalHeight / 2.3)
                }else{
                    setWidth = Int(originalWidth / 2.0)
                    setlHeight = Int(originalHeight / 2.0)
                }
                aQuality = Float(setWidth * setlHeight *  10)
                bitrate = min(aQuality, videoTrack.estimatedDataRate)
            }else if sizeVideo < 30.0 {
                //COMPRESS_QUALITY_MEDIUM:
                if totalSeconds > 35{
                    setWidth = Int(originalWidth / 3)
                    setlHeight = Int(originalHeight / 3)
                }else if totalSeconds > 20 {
                    setWidth = Int(originalWidth / 2.5)
                    setlHeight = Int(originalHeight / 2.5)
                }else{
                    setWidth = Int(originalWidth / 2.0)
                    setlHeight = Int(originalHeight / 2.0)
                }
                aQuality = Float(setWidth * setlHeight * 10)
                bitrate = min(aQuality, videoTrack.estimatedDataRate)
            }else{
                if totalSeconds > 35{
                    setWidth = Int(originalWidth / 3.0)
                    setlHeight = Int(originalHeight / 3.0)
                }else if totalSeconds > 25 {
                    setWidth = Int(originalWidth / 2.5)
                    setlHeight = Int(originalHeight / 2.5)
                }else{
                    setWidth = Int(originalWidth / 2.0)
                    setlHeight = Int(originalHeight / 2.0)
                }
                aQuality = Float(setWidth * setlHeight * 10)
                bitrate = min(aQuality, videoTrack.estimatedDataRate)
            }
    
            print("aQuality")
            print(Float(aQuality))
            print("bitrate")
            print(Float(bitrate))
            let encoder = SDAVAssetExportSession(asset: anAsset)
            encoder?.shouldOptimizeForNetworkUse = true
     
            encoder?.timeRange = timeRange
            encoder?.outputFileType = AVFileType.mp4.rawValue
            encoder?.outputURL = aOutputURL
            //960 X 540 , 1280 * 720 , 1920*1080 // size reduce parameter
            encoder?.videoSettings = [
                AVVideoCodecKey: AVVideoCodecType.h264,
                AVVideoWidthKey:  landscap ? NSNumber(value:1280) : NSNumber(value:720) ,
                AVVideoHeightKey:  landscap ? NSNumber(value:720) : NSNumber(value:1280),
                AVVideoCompressionPropertiesKey: [
                    AVVideoAverageBitRateKey: NSNumber(value: bitrate),
                    AVVideoProfileLevelKey: AVVideoProfileLevelH264High40
                ]
            ]
            encoder?.audioSettings = [
                AVFormatIDKey: NSNumber(value: kAudioFormatMPEG4AAC),
                AVNumberOfChannelsKey: NSNumber(value: 2),
                AVSampleRateKey: NSNumber(value: 44100),
                AVEncoderBitRateKey: NSNumber(value: 128000)
            ]
            
            encoder?.exportAsynchronously(completionHandler: {
                if encoder?.status == .completed {
                    print("Video export succeeded")
                    DispatchQueue.main.async {
                        appDelegate.hideLoader()
                        //NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
                        //self.sendCompletion?(UIImage(), aOutputURL)
                        let text = "Original video-  \(inputURL.verboseFileSizeInMB()) \n and Compressed video \(aOutputURL.verboseFileSizeInMB()) "
                        let alertController = UIAlertController.init(title: "Compressed!!", message: text , preferredStyle: .alert)
                        alertController.addAction(UIAlertAction.init(title: "share to server!", style: .default, handler: { (action) in
                            // Completion block
                            NotificationCenter.default.post(name: Notification.Name("getMediaEffect"), object: "3")
                            self.sendCompletion?(UIImage(), aOutputURL)
                        }))
                        alertController.addAction(UIAlertAction.init(title: "Save", style: .default, handler: { (action) in
                            // Completion block
                            DispatchQueue.main.async {
                                appDelegate.hideLoader()
                                if let videoURL = aOutputURL as? URL{
                                    self.shareVideo(aUrl:videoURL )
                                }
                            }
                        }))
                        alertController.addAction(UIAlertAction.init(title: "cancel!", style: .default, handler: { (action) in
                        }))
                        self.present(alertController, animated: true, completion: nil)
                    }
                    
                } else if encoder?.status == .cancelled {
                    print("Video export cancelled")
                    DispatchQueue.main.async {
                        appDelegate.hideLoader()
                        self.view.makeToast("error_something_went_wrong".localized)
                    }
                } else {
                    print("Video export failed with error: \(encoder!.error.localizedDescription) ")
                    DispatchQueue.main.async {
                        appDelegate.hideLoader()
                        self.view.makeToast("error_something_went_wrong".localized)
                    }
                }
            })
        }
    
     func isLandScapVideo(afileURL: URL) -> Bool{
            let resolution = self.resolutionForLocalVideo(url: afileURL)
            guard let width = resolution?.width, let height = resolution?.height else {
                return false
            }
            if abs(width) > abs(height){
                //landscap
                return true
            }else{
                //potrait
                return false
            }
        }
    extension URL {
        func verboseFileSizeInMB() -> Float{
            let p = self.path
            
            let attr = try? FileManager.default.attributesOfItem(atPath: p)
            
            if let attr = attr {
                let fileSize = Float(attr[FileAttributeKey.size] as! UInt64) / (1024.0 * 1024.0)
                print(String(format: "FILE SIZE: %.2f MB", fileSize))
                return fileSize
            } else {
                return Float.zero
            }
        }
    }
    
    
    //Below update if any issue in library at SDAVAssetExportSession library changes below at m file:(changes as per your requirement)
         ——   CGAffineTransform matrix = CGAffineTransformMakeTranslation(transx / xratio, transy / yratio - transform.ty);
    
     ——//fix Orientation - 1
        UIImageOrientation videoAssetOrientation = UIImageOrientationUp;
        BOOL isVideoAssetPortrait = NO;
        CGAffineTransform videoTransform = videoTrack.preferredTransform;
        if (videoTransform.a == 0 && videoTransform.b == 1.0 && videoTransform.c == -1.0 && videoTransform.d == 0) {
            videoAssetOrientation = UIImageOrientationRight;
            isVideoAssetPortrait = YES;
        }
        if (videoTransform.a == 0 && videoTransform.b == -1.0 && videoTransform.c == 1.0 && videoTransform.d == 0) {
            videoAssetOrientation =  UIImageOrientationLeft;
            isVideoAssetPortrait = YES;
        }
        if (videoTransform.a == 1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == 1.0) {
            videoAssetOrientation =  UIImageOrientationUp;
        }
        if (videoTransform.a == -1.0 && videoTransform.b == 0 && videoTransform.c == 0 && videoTransform.d == -1.0) {
            videoAssetOrientation = UIImageOrientationDown;
        }
       // [passThroughLayer setTransform:transform atTime:kCMTimeZero];
        if ((videoAssetOrientation = UIImageOrientationDown) || (videoAssetOrientation = UIImageOrientationLeft)){
            [passThroughLayer setTransform:videoTrack.preferredTransform atTime:kCMTimeZero];
        }else{
            [passThroughLayer setTransform:transform atTime:kCMTimeZero];
        }
Share:
15,522
alionthego
Author by

alionthego

Updated on June 09, 2022

Comments

  • alionthego
    alionthego almost 2 years

    I am picking a video from my swift iOS app using the UIImagePickerController. I am saving this URL and now would like to convert it to data to send to my server for storage using:

    let messageVideoData = NSData(contentsOfURL: chosenVideoURL)
    

    The problem is that the file size is very large. For a 7 second video shot on my iPhone 6s the resolution is 1280, 720, the frame rate is 30 and the file size is over 4 MB. I have noticed the same image sent with whatsapp and other chat apps is reduced to a few hundred KB.

    What is the best method to reduce the file size for external storage? The video is primarily targeted for phones so reducing the resolution to 800 or less is fine.

    I tried setting the UIImagePickerController quality to:

    picker.videoQuality = UIImagePickerControllerQualityType.Type640x480
    

    but this only reduced the file size to 3.5 MB.

    Using:

    picker.videoQuality = UIImagePickerControllerQualityType.TypeLow
    

    reduced the resolution to a value far lower than is desirable.

    Is there another approach I should be taking to reduce my video file size for storing on my server?