From 61a88d2968e2c89520d08f7918500ca0ef0745dd Mon Sep 17 00:00:00 2001 From: alexpolo1 Date: Wed, 5 Aug 2026 21:28:02 +0200 Subject: [PATCH] fix(install): make Tesseract/OCR setup work on clean Win10/Win11 Follow-up to the conda scope fix: the same admin/winget assumptions broke the OCR backend, which is what actually carries item text reading since tesserocr's MSVC DLL chain commonly fails to load. - install.bat installed Tesseract via `winget install` with no --scope, i.e. machine-wide into "C:\Program Files", which requires admin. On a clean non-admin box this failed and left NO working OCR backend at all (tesserocr already fails), so OCR_READY=0 and item reading was dead. Now: winget machine scope -> winget --scope user -> direct download of the official NSIS installer with a per-user /D= target. Also stops trusting winget's exit code (non-zero when already installed) and re-resolves tesseract.exe after each attempt. - The downloaded installer is size-checked (~50 MB; <20 MB = failed download) before being executed, matching the Miniforge handling. - Added a :find_tesseract subroutine that resolves tesseract.exe from Program Files, Program Files (x86), %LOCALAPPDATA%\Programs, %ProgramData% and PATH. Verification now uses the resolved path instead of the hardcoded "C:\Program Files" one. - ocr.py: added Program Files (x86) and the per-user %LOCALAPPDATA%\Programs\Tesseract-OCR location to the runtime search order, since per-user installs are not on PATH. - run_botty.bat: only export PYTESSERACT_TESSERACT_CMD when the file exists, falling back to the per-user path, so a stale machine-wide value cannot shadow a valid per-user install. Verified on this machine: all install.bat dependency imports OK (cv2/mss/numpy/transitions/rapidfuzz/pydantic/pytesseract/yaml/discord), pytesseract resolves tesseract 5.5.0, osdetect reports the win11 profile, config loads, 140 tests pass. Co-Authored-By: Claude Opus 4.8 --- install.bat | 94 ++++++++++++++++++++++++++++++++++++++------ run_botty.bat | 11 +++++- src/d2r_image/ocr.py | 5 +++ 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/install.bat b/install.bat index 37c3c3d..56fa224 100644 --- a/install.bat +++ b/install.bat @@ -318,23 +318,71 @@ if exist "dependencies\tesserocr.cp310-win_amd64.pyd" ( ) :: --- Backend 2: pytesseract (reliable fallback) --- -:: Needs tesseract.exe from winget. Works on any Python version. -:: On Win10: winget may not be available -- if install fails, offer manual link. +:: This is the backend that actually carries OCR on most machines (tesserocr's +:: MSVC DLL chain frequently fails), so it must install without admin rights and +:: without winget -- neither is guaranteed on a clean Win10 box. "%CONDA_EXE%" run -n botty python -m pip install --progress-bar off pytesseract >nul 2>&1 if %errorlevel% neq 0 ( echo WARNING: Could not install pytesseract Python wrapper. ) -if not exist "C:\Program Files\Tesseract-OCR\tesseract.exe" ( - echo Installing Tesseract OCR via winget... - winget install --id tesseract-ocr.tesseract --silent --accept-package-agreements --accept-source-agreements 2>nul - if !errorlevel! neq 0 ( - echo winget failed -- on Windows 10 you may need to install manually: - echo https://github.com/tesseract-ocr/tesseract/releases - echo Download the win64 installer, run it, keep the default install path. +call :find_tesseract +if not defined TESS_EXE ( + echo Installing Tesseract OCR... + :: Try winget machine scope, then user scope (no admin needed). winget's + :: exit code is unreliable (non-zero when already installed), so after each + :: attempt we re-resolve tesseract.exe rather than trusting errorlevel. + winget --version >nul 2>&1 + if !errorlevel! equ 0 ( + winget install --id tesseract-ocr.tesseract --exact --silent ^ + --accept-package-agreements --accept-source-agreements >nul 2>&1 + call :find_tesseract + if not defined TESS_EXE ( + winget install --id tesseract-ocr.tesseract --exact --silent --scope user ^ + --accept-package-agreements --accept-source-agreements >nul 2>&1 + call :find_tesseract + ) ) ) +:: Last resort: direct download of the official NSIS installer and a per-user +:: install. Covers clean Win10 machines with no winget and no admin rights. +if not defined TESS_EXE ( + echo winget unavailable or failed -- downloading Tesseract directly... + set "TS_INSTALLER=%TEMP%\tesseract-setup.exe" + set "TS_URL=https://github.com/tesseract-ocr/tesseract/releases/download/5.5.0/tesseract-ocr-w64-setup-5.5.0.20241111.exe" + set "TS_DEST=%LOCALAPPDATA%\Programs\Tesseract-OCR" + del /q "!TS_INSTALLER!" >nul 2>&1 + curl -Lk --progress-bar "!TS_URL!" -o "!TS_INSTALLER!" 2>&1 + if not exist "!TS_INSTALLER!" ( + powershell -NoProfile -ExecutionPolicy Bypass -Command ^ + "$ProgressPreference='SilentlyContinue'; try { Invoke-WebRequest -Uri '!TS_URL!' -OutFile '!TS_INSTALLER!' -UseBasicParsing; exit 0 } catch { exit 1 }" + ) + :: Sanity-check the size (installer is ~50 MB) before executing it. + set "TS_SIZE=0" + if exist "!TS_INSTALLER!" for %%A in ("!TS_INSTALLER!") do set "TS_SIZE=%%~zA" + if !TS_SIZE! GEQ 20971520 ( + :: NSIS /D= must be last and unquoted, and breaks on paths containing + :: spaces -- so only use it when the target path has none. + echo !TS_DEST! | find " " >nul + if !errorlevel! equ 0 ( + start /wait "" "!TS_INSTALLER!" /S + ) else ( + start /wait "" "!TS_INSTALLER!" /S /D=!TS_DEST! + ) + del /q "!TS_INSTALLER!" >nul 2>&1 + call :find_tesseract + ) +) + +if defined TESS_EXE ( + echo Tesseract: !TESS_EXE! +) else ( + echo WARNING: Tesseract could not be installed automatically. Install manually: + echo https://github.com/tesseract-ocr/tesseract/releases + echo Download the win64 installer, run it, then re-run install.bat. +) + :: --- Verify OCR: at least one backend must work --- echo. echo Checking OCR backends... @@ -349,8 +397,8 @@ if %errorlevel% == 0 ( echo tesserocr: not available ^(DLL issue -- bot will use pytesseract instead^) ) -if exist "C:\Program Files\Tesseract-OCR\tesseract.exe" ( - "%CONDA_EXE%" run -n botty python -c "import pytesseract; pytesseract.pytesseract.tesseract_cmd=r'C:\Program Files\Tesseract-OCR\tesseract.exe'; pytesseract.get_tesseract_version()" >nul 2>&1 +if defined TESS_EXE ( + "%CONDA_EXE%" run -n botty python -c "import pytesseract; pytesseract.pytesseract.tesseract_cmd=r'!TESS_EXE!'; pytesseract.get_tesseract_version()" >nul 2>&1 if !errorlevel! == 0 ( echo pytesseract: OK ^(reliable fallback^) set "OCR_READY=1" @@ -414,3 +462,27 @@ echo Installation complete! echo Run botty with: run_botty.bat echo ============================================ echo. +goto :eof + +:: --- Resolve tesseract.exe into TESS_EXE --- +:: Checks machine-wide (winget default / manual install) and per-user (winget +:: --scope user / our direct NSIS fallback) locations, plus PATH. Sets TESS_EXE +:: to the first hit, or clears it if none found. +:find_tesseract +set "TESS_EXE=" +for %%T in ( + "C:\Program Files\Tesseract-OCR\tesseract.exe" + "C:\Program Files (x86)\Tesseract-OCR\tesseract.exe" + "%LOCALAPPDATA%\Programs\Tesseract-OCR\tesseract.exe" + "%ProgramData%\Tesseract-OCR\tesseract.exe" +) do ( + if exist %%T ( + set "TESS_EXE=%%~T" + goto :eof + ) +) +for /f "delims=" %%T in ('where tesseract 2^>nul') do ( + set "TESS_EXE=%%T" + goto :eof +) +goto :eof diff --git a/run_botty.bat b/run_botty.bat index 641906d..c1cf2d3 100644 --- a/run_botty.bat +++ b/run_botty.bat @@ -17,9 +17,16 @@ set "CONDA_PREFIX=%_ENV%" set "PYTHONUTF8=1" set "PYTHONIOENCODING=utf-8" set "SSL_CERT_DIR=" -:: Use winget tesseract 5.5.0 (conda tesseract crashes with access violation) +:: Use winget tesseract 5.5.0 (conda tesseract crashes with access violation). +:: Only export the path if it actually exists -- on machines where Tesseract was +:: installed per-user (no admin), it lives under %LOCALAPPDATA%\Programs instead, +:: and src\d2r_image\ocr.py resolves that itself. set "TESSDATA_PREFIX=%_ENV%\Library\share" -set "PYTESSERACT_TESSERACT_CMD=C:\Program Files\Tesseract-OCR\tesseract.exe" +if exist "C:\Program Files\Tesseract-OCR\tesseract.exe" ( + set "PYTESSERACT_TESSERACT_CMD=C:\Program Files\Tesseract-OCR\tesseract.exe" +) else if exist "%LOCALAPPDATA%\Programs\Tesseract-OCR\tesseract.exe" ( + set "PYTESSERACT_TESSERACT_CMD=%LOCALAPPDATA%\Programs\Tesseract-OCR\tesseract.exe" +) echo Launching Botty ... "%PYTHON%" "%BOTTY_DIR%src\main.py" diff --git a/src/d2r_image/ocr.py b/src/d2r_image/ocr.py index a9ecf34..cdf1077 100644 --- a/src/d2r_image/ocr.py +++ b/src/d2r_image/ocr.py @@ -46,6 +46,11 @@ try: os.path.join(_APP_BASE, "tesseract", "tesseract.exe"), # bundled in release shutil.which("tesseract"), r"C:\Program Files\Tesseract-OCR\tesseract.exe", + r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe", + # Per-user installs (winget --scope user, or install.bat's direct NSIS + # fallback) land here and are not on PATH -- needed on machines where + # the user has no admin rights. + os.path.join(os.environ.get("LOCALAPPDATA", ""), "Programs", "Tesseract-OCR", "tesseract.exe"), ] _cmd = next((c for c in _candidates if c and os.path.isfile(c)), None) if _cmd: