0

Goal is to get files thumbnails from selected folder on windows in a minimum possible amount of time.

Imagemagick and other tools open files every time, so they may not be the most efficient. Built-in mechanism in Windows has a possibility that the thumbnails can be in cache, which theoretically gives a considerable speed boost.

From How to get thumbnail of file using the Windows API? (answer) it became clear about the ways, but there was a lot of problems:

  • IThumbnailProvider and IThumbnailCache not found in:
    1. ctypes.windll.shell32 (there is only functions, and seems that there is no simple way to get interfaces)
    2. win32com.shell.shell (PyWin32)
  • IID_IThumbnailProvider and IShellItemImageFactory through SHCreateItemFromParsingName and IID:
    1. That IID's not found in win32com.shell.shell, so they was provided manually.

    2. IThumbnailProvider, IShellItem::BindToHandler method: fixed, but now error is the same as below:

      # str(shell.IID_IShellItem) == '{43826D1E-E718-42EE-BC55-A1E261C37BFE}'
      # str(shell.BHID_ThumbnailHandler) == '{7B2E650A-8E20-4F4A-B09E-6597AFC72FB0}'
      
      item = shell.SHCreateItemFromParsingName(r'd:\some.png', None, str(shell.IID_IShellItem))  # str() is works
      IID_IThumbnailProvider = '{e357fccd-a995-4576-b01f-234630154e96}'
      provider = item.BindToHandler(None, shell.BHID_ThumbnailHandler, IID_IThumbnailProvider)  # error
      
      > TypeError: 'There is no interface object registered that supports this IID'
      
    3. IShellItemImageFactory: suspect that there is wrong usage of IID, but from answer below it should work:

      IID_IShellItemImageFactory = '{bcc18b79-ba16-442f-80c4-8a59c30c463b}'
      item = shell.SHCreateItemFromParsingName(r'd:\some.png', None, IID_IShellItemImageFactory)  # error
      
      > TypeError: 'There is no interface object registered that supports this IID'
      

It would be great to understand how to swim in all this without learning the whole winapi..

Thanks in advance.

Windows 10 (20H2), Python 3.9.5, PyWin32 301


Update: Found answer about PyWin32 (pythoncom.CoCreateInstance can't create IZoneIdentifier; the interface is missing from registry too despite being documented in MSDN). In short, these interfaces are not implemented. Checked by:

> import pythoncom
> pythoncom.InterfaceNames

So .. there are 2 questions left. Are there any options to get to thumbnails without using PyWin32? Via ctypes, for example. And how?

C0oo1D
  • 95
  • 7
  • 1
    [BHID_ThumbnailHandler](https://learn.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-ishellitem-bindtohandler#bhid_thumbnailhandler): *"Restricts usage to `IExtractImage` or `IThumbnailProvider`."* – IInspectable Jun 09 '21 at 12:51
  • Thanks, attentiveness let down. – C0oo1D Jun 09 '21 at 13:06
  • 1
    Are you sure you can specify IID as a string like that? In C/C++ it's generally a binary structure. – Mark Ransom Jun 09 '21 at 14:05
  • 1
    Your code 3. works fine with C++. The problem comes from python (how you use win32com.shell). – Simon Mourier Jun 09 '21 at 14:10
  • @MarkRansom, yes, str(shell.IID_IShellItem) above is works correctly. – C0oo1D Jun 09 '21 at 14:28
  • @SimonMourier, thanks for check, then the problem is on the side of PyWin32.. code fragments above use that import: from win32com.shell import shell – C0oo1D Jun 09 '21 at 14:28
  • Sure maybe that one case works correctly, but doesn't it occur to you that a function ending with `ParsingName` might be doing extra work because it expects a string as input? – Mark Ransom Jun 09 '21 at 14:51
  • Have a look at [python: how to convert a valid uuid from String to UUID?](https://stackoverflow.com/q/15859156/5987), maybe it will help. – Mark Ransom Jun 09 '21 at 14:56
  • @MarkRansom, thanks for advices, but reason is that it not implemented in PyWin32.. Question was updated. – C0oo1D Jun 09 '21 at 15:15
  • The simplest way would be to write a python module in C or make a pull request to win32com.shell which is missing IShellItemImageFactory, IThumbnailProvider, etc. – Simon Mourier Jun 09 '21 at 15:37

1 Answers1

1

Here some working Python 3 code based on ctypes and comtypes:

from ctypes import POINTER, byref, cast, windll, c_void_p, c_wchar_p
from ctypes.wintypes import SIZE, UINT, HANDLE, HBITMAP
from comtypes import GUID, IUnknown, COMMETHOD, HRESULT

shell32 = windll.shell32
shell32.SHCreateItemFromParsingName.argtypes = [c_wchar_p, c_void_p, POINTER(GUID), POINTER(HANDLE)]
shell32.SHCreateItemFromParsingName.restype = HRESULT

SIIGBF_RESIZETOFIT = 0

class IShellItemImageFactory(IUnknown):
    _case_insensitive_ = True
    _iid_ = GUID('{bcc18b79-ba16-442f-80c4-8a59c30c463b}')
    _idlflags_ = []

IShellItemImageFactory._methods_ = [
    COMMETHOD([], HRESULT, 'GetImage',
        ( ['in'], SIZE, 'size' ),
        ( ['in'], UINT, 'flags' ),
        ( ['out'], POINTER(HBITMAP), 'phbm' )),
    ]

LP_IShellItemImageFactory = POINTER(IShellItemImageFactory)

def get_thumbnail(filename, icon_size):
    ''' Returns thumbnail image as HBITMAP'''
    h_siif = HANDLE()
    hr = shell32.SHCreateItemFromParsingName(filename, 0,
            byref(IShellItemImageFactory._iid_), byref(h_siif))
    if hr < 0:
        raise Exception(f'SHCreateItemFromParsingName failed: {hr}')
    h_siif = cast(h_siif, LP_IShellItemImageFactory)
    # Raises exception on failure.
    return h_siif.GetImage(SIZE(icon_size, icon_size), SIIGBF_RESIZETOFIT)

if __name__ == "__main__":
    h_bitmap = get_thumbnail(r'C:\test\test.jpg', 64)
    print(h_bitmap)
user15566053
  • 41
  • 1
  • 2
  • 1
    Seems that half of work is done, and code is really seems to work. But result is not image.. If i understand correctly - need some code to get it from windows structure, like in answer update [here](https://stackoverflow.com/questions/37046458/how-get-image-icon-desktop) – C0oo1D Jul 01 '23 at 20:26
  • @C0oo1D You can convert the returned HBITMAP into a PIL image object by using CreateCompatibleDC, SelectObject and GetDIBits (all implemented by windll.gdi32) and then finally Image.frombytes("RGBA", (icon_size, icon_size), bits, "raw", "BGRA"). But I think this would be too much extra code to add it to my original answer. – user15566053 Jul 02 '23 at 14:27