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:
@@ -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 "
|
||||
|
||||
@@ -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
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user