2023-04-21 19:04:02 +03:00
|
|
|
|
//
|
2024-09-06 16:34:30 +03:00
|
|
|
|
// Copyright 2023, 2024 New Vector Ltd.
|
2023-04-21 19:04:02 +03:00
|
|
|
|
//
|
2025-01-06 11:27:37 +01:00
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
|
|
// Please see LICENSE files in the repository root for full details.
|
2023-04-21 19:04:02 +03:00
|
|
|
|
//
|
|
|
|
|
|
2024-10-16 14:50:24 +01:00
|
|
|
|
import UniformTypeIdentifiers
|
2023-04-21 19:04:02 +03:00
|
|
|
|
import XCTest
|
|
|
|
|
|
|
|
|
|
@testable import ElementX
|
|
|
|
|
|
|
|
|
|
final class MediaUploadingPreprocessorTests: XCTestCase {
|
2024-10-14 14:48:59 +01:00
|
|
|
|
var appSettings: AppSettings!
|
|
|
|
|
var mediaUploadingPreprocessor: MediaUploadingPreprocessor!
|
|
|
|
|
|
|
|
|
|
override func setUp() {
|
|
|
|
|
AppSettings.resetAllSettings()
|
|
|
|
|
appSettings = AppSettings()
|
2024-10-31 14:14:14 +00:00
|
|
|
|
appSettings.optimizeMediaUploads = false
|
2024-10-14 14:48:59 +01:00
|
|
|
|
ServiceLocator.shared.register(appSettings: appSettings)
|
|
|
|
|
mediaUploadingPreprocessor = MediaUploadingPreprocessor(appSettings: appSettings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
override func tearDown() {
|
|
|
|
|
AppSettings.resetAllSettings()
|
|
|
|
|
}
|
2023-04-21 19:04:02 +03:00
|
|
|
|
|
|
|
|
|
func testAudioFileProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "test_audio.mp3", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .audio(audioURL, audioInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the file name is preserved
|
|
|
|
|
XCTAssertEqual(audioURL.lastPathComponent, "test_audio.mp3")
|
|
|
|
|
|
|
|
|
|
XCTAssertEqual(audioInfo.mimetype, "audio/mpeg")
|
2024-09-10 10:14:28 +03:00
|
|
|
|
XCTAssertEqual(audioInfo.duration ?? 0, 27, accuracy: 100)
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(audioInfo.size ?? 0, 194_811, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-20 13:30:43 +03:00
|
|
|
|
func testLandscapeMovVideoProcessing() async {
|
2024-10-29 15:59:22 +00:00
|
|
|
|
// Allow an increased execution time as we encode the video twice now.
|
|
|
|
|
executionTimeAllowance = 180
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
2023-07-20 13:30:43 +03:00
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil) else {
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .video(videoURL, thumbnailURL, videoInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the file name is preserved
|
2023-07-20 13:30:43 +03:00
|
|
|
|
XCTAssertEqual(videoURL.lastPathComponent, "landscape_test_video.mp4")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(videoURL.pathExtension, "mp4", "The file extension should match the container we use.")
|
2023-04-21 19:04:02 +03:00
|
|
|
|
|
|
|
|
|
// Check that the thumbnail is generated correctly
|
|
|
|
|
guard let thumbnailData = try? Data(contentsOf: thumbnailURL),
|
|
|
|
|
let thumbnail = UIImage(data: thumbnailData) else {
|
|
|
|
|
XCTFail("Invalid thumbnail")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
|
|
|
|
|
XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
|
|
|
|
|
|
|
|
|
|
// Check resulting video info
|
|
|
|
|
XCTAssertEqual(videoInfo.mimetype, "video/mp4")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(videoInfo.blurhash, "K9F$LJZ9,+8yA9-:yT,@%1")
|
|
|
|
|
XCTAssertEqual(videoInfo.size ?? 0, 4_016_620, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.width, 1280)
|
|
|
|
|
XCTAssertEqual(videoInfo.height, 720)
|
2024-09-10 10:14:28 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.duration ?? 0, 30, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(videoInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.size ?? 0, 183_093, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.width, 800)
|
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.height, 450)
|
2024-10-14 14:48:59 +01:00
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
2024-10-16 14:50:24 +01:00
|
|
|
|
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
2024-10-14 14:48:59 +01:00
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(optimizedVideoURL.pathExtension, "mp4", "The file extension should match the container we use.")
|
|
|
|
|
|
2024-10-14 14:48:59 +01:00
|
|
|
|
// Check optimised video info
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.mimetype, "video/mp4")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.blurhash, "K9F$LJZ9,+8yA9-:yT,@%1")
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.size ?? 0, 4_016_620, accuracy: 100) // Note: The video is already 720p so it doesn't change size.
|
2024-10-29 11:44:46 +00:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.width, 1280)
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.height, 720)
|
2024-10-14 14:48:59 +01:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.duration ?? 0, 30, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-07-20 13:30:43 +03:00
|
|
|
|
func testPortraitMp4VideoProcessing() async {
|
2024-10-29 15:59:22 +00:00
|
|
|
|
// Allow an increased execution time as we encode the video twice now.
|
|
|
|
|
executionTimeAllowance = 180
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
2023-07-20 13:30:43 +03:00
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "portrait_test_video.mp4", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .video(videoURL, thumbnailURL, videoInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check that the file name is preserved
|
|
|
|
|
XCTAssertEqual(videoURL.lastPathComponent, "portrait_test_video.mp4")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(videoURL.pathExtension, "mp4", "The file extension should match the container we use.")
|
2023-07-20 13:30:43 +03:00
|
|
|
|
|
|
|
|
|
// Check that the thumbnail is generated correctly
|
|
|
|
|
guard let thumbnailData = try? Data(contentsOf: thumbnailURL),
|
|
|
|
|
let thumbnail = UIImage(data: thumbnailData) else {
|
|
|
|
|
XCTFail("Invalid thumbnail")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
|
|
|
|
|
XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
|
|
|
|
|
|
|
|
|
|
// Check resulting video info
|
|
|
|
|
XCTAssertEqual(videoInfo.mimetype, "video/mp4")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(videoInfo.blurhash, "KSB{UFO]MuwQS4oJvcaIt8")
|
|
|
|
|
XCTAssertEqual(videoInfo.size ?? 0, 5_824_946, accuracy: 100)
|
2023-07-20 13:30:43 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.width, 1080)
|
|
|
|
|
XCTAssertEqual(videoInfo.height, 1920)
|
2024-09-10 10:14:28 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.duration ?? 0, 21, accuracy: 100)
|
2023-07-20 13:30:43 +03:00
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(videoInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.size ?? 0, 41444, accuracy: 100)
|
2023-07-20 13:30:43 +03:00
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.width, 337)
|
|
|
|
|
XCTAssertEqual(videoInfo.thumbnailInfo?.height, 600)
|
2024-10-14 14:48:59 +01:00
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
2024-10-16 14:50:24 +01:00
|
|
|
|
case let .video(optimizedVideoURL, _, optimizedVideoInfo) = optimizedResult else {
|
2024-10-14 14:48:59 +01:00
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(optimizedVideoURL.pathExtension, "mp4", "The file extension should match the container we use.")
|
|
|
|
|
|
2024-10-14 14:48:59 +01:00
|
|
|
|
// Check optimised video info
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.mimetype, "video/mp4")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.blurhash, "KSB{UFO]MuwQS4oJvcaIt8")
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.size ?? 0, 12_169_117, accuracy: 100) // Note: This is slightly stupid because it is larger now 🤦♂️
|
2024-10-29 11:44:46 +00:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.width, 720)
|
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.height, 1280)
|
2024-10-14 14:48:59 +01:00
|
|
|
|
XCTAssertEqual(optimizedVideoInfo.duration ?? 0, 30, accuracy: 100)
|
2023-07-20 13:30:43 +03:00
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 19:04:02 +03:00
|
|
|
|
func testLandscapeImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "landscape_test_image.jpg", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(imageInfo.blurhash, "K%I#.NofkC_4ayayxujsWB")
|
2024-02-27 16:22:47 +02:00
|
|
|
|
XCTAssertEqual(imageInfo.size ?? 0, 3_305_795, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertEqual(imageInfo.width, 6103)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 2621)
|
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.size ?? 0, 87733, accuracy: 100)
|
2023-09-01 14:58:36 +03:00
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 800)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 344)
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check optimised image info
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.blurhash, "K%I#.NofkC_4ayaxxujsWB")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.size ?? 0, 524_226, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 2048)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 879)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPortraitImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "portrait_test_image.jpg", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/jpeg")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(imageInfo.blurhash, "KdE|0Ls+RP^-n*RP%OWAV@")
|
2024-02-27 16:22:47 +02:00
|
|
|
|
XCTAssertEqual(imageInfo.size ?? 0, 4_414_666, accuracy: 100)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertEqual(imageInfo.width, 3024)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 4032)
|
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.size ?? 0, 258_914, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 600)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 800)
|
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check optimised image info
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.blurhash, "KdE|0Ls+RP^-n*RP%OWAV@")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.size ?? 0, 1_462_937, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 1536)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 2048)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testPNGImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "test_image.png", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, _, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: convertedImageURL), "image/png", "PNGs should always be sent as PNG to preserve the alpha channel.")
|
|
|
|
|
XCTAssertEqual(convertedImageURL.pathExtension, "png", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/png")
|
|
|
|
|
XCTAssertEqual(imageInfo.blurhash, "K0TSUA~qfQ~qj[fQfQfQfQ")
|
|
|
|
|
XCTAssertEqual(imageInfo.size ?? 0, 4868, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(imageInfo.width, 240)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 240)
|
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.size ?? 0, 1725, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 240)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 240)
|
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: optimizedImageURL), "image/png", "PNGs should always be sent as PNG to preserve the alpha channel.")
|
|
|
|
|
XCTAssertEqual(optimizedImageURL.pathExtension, "png", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Check optimised image info
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/png")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.blurhash, "K0TSUA~qfQ~qj[fQfQfQfQ")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.size ?? 0, 8199, accuracy: 100)
|
|
|
|
|
// Assert that resizing didn't upscale to the maxPixelSize.
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 240)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 240)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testHEICImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "test_apple_image.heic", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: convertedImageURL), "image/heic", "Unoptimised HEICs should always be sent as is.")
|
|
|
|
|
XCTAssertEqual(convertedImageURL.pathExtension, "heic", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/heic")
|
|
|
|
|
XCTAssertEqual(imageInfo.blurhash, "KGD]3ns:T00$kWxFXmt6xv")
|
2024-11-12 12:24:08 +00:00
|
|
|
|
XCTAssertEqual(imageInfo.size ?? 0, 1_850_479, accuracy: 100)
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(imageInfo.width, 3024)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 4032)
|
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.size ?? 0, 218_108, accuracy: 100)
|
2023-09-01 14:58:36 +03:00
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 600)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 800)
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: optimizedImageURL), "image/jpeg", "Optimised HEICs should always be converted to JPEG for compatibility.")
|
|
|
|
|
XCTAssertEqual(optimizedImageURL.pathExtension, "jpeg", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Check optimised image info
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.blurhash, "KGD]3ns:T00#kWxFb^s:xv")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.size ?? 0, 1_049_393, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 1536)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 2048)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func testGIFImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "test_animated_image.gif", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
guard let originalSize = try? FileManager.default.sizeForItem(at: url), originalSize > 0 else {
|
|
|
|
|
XCTFail("Failed fetching test asset's original size")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, _, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: convertedImageURL), "image/gif", "GIFs should always be sent as GIF to preserve the animation.")
|
|
|
|
|
XCTAssertEqual(convertedImageURL.pathExtension, "gif", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/gif")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(imageInfo.blurhash, "KpRMPTj[_NxuaeRj%MofMx")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(imageInfo.size ?? 0, UInt64(originalSize), accuracy: 100)
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(imageInfo.width, 331)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 472)
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.mimetype, "image/jpeg")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.size ?? 0, 34215, accuracy: 100)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 331)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 472)
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, _, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Make sure the output file matches the image info.
|
|
|
|
|
XCTAssertEqual(mimeType(from: optimizedImageURL), "image/gif", "GIFs should always be sent as GIF to preserve the animation.")
|
|
|
|
|
XCTAssertEqual(optimizedImageURL.pathExtension, "gif", "The file extension should match the MIME type.")
|
|
|
|
|
|
|
|
|
|
// Ensure optimised image is still the same as the original image.
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/gif")
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(optimizedImageInfo.blurhash, "KpRMPTj[_NxuaeRj%MofMx")
|
2024-10-16 14:50:24 +01:00
|
|
|
|
XCTAssertEqual(optimizedImageInfo.size ?? 0, UInt64(originalSize), accuracy: 100)
|
2025-02-11 11:53:35 +00:00
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 331)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 472)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|
|
|
|
|
|
2024-10-21 15:28:23 +01:00
|
|
|
|
func testRotatedImageProcessing() async {
|
|
|
|
|
guard let url = Bundle(for: Self.self).url(forResource: "test_rotated_image.jpg", withExtension: nil) else {
|
|
|
|
|
XCTFail("Failed retrieving test asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard case let .success(result) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(convertedImageURL, thumbnailURL, imageInfo) = result else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: convertedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check resulting image info
|
|
|
|
|
XCTAssertEqual(imageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(imageInfo.width, 2848)
|
|
|
|
|
XCTAssertEqual(imageInfo.height, 4272)
|
|
|
|
|
|
|
|
|
|
XCTAssertNotNil(imageInfo.thumbnailInfo)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.width, 533)
|
|
|
|
|
XCTAssertEqual(imageInfo.thumbnailInfo?.height, 800)
|
|
|
|
|
|
|
|
|
|
// Repeat with optimised media setting
|
|
|
|
|
appSettings.optimizeMediaUploads = true
|
|
|
|
|
|
|
|
|
|
guard case let .success(optimizedResult) = await mediaUploadingPreprocessor.processMedia(at: url),
|
|
|
|
|
case let .image(optimizedImageURL, thumbnailURL, optimizedImageInfo) = optimizedResult else {
|
|
|
|
|
XCTFail("Failed processing asset")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
compare(originalImageAt: url, toConvertedImageAt: optimizedImageURL, withThumbnailAt: thumbnailURL)
|
|
|
|
|
|
|
|
|
|
// Check optimised image info
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.mimetype, "image/jpeg")
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.width, 1365)
|
|
|
|
|
XCTAssertEqual(optimizedImageInfo.height, 2048)
|
|
|
|
|
}
|
|
|
|
|
|
2023-04-21 19:04:02 +03:00
|
|
|
|
// MARK: - Private
|
|
|
|
|
|
|
|
|
|
private func compare(originalImageAt originalImageURL: URL, toConvertedImageAt convertedImageURL: URL, withThumbnailAt thumbnailURL: URL) {
|
|
|
|
|
guard let originalImageData = try? Data(contentsOf: originalImageURL),
|
|
|
|
|
let originalImage = UIImage(data: originalImageData),
|
|
|
|
|
let convertedImageData = try? Data(contentsOf: convertedImageURL),
|
|
|
|
|
let convertedImage = UIImage(data: convertedImageData) else {
|
|
|
|
|
fatalError()
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-16 14:50:24 +01:00
|
|
|
|
if appSettings.optimizeMediaUploads {
|
|
|
|
|
// Check that new image has been scaled within the requirements for an optimised image
|
|
|
|
|
XCTAssert(convertedImage.size.width <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
|
|
|
|
XCTAssert(convertedImage.size.height <= MediaUploadingPreprocessor.Constants.optimizedMaxPixelSize)
|
|
|
|
|
} else {
|
|
|
|
|
// Check that the file name is preserved
|
|
|
|
|
XCTAssertEqual(originalImageURL.lastPathComponent, convertedImageURL.lastPathComponent)
|
|
|
|
|
// Check that new image is the same size as the original one
|
|
|
|
|
XCTAssertEqual(originalImage.size, convertedImage.size)
|
|
|
|
|
}
|
2023-04-21 19:04:02 +03:00
|
|
|
|
|
|
|
|
|
// Check that the GPS data has been stripped
|
2024-10-14 14:48:59 +01:00
|
|
|
|
let originalMetadata = metadata(from: originalImageData)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertNotNil(originalMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)"))
|
|
|
|
|
|
2024-10-14 14:48:59 +01:00
|
|
|
|
let convertedMetadata = metadata(from: convertedImageData)
|
2023-04-21 19:04:02 +03:00
|
|
|
|
XCTAssertNil(convertedMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)"))
|
|
|
|
|
|
|
|
|
|
// Check that the thumbnail is generated correctly
|
|
|
|
|
guard let thumbnailData = try? Data(contentsOf: thumbnailURL),
|
|
|
|
|
let thumbnail = UIImage(data: thumbnailData) else {
|
|
|
|
|
XCTFail("Invalid thumbnail")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-01 14:58:36 +03:00
|
|
|
|
if thumbnail.size.width > thumbnail.size.height {
|
|
|
|
|
XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
|
|
|
|
|
XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
|
|
|
|
|
} else {
|
|
|
|
|
XCTAssert(thumbnail.size.width <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.height)
|
|
|
|
|
XCTAssert(thumbnail.size.height <= MediaUploadingPreprocessor.Constants.maximumThumbnailSize.width)
|
|
|
|
|
}
|
2024-10-14 14:48:59 +01:00
|
|
|
|
|
|
|
|
|
let thumbnailMetadata = metadata(from: thumbnailData)
|
|
|
|
|
XCTAssertNil(thumbnailMetadata.value(forKeyPath: "\(kCGImagePropertyGPSDictionary)"))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private func metadata(from imageData: Data) -> NSDictionary {
|
|
|
|
|
guard let imageSource = CGImageSourceCreateWithData(imageData as NSData, nil) else {
|
|
|
|
|
XCTFail("Invalid asset")
|
|
|
|
|
return [:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let convertedMetadata: NSDictionary = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) else {
|
|
|
|
|
XCTFail("Test asset is expected to contain metadata")
|
|
|
|
|
return [:]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return convertedMetadata
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|
2024-10-16 14:50:24 +01:00
|
|
|
|
|
|
|
|
|
private func mimeType(from url: URL) -> String? {
|
|
|
|
|
guard let imageSource = CGImageSourceCreateWithURL(url as NSURL, nil),
|
|
|
|
|
let typeIdentifier = CGImageSourceGetType(imageSource),
|
|
|
|
|
let type = UTType(typeIdentifier as String),
|
|
|
|
|
let mimeType = type.preferredMIMEType else {
|
|
|
|
|
XCTFail("Failed to get mimetype from URL.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
return mimeType
|
|
|
|
|
}
|
2023-04-21 19:04:02 +03:00
|
|
|
|
}
|