视频缩略图是现代媒体丰富应用的基石,为用户提供了快速的视觉预览,而无需播放完整视频. 在iOS开发中, AVFoundation提供了最能提取缩略图图像的性能和灵活的工具. 本文走过自定义视频缩略图生成器,可以处理真实世界的情景——包括本地和远程文件,缓存,错误处理,以及性能调谐——使用Swift和AVoundation.

理解基金会在缩略图生成中的作用

AVFoundation是苹果公司与视听媒体合作的主要框架。对于缩略图生成,关键类是 AVASSet 和[ AVASSetImageGenerator[

  • AVASet: 代表一个定时视听资产(本地或远程视频),包含音轨信息,持续时间和元数据.
  • AVASetImageGenerator:提供在特定时间间隔从资产中获取CGImage Refers的方法,它尊重视频导向,应用色彩空间调整,并支持同步批量生成.

使用这些类直接使您完全控制图像大小,时间准确度,以及变换,与UIKit快捷键不同,后者经常隐藏配置选项.

建立项目

从将 AVFoundation 导入到您的 Swift 文件开始 :

import AVFoundation

您还需要 UIKit 来显示生成的图像。 从 URL 创建 [[FLT: 1]] 。 对于本地文件, 请使用 [[FLT: 2]] 。 对于远程视频, 请使用 [[FLT: 3] 并确保资产可以初始化 :

// Local
let localURL = Bundle.main.url(forResource: "sample", withExtension: "mp4")!
let asset = AVAsset(url: localURL)

// Remote
let remoteURL = URL(string: "https://example.com/video.mp4")!
let remoteAsset = AVAsset(url: remoteURL)

生成一个单缩略图

要在指定时间生成一个缩略图, 请创建 [[FLT: 5]] 并呼叫 [[FLT: 6]]。 这种方法是同步的, 应该运行在主线上, 以避免阻断 UI 。

let imageGenerator = AVAssetImageGenerator(asset: asset)
imageGenerator.appliesPreferredTrackTransform = true

let time = CMTime(seconds: 2.0, preferredTimescale: 600) // 2 seconds into the video

do {
 let cgImage = try imageGenerator.copyCGImage(at: time, actualTime: nil)
 let thumbnail = UIImage(cgImage: cgImage)
 // Update UI on main thread
 DispatchQueue.main.async {
 imageView.image = thumbnail
 }
} catch {
 print("Thumbnail generation failed: \(error.localizedDescription)")
}

重要性:[ 设定至]确保由此产生的图像尊重视频的旋转元数据(图片对地貌 ) 。 没有这个,一个直立的视频可能会出现侧面。

选择正确的时间

缩略图通常在视频开头拍摄,但考虑使用一个代表内容的时间——比如,在视频长度中为10%。要获得有意义的时间点,请查询资产的持续时间:

let duration = asset.duration
let midTime = CMTimeMultiplyByFloat64(duration, multiplier: 0.1)

处理错误和边缘案例

缩略图生成可能由于几个原因而失败。 总是优雅地处理错误并提供倒置 UI 。

  • 无效资产:[ 视频可能损坏或URL无法访问.
  • 错过视频音轨:[ 有些资产只包含音频;检查]].
  • 超出范围的时间: 确保所要求的时间在之间,以及资产的期限。
  • 网络错误: 远程资产可能无法装入。 在生成缩略图之前使用 。

预装资产属性示例:

let assetKeys = ["tracks", "duration", "playable"]
asset.loadValuesAsynchronously(forKeys: assetKeys) {
 var error: NSError?
 let status = asset.statusOfValue(forKey: "tracks", error: &error)
 if status == .loaded {
 // Safe to generate thumbnails
 } else {
 // Handle error
 }
}

自定义缩略图输出

AVASetImageGenerator提供了多个属性来微调输出图像.

最大大小

将生成的图像限制在使用 [[FLT: 15]] 的指定边框内。 这会减少内存的超高, 并加快大型视频的生成 。

imageGenerator.maximumSize = CGSize(width: 640, height: 480)

生成的图像将按比例调整,以适应这些维度,同时保持宽度比。

请求时间容忍

默认情况下, [[FLT: 17] ] 在请求的时间返回精确的框架( 或尽可能接近) 。 您可以通过设置 [[FLT: 18] 和 [[FLT: 19]] 来交换速度的精确度 。 对于缩略图, 0. 5 秒的容积通常可以接受, 速度更快 :

imageGenerator.requestedTimeToleranceBefore = CMTime(seconds: 0.5, preferredTimescale: 600)
imageGenerator.requestedTimeToleranceAfter = CMTime(seconds: 0.5, preferredTimescale: 600)

孔径模式

]控制生成的图像如何处理干净的孔径对生产孔径。使用[去除边缘尘埃并生成更干净的缩略图:

imageGenerator.apertureMode = .cleanAperture

应用转换

如果您需要旋转或翻转图像(例如,对于特定方向),请使用 Core 图形转换。一种方法是捕捉原始图像,然后手动应用 CGAffine Transform 。 或者,您可以将 设置为 (如所示) 并信任资产的自然方向。

高效生成多缩略图

对于视频网格,时间线擦除,或故事板预览,您通常需要数十个缩略图。避免在主线程的循环中调用 。而是使用同步方法。它高效地处理多个时间请求,利用内部平行性。

var times: [NSValue] = []
for i in 0..<10 {
 let time = CMTime(seconds: Double(i) * 2.0, preferredTimescale: 600)
 times.append(NSValue(time: time))
}

imageGenerator.generateCGImagesAsynchronously(forTimes: times) { requestedTime, cgImage, actualTime, result, error in
 if result == .succeeded, let cgImage = cgImage {
 let thumbnail = UIImage(cgImage: cgImage)
 DispatchQueue.main.async {
 // Append to array or update collection view cell
 }
 } else {
 print("Failed at \(requestedTime): \(error?.localizedDescription ?? "unknown")")
 }
}

这种方法可以让您逐步更新 UI 。 如果用户离开屏幕释放资源, 请记住调用 [[FLT: 29] 。

与远程视频合作

当视频 URL 指向远程服务器时, 您必须等待资产被装满或至少可以获取其音轨信息。 使用 [[FLT: 30] ] 适当的缓存头以避免重复下载 。

let urlAsset = AVURLAsset(url: remoteURL, options: [AVURLAssetPreferPreciseDurationAndTimingKey: true])
urlAsset.loadValuesAsynchronously(forKeys: ["tracks"]) {
 // Then create image generator and proceed
}

对于制作应用,请考虑在磁盘上抓取缩略图,这样重复访问同一视频不会重排图像。使用 或基于文件的缓存, 将视频 URL 作为密钥 。

缓存策略示例

class ThumbnailCache {
 static let shared = ThumbnailCache()
 private let cache = NSCache<NSString, UIImage>()

 func thumbnail(for url: URL) -> UIImage? {
 return cache.object(forKey: url.absoluteString as NSString)
 }

 func setThumbnail(_ image: UIImage, for url: URL) {
 cache.setObject(image, forKey: url.absoluteString as NSString)
 }
}

将此与图像生成器合并,以避免冗余工作.

业绩考量

生成缩略图是一个CPU的密集操作, 特别是高分辨率的 4K 视频。 遵循这些最佳做法 :

  • 总是在背景队列上执行同步调用(例如]).
  • 设置一个合理的 [[FLT: 35]] 来减小输出图像尺寸。 大小为 320x240 的缩略图通常足够 。
  • 使用 [[FLT: 36]]] ,使生成器可以跳过最近的帧,而不是解码精确.
  • 当视图消失时, 取消任何同步生成的不同步 。
  • 对于收藏视图中的显示,生成精确单元格大小的缩略图,避免在主线程上重定大小.

与斯威夫特货币(Async/Await)的结合

如果您的应用目标为iOS 15+, 您可以将封杀的呼叫包成一个连续的 干净的 Async/ 等待代码 :

func generateThumbnail(forVideoAt url: URL, at time: CMTime) async throws -> UIImage {
 let asset = AVAsset(url: url)
 let imageGenerator = AVAssetImageGenerator(asset: asset)
 imageGenerator.appliesPreferredTrackTransform = true
 imageGenerator.maximumSize = CGSize(width: 640, height: 480)

 return try await withCheckedThrowingContinuation { continuation in
 let times = [NSValue(time: time)]
 imageGenerator.generateCGImagesAsynchronously(forTimes: times) { _, cgImage, _, result, error in
 if let cgImage = cgImage, result == .succeeded {
 let image = UIImage(cgImage: cgImage)
 continuation.resume(returning: image)
 } else {
 continuation.resume(throwing: error ?? ThumbnailError.generationFailed)
 }
 }
 }
}

高级自定义

带有视频构成的缩略图

如果您的视频使用](例如水印上,颜色调整),您可以将其传递给图像生成器:

imageGenerator.videoComposition = myVideoComposition

生成的缩略图将反映复合输出.

生成活照片缩略图

Live Photos 是视频和静态图像的组合。要从基本视频中提取缩略图,请使用 ] ,并在输入AVFoundation前请求视频URL。

无障碍和用户经验

缩略图增强视觉扫描, 但总是为 VoiceOver 用户提供文本替代。 在图像视图上使用 ], 尽可能描述视频内容 。

imageView.isAccessibilityElement = true
imageView.accessibilityLabel = "Thumbnail of video: \(videoTitle)"

外部资源

为了加深理解,请参考以下内容:

结论

使用 AVFoundation 创建自定义视频缩略图生成器, 使您在图像质量、 时间和性能方面完全自由。 通过同步和同步的[] , 处理错误和缓存结果, 您可以在您的 iOS 应用程序中提供无缝用户体验。 无论您正在处理本地文件还是远程视频, 覆盖的技术都会帮助您快速高效地制作简洁的, 上下文中的缩略图 。