iOS thumbnails: use partial 512KB read for faster thumbnail generation
Add pull-thumb command to helper script that uses afc.fread() to read only the first 512KB of each photo. EXIF thumbnails are in the first few KB so sips can generate a resized thumbnail from the partial file. Falls back to full afc.pull() if fread is unavailable. IosConnector.pullToFile() uses pull-thumb for thumbnail requests. Full afc.pull() still used for actual photo import.
This commit is contained in:
@@ -147,7 +147,8 @@ class IosConnector(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun pullToFile(device: DeviceInfo, devicePath: String, destinationPath: Path): Boolean {
|
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()
|
return !output.contains("Error") && destinationPath.toFile().exists()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,28 @@ async def cmd_pull(device_path: str, local_path: str):
|
|||||||
sys.exit(1)
|
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):
|
async def cmd_delete(device_path: str):
|
||||||
try:
|
try:
|
||||||
lockdown = await get_lockdown()
|
lockdown = await get_lockdown()
|
||||||
@@ -159,6 +181,8 @@ if __name__ == '__main__':
|
|||||||
asyncio.run(cmd_list_photos())
|
asyncio.run(cmd_list_photos())
|
||||||
elif command == 'pull' and len(sys.argv) == 4:
|
elif command == 'pull' and len(sys.argv) == 4:
|
||||||
asyncio.run(cmd_pull(sys.argv[2], sys.argv[3]))
|
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:
|
elif command == 'delete' and len(sys.argv) == 3:
|
||||||
asyncio.run(cmd_delete(sys.argv[2]))
|
asyncio.run(cmd_delete(sys.argv[2]))
|
||||||
else:
|
else:
|
||||||
|
|||||||
Reference in New Issue
Block a user