diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index ae2df7c96..529e31cef 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -65,7 +65,6 @@ 18867F4F1C8991EEC56EA932 /* UTType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 897DF5E9A70CE05A632FC8AF /* UTType.swift */; }; 1950A80CD198BED283DFC2CE /* ClientProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 18F2958E6D247AE2516BEEE8 /* ClientProxy.swift */; }; 19FE025AE9BA2959B6589B0D /* RoomMemberDetailsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CC575D1895FA62591451A93 /* RoomMemberDetailsScreen.swift */; }; - 1A402DD75FEE7AA50C0EB4FD /* test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = CB393C632ED176580460214B /* test_video.mov */; }; 1A70A2199394B5EC660934A5 /* MatrixRustSDK in Frameworks */ = {isa = PBXBuildFile; productRef = A678E40E917620059695F067 /* MatrixRustSDK */; }; 1A83DD22F3E6F76B13B6E2F9 /* VideoRoomTimelineItemContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8616254EE40CA8BA5E9BC2 /* VideoRoomTimelineItemContent.swift */; }; 1AB3D8563AB12635250A6A6E /* StaticLocationScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = C15E0017717EAE3A1D02D005 /* StaticLocationScreenCoordinator.swift */; }; @@ -285,6 +284,7 @@ 6AD722DD92E465E56D2885AB /* BugReportScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA919F521E9F0EE3638AFC85 /* BugReportScreen.swift */; }; 6B15FF984906AAFCF9DC4F58 /* OnboardingUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C88046D6A070D9827181C4D /* OnboardingUITests.swift */; }; 6B31508C6334C617360C2EAB /* RoomMemberDetailsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC589E641AE46EFB2962534D /* RoomMemberDetailsViewModelTests.swift */; }; + 6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */ = {isa = PBXBuildFile; fileRef = F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */; }; 6C34237AFB808E38FC8776B9 /* RoomStateEventStringBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D55702474F279D910D2D162 /* RoomStateEventStringBuilder.swift */; }; 6C5A2C454E6C198AB39ED760 /* SharedUserDefaultsKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBA8DC95C079805B0B56E8A9 /* SharedUserDefaultsKeys.swift */; }; 6CD61FAF03E8986523C2ABB8 /* StartChatScreenCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = B3005886F00029F058DB62BE /* StartChatScreenCoordinator.swift */; }; @@ -611,6 +611,7 @@ D02AA6208C7ACB9BE6332394 /* UNNotificationContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = BE148A4FFEE853C5A281500C /* UNNotificationContent.swift */; }; D12F440F7973F1489F61389D /* NotificationSettingsScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0F64447FF544298A6A3BEF85 /* NotificationSettingsScreenModels.swift */; }; D181AC8FF236B7F91C0A8C28 /* MapTiler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AA3F4B285570805CB0CCDD /* MapTiler.swift */; }; + D1EEF0CB0F5D9C15E224E670 /* landscape_test_video.mov in Resources */ = {isa = PBXBuildFile; fileRef = 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */; }; D2A15D03F81342A09340BD56 /* AnalyticsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEFEEE93B82937B2E86F92EB /* AnalyticsScreen.swift */; }; D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; }; D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; }; @@ -1202,6 +1203,7 @@ 98A2932515EA11D3DD8A3506 /* TimelineItemBubbledStylerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineItemBubbledStylerView.swift; sourceTree = ""; }; 9A008E57D52B07B78DFAD1BB /* RoomFlowCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomFlowCoordinator.swift; sourceTree = ""; }; 9A22A05E472533ED3C5A31B3 /* NavigationModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationModule.swift; sourceTree = ""; }; + 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; path = landscape_test_video.mov; sourceTree = ""; }; 9A68BCE6438873D2661D93D0 /* BugReportServiceProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportServiceProtocol.swift; sourceTree = ""; }; 9B06663F7858E45882E63471 /* StaticLocationScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StaticLocationScreen.swift; sourceTree = ""; }; 9B65A314DF40B6BBF775C2BC /* AnalyticsPromptScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsPromptScreenCoordinator.swift; sourceTree = ""; }; @@ -1348,7 +1350,6 @@ CA89A2DD51B6BBE1DA55E263 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; CA90BD288E5AE6BC643AFDDF /* TemplateScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemplateScreenCoordinator.swift; sourceTree = ""; }; CACA846B3E3E9A521D98B178 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/Localizable.strings; sourceTree = ""; }; - CB393C632ED176580460214B /* test_video.mov */ = {isa = PBXFileReference; lastKnownFileType = video.quicktime; path = test_video.mov; sourceTree = ""; }; CBBCC6E74774E79B599625D0 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = ""; }; CBF9AEA706926DD0DA2B954C /* JoinedRoomSize+MemberCount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "JoinedRoomSize+MemberCount.swift"; sourceTree = ""; }; CC03209FDE8CE0810617BFFF /* RoomMembersListScreenMemberCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoomMembersListScreenMemberCell.swift; sourceTree = ""; }; @@ -1445,6 +1446,7 @@ F174A5627CDB3CAF280D1880 /* EmojiPickerScreenModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenModels.swift; sourceTree = ""; }; F17EFA1D3D09FC2F9C5E1CB2 /* MediaProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaProvider.swift; sourceTree = ""; }; F1B8500C152BC59445647DA8 /* UnsupportedRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnsupportedRoomTimelineItem.swift; sourceTree = ""; }; + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */ = {isa = PBXFileReference; path = portrait_test_video.mp4; sourceTree = ""; }; F31F59030205A6F65B057E1A /* MatrixEntityRegexTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MatrixEntityRegexTests.swift; sourceTree = ""; }; F348B5F2C12F9D4F4B4D3884 /* VideoRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoRoomTimelineItem.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; @@ -3059,11 +3061,12 @@ isa = PBXGroup; children = ( 7A5D2323D7B6BF4913EB7EED /* landscape_test_image.jpg */, + 9A2AC7BE17C05CF7D2A22338 /* landscape_test_video.mov */, AF042B0FB2EE88977C91E330 /* portrait_test_image.jpg */, + F2D513D2477B57F90E98EEC0 /* portrait_test_video.mp4 */, D5E26C54362206BBDD096D83 /* test_audio.mp3 */, C733D11B421CFE3A657EF230 /* test_image.png */, 3FFDA99C98BE05F43A92343B /* test_pdf.pdf */, - CB393C632ED176580460214B /* test_video.mov */, ); path = Media; sourceTree = ""; @@ -3615,7 +3618,7 @@ path = Timeline; sourceTree = ""; }; - "TEMP_B63C29BF-60FF-4E4E-9DAA-679D9808BBBF" /* element-x-ios */ = { + "TEMP_07B19EE7-2FF6-44F7-AEA0-799EBE8F8A81" /* element-x-ios */ = { isa = PBXGroup; children = ( 41553551C55AD59885840F0E /* secrets.xcconfig */, @@ -3923,12 +3926,13 @@ buildActionMask = 2147483647; files = ( 3471204F2CC05D4821C35F23 /* landscape_test_image.jpg in Resources */, + D1EEF0CB0F5D9C15E224E670 /* landscape_test_video.mov in Resources */, 858276B19C7C0AD4CA98EA78 /* portrait_test_image.jpg in Resources */, + 6BB6944443C421C722ED1E7D /* portrait_test_video.mp4 in Resources */, 35E975CFDA60E05362A7CF79 /* target.yml in Resources */, 87CEDB8A0696F0D5AE2ABB28 /* test_audio.mp3 in Resources */, 21BF2B7CEDFE3CA67C5355AD /* test_image.png in Resources */, E77469C5CD7F7F58C0AC9752 /* test_pdf.pdf in Resources */, - 1A402DD75FEE7AA50C0EB4FD /* test_video.mov in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift index ff453ab6d..a96b00416 100644 --- a/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift +++ b/ElementX/Sources/Services/Media/MediaUploadingPreprocessor.swift @@ -416,13 +416,13 @@ struct MediaUploadingPreprocessor { let newAsset = AVURLAsset(url: newOutputURL) guard let track = try? await newAsset.loadTracks(withMediaType: .video).first, let durationInSeconds = try? await newAsset.load(.duration).seconds, - let naturalSize = try? await track.load(.naturalSize) else { + let adjustedNaturalSize = try? await track.size else { return .failure(.failedConvertingVideo) } return .success(.init(url: newOutputURL, - height: naturalSize.height, - width: naturalSize.width, + height: adjustedNaturalSize.height, + width: adjustedNaturalSize.width, duration: durationInSeconds * 1000, mimeType: "video/mp4")) } catch { @@ -433,3 +433,25 @@ struct MediaUploadingPreprocessor { } } } + +private extension AVAssetTrack { + var size: CGSize { + get async throws { + let naturalSize = try await load(.naturalSize) + guard mediaType == .video else { + return naturalSize + } + + // The naturalSize does not take the preferredTransform into consideration resulting + // in portrait videos reporting inverted values. + let transform = try await load(.preferredTransform) + + switch (transform.a, transform.b, transform.c, transform.d) { + case (0, 1, -1, 0), (0, -1, 1, 0): + return CGSize(width: naturalSize.height, height: naturalSize.width) + default: + return CGSize(width: naturalSize.width, height: naturalSize.height) + } + } + } +} diff --git a/UnitTests/Resources/Media/test_video.mov b/UnitTests/Resources/Media/landscape_test_video.mov similarity index 100% rename from UnitTests/Resources/Media/test_video.mov rename to UnitTests/Resources/Media/landscape_test_video.mov diff --git a/UnitTests/Resources/Media/portrait_test_video.mp4 b/UnitTests/Resources/Media/portrait_test_video.mp4 new file mode 100644 index 000000000..cec0d8767 --- /dev/null +++ b/UnitTests/Resources/Media/portrait_test_video.mp4 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ffae6753d36919b7001b4ef5c834ab040546319919d1ad36c59c8c33c370f3c7 +size 9775386 diff --git a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift index be355ace9..7d7544f0c 100644 --- a/UnitTests/Sources/MediaUploadingPreprocessorTests.swift +++ b/UnitTests/Sources/MediaUploadingPreprocessorTests.swift @@ -41,8 +41,8 @@ final class MediaUploadingPreprocessorTests: XCTestCase { XCTAssertEqual(audioInfo.size, 764_176) } - func testVideoProcessing() async { - guard let url = Bundle(for: Self.self).url(forResource: "test_video.mov", withExtension: nil) else { + func testLandscapeMovVideoProcessing() async { + guard let url = Bundle(for: Self.self).url(forResource: "landscape_test_video.mov", withExtension: nil) else { XCTFail("Failed retrieving test asset") return } @@ -54,7 +54,7 @@ final class MediaUploadingPreprocessorTests: XCTestCase { } // Check that the file name is preserved - XCTAssertEqual(videoURL.lastPathComponent, "test_video.mp4") + XCTAssertEqual(videoURL.lastPathComponent, "landscape_test_video.mp4") // Check that the thumbnail is generated correctly guard let thumbnailData = try? Data(contentsOf: thumbnailURL), @@ -81,6 +81,46 @@ final class MediaUploadingPreprocessorTests: XCTestCase { XCTAssertEqual(videoInfo.thumbnailInfo?.height, 450) } + func testPortraitMp4VideoProcessing() async { + 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") + + // 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") + XCTAssertEqual(videoInfo.blurhash, "K7C$_zt70LKQMx^+~B9GIU") + XCTAssertEqual(videoInfo.size, 9_775_822) + XCTAssertEqual(videoInfo.width, 1080) + XCTAssertEqual(videoInfo.height, 1920) + XCTAssertEqual(floor(videoInfo.duration ?? 0), 21000) + + XCTAssertNotNil(videoInfo.thumbnailInfo) + XCTAssertEqual(videoInfo.thumbnailInfo?.mimetype, "image/jpeg") + XCTAssertEqual(videoInfo.thumbnailInfo?.size, 82854) + XCTAssertEqual(videoInfo.thumbnailInfo?.width, 337) + XCTAssertEqual(videoInfo.thumbnailInfo?.height, 600) + } + func testLandscapeImageProcessing() async { guard let url = Bundle(for: Self.self).url(forResource: "landscape_test_image.jpg", withExtension: nil) else { XCTFail("Failed retrieving test asset") diff --git a/changelog.d/1262.bugfix b/changelog.d/1262.bugfix new file mode 100644 index 000000000..ec7826c20 --- /dev/null +++ b/changelog.d/1262.bugfix @@ -0,0 +1 @@ +Compute correct sizes for portrait videos \ No newline at end of file