From 08509f6693dc7c2ded200dbe1a97d04a51915589 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Mon, 23 Feb 2026 15:33:21 -0500 Subject: [PATCH] Fix bug where video dimensions aren't always correct in chat view. --- .../securesms/database/AttachmentTable.kt | 7 +- .../securesms/mediasend/MediaRepository.java | 11 +++- .../mediasend/v2/MediaSelectionRepository.kt | 3 +- .../mediasend/v3/MediaSendV3Repository.kt | 2 + .../securesms/mms/SlideFactory.java | 8 ++- .../securesms/util/MediaUtil.java | 66 +++++++++++++++++-- 6 files changed, 85 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt index c3cd1800af..96350ccf8e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/AttachmentTable.kt @@ -2099,12 +2099,15 @@ class AttachmentTable( val contentValues = contentValuesOf( DATA_SIZE to newDataFileInfo.length, CONTENT_TYPE to mediaStream.mimeType, - WIDTH to mediaStream.width, - HEIGHT to mediaStream.height, DATA_FILE to newDataFileInfo.file.absolutePath, DATA_RANDOM to newDataFileInfo.random ) + if (mediaStream.width > 0 && mediaStream.height > 0) { + contentValues.put(WIDTH, mediaStream.width) + contentValues.put(HEIGHT, mediaStream.height) + } + val updateCount = db.update(TABLE_NAME) .values(contentValues) .where("$ID = ? OR $DATA_FILE = ?", attachmentId.id, existingDataFileInfo.file.absolutePath) diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java index 52bf2c19b8..0ee71a82cf 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/MediaRepository.java @@ -262,6 +262,8 @@ public class MediaRepository { if (isImage) { projection = new String[]{Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.ORIENTATION, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE}; + } else if (Build.VERSION.SDK_INT >= 29) { + projection = new String[]{Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, MediaStore.MediaColumns.ORIENTATION, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE, Video.Media.DURATION}; } else { projection = new String[]{Images.Media._ID, Images.Media.MIME_TYPE, Images.Media.DATE_MODIFIED, Images.Media.WIDTH, Images.Media.HEIGHT, Images.Media.SIZE, Video.Media.DURATION}; } @@ -277,7 +279,13 @@ public class MediaRepository { Uri uri = ContentUris.withAppendedId(contentUri, rowId); String mimetype = cursor.getString(cursor.getColumnIndexOrThrow(Images.Media.MIME_TYPE)); long date = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.DATE_MODIFIED)); - int orientation = isImage ? cursor.getInt(cursor.getColumnIndexOrThrow(Images.Media.ORIENTATION)) : 0; + int orientation; + if (isImage) { + orientation = cursor.getInt(cursor.getColumnIndexOrThrow(Images.Media.ORIENTATION)); + } else { + int orientationIdx = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION); + orientation = orientationIdx != -1 ? cursor.getInt(orientationIdx) : 0; + } int width = cursor.getInt(cursor.getColumnIndexOrThrow(getWidthColumn(orientation))); int height = cursor.getInt(cursor.getColumnIndexOrThrow(getHeightColumn(orientation))); long size = cursor.getLong(cursor.getColumnIndexOrThrow(Images.Media.SIZE)); @@ -386,6 +394,7 @@ public class MediaRepository { try (Cursor cursor = context.getContentResolver().query(media.getUri(), null, null, null, null)) { if (cursor != null && cursor.moveToFirst() && cursor.getColumnIndex(OpenableColumns.SIZE) >= 0) { size = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE)); + } else { } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt index 9af968527a..e3422644b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v2/MediaSelectionRepository.kt @@ -68,7 +68,8 @@ class MediaSelectionRepository(context: Context) { return Single.fromCallable { val populatedMedia = mediaRepository.getPopulatedMedia(context, media) - MediaValidator.filterMedia(context, populatedMedia, mediaConstraints, maxSelection, isStory) + val result = MediaValidator.filterMedia(context, populatedMedia, mediaConstraints, maxSelection, isStory) + result }.subscribeOn(Schedulers.io()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt index c6ac68de10..4f2b7fb278 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/mediasend/v3/MediaSendV3Repository.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext import org.signal.core.models.media.Media import org.signal.core.models.media.MediaFolder +import org.signal.core.util.logging.Log import org.signal.mediasend.EditorState import org.signal.mediasend.MediaFilterError import org.signal.mediasend.MediaFilterResult @@ -49,6 +50,7 @@ import kotlin.time.Duration.Companion.seconds */ object MediaSendV3Repository : MediaSendRepository { + private val TAG = Log.tag(MediaSendV3Repository::class.java) private val appContext = AppDependencies.application private val legacyRepository = MediaSelectionRepository(appContext) private val mediaRepository = MediaRepository() diff --git a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java index d8d93f4889..aa445a69c8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/mms/SlideFactory.java @@ -49,8 +49,11 @@ public final class SlideFactory { } else { Slide result = getContentResolverSlideInfo(context, mediaType, uri, width, height, transformProperties); - if (result == null) return getManuallyCalculatedSlideInfo(context, mediaType, uri, width, height, transformProperties); - else return result; + if (result == null) { + return getManuallyCalculatedSlideInfo(context, mediaType, uri, width, height, transformProperties); + } else { + return result; + } } } catch (IOException e) { Log.w(TAG, e); @@ -86,6 +89,7 @@ public final class SlideFactory { Log.d(TAG, "remote slide with size " + fileSize + " took " + (System.currentTimeMillis() - start) + "ms"); return mediaType.createSlide(context, uri, fileName, mimeType, null, fileSize, width, height, false, transformProperties); + } else { } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java index 10564e891a..6da9b73516 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/MediaUtil.java @@ -244,12 +244,8 @@ public class MediaUtil { } catch (ExecutionException e) { Log.w(TAG, "Glide experienced an exception while trying to get GIF dimensions.", e); } - } else if (MediaUtil.hasVideoThumbnail(context, uri)) { - Bitmap thumbnail = MediaUtil.getVideoThumbnail(context, uri, 1000); - - if (thumbnail != null) { - dimens = new Pair<>(thumbnail.getWidth(), thumbnail.getHeight()); - } + } else if (MediaUtil.isVideoType(contentType)) { + dimens = getVideoDimensions(context, uri); } else { InputStream attachmentStream = null; try { @@ -258,6 +254,9 @@ public class MediaUtil { dimens = BitmapUtil.getExifDimensions(new ExifInterface(attachmentStream)); attachmentStream.close(); attachmentStream = null; + if (dimens != null) { + } else { + } } if (dimens == null) { attachmentStream = PartAuthority.getAttachmentStream(context, uri); @@ -286,6 +285,61 @@ public class MediaUtil { return dimens; } + @WorkerThread + private static @NonNull Pair getVideoDimensions(@NonNull Context context, @NonNull Uri uri) { + try { + MediaMetadataRetriever retriever = new MediaMetadataRetriever(); + + try { + if (BlobProvider.isAuthority(uri) && MediaUtil.isVideo(BlobProvider.getMimeType(uri))) { + MediaDataSource source = BlobProvider.getInstance().getMediaDataSource(context, uri); + retriever.setDataSource(source); + } else if (PartAuthority.isAttachmentUri(uri)) { + MediaDataSource source = SignalDatabase.attachments().mediaDataSourceFor(PartAuthority.requireAttachmentId(uri), false); + if (source == null) { + throw new IOException("No media data source for attachment URI"); + } + retriever.setDataSource(source); + } else { + retriever.setDataSource(context, uri); + } + + String widthString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH); + String heightString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT); + String rotationString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION); + + if (widthString == null || heightString == null) { + throw new IOException("Could not extract video dimensions from metadata"); + } + + int width = Integer.parseInt(widthString); + int height = Integer.parseInt(heightString); + int rotation = rotationString != null ? Integer.parseInt(rotationString) : 0; + + if (rotation % 180 == 90) { + //noinspection SuspiciousNameCombination + return new Pair<>(height, width); + } else { + return new Pair<>(width, height); + } + } finally { + retriever.release(); + } + } catch (Exception e) { + Log.w(TAG, "Failed to get video dimensions via metadata for URI: " + uri, e); + } + + if (MediaUtil.hasVideoThumbnail(context, uri)) { + Bitmap thumbnail = MediaUtil.getVideoThumbnail(context, uri, 1000); + if (thumbnail != null) { + return new Pair<>(thumbnail.getWidth(), thumbnail.getHeight()); + } + } else { + } + + return new Pair<>(0, 0); + } + public static boolean isMms(String contentType) { return ContentTypeUtil.isMms(contentType); }