beetbox / pyacoustid

Python bindings for Chromaprint acoustic fingerprinting and the Acoustid Web service
MIT License
325 stars 66 forks source link

Fix the shared library load for Windows #55

Closed valpogus closed 4 years ago

valpogus commented 4 years ago

Search for the chromaprint shared library on Windows using ctypes.util.find_library() and use the full path as argument for ctypes.cdll.LoadLibrary() instead of just the library name. Solves #54: "couldn't find libchromaprint" error on Windows and Python 3.8.

valpogus commented 4 years ago

ctypes.util.find_library() returns None if the libary is not found. In this case, ctypes.cdll.LoadLibrary() will raise a TypeError: "LoadLibrary() argument 1 must be str, not None" exception on Windows. I catch this exception and raise as OSError instead, in order to make so few changes to the original code as possible.

I guess another solution might be:

import ctypes.util

def _load_library(name):
    if sys.platform == 'win32':
        return ctypes.cdll.LoadLibrary(ctypes.util.find_library(name))
    else:
        return ctypes.cdll.LoadLibrary(name)

for name in _guess_lib_name():
    try:
        _libchromaprint = _load_library(name)
        break
    except (OSError, TypeError):
        pass
else:
    raise ImportError("couldn't find libchromaprint")

I have done some testing on the systems I have access to (Cygwin, Ubuntu-on-WSL, Windows 10):

#!/usr/bin/env python3

# Win10 Python 3.7

import chromaprint  # OK

import ctypes
ctypes.cdll.LoadLibrary("chromaprint.dll")     # OSError
ctypes.cdll.LoadLibrary("libchromaprint.dll")  # OK: 'libchromaprint.dll'
ctypes.cdll.LoadLibrary("chromaprint")         # OSError
ctypes.cdll.LoadLibrary("libchromaprint")      # OK: 'libchromaprint'

import ctypes
import ctypes.util
ctypes.util.find_library("chromaprint.dll")     # None
ctypes.util.find_library("libchromaprint.dll")  # OK: 'C:\lib\libchromaprint.dll'
ctypes.util.find_library("chromaprint")         # None
ctypes.util.find_library("libchromaprint")      # OK: 'C:\lib\libchromaprint.dll'

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint.dll"))     # TypeError
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.dll"))  # OK: 'C:\lib\libchromaprint.dll'
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))         # TypeError
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))      # OK: 'C:\lib\libchromaprint.dll'

# Win10 Python 3.8

import chromaprint  # ImportError

import ctypes
ctypes.cdll.LoadLibrary("chromaprint.dll")     # FileNotFoundError
ctypes.cdll.LoadLibrary("libchromaprint.dll")  # FileNotFoundError
ctypes.cdll.LoadLibrary("chromaprint")         # FileNotFoundError
ctypes.cdll.LoadLibrary("libchromaprint")      # FileNotFoundError

import ctypes
import ctypes.util
ctypes.util.find_library("chromaprint.dll")     # None
ctypes.util.find_library("libchromaprint.dll")  # OK: 'C:\lib\libchromaprint.dll'
ctypes.util.find_library("chromaprint")         # None
ctypes.util.find_library("libchromaprint")      # OK: 'C:\lib\libchromaprint.dll'

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint.dll"))     # TypeError
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.dll"))  # OK: 'C:\lib\libchromaprint.dll'
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))         # TypeError
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))      # OK: 'C:\lib\libchromaprint.dll'

# Ubuntu Python 3.7

import chromaprint  # OK

import ctypes
ctypes.cdll.LoadLibrary("libchromaprint.so.1")  # OK
ctypes.cdll.LoadLibrary("libchromaprint.so.0")  # OSError
ctypes.cdll.LoadLibrary("libchromaprint.so")    # OSError
ctypes.cdll.LoadLibrary("libchromaprint")       # OSError
ctypes.cdll.LoadLibrary("chromaprint.so")       # OSError
ctypes.cdll.LoadLibrary("chromaprint")          # OSError

import ctypes
import ctypes.util
ctypes.util.find_library("libchromaprint.so.1")  # None
ctypes.util.find_library("libchromaprint.so.0")  # None
ctypes.util.find_library("libchromaprint.so")    # None
ctypes.util.find_library("libchromaprint")       # None
ctypes.util.find_library("chromaprint.so")       # OK: 'libchromaprint.so.1'
ctypes.util.find_library("chromaprint")          # OK: 'libchromaprint.so.1'

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so.1"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so.0"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so"))    # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))       # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint.so"))       # OK: 'libchromaprint.so.1'
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))          # OK: 'libchromaprint.so.1'

# Ubuntu Python 3.8

import chromaprint  # OK

import ctypes
ctypes.cdll.LoadLibrary("libchromaprint.so.1")  # OK
ctypes.cdll.LoadLibrary("libchromaprint.so.0")  # OSError
ctypes.cdll.LoadLibrary("libchromaprint.so")    # OSError
ctypes.cdll.LoadLibrary("libchromaprint")       # OSError
ctypes.cdll.LoadLibrary("chromaprint.so")       # OSError
ctypes.cdll.LoadLibrary("chromaprint")          # OSError

import ctypes
import ctypes.util
ctypes.util.find_library("libchromaprint.so.1")  # None
ctypes.util.find_library("libchromaprint.so.0")  # None
ctypes.util.find_library("libchromaprint.so")    # None
ctypes.util.find_library("libchromaprint")       # None
ctypes.util.find_library("chromaprint.so")       # OK: 'libchromaprint.so.1'
ctypes.util.find_library("chromaprint")          # OK: 'libchromaprint.so.1'

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so.1"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so.0"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.so"))    # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))       # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint.so"))       # OK: 'libchromaprint.so.1'
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))          # OK: 'libchromaprint.so.1'

# Cygwin Python 3.7

import chromaprint  # OK

import ctypes
ctypes.cdll.LoadLibrary("libchromaprint.dll.a")  # OSError
ctypes.cdll.LoadLibrary("cygchromaprint-1.dll")  # OK
ctypes.cdll.LoadLibrary("cygchromaprint-0.dll")  # OSError
ctypes.cdll.LoadLibrary("libchromaprint")        # OSError
ctypes.cdll.LoadLibrary("chromaprint")           # OSError

import ctypes
import ctypes.util
ctypes.util.find_library("libchromaprint.dll.a")  # None
ctypes.util.find_library("cygchromaprint-1.dll")  # None
ctypes.util.find_library("cygchromaprint-0.dll")  # None
ctypes.util.find_library("libchromaprint")        # None
ctypes.util.find_library("chromaprint")           # None
ctypes.util.find_library("chromaprint.dll.a")     # None
ctypes.util.find_library("chromaprint-1.dll")     # None
ctypes.util.find_library("chromaprint-0.dll")     # None

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.dll.a"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("cygchromaprint-1.dll"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("cygchromaprint-0.dll"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))        # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))           # ERROR: loads None!

# Cygwin Python 3.8

import chromaprint  # OK

import ctypes
ctypes.cdll.LoadLibrary("libchromaprint.dll.a")  # OSError
ctypes.cdll.LoadLibrary("cygchromaprint-1.dll")  # OK
ctypes.cdll.LoadLibrary("cygchromaprint-0.dll")  # OSError
ctypes.cdll.LoadLibrary("libchromaprint")        # OSError
ctypes.cdll.LoadLibrary("chromaprint")           # OSError

import ctypes
import ctypes.util
ctypes.util.find_library("libchromaprint.dll.a")  # None
ctypes.util.find_library("cygchromaprint-1.dll")  # None
ctypes.util.find_library("cygchromaprint-0.dll")  # None
ctypes.util.find_library("libchromaprint")        # None
ctypes.util.find_library("chromaprint")           # None
ctypes.util.find_library("chromaprint.dll.a")     # None
ctypes.util.find_library("chromaprint-1.dll")     # None
ctypes.util.find_library("chromaprint-0.dll")     # None

import ctypes
import ctypes.util
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint.dll.a"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("cygchromaprint-1.dll"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("cygchromaprint-0.dll"))  # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("libchromaprint"))        # ERROR: loads None!
ctypes.cdll.LoadLibrary(ctypes.util.find_library("chromaprint"))           # ERROR: loads None!

Important things to note:

Therefore, I would only use ctypes.util.find_library() on Windows and leave the other OSs as they are.

sampsyo commented 4 years ago

Wow; thank you for the detailed investigation! That thing about handling of None is quite a mess… anyway, you're absolutely right and we should clearly only use find_library on Windows. I'll merge this now!

Thanks again for all your help. :sparkles: