PhotoLibrayにある画像、ビデオのファイルの名前、作成日時(アップロード日時)を取得する方法(iCloud共有含む)
iCloud共有にアップロードされたファイルをダウンロードすると、jpegの場合はExifデータから撮影日がわかるが、ビデオは撮影日時を調べるのに写真アプリで情報をチェックする必要があり、面倒だった。そこでプログラムでファイル名、CreateionDateを取得しようとすてみると、これもまた意外と面倒でした。
次のサンプルコードはPhotoLibray内のiCould、ローカル、画像、ビデオ、ライブフォトを含めてファイル名を取得するようにしたものです。
iPhoneで撮影したvideoをそのデバイスからiCloud共有にアップロードした場合は作成日時=撮影日時と考えて良いと思いますが、それ以外で撮影し、Macからアップロードした場合は作成日時=アップロード日時になるでしょう。また、その場合に拡張子がMP4以外の場合、iCloud共有からイクスポートしたファイルはもとの拡張子(M4Vなど)ですが、以下のプログラムで取得するファイル名の拡張子は全てMP4になります。
iPhoneとMacでiCloud共有の同じ写真やビデオのファイル名を比べてみると、同じものが多いものの、異なるものが結構あります。デバイス毎にファイル名が異なりますね。
MyAssetListViewControllerを呼び出す遷移元で、次の要領でPHAssetをフェッチする。
iCloud共有にアップロードされたファイルをダウンロードすると、jpegの場合はExifデータから撮影日がわかるが、ビデオは撮影日時を調べるのに写真アプリで情報をチェックする必要があり、面倒だった。そこでプログラムでファイル名、CreateionDateを取得しようとすてみると、これもまた意外と面倒でした。
次のサンプルコードはPhotoLibray内のiCould、ローカル、画像、ビデオ、ライブフォトを含めてファイル名を取得するようにしたものです。
iPhoneで撮影したvideoをそのデバイスからiCloud共有にアップロードした場合は作成日時=撮影日時と考えて良いと思いますが、それ以外で撮影し、Macからアップロードした場合は作成日時=アップロード日時になるでしょう。また、その場合に拡張子がMP4以外の場合、iCloud共有からイクスポートしたファイルはもとの拡張子(M4Vなど)ですが、以下のプログラムで取得するファイル名の拡張子は全てMP4になります。
iPhoneとMacでiCloud共有の同じ写真やビデオのファイル名を比べてみると、同じものが多いものの、異なるものが結構あります。デバイス毎にファイル名が異なりますね。
MyAssetListViewControllerを呼び出す遷移元で、次の要領でPHAssetをフェッチする。
let sharedAlbums = PHAssetCollection.fetchAssetCollections(
with: PHAssetCollectionType.album,
subtype: PHAssetCollectionSubtype.albumCloudShared,
options: fetchOptions)
let assetsFetchResults = PHAsset.fetchAssets(in: sharedAlbums, options: nil)
画面遷移時にMyAssetListViewControllerにassetsFetchResultsをセットする。
//---- MyAssetListViewController ----
-----------------------------
画面遷移時にMyAssetListViewControllerにassetsFetchResultsをセットする。
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let assetGridViewController = segue.destination as! MyAssetListViewController assetGridViewController.assetsFetchResults = assetsFetchResults
}
import UIKit
import Photos
/// UITableViewCellにプロパティーを追加。IBでコネクトする。
class MyTableViewCell: UITableViewCell {
@IBOutlet var thumbnail: UIImageView?
@IBOutlet var fileName: UILabel?
@IBOutlet var creationDate: UILabel?
@IBOutlet var resolution: UILabel?
@IBOutlet var duration: UILabel?
}
class MyAssetListViewController: UITableViewController {
// MyRootListViewControllerから画面遷移する時にセットするインスタンス変数
var assetsFetchResults: PHFetchResult<PHAsset>?
var albumTitle: String?
var assetToFileName = [PHAsset: String]()
let reuseIdentifier = "Cell"
let imageManager = PHCachingImageManager()
let dateFormatter = DateFormatter()
let timeFormatter = DateComponentsFormatter()
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.title = albumTitle
// Formatterの設定
dateFormatter.timeZone = TimeZone.current
dateFormatter.dateFormat = "yyyy/MM/dd HH:mm:ss"
timeFormatter.allowedUnits = [NSCalendar.Unit.minute, NSCalendar.Unit.second]
timeFormatter.zeroFormattingBehavior = .pad
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let assets = self.assetsFetchResults else { return 0 }
return assets.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
guard let assets = self.assetsFetchResults else { return cell }
let asset = assets[indexPath.item]
cell.tag = asset.localIdentifier.hash
//iCloudへアクセスするようにOptionを設定
let options = PHImageRequestOptions()
options.isNetworkAccessAllowed = true
self.imageManager.requestImage(for: asset,
targetSize: CGSize(width:80, height:80),
contentMode: PHImageContentMode.aspectFit,
options: options,
resultHandler: {(result, info)->Void in
//非同期処理ためcellの再利用により異なるassetの内容を表示している場合がある。
//localIdentifierが異なる場合は何もしない。
guard cell.tag == asset.localIdentifier.hash,
let result = result else { return }
cell.thumbnail!.image = result
})
// assetから取得できるデータをセット
cell.fileName!.text = nil
cell.creationDate!.text = asset.creationDate == nil ? "(unknown)" : dateFormatter.string(from:asset.creationDate!)
cell.resolution!.text = "\(asset.pixelWidth) x \(asset.pixelHeight)"
cell.duration!.text = timeFormatter.string(from: asset.duration)
// ファイル名セット
// 既にassetResourcesがキャッシュされている場合はそれからファイル名を取得する。
// iOS10時点では、LivePhotoの場合にassetResourcesにJPG、MOVの対がセットされている。
let assetResources = PHAssetResource.assetResources(for: asset)
if setFileName(from: assetResources, to: cell) == false {
// assetResourcesが空の場合はfalseが返される。
// LivePhoto以外のimage、videoの場合は空。LivePhotoでもキャッシュされていない場合は空。
// assetResourcesがない場合はrequestContentEditingInputを取得する。
setFileNameFromContentEditingInput(with: asset, to: cell)
// AVURLAssetを使う場合はこちらに変更。
//setFileNameFromAVAsset(with: asset, to: cell)
}
return cell
}
/// assetResource配列の各要素のoriginalFilenameを結合してlabelにセットする。
/// assetResourcesが空の場合は"(searching)"をセットする。
/// - parameter from: PHAssetResource.assetResources(asset)
/// - parameter cell: MyTableViewCell
func setFileName(from assetResources:[PHAssetResource], to cell: MyTableViewCell) -> Bool {
let label = cell.fileName!
if assetResources.count > 0 {
label.text = nil
for assetResource in assetResources {
let fileName = assetResource.originalFilename
if label.text == nil {
label.text = fileName
} else {
//assetResourcesが複数あるので同じfileNameが重複する可能性がある。
label.text = label.text! + ", " + fileName
}
}
return true
} else {
//assetResourcesが空の場合は"(searching)"をセット。
label.text = "(searching)"
return false
}
}
/// assetからContentEditingInputを得て、livePhoto、image、videoの種類に応じてfileNameをセットする。
/// - parameter with: PHAsset
/// - parameter to: MyTableViewCell
func setFileNameFromContentEditingInput(with asset: PHAsset, to cell: MyTableViewCell) {
let options = PHContentEditingInputRequestOptions()
options.isNetworkAccessAllowed = true
asset.requestContentEditingInput(with: options,
completionHandler: { (contentEditingInput, info) -> Void in
guard cell.tag == asset.localIdentifier.hash else { return }
if let livePhoto = contentEditingInput?.livePhoto {
// live photoの場合はassetResourcesを取得してファイル名をセットする。
let _ = self.setFileName(from: PHAssetResource.assetResources(for: livePhoto), to: cell)
} else if let url = contentEditingInput?.fullSizeImageURL {
// imageの場合
cell.fileName!.text = url.path.components(separatedBy: "/").last
} else if let urlAsset = contentEditingInput?.audiovisualAsset as? AVURLAsset {
// videoの場合
cell.fileName!.text = urlAsset.url.path.components(separatedBy: "/").last
cell.duration!.text = self.timeFormatter.string(from: asset.duration)
} else {
cell.fileName!.text = "(not found)"
}
})
}
}
-----------------------------
Live Photoの場合にJPG、MOVの対を取得するにはContentEditingInputを使わないとダメそうですが、MOVだけでよければ次のようなコードでAVURLAssetからファイル名を取得することができます。
上のコードのsetFileNameFromContentEditingInput呼び出しをこれの呼び出しに変更します。
上のコードのsetFileNameFromContentEditingInput呼び出しをこれの呼び出しに変更します。
func setFileNameFromAVAsset(with asset: PHAsset, to cell: MyTableViewCell) {
if asset.mediaType == PHAssetMediaType.video {
//iCloudへアクセスするようにOptionを設定
let options = PHVideoRequestOptions()
options.isNetworkAccessAllowed = true
//PHAssetに対応するAVPlayerItemを取得
let _ = self.imageManager.requestAVAsset(forVideo: asset,
options: options,
resultHandler: { (avAsset, avAudioMix, info) -> Void in
guard let urlAsset = avAsset as? AVURLAsset,
let fileName = urlAsset.url.path.components(separatedBy: "/").last
else { return; }
cell.fileName!.text = fileName
})
}
}
-----------------------------
PHAssetからvalue(for: "filename")でファイル名を取得する方法もありました。
ビデオの場合も拡張子JPGのファイル名が返されますが、用途によってはこれで十分な場合もあるでしょう。
なお、PHAssetの公開仕様ではないので、今後のバージョンで同じ方法が使い続けれらるかはわかりません。
-----------------------------
PHAssetからvalue(for: "filename")でファイル名を取得する方法もありました。
ビデオの場合も拡張子JPGのファイル名が返されますが、用途によってはこれで十分な場合もあるでしょう。
なお、PHAssetの公開仕様ではないので、今後のバージョンで同じ方法が使い続けれらるかはわかりません。
cell.fileName!.text = asset.value(forKey: "filename") as? String ?? "<no filename>"