Drop remnants of support for Tesseract without has_textonly_pdf

Also improve Tesseract version checking so it can compare all of their
weird conventions.
This commit is contained in:
James R. Barlow
2021-04-07 23:05:21 -07:00
parent 906d77b389
commit 336d274a54
4 changed files with 68 additions and 58 deletions
+21 -21
View File
@@ -9,8 +9,10 @@
import logging
import os
import re
import shutil
from collections import namedtuple
from distutils.version import StrictVersion
from os import fspath
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
@@ -53,31 +55,29 @@ class TesseractLoggerAdapter(logging.LoggerAdapter):
return '[tesseract] %s' % (msg), kwargs
class TesseractVersion(StrictVersion):
version_re = re.compile(
r'''
^(\d+) \. (\d+) (\. (\d+))? # groups: 1/major, 2/minor, 3/[skip], 4/patch
[-]? # optional hyphen separator
(?:(alpha|beta|rc|dev)[.\-\ ]?(\d+)?)? # 5/prerelease, 6/prerelease_num
$
''',
re.VERBOSE | re.ASCII,
)
def parse(self, vstring):
try:
super().parse(vstring)
except TypeError as e:
if 'int() argument must be a string' in str(e):
super().parse(vstring + '0')
def version():
return get_version('tesseract', regex=r'tesseract\s(.+)')
def has_textonly_pdf(langs=None):
"""Does Tesseract have textonly_pdf capability?
Available in v4.00.00alpha since January 2017. Best to
parse the parameter list.
"""
args_tess = tess_base_args(langs, engine_mode=None) + ['--print-parameters', 'pdf']
params = ''
try:
proc = run(args_tess, check=True, stdout=PIPE, stderr=STDOUT)
params = proc.stdout
except CalledProcessError as e:
raise MissingDependencyError(
"Could not --print-parameters from tesseract. This can happen if the "
"TESSDATA_PREFIX environment is not set to a valid tessdata folder. "
) from e
if b'textonly_pdf' in params:
return True
return False
def has_user_words():
"""Does Tesseract have --user-words capability?
@@ -81,19 +81,13 @@ def check_options(options):
package={'linux': 'tesseract-ocr'},
version_checker=tesseract.version,
need_version='4.0.0', # using backport for Travis CI
version_parser=tesseract.TesseractVersion,
)
# Decide on what renderer to use
if options.pdf_renderer == 'auto':
options.pdf_renderer = 'sandwich'
if options.pdf_renderer == 'sandwich' and not tesseract.has_textonly_pdf(
set(options.languages)
):
raise MissingDependencyError(
"You are using an alpha version of Tesseract 4.0 that does not support "
"the textonly_pdf parameter. We don't support versions this old."
)
if not tesseract.has_user_words() and (options.user_words or options.user_patterns):
log.warning(
"Tesseract 4.0 ignores --user-words and --user-patterns, so these "
+29 -9
View File
@@ -13,14 +13,17 @@ import re
import sys
from collections.abc import Mapping
from contextlib import suppress
from distutils.version import LooseVersion
from distutils.version import LooseVersion, Version
from functools import lru_cache
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
from subprocess import run as subprocess_run
from typing import Callable, Optional, Type, Union
from ocrmypdf.exceptions import MissingDependencyError
# pylint: disable=logging-format-interpolation
log = logging.getLogger(__name__)
@@ -264,13 +267,30 @@ def _error_old_version(program, package, need_version, found_version, required_f
def check_external_program(
*,
program,
package,
version_checker,
need_version,
required_for=None,
program: str,
package: str,
version_checker: Union[str, Callable],
need_version: str,
required_for: Optional[str] = None,
recommended=False,
version_parser: Type[Version] = LooseVersion,
):
"""Check for required version of external program and raise exception if not.
Args:
program: The name of the program to test.
package: The name of a software package that typically supplies this program.
Usually the same as program.
version_check: A callable without arguments that retrieves the installed
version of program.
need_version: The minimum required version.
required_for: The name of an argument of feature that requires this program.
recommended: If this external program is recommended, instead of raising
an exception, log a warning and allow execution to continue.
version_parser: A class that should be used to parse and compare version
numbers. Used when version numbers do not follow standard conventions.
"""
try:
if callable(version_checker):
found_version = version_checker()
@@ -279,7 +299,7 @@ def check_external_program(
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
_error_missing_program(program, package, required_for, recommended)
if not recommended:
raise MissingDependencyError()
raise MissingDependencyError(program)
return
def remove_leading_v(s):
@@ -290,9 +310,9 @@ def check_external_program(
found_version = remove_leading_v(found_version)
need_version = remove_leading_v(need_version)
if found_version and LooseVersion(found_version) < LooseVersion(need_version):
if found_version and version_parser(found_version) < version_parser(need_version):
_error_old_version(program, package, need_version, found_version, required_for)
if not recommended:
raise MissingDependencyError()
raise MissingDependencyError(program)
log.debug('Found %s %s', program, found_version)
+17 -21
View File
@@ -13,6 +13,7 @@ import pytest
from ocrmypdf import _validation as vd
from ocrmypdf._concurrent import NullProgressBar, SerialExecutor
from ocrmypdf._exec.tesseract import TesseractVersion
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf.api import create_options
from ocrmypdf.cli import get_parser
@@ -52,29 +53,23 @@ def test_hocr_notlatin_warning(caplog):
def test_old_ghostscript(caplog):
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.19'), patch(
'ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True
):
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.19'):
vd._check_options(
*make_opts_pm(language='chi_sim', output_type='pdfa'), {'chi_sim'}
)
assert 'does not work correctly' in caplog.text
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.18'), patch(
'ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True
):
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.18'):
with pytest.raises(MissingDependencyError):
vd._check_options(*make_opts_pm(output_type='pdfa-3'), set())
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.24'), patch(
'ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True
):
with patch('ocrmypdf._exec.ghostscript.version', return_value='9.24'):
with pytest.raises(MissingDependencyError):
vd._check_options(*make_opts_pm(), set())
def test_old_tesseract_error():
with patch('ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=False):
with patch('ocrmypdf._exec.tesseract.version', return_value='4.00.00alpha'):
with pytest.raises(MissingDependencyError):
opts = make_opts(pdf_renderer='sandwich', language='eng')
plugin_manager = get_plugin_manager(opts.plugins)
@@ -227,17 +222,20 @@ def test_version_comparison():
version_checker=lambda: '10.0',
need_version='8.0.2',
)
vd.check_external_program(
program="tesseract",
package="tesseract",
version_checker=lambda: '4.0.0-beta.1',
need_version='4.0.0',
)
with pytest.raises(MissingDependencyError):
vd.check_external_program(
program="tesseract",
package="tesseract",
version_checker=lambda: '4.0.0-beta.1',
need_version='4.0.0',
version_parser=TesseractVersion,
)
vd.check_external_program(
program="tesseract",
package="tesseract",
version_checker=lambda: 'v5.0.0-alpha.20200201',
need_version='4.0.0',
version_parser=TesseractVersion,
)
with pytest.raises(MissingDependencyError):
vd.check_external_program(
@@ -277,11 +275,9 @@ def test_pagesegmode_warning(caplog):
def test_two_languages():
with patch('ocrmypdf._exec.tesseract.has_textonly_pdf', return_value=True) as mock:
vd._check_options(
*make_opts_pm(language='fakelang1+fakelang2'), {'fakelang1', 'fakelang2'}
)
mock.assert_called()
vd._check_options(
*make_opts_pm(language='fakelang1+fakelang2'), {'fakelang1', 'fakelang2'}
)
def test_sidecar_equals_output(resources, no_outpdf):