From 20448bfeb22545c9dd2dc567ce45fe03d1da810d Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Fri, 26 Dec 2025 13:05:46 +0100 Subject: [PATCH] fix(windows): Bundle tessdata for OCR support out of the box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows release was missing Tesseract OCR runtime dependencies (tessdata files) needed for the HardSubx feature to work. Users had to manually install Tesseract OCR and set TESSDATA_PREFIX. Changes: - Add get_executable_directory() to ocr.c that returns the directory containing the executable (works on Windows, Linux, and macOS) - Update probe_tessdata_location() to search for tessdata in the executable directory, enabling bundled tessdata to be found - Update release workflow to download eng.traineddata and osd.traineddata from tesseract-ocr/tessdata_fast during release builds - Update WiX installer to include tessdata directory with the traineddata files Now the Windows release includes tessdata files, and CCExtractor will automatically find them in the installation directory without requiring users to install Tesseract separately or set environment variables. Fixes #1578 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .github/workflows/release.yml | 8 ++++ src/lib_ccx/ocr.c | 74 ++++++++++++++++++++++++++++++++++- windows/installer.wxs | 11 ++++++ 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6bee9b53..1dff7a97 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -81,6 +81,14 @@ jobs: - name: Copy files to directory for installer run: mkdir installer; cp ./x64/Release-Full/ccextractorwinfull.exe ./installer; cp ./x64/Release-Full/*.dll ./installer working-directory: ./windows + - name: Download tessdata for OCR support + run: | + mkdir -p ./installer/tessdata + # Download English traineddata from tessdata_fast (smaller, faster, good for most use cases) + Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata_fast/raw/main/eng.traineddata" -OutFile "./installer/tessdata/eng.traineddata" + # Download OSD (Orientation and Script Detection) for automatic script detection + Invoke-WebRequest -Uri "https://github.com/tesseract-ocr/tessdata_fast/raw/main/osd.traineddata" -OutFile "./installer/tessdata/osd.traineddata" + working-directory: ./windows - name: install WiX run: dotnet tool uninstall --global wix; dotnet tool install --global wix --version 6.0.2 && wix extension add -g WixToolset.UI.wixext/6.0.2 - name: Make sure WiX works diff --git a/src/lib_ccx/ocr.c b/src/lib_ccx/ocr.c index 028aeb59..3bbfbc54 100644 --- a/src/lib_ccx/ocr.c +++ b/src/lib_ccx/ocr.c @@ -8,6 +8,11 @@ #include #include "ccx_encoders_helpers.h" #include "ccx_encoders_spupng.h" +#ifdef _WIN32 +#include +#elif defined(__APPLE__) +#include +#endif #include "ocr.h" struct ocrCtx @@ -100,6 +105,68 @@ void delete_ocr(void **arg) freep(arg); } +/** + * get_executable_directory + * + * Returns the directory containing the executable. + * Returns a pointer to a static buffer, or NULL on failure. + */ +static const char *get_executable_directory(void) +{ + static char exe_dir[1024] = {0}; + static int initialized = 0; + + if (initialized) + return exe_dir[0] ? exe_dir : NULL; + + initialized = 1; + +#ifdef _WIN32 + char exe_path[MAX_PATH]; + DWORD len = GetModuleFileNameA(NULL, exe_path, MAX_PATH); + if (len == 0 || len >= MAX_PATH) + return NULL; + + // Find the last backslash and truncate there + char *last_sep = strrchr(exe_path, '\\'); + if (last_sep) + { + *last_sep = '\0'; + strncpy(exe_dir, exe_path, sizeof(exe_dir) - 1); + exe_dir[sizeof(exe_dir) - 1] = '\0'; + } +#elif defined(__linux__) + char exe_path[1024]; + ssize_t len = readlink("/proc/self/exe", exe_path, sizeof(exe_path) - 1); + if (len <= 0) + return NULL; + exe_path[len] = '\0'; + + char *last_sep = strrchr(exe_path, '/'); + if (last_sep) + { + *last_sep = '\0'; + strncpy(exe_dir, exe_path, sizeof(exe_dir) - 1); + exe_dir[sizeof(exe_dir) - 1] = '\0'; + } +#elif defined(__APPLE__) + char exe_path[1024]; + uint32_t size = sizeof(exe_path); + if (_NSGetExecutablePath(exe_path, &size) != 0) + return NULL; + + char *last_sep = strrchr(exe_path, '/'); + if (last_sep) + { + *last_sep = '\0'; + strncpy(exe_dir, exe_path, sizeof(exe_dir) - 1); + exe_dir[sizeof(exe_dir) - 1] = '\0'; + } +#endif + + return exe_dir[0] ? exe_dir : NULL; +} + /** * probe_tessdata_location * @@ -107,8 +174,10 @@ void delete_ocr(void **arg) * * Priority of Tesseract traineddata file search paths:- * 1. tessdata in TESSDATA_PREFIX, if it is specified. Overrides others - * 2. tessdata in current working directory - * 3. tessdata in /usr/share + * 2. tessdata in executable directory (for bundled tessdata) + * 3. tessdata in current working directory + * 4. tessdata in system locations (/usr/share, etc.) + * 5. tessdata in default Tesseract install location (Windows) */ char *probe_tessdata_location(const char *lang) { @@ -116,6 +185,7 @@ char *probe_tessdata_location(const char *lang) const char *paths[] = { getenv("TESSDATA_PREFIX"), + get_executable_directory(), "./", "/usr/share/", "/usr/local/share/", diff --git a/windows/installer.wxs b/windows/installer.wxs index d322361d..b4f9b72e 100644 --- a/windows/installer.wxs +++ b/windows/installer.wxs @@ -4,6 +4,7 @@ + @@ -40,6 +41,7 @@ + @@ -194,6 +196,15 @@ + + + + + + + + +