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:
Kyle Bolen
2026-07-30 05:58:51 +00:00
parent bc3a89e858
commit 4171ff04d2
2 changed files with 16 additions and 7 deletions

View File

@@ -365,23 +365,32 @@ private fun PhotoPreviewDialog(
LaunchedEffect(photo.devicePath) {
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) {
java.nio.file.Files.readAllBytes(cachePath)
} 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")
tmpDir.toFile().mkdirs()
val tmpFile = tmpDir.resolve("preview-${photo.filename}")
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) {
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) {
val tmpDir = Paths.get(System.getProperty("java.io.tmpdir"), "photophetch-preview")
val movFilename = photo.companionDevicePath.substringAfterLast("/")

View File

@@ -310,7 +310,7 @@ private fun VerifyResultRow(
Text(
when {
isFailed -> result.error ?: "Failed"
exifUnverified -> "Make: '${result.exifMake?.ifEmpty { "absent" } ?: "absent"}'" +
exifUnverified -> "Origin unknown" +
if (showCheckbox) " — kept on phone" else ""
exifUnavailable -> "${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)) {
Text(result.photo.filename, fontWeight = FontWeight.SemiBold)
Text(when (result.exifVerified) {
true -> "EXIF Make: ${result.exifMake}"
false -> "Make: '${result.exifMake?.ifEmpty { "(absent)" } ?: "(absent)"}' — not a confirmed camera original"
true -> "Camera original — Make: ${result.exifMake}"
false -> "Origin unknown — may not be your photo${if (!result.exifMake.isNullOrBlank()) " (Make: ${result.exifMake})" else ""}"
null -> "EXIF not checked (install exiftool)"
}, style = MaterialTheme.typography.bodySmall,
color = when (result.exifVerified) {