#1436 search more broadly in Samsung SEFD box for motion photo video

This commit is contained in:
Thibault Deckers 2025-02-17 19:32:27 +01:00
parent 5769593799
commit f4108c244b
4 changed files with 18 additions and 41 deletions

View file

@ -14,7 +14,6 @@ import com.adobe.internal.xmp.options.SerializeOptions
import com.adobe.internal.xmp.properties.XMPPropertyInfo
import com.drew.lang.KeyValuePair
import com.drew.lang.Rational
import com.drew.lang.SequentialByteArrayReader
import com.drew.metadata.Tag
import com.drew.metadata.avi.AviDirectory
import com.drew.metadata.exif.ExifDirectoryBase
@ -475,19 +474,9 @@ class MetadataFetchHandler(private val context: Context) : MethodCallHandler {
// and only identify at most one
} else if (isHeic(mimeType)) {
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (_, bytes) ->
val dir = hashMapOf(
metadataMap[Mp4ParserHelper.SAMSUNG_MAKERNOTE_BOX_TYPE] = hashMapOf(
"Size" to bytes.size.toString(),
)
val reader = SequentialByteArrayReader(bytes).apply {
isMotorolaByteOrder = false
}
val start = reader.uInt16
val tag = reader.uInt16
if (start == 0 && tag == Mp4ParserHelper.SEFD_EMBEDDED_VIDEO_TAG) {
val nameSize = reader.uInt32
dir["Embedded Video Type"] = reader.getString(nameSize.toInt())
}
metadataMap[Mp4ParserHelper.SAMSUNG_MAKERNOTE_BOX_TYPE] = dir
}
}

View file

@ -47,7 +47,6 @@ object Mp4ParserHelper {
private const val BOX_SIZE_DANGER_THRESHOLD = 3 * (1 shl 20) // MB
const val SAMSUNG_MAKERNOTE_BOX_TYPE = "sefd"
const val SEFD_EMBEDDED_VIDEO_TAG = 0x0a30
const val SEFD_MOTION_PHOTO_NAME = "MotionPhoto_Data"
private val largerTypeWhitelist = listOf(

View file

@ -11,7 +11,6 @@ import android.os.ParcelFileDescriptor
import android.util.Log
import com.adobe.internal.xmp.XMPMeta
import com.drew.imaging.jpeg.JpegSegmentType
import com.drew.lang.SequentialByteArrayReader
import com.drew.metadata.exif.ExifDirectoryBase
import com.drew.metadata.exif.ExifIFD0Directory
import com.drew.metadata.xmp.XmpDirectory
@ -88,19 +87,23 @@ object MultiPage {
}
fun isHeicSefdMotionPhoto(context: Context, uri: Uri): Boolean {
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (_, bytes) ->
val reader = SequentialByteArrayReader(bytes).apply {
isMotorolaByteOrder = false
}
val start = reader.uInt16
val tag = reader.uInt16
if (start == 0 && tag == Mp4ParserHelper.SEFD_EMBEDDED_VIDEO_TAG) {
val nameSize = reader.uInt32
val name = reader.getString(nameSize.toInt())
return name == Mp4ParserHelper.SEFD_MOTION_PHOTO_NAME
return getHeicSefdMotionPhotoVideoSizing(context, uri) != null
}
private fun getHeicSefdMotionPhotoVideoSizing(context: Context, uri: Uri): Pair<Long, Long>? {
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (sefdOffset, sefdBytes) ->
// we could properly parse each tag until we find the "embedded video" tag (0x0a30)
// but it seems that decoding the SEFT trailer is necessary for this,
// so we simply search for the "MotionPhoto_Data" sequence instead
val name = Mp4ParserHelper.SEFD_MOTION_PHOTO_NAME
val index = sefdBytes.indexOfBytes(name.toByteArray(Charsets.UTF_8))
if (index != -1) {
val videoOffset = sefdOffset + index + name.length
val videoSize = sefdBytes.size - (videoOffset - sefdOffset)
return Pair(videoOffset, videoSize)
}
}
return false
return null
}
private fun getJpegMpfPrimaryRotation(context: Context, uri: Uri, sizeBytes: Long): Int {
@ -392,22 +395,7 @@ object MultiPage {
if (MimeTypes.isHeic(mimeType)) {
// fallback to video within Samsung SEFD box
Mp4ParserHelper.getSamsungSefd(context, uri)?.let { (sefdOffset, bytes) ->
val reader = SequentialByteArrayReader(bytes).apply {
isMotorolaByteOrder = false
}
val start = reader.uInt16
val tag = reader.uInt16
if (start == 0 && tag == Mp4ParserHelper.SEFD_EMBEDDED_VIDEO_TAG) {
val nameSize = reader.uInt32
val name = reader.getString(nameSize.toInt())
if (name == Mp4ParserHelper.SEFD_MOTION_PHOTO_NAME) {
val videoOffset = sefdOffset + reader.position
val videoSize = reader.available().toLong()
return Pair(videoOffset, videoSize)
}
}
}
return getHeicSefdMotionPhotoVideoSizing(context, uri)
}
return null

View file

@ -20,6 +20,7 @@ fun <E> MutableList<E>.compatRemoveIf(filter: (t: E) -> Boolean): Boolean {
}
// Boyer-Moore algorithm for pattern searching
// Returns: an index of the first occurrence of the pattern or -1 if none is found.
fun ByteArray.indexOfBytes(pattern: ByteArray, start: Int = 0): Int {
val n: Int = this.size
val m: Int = pattern.size