Проблема в том, что файл, созданный с помощью этого кода и сохраненный на телефоне Android, не может быть распознан как файл изображения или MotionPhoto с помощью приложения Google Photo. Но с помощью приложения «Файл» это изображение можно просмотреть как статическую фотографию.
Код: Выделить всё
import android.media.MediaScannerConnection
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.PickVisualMediaRequest
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.Image
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.rounded.ArrowForward
import androidx.compose.material3.Card
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.exifinterface.media.ExifInterface
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import coil3.compose.AsyncImage
import java.io.File
import java.io.InputStream
@Composable
fun ImageItem(
getInputStream: (Uri) -> InputStream?
) {
var imageUri by remember { mutableStateOf(null) }
var videoUri by remember { mutableStateOf(null) }
var imageInputStream by remember { mutableStateOf(null) }
var videoInputStream by remember { mutableStateOf(null) }
val imagePicker =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
imageUri = uri
imageInputStream = getInputStream(uri)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
val videoPicker =
rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
if (uri != null) {
videoUri = uri
videoInputStream = getInputStream(uri)
} else {
Log.d("PhotoPicker", "No media selected")
}
}
Card {
Row(
modifier = Modifier.padding(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.clip(
RoundedCornerShape(10.dp)
)
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.clickable {
imagePicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
}
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (imageUri) {
null -> Image(
painter = painterResource(R.drawable.ic_image),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
else -> AsyncImage(
model = imageUri,
contentDescription = null,
contentScale = ContentScale.Crop
)
}
}
Spacer(modifier = Modifier.width(8.dp))
Box(
modifier = Modifier
.clip(
RoundedCornerShape(10.dp)
)
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.clickable {
videoPicker.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.VideoOnly))
}
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (videoUri) {
null -> Image(
painter = painterResource(R.drawable.ic_video),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
else -> {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videoUri!!))
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ONE
}
}
PlayerSurface(
player = exoPlayer,
surfaceType = SURFACE_TYPE_SURFACE_VIEW
)
}
}
}
Spacer(modifier = Modifier.weight(1f))
val context = LocalContext.current
Icon(
Icons.AutoMirrored.Rounded.ArrowForward,
contentDescription = null,
modifier = Modifier.clickable {
if (imageInputStream != null && videoInputStream != null) {
// Create temp JPEG file to store image data and EXIF data
val tempImageFile =
File.createTempFile("temp_image", ".jpg", context.cacheDir)
val motionPhotoFile = File(
context.filesDir,
"dynamic_photo_${System.currentTimeMillis()}.MP.jpg"
)
try {
// Write the image to the temp file
tempImageFile.outputStream().use { imageOutput ->
imageInputStream?.copyTo(imageOutput)
}
// Write image and video to final file
var videoLength: Int
motionPhotoFile.outputStream().use { outputStream ->
// Write image data
tempImageFile.inputStream().use { it.copyTo(outputStream) }
// Record video offset
val videoStartOffset = outputStream.channel.position()
// Write video data
videoInputStream?.copyTo(outputStream)
// Record video length
videoLength = (outputStream.channel.size() - videoStartOffset).toInt()
}
android.util.Log.e("MotionPhoto", "stream size: ${videoInputStream?.readBytes()?.size}, length: $videoLength")
// Add EXIF and XMP metadata to temp file
val exifInterface = ExifInterface(motionPhotoFile)
exifInterface.setAttribute(ExifInterface.TAG_XMP, getXmpMetadata(videoLength))
exifInterface.saveAttributes()
MediaScannerConnection.scanFile(
context,
arrayOf(motionPhotoFile.absolutePath),
arrayOf("image/jpeg", "image/heic")
) { _, uri ->
Log.d("MotionPhoto", "Save success, URI: $uri")
}
} catch (e: Exception) {
Log.e("MotionPhoto", "MotionPhoto save failed: ${e.message}")
} finally {
// delete temp file
tempImageFile.delete()
}
} else {
Log.e("MotionPhoto", "Image or video input stream is null")
}
}
)
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.border(
1.dp,
MaterialTheme.colorScheme.surfaceTint,
RoundedCornerShape(10.dp)
)
.size(75.dp),
contentAlignment = Alignment.Center
) {
when (videoUri) {
else -> Image(
painter = painterResource(R.drawable.ic_image),
contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.surfaceTint)
)
}
}
}
}
}
@Preview(showBackground = true)
@Composable
fun ImageItemPreview() {
ImageItem { null }
}
fun getXmpMetadata(videoLengthInBytes: Int): String {
return """
""".trimIndent()
}
https://developer.android.com/media/pla ... oto-format
Но примера создания MotionPhoto нет.
Я прочитал некоторые данные EXIF XMP файла MotionPhoto, известные о том, как выглядят метаданные XMP MotionPhoto. Но я понятия не имею, как изменить приведенный выше код для их записи. Или я пытаюсь записать метаданные не в том месте.
Ожидается объединение изображения и видеофайла для MotionPhoto может быть распознан Google Фото.
Подробнее здесь: https://stackoverflow.com/questions/792 ... reating-fi
Мобильная версия