Detect Live Photo companions and show video file size

iOS helper: classify_iphone_file now detects Live Photo MOV companions
by checking if IMG_XXXX.MOV has a matching IMG_XXXX.HEIC/JPG in the
same DCIM folder. Falls back to size heuristic (<5MB = Live Photo).

Live Photo companions appear in 'Live Photo' group (Other section) —
excluded from camera-roll default selection.

DenseThumb: show file size on video badge (e.g. '▶ 2MB' or '▶ 42MB')
so Live Photo clips (small) are visually distinct from real videos (large).
This commit is contained in:
Kyle Bolen
2026-07-30 04:24:22 +00:00
parent e6d27d3ab3
commit 6876709051
3 changed files with 29 additions and 6 deletions

View File

@@ -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'),
})