酢ろぐ!

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

Swiftで Photos.frameworkを使って動画からサムネイル画像を生成する

UIImagePickerControllerを使うと静止画を選択することができます。それだけではなく動画も選択することが可能です。選択した静止画(または動画)はPHAssetオブジェクトの形でデータを扱うこととなります。

本記事では、取得したPHAssetオブジェクトからサムネイル画像を生成する方法をご紹介します。

動画からサムネイル画像を生成する

UIImagePickerControllerを起動して、静止画と動画を選択できるようにする。

let vc = UIImagePickerController()
vc.mediaTypes = [ "public.image", "public.movie" ]
vc.sourceType = UIImagePickerController.SourceType.photoLibrary
vc.delegate = self
self?.present(vc, animated: true, completion: nil)

ユーザーが選択したコンテンツのPHAssetを取得する

ユーザーがコンテンツを選択すると UIImagePickerControllerDelegate#imagePickerController(_, didFinishPickingMediaWithInfo:) が呼ばれるので、この中で 選択したコンテンツから表示すべき静止画を取得します。

静止画の場合はそのままオリジナル画像を取得しても良いが、動画の場合は選択された動画からサムネイル画像を生成する。

// MARK: - UIImagePickerControllerDelegate

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        
    //コンテンツタイプを取得する
    guard let mediaType = info[.mediaType] as? String else {
        return
    }
    
    // コンテンツタイプによって処理を分ける
    switch mediaType.lowercased() {
    case "public.image":
        guard let image = (info[.originalImage] as? UIImage) else {
            return
        }
        // 静止画の場合は、そのままオリジナル画像を取得する
        
    case "public.movie":
        guard let asset = info[.phAsset] as? PHAsset else {
            return
        }
        guard let image = VideoUtil.loadThumbnail(asset: asset, longSideSize: 1024) else {
            return
        }
        // 動画の場合は、選択した動画からサムネイル画像を生成する
        
    default:
        return
    }
}

PHAssetの動画からサムネイル画像を生成する

最近のiPhoneデバイスでは高画質の動画を扱うことも増えてきました。UITableViewなどでリストのサムネイルとして使う場合、オリジナル動画サイズのサムネイル画像を生成するのは負荷面を考えると避けるべきです。

長辺を指定した上で、動画からサムネイル画像を生成する方法をご紹介します。

import UIKit
import Photos

class VideoUtil {

    /// サムネイルを取得する
    static func loadThumbnail(asset: PHAsset, longSideSize: CGFloat? = nil) -> UIImage? {
        let size = calcAspectSize(width: asset.pixelWidth, height: asset.pixelHeight, longSideSize: longSideSize)

        var thumbnailImage: UIImage? = nil

        let option = PHImageRequestOptions()
        option.isSynchronous = true
        option.isNetworkAccessAllowed = true
        option.progressHandler = { (progress, error, stop, info) in
            print("load thumbnail: \(progress)")
        }
        
        //NOTE: PHImageManagerMaximumSizeと使うと画質が悪化してしまう
        let manager = PHImageManager.default()
        manager.requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: option) { (result, info) in
            
            if let error = info?[PHImageErrorKey] as? NSError {
                print("fail to load thumbnail: \(info)")
                print(error.debugDescription)
            }
            
            thumbnailImage = result
        }

        return thumbnailImage
    }
    
    /// 長辺をlongSideSize合わせにして縮小したサイズを求める
    static func calcAspectSize(width: Int, height: Int, longSideSize: CGFloat?) -> CGSize {
        guard let longSize = longSideSize else {
            return CGSize(width: width, height: height)
        }
        
        let w: CGFloat
        let h: CGFloat
        
        if (width >= height) {
            //横長
            let scale = (longSize / CGFloat(width))
            w = longSize
            h = CGFloat(height) * scale
        } else {
            //縦長
            let scale = (longSize / CGFloat(height))
            w = CGFloat(width) * scale
            h = longSize
        }
        return CGSize(width: w, height: h)
    }
}

PHImageManager#requestImage(for:targetSize:contentMode:options:resultHandler:) を使う上で注意する点としては、iCloudにアップロード済みの動画は isNetworkAccessAllowed = true にしておかないとサムネイルの生成ができません。iOSシミュレータで処理が成功してるのに実機でサムネイルが生成できない時はこのプロパティをご確認ください。

また、PHImageManagerMaximumSize と使うと画質が悪化してしまうのが気になるところです。

動作確認環境

  • iOS 13.4
  • Xcode 11.4.1

関連記事

iOSアプリ開発Tips/動画編集にて関連した記事をまとめています。

それ以外にも iOSアプリ開発で役立つ情報をまとめています。