酢ろぐ!

カレーが嫌いなスマートフォンアプリプログラマのブログ。

UIImagePickerControllerを使って写真.appで編集済みの動画を選択しても編集前のオリジナル動画が使われてしまう

動画のアップロード機能を実装していると、iOS標準の写真.appでクリッピングやトリミングしている編集済みの動画を選択した場合に、編集しているにもかかわらず編集前のオリジナル動画がアップロードされてしまう現象に気が付いた。

動画を選択するのに UIImagePickerController を使っている。未編集の動画を選択した場合とクリッピングした動画を選択した場合で違いがあるのかについて調査した。

結論から書くとUIImagePickerControllerは関係なく、PHAssetからAVAssetを取り出す際のPHVideoRequestOptionsの指定方法に誤りがあることがわかった。

編集済みの動画の場合はPHAsset#adjustedが1になっている

UIImagePickerControllerで動画や写真を選択するとUIImagePickerControllerDelegate.imagePickerController(_:didFinishPickingMediaWithInfo:)が呼ばれる。未編集動画と編集済み動画でどんな差異があるのか、info の中身をまず調べることにした。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
  // ここでinfoの中身を調べた
  print("\(info)")
}

調査の結果、未編集の動画と編集済みの動画との違いはPHAsset#adjustedの値の違いだけであることがわかった。少なくとも info[.editedPhAsset] などからデータを取り出さないといけないわけではない。PHAssetから「編集前のオリジナル動画」ではなく「編集済みの動画」を取得するにはどうしたらよいのか?については後述する。

あまり重要な情報ではないが、それぞれの動画を選択した場合のログを載せてく。

編集済みの動画を選択した場合には、info[.phAsset]で取り出したPHAssetのadjusted1になっている。

[
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): 
public.movie, 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerPHAsset): 
<PHAsset: 0x13024f720> A6BC4CB3-E92F-4BB3-A967-5EA773F1DCDF/L0/001 mediaType=2/0, sourceType=1, (1920x1080), creationDate=2022-01-14 13:59:02 +0000, location=1, hidden=0, favorite=0, adjusted=1 , 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): 
assets-library://asset/asset.MOV?id=A6BC4CB3-E92F-4BB3-A967-5EA773F1DCDF&ext=MOV, 
 __C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL): 
file:///private/var/mobile/Containers/Data/PluginKitPlugin/F543103D-63B7-4566-B24D-758E8D85CA17/tmp/trim.E02629B0-C7EE-4FA1-998E-3EFE22F9D590.MOV
]

未編集の動画を選択した場合には、info[.phAsset]で取り出したPHAssetのadjusted0になっている。

[
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerPHAsset): 
<PHAsset: 0x135141e30> D82F6058-C843-45E6-B82F-3D0614A6A5A5/L0/001 mediaType=2/0, sourceType=1, (1920x1080), creationDate=2022-01-14 13:59:02 +0000, location=1, hidden=0, favorite=0, adjusted=0 , 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType): 
public.movie, 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL): 
assets-library://asset/asset.MOV?id=D82F6058-C843-45E6-B82F-3D0614A6A5A5&ext=MOV, 
__C.UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL): 
file:///private/var/mobile/Containers/Data/PluginKitPlugin/F543103D-63B7-4566-B24D-758E8D85CA17/tmp/trim.20082119-CD92-4146-A76D-3AE767B38FCC.MOV
]

問題はUIImagePickerControllerで選択した動画情報(PHAsset)の取り出し方にあるわけではなくて、PHAssetからAVAssetを取得する際のオプションの指定の仕方が悪いことがわかった。

PHAssetからAVAssetの取得時にオプションでオリジナル動画かクリッピングした動画かを選択する

既存の実装では、PHAssetからAVAssetを取り出す際のPHVideoRequestOptionsの指定を PHVideoRequestOptionsVersion.original としていた。このため編集前のオリジナル動画がアップロードされていたことがわかった。

let options: PHVideoRequestOptions = PHVideoRequestOptions()
// 編集前のオリジナル動画がほしい場合
//options.version = PHVideoRequestOptionsVersion.original
// 編集後の動画がほしい場合
options.version = PHVideoRequestOptionsVersion.current

PHImageManager.default().requestAVAsset(forVideo: self, options: options, resultHandler: {(asset: AVAsset?, _: AVAudioMix?, _: [AnyHashable: Any]?) -> Void in
    DispatchQueue.main.async {
        if let urlAsset = asset as? AVURLAsset {
            let localVideoUrl: URL = urlAsset.url as URL
            completionHandler(localVideoUrl)
        } else {
            completionHandler(nil)
        }
    }
})

写真.appで編集したとしても同じ動画として保存した場合には、写真.appで見た場合にサムネイルが変わっていたとしても、オリジナル動画が上書きされるわけではなくて少なくともファイルパス等はそのまま残っている。編集後の動画が欲しい場合には PHVideoRequestOptions#versionの指定を PHVideoRequestOptionsVersion.current とする必要がある。

f:id:ch3cooh393:20220115101436p:plain

以上で問題は解決した。