iPhone file classification by filename pattern
iOS helper: classify_iphone_file() groups photos by name pattern:
- Camera: IMG_XXXX.{HEIC,JPG} — camera originals
- Edited: IMG_EXXXX.{HEIC,JPG} — Photos.app edits
- Videos: IMG_XXXX.MOV and other video files
- Screenshots: any .PNG (iPhone camera never produces PNG)
- Screen Recordings: RPReplay_Final*.mp4
- Other: everything else (web saves, app exports, etc.)
ExifFilter.isCameraRollFolder: Camera and Edited are camera-roll,
Screenshots/Screen Recordings/Other are not.
sourceFolder now reflects these groups instead of raw DCIM folder names
(145APPLE etc.) so the left panel shows meaningful categories.
This commit is contained in:
@@ -100,10 +100,15 @@ object ExifFilter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if a folder name is likely a camera-roll source
|
* Returns true if a folder name is likely a camera-roll source.
|
||||||
* (i.e. not screenshots, received images, etc.)
|
* Handles both Android path-based names and iOS classified group names.
|
||||||
*/
|
*/
|
||||||
fun isCameraRollFolder(folderName: String): Boolean {
|
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()
|
val lower = folderName.lowercase()
|
||||||
return excludedPathFragments.none { lower.contains(it) } &&
|
return excludedPathFragments.none { lower.contains(it) } &&
|
||||||
screenshotFilePrefixes.none { lower.startsWith(it) }
|
screenshotFilePrefixes.none { lower.startsWith(it) }
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import os
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
MEDIA_EXTENSIONS = {
|
MEDIA_EXTENSIONS = {
|
||||||
'jpg', 'jpeg', 'heic', 'heif', 'avif', 'webp', 'png',
|
'jpg', 'jpeg', 'heic', 'heif', 'avif', 'webp', 'png',
|
||||||
'dng', 'raw', 'arw', 'cr2', 'cr3', 'nef', 'orf', 'rw2',
|
'dng', 'raw', 'arw', 'cr2', 'cr3', 'nef', 'orf', 'rw2',
|
||||||
@@ -31,6 +33,35 @@ VIDEO_EXTENSIONS = {
|
|||||||
'mp4', 'm4v', 'mov', 'mpeg', 'mpg', '3gp', '3g2', 'avi', 'mkv', 'webm',
|
'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():
|
async def get_lockdown():
|
||||||
from pymobiledevice3.lockdown import create_using_usbmux
|
from pymobiledevice3.lockdown import create_using_usbmux
|
||||||
@@ -110,7 +141,7 @@ async def cmd_list_photos():
|
|||||||
'filename': filename,
|
'filename': filename,
|
||||||
'sizeBytes': size_bytes,
|
'sizeBytes': size_bytes,
|
||||||
'dateIso': date_iso,
|
'dateIso': date_iso,
|
||||||
'sourceFolder': folder,
|
'sourceFolder': classify_iphone_file(filename),
|
||||||
'mediaType': 'VIDEO' if ext in VIDEO_EXTENSIONS else 'PHOTO',
|
'mediaType': 'VIDEO' if ext in VIDEO_EXTENSIONS else 'PHOTO',
|
||||||
'isHeic': ext in ('heic', 'heif'),
|
'isHeic': ext in ('heic', 'heif'),
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user