2015-07-22 02:59:25 -07:00
|
|
|
#!/usr/bin/env python3
|
2015-07-28 04:36:58 -07:00
|
|
|
# © 2015 James R. Barlow: github.com/jbarlow83
|
2015-07-22 02:59:25 -07:00
|
|
|
|
2015-07-25 00:57:07 -07:00
|
|
|
from __future__ import print_function
|
2015-07-27 16:11:51 -07:00
|
|
|
from subprocess import Popen, PIPE, check_output
|
2015-07-22 02:59:25 -07:00
|
|
|
import os
|
2015-07-22 04:00:59 -07:00
|
|
|
import shutil
|
|
|
|
from contextlib import suppress
|
2015-07-22 11:21:33 -07:00
|
|
|
import sys
|
2015-07-28 00:43:22 -07:00
|
|
|
import pytest
|
2015-07-28 02:31:18 -07:00
|
|
|
from ocrmypdf.pageinfo import pdf_get_all_pageinfo
|
2015-08-05 17:09:38 -07:00
|
|
|
import PyPDF2 as pypdf
|
2015-08-11 00:17:02 -07:00
|
|
|
from ocrmypdf import ExitCode
|
2015-07-27 22:07:04 -07:00
|
|
|
|
2015-07-22 11:21:33 -07:00
|
|
|
|
|
|
|
if sys.version_info.major < 3:
|
|
|
|
print("Requires Python 3.4+")
|
|
|
|
sys.exit(1)
|
2015-07-22 02:59:25 -07:00
|
|
|
|
|
|
|
TESTS_ROOT = os.path.abspath(os.path.dirname(__file__))
|
|
|
|
PROJECT_ROOT = os.path.dirname(TESTS_ROOT)
|
|
|
|
OCRMYPDF = os.path.join(PROJECT_ROOT, 'OCRmyPDF.sh')
|
|
|
|
TEST_RESOURCES = os.path.join(PROJECT_ROOT, 'tests', 'resources')
|
2015-08-20 01:25:31 -07:00
|
|
|
TEST_OUTPUT = os.environ.get(
|
|
|
|
'OCRMYPDF_TEST_OUTPUT',
|
|
|
|
default=os.path.join(PROJECT_ROOT, 'tests', 'output'))
|
2015-08-11 00:44:43 -07:00
|
|
|
TEST_BINARY_PATH = os.path.join(TEST_OUTPUT, 'fakebin')
|
2015-07-22 02:59:25 -07:00
|
|
|
|
|
|
|
|
2015-07-22 03:16:19 -07:00
|
|
|
def setup_module():
|
2015-07-22 04:00:59 -07:00
|
|
|
with suppress(FileNotFoundError):
|
|
|
|
shutil.rmtree(TEST_OUTPUT)
|
|
|
|
with suppress(FileExistsError):
|
2015-07-22 03:16:19 -07:00
|
|
|
os.mkdir(TEST_OUTPUT)
|
|
|
|
|
|
|
|
|
2015-07-27 15:39:54 -07:00
|
|
|
def run_ocrmypdf_sh(input_file, output_file, *args):
|
2015-07-22 04:00:59 -07:00
|
|
|
sh_args = ['sh', OCRMYPDF] + list(args) + [input_file, output_file]
|
2015-07-22 02:59:25 -07:00
|
|
|
sh = Popen(
|
|
|
|
sh_args, close_fds=True, stdout=PIPE, stderr=PIPE,
|
|
|
|
universal_newlines=True)
|
|
|
|
out, err = sh.communicate()
|
|
|
|
return sh, out, err
|
|
|
|
|
|
|
|
|
2015-08-05 17:09:38 -07:00
|
|
|
def _make_input(input_basename):
|
|
|
|
return os.path.join(TEST_RESOURCES, input_basename)
|
|
|
|
|
|
|
|
|
|
|
|
def _make_output(output_basename):
|
|
|
|
return os.path.join(TEST_OUTPUT, output_basename)
|
|
|
|
|
|
|
|
|
2015-07-28 00:43:22 -07:00
|
|
|
def check_ocrmypdf(input_basename, output_basename, *args):
|
2015-08-05 17:09:38 -07:00
|
|
|
input_file = _make_input(input_basename)
|
|
|
|
output_file = _make_output(output_basename)
|
2015-07-22 04:00:59 -07:00
|
|
|
|
2015-07-27 15:39:54 -07:00
|
|
|
sh, _, err = run_ocrmypdf_sh(input_file, output_file, *args)
|
2015-07-22 02:59:25 -07:00
|
|
|
assert sh.returncode == 0, err
|
2015-07-22 04:00:59 -07:00
|
|
|
assert os.path.exists(output_file), "Output file not created"
|
2015-07-27 15:39:54 -07:00
|
|
|
assert os.stat(output_file).st_size > 100, "PDF too small or empty"
|
|
|
|
return output_file
|
2015-07-22 02:59:25 -07:00
|
|
|
|
|
|
|
|
2015-08-11 00:23:48 -07:00
|
|
|
def run_ocrmypdf_env(input_basename, output_basename, *args, env=None):
|
2015-08-10 13:57:28 -07:00
|
|
|
input_file = _make_input(input_basename)
|
|
|
|
output_file = _make_output(output_basename)
|
|
|
|
|
2015-08-11 00:23:48 -07:00
|
|
|
if env is None:
|
|
|
|
env = os.environ
|
|
|
|
|
2015-08-10 13:57:28 -07:00
|
|
|
p_args = ['ocrmypdf'] + list(args) + [input_file, output_file]
|
|
|
|
p = Popen(
|
|
|
|
p_args, close_fds=True, stdout=PIPE, stderr=PIPE,
|
|
|
|
universal_newlines=True, env=env)
|
|
|
|
out, err = p.communicate()
|
|
|
|
return p, out, err
|
|
|
|
|
|
|
|
|
2015-07-28 00:43:22 -07:00
|
|
|
def test_quick():
|
|
|
|
check_ocrmypdf('c02-22.pdf', 'test_quick.pdf')
|
2015-07-22 04:00:59 -07:00
|
|
|
|
|
|
|
|
2015-07-28 00:43:22 -07:00
|
|
|
def test_deskew():
|
2015-07-27 16:11:51 -07:00
|
|
|
# Run with deskew
|
2015-07-28 00:43:22 -07:00
|
|
|
deskewed_pdf = check_ocrmypdf('skew.pdf', 'test_deskew.pdf', '-d')
|
2015-07-27 16:11:51 -07:00
|
|
|
|
|
|
|
# Now render as an image again and use Leptonica to find the skew angle
|
|
|
|
# to confirm that it was deskewed
|
2015-07-27 15:39:54 -07:00
|
|
|
from ocrmypdf.ghostscript import rasterize_pdf
|
|
|
|
import logging
|
|
|
|
log = logging.getLogger()
|
|
|
|
|
2015-08-05 17:09:38 -07:00
|
|
|
deskewed_png = _make_output('deskewed.png')
|
2015-07-27 15:39:54 -07:00
|
|
|
|
|
|
|
rasterize_pdf(
|
|
|
|
deskewed_pdf,
|
|
|
|
deskewed_png,
|
|
|
|
xres=150,
|
|
|
|
yres=150,
|
|
|
|
raster_device='pngmono',
|
|
|
|
log=log)
|
|
|
|
|
|
|
|
from ocrmypdf.leptonica import pixRead, pixDestroy, pixFindSkew
|
|
|
|
pix = pixRead(deskewed_png)
|
|
|
|
skew_angle, skew_confidence = pixFindSkew(pix)
|
|
|
|
pix = pixDestroy(pix)
|
|
|
|
|
|
|
|
print(skew_angle)
|
|
|
|
assert -0.5 < skew_angle < 0.5, "Deskewing failed"
|
2015-07-22 04:00:59 -07:00
|
|
|
|
|
|
|
|
2015-07-28 00:43:22 -07:00
|
|
|
def test_clean():
|
|
|
|
check_ocrmypdf('skew.pdf', 'test_clean.pdf', '-c')
|
2015-07-27 15:39:54 -07:00
|
|
|
|
|
|
|
|
2015-08-05 17:09:38 -07:00
|
|
|
def test_preserve_metadata():
|
|
|
|
pdf_before = pypdf.PdfFileReader(_make_input('graph.pdf'))
|
|
|
|
|
|
|
|
output = check_ocrmypdf('graph.pdf', 'test_metadata_preserve.pdf')
|
|
|
|
|
|
|
|
pdf_after = pypdf.PdfFileReader(output)
|
|
|
|
|
|
|
|
for key in ('/Title', '/Author'):
|
|
|
|
assert pdf_before.documentInfo[key] == pdf_after.documentInfo[key]
|
|
|
|
|
|
|
|
|
2015-08-05 16:57:04 -07:00
|
|
|
def test_override_metadata():
|
2015-08-11 00:23:48 -07:00
|
|
|
input_file = _make_input('c02-22.pdf')
|
|
|
|
output_file = _make_output('test_override_metadata.pdf')
|
|
|
|
|
2015-08-05 16:57:04 -07:00
|
|
|
german = 'Du siehst den Wald vor lauter Bäumen nicht.'
|
|
|
|
chinese = '孔子'
|
|
|
|
high_unicode = 'U+1030C is: 𐌌'
|
|
|
|
|
2015-08-11 00:23:48 -07:00
|
|
|
p, out, err = run_ocrmypdf_env(
|
|
|
|
input_file, output_file,
|
2015-08-05 16:57:04 -07:00
|
|
|
'--title', german,
|
|
|
|
'--author', chinese,
|
|
|
|
'--subject', high_unicode)
|
2015-07-27 16:11:51 -07:00
|
|
|
|
2015-08-11 00:23:48 -07:00
|
|
|
if p.returncode == ExitCode.invalid_output_pdfa:
|
|
|
|
print("Got invalid PDF return code, as expected - JHOVE bug")
|
|
|
|
assert p.returncode in (ExitCode.ok, ExitCode.invalid_output_pdfa)
|
|
|
|
|
|
|
|
pdf = output_file
|
|
|
|
|
2015-07-27 16:11:51 -07:00
|
|
|
out_pdfinfo = check_output(['pdfinfo', pdf], universal_newlines=True)
|
|
|
|
lines_pdfinfo = out_pdfinfo.splitlines()
|
|
|
|
pdfinfo = {}
|
|
|
|
for line in lines_pdfinfo:
|
|
|
|
k, v = line.strip().split(':', maxsplit=1)
|
|
|
|
pdfinfo[k.strip()] = v.strip()
|
|
|
|
|
2015-08-05 16:57:04 -07:00
|
|
|
assert pdfinfo['Title'] == german
|
|
|
|
assert pdfinfo['Author'] == chinese
|
|
|
|
assert pdfinfo['Subject'] == high_unicode
|
2015-07-27 16:11:51 -07:00
|
|
|
assert pdfinfo.get('Keywords', '') == ''
|
2015-07-27 17:18:02 -07:00
|
|
|
|
|
|
|
|
2015-07-27 22:07:04 -07:00
|
|
|
def check_oversample(renderer):
|
2015-07-28 00:43:22 -07:00
|
|
|
oversampled_pdf = check_ocrmypdf(
|
2015-07-27 22:07:04 -07:00
|
|
|
'skew.pdf', 'test_oversample_%s.pdf' % renderer, '--oversample', '300',
|
|
|
|
'--pdf-renderer', renderer)
|
2015-07-27 17:18:02 -07:00
|
|
|
|
|
|
|
pdfinfo = pdf_get_all_pageinfo(oversampled_pdf)
|
|
|
|
|
|
|
|
print(pdfinfo[0]['xres'])
|
|
|
|
assert abs(pdfinfo[0]['xres'] - 300) < 1
|
2015-07-27 22:07:04 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_oversample():
|
|
|
|
yield check_oversample, 'hocr'
|
|
|
|
yield check_oversample, 'tesseract'
|
|
|
|
|
|
|
|
|
|
|
|
def test_repeat_ocr():
|
2015-07-28 00:43:22 -07:00
|
|
|
sh, _, _ = run_ocrmypdf_sh('graph_ocred.pdf', 'wontwork.pdf')
|
|
|
|
assert sh.returncode != 0
|
2015-07-27 22:07:04 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_force_ocr():
|
2015-07-28 02:31:18 -07:00
|
|
|
out = check_ocrmypdf('graph_ocred.pdf', 'test_force.pdf', '-f')
|
|
|
|
pdfinfo = pdf_get_all_pageinfo(out)
|
|
|
|
assert pdfinfo[0]['has_text']
|
2015-07-27 22:07:04 -07:00
|
|
|
|
|
|
|
|
2015-07-28 00:43:22 -07:00
|
|
|
def test_skip_ocr():
|
2015-07-28 03:02:35 -07:00
|
|
|
check_ocrmypdf('graph_ocred.pdf', 'test_skip.pdf', '-s')
|
2015-07-28 01:47:30 -07:00
|
|
|
|
|
|
|
|
2015-08-05 23:17:38 -07:00
|
|
|
def test_argsfile():
|
|
|
|
with open(_make_output('test_argsfile.txt'), 'w') as argsfile:
|
|
|
|
print('--title', 'ArgsFile Test', '--author', 'Test Cases',
|
|
|
|
sep='\n', end='\n', file=argsfile)
|
|
|
|
check_ocrmypdf('graph.pdf', 'test_argsfile.pdf',
|
|
|
|
'@' + _make_output('test_argsfile.txt'))
|
|
|
|
|
|
|
|
|
2015-07-28 01:47:30 -07:00
|
|
|
def check_ocr_timeout(renderer):
|
2015-07-28 02:31:18 -07:00
|
|
|
out = check_ocrmypdf('skew.pdf', 'test_timeout_%s.pdf' % renderer,
|
|
|
|
'--tesseract-timeout', '1.0')
|
|
|
|
pdfinfo = pdf_get_all_pageinfo(out)
|
|
|
|
assert pdfinfo[0]['has_text'] == False
|
2015-07-28 01:47:30 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_ocr_timeout():
|
|
|
|
yield check_ocr_timeout, 'hocr'
|
|
|
|
yield check_ocr_timeout, 'tesseract'
|
2015-07-28 02:31:18 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_skip_big():
|
|
|
|
out = check_ocrmypdf('enormous.pdf', 'test_enormous.pdf',
|
|
|
|
'--skip-big', '10')
|
|
|
|
pdfinfo = pdf_get_all_pageinfo(out)
|
|
|
|
assert pdfinfo[0]['has_text'] == False
|
|
|
|
|
2015-07-28 03:02:35 -07:00
|
|
|
|
|
|
|
def check_maximum_options(renderer):
|
|
|
|
check_ocrmypdf(
|
|
|
|
'multipage.pdf', 'test_multipage%s.pdf' % renderer,
|
|
|
|
'-d', '-c', '-i', '-g', '-f', '-k', '--oversample', '300',
|
|
|
|
'--skip-big', '10', '--title', 'Too Many Weird Files',
|
|
|
|
'--author', 'py.test', '--pdf-renderer', renderer)
|
|
|
|
|
|
|
|
|
|
|
|
def test_maximum_options():
|
|
|
|
yield check_maximum_options, 'hocr'
|
|
|
|
yield check_maximum_options, 'tesseract'
|
2015-08-10 13:57:28 -07:00
|
|
|
|
|
|
|
|
|
|
|
def override_binary(binary, replacement):
|
2015-08-11 00:44:43 -07:00
|
|
|
'''Create a directory that contains a symlink named 'binary' that
|
|
|
|
points to replacement, another program to use in place of the
|
|
|
|
regular binary for testing.
|
|
|
|
|
|
|
|
override_binary('gs', 'replace_gs.py') will create an environment
|
|
|
|
in which "gs" will invoke replace_gs.py.
|
|
|
|
|
|
|
|
Not thread-safe with other test at the moment.
|
|
|
|
|
|
|
|
Returns the os.environ["PATH"] string under which this binary will
|
|
|
|
be invoked.'''
|
2015-08-10 13:57:28 -07:00
|
|
|
|
|
|
|
replacement_path = os.path.abspath(os.path.join(TESTS_ROOT,
|
|
|
|
replacement))
|
2015-08-11 00:44:43 -07:00
|
|
|
subdir = os.path.splitext(os.path.basename(replacement))[0]
|
2015-08-10 13:57:28 -07:00
|
|
|
binary_path = os.path.abspath(os.path.join(TEST_BINARY_PATH,
|
2015-08-11 00:44:43 -07:00
|
|
|
subdir,
|
2015-08-10 13:57:28 -07:00
|
|
|
binary))
|
2015-08-11 00:44:43 -07:00
|
|
|
with suppress(FileExistsError):
|
|
|
|
os.makedirs(os.path.dirname(binary_path))
|
|
|
|
assert os.path.isdir(os.path.dirname(binary_path))
|
2015-08-10 13:57:28 -07:00
|
|
|
assert not os.path.lexists(binary_path)
|
|
|
|
print("symlink %s -> %s" % (replacement_path, binary_path))
|
|
|
|
os.symlink(replacement_path, binary_path)
|
|
|
|
|
|
|
|
os.chmod(replacement_path, int('755', base=8))
|
|
|
|
|
|
|
|
return os.path.dirname(binary_path) + os.pathsep + os.environ["PATH"]
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def break_ghostscript_pdfa():
|
|
|
|
return override_binary('gs', 'replace_ghostscript_nopdfa.py')
|
|
|
|
|
|
|
|
|
2015-08-20 02:36:28 -07:00
|
|
|
@pytest.mark.skipif(os.environ.get('OCRMYPDF_IN_DOCKER', False),
|
|
|
|
reason="Requires writable filesystem")
|
2015-08-10 13:57:28 -07:00
|
|
|
def test_ghostscript_pdfa_fails(break_ghostscript_pdfa):
|
2015-08-16 01:57:35 -07:00
|
|
|
env = os.environ.copy()
|
2015-08-10 13:57:28 -07:00
|
|
|
env['PATH'] = break_ghostscript_pdfa
|
|
|
|
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
2015-08-11 00:23:48 -07:00
|
|
|
'graph_ocred.pdf', 'not_a_pdfa.pdf', '-v', '1', '--skip-text', env=env)
|
2015-08-16 01:57:35 -07:00
|
|
|
assert p.returncode == ExitCode.ok, err # no longer using JHOVE PDFA check
|
2015-08-11 00:17:02 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_tesseract_missing_tessdata():
|
2015-08-16 01:57:35 -07:00
|
|
|
env = os.environ.copy()
|
2015-08-11 00:17:02 -07:00
|
|
|
env['TESSDATA_PREFIX'] = '/tmp'
|
|
|
|
|
|
|
|
p, _, err = run_ocrmypdf_env(
|
2015-08-11 00:23:48 -07:00
|
|
|
'graph_ocred.pdf', 'not_a_pdfa.pdf', '-v', '1', '--skip-text', env=env)
|
2015-08-11 00:17:02 -07:00
|
|
|
assert p.returncode == ExitCode.missing_dependency, err
|
|
|
|
|
2015-08-11 02:19:46 -07:00
|
|
|
|
|
|
|
def test_invalid_input_pdf():
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
|
|
|
'invalid.pdf', 'wont_be_created.pdf')
|
|
|
|
assert p.returncode == ExitCode.input_file, err
|
|
|
|
|
2015-08-14 00:46:50 -07:00
|
|
|
|
|
|
|
def test_blank_input_pdf():
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
|
|
|
'blank.pdf', 'still_blank.pdf')
|
|
|
|
assert p.returncode == ExitCode.ok
|
|
|
|
|
2015-08-18 23:27:50 -07:00
|
|
|
|
|
|
|
def test_french():
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
2015-08-20 02:36:28 -07:00
|
|
|
'francais.pdf', 'francais.pdf', '-l', 'fra')
|
2015-08-18 23:27:50 -07:00
|
|
|
assert p.returncode == ExitCode.ok, \
|
|
|
|
"This test may fail if Tesseract language packs are missing"
|
|
|
|
|
|
|
|
|
|
|
|
def test_klingon():
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
2015-08-18 23:56:30 -07:00
|
|
|
'francais.pdf', 'francais.pdf', '-l', 'klz')
|
2015-08-18 23:27:50 -07:00
|
|
|
assert p.returncode == ExitCode.bad_args
|
2015-08-24 01:23:30 -07:00
|
|
|
|
|
|
|
|
|
|
|
def test_missing_docinfo():
|
|
|
|
p, out, err = run_ocrmypdf_env(
|
|
|
|
'missing_docinfo.pdf', 'missing_docinfo.pdf', '-l', 'eng')
|
|
|
|
assert p.returncode == ExitCode.ok, err
|
|
|
|
|