Fix exiftool warning pollution + verify screen messaging

Copier: exiftool output parsed with stderr separate; Warning:/Error:
lines stripped from stdout so IPTC/XMP warnings don't appear as
the Make value.

VerifyScreen:
- 'kept on phone' suffix on unverified only shown when deleteAfterVerify=on
- Summary header adapts: no delete messaging when delete is off,
  '(pre-unchecked)' label only when checkboxes are visible
This commit is contained in:
Kyle Bolen
2026-07-30 05:23:48 +00:00
parent 35984b0b85
commit f90baa92d4
2 changed files with 22 additions and 9 deletions

View File

@@ -249,14 +249,21 @@ class Copier(
private fun readExifMake(file: Path): String? { private fun readExifMake(file: Path): String? {
return try { return try {
val process = ProcessBuilder("exiftool", "-Make", "-s3", file.absolutePathString()) val process = ProcessBuilder("exiftool", "-Make", "-s3", file.absolutePathString())
.redirectErrorStream(true) .redirectErrorStream(false) // keep stderr separate
.start() .start()
val output = process.inputStream.bufferedReader().readText().trim() val stdout = process.inputStream.bufferedReader().readText()
process.errorStream.readBytes() // drain stderr
process.waitFor() process.waitFor()
// -s3 gives bare value with no label. Empty output = tag absent.
output.ifEmpty { "" } // empty string = no Make tag (not null = exiftool ran fine) // Strip warning lines (exiftool prints "Warning: ..." to stdout with -s3)
val make = stdout.lines()
.filter { !it.startsWith("Warning:") && !it.startsWith("Error:") && it.isNotBlank() }
.firstOrNull()
?.trim()
?: "" // empty string = tag absent, exiftool ran fine
make
} catch (e: Exception) { } catch (e: Exception) {
// exiftool not installed or not on PATH
log.debug("exiftool not available: ${e.message}") log.debug("exiftool not available: ${e.message}")
null null
} }

View File

@@ -104,6 +104,7 @@ fun VerifyScreen(state: AppState) {
failure = r.failureCount, failure = r.failureCount,
exifUnverified = r.results.count { it.exifVerified == false }, exifUnverified = r.results.count { it.exifVerified == false },
destination = "${r.job.stagingRoot}/${r.job.folderName}", destination = "${r.job.stagingRoot}/${r.job.folderName}",
deleteAfterVerify = importConfig.deleteAfterVerify,
) )
Spacer(Modifier.height(12.dp)) Spacer(Modifier.height(12.dp))
@@ -227,7 +228,7 @@ fun VerifyScreen(state: AppState) {
} }
@Composable @Composable
private fun ResultSummaryHeader(success: Int, failure: Int, exifUnverified: Int, destination: String) { private fun ResultSummaryHeader(success: Int, failure: Int, exifUnverified: Int, destination: String, deleteAfterVerify: Boolean) {
Card(colors = CardDefaults.cardColors( Card(colors = CardDefaults.cardColors(
containerColor = if (failure == 0) MaterialTheme.colorScheme.primaryContainer containerColor = if (failure == 0) MaterialTheme.colorScheme.primaryContainer
else MaterialTheme.colorScheme.errorContainer, else MaterialTheme.colorScheme.errorContainer,
@@ -237,8 +238,12 @@ private fun ResultSummaryHeader(success: Int, failure: Int, exifUnverified: Int,
if (failure == 0) "Import complete ✓" else "Import complete with errors", if (failure == 0) "Import complete ✓" else "Import complete with errors",
style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold, style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold,
) )
Text("$success copied, $failure failed" + Text(buildString {
if (exifUnverified > 0) ", $exifUnverified EXIF unverified (⚠ not auto-deleted)" else "") append("$success copied")
if (failure > 0) append(", $failure failed")
if (exifUnverified > 0 && deleteAfterVerify) append(", $exifUnverified unverified (⚠ pre-unchecked)")
else if (exifUnverified > 0) append(", $exifUnverified unverified EXIF")
})
Text("$destination", style = MaterialTheme.typography.bodySmall, Text("$destination", style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant) color = MaterialTheme.colorScheme.onSurfaceVariant)
} }
@@ -296,7 +301,8 @@ private fun CopyResultRow(
Text( Text(
when { when {
isFailed -> result.error ?: "Failed" isFailed -> result.error ?: "Failed"
exifUnverified -> "⚠ Make: '${result.exifMake?.ifEmpty { "(absent)" } ?: "(absent)"}' — kept on phone" exifUnverified -> "⚠ Make: '${result.exifMake?.ifEmpty { "(absent)" } ?: "(absent)"}'" +
if (showCheckbox) " — kept on phone" else ""
exifUnavailable -> "Copied · ${result.photo.sizeBytes / 1024} KB" exifUnavailable -> "Copied · ${result.photo.sizeBytes / 1024} KB"
else -> "✓ Make: ${result.exifMake} · ${result.photo.sizeBytes / 1024} KB" else -> "✓ Make: ${result.exifMake} · ${result.photo.sizeBytes / 1024} KB"
}, },