2020/08/09
UIImagePickerControllerのimagePickerController関数の引数についてメモします。
動画をカメラロールから読み込む際、「選択」を押した後に呼ばれるのがこの関数のようです。
動画のURLの取得が諸々のサイトをコピペした通りに描いてもうまくいかなかったので、うまく行った結果をここに掲載しておきます。
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL
imagePickerController.dismiss(animated: true, completion: nil)
}
これでこの関数をもつクラスの予め定義しておいたメンバーvideoURLにURLが渡される訳です。
infoの型とinfoからURLを取り出す際に困った人は試してみてください。
以下のコードが説明しないけど開発中に作ったクラス。カメラロールから動画をとってきて、Assetsから写真を選んで動画に合成している。う○こみたいなコードです。Assetsには予め自分で画像を入れておいた前提です。人のコードをチグハグにつなぎ合わせて、少しづついじっています。僕と同じくらいSwift初心者のためにいいますが、このクラスをインスタンス化してpresentで画面遷移して画像をAssetsに用意しておけば使えるはずです。
import UIKit
import AVKit
import AVFoundation
import Photos
class Sample: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
let imagePickerController = UIImagePickerController()
var videoURL: URL?
var carouselView: CarouselView!
//以下編集のために追加したやつ
var _assetExport: AVAssetExportSession!
@IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let width = self.view.frame.width
let height = self.view.frame.height
carouselView = CarouselView(frame: CGRect(x: 0, y: 0, width: width, height: height))
carouselView.center = CGPoint(x: width/2, y: height/2)
self.view.addSubview(carouselView)
addEditButton()
saisei()
addHaikeiSashikaeButton()
}
@IBAction func selectImage(_ sender: Any) {
print("UIBarButtonItem。カメラロールから動画を選択")
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
//imagePickerController.mediaTypes = ["public.image", "public.movie"]
//動画だけ
imagePickerController.mediaTypes = ["public.movie"]
//画像だけ
//imagePickerController.mediaTypes = ["public.image"]
present(imagePickerController, animated: true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
print("imagePickerControllerが呼ばれたよ")
// for key in info.keys {
// print(key)
// }
// print("keyの表示終了")
// UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL)
// UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType)
// UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL)
// videoURL = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerReferenceURL")] as? URL
//これでうまく行ったー!!!!!なんで.mediaURLなんだろう=>構造体だから
videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL
print(videoURL!)
print(UIImagePickerController.InfoKey.mediaURL)
print("infoのキーを表示") //確かにinfo.keysの値の一つとなっている
// print(previewImageFromVideo(videoURL!)!)
print("プレビュー")
print(previewImageFromVideo(videoURL!)!) //こいつはうまく行った
//まあimageViewは今はいいや imageViewはOutLet接続してないからかnilになる
// print(imageView)
// imageView.image = previewImageFromVideo(videoURL!)!
// imageView.contentMode = .scaleAspectFit
imagePickerController.dismiss(animated: true, completion: nil)
}
func previewImageFromVideo(_ url:URL) -> UIImage? {
print("動画からサムネイルを生成する")
let asset = AVAsset(url:url)
print(asset)
print("asset表示")
let imageGenerator = AVAssetImageGenerator(asset:asset)
print(imageGenerator)
print("imageGeneratorの表示")
imageGenerator.appliesPreferredTrackTransform = true
var time = asset.duration
print(time)
print("時間表示")
time.value = min(time.value,2)
print(time.value)
print("time.valueを表示してみた")
do {
let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil)
return UIImage(cgImage: imageRef)
} catch {
print("死亡")
return nil
}
}
@IBAction func playMovie(_ sender: Any) {
if let videoURL = videoURL{
let player = AVPlayer(url: videoURL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
present(playerViewController, animated: true){
print("動画再生")
playerViewController.player!.play()
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
print("キャンセル")
// モーダルビューを閉じる
self.dismiss(animated: true, completion: nil)
}
func addEditButton() {
let btn = UIButton(frame: CGRect(x: 200, y: 200, width: 200, height: 50))
btn.setTitle("動画を選択", for: .normal)
btn.backgroundColor = .red
btn.addTarget(self, action: #selector(selectImage), for: .touchUpInside)
view.addSubview(btn)
}
func saisei() {
let btn = UIButton(frame: CGRect(x: 200, y: 300, width: 200, height: 50))
btn.setTitle("再生", for: .normal)
btn.backgroundColor = .blue
btn.addTarget(self, action: #selector(playMovie), for: .touchUpInside)
view.addSubview(btn)
}
//こっからが本題
func addHaikeiSashikaeButton() {
let btn = UIButton(frame: CGRect(x: 200, y: 400, width: 200, height: 50))
btn.setTitle("動画の背景を差し替え", for: .normal)
btn.backgroundColor = .blue
btn.addTarget(self, action: #selector(mergeMovie), for: .touchUpInside)
view.addSubview(btn)
}
@objc func haikeiSashikae() {
}
@objc func mergeMovie() {
//元の動画のURLを取得
let baseMovieURL = self.getBaseMovieURL()
print(baseMovieURL)
print("URLは取得できてる?")
//アセットの作成
//動画のアセットとトラックを作成
var videoAsset: AVURLAsset
var videoTrack: AVAssetTrack
var audioTrack: AVAssetTrack
videoAsset = AVURLAsset(url: baseMovieURL, options:nil)
print(videoAsset)
print("videoAssetを取得できている?")
let videoTracks = videoAsset.tracks(withMediaType: AVMediaType.video)
videoTrack = videoTracks[0] //トラックの取得
let audioTracks = videoAsset.tracks(withMediaType: AVMediaType.audio)
audioTrack = audioTracks[0] //トラックの取得
//コンポジション作成
let mixComposition : AVMutableComposition = AVMutableComposition()
// ベースとなる動画のコンポジション作成
let compositionVideoTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid)
// ベースとなる音声のコンポジション作成
let compositionAudioTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid)
// コンポジションの設定
// 動画の長さ設定
try! compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: videoTrack, at: CMTime.zero)
// 音声の長さ設定
try! compositionAudioTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: audioTrack, at: CMTime.zero)
// 回転方向の設定
compositionVideoTrack.preferredTransform = videoAsset.tracks(withMediaType: AVMediaType.video)[0].preferredTransform
// 動画のサイズを取得
let videoSize: CGSize = videoTrack.naturalSize
// キャプチャ画像レイヤの作成
let movieLayer = self.makeMovieLayer(videoSize)
// 親レイヤーを作成
let parentLayer: CALayer = CALayer()
let videoLayer: CALayer = CALayer()
parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
parentLayer.addSublayer(videoLayer)
parentLayer.addSublayer(movieLayer) //キャプチャ画像レイヤを追加
// 合成用コンポジション作成
let videoComp: AVMutableVideoComposition = AVMutableVideoComposition()
videoComp.renderSize = videoSize
videoComp.frameDuration = CMTimeMake(value: 1, timescale: 30)
videoComp.animationTool = AVVideoCompositionCoreAnimationTool.init(postProcessingAsVideoLayer: videoLayer, in: parentLayer)
// 最後にanimationToolに設定
// 合成用コンポジション作成
// let videoComp: AVMutableVideoComposition = AVMutableVideoComposition()
// videoComp.renderSize = videoSize
// videoComp.frameDuration = CMTimeMake(value: 1, timescale: 30)
// インストラクションを合成用コンポジションに設定
let instruction: AVMutableVideoCompositionInstruction = AVMutableVideoCompositionInstruction()
instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration)
let layerInstruction: AVMutableVideoCompositionLayerInstruction = AVMutableVideoCompositionLayerInstruction.init(assetTrack: compositionVideoTrack)
instruction.layerInstructions = [layerInstruction]
videoComp.instructions = [instruction]
// 動画のコンポジションをベースにAVAssetExportを生成
_assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality)
// 合成用コンポジションを設定
_assetExport?.videoComposition = videoComp
// エクスポートファイルの設定
let exportPath: String = NSHomeDirectory() + "/tmp/createdMovie.mov"
let exportUrl: URL = URL(fileURLWithPath: exportPath)
_assetExport?.outputFileType = AVFileType.mov
_assetExport?.outputURL = exportUrl
_assetExport?.shouldOptimizeForNetworkUse = true
// ファイルが存在している場合は削除
if FileManager.default.fileExists(atPath: exportPath) {
try! FileManager.default.removeItem(atPath: exportPath)
}
// エクスポート実行
_assetExport?.exportAsynchronously(completionHandler: {() -> Void in
if self._assetExport?.status == AVAssetExportSession.Status.failed {
// 失敗した場合
print("failed:", self._assetExport?.error)
}
if self._assetExport?.status == AVAssetExportSession.Status.completed {
// 成功した場合
print("completed")
// カメラロールに保存
PHPhotoLibrary.shared().performChanges({
PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: exportUrl)
})
}
})
}
//元の動画の取得
func getBaseMovieURL() -> URL {
// プロジェクト入れたファイルはこれで取得可能
// let baseMovieURL:URL = Bundle.main.bundleURL.appendingPathComponent("basemovie.mov")
// return baseMovieURL
return videoURL!
}
func makeMovieLayer(_ videoSize: CGSize) -> CALayer {
//親レイヤの作成
let movieLayer: CALayer = CALayer()
// movieLayer.frame = CGRect(x: 0, y: 0, width: 607, height: 1080) //x: 100->0
movieLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height)
movieLayer.opacity = 1.0
movieLayer.masksToBounds = true
//静止画のレイヤー作成 元々親レイヤに大きさを揃えている
let imageLayer: CALayer = CALayer()
imageLayer.frame = CGRect(x: 0, y: 0, width: movieLayer.frame.width, height: movieLayer.frame.height)
imageLayer.contentsGravity = CALayerContentsGravity.resizeAspectFill
// 元の動画を取得
//マージする元動画のURLを取得
let orgVideoURL = self.getBaseMovieURL()
//動画のアセットを作成
var videoAsset: AVURLAsset
videoAsset = AVURLAsset(url: orgVideoURL, options:nil)
//元動画から静止画を抜き出す
var gene : AVAssetImageGenerator
gene = AVAssetImageGenerator(asset: videoAsset)
gene.requestedTimeToleranceAfter = CMTimeMake(value: 1,timescale: 30)
gene.requestedTimeToleranceBefore = CMTimeMake(value: 1,timescale: 30)
gene.maximumSize = videoSize //オリジナルサイズ
//静止画アニメーション
let animImg:CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "contents")
animImg.beginTime = -1.0 //0.0だとうまくいかなかった
animImg.duration = 3.0
animImg.repeatCount = 1
animImg.autoreverses = false
animImg.isRemovedOnCompletion = false
animImg.fillMode = CAMediaTimingFillMode.forwards
animImg.calculationMode = CAAnimationCalculationMode.discrete
var imgKeyTimes:Array<NSNumber> = []
let frameCount = 90 // (3.0秒 x 30フレーム)
for i in 0 ... frameCount {
imgKeyTimes.append((Double(i)/Double(frameCount)) as NSNumber)
}
animImg.keyTimes = imgKeyTimes
// 始めの3秒を90フレームに分割して静止画を取得=>一つの画像に変更
var imgValue:Array<CGImage> = []
for i in 0 ... frameCount {
// imgValue.append(try! gene.copyCGImage(at: CMTimeMultiplyByFloat64(CMTimeMake(value: 3 ,timescale: 1), multiplier: Double(i)/Double(frameCount)), actualTime: nil))
//画像をAssetsから取り出してCGImageに変換したい
// let image: UIImage! = self.getImageToDocumentDirectory(fileName: "あなたがつけたファイル名.png")
let image: UIImage! = UIImage(named: "あなたがつけたファイル名.png")
let cgImage: CGImage! = image?.cgImage
//挿入する画像を全て静止画にした
// imgValue.append(try! gene.copyCGImage(at: CMTimeMultiplyByFloat64(CMTimeMake(value: 3 ,timescale: 1), multiplier: Double(0)/Double(frameCount)), actualTime: nil))
//読み込んで作ったCGImageを利用
imgValue.append(try! cgImage!)
}
animImg.values = imgValue
imageLayer.add(animImg, forKey: nil)
//サブレイヤに追加
movieLayer.addSublayer(imageLayer)
return movieLayer
}
//画像をAssetsから取り出す
func getImageToDocumentDirectory(fileName: String) -> UIImage? {
if let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
let fileURL = documentURL.appendingPathComponent(fileName)
if let imageData = try? Data(contentsOf: fileURL),
let image = UIImage(data: imageData) {
return image
}
}
return nil
}
}