Fix Android photo discovery: dynamically scan all DCIM subdirs
Instead of hardcoding /sdcard/DCIM/Camera, list all subdirectories under /sdcard/DCIM and /storage/emulated/0/DCIM dynamically. This handles Samsung, Pixel, OnePlus and other vendors that use different DCIM subfolder names. Falls back to hardcoded list if discovery finds nothing.
This commit is contained in:
@@ -24,8 +24,9 @@ class AndroidConnector(
|
|||||||
|
|
||||||
private val log = LoggerFactory.getLogger(AndroidConnector::class.java)
|
private val log = LoggerFactory.getLogger(AndroidConnector::class.java)
|
||||||
|
|
||||||
// Directories on Android that contain camera-roll originals
|
// Directories on Android that contain camera-roll originals.
|
||||||
private val cameraDirs = listOf(
|
// Used as fallback if dynamic DCIM discovery finds nothing.
|
||||||
|
private val fallbackCameraDirs = listOf(
|
||||||
"/sdcard/DCIM/Camera",
|
"/sdcard/DCIM/Camera",
|
||||||
"/sdcard/DCIM/100ANDRO",
|
"/sdcard/DCIM/100ANDRO",
|
||||||
"/sdcard/Pictures/Camera",
|
"/sdcard/Pictures/Camera",
|
||||||
@@ -65,13 +66,30 @@ class AndroidConnector(
|
|||||||
val serial = requireSerial(device)
|
val serial = requireSerial(device)
|
||||||
val photos = mutableListOf<PhonePhoto>()
|
val photos = mutableListOf<PhonePhoto>()
|
||||||
|
|
||||||
for (dir in cameraDirs) {
|
// Dynamically discover all subdirectories under DCIM on common storage paths
|
||||||
// Check if directory exists first
|
val dcimRoots = listOf("/sdcard/DCIM", "/storage/emulated/0/DCIM")
|
||||||
|
val cameraDirs = mutableListOf<String>()
|
||||||
|
|
||||||
|
for (root in dcimRoots) {
|
||||||
|
val lsOutput = runAdb(serial, "shell", "ls", root, "2>/dev/null")
|
||||||
|
if (lsOutput.contains("No such file") || lsOutput.isBlank()) continue
|
||||||
|
lsOutput.lines()
|
||||||
|
.map { it.trim() }
|
||||||
|
.filter { it.isNotBlank() && !it.contains("No such file") }
|
||||||
|
.forEach { subdir -> cameraDirs.add("$root/$subdir") }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also check Pictures/Camera (some Samsung models store there)
|
||||||
|
cameraDirs.add("/sdcard/Pictures/Camera")
|
||||||
|
cameraDirs.add("/storage/emulated/0/Pictures/Camera")
|
||||||
|
|
||||||
|
// Fall back to hardcoded list if dynamic discovery found nothing
|
||||||
|
val dirsToScan = cameraDirs.ifEmpty { fallbackCameraDirs }
|
||||||
|
|
||||||
|
for (dir in dirsToScan) {
|
||||||
val check = runAdb(serial, "shell", "ls", dir, "2>/dev/null")
|
val check = runAdb(serial, "shell", "ls", dir, "2>/dev/null")
|
||||||
if (check.contains("No such file") || check.isBlank()) continue
|
if (check.contains("No such file") || check.isBlank()) continue
|
||||||
|
|
||||||
// List files with metadata: size, date, time, name
|
|
||||||
// `stat` format: size=%s mtime=%Y path=%n
|
|
||||||
val statOutput = runAdb(
|
val statOutput = runAdb(
|
||||||
serial, "shell",
|
serial, "shell",
|
||||||
"find", dir, "-maxdepth", "1", "-type", "f",
|
"find", dir, "-maxdepth", "1", "-type", "f",
|
||||||
@@ -98,8 +116,6 @@ class AndroidConnector(
|
|||||||
filename = filename,
|
filename = filename,
|
||||||
sizeBytes = sizeBytes,
|
sizeBytes = sizeBytes,
|
||||||
dateTaken = Instant.ofEpochSecond(epochSecs),
|
dateTaken = Instant.ofEpochSecond(epochSecs),
|
||||||
// Android stat doesn't give EXIF; we infer Make/Model from
|
|
||||||
// the device itself rather than per-file EXIF
|
|
||||||
exifMake = device.displayName,
|
exifMake = device.displayName,
|
||||||
exifModel = device.displayName,
|
exifModel = device.displayName,
|
||||||
mediaType = if (ext in setOf("mp4", "mov", "3gp", "avi"))
|
mediaType = if (ext in setOf("mp4", "mov", "3gp", "avi"))
|
||||||
|
|||||||
Reference in New Issue
Block a user