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? {
return try {
val process = ProcessBuilder("exiftool", "-Make", "-s3", file.absolutePathString())
.redirectErrorStream(true)
.redirectErrorStream(false) // keep stderr separate
.start()
val output = process.inputStream.bufferedReader().readText().trim()
val stdout = process.inputStream.bufferedReader().readText()
process.errorStream.readBytes() // drain stderr
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) {
// exiftool not installed or not on PATH
log.debug("exiftool not available: ${e.message}")
null
}

View File

@@ -104,6 +104,7 @@ fun VerifyScreen(state: AppState) {
failure = r.failureCount,
exifUnverified = r.results.count { it.exifVerified == false },
destination = "${r.job.stagingRoot}/${r.job.folderName}",
deleteAfterVerify = importConfig.deleteAfterVerify,
)
Spacer(Modifier.height(12.dp))
@@ -227,7 +228,7 @@ fun VerifyScreen(state: AppState) {
}
@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(
containerColor = if (failure == 0) MaterialTheme.colorScheme.primaryContainer
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",
style = MaterialTheme.typography.titleMedium, fontWeight = FontWeight.SemiBold,
)
Text("$success copied, $failure failed" +
if (exifUnverified > 0) ", $exifUnverified EXIF unverified (⚠ not auto-deleted)" else "")
Text(buildString {
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,
color = MaterialTheme.colorScheme.onSurfaceVariant)
}
@@ -296,7 +301,8 @@ private fun CopyResultRow(
Text(
when {
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"
else -> "✓ Make: ${result.exifMake} · ${result.photo.sizeBytes / 1024} KB"
},