diff --git a/src/main/kotlin/com/bolenpad/photophetch/device/IosConnector.kt b/src/main/kotlin/com/bolenpad/photophetch/device/IosConnector.kt index c60390a..8a8e10c 100644 --- a/src/main/kotlin/com/bolenpad/photophetch/device/IosConnector.kt +++ b/src/main/kotlin/com/bolenpad/photophetch/device/IosConnector.kt @@ -147,7 +147,8 @@ class IosConnector( } override suspend fun pullToFile(device: DeviceInfo, devicePath: String, destinationPath: Path): Boolean { - val output = runHelper("pull", devicePath, destinationPath.absolutePathString()) + // Use pull-thumb (partial 512KB read) for speed — sips can extract EXIF thumbnail from partial file + val output = runHelper("pull-thumb", devicePath, destinationPath.absolutePathString()) return !output.contains("Error") && destinationPath.toFile().exists() } diff --git a/src/main/resources/scripts/photophetch-ios-helper.py b/src/main/resources/scripts/photophetch-ios-helper.py index f19de91..47176ef 100644 --- a/src/main/resources/scripts/photophetch-ios-helper.py +++ b/src/main/resources/scripts/photophetch-ios-helper.py @@ -135,6 +135,28 @@ async def cmd_pull(device_path: str, local_path: str): sys.exit(1) +async def cmd_pull_thumb(device_path: str, local_path: str): + """Pull first 512KB — enough for EXIF thumbnail extraction via sips.""" + try: + lockdown = await get_lockdown() + afc = await get_afc(lockdown) + os.makedirs(os.path.dirname(os.path.abspath(local_path)), exist_ok=True) + try: + # fread(path, size, offset) reads partial file — much faster than full pull + data = await afc.fread(device_path, 512 * 1024, 0) + except Exception: + # fread may not exist or may fail — fall back to full pull + await afc.pull(device_path, local_path) + print(json.dumps({'ok': True, 'path': local_path})) + return + with open(local_path, 'wb') as f: + f.write(data) + print(json.dumps({'ok': True, 'path': local_path})) + except Exception as e: + print(f'Error: {e}', file=sys.stderr) + sys.exit(1) + + async def cmd_delete(device_path: str): try: lockdown = await get_lockdown() @@ -159,6 +181,8 @@ if __name__ == '__main__': asyncio.run(cmd_list_photos()) elif command == 'pull' and len(sys.argv) == 4: asyncio.run(cmd_pull(sys.argv[2], sys.argv[3])) + elif command == 'pull-thumb' and len(sys.argv) == 4: + asyncio.run(cmd_pull_thumb(sys.argv[2], sys.argv[3])) elif command == 'delete' and len(sys.argv) == 3: asyncio.run(cmd_delete(sys.argv[2])) else: