diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt index edccde577..6997406c9 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/MetadataFetchHandler.kt @@ -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 } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt index 25bac11ec..1ba6df6e9 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/Mp4ParserHelper.kt @@ -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( diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt index fdb535a30..9619f8336 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/metadata/MultiPage.kt @@ -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? { + 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 diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt index 55ed6ce6c..a2af9f418 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/utils/CollectionUtils.kt @@ -20,6 +20,7 @@ fun MutableList.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