Change most tests to use ocrmypdf API instead of subprocess
The main benefit of this is code coverage gains can actually follow it. Also removes most ugly os.environ hacks.
This commit is contained in:
@@ -35,8 +35,6 @@ def run(args=None):
|
||||
if not check_closed_streams(options):
|
||||
return ExitCode.bad_args
|
||||
|
||||
if os.environ.get('PYTEST_CURRENT_TEST'):
|
||||
os.environ['_OCRMYPDF_TEST_INFILE'] = options.input_file
|
||||
if hasattr(os, 'nice'):
|
||||
os.nice(5)
|
||||
|
||||
|
||||
@@ -348,6 +348,7 @@ def get_orientation_correction(preview, page_context):
|
||||
engine_mode=page_context.options.tesseract_oem,
|
||||
timeout=page_context.options.tesseract_timeout,
|
||||
log=page_context.log,
|
||||
tesseract_env=page_context.options.tesseract_env,
|
||||
)
|
||||
|
||||
direction = {0: '⇧', 90: '⇨', 180: '⇩', 270: '⇦'}
|
||||
@@ -548,6 +549,7 @@ def ocr_tesseract_hocr(input_file, page_context):
|
||||
pagesegmode=options.tesseract_pagesegmode,
|
||||
user_words=options.user_words,
|
||||
user_patterns=options.user_patterns,
|
||||
tesseract_env=options.tesseract_env,
|
||||
log=page_context.log,
|
||||
)
|
||||
return (hocr_out, hocr_text_out)
|
||||
@@ -627,6 +629,7 @@ def ocr_tesseract_textonly_pdf(input_image, page_context):
|
||||
pagesegmode=options.tesseract_pagesegmode,
|
||||
user_words=options.user_words,
|
||||
user_patterns=options.user_patterns,
|
||||
tesseract_env=options.tesseract_env,
|
||||
log=page_context.log,
|
||||
)
|
||||
return (output_pdf, output_text)
|
||||
|
||||
+23
-5
@@ -16,6 +16,7 @@
|
||||
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from enum import IntEnum
|
||||
from pathlib import Path
|
||||
@@ -113,13 +114,16 @@ def configure_logging(verbosity, progress_bar_friendly=True, manage_root_logger=
|
||||
|
||||
def create_options(*, input_file, output_file, **kwargs):
|
||||
cmdline = []
|
||||
filters = []
|
||||
deferred = []
|
||||
|
||||
for arg, val in kwargs.items():
|
||||
if val is None:
|
||||
continue
|
||||
if arg.startswith('filter') and (callable(val) or isinstance(val, str)):
|
||||
filters.append((arg, val))
|
||||
deferred.append((arg, val))
|
||||
continue
|
||||
elif arg == 'tesseract_env':
|
||||
deferred.append((arg, val))
|
||||
continue
|
||||
cmd_style_arg = arg.replace('_', '-')
|
||||
cmdline.append(f"--{cmd_style_arg}")
|
||||
@@ -132,15 +136,20 @@ def create_options(*, input_file, output_file, **kwargs):
|
||||
elif isinstance(val, Path):
|
||||
cmdline.append(str(val))
|
||||
else:
|
||||
raise TypeError(f"{val} ({type(val)})")
|
||||
raise TypeError(f"{arg}: {val} ({type(val)})")
|
||||
|
||||
cmdline.append(str(input_file))
|
||||
cmdline.append(str(output_file))
|
||||
|
||||
parser.api_mode = True
|
||||
options = parser.parse_args(cmdline)
|
||||
for keyword, function in filters:
|
||||
setattr(options, keyword, function)
|
||||
for keyword, val in deferred:
|
||||
setattr(options, keyword, val)
|
||||
|
||||
# If we are running a Tesseract spoof, ensure it knows what the input file is
|
||||
if os.environ.get('PYTEST_CURRENT_TEST') and options.tesseract_env:
|
||||
options.tesseract_env['_OCRMYPDF_TEST_INFILE'] = input_file
|
||||
|
||||
return options
|
||||
|
||||
|
||||
@@ -190,9 +199,18 @@ def ocrmypdf( # pylint: disable=unused-argument
|
||||
keep_temporary_files=None,
|
||||
progress_bar=None,
|
||||
filter_ocr_image=None,
|
||||
tesseract_env=None,
|
||||
):
|
||||
"""Run OCRmyPDF on one PDF or image.
|
||||
|
||||
For most arguments, see documentation for the equivalent command line parameter.
|
||||
A few specific arguments are discussed here:
|
||||
|
||||
Args:
|
||||
use_threads (bool): Use worker threads instead of processes. This reduces
|
||||
performance but may make debugging easier since it is easier to set
|
||||
breakpoints.
|
||||
tesseract_env (dict): Override environment variables for Tesseract
|
||||
Raises:
|
||||
ocrmypdf.PdfMergeFailedError: If the input PDF is malformed, preventing merging
|
||||
with the OCR layer.
|
||||
|
||||
@@ -482,3 +482,4 @@ debugging.add_argument(
|
||||
action='store_true',
|
||||
help="Keep temporary files (helpful for debugging)",
|
||||
)
|
||||
debugging.add_argument('--tesseract-env', type=str, help=argparse.SUPPRESS)
|
||||
|
||||
@@ -28,7 +28,7 @@ from collections.abc import Mapping
|
||||
log = logging.Logger(__name__)
|
||||
|
||||
|
||||
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)'):
|
||||
def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)', env=None):
|
||||
"Get the version of the specified program"
|
||||
args_prog = [program, version_arg]
|
||||
try:
|
||||
@@ -39,6 +39,7 @@ def get_version(program, *, version_arg='--version', regex=r'(\d+(\.\d+)*)'):
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
check=True,
|
||||
env=env,
|
||||
)
|
||||
output = proc.stdout
|
||||
except FileNotFoundError as e:
|
||||
|
||||
@@ -60,18 +60,16 @@ HOCR_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
|
||||
"""
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def version():
|
||||
return get_version('tesseract', regex=r'tesseract\s(.+)')
|
||||
def version(tesseract_env=None):
|
||||
return get_version('tesseract', regex=r'tesseract\s(.+)', env=tesseract_env)
|
||||
|
||||
|
||||
def v4():
|
||||
def v4(tesseract_env=None):
|
||||
"Is this Tesseract v4.0?"
|
||||
return version() >= '4'
|
||||
return version(tesseract_env) >= '4'
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def has_textonly_pdf():
|
||||
def has_textonly_pdf(tesseract_env=None):
|
||||
"""Does Tesseract have textonly_pdf capability?
|
||||
|
||||
Available in v4.00.00alpha since January 2017. Best to
|
||||
@@ -80,7 +78,15 @@ def has_textonly_pdf():
|
||||
args_tess = ['tesseract', '--print-parameters', 'pdf']
|
||||
params = ''
|
||||
try:
|
||||
params = check_output(args_tess, universal_newlines=True, stderr=STDOUT)
|
||||
proc = run(
|
||||
args_tess,
|
||||
check=True,
|
||||
universal_newlines=True,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
env=tesseract_env,
|
||||
)
|
||||
params = proc.stdout
|
||||
except CalledProcessError as e:
|
||||
print("Could not --print-parameters from tesseract", file=sys.stderr)
|
||||
raise MissingDependencyError from e
|
||||
@@ -89,8 +95,7 @@ def has_textonly_pdf():
|
||||
return False
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def languages():
|
||||
def languages(tesseract_env=None):
|
||||
def lang_error(output):
|
||||
msg = dedent(
|
||||
"""Tesseract failed to report available languages.
|
||||
@@ -104,7 +109,12 @@ def languages():
|
||||
args_tess = ['tesseract', '--list-langs']
|
||||
try:
|
||||
proc = run(
|
||||
args_tess, universal_newlines=True, stdout=PIPE, stderr=STDOUT, check=True
|
||||
args_tess,
|
||||
universal_newlines=True,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
check=True,
|
||||
env=tesseract_env,
|
||||
)
|
||||
output = proc.stdout
|
||||
except CalledProcessError as e:
|
||||
@@ -127,7 +137,7 @@ def tess_base_args(langs, engine_mode):
|
||||
return args
|
||||
|
||||
|
||||
def get_orientation(input_file, engine_mode, timeout: float, log):
|
||||
def get_orientation(input_file, engine_mode, timeout: float, log, tesseract_env=None):
|
||||
args_tesseract = tess_base_args(['osd'], engine_mode) + [
|
||||
'--psm',
|
||||
'0',
|
||||
@@ -136,7 +146,15 @@ def get_orientation(input_file, engine_mode, timeout: float, log):
|
||||
]
|
||||
|
||||
try:
|
||||
stdout = check_output(args_tesseract, stderr=STDOUT, timeout=timeout)
|
||||
p = run(
|
||||
args_tesseract,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
timeout=timeout,
|
||||
check=True,
|
||||
env=tesseract_env,
|
||||
)
|
||||
stdout = p.stdout
|
||||
except TimeoutExpired:
|
||||
return OrientationConfidence(angle=0, confidence=0.0)
|
||||
except CalledProcessError as e:
|
||||
@@ -235,6 +253,7 @@ def generate_hocr(
|
||||
pagesegmode: int,
|
||||
user_words,
|
||||
user_patterns,
|
||||
tesseract_env,
|
||||
log,
|
||||
):
|
||||
|
||||
@@ -258,7 +277,15 @@ def generate_hocr(
|
||||
args_tesseract.extend([input_file, prefix, 'hocr', 'txt'] + tessconfig)
|
||||
try:
|
||||
log.debug(args_tesseract)
|
||||
stdout = check_output(args_tesseract, stderr=STDOUT, timeout=timeout)
|
||||
p = run(
|
||||
args_tesseract,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
timeout=timeout,
|
||||
check=True,
|
||||
env=tesseract_env,
|
||||
)
|
||||
stdout = p.stdout
|
||||
except TimeoutExpired:
|
||||
# Generate a HOCR file with no recognized text if tesseract times out
|
||||
# Temporary workaround to hocrTransform not being able to function if
|
||||
@@ -310,9 +337,10 @@ def generate_pdf(
|
||||
pagesegmode: int,
|
||||
user_words,
|
||||
user_patterns,
|
||||
tesseract_env,
|
||||
log,
|
||||
):
|
||||
'''Use Tesseract to render a PDF.
|
||||
"""Use Tesseract to render a PDF.
|
||||
|
||||
input_image -- image to analyze
|
||||
skip_pdf -- if we time out, use this file as output
|
||||
@@ -324,14 +352,14 @@ def generate_pdf(
|
||||
tessconfig -- tesseract configuration
|
||||
timeout -- timeout (seconds)
|
||||
log -- logger object
|
||||
'''
|
||||
"""
|
||||
|
||||
args_tesseract = tess_base_args(language, engine_mode)
|
||||
|
||||
if pagesegmode is not None:
|
||||
args_tesseract.extend(['--psm', str(pagesegmode)])
|
||||
|
||||
if text_only and has_textonly_pdf():
|
||||
if text_only and has_textonly_pdf(tesseract_env):
|
||||
args_tesseract.extend(['-c', 'textonly_pdf=1'])
|
||||
|
||||
if user_words:
|
||||
@@ -348,7 +376,15 @@ def generate_pdf(
|
||||
args_tesseract.extend([input_image, prefix, 'pdf', 'txt'] + tessconfig)
|
||||
try:
|
||||
log.debug(args_tesseract)
|
||||
stdout = check_output(args_tesseract, stderr=STDOUT, timeout=timeout)
|
||||
p = run(
|
||||
args_tesseract,
|
||||
stdout=PIPE,
|
||||
stderr=STDOUT,
|
||||
timeout=timeout,
|
||||
check=True,
|
||||
env=tesseract_env,
|
||||
)
|
||||
stdout = p.stdout
|
||||
if os.path.exists(prefix + '.txt'):
|
||||
shutil.move(prefix + '.txt', output_text)
|
||||
except TimeoutExpired:
|
||||
|
||||
Reference in New Issue
Block a user