Extract Media and TransformProperties to core/util/models.

This commit is contained in:
Alex Hart
2026-01-20 15:21:27 -04:00
committed by GitHub
parent f505d48d80
commit 471b214b4a
124 changed files with 413 additions and 302 deletions

View File

@@ -20,21 +20,17 @@ import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.media.MediaDataSource
import android.os.Parcelable
import android.text.TextUtils
import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import androidx.core.content.contentValuesOf
import com.bumptech.glide.Glide
import com.fasterxml.jackson.annotation.JsonProperty
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import okio.ByteString.Companion.toByteString
import org.json.JSONArray
import org.json.JSONException
import org.signal.core.models.backup.MediaId
import org.signal.core.models.backup.MediaName
import org.signal.core.models.media.TransformProperties
import org.signal.core.util.Base64
import org.signal.core.util.SqlUtil
import org.signal.core.util.ThreadUtil
@@ -110,7 +106,6 @@ import org.thoughtcrime.securesms.mms.DecryptableUri
import org.thoughtcrime.securesms.mms.MediaStream
import org.thoughtcrime.securesms.mms.MmsException
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.thoughtcrime.securesms.stickers.StickerLocator
import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.FileUtils
@@ -124,7 +119,6 @@ import org.thoughtcrime.securesms.video.EncryptedMediaDataSource
import org.whispersystems.signalservice.api.attachment.AttachmentUploadResult
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
import org.whispersystems.signalservice.internal.util.JsonUtil
import java.io.ByteArrayInputStream
import java.io.File
import java.io.FileNotFoundException
@@ -134,7 +128,6 @@ import java.security.DigestInputStream
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.util.LinkedList
import java.util.Optional
import java.util.UUID
import kotlin.text.appendLine
import kotlin.time.Duration
@@ -2256,7 +2249,7 @@ class AttachmentTable(
.where("$ID = ?", attachmentId.id)
.run()
.readToSingleObject {
TransformProperties.parse(it.requireString(TRANSFORM_PROPERTIES))
parseTransformProperties(it.requireString(TRANSFORM_PROPERTIES))
}
}
@@ -2323,7 +2316,7 @@ class AttachmentTable(
},
blurHash = if (MediaUtil.isAudioType(contentType)) null else BlurHash.parseOrNull(jsonObject.getString(BLUR_HASH)),
audioHash = if (MediaUtil.isAudioType(contentType)) AudioHash.parseOrNull(jsonObject.getString(BLUR_HASH)) else null,
transformProperties = TransformProperties.parse(jsonObject.getString(TRANSFORM_PROPERTIES)),
transformProperties = parseTransformProperties(jsonObject.getString(TRANSFORM_PROPERTIES)),
displayOrder = jsonObject.getInt(DISPLAY_ORDER),
uploadTimestamp = jsonObject.getLong(UPLOAD_TIMESTAMP),
dataHash = jsonObject.getString(DATA_HASH_END),
@@ -3369,7 +3362,7 @@ class AttachmentTable(
stickerLocator = cursor.readStickerLocator(),
blurHash = if (MediaUtil.isAudioType(contentType)) null else BlurHash.parseOrNull(cursor.requireString(BLUR_HASH)),
audioHash = if (MediaUtil.isAudioType(contentType)) AudioHash.parseOrNull(cursor.requireString(BLUR_HASH)) else null,
transformProperties = TransformProperties.parse(cursor.requireString(TRANSFORM_PROPERTIES)),
transformProperties = parseTransformProperties(cursor.requireString(TRANSFORM_PROPERTIES)),
displayOrder = cursor.requireInt(DISPLAY_ORDER),
uploadTimestamp = cursor.requireLong(UPLOAD_TIMESTAMP),
dataHash = cursor.requireString(DATA_HASH_END),
@@ -3400,7 +3393,7 @@ class AttachmentTable(
random = random,
hashStart = this.requireString(DATA_HASH_START),
hashEnd = this.requireString(DATA_HASH_END),
transformProperties = TransformProperties.parse(this.requireString(TRANSFORM_PROPERTIES)),
transformProperties = parseTransformProperties(this.requireString(TRANSFORM_PROPERTIES)),
uploadTimestamp = this.requireLong(UPLOAD_TIMESTAMP),
archiveCdn = this.requireIntOrNull(ARCHIVE_CDN),
archiveTransferState = this.requireInt(ARCHIVE_TRANSFER_STATE),
@@ -3545,7 +3538,9 @@ class AttachmentTable(
.readToSingleLong(0)
}
val uniqueEligibleMediaNamesWithThumbnailsCount = readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $THUMBNAIL_FILE NOT NULL AND $QUOTE = 0 AND $MESSAGE_ID != $WALLPAPER_MESSAGE_ID)").readToSingleLong(-1L)
val uniqueEligibleMediaNamesWithThumbnailsCount =
readableDatabase.query("SELECT COUNT(*) FROM (SELECT DISTINCT $DATA_HASH_END, $REMOTE_KEY FROM $TABLE_NAME WHERE $DATA_HASH_END NOT NULL AND $REMOTE_KEY NOT NULL AND $THUMBNAIL_FILE NOT NULL AND $QUOTE = 0 AND $MESSAGE_ID != $WALLPAPER_MESSAGE_ID)")
.readToSingleLong(-1L)
val archiveStatusMediaNameThumbnailCounts: Map<ArchiveTransferState, Long> = ArchiveTransferState.entries.associateWith { state ->
readableDatabase.query(
"""
@@ -3802,119 +3797,6 @@ class AttachmentTable(
val random: ByteArray
)
@Serializable
@Parcelize
data class TransformProperties(
@JsonProperty("skipTransform")
@JvmField
val skipTransform: Boolean = false,
@JsonProperty("videoTrim")
@JvmField
val videoTrim: Boolean = false,
@JsonProperty("videoTrimStartTimeUs")
@JvmField
val videoTrimStartTimeUs: Long = 0,
@JsonProperty("videoTrimEndTimeUs")
@JvmField
val videoTrimEndTimeUs: Long = 0,
@JsonProperty("sentMediaQuality")
@JvmField
val sentMediaQuality: Int = SentMediaQuality.STANDARD.code,
@JsonProperty("mp4Faststart")
@JvmField
val mp4FastStart: Boolean = false
) : Parcelable {
fun shouldSkipTransform(): Boolean {
return skipTransform
}
@IgnoredOnParcel
@JsonProperty("videoEdited")
val videoEdited: Boolean = videoTrim
fun withSkipTransform(): TransformProperties {
return this.copy(
skipTransform = true
)
}
fun withMp4FastStart(): TransformProperties {
return this.copy(mp4FastStart = true)
}
fun serialize(): String {
return JsonUtil.toJson(this)
}
companion object {
private val DEFAULT_MEDIA_QUALITY = SentMediaQuality.STANDARD.code
@JvmStatic
fun empty(): TransformProperties {
return TransformProperties(
skipTransform = false,
videoTrim = false,
videoTrimStartTimeUs = 0,
videoTrimEndTimeUs = 0,
sentMediaQuality = DEFAULT_MEDIA_QUALITY,
mp4FastStart = false
)
}
fun forSkipTransform(): TransformProperties {
return TransformProperties(
skipTransform = true,
videoTrim = false,
videoTrimStartTimeUs = 0,
videoTrimEndTimeUs = 0,
sentMediaQuality = DEFAULT_MEDIA_QUALITY,
mp4FastStart = false
)
}
fun forVideoTrim(videoTrimStartTimeUs: Long, videoTrimEndTimeUs: Long): TransformProperties {
return TransformProperties(
skipTransform = false,
videoTrim = true,
videoTrimStartTimeUs = videoTrimStartTimeUs,
videoTrimEndTimeUs = videoTrimEndTimeUs,
sentMediaQuality = DEFAULT_MEDIA_QUALITY,
mp4FastStart = false
)
}
@JvmStatic
fun forSentMediaQuality(currentProperties: Optional<TransformProperties>, sentMediaQuality: SentMediaQuality): TransformProperties {
val existing = currentProperties.orElse(empty())
return existing.copy(sentMediaQuality = sentMediaQuality.code)
}
@JvmStatic
fun forSentMediaQuality(sentMediaQuality: Int): TransformProperties {
return TransformProperties(sentMediaQuality = sentMediaQuality)
}
@JvmStatic
fun parse(serialized: String?): TransformProperties {
return if (serialized == null) {
empty()
} else {
try {
JsonUtil.fromJson(serialized, TransformProperties::class.java)
} catch (e: IOException) {
Log.w(TAG, "Failed to parse TransformProperties!", e)
empty()
}
}
}
}
}
enum class ThumbnailRestoreState(val value: Int) {
/** No thumbnail downloaded. */
NONE(0),

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database
import org.signal.core.models.media.TransformProperties
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.mms.SentMediaQuality
import org.whispersystems.signalservice.internal.util.JsonUtil
import java.io.IOException
import java.util.Optional
private val TAG = Log.tag(TransformProperties::class.java)
/**
* Serializes the TransformProperties to a JSON string using Jackson.
*/
fun TransformProperties.serialize(): String {
return JsonUtil.toJson(this)
}
/**
* Parses a JSON string to create a TransformProperties instance.
*/
fun parseTransformProperties(serialized: String?): TransformProperties {
return if (serialized == null) {
TransformProperties.empty()
} else {
try {
JsonUtil.fromJson(serialized, TransformProperties::class.java)
} catch (e: IOException) {
Log.w(TAG, "Failed to parse TransformProperties!", e)
TransformProperties.empty()
}
}
}
/**
* Creates TransformProperties for the given media quality, preserving existing properties.
*/
fun transformPropertiesForSentMediaQuality(currentProperties: Optional<TransformProperties>, sentMediaQuality: SentMediaQuality): TransformProperties {
val existing = currentProperties.orElse(TransformProperties.empty())
return existing.copy(sentMediaQuality = sentMediaQuality.code)
}