Help us understand the problem. What is going on with this article?

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

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

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

(参考記事が少なすぎる💢)

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

UIImagePickerControllerとUIVideoEditorControllerを用いてカメラロールから取得した動画トリミング

いらない記述があるかもしれない。
参考記事 https://github.com/albertbori/iOS-Sample-Video-Trimmer

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)

完成コードはこれ


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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした