fix: replace custom tesserocr wheel with conda-forge package

The custom tesserocr-2.5.2 wheel was compiled on a specific machine
against DLL versions that do not match a fresh conda-forge installation.
This caused persistent ImportError: DLL load failed regardless of PATH
or LoadLibraryExW approach.

conda-forge's tesserocr package is compiled against the exact same
conda-forge tesseract/leptonica binaries, so all DLL dependencies
are automatically satisfied within the conda environment — no manual
DLL path manipulation needed.

Changes:
- environment.yml: add tesserocr + tesseract as conda-forge packages,
  remove leptonica pin (no longer needed), remove custom wheel from pip
- install.bat: replace wheel force-reinstall with pip uninstall cleanup
- src/*.py: simplify DLL fix to os.add_dll_directory only

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
alex
2026-05-24 14:49:00 +02:00
co-authored by Claude Sonnet 4.6
parent e1c18faf26
commit d6c8d8d5f7
5 changed files with 24 additions and 58 deletions
+4 -4
View File
@@ -5,8 +5,9 @@ dependencies:
- python=3.10
- pywin32
- pip
- leptonica=1.78.0 # pinned: tesserocr wheel links against leptonica-1.78.0.dll
- tesseract=5.1.0
- tesserocr # conda-forge build — compiled against conda-forge tesseract/leptonica,
# so all DLL deps are resolved automatically within the env
- tesseract # version chosen by conda to match tesserocr
- pkg-config
- rapidfuzz=2.15.1
- pip:
@@ -31,7 +32,6 @@ dependencies:
- pillow
- discord.py
- parse
- dependencies/tesserocr-2.5.2-cp310-cp310-win_amd64.whl
- typing_extensions
- colorama
- dataclasses-json
- dataclasses-json
+3 -3
View File
@@ -91,10 +91,10 @@ exit /b 1
:env_created
echo Botty Python: %PYTHON%
:: --- Force-reinstall the tesserocr wheel so the correct binary is always used ---
:: --- Remove old pip-installed tesserocr wheel if present (replaced by conda-forge package) ---
echo.
echo Installing tesserocr wheel...
"%PYTHON%" -m pip install --force-reinstall "%~dp0dependencies\tesserocr-2.5.2-cp310-cp310-win_amd64.whl" >nul 2>&1
echo Removing old pip-installed tesserocr if present...
"%PYTHON%" -m pip uninstall tesserocr -y >nul 2>&1
:: --- Smoke test via conda run (activates the full env like "conda activate botty") ---
:: This is the only reliable way to ensure all DLL transitive deps are resolved.
+6 -18
View File
@@ -1,27 +1,15 @@
import os
import sys
if os.name == 'nt':
import ctypes
_dll_dirs = [d for d in [
# Register conda DLL dirs so Python's .pyd loader (LOAD_LIBRARY_SEARCH_USER_DIRS)
# can find tesserocr's dependencies (tesseract, leptonica, etc.)
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)]
# Register dirs for Python's .pyd loader
for _d in _dll_dirs:
os.add_dll_directory(_d)
# Preload tesseract51.dll via LoadLibraryExW with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS.
# Unlike ctypes.WinDLL/LoadLibraryW, this flag propagates to the ENTIRE
# transitive dep chain (leptonica, mingw runtimes, etc.), resolving them
# all via the user DLL dirs registered above.
_LoadLibEx = ctypes.windll.kernel32.LoadLibraryExW
_LoadLibEx.restype = ctypes.c_void_p
_LoadLibEx.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_ulong]
for _d in _dll_dirs:
_tess = os.path.join(_d, 'tesseract51.dll')
if os.path.isfile(_tess):
_LoadLibEx(_tess, None, 0x1000) # LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
break
]:
if os.path.isdir(_d):
os.add_dll_directory(_d)
from tesserocr import PyTessBaseAPI, OEM
import numpy as np
import cv2
+6 -18
View File
@@ -1,26 +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).
# This must run BEFORE any import that might trigger tesserocr loading.
# Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs.
# Must run BEFORE any import that might trigger tesserocr loading.
import os, sys
if sys.platform == "win32":
import ctypes
_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 _dll_dirs:
os.add_dll_directory(_d)
# Preload tesseract51.dll via LoadLibraryExW with LOAD_LIBRARY_SEARCH_DEFAULT_DIRS
# (0x1000) so transitive deps are resolved via user DLL dirs, not just PATH.
_LoadLibEx = ctypes.windll.kernel32.LoadLibraryExW
_LoadLibEx.restype = ctypes.c_void_p
_LoadLibEx.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_ulong]
for _d in _dll_dirs:
_tess = os.path.join(_d, "tesseract51.dll")
if os.path.isfile(_tess):
_LoadLibEx(_tess, None, 0x1000)
break
]:
if os.path.isdir(_d):
os.add_dll_directory(_d)
from dataclasses import dataclass
from input_layer import keyboard
+5 -15
View File
@@ -1,23 +1,13 @@
# 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).
# Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs.
import os, sys
if sys.platform == "win32":
import ctypes
_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 _dll_dirs:
os.add_dll_directory(_d)
_LoadLibEx = ctypes.windll.kernel32.LoadLibraryExW
_LoadLibEx.restype = ctypes.c_void_p
_LoadLibEx.argtypes = [ctypes.c_wchar_p, ctypes.c_void_p, ctypes.c_ulong]
for _d in _dll_dirs:
_tess = os.path.join(_d, "tesseract51.dll")
if os.path.isfile(_tess):
_LoadLibEx(_tess, None, 0x1000)
break
]:
if os.path.isdir(_d):
os.add_dll_directory(_d)
from beautifultable import BeautifulTable
import logging