Home > Blockchain >  Split video into chunks of 30 seconds Ios swift 5
Split video into chunks of 30 seconds Ios swift 5

Time:10-02

I've been trying to let user choose from video gallery and split video to chunks of 30 seconds!

When I select a video of 1 minute, it splits to two videos of 30 seconds each, and it works fine!

I tried another example of video 35 seconds, and it splits to two videos of 30 and 4 seconds each

But when I select a video with more than 1 minute, it splits the video to chunks of random

numbers such as 34 seconds or 40, etc.

I don't want that!

I want to split videos to 30 seconds each!

This's my viewController code so far

import UIKit
import AVKit
import MobileCoreServices
import Photos
class ViewController: UIViewController { 
    @IBOutlet weak var videoView: UIImageView!
    @IBOutlet var imageView: UIImageView!
    var player: AVPlayer!
    var avpController = AVPlayerViewController()
    var isVideoGettinGEdited = false
    @IBAction func didTapButton(){     
        let picker = UIImagePickerController()
        picker.delegate = self
        picker.sourceType = .savedPhotosAlbum
        picker.mediaTypes = ["public.movie"]
        picker.allowsEditing = false
        present(picker, animated: true, completion: nil)
    }  
}

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {  
        if let image = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerEditedImage")]as? UIImage{
            imageView.image = image
        } else {
            guard
                let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
                mediaType == (kUTTypeMovie as String),
                let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
            else { return }
            let videoAsset = AVURLAsset(url: url)
            let videoDuration = videoAsset.duration
            let durationTime = ceil( CMTimeGetSeconds(videoDuration))
            var startTime = 0.0
            var endTime = durationTime
            var numberOfBreaks = Int((Double(durationTime)/30.0))
            let isReminderTime = Double(durationTime.truncatingRemainder(dividingBy: 30.0))
            if isReminderTime > 0 {
                numberOfBreaks = numberOfBreaks   1
            }
            if Double(durationTime) <= 30 {
                self.cropVideo(atURL: url, startTime: startTime, endTime: endTime, fileName: "Output.mp4")
            } else {
                endTime = 30
                while numberOfBreaks != 0 {
                    if !isVideoGettinGEdited {
                        print("Start time = \(startTime) and Endtime = \(endTime)")
                        self.cropVideo(atURL: url, startTime: startTime, endTime: endTime, fileName: "Output-\(numberOfBreaks).mp4")
                        numberOfBreaks = numberOfBreaks - 1
                        startTime = endTime
                        let timeLeft = Double(durationTime) - startTime
                        if timeLeft >= 30.0 {
                         endTime = endTime   30.0
                        } else {
                            endTime =  timeLeft
                        }
                    }
                }
            }    
        }
        picker.dismiss(animated: true, completion: nil)
    }
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

extension ViewController {
    func cropVideo(atURL url:URL, startTime:Double, endTime:Double, fileName:String) {
        let asset = AVURLAsset(url: url)
        let exportSession = AVAssetExportSession.init(asset: asset, presetName: AVAssetExportPresetHighestQuality)!
        var outputURL = URL(string:NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.userDomainMask, true).last!)
        let fileManager = FileManager.default
        do {
            try fileManager.createDirectory(at: outputURL!, withIntermediateDirectories: true, attributes: nil)
        } catch {
            print(error)
        }
        outputURL?.appendPathComponent("\(fileName).mp4")
        // Remove existing file
        do {
            try fileManager.removeItem(atPath: outputURL!.absoluteString)
        } catch {
            print(error)
        }
        exportSession.outputURL = URL(fileURLWithPath: outputURL!.absoluteString)
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.outputFileType = AVFileType.mp4
        let start = CMTimeMakeWithSeconds(startTime, preferredTimescale: 600) // you will modify time range here
        let duration = CMTimeMakeWithSeconds(endTime, preferredTimescale: 600)
        let range = CMTimeRangeMake(start: start, duration: duration)
        exportSession.timeRange = range
        exportSession.exportAsynchronously {
            self.isVideoGettinGEdited = false
            switch(exportSession.status) {   
            case .completed:
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: outputURL!.absoluteString))
                }) { completed, error in
                    DispatchQueue.main.async {
                        self.view.isUserInteractionEnabled = true
                        if completed {
                            print("Video has been saved to your photos.")  
                        } else {
                            if error != nil {
                                print("Failed to save video in photos \(error).")
                            }
                        }
                    }
                }
                break
            case .failed:
                print("failed with \(exportSession.error)")
                break
            case .cancelled: break
            default:
                print("default")
                break
            }
        }
    }
    //MARK:- saveVideoFromURL
    func saveVideoFromURL(_ videoURL:String) {
        self.view.isUserInteractionEnabled = false
        DispatchQueue.global(qos: .background).async {
            if let url = URL(string: videoURL),
                let urlData = NSData(contentsOf: url) {
                let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0];
                print("url path component = \(url.lastPathComponent)")
                let filePath="\(documentsPath)/\(url.lastPathComponent)"
                    urlData.write(toFile: filePath, atomically: true)
                    PHPhotoLibrary.shared().performChanges({
                        PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: URL(fileURLWithPath: filePath))
                    }) { completed, error in
                        DispatchQueue.main.async {
                            self.view.isUserInteractionEnabled = true
                            if completed {
                                print("Video has been saved to your photos.")
                            } else {
                                if error != nil {
                                    print("Failed to save video.")
                                }
                            }
                        }
                    }
            }
        }
    }
}

Thanks!

CodePudding user response:

You main issue is when calculating the duration. Btw I would change the preferred scale to 1 as well:

change

let duration = CMTimeMakeWithSeconds(endTime, preferredTimescale: 1)

to

let duration = CMTimeMakeWithSeconds(endTime-startTime, preferredTimescale: 1)

I have also done some other changes to your code as following:


extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        if let image = info[.editedImage] as? UIImage {
            imageView.image = image
        } else {
            guard
                let mediaType = info[.mediaType] as? String,
                mediaType == "public.movie",
                let url = info[.mediaURL] as? URL
            else { return }
            let videoAsset = AVURLAsset(url: url)
            let videoDuration = videoAsset.duration
            let durationTime = ceil(videoDuration.seconds)
            print("durationTime:" , durationTime)
            struct Duration {
                let start: Double
                let end: Double
            }
            let durations: [Duration]
            if durationTime < 30 {
                durations = [Duration(start: 0, end: durationTime)]
            } else {
                durations = (0...Int(durationTime)/30).compactMap {
                    if Double($0*30) == min(Double($0*30) 30, durationTime) {
                        return nil
                    }
                    return Duration(
                        start: Double($0*30),
                        end: min(Double($0*30) 30, durationTime)
                    )
                }
            }
            for index in durations.indices {
                let startTime = durations[index].start
                let endTime = durations[index].end
                print("Start time = \(startTime) and Endtime = \(endTime)")
                saveVideo(
                    at: url,
                    startTime: startTime,
                    endTime: endTime,
                    fileName: "Output-\(index)"
                )
            }
        }
        picker.dismiss(animated: true, completion: nil)
    }
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

extension ViewController {
    func saveVideo(
        at url: URL,
        startTime: Double,
        endTime:Double,
        fileName: String
    ) {
        let asset = AVURLAsset(url: url)
        let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality)!
        let outputURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
            .appendingPathComponent(fileName)
            .appendingPathExtension("mp4")
        // Remove existing file
        if FileManager.default.fileExists(atPath: outputURL.path) {
            do {
                try FileManager.default.removeItem(at: outputURL)
            } catch {
                print(error)
            }
        }
        exportSession.outputURL = outputURL
        exportSession.shouldOptimizeForNetworkUse = true
        exportSession.outputFileType = .mp4
        let start = CMTimeMakeWithSeconds(startTime, preferredTimescale: 1)
        let duration = CMTimeMakeWithSeconds(endTime-startTime, preferredTimescale: 1)
        let range = CMTimeRangeMake(start: start, duration: duration)
        print("Will Render \(fileName) from \(start.seconds) to \(duration)")
        exportSession.timeRange = range
        exportSession.exportAsynchronously {
            print("Did Render \(fileName) from \(start.seconds) to \(duration)")
            self.isVideoGettinGEdited = false
            switch exportSession.status {
            case .completed:
                self.checkDuration(for: fileName, at: outputURL)
                PHPhotoLibrary.shared().performChanges({
                    PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: outputURL)
                }) { completed, error in
                    if let error = error {
                        print("Failed to save video in photos", error)
                        return
                    }
                    DispatchQueue.main.async {
                        self.view.isUserInteractionEnabled = true
                        if completed {
                            print("Video has been saved to your photos.")
                        } else {
                            print("Video saving has NOT been completed")
                        }
                    }
                }
                break
            case .failed:
                print("failed with:", exportSession.error ?? "no error")
                break
            case .cancelled: break
            default: break
            }
        }
    }
}

Sample Project

  • Related