DataUtils.swift 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. // Copyright 2025 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. import AVFoundation
  15. import SwiftUI
  16. extension NSDataAsset {
  17. /// The preferred file extension for this asset, if any.
  18. ///
  19. /// This is set in the Asset catalog under the `File Type` field.
  20. var fileExtension: String? {
  21. UTType(typeIdentifier)?.preferredFilenameExtension
  22. }
  23. /// Extracts `.png` frames from a video at a rate of 1 FPS.
  24. ///
  25. /// - Returns:
  26. /// An array of `Data` corresponding to individual images for each frame.
  27. func videoFrames() async throws -> [Data] {
  28. guard let fileExtension else {
  29. fatalError(
  30. "Failed to find file extension; ensure the \"File Type\" is set in the asset catalog."
  31. )
  32. }
  33. // we need a temp file so we can provide a URL to AVURLAsset
  34. let tempFileURL = URL(fileURLWithPath: NSTemporaryDirectory())
  35. .appendingPathComponent(UUID().uuidString, isDirectory: false)
  36. .appendingPathExtension(fileExtension)
  37. try data.write(to: tempFileURL)
  38. defer {
  39. try? FileManager.default.removeItem(at: tempFileURL)
  40. }
  41. let asset = AVURLAsset(url: tempFileURL)
  42. let generator = AVAssetImageGenerator(asset: asset)
  43. let duration = try await asset.load(.duration).seconds
  44. return try stride(from: 0, to: duration, by: 1).map { seconds in
  45. let time = CMTime(seconds: seconds, preferredTimescale: 1)
  46. let cg = try generator.copyCGImage(at: time, actualTime: nil)
  47. let image = UIImage(cgImage: cg)
  48. guard let png = image.pngData() else {
  49. fatalError("Failed to encode image to png")
  50. }
  51. return png
  52. }
  53. }
  54. }