Fix HEIC preview + clean up EXIF unverified messages
PhotoPreviewDialog: always use sips-converted JPEG from cache for display. On cache miss: pull file, run sips --setProperty format jpeg to convert, cache the JPEG. HEIC now displays inline without QuickTime. Verify/VerifyRow: replace 'Make: absent — not a confirmed camera original' with 'Origin unknown' (row) and 'Origin unknown — may not be your photo' (preview dialog). Less developer-jargon, more understandable.
This commit is contained in:
@@ -365,23 +365,32 @@ private fun PhotoPreviewDialog(
|
|||||||
|
|
||||||
LaunchedEffect(photo.devicePath) {
|
LaunchedEffect(photo.devicePath) {
|
||||||
val bytes = withContext(Dispatchers.IO) {
|
val bytes = withContext(Dispatchers.IO) {
|
||||||
// Use cached thumbnail if available
|
// Always use the cached JPEG (sips-converted) — works for HEIC too
|
||||||
if (cachePath.toFile().exists() && cachePath.toFile().length() > 0) {
|
if (cachePath.toFile().exists() && cachePath.toFile().length() > 0) {
|
||||||
java.nio.file.Files.readAllBytes(cachePath)
|
java.nio.file.Files.readAllBytes(cachePath)
|
||||||
} else {
|
} else {
|
||||||
// Pull fresh for preview
|
// Pull to temp, convert to JPEG via sips, cache it
|
||||||
val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview")
|
val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview")
|
||||||
tmpDir.toFile().mkdirs()
|
tmpDir.toFile().mkdirs()
|
||||||
val tmpFile = tmpDir.resolve("preview-${photo.filename}")
|
val tmpFile = tmpDir.resolve("preview-${photo.filename}")
|
||||||
val ok = pullFn(photo.devicePath, tmpFile)
|
val ok = pullFn(photo.devicePath, tmpFile)
|
||||||
if (ok && tmpFile.toFile().exists()) java.nio.file.Files.readAllBytes(tmpFile) else null
|
if (ok && tmpFile.toFile().exists()) {
|
||||||
|
val sips = ProcessBuilder("sips", "--resampleWidth", "800",
|
||||||
|
"--setProperty", "format", "jpeg",
|
||||||
|
tmpFile.toString(), "--out", cachePath.toString())
|
||||||
|
.redirectErrorStream(true).start()
|
||||||
|
sips.inputStream.readBytes()
|
||||||
|
sips.waitFor()
|
||||||
|
tmpFile.toFile().delete()
|
||||||
|
if (cachePath.toFile().exists()) java.nio.file.Files.readAllBytes(cachePath) else null
|
||||||
|
} else null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bytes != null) {
|
if (bytes != null) {
|
||||||
runCatching { bitmap = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap() }
|
runCatching { bitmap = SkiaImage.makeFromEncoded(bytes).toComposeImageBitmap() }
|
||||||
}
|
}
|
||||||
|
|
||||||
// For Live Photos: also open the companion MOV in system viewer
|
// For Live Photos: open the companion MOV in system viewer
|
||||||
if (photo.isLivePhotoStill && photo.companionDevicePath != null) {
|
if (photo.isLivePhotoStill && photo.companionDevicePath != null) {
|
||||||
val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview")
|
val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview")
|
||||||
val movFilename = photo.companionDevicePath.substringAfterLast("/")
|
val movFilename = photo.companionDevicePath.substringAfterLast("/")
|
||||||
|
|||||||
@@ -310,7 +310,7 @@ private fun VerifyResultRow(
|
|||||||
Text(
|
Text(
|
||||||
when {
|
when {
|
||||||
isFailed -> result.error ?: "Failed"
|
isFailed -> result.error ?: "Failed"
|
||||||
exifUnverified -> "⚠ Make: '${result.exifMake?.ifEmpty { "absent" } ?: "absent"}'" +
|
exifUnverified -> "⚠ Origin unknown" +
|
||||||
if (showCheckbox) " — kept on phone" else ""
|
if (showCheckbox) " — kept on phone" else ""
|
||||||
exifUnavailable -> "${result.photo.sizeBytes / 1024} KB"
|
exifUnavailable -> "${result.photo.sizeBytes / 1024} KB"
|
||||||
else -> "✓ ${result.exifMake} · ${result.photo.sizeBytes / 1024} KB"
|
else -> "✓ ${result.exifMake} · ${result.photo.sizeBytes / 1024} KB"
|
||||||
@@ -381,8 +381,8 @@ private fun ResultPreviewDialog(result: CopyResult, onDismiss: () -> Unit) {
|
|||||||
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
Text(result.photo.filename, fontWeight = FontWeight.SemiBold)
|
Text(result.photo.filename, fontWeight = FontWeight.SemiBold)
|
||||||
Text(when (result.exifVerified) {
|
Text(when (result.exifVerified) {
|
||||||
true -> "✓ EXIF Make: ${result.exifMake}"
|
true -> "✓ Camera original — Make: ${result.exifMake}"
|
||||||
false -> "⚠ Make: '${result.exifMake?.ifEmpty { "(absent)" } ?: "(absent)"}' — not a confirmed camera original"
|
false -> "⚠ Origin unknown — may not be your photo${if (!result.exifMake.isNullOrBlank()) " (Make: ${result.exifMake})" else ""}"
|
||||||
null -> "EXIF not checked (install exiftool)"
|
null -> "EXIF not checked (install exiftool)"
|
||||||
}, style = MaterialTheme.typography.bodySmall,
|
}, style = MaterialTheme.typography.bodySmall,
|
||||||
color = when (result.exifVerified) {
|
color = when (result.exifVerified) {
|
||||||
|
|||||||
Reference in New Issue
Block a user