Live Photo handling: hide MOV companions, LIVE badge, atomic import/delete
PhonePhoto: add companionDevicePath (MOV path stored on the HEIC). iOS helper: detect Live Photo pairs by IMG_XXXX filename matching. MOVs with a paired HEIC get isLivePhotoMotion=true, sourceFolder='Live Photo'. HEICs with a paired MOV get isLivePhotoStill=true, companionDevicePath set. Grid: Live Photo MOVs appear only in 'Live Photo' folder (Other section). HEIC stills show a LIVE badge. Camera folder stays clean. Import: 'Import Live Photo motion clips' toggle (default off). When on, Copier pulls the companion MOV alongside the HEIC atomically. Delete: deleteVerifiedFromDevice also deletes the companion MOV when deleting a Live Photo still.
This commit is contained in:
@@ -127,12 +127,40 @@ async def cmd_list_photos():
|
||||
# Build a set of all filenames in this folder for Live Photo pairing
|
||||
all_filenames_in_folder = set(entries)
|
||||
|
||||
# Pre-compute Live Photo pairs: IMG_XXXX.MOV + IMG_XXXX.HEIC/JPG
|
||||
# Key: base name (IMG_XXXX) -> MOV device path
|
||||
live_photo_mov_paths = {}
|
||||
for fname in entries:
|
||||
if re.match(r'^IMG_\d{4}\.MOV$', fname, re.IGNORECASE):
|
||||
base = fname.rsplit('.', 1)[0]
|
||||
for still_ext in ('HEIC', 'HEIF', 'JPG', 'JPEG'):
|
||||
if f'{base}.{still_ext}' in all_filenames_in_folder:
|
||||
# Confirm it's small enough to be a Live Photo clip
|
||||
# (we'll verify size when we hit the MOV entry)
|
||||
live_photo_mov_paths[base] = f'{folder_path}/{fname}'
|
||||
break
|
||||
|
||||
for filename in entries:
|
||||
ext = filename.rsplit('.', 1)[-1].lower() if '.' in filename else ''
|
||||
if ext not in MEDIA_EXTENSIONS:
|
||||
continue
|
||||
|
||||
device_path = f'{folder_path}/{filename}'
|
||||
base = filename.rsplit('.', 1)[0]
|
||||
|
||||
# Determine Live Photo status
|
||||
is_live_motion = False
|
||||
companion_device_path = None
|
||||
|
||||
if re.match(r'^IMG_\d{4}\.MOV$', filename, re.IGNORECASE):
|
||||
if base in live_photo_mov_paths:
|
||||
# This MOV is a companion — mark it but still include it
|
||||
# (AppState will filter it from display)
|
||||
is_live_motion = True
|
||||
|
||||
elif re.match(r'^IMG_\d{4}\.(HEIC|HEIF|JPG|JPEG)$', filename, re.IGNORECASE):
|
||||
if base in live_photo_mov_paths:
|
||||
companion_device_path = live_photo_mov_paths[base]
|
||||
|
||||
# Get file metadata via stat
|
||||
size_bytes = 0
|
||||
@@ -162,6 +190,8 @@ async def cmd_list_photos():
|
||||
'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'),
|
||||
'isLivePhotoMotion': is_live_motion,
|
||||
'companionDevicePath': companion_device_path,
|
||||
})
|
||||
|
||||
print(json.dumps(photos))
|
||||
|
||||
Reference in New Issue
Block a user