MTTransitions icon indicating copy to clipboard operation
MTTransitions copied to clipboard

Cannot Provide Dynamic duration to each image.

Open DevAK0000001 opened this issue 2 years ago • 1 comments

Hi , i am using this library and its way too good but i am stuck when trying to add dynamic duration to each image in array for example there are 6 images and adding duration to each image like [0.0,4.0,2.0,0.5,5.0,6.0]. it only works fine when i am adding static duration for all images like [2.0], then all images duration will be 2.0.

Pleas help me to provide flexible duration for each image while creating video of images.

Below is the Error:-

Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-16364), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x28023c5a0 {Error Domain=NSOSStatusErrorDomain Code=-16364 ["(null)"}}``](url Screenshot 2022-07-12 at 4 29 55 PM )

DevAK0000001 avatar Jul 12 '22 11:07 DevAK0000001

I think you can create extension of MTMovieMaker and pass an array of transition durations. The count of the array should equal to effects.

There are two duration:

  • frameDuration
  • transitionDuration

Something like following (not tested, do not know if this works)

extension MTMovieMaker {
public func createVideo(with images: [MTIImage],
                            effects: [MTTransition.Effect],
                            frameDurations: [TimeInterval] = [1],
                            transitionDuration: TimeInterval = 0.8,
                            audioURL: URL? = nil,
                            completion: MTMovieMakerCompletion? = nil) throws {
        
        guard images.count >= 2 else {
            throw MTMovieMakerError.imagesMustMoreThanTwo
        }
        guard effects.count == images.count - 1 else {
            throw MTMovieMakerError.imagesAndEffectsDoesNotMatch
        }
        guard frameDurations.count == effects.count else {
            throw MTMovieMakerError.imagesAndEffectsDoesNotMatch
         }
        if FileManager.default.fileExists(atPath: outputURL.path) {
            try FileManager.default.removeItem(at: outputURL)
        }
        
        writer = try AVAssetWriter(outputURL: outputURL, fileType: .mp4)
        let outputSize = images.first!.size
        let videoSettings: [String: Any] = [
            AVVideoCodecKey: AVVideoCodecH264,
            AVVideoWidthKey: outputSize.width,
            AVVideoHeightKey: outputSize.height
        ]
        let writerInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoSettings)
        let attributes = sourceBufferAttributes(outputSize: outputSize)
        let pixelBufferAdaptor = AVAssetWriterInputPixelBufferAdaptor(assetWriterInput: writerInput,
                                                                      sourcePixelBufferAttributes: attributes)
        writer?.add(writerInput)
        
        guard let success = writer?.startWriting(), success == true else {
            fatalError("Can not start writing")
        }
        
        guard let pixelBufferPool = pixelBufferAdaptor.pixelBufferPool else {
            fatalError("AVAssetWriterInputPixelBufferAdaptor pixelBufferPool empty")
        }
        
        self.writer?.startSession(atSourceTime: .zero)
        writerInput.requestMediaDataWhenReady(on: self.writingQueue) {
            var index = 0
            while index < (images.count - 1) {
                let frameDuration = frameDurations[index]
                var presentTime = CMTimeMake(value: Int64(frameDuration * Double(index) * 1000), timescale: 1000)
                let transition = effects[index].transition
                transition.inputImage = images[index]
                transition.destImage = images[index + 1]
                transition.duration = transitionDuration
                
                let frameBeginTime = presentTime
                let frameCount = 29
                for counter in 0 ... frameCount {
                    autoreleasepool {
                        while !writerInput.isReadyForMoreMediaData {
                            Thread.sleep(forTimeInterval: 0.01)
                        }
                        let progress = Float(counter) / Float(frameCount)
                        transition.progress = progress
                        let frameTime = CMTimeMake(value: Int64(transitionDuration * Double(progress) * 1000), timescale: 1000)
                        presentTime = CMTimeAdd(frameBeginTime, frameTime)
                        var pixelBuffer: CVPixelBuffer?
                        CVPixelBufferPoolCreatePixelBuffer(kCFAllocatorDefault, pixelBufferPool, &pixelBuffer)
                        if let buffer = pixelBuffer, let frame = transition.outputImage {
                            try? MTTransition.context?.render(frame, to: buffer)
                            pixelBufferAdaptor.append(buffer, withPresentationTime: presentTime)
                        }
                    }
                }
                index += 1
            }
            writerInput.markAsFinished()
            self.writer?.finishWriting {
                if let audioURL = audioURL, self.writer?.error == nil {
                    do {
                        let audioAsset = AVAsset(url: audioURL)
                        let videoAsset = AVAsset(url: self.outputURL)
                        try self.mixAudio(audioAsset, video: videoAsset, completion: completion)
                    } catch {
                        completion?(.failure(error))
                    }
                } else {
                    DispatchQueue.main.async {
                        if let error = self.writer?.error {
                            completion?(.failure(error))
                        } else {
                            completion?(.success(self.outputURL))
                        }
                    }
                }
            }
        }
    }
}

alexiscn avatar Jul 12 '22 14:07 alexiscn