diff --git a/install.bat b/install.bat index fdb309a..132e631 100644 --- a/install.bat +++ b/install.bat @@ -104,14 +104,17 @@ echo Installing tesserocr wheel... "%PYTHON%" -m pip install --force-reinstall "%~dp0dependencies\tesserocr-2.5.2-cp310-cp310-win_amd64.whl" >nul 2>&1 :: --- Smoke test: import key dependencies --- +:: Note: os.add_dll_directory (not ctypes.WinDLL) is the correct mechanism. +:: Python 3.8+ loads .pyd files with LOAD_LIBRARY_SEARCH_USER_DIRS which +:: searches user-registered dirs for the pyd AND all its transitive deps. echo. echo Verifying dependencies... -"%PYTHON%" -c "import os,sys,ctypes;dirs=[d for d in [os.path.join(sys.prefix,'Library',x) for x in ['bin','mingw-w64\\bin','usr\\bin']] if os.path.isdir(d)];[os.add_dll_directory(d) for d in dirs];os.environ.__setitem__('PATH',os.pathsep.join(dirs)+os.pathsep+os.environ.get('PATH',''));t=os.path.join(sys.prefix,'Library','bin','tesseract51.dll');ctypes.WinDLL(t) if os.path.isfile(t) else None;import cv2,mss,numpy,tesserocr,discord,transitions,rapidfuzz" >nul 2>&1 +"%PYTHON%" -c "import os,sys;[os.add_dll_directory(d) for d in [os.path.join(sys.prefix,'Library',x) for x in ['bin','mingw-w64\\bin','usr\\bin']] if os.path.isdir(d)];import cv2,mss,numpy,tesserocr,discord,transitions,rapidfuzz" >nul 2>&1 if %errorlevel% neq 0 ( echo. echo WARNING: Dependency check failed. Running diagnostics... echo. - "%PYTHON%" -c "import os,sys,ctypes; lb=os.path.join(sys.prefix,'Library','bin'); dlls=[f for f in (os.listdir(lb) if os.path.isdir(lb) else []) if any(k in f.lower() for k in ['tess','lepton'])]; print(' Tesseract DLLs in Library/bin:',dlls or 'NONE'); dirs=[d for d in [os.path.join(sys.prefix,'Library',x) for x in ['bin','mingw-w64\\bin','usr\\bin']] if os.path.isdir(d)]; [os.add_dll_directory(d) for d in dirs]; os.environ.__setitem__('PATH',os.pathsep.join(dirs)+os.pathsep+os.environ.get('PATH','')); t=os.path.join(lb,'tesseract51.dll'); r=ctypes.WinDLL(t) if os.path.isfile(t) else 'missing'; print(' ctypes tesseract51.dll:',type(r).__name__); __import__('tesserocr'); print(' tesserocr import: OK')" 2>&1 + "%PYTHON%" -c "import os,sys; lb=os.path.join(sys.prefix,'Library','bin'); dlls=[f for f in (os.listdir(lb) if os.path.isdir(lb) else []) if any(k in f.lower() for k in ['tess','lepton'])]; print(' Tesseract DLLs in Library/bin:',dlls or 'NONE'); [os.add_dll_directory(d) for d in [os.path.join(sys.prefix,'Library',x) for x in ['bin','mingw-w64\\bin','usr\\bin']] if os.path.isdir(d)]; import tesserocr; print(' tesserocr import: OK')" 2>&1 echo. echo *** Do NOT run "pip install tesserocr" manually - it builds from source and echo *** will always fail on Windows. Re-run install.bat to fix dependencies. diff --git a/src/d2r_image/ocr.py b/src/d2r_image/ocr.py index 2c4f868..77c0e34 100644 --- a/src/d2r_image/ocr.py +++ b/src/d2r_image/ocr.py @@ -1,28 +1,17 @@ import os import sys if os.name == 'nt': - import ctypes - # Collect all conda DLL dirs that exist - _conda_dll_dirs = [d for d in [ + # Python 3.8+ loads .pyd files with LOAD_LIBRARY_SEARCH_USER_DIRS, which + # searches directories registered via os.add_dll_directory() — including + # all transitive DLL dependencies. This is the correct mechanism for + # making tesserocr find tesseract51.dll and leptonica without conda activate. + for _d in [ os.path.join(sys.prefix, 'Library', 'bin'), os.path.join(sys.prefix, 'Library', 'mingw-w64', 'bin'), os.path.join(sys.prefix, 'Library', 'usr', 'bin'), - ] if os.path.isdir(d)] - # 1) os.add_dll_directory: lets Python find tesserocr's direct deps when - # loading the .pyd via LOAD_LIBRARY_SEARCH_USER_DIRS. - for _d in _conda_dll_dirs: - os.add_dll_directory(_d) - # 2) Prepend to PATH: when ctypes.WinDLL loads tesseract51.dll by absolute - # path, Windows resolves *that DLL's* own transitive deps (leptonica, - # zlib, libpng, …) using the standard search order: app-dir → System32 - # → Windows-dir → cwd → PATH. Those libs live in Library\bin, not - # System32, so without PATH they won't be found. - os.environ['PATH'] = os.pathsep.join(_conda_dll_dirs) + os.pathsep + os.environ.get('PATH', '') - # 3) Pre-load tesseract51.dll now (PATH is set, so all its deps resolve). - # Once it's in the DLL cache the .pyd import below reuses it. - _tess = os.path.join(sys.prefix, 'Library', 'bin', 'tesseract51.dll') - if os.path.isfile(_tess): - ctypes.WinDLL(_tess) + ]: + if os.path.isdir(_d): + os.add_dll_directory(_d) from tesserocr import PyTessBaseAPI, OEM import numpy as np import cv2 diff --git a/src/main.py b/src/main.py index 72e4db4..76b0694 100644 --- a/src/main.py +++ b/src/main.py @@ -1,22 +1,17 @@ # Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs # that tesserocr depends on (tesseract51.dll, leptonica-1.78.0.dll). # This must run BEFORE any import that might trigger tesserocr loading. +# os.add_dll_directory registers dirs with LOAD_LIBRARY_SEARCH_USER_DIRS, +# which applies to the .pyd and all its transitive DLL dependencies. import os, sys if sys.platform == "win32": - import ctypes - _bundled = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "conda_env", "Library", "bin") - _conda_dll_dirs = [d for d in [ - _bundled, + for _d in [ os.path.join(sys.prefix, "Library", "bin"), os.path.join(sys.prefix, "Library", "mingw-w64", "bin"), os.path.join(sys.prefix, "Library", "usr", "bin"), - ] if os.path.isdir(d)] - for _d in _conda_dll_dirs: - os.add_dll_directory(_d) - os.environ['PATH'] = os.pathsep.join(_conda_dll_dirs) + os.pathsep + os.environ.get('PATH', '') - _tess = os.path.join(sys.prefix, "Library", "bin", "tesseract51.dll") - if os.path.isfile(_tess): - ctypes.WinDLL(_tess) + ]: + if os.path.isdir(_d): + os.add_dll_directory(_d) from dataclasses import dataclass from input_layer import keyboard diff --git a/src/shopper.py b/src/shopper.py index 46547c6..344fb64 100644 --- a/src/shopper.py +++ b/src/shopper.py @@ -1,18 +1,14 @@ # Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs +# that tesserocr depends on (tesseract51.dll, leptonica-1.78.0.dll). import os, sys if sys.platform == "win32": - import ctypes - _conda_dll_dirs = [d for d in [ + for _d in [ os.path.join(sys.prefix, "Library", "bin"), os.path.join(sys.prefix, "Library", "mingw-w64", "bin"), os.path.join(sys.prefix, "Library", "usr", "bin"), - ] if os.path.isdir(d)] - for _d in _conda_dll_dirs: - os.add_dll_directory(_d) - os.environ['PATH'] = os.pathsep.join(_conda_dll_dirs) + os.pathsep + os.environ.get('PATH', '') - _tess = os.path.join(sys.prefix, "Library", "bin", "tesseract51.dll") - if os.path.isfile(_tess): - ctypes.WinDLL(_tess) + ]: + if os.path.isdir(_d): + os.add_dll_directory(_d) from beautifultable import BeautifulTable import logging