diff --git a/src/main/kotlin/com/bolenpad/photophetch/ui/DensePhotoGrid.kt b/src/main/kotlin/com/bolenpad/photophetch/ui/DensePhotoGrid.kt index 7dab1dd..2074b83 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/ui/DensePhotoGrid.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/ui/DensePhotoGrid.kt @@ -247,9 +247,14 @@ private fun DenseThumb( } } if (photo.mediaType == MediaType.VIDEO) { + val sizeLabel = if (photo.sizeBytes > 1024 * 1024) + "▶ ${photo.sizeBytes / (1024 * 1024)}MB" + else if (photo.sizeBytes > 0) + "▶ ${photo.sizeBytes / 1024}KB" + else "▶" Badge(containerColor = MaterialTheme.colorScheme.tertiary, modifier = Modifier.align(Alignment.BottomStart).padding(2.dp)) { - Text("▶", style = MaterialTheme.typography.labelSmall) + Text(sizeLabel, style = MaterialTheme.typography.labelSmall) } } if (isSelected) { diff --git a/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt b/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt index 9c9eb18..15e0863 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt @@ -106,7 +106,7 @@ object ExifFilter { fun isCameraRollFolder(folderName: String): Boolean { // iPhone classified groups (from iOS helper classify_iphone_file) if (folderName == "Camera" || folderName == "Edited") return true - if (folderName in setOf("Screenshots", "Screen Recordings", "Videos", "Other")) return false + if (folderName in setOf("Screenshots", "Screen Recordings", "Videos", "Live Photo", "Other")) return false // Android: path-based exclusion val lower = folderName.lowercase() diff --git a/src/main/resources/scripts/photophetch-ios-helper.py b/src/main/resources/scripts/photophetch-ios-helper.py index a03ad9a..a1df7a8 100644 --- a/src/main/resources/scripts/photophetch-ios-helper.py +++ b/src/main/resources/scripts/photophetch-ios-helper.py @@ -42,10 +42,13 @@ SCREENSHOT_RE = re.compile(r'.*\.PNG$', re.IGNORECASE) # Screen recordings SCREEN_RECORDING_RE = re.compile(r'^RPReplay_Final.*\.mp4$', re.IGNORECASE) -def classify_iphone_file(filename: str) -> str: +def classify_iphone_file(filename: str, all_filenames: set = None, size_bytes: int = 0) -> str: """ Returns a group name for display in the folder panel. - Groups: 'Camera', 'Edited', 'Screenshots', 'Videos', 'Other' + Groups: 'Camera', 'Edited', 'Live Photo', 'Screenshots', 'Videos', 'Other' + + all_filenames: set of all filenames in the same DCIM folder (for pairing detection). + size_bytes: used to distinguish Live Photo clips (<5MB) from real videos. """ ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else '' if SCREEN_RECORDING_RE.match(filename): @@ -54,7 +57,19 @@ def classify_iphone_file(filename: str) -> str: return 'Screenshots' if CAMERA_ORIGINAL_RE.match(filename): if ext in ('mov',): - return 'Videos' + # Check if this is a Live Photo companion + base = filename.rsplit('.', 1)[0] # IMG_XXXX + is_live_companion = False + if all_filenames is not None: + # Paired HEIC/JPEG exists with same base name → Live Photo companion + for still_ext in ('HEIC', 'HEIF', 'JPG', 'JPEG'): + if f'{base}.{still_ext}' in all_filenames: + is_live_companion = True + break + elif size_bytes > 0 and size_bytes < 5 * 1024 * 1024: + # No pairing info but very small MOV → likely Live Photo + is_live_companion = True + return 'Live Photo' if is_live_companion else 'Videos' return 'Camera' if CAMERA_EDITED_RE.match(filename): return 'Edited' @@ -109,6 +124,9 @@ async def cmd_list_photos(): except Exception: continue + # Build a set of all filenames in this folder for Live Photo pairing + all_filenames_in_folder = set(entries) + for filename in entries: ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else '' if ext not in MEDIA_EXTENSIONS: @@ -141,7 +159,7 @@ async def cmd_list_photos(): 'filename': filename, 'sizeBytes': size_bytes, 'dateIso': date_iso, - 'sourceFolder': classify_iphone_file(filename), + 'sourceFolder': classify_iphone_file(filename, all_filenames_in_folder, size_bytes), 'mediaType': 'VIDEO' if ext in VIDEO_EXTENSIONS else 'PHOTO', 'isHeic': ext in ('heic', 'heif'), })