[Swift] カメラロールから取得した動画のトリミング

#カメラロールから取得した動画のトリミング方法

調べた記事を見ていると、
UIImagePickerControllerとUIVideoEditorControllerを用いてやる的なことが書いてあったのだけど、実はUIImagePickerControllerだけでできるとのこと、、、

（参考記事が少なすぎる💢）

とりあえずどちらでもできるようにしたので、どっちも書きます。。。
どちらも
iOS13.1.2で動作確認ずみ

# UIImagePickerControllerとUIVideoEditorControllerを用いてカメラロールから取得した動画トリミング
いらない記述があるかもしれない。
参考記事　https://github.com/albertbori/iOS-Sample-Video-Trimmer

``` swift
import UIKit
import Photos
import MobileCoreServices

//省略

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        //動画のとき
        if #available(iOS 11.0, *) {
            if let asset = info[.phAsset] as? PHAsset {

                let options = PHVideoRequestOptions()
                options.isNetworkAccessAllowed = true

                let manager = PHImageManager.default()
                manager.requestAVAsset(forVideo: asset, options: options) {asset, audioMix, info in
                    guard let asset = asset else {
                        print("asset is nil")
                        return
                    }

                    if let assetUrl = asset as? AVURLAsset {
                        let tempFolderUrl = URL(fileURLWithPath: NSTemporaryDirectory()).standardizedFileURL
                        let asset = AVAsset(url: assetUrl.url)
                        let outputUrl = tempFolderUrl.appendingPathComponent(assetUrl.url.lastPathComponent)
                        guard let exporter = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetHighestQuality) else {
                            print("次のビデオをエクスポートできませんでした: \(assetUrl.url)")
                            return
                        }
                        exporter.outputURL = outputUrl
                        exporter.outputFileType = AVFileType.mp4
                        exporter.exportAsynchronously {
                            DispatchQueue.main.async {
                                picker.dismiss(animated: true, completion: nil)
                            }

                            self.showEditor(for: outputUrl)
                        }
                    }
                }
            }
        }
    }

    func mimeTypeForPath(path: String) -> String {
        let url = NSURL(fileURLWithPath: path)
        let pathExtension = url.pathExtension

        if let uti = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, pathExtension! as NSString, nil)?.takeRetainedValue() {
            if let mimetype = UTTypeCopyPreferredTagWithClass(uti, kUTTagClassMIMEType)?.takeRetainedValue() {
                return mimetype as String

            }

        }
        return "application/octet-stream"

    }

    func showEditor(for outputUrl: URL) {
        guard UIVideoEditorController.canEditVideo(atPath: outputUrl.path) else {
            print("ビデオは編集できません： \(outputUrl.path)")
            return

        }
        DispatchQueue.main.async {
            let vc = UIVideoEditorController()
            vc.videoPath = outputUrl.path
            vc.videoMaximumDuration = 15
            vc.videoQuality = UIImagePickerController.QualityType.typeIFrame960x540
            vc.delegate = self
            self.present(vc, animated: true, completion: nil)
        }
    }

    static func deleteAsset(at path: String) {
        do {
            try FileManager.default.removeItem(at: URL(fileURLWithPath: path))
            print("Deleted asset file at: \(path)")
        } catch {
            print("Failed to delete assete file at: \(path).")
            print("\(error)")
        }
    }

    //ビデオのURLからサムネイル画像を作成
    func previewImageFromVideo(_ url:URL) -> UIImage? {
        print("動画からサムネイルを生成(URL)")
        let asset = AVAsset(url: url)
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        imageGenerator.appliesPreferredTrackTransform = true
        var time = asset.duration
        time.value = min(time.value, 2)
        do {
            let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
            return UIImage(cgImage: imageRef)
        } catch {
            print(error) //エラーを黙って捨ててしまってはいけない
            return nil
        }
    }


    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }

}


extension GameEditVC: UIVideoEditorControllerDelegate {

    //動画トリミング成功時
    func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath editedVideoPath: String) {

        //エラーで2回これが行われるので、こういった処理をする
        if editorBool == false {
            editorBool = true
            return
        } else {
            editorBool = false
            print("このパスに保存完了: \(editedVideoPath)")
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
                GameEditVC.deleteAsset(at: editor.videoPath)

            }
            let videoURL = URL(fileURLWithPath: editedVideoPath)
            print(videoURL) //<-危険な強制アンラップは可能な限り避ける
            guard let image =  previewImageFromVideo(videoURL) else {
                print("previewImageFromVideo(\(videoURL)) is nil")
                return
            }
            self.profileImages.append(image)
            DispatchQueue.main.async {
                self.collectionView.reloadData()
            }
            editor.dismiss(animated:true, completion: nil)
        }
    }

    // 動画トリミング失敗時の処理
    private func videoEditorController(editor: UIVideoEditorController, didFailWithError error: NSError) {
        print("an error occurred: \(error.localizedDescription)")
        dismiss(animated:true)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            GameEditVC.deleteAsset(at: editor.videoPath)
        }
    }

    // 動画トリミングキャンセル時の処理
    func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) {
        dismiss(animated:true)
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            GameEditVC.deleteAsset(at: editor.videoPath)
        }
    }

}
```



# UIImagePickerControllerのみでカメラロールから取得した動画トリミング
普通にこちらがおすすめです。

参考記事：
https://qiita.com/k_kuni/items/08e66d4d8074bff2c4a3
https://stackoverflow.com/questions/40354689/swift-how-to-record-video-in-mp4-format-with-uiimagepickercontroller/40354948#40354948

またアンドロイド用に.movではなく.mp4で保存したいので、そちらもやるか、、、
そしてプラスでfirestorageに保存するところまで！！

かなりいろいろ見たけど以下でできたよ、、、（まじで疲れたwww）


完成コードはこれ

```swift


import UIKit
import FirebaseAuth
import Firebase
import FirebaseStorage
import Photos
import MobileCoreServices
import AVFoundation

//撮影完了時-------------------------------

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {


        if let url = info[UIImagePickerController.InfoKey.mediaURL] as? NSURL {
            self.encodeVideo(videoURL: url as URL)
            picker.dismiss(animated: true, completion: nil)
        }

    }

    func encodeVideo(videoURL: URL){
        indicator.startFullIndicator(view: view)
        let avAsset = AVURLAsset(url: videoURL)
        let startDate = Date()
        let exportSession = AVAssetExportSession(asset: avAsset, presetName: AVAssetExportPresetPassthrough)

        let docDir = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
        let myDocPath = NSURL(fileURLWithPath: docDir).appendingPathComponent("temp.mp4")?.absoluteString

        let docDir2 = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] as NSURL

        let filePath = docDir2.appendingPathComponent("rendered-Video.mp4")
        deleteFile(filePath!)

        if FileManager.default.fileExists(atPath: myDocPath!){
            do{
                try FileManager.default.removeItem(atPath: myDocPath!)
            }catch let error{
                print(error)
                indicator.stopFullIndicator(view: view)
            }
        }

        exportSession?.outputURL = filePath
        exportSession?.outputFileType = AVFileType.mp4
        exportSession?.shouldOptimizeForNetworkUse = true

        let start = CMTimeMakeWithSeconds(0.0, preferredTimescale: 0)
        let range = CMTimeRange(start: start, duration: avAsset.duration)
        exportSession?.timeRange = range

        exportSession!.exportAsynchronously{() -> Void in
            switch exportSession!.status{
            case .failed:
                print("\(exportSession!.error!)")
                self.indicator.stopFullIndicator(view: self.view)
            case .cancelled:
                print("Export cancelled")
                self.indicator.stopFullIndicator(view: self.view)
            case .completed:
                let endDate = Date()
                let time = endDate.timeIntervalSince(startDate)
                print(time)
                print("Successful")
                print(exportSession?.outputURL ?? "")
                let profileMovieRef = self.gameUserStorageRef.child("profileImage_\(self.profileImages.count).mp4")
                profileMovieRef.putFile(from: exportSession!.outputURL!, metadata: nil, completion: { (metadata, error) in
                        if error != nil {
                            print("エラー：\(error!)")
                            self.indicator.stopFullIndicator(view: self.view)
                        } else {
                            profileMovieRef.downloadURL { (url, error) in
                                guard let downloadURL = url else {
                                    print("エラー：\(error!)")
                                    self.indicator.stopFullIndicator(view: self.view)
                                    return

                                }
                                self.indicator.stopFullIndicator(view: self.view)
                            }
                        }
                    }
                )
            default:
                break
            }

        }
    }

    func deleteFile(_ filePath:URL) {
        guard FileManager.default.fileExists(atPath: filePath.path) else{
            return
        }
        do {
            try FileManager.default.removeItem(atPath: filePath.path)
        }catch{
            fatalError("Unable to delete file: \(error) : \(#function).")
        }
    }

    //ビデオのURLからサムネイル画像を作成
    func previewImageFromVideo(_ url:URL) -> UIImage? {
        print("動画からサムネイルを生成(URL)")
        let asset = AVAsset(url: url)
        let imageGenerator = AVAssetImageGenerator(asset: asset)
        imageGenerator.appliesPreferredTrackTransform = true
        var time = asset.duration
        time.value = min(time.value, 2)
        do {
            let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
            return UIImage(cgImage: imageRef)
        } catch {
            print(error) //エラーを黙って捨ててしまってはいけない
            return nil
        }
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }


    func showAddAlertController() {
        let alertController = UIAlertController(title: "選択してください", message: "画像・動画を追加します", preferredStyle: .actionSheet)

        let albumButton = UIAlertAction(title: "画像を選択", style: .default) { (action: UIAlertAction!) in
            self.indicator.startFullIndicator(view: self.view)
            let sourceType:UIImagePickerController.SourceType = .photoLibrary

            if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){

                let cameraPicker = UIImagePickerController()
                cameraPicker.sourceType = sourceType
                cameraPicker.mediaTypes = ["public.image"]
                cameraPicker.delegate = self
                self.present(cameraPicker, animated: true, completion: nil)
                self.indicator.stopFullIndicator(view: self.view)
            }
        }

        let movieButton = UIAlertAction(title: "動画を選択", style: .default) { (action: UIAlertAction!) in
            let sourceType:UIImagePickerController.SourceType = .photoLibrary

            if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.photoLibrary){

                let controller = UIImagePickerController()
                controller.sourceType = sourceType
                controller.mediaTypes=[kUTTypeMovie as String] // 動画のみ
                controller.delegate = self
                controller.allowsEditing = true
                controller.videoMaximumDuration = 10 // 10秒で動画を切り取る
                controller.videoQuality = UIImagePickerController.QualityType.typeMedium

                self.present(controller, animated: true, completion: nil)
            }
        }

        let cancelButton = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)

        alertController.addAction(albumButton)
        alertController.addAction(movieButton)
        alertController.addAction(cancelButton)

        //alertControllerを表示させる
        self.present(alertController, animated: true, completion: nil)
    }

```