168 lines
7.2 KiB
Swift
168 lines
7.2 KiB
Swift
import SwiftUI
|
|
import PhotosUI
|
|
import os.log
|
|
import AVKit
|
|
|
|
struct MediaPickerWithLogging: UIViewControllerRepresentable {
|
|
@Binding var selectedMedia: [MediaType]
|
|
let selectionLimit: Int
|
|
let onDismiss: (() -> Void)?
|
|
|
|
func makeUIViewController(context: Context) -> PHPickerViewController {
|
|
var configuration = PHPickerConfiguration(photoLibrary: PHPhotoLibrary.shared())
|
|
configuration.filter = .any(of: [.videos, .images])
|
|
configuration.selectionLimit = selectionLimit
|
|
configuration.preferredAssetRepresentationMode = .current
|
|
|
|
let picker = PHPickerViewController(configuration: configuration)
|
|
picker.delegate = context.coordinator
|
|
return picker
|
|
}
|
|
|
|
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
Coordinator(self)
|
|
}
|
|
|
|
class Coordinator: NSObject, PHPickerViewControllerDelegate {
|
|
let parent: MediaPickerWithLogging
|
|
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.example.app", category: "MediaPickerWithLogging")
|
|
|
|
init(_ parent: MediaPickerWithLogging) {
|
|
self.parent = parent
|
|
}
|
|
|
|
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
|
|
guard !results.isEmpty else {
|
|
parent.onDismiss?()
|
|
return
|
|
}
|
|
|
|
var processedMedia: [MediaType] = []
|
|
let group = DispatchGroup()
|
|
|
|
for result in results {
|
|
let itemProvider = result.itemProvider
|
|
|
|
if itemProvider.hasItemConformingToTypeIdentifier(UTType.image.identifier) {
|
|
group.enter()
|
|
processImage(itemProvider: itemProvider) { media in
|
|
if let media = media {
|
|
processedMedia.append(media)
|
|
}
|
|
group.leave()
|
|
}
|
|
} else if itemProvider.hasItemConformingToTypeIdentifier(UTType.movie.identifier) {
|
|
group.enter()
|
|
processVideo(itemProvider: itemProvider) { media in
|
|
if let media = media {
|
|
processedMedia.append(media)
|
|
}
|
|
group.leave()
|
|
}
|
|
}
|
|
}
|
|
|
|
group.notify(queue: .main) {
|
|
self.parent.selectedMedia = processedMedia
|
|
self.printMediaInfo(media: processedMedia)
|
|
self.parent.onDismiss?()
|
|
}
|
|
}
|
|
|
|
private func processImage(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
|
|
itemProvider.loadObject(ofClass: UIImage.self) { (object, error) in
|
|
if let image = object as? UIImage {
|
|
completion(.image(image))
|
|
} else {
|
|
self.logger.error("Failed to load image: \(error?.localizedDescription ?? "Unknown error")")
|
|
completion(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func processVideo(itemProvider: NSItemProvider, completion: @escaping (MediaType?) -> Void) {
|
|
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.movie.identifier) { url, error in
|
|
guard let videoURL = url, error == nil else {
|
|
self.logger.error("Failed to load video: \(error?.localizedDescription ?? "Unknown error")")
|
|
completion(nil)
|
|
return
|
|
}
|
|
|
|
let tempDirectory = FileManager.default.temporaryDirectory
|
|
let targetURL = tempDirectory.appendingPathComponent("\(UUID().uuidString).\(videoURL.pathExtension)")
|
|
|
|
do {
|
|
if FileManager.default.fileExists(atPath: targetURL.path) {
|
|
try FileManager.default.removeItem(at: targetURL)
|
|
}
|
|
try FileManager.default.copyItem(at: videoURL, to: targetURL)
|
|
|
|
MediaUtils.extractFirstFrame(from: targetURL) { result in
|
|
switch result {
|
|
case .success(let thumbnail):
|
|
completion(.video(targetURL, thumbnail))
|
|
case .failure(let error):
|
|
self.logger.error("Failed to extract video thumbnail: \(error.localizedDescription)")
|
|
completion(.video(targetURL, nil))
|
|
}
|
|
}
|
|
} catch {
|
|
self.logger.error("Failed to copy video file: \(error.localizedDescription)")
|
|
completion(nil)
|
|
}
|
|
}
|
|
}
|
|
|
|
private func printMediaInfo(media: [MediaType]) {
|
|
print("=== Selected Media Information ===")
|
|
for (index, media) in media.enumerated() {
|
|
print("\nItem \(index + 1):")
|
|
|
|
switch media {
|
|
case .image(let image):
|
|
print("Type: Image")
|
|
print("Dimensions: \(Int(image.size.width))x\(Int(image.size.height))")
|
|
if let data = image.jpegData(compressionQuality: 1.0) {
|
|
print("File Size: \(formatFileSize(Int64(data.count)))")
|
|
}
|
|
|
|
case .video(let url, _):
|
|
print("Type: Video")
|
|
print("File Name: \(url.lastPathComponent)")
|
|
print("File Path: \(url.path)")
|
|
|
|
if let attributes = try? FileManager.default.attributesOfItem(atPath: url.path),
|
|
let fileSize = attributes[.size] as? Int64 {
|
|
print("File Size: \(formatFileSize(fileSize))")
|
|
}
|
|
|
|
let asset = AVURLAsset(url: url)
|
|
let duration = asset.duration.seconds
|
|
print("Duration: \(formatTimeInterval(duration))")
|
|
|
|
if let track = asset.tracks(withMediaType: .video).first {
|
|
let size = track.naturalSize
|
|
print("Video Dimensions: \(Int(size.width))x\(Int(size.height))")
|
|
}
|
|
}
|
|
}
|
|
print("================================\n")
|
|
}
|
|
|
|
private func formatFileSize(_ bytes: Int64) -> String {
|
|
let formatter = ByteCountFormatter()
|
|
formatter.allowedUnits = [.useBytes, .useKB, .useMB, .useGB]
|
|
formatter.countStyle = .file
|
|
return formatter.string(fromByteCount: bytes)
|
|
}
|
|
|
|
private func formatTimeInterval(_ interval: TimeInterval) -> String {
|
|
let formatter = DateComponentsFormatter()
|
|
formatter.allowedUnits = [.hour, .minute, .second]
|
|
formatter.zeroFormattingBehavior = .pad
|
|
return formatter.string(from: interval) ?? "00:00"
|
|
}
|
|
}
|
|
} |