Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Merging Videos but AVAssetExportSession never completes

Trying to merge some videos together and export them as a single file, everything seems to be correct from looking at tutorials/examples however my AVAssetExportSession never seems to be complete, and my video file is never exported, any help as to a blindingly obvious error I'm missing would be very appreciated.

Below is the function where i merge the videos

note 'videos' in the loop is a member variable var videos = [AVAsset]() which gets populated (and it does i checked) before merge is called.

private func merge()
{
    // Create AVMutableComposition to contain all AVMutableComposition tracks
    var mix_composition = AVMutableComposition()
    var total_time_seconds  = 0.0
    var tracks = [AVCompositionTrack]()

    // Loop over videos and create tracks, keep incrementing total duration
    for video in videos
    {
        // Create the composition track for this video
        let track = mix_composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))

        // Add video duration to total time
        total_time_seconds = total_time_seconds + video.duration.seconds

        // Add track to array of tracks
        tracks.append(track)

        // Add time range to track
        do
        {
            try track.insertTimeRange(CMTimeRangeMake(kCMTimeZero, video.duration), ofTrack: video.tracksWithMediaType(AVMediaTypeVideo)[0], atTime: video.duration)
        }
        catch _
        {
        }
    }

    // Set total time
    let preferred_time_scale: Int32 = 600;
    let total_time = CMTimeMakeWithSeconds(total_time_seconds, preferred_time_scale)

    // Create main instrcution for video composition
    let main_instruction = AVMutableVideoCompositionInstruction()
    main_instruction.timeRange = CMTimeRangeMake(kCMTimeZero, total_time)

    // Create array to hold instructions
    var layer_instructions = [AVVideoCompositionLayerInstruction]()

    // Ensure we have the same number of tracks as videos
    if videos.count == tracks.count
    {
        // Loop number of videos and tracks
        for var index = 0; index < videos.count; ++index
        {
            // Create compositioninstruction for each track
            let instruction = videoCompositionInstructionForTrack(tracks[index], asset: videos[index])

            if(index == 0)
            {
                instruction.setOpacity(0.0, atTime: videos[index].duration)
            }

            // Add instruction to instructions array
            layer_instructions.append(instruction)
        }
    }

    // Set tack instructions to main instruction
    main_instruction.layerInstructions = layer_instructions
    let main_composition = AVMutableVideoComposition()
    main_composition.instructions = [main_instruction]
    main_composition.frameDuration = CMTimeMake(1, 30)
    main_composition.renderSize = CGSize(width: UIScreen.mainScreen().bounds.width, height: UIScreen.mainScreen().bounds.height)

    // Get path for Final video in the current project directory
    let documents_url = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let final_url = documents_url.URLByAppendingPathComponent("TEST.mp4")

    // Create AV Export Session
    let exporter = AVAssetExportSession(asset: mix_composition, presetName: AVAssetExportPresetHighestQuality)
    exporter!.outputURL = final_url
    exporter!.outputFileType = AVFileTypeMPEG4
    exporter!.shouldOptimizeForNetworkUse = true
    exporter!.videoComposition = main_composition

    // Perform the Export
    exporter!.exportAsynchronouslyWithCompletionHandler() {
        dispatch_async(dispatch_get_main_queue(), { () -> Void in
            self.exportDidFinish(exporter!)
        })
    }
}

Below shows the exportDidFinished function which is called when exportAsynchronouslyWithCompletionHandler is called. I get inside this function but nothing ever happens because the session status is never completed.

func exportDidFinish(session: AVAssetExportSession)
{
    if session.status == AVAssetExportSessionStatus.Completed
    {
        let outputURL = session.outputURL
        let library = ALAssetsLibrary()
        if library.videoAtPathIsCompatibleWithSavedPhotosAlbum(outputURL)
        {
            library.writeVideoAtPathToSavedPhotosAlbum(outputURL,
                completionBlock: { (assetURL:NSURL!, error:NSError!) -> Void in
                    if error != nil
                    {
                        let alert = UIAlertView(title: "Error", message: "Video Not Saved", delegate: nil, cancelButtonTitle: "OK")
                        alert.show()

                    }
                    else
                    {
                        let alert = UIAlertView(title: "Success", message: "Video Saved", delegate: nil, cancelButtonTitle: "OK")
                        alert.show()
                    }
            })
        }
    }
}

printed the session status showed that it was 4 which is failed, so i printed session.error and got this, but I'm unsure on what it means, any help would be great

Optional(Error Domain=AVFoundationErrorDomain Code=-11841 "Operation Stopped" UserInfo={NSLocalizedDescription=Operation Stopped, NSLocalizedFailureReason=The video could not be composed.}) 
like image 702
AngryDuck Avatar asked Sep 27 '15 14:09

AngryDuck


1 Answers

If exportDidFinish is called but nothing happens, your session's status is not AVAssetExportSessionStatus.Completed. It may be AVAssetExportSessionStatus.Failed, or some other value. Check those values and if it did fail check the session.error property for more info.

EDIT: If you want one video to play after another, create only one AVMutableCompositionTrack. See below for relevant changes:

    ...
    var mix_composition = AVMutableComposition()

    // Create the composition track for the videos
    let track = mix_composition.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: Int32(kCMPersistentTrackID_Invalid))

    //keep track of total time
    var totalTime = kCMTimeZero

    for video in videos
    {
        // Add time range to track
        do
        {
            let videoTrack = video.tracksWithMediaType(AVMediaTypeVideo)[0]
            let videoDuration = videoTrack.duration
            let timeRange = CMTimeRangeMake(kCMTimeZero,videoDuration)

            try track.insertTimeRange(timeRange, ofTrack: videoTrack, atTime: totalTime)

            totalTime = CMTimeAdd(totalTime,videoDuration)
        }
        catch _
        {
        }
    }

    // Create main instruction for video composition
    let main_instruction = AVMutableVideoCompositionInstruction()
    main_instruction.timeRange = CMTimeRangeMake(kCMTimeZero, totalTime)

    // Create array to hold instructions
    var layer_instructions = [AVVideoCompositionLayerInstruction]()

    // Create layer instruction
    let layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: track)

    // Add it to the array
    layer_instructions.append(layerInstruction)

    ...

You'll also want to adjust your renderSize to something appropriate. The size of your videos may not be the size of your screen.

like image 165
jlw Avatar answered Oct 17 '22 01:10

jlw