🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Swift 2

Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。

Q&A

解決済

3回答

1944閲覧

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

shima_shima

総合スコア16

iOS

iOSとは、Apple製のスマートフォンであるiPhoneやタブレット端末のiPadに搭載しているオペレーションシステム(OS)です。その他にもiPod touch・Apple TVにも搭載されています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Swift 2

Swift 2は、Apple社が独自に開発を行っている言語「Swift」のアップグレード版です。iOSやOS X、さらにLinuxにも対応可能です。また、throws-catchベースのエラーハンドリングが追加されています。

0グッド

0クリップ

投稿2019/10/19 09:16

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

カメラロールから動画を取得し、トリミング画面で「Save」ボタンを押すとエラーが起きてしまいます。

なぜこのエラーが起きるのか、
どう記述すればいいのかアドバイスいただければ幸いです。

どうぞよろしくお願いいたします!!

コード

import UIKit import FirebaseAuth import Firebase import FirebaseStorage import WARangeSlider import Photos import SDWebImage class GameEditVC: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UIPickerViewDelegate, UIPickerViewDataSource, UITextViewDelegate, UIVideoEditorControllerDelegate{   ---省略----------- override func viewDidLoad() { super.viewDidLoad()      ---省略----------- //写真系のアラート PHPhotoLibrary.requestAuthorization { (status) in switch(status){ case .authorized: print("authorized") case .denied: print("denied") case .notDetermined: print("notDetermined") case .restricted: print("restricted") default: break } } } func getImageByUrl(url: URL) -> UIImage{ do { let data = try Data(contentsOf: url) return UIImage(data: data)! } catch let err { print("Error : (err.localizedDescription)") } return UIImage() } ----省略---------- //撮影完了時------------------------------- func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { //画像のとき if let picker = info[UIImagePickerController.InfoKey.originalImage] as? UIImage { indicator.startFullIndicator(view: view) ----省略---------- } //動画のとき if #available(iOS 11.0, *) { //PHAssetが取れればそれを使う if let asset = info[.phAsset] as? PHAsset { let manager = PHImageManager.default() manager.requestAVAsset(forVideo: asset, options: nil) {asset, audioMix, info in guard let asset = asset else { print("asset is nil") return } if let urlAsset = asset as? AVURLAsset { let videoPath = urlAsset.url.absoluteString DispatchQueue.main.async { let videoEditor: UIVideoEditorController = UIVideoEditorController() videoEditor.videoPath = videoPath videoEditor.videoMaximumDuration = 10 videoEditor.videoQuality = .typeMedium videoEditor.delegate = self picker.present(videoEditor, animated: true, completion: nil) } } } } } } //動画トリミング成功時 func videoEditorController(_ editor: UIVideoEditorController, didSaveEditedVideoToPath editedVideoPath: String) { print("編集ビデオパス: (editedVideoPath)") editor.dismiss(animated: true, completion: nil) } // 動画トリミング失敗時の処理 private func videoEditorController(editor: UIVideoEditorController, didFailWithError error: NSError) { print("error: (error)") } // 動画トリミングキャンセル時の処理 func videoEditorControllerDidCancel(_ editor: UIVideoEditorController) { } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { picker.dismiss(animated: true, completion: nil) } }

起こるエラー

トリミング画面で「Save」を押すと以下のエラーがはかれます。
イメージ説明

2019-10-19 18:02:00.607224+0900 AMU[6533:949699] [Generic] Video export failed for asset <AVURLAsset: 0x281c141c0, URL = file:///file:/var/mobile/Media/DCIM/104APPLE/IMG_4202.MOV>: Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x28126e7f0 {Error Domain=NSOSStatusErrorDomain Code=-16979 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16979), NSURL=file:///file:/var/mobile/Media/DCIM/104APPLE/IMG_4202.MOV, NSLocalizedDescription=The operation could not be completed}

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答3

0

自己解決

と思ったけどちょっとまて、そもそもUIImagePickerControllerだけでトリミングできるやんけ、、
ってのを
https://qiita.com/k_kuni/items/08e66d4d8074bff2c4a3
の記事で知ったので修正。

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

かなりいろいろ見たけど以下でできたよ、、、(まじで疲れたwww)
https://stackoverflow.com/questions/40354689/swift-how-to-record-video-in-mp4-format-with-uiimagepickercontroller/40354948#40354948

完成コードはこれ

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

投稿2019/10/21 09:35

shima_shima

総合スコア16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

いらない記述があるかもしれないが、やりたいことはできた!
以下を参考にした!!
[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) } } }

投稿2019/10/21 05:24

shima_shima

総合スコア16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

0

いらない記述があるかもしれないが、やりたいことはできた!
以下を参考にした!!
[https://github.com/albertbori/iOS-Sample-Video-Trimmer]

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

投稿2019/10/21 05:20

shima_shima

総合スコア16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問