Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot Provide Dynamic duration to each image. #24

Open
DevAK0000001 opened this issue Jul 12, 2022 · 1 comment
Open

Cannot Provide Dynamic duration to each image. #24

DevAK0000001 opened this issue Jul 12, 2022 · 1 comment

Comments

@DevAK0000001
Copy link

DevAK0000001 commented Jul 12, 2022

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 DevAK0000001 changed the title Cannot Provide Dynamic duration to per image. Cannot Provide Dynamic duration to each image. Jul 12, 2022
@alexiscn
Copy link
Owner

alexiscn commented Jul 12, 2022

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))
                        }
                    }
                }
            }
        }
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants