fix(ocr): ensure Tesseract language data for embed_text_layer regardless of active OCR provider

Root cause: ensure_ocr_languages_from_settings() only downloaded tessdata
when the 'tesseract' provider was active, but embed_text_layer() uses
ocrmypdf (which needs tessdata) as a fallback for ALL OCR providers.

- embed_text_layer(): call ensure_tesseract_languages(language) after
  confirming ocrmypdf is on PATH, so language data is present before
  ocrmypdf is invoked (prevents exit code 3 for fra/deu/etc.)
- ensure_ocr_languages_from_settings(): extend the condition from
  'tesseract' in active_providers to also trigger when ocrmypdf is
  on PATH, enabling proactive pre-download at startup for any config
- Tests: mock shutil.which and ensure_tesseract_languages in affected
  test cases; rename azure-only test and add new test for ocrmypdf case

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-02-24 18:02:19 +00:00
parent 8f3b554b0d
commit 2b2a97c2fa
4 changed files with 59 additions and 4 deletions
+29 -3
View File
@@ -344,7 +344,10 @@ class TestEnsureOCRLanguagesFromSettings:
assert result == {"tesseract_missing": [], "easyocr_failed": []}
def test_runs_easyocr_check_when_provider_active(self):
"""Calls ensure_easyocr_models when 'easyocr' is in ocr_providers."""
"""Calls ensure_easyocr_models when 'easyocr' is in ocr_providers.
When ocrmypdf is absent, only easyocr language checks run.
"""
from app.utils.ocr_language_manager import ensure_ocr_languages_from_settings
mock_settings = self._mock_settings(providers="easyocr", easyocr_langs="en,de")
@@ -352,6 +355,7 @@ class TestEnsureOCRLanguagesFromSettings:
patch("app.utils.ocr_language_manager.ensure_tesseract_languages", return_value=[]) as mock_tess,
patch("app.utils.ocr_language_manager.ensure_easyocr_models", return_value=[]) as mock_easy,
patch("app.config.settings", mock_settings),
patch("shutil.which", return_value=None),
):
result = ensure_ocr_languages_from_settings()
@@ -359,8 +363,8 @@ class TestEnsureOCRLanguagesFromSettings:
mock_easy.assert_called_once_with(["en", "de"])
assert result["easyocr_failed"] == []
def test_skips_both_when_only_azure(self):
"""Neither Tesseract nor EasyOCR checks run when only Azure is configured."""
def test_skips_both_when_only_azure_and_no_ocrmypdf(self):
"""Neither Tesseract nor EasyOCR checks run when only Azure is configured and ocrmypdf is absent."""
from app.utils.ocr_language_manager import ensure_ocr_languages_from_settings
mock_settings = self._mock_settings(providers="azure")
@@ -368,6 +372,7 @@ class TestEnsureOCRLanguagesFromSettings:
patch("app.utils.ocr_language_manager.ensure_tesseract_languages") as mock_tess,
patch("app.utils.ocr_language_manager.ensure_easyocr_models") as mock_easy,
patch("app.config.settings", mock_settings),
patch("shutil.which", return_value=None),
):
result = ensure_ocr_languages_from_settings()
@@ -375,6 +380,27 @@ class TestEnsureOCRLanguagesFromSettings:
mock_easy.assert_not_called()
assert result == {"tesseract_missing": [], "easyocr_failed": []}
def test_runs_tesseract_when_ocrmypdf_available_with_non_tesseract_provider(self):
"""Tesseract language check runs when ocrmypdf is on PATH even without the tesseract provider.
embed_text_layer() uses ocrmypdf internally regardless of the active OCR
provider. Pre-downloading language data on startup prevents code-3 failures.
"""
from app.utils.ocr_language_manager import ensure_ocr_languages_from_settings
mock_settings = self._mock_settings(providers="azure", tesseract_lang="eng+deu+fra")
with (
patch("app.utils.ocr_language_manager.ensure_tesseract_languages", return_value=[]) as mock_tess,
patch("app.utils.ocr_language_manager.ensure_easyocr_models") as mock_easy,
patch("app.config.settings", mock_settings),
patch("shutil.which", return_value="/usr/local/bin/ocrmypdf"),
):
result = ensure_ocr_languages_from_settings()
mock_tess.assert_called_once_with("eng+deu+fra")
mock_easy.assert_not_called()
assert result == {"tesseract_missing": [], "easyocr_failed": []}
def test_runs_both_when_both_providers_active(self):
"""Both checks run when both 'tesseract' and 'easyocr' are in ocr_providers."""
from app.utils.ocr_language_manager import ensure_ocr_languages_from_settings