David
David

Reputation: 348

How can I iterate across the photos on my connected iPhone from Windows 7 in Python?

When I connect my iPhone to my Windows 7 system, the Windows Explorer opens a Virtual Folder to the DCIM content. I can access the shell library interface via Pywin32 (218) as mentioned here: Can I use library abstractions in python?

Given a user-facing editing path (SIGDN_DESKTOPABSOLUTEEDITING) that works in the Windows Explorer, and launches the Windows Photo Viewer:

Computer\My iPhone\Internal Storage\DCIM\828RTETC\IMG_2343.JPG

How can I obtain a parsing path (SIGDN_DESKTOPABSOLUTEPARSING) for use with SHCreateItemFromParsingName() to create a ShellItem? (From which I'd bind a stream and copy to a local disk like this: Can images be read from an iPhone programatically using CreateFile in Windows? )

from win32com.shell import shell

edit_path = r'Computer\My iPhone\Internal Storage\DCIM\828RTETC\IMG_2343.JPG'
parse_path = # How to convert edit_path to a SIGDN_DESKTOPABSOLUTEPARSING path?
i = shell.SHCreateItemFromParsingName(parse_path, None, shell.IID_IShellItem)

The final goal will be to iterate the DCIM "folder" via something like the IShellFolder interface, and copy the most recent photos to the local disk. I don't want to have to open a FileOpenDialog for the parsing name. But before getting to that point, I thought creating a ShellItem for one of the files would be a good test.

Upvotes: 8

Views: 4724

Answers (3)

Jack Chang
Jack Chang

Reputation: 9

I have the same problem, and thanks to @Stephen Brodie's answer in #72842042, I successed to copy a picture to my PC with windows10.

@Stephen Brodie menstioned:

from win32com.shell import shell, shellcon
import pythoncom
#fo is the folder IShellFolder object
#dsk is the destination IShellFolder object
#fi is the PIDL of the item you wish to copy (eg. the photo on your iPhone)

fidl = shell.SHGetIDListFromObject(fo) #Grab the PIDL from the folder object
didl = shell.SHGetIDListFromObject(dsk) #Grab the PIDL from the folder object

si = shell.SHCreateShellItem(fidl, None, fi) #Create a ShellItem of the source file
dst = shell.SHCreateItemFromIDList(didl)

#Python IFileOperation
pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation,None,pythoncom.CLSCTX_ALL,shell.IID_IFileOperation)
pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION)
pfo.CopyItem(si, dst, "Destination Name.jpg") # Schedule an operation to be performed
success = pfo.PerformOperations() #perform operation

Below is my code according to @Stephen Brodie's.

Source file: \Computer\Apple iPhone\Internal Storage\DCIM\100APPLE\IMG_0199.JPG
Destination folder: C:\Users\Administrator\Desktop\myFolder\savedImages

from win32com.shell import shell, shellcon
import pythoncom

# get the PIDL of source file and the ShellObject of the folder in which source file is
# and the PIDL of the folder

desktop = shell.SHGetDesktopFolder()
for pidl in desktop:
    if desktop.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Computer":
        pidl_get = pidl
        break
folder = desktop.BindToObject(pidl_get, None, shell.IID_IShellFolder)

for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Apple iPhone":
        pidl_get = pidl
        break
folder = folder.BindToObject(pidl_get, None, shell.IID_IShellFolder)

for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Internal Storage":
        pidl_get = pidl
        break
folder = folder.BindToObject(pidl_get, None, shell.IID_IShellFolder)

for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "DCIM":
        pidl_get = pidl
        break
folder = folder.BindToObject(pidl_get, None, shell.IID_IShellFolder)

for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "100APPLE":
        pidl_get = pidl
        break
folderPIDL = pidl_get
folder = folder.BindToObject(pidl_get, None, shell.IID_IShellFolder)

NOW we have

folderPIDL: PIDL of the folder in which the source file is
folder: the ShellObject of the folder in which the source file is

for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "IMG_0199.JPG":
        imgPIDL = pidl

# imgPIDL: PIDL of the source file

And we get imgPIDL.
(Remember to enable windows10 to show extensions in File Explorer.
Otherwise, folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) will show "IMG_0199"
instead of "IMG_0199.JPG".)

# then get the ShellObject of Destination folder

for pidl in desktop:
    if desktop.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "myFolder":
        pidl_dst = pidl
        break
dstFolder = desktop.BindToObject(pidl_dst, None, shell.IID_IShellFolder)

for pidl in dstFolder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "savedImages":
        pidl_dst = pidl
        break
dstFolder = folder.BindToObject(pidl_dst, None, shell.IID_IShellFolder)

All we need for copy file are 4 var: folderPIDL, folder, imgPIDL and dstFolder

Start to Copy file (@Stephen Brodie's code)

fidl = shell.SHGetIDListFromObject(folder) #Grab the PIDL from the folder object
didl = shell.SHGetIDListFromObject(dstFolder) #Grab the PIDL from the folder object

si = shell.SHCreateShellItem(fidl, None, imgPIDL) #Create a ShellItem of the source file
dst = shell.SHCreateItemFromIDList(didl)

#Python IFileOperation
pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation,None,pythoncom.CLSCTX_ALL,shell.IID_IFileOperation)
pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION)
pfo.CopyItem(si, dst, "Destination Name.jpg") # Schedule an operation to be performed
success = pfo.PerformOperations() #perform operation

And success!!
At first, I tried to skip fidl and didl, and just used si and dst. I failed. The error message said last line pfo.PerformOperations() cannot run. Maybe SHGetIDListFromObject() is essential for changing ShellObject to IDList.

Besides, my PC show Chinese character "本機" instead of "Computer". All I need is to change "Computer" to "本機" in the codes above.

Upvotes: 1

lani
lani

Reputation: 1

Thanks for the above information, with it I was able to make a python implementation for moving photos from iPhone X to Windows10 PC. Key functions below

# imports probably needed
from win32com.shell import shell, shellcon
from win32com.propsys import propsys
import pythoncom

# Recursive function to browse into a non filesystem path
def recurse_and_get_ishellfolder(base_ishellfolder, path):
    splitted_path = path.split("\\", 1)

    for pidl in base_ishellfolder:
        if base_ishellfolder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == splitted_path[0]:
            break

    folder = base_ishellfolder.BindToObject(pidl, None, shell.IID_IShellFolder)

    if len(splitted_path) > 1:
        # More to recurse
        return recurse_and_get_ishellfolder(folder, splitted_path[1])
    else:
        return folder


# How to move non filesystem file to filesystem patj
def move_file_by_pidl_to_path(src_ishellfolder, src_pidl, dst_path, dst_filename):
    pidl_folder_dst, flags = shell.SHILCreateFromPath(dst_path, 0)
    dst_ishellfolder = shell.SHGetDesktopFolder().BindToObject(pidl_folder_dst, None, shell.IID_IShellFolder)

    fidl = shell.SHGetIDListFromObject(src_ishellfolder)  # Grab the PIDL from the folder object
    didl = shell.SHGetIDListFromObject(dst_ishellfolder)  # Grab the PIDL from the folder object

    si = shell.SHCreateShellItem(fidl, None, src_pidl)  # Create a ShellItem of the source file
    dst = shell.SHCreateItemFromIDList(didl)

    pfo = pythoncom.CoCreateInstance(shell.CLSID_FileOperation, None, pythoncom.CLSCTX_ALL, shell.IID_IFileOperation)
    pfo.SetOperationFlags(shellcon.FOF_NOCONFIRMATION | shellcon.FOF_SILENT | shellcon.FOF_NOERRORUI)
    pfo.MoveItem(si, dst, dst_filename) # Schedule an operation to be performed
    pfo.PerformOperations()
    return not pfo.GetAnyOperationsAborted()


# Bonus: get file modification datetime for a non filesystem file
DATE_PROP_KEY = propsys.PSGetPropertyKeyFromName("System.DateModified")
DATE_PROP_PARSE_STR = '%Y/%m/%d:%H:%M:%S.%f' # not sure bout the f modifier but it does not really matter
def getmodified_datetime_by_pidl(src_ishellfolder, src_pidl):
    fidl = shell.SHGetIDListFromObject(src_ishellfolder)  # Grab the PIDL from the folder object
    si = shell.SHCreateShellItem(fidl, None, src_pidl)  # Create a ShellItem of the source file
    ps = propsys.PSGetItemPropertyHandler(si)
    date_str = ps.GetValue(DATE_PROP_KEY).ToString()
    return datetime.strptime(date_str, DATE_PROP_PARSE_STR)


# Example photo moving main logic
def move_files():
    main_folder = recurse_and_get_ishellfolder(shell.SHGetDesktopFolder(), "This Pc\\path\\to\\DCIM")

    for photo_folder_pidl in main_folder:
        folder_name = main_folder.GetDisplayNameOf(photo_folder_pidl, shellcon.SHGDN_NORMAL)
        folder = main_folder.BindToObject(photo_folder_pidl, None, shell.IID_IShellFolder)
        for pidl in folder:
            child_name = folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL)

            file_mod_date = getmodified_datetime_by_pidl(folder, pidl)
            if not older_than_datetime or file_mod_date < older_than_datetime:
                print("Transferring file: " + child_name)
                move_file_by_pidl_to_path(...)
            else:
                print("Skipping too recent file: " + child_name)

Full script for moving photos: https://gitlab.com/lassi.niemisto/iphone-photo-dump

EDIT: Key parts from linked implementation copied here as code

Upvotes: -1

David
David

Reputation: 348

Instead of translating from an editing name to a parsing name, I think @jonathan-potter's suggestion is a better way to go. Here's a hard-coded snippet that shows how to start at the Desktop folder and excludes error handling:

from win32com.shell import shell, shellcon

desktop = shell.SHGetDesktopFolder()
for pidl in desktop:
    if desktop.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Computer":
        break
folder = desktop.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
     if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "My iPhone":
         break
folder = folder.BindToObject(pidl, None, shell.IID_IShellFolder)
for pidl in folder:
    if folder.GetDisplayNameOf(pidl, shellcon.SHGDN_NORMAL) == "Internal Storage":
        break
# And so on...

Upvotes: 6

Related Questions