トップ «前の日記(2014-12-31) 最新 次の日記(2015-01-16)» 編集

Cocoa練習帳

iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど

2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|

2015-01-04 [iOS]動画を90度回転させる(その二)

保存が失敗する原因がわかった。AVMutableVideoCompositionLayerInstructionに設定するトラックが間違えていた!

これで安心して新年をむかえられる。

import UIKit
import AVFoundation
import AssetsLibrary
 
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
 
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
 
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
 
    @IBOutlet weak var rotateButton: UIButton!
    
    @IBAction func rotate(sender:AnyObject) {
        println(__FUNCTION__)
        
        if UIImagePickerController.isSourceTypeAvailable(.SavedPhotosAlbum) == false {
            return
        }
        
        let mediaUI = UIImagePickerController()
        mediaUI.sourceType = .SavedPhotosAlbum
        if let mediaTypes = UIImagePickerController.availableMediaTypesForSourceType(.SavedPhotosAlbum) {
            mediaUI.mediaTypes = mediaTypes
            NSLog("%s mediaTypes:%@", __FUNCTION__, mediaTypes)
        }
        
        // mediaUI.mediaTypes = [kUTTypeMovie]
        mediaUI.mediaTypes = ["public.movie"]
        
        mediaUI.allowsEditing = false
        
        mediaUI.delegate = self
        
        self.presentViewController(mediaUI, animated: true, completion: nil)
    }
    
    var mutableComposition: AVMutableComposition?
    
    var mutableVideoComposition: AVMutableVideoComposition?
    
    var exportSession: AVAssetExportSession?
    
    func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [NSObject: AnyObject]) {
        println(__FUNCTION__)
        if info[UIImagePickerControllerMediaURL] != nil {
            /* アセットオブジェクトの作成 */
            let url: NSURL = info[UIImagePickerControllerMediaURL] as NSURL
            var options = [String: Bool]()
            options[AVURLAssetPreferPreciseDurationAndTimingKey] = true
            var asset = AVURLAsset(URL: url, options: options)
            NSLog("%s url:%@", __FUNCTION__, url)
            
            /* アセットから動画/音声トラックを取り出す */
            let assetVideoTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeVideo)[0] as AVAssetTrack
            let assetAudioTrack: AVAssetTrack = asset.tracksWithMediaType(AVMediaTypeAudio)[0] as AVAssetTrack
            
            let insertionPoint: CMTime = kCMTimeZero
            var error: NSError? = nil
            
            /* コンポジションを作成 */
            if mutableComposition == nil {
                mutableComposition = AVMutableComposition()
                
                /* 動画コンポジショントラックの作成 */
                let compositionVideoTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeVideo, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 動画データをコンポジションに追加 */
                error = nil
                compositionVideoTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetVideoTrack.timeRange.duration), ofTrack: assetVideoTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertVideoTack error:%@", __FUNCTION__, error!)
                }
                
                /* 音声コンポジショントラックの作成 */
                let compositionAudioTrack: AVMutableCompositionTrack = mutableComposition!.addMutableTrackWithMediaType(AVMediaTypeAudio, preferredTrackID: CMPersistentTrackID(kCMPersistentTrackID_Invalid))
                
                /* 音声データをコンポジションに追加 */
                error = nil
                compositionAudioTrack.insertTimeRange(CMTimeRangeMake(insertionPoint, assetAudioTrack.timeRange.duration), ofTrack: assetAudioTrack, atTime: kCMTimeZero, error: &error)
                if error != nil {
                    NSLog("%s insertAudioTrach error:%@", __FUNCTION__, error!)
                }
            }
            var compositionVideoTrack: AVMutableCompositionTrack? = nil
            var compositionAudioTrack: AVMutableCompositionTrack? = nil
            for track in mutableComposition!.tracks {
                if track.isKindOfClass(AVMutableCompositionTrack) {
                    var mutableCompositionTrack = track as AVMutableCompositionTrack
                    if track.mediaType == AVMediaTypeVideo {
                        compositionVideoTrack = mutableCompositionTrack
                    }
                    else if track.mediaType == AVMediaTypeAudio {
                        compositionAudioTrack = mutableCompositionTrack
                    }
                }
            }
            
            var instruction: AVMutableVideoCompositionInstruction
            var layerInstruction: AVMutableVideoCompositionLayerInstruction
            
            /* 移動して回転 */
            let t1: CGAffineTransform = CGAffineTransformMakeTranslation(assetVideoTrack.naturalSize.height, 0.0)
            let t2: CGAffineTransform = CGAffineTransformRotate(t1, ((90.0 / 180.0) * 3.14159265358979323846264338327950288))
            
            if mutableVideoComposition == nil {
                /* 動画コンポジションの作成 */
                mutableVideoComposition = AVMutableVideoComposition()
                mutableVideoComposition!.renderSize = CGSizeMake(assetVideoTrack.naturalSize.height, assetVideoTrack.naturalSize.width)
                mutableVideoComposition!.frameDuration = CMTimeMake(1, 30);
                
                /* 動画コンポジション命令 */
                instruction = AVMutableVideoCompositionInstruction()
                instruction.timeRange = CMTimeRangeMake(kCMTimeZero, mutableComposition!.duration);
                layerInstruction = AVMutableVideoCompositionLayerInstruction(assetTrack: compositionVideoTrack);
                layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                NSLog("%s instruction:%@", __FUNCTION__, instruction)
                NSLog("%s layerInstruction:%@", __FUNCTION__, layerInstruction)
            }
            else {
                mutableVideoComposition!.renderSize = CGSizeMake(mutableVideoComposition!.renderSize.height, mutableVideoComposition!.renderSize.width);
                
                /* 動画コンポジション命令の抽出 */
                instruction = mutableVideoComposition!.instructions[0] as AVMutableVideoCompositionInstruction
                layerInstruction = instruction.layerInstructions[0] as AVMutableVideoCompositionLayerInstruction
                
                /* 内容の確認 */
                var existingTransform = CGAffineTransform(a: 0.0, b: 0.0, c: 0.0, d: 0.0, tx: 0.0, ty: 0.0)
                if layerInstruction.getTransformRampForTime(mutableComposition!.duration, startTransform: &existingTransform, endTransform: nil, timeRange: nil) == false {
                    layerInstruction.setTransform(t2, atTime: kCMTimeZero)
                }
                else {
                    /* 原点補償 */
                    let t3: CGAffineTransform = CGAffineTransformMakeTranslation(-1.0 * assetVideoTrack.naturalSize.height / 2.0, 0.0)
                    let newTransform: CGAffineTransform = CGAffineTransformConcat(existingTransform, CGAffineTransformConcat(t2, t3))
                    layerInstruction.setTransform(newTransform, atTime: kCMTimeZero)
                }
            }
            
            /* コンポジションに命令を追加 */
            instruction.layerInstructions = [layerInstruction]
            mutableVideoComposition!.instructions = [instruction]
            
            /* 出力URL */
            var documentsPath: NSString = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
            error = nil
            NSFileManager.defaultManager().createDirectoryAtPath(documentsPath, withIntermediateDirectories: true, attributes: nil, error: &error)
            if error != nil {
                NSLog("%s createDir error:%@", __FUNCTION__, error!)
            }
            let now = NSDate()
            let dateFormatter = NSDateFormatter()
            dateFormatter.locale = NSLocale(localeIdentifier: "ja_JP")
            dateFormatter.dateFormat = "yyyyMMddHHmmss"
            let filename = String(format: "%@.mp4", arguments: [dateFormatter.stringFromDate(now)])
            var exportPath: NSString = documentsPath.stringByAppendingPathComponent(filename)
            error = nil
            //NSFileManager.defaultManager().removeItemAtPath(exportPath, error: &error)
            if error != nil {
                NSLog("%s removeFile error:%@", __FUNCTION__, error!)
            }
            var exportUrl: NSURL = NSURL.fileURLWithPath(exportPath)!
            
            /* セッションを作成し、フォトライブラリに書き出す */
            exportSession = AVAssetExportSession(asset: mutableComposition!.copy() as AVAsset, presetName: AVAssetExportPresetHighestQuality)
            exportSession!.videoComposition = mutableVideoComposition
            exportSession!.outputURL = exportUrl
            exportSession!.outputFileType = AVFileTypeQuickTimeMovie
            
            exportSession!.exportAsynchronouslyWithCompletionHandler({
                () -> Void in
                NSLog("%@", __FUNCTION__)
                switch self.exportSession!.status {
                case AVAssetExportSessionStatus.Completed:
                    NSLog("%@ AVAssetExportSessionStatus.Completed", __FUNCTION__)
                    let assetsLib = ALAssetsLibrary()
                    assetsLib.writeVideoAtPathToSavedPhotosAlbum(exportUrl, completionBlock: {
                        (nsurl, error) -> Void in
                        if error != nil {
                            NSLog("%@ error:%@", __FUNCTION__, error)
                        }
                    })
                case AVAssetExportSessionStatus.Failed:
                    NSLog("%@ AVAssetExportSessionStatus.Failed exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                case AVAssetExportSessionStatus.Cancelled:
                    NSLog("%@ AVAssetExportSessionStatus.Cancelled exporter:%@ error:%@", __FUNCTION__, self.exportSession!, self.exportSession!.error)
                default:
                    NSLog("%@ none exporter:%@", __FUNCTION__, self.exportSession!)
                }
            })
        }
        picker.dismissViewControllerAnimated(true, completion: nil);
    }
 
}

_ ソースコード

GitHubからどうぞ。
https://github.com/murakami/workbook/tree/master/ios/AVEditor - GitHub

_ 【Cocoa練習帳】

http://www.bitz.co.jp/weblog/
http://ameblo.jp/bitz/(ミラー・サイト)

トップ «前の日記(2014-12-31) 最新 次の日記(2015-01-16)» 編集