diff --git a/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt b/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt index 0701d2f..9c9eb18 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/util/ExifFilter.kt @@ -100,10 +100,15 @@ object ExifFilter { } /** - * Returns true if a folder name is likely a camera-roll source - * (i.e. not screenshots, received images, etc.) + * Returns true if a folder name is likely a camera-roll source. + * Handles both Android path-based names and iOS classified group names. */ 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 + + // Android: path-based exclusion val lower = folderName.lowercase() return excludedPathFragments.none { lower.contains(it) } && screenshotFilePrefixes.none { lower.startsWith(it) } diff --git a/src/main/resources/scripts/photophetch-ios-helper.py b/src/main/resources/scripts/photophetch-ios-helper.py index 1a13d11..a03ad9a 100644 --- a/src/main/resources/scripts/photophetch-ios-helper.py +++ b/src/main/resources/scripts/photophetch-ios-helper.py @@ -20,6 +20,8 @@ import os import asyncio from datetime import datetime +import re + MEDIA_EXTENSIONS = { 'jpg', 'jpeg', 'heic', 'heif', 'avif', 'webp', 'png', 'dng', 'raw', 'arw', 'cr2', 'cr3', 'nef', 'orf', 'rw2', @@ -31,6 +33,35 @@ VIDEO_EXTENSIONS = { 'mp4', 'm4v', 'mov', 'mpeg', 'mpg', '3gp', '3g2', 'avi', 'mkv', 'webm', } +# iPhone camera originals: IMG_XXXX.{HEIC,JPG,MOV} — exactly 4 digits +CAMERA_ORIGINAL_RE = re.compile(r'^IMG_\d{4}\.(HEIC|HEIF|JPG|JPEG|MOV)$', re.IGNORECASE) +# Edited versions: IMG_EXXXX — edited in Photos.app +CAMERA_EDITED_RE = re.compile(r'^IMG_E\d{4}\.(HEIC|HEIF|JPG|JPEG)$', re.IGNORECASE) +# Screenshots: PNG files (iPhone camera never produces PNG) +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: + """ + Returns a group name for display in the folder panel. + Groups: 'Camera', 'Edited', 'Screenshots', 'Videos', 'Other' + """ + ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else '' + if SCREEN_RECORDING_RE.match(filename): + return 'Screen Recordings' + if SCREENSHOT_RE.match(filename): + return 'Screenshots' + if CAMERA_ORIGINAL_RE.match(filename): + if ext in ('mov',): + return 'Videos' + return 'Camera' + if CAMERA_EDITED_RE.match(filename): + return 'Edited' + if ext in VIDEO_EXTENSIONS: + return 'Videos' + return 'Other' + async def get_lockdown(): from pymobiledevice3.lockdown import create_using_usbmux @@ -110,7 +141,7 @@ async def cmd_list_photos(): 'filename': filename, 'sizeBytes': size_bytes, 'dateIso': date_iso, - 'sourceFolder': folder, + 'sourceFolder': classify_iphone_file(filename), 'mediaType': 'VIDEO' if ext in VIDEO_EXTENSIONS else 'PHOTO', 'isHeic': ext in ('heic', 'heif'), })