7

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.

Community
  • 1
  • 1
David
  • 328
  • 2
  • 9
  • 1
    The parsing names for portable devices are long strings embedded with USB bus information. It might be easier to enumerate the Computer folder and find an item with "My iPhone" as its label rather than try to work out how to generate a parsing name for it. – Jonathan Potter Dec 21 '14 at 23:55

3 Answers3

5

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...
David
  • 328
  • 2
  • 9
  • Great question @David! Could you add more details showing how to browse in `Computer\My iPhone\Internal Storage\DCIM\` and finally copy files from there to a local directory? Thanks a lot in advance! I'll start a bounty for this. – Basj Apr 30 '20 at 20:40
  • Does anyone know how to do this? It's so annoying, I can get the names of all the files and folders but there doesn't seem to be any way to actually copy a file using the PIDL! – Oliver Spencer Jul 14 '20 at 22:22
  • After trying this solution and failing, it turns out that the import statement should now read "from win32comext.shell import shell, shellcon" – Dan Stangel Feb 05 '22 at 18:10
1

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.

-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

lani
  • 1
  • 1
  • Use links for reference, but a question needs to be self contained. – klutt Feb 18 '22 at 12:28
  • This does not provide an answer to the question. Once you have sufficient [reputation](https://stackoverflow.com/help/whats-reputation) you will be able to [comment on any post](https://stackoverflow.com/help/privileges/comment); instead, [provide answers that don't require clarification from the asker](https://meta.stackexchange.com/questions/214173/why-do-i-need-50-reputation-to-comment-what-can-i-do-instead). - [From Review](/review/late-answers/31100163) – eatsfood Feb 22 '22 at 19:22