Compare commits

...
23 Commits
Author SHA1 Message Date
James R. Barlow 84d120e850 v5.6.1 notes 2018-03-09 08:00:42 -08:00
James R. Barlow 8159cc6b88 Skip one test that fails for qpdf 8.0.[0,1], due to qpdf regression 2018-03-09 07:57:22 -08:00
James R. Barlow 4046766ca5 Fix Python 3.5 test suite failure on symlinks
Did not account for API difference in pathlib
2018-03-02 16:57:46 -08:00
James R. Barlow 810390df0b pipeline: Refactor duplicate with clause 2018-03-02 16:57:15 -08:00
James R. Barlow de749bc7ae Fix regression - output to stdout broken 2018-03-02 15:35:51 -08:00
James R. Barlow 9965b8800c Move Dockerfiles out of the way 2018-03-02 15:26:41 -08:00
James R. Barlow ab870fddd6 Dockerignore: glob supported now 2018-03-02 15:24:42 -08:00
James R. Barlow a79d6807cf Dockerfiles: remove deprecated MAINTAINER tag 2018-03-02 15:24:28 -08:00
James R. Barlow 8185fb7e43 Migrate Travis CI setup to Brewfile 2018-03-02 15:16:05 -08:00
Tucker Barbourandjbarlow83 4b10929b25 Fix Homebrew python package (#227)
Homebrew removed python3 and python now defaults to version 3. Here we
use `brew upgrade python` to upgrade the pre-installed version of
python to python3.
2018-03-02 14:59:23 -08:00
James R. Barlow 2d8aad1086 Improve docs 2018-03-01 00:24:38 -08:00
James R. Barlow 74ca736333 Issue #223: improve text of encrypted PDF error message 2018-02-27 15:08:22 -08:00
James R. Barlow 5e4fd8b0b9 compile_leptonica should have shebang 2018-02-24 12:49:36 -08:00
James R. Barlow 8ab8132411 lint: unused variables, wildcard imports 2018-02-24 12:48:52 -08:00
James R. Barlow 6899dd46e4 lint: remove extraneous backslash 2018-02-24 12:42:37 -08:00
James R. Barlow 8ad0697a20 lint: remove duplicate property definition 2018-02-24 12:42:03 -08:00
James R. Barlow b47e5672e6 Remove old test case that no longer works 2018-02-24 12:40:14 -08:00
James R. Barlow 45c7bd9a60 lint: Remove shebangs from non-executable files 2018-02-24 12:38:58 -08:00
James R. Barlow e7bcb95635 Fix pylint errors 2018-02-24 11:59:01 -08:00
James R. Barlow 3de83627a9 Handle output to /dev/null or directory (#219)
Previously we threw an exception if the output name was a directory (only after doing OCR) and would trigger a PermissionError on trying to flip permission bits of /dev/null due to shutil.copyfile implementation. Instead of copying file use shutil.copyfileobj which should also respect umask etc.
2018-02-19 22:15:07 -08:00
James R. Barlow f248576994 Change instructions to point away from private tap 2018-02-19 17:33:58 -08:00
James R. Barlow aac5b6de3b Update autobrew script to match final changes 2018-02-17 00:12:03 -08:00
James R. Barlow 24435f11e0 We are now in homebrew 2018-02-15 17:42:16 -08:00
38 changed files with 170 additions and 149 deletions
+1 -2
View File
@@ -1,7 +1,6 @@
# OCRmyPDF
#
FROM ubuntu:17.10
MAINTAINER James R. Barlow <jim@purplerock.ca>
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
@@ -52,4 +51,4 @@ WORKDIR /home/docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
@@ -14,4 +14,4 @@ USER docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
@@ -1,7 +1,6 @@
# OCRmyPDF
#
FROM ubuntu:17.10
MAINTAINER James R. Barlow <jim@purplerock.ca>
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common python-software-properties \
@@ -62,4 +61,4 @@ WORKDIR /home/docker
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
ENTRYPOINT ["/application/.docker/docker-wrapper.sh"]
+3 -12
View File
@@ -3,18 +3,9 @@
*.pyc
*.rst
*.sublime*
*/*.pyc
*/*/*.pyc
*/*/*/*.pyc
*/*/*/*/*.pyc
*/*/*/*/*/*.pyc
*/*/*/*/*/*/*.pyc
*/*/*/*/*/*/*/*.pyc
.cache/
.eggs/
.github/
.git/
.ipynb_checkpoints/
**/*.pyc
.*/
!.docker/
.ruffus_history.sqlite
bin/
build/
+3 -1
View File
@@ -34,7 +34,9 @@ before_install: |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
bash .travis/linux_before_install.sh
elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
bash .travis/osx_before_install.sh
brew update && brew bundle --file=.travis/Brewfile
pip3 install --upgrade pip
pip3 install wheel
fi
install:
+12
View File
@@ -0,0 +1,12 @@
brew 'python'
brew 'ghostscript'
brew 'jbig2dec'
brew 'leptonica'
brew 'libffi'
brew 'libtiff'
brew 'libxml2'
brew 'openjpeg'
brew 'qpdf'
brew 'tesseract'
brew 'unpaper'
+5 -4
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# © 2016-7 James R. Barlow: github.com/jbarlow83
# © 2017-18 James R. Barlow: github.com/jbarlow83
from string import Template
from subprocess import run, PIPE
@@ -19,7 +19,7 @@ class Ocrmypdf < Formula
depends_on "ghostscript"
depends_on "jpeg"
depends_on "libpng"
depends_on "python3"
depends_on "python"
depends_on "qpdf"
depends_on "tesseract"
depends_on "unpaper"
@@ -55,8 +55,9 @@ ${resources}
# Since we use Python 3, we require a UTF-8 locale
ENV["LC_ALL"] = "en_US.UTF-8"
# Use ocrmypdf -f to rasterize the PDF to image before doing OCR
system "#{bin}/ocrmypdf", "-f", "-q", "--deskew", test_fixtures("test.pdf"), testpath/"ocr.pdf"
system "#{bin}/ocrmypdf", "-f", "-q", "--deskew",
test_fixtures("test.pdf"), "ocr.pdf"
assert_predicate testpath/"ocr.pdf", :exist?
end
end
""")
-17
View File
@@ -1,17 +0,0 @@
#!/bin/bash
# © 2017 James R. Barlow: github.com/jbarlow83
set -euo pipefail
set -x
brew update
brew install openjpeg jbig2dec libtiff # image libraries
brew install qpdf
brew install ghostscript
brew install python3
brew install libxml2 libffi leptonica
brew install unpaper # optional
brew install tesseract
pip3 install --upgrade pip
pip3 install wheel
+1 -2
View File
@@ -74,11 +74,10 @@ Users of Debian 9 or later or Ubuntu 16.10 or later may simply
apt-get install ocrmypdf
and macOS users may simply
and macOS users with Homebrew may simply
.. code-block:: bash
brew tap jbarlow83/ocrmypdf
brew install ocrmypdf
For everyone else, `see our documentation <https://ocrmypdf.readthedocs.io/en/latest/installation.html>`_ for installation steps.
+1 -1
View File
@@ -9,7 +9,7 @@ OCRmyPDF documentation
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to
be searched.
PDFs are the best format for scanned documents. Unfortunately, PDFs can be difficult to work with. OCRmyPDF makes it easy to apply image processing and OCR to existing PDFs.
PDF is the best format for storing and exchanging scanned documents. Unfortunately, PDFs can be difficult to modify. OCRmyPDF makes it easy to apply image processing and OCR to existing PDFs.
.. toctree::
:maxdepth: 1
+6 -4
View File
@@ -15,18 +15,20 @@ Users of Debian 9 ("stretch") or later or Ubuntu 16.10 or later may simply
Installing on macOS
-------------------
A `Homebrew <https://brew.sh>`_ tap is available for macOS:
OCRmyPDF is now a standard `Homebrew <https://brew.sh>`_ formula. To install on macOS:
.. code-block:: bash
brew tap jbarlow83/ocrmypdf
brew install ocrmypdf
.. warning::
.. note::
Users who previously installed OCRmyPDF on macOS using ``pip install ocrmypdf`` should remove the pip version (``pip3 uninstall ocrmypdf``) before switching to the Homebrew version.
.. note::
Users who previously installed OCRmyPDF from the private tap should switch to the mainline version (``brew untap jbarlow83/ocrmypdf``) and install from there.
.. _Docker-install:
Installing the Docker image
+10
View File
@@ -5,6 +5,16 @@ OCRmyPDF uses `semantic versioning <http://semver.org/>`_ for its command line i
The OCRmyPDF package itself does not contain a public API, although it is fairly stable and breaking changes are usually timed with a major release. A future release will clearly define the stable public API.
v5.6.1
------
- Fix issue #219: change how the final output file is created to avoid triggering permission errors when the output is a special file such as ``/dev/null``
- Fix test suite failures due to a qpdf 8.0.0 regression and Python 3.5's handling of symlink
- The "encrypted PDF" error message was different depending on the type of PDF encryption. Now a single clear message appears for all types of PDF encryption.
- ocrmypdf is now in Homebrew. Homebrew users are advised to the version of ocrmypdf in the official homebrew-core formulas rather than the private tap.
- Some linting
v5.6.0
------
+12 -4
View File
@@ -30,14 +30,16 @@ Depending on the options specified, OCRmyPDF may graft the OCR layer into the ex
Using OCRmyPDF online or as a service
-------------------------------------
OCRmyPDF should not be deployed as a public-facing service, like a website where a potential attacker could upload a PDF of their choice for OCR. OCRmyPDF is not designed to be secure against PDF malware. Another concern is PDFs specifically designed to be a denial of service attack: PDFs can contain recursive data structures that sometimes send parsers into infinite loops, and issue complex graphics drawing commands.
OCRmyPDF should not be deployed as a public-facing service, such as a website where a potential attacker could upload a PDF of their choice for OCR. OCRmyPDF is not designed to be secure against PDF malware. Another concern is PDFs specifically designed to be a denial of service attack: PDFs can contain recursive data structures that sometimes send parsers into infinite loops, and issue complex graphics drawing commands.
Setting aside these concerns, a side effect of OCRmyPDF is it may incidentally sanitize PDFs that contain malware. It runs ``qpdf`` to repair the PDF, which could correct malformed PDF structures that are part of an attack. When PDF/A output is selected (the default), the input PDF is partially reconstructed by Ghostscript. When ``--force-ocr`` is used, all pages are rasterized and reconverted to PDF, which could remove malware in embedded images. No guarantees.
OCRmyPDF should be relatively safe to use in a trusted intranet, with some considerations:
Limiting CPU usage
^^^^^^^^^^^^^^^^^^
OCRmyPDF will attempt to use all available CPUs and storage, so executing ``nice ocrmypdf`` or limiting the number of jobs with the ``-j`` argument may ensure the server remains available. Another option would be run OCRmyPDF jobs inside a Docker container or virtual machine, which can impose its own limits on CPU usage.
OCRmyPDF will attempt to use all available CPUs and storage, so executing ``nice ocrmypdf`` or limiting the number of jobs with the ``-j`` argument may ensure the server remains available. Another option would be run OCRmyPDF jobs inside a Docker container, a virtual machine, or a cloud instance, which can impose its own limits on CPU usage and be terminated "from orbit" if it fails to complete.
Temporary storage requirements
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -46,7 +48,7 @@ OCRmyPDF will use a large amount of temporary storage for its work, proportional
To check temporary storage usage on actual files, run ``ocrmypdf -k ...`` which will preserve and print the path to temporary storage when the job is done.
To change where temporary files are stored, change the ``TMPDIR`` environment variable for ocrmypdf's environment. (Python's ``tempfile.gettempdir()`` returns the root directory in which temporary files will be stored.)
To change where temporary files are stored, change the ``TMPDIR`` environment variable for ocrmypdf's environment. (Python's ``tempfile.gettempdir()`` returns the root directory in which temporary files will be stored.) For example, one could redirect ``TMPDIR`` to a large RAM disk to avoid wear on HDD/SSD and potentially improve performance. On Amazon Web Services, ``TMPDIR`` can be set to `empheral storage <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html>`_.
Timeouts
^^^^^^^^
@@ -64,7 +66,13 @@ Abbyy Cloud OCR is a viable commercial alternative with a web services API.
Password protection, digital signatures and certification
---------------------------------------------------------
OCRmyPDF cannot remove password protection from a PDF. ``qpdf``, one of its dependencies, has this capability. After OCR is applied, password protection is not permitted on PDF/A documents but the file can be converted to regular PDF.
Password protected PDFs usually have two passwords, and owner and user password. When the user password is set to empty, PDF readers will open the file automatically and marked it as "(SECURED)". While not as reliable as a digital signature, this indicates that whoever set the password approved of the file at that time. When the user password is set, the document cannot be viewed without the password.
Either way, OCRmyPDF does not remove passwords from PDFs and exits with an error on encountering them.
``qpdf``, one of OCRmyPDF's dependencies, can remove passwords. If the owner and user password are set, a password is required for ``qpdf``. If only the owner password is set, then the password can be stripped, even if one does not have the owner password.
After OCR is applied, password protection is not permitted on PDF/A documents but the file can be converted to regular PDF.
Many programs exist which are capable of inserting an image of someone's signature. On its own, this offers no security guarantees. It is trivial to remove the signature image and apply it to other files. This practice offers no real security.
+26 -21
View File
@@ -27,7 +27,7 @@ from .helpers import is_iterable_notstr, re_symlink, is_file_writable
from .exec import tesseract, qpdf, ghostscript
from . import PROGRAM_NAME, VERSION
from .exceptions import *
from .exceptions import ExitCode, ExitCodeException, MissingDependencyError
from . import exceptions as ocrmypdf_exceptions
from ._unicodefun import verify_python3_env
@@ -554,6 +554,7 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
exit_code = None
task_name, job_name, exc_name, exc_value, exc_stack = ruffus_five_tuple
job_name = job_name # unused
if exc_name == 'builtins.SystemExit':
match = re.search(r"\.(.+?)\)", exc_value)
exit_code_name = match.groups()[0]
@@ -576,6 +577,21 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
msg = "Error occurred while running this command:"
log.error(msg + '\n' + exc_value)
exit_code = ExitCode.child_process_error
elif (exc_name == 'PyPDF2.utils.PdfReadError' and \
'not been decrypted' in exc_value) or \
(exc_name == 'ocrmypdf.exceptions.EncryptedPdfError'):
log.error(textwrap.dedent("""\
Input PDF is encrypted. The encryption must be removed to
perform OCR.
For information about this PDF's security use
qpdf --show-encryption infilename
You can remove the encryption using
qpdf --decrypt [--password=[password]] infilename
"""))
exit_code = ExitCode.encrypted_pdf
elif exc_name == 'ocrmypdf.exceptions.PdfMergeFailedError':
log.error(textwrap.dedent("""\
Failed to merge PDF image layer with OCR layer
@@ -591,19 +607,6 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
base_exc_name = exc_name.replace('ocrmypdf.exceptions.', '')
exc_class = getattr(ocrmypdf_exceptions, base_exc_name)
exit_code = exc_class.exit_code
elif exc_name == 'PyPDF2.utils.PdfReadError' and \
'not been decrypted' in exc_value:
log.error(textwrap.dedent("""\
Input PDF uses either an encryption algorithm or a PDF security
handler that is not supported by ocrmypdf.
For information about this PDF's security use
qpdf --show-encryption [...input PDF...]
(Only algorithms "R = 1" and "R = 2" are supported.)
"""))
exit_code = ExitCode.encrypted_pdf
elif exc_name == 'PIL.Image.DecompressionBombError':
msg = cleanup_ruffus_error_message(exc_value)
msg += ("\nUse the --max-image-mpixels argument to set increase the "
@@ -683,7 +686,7 @@ def log_page_orientations(pdfinfo, _log):
180: 's', 270: 'w'}
orientations = []
for n, page in enumerate(pdfinfo):
angle = pdfinfo[n].rotation or 0
angle = page.rotation or 0
if angle != 0:
orientations.append('{0}{1}'.format(
n + 1,
@@ -757,14 +760,14 @@ def run_pipeline():
elif not is_file_writable(options.output_file):
_log.error(
"Output file location (" + options.output_file + ") " +
"is not writable.")
"is not a writable file.")
return ExitCode.file_access_error
manager = JobContextManager()
manager.register('JobContext', JobContext)
manager.register('JobContext', JobContext) # pylint: disable=no-member
manager.start()
context = manager.JobContext()
context = manager.JobContext() # pylint: disable=no-member
context.set_options(options)
context.set_work_folder(work_folder)
@@ -804,7 +807,11 @@ def run_pipeline():
if options.flowchart:
_log.info("Flowchart saved to {}".format(options.flowchart))
elif options.output_file != '-':
elif options.output_file == '-':
_log.info("Output sent to stdout")
elif os.path.samefile(options.output_file, os.devnull):
pass # Say nothing when sending to dev null
else:
if options.output_type.startswith('pdfa'):
pdfa_info = file_claims_pdfa(options.output_file)
if pdfa_info['pass']:
@@ -817,8 +824,6 @@ def run_pipeline():
if not qpdf.check(options.output_file, _log):
_log.warning('Output file: The generated PDF is INVALID')
return ExitCode.invalid_output_pdf
else:
_log.info("Output sent to stdout")
pdfinfo = context.get_pdfinfo()
if options.verbose:
+1 -1
View File
@@ -97,7 +97,7 @@ def verify_python3_env():
break
if bad_locale is not None:
extra += (
'\n\ocrmypdf discovered that you exported a UTF-8 locale\n'
'\nocrmypdf discovered that you exported a UTF-8 locale\n'
'but the locale system could not pick up from it because\n'
'it does not exist. The exported locale is "%s" but it\n'
'is not supported'
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
"""Wrappers to manage subprocess calls"""
@@ -46,4 +45,4 @@ def get_version(program, *,
"Message was:\n{}").format(program, output)
)
return version
return version
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from tempfile import NamedTemporaryFile
-3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from subprocess import CalledProcessError, STDOUT, PIPE, run
@@ -68,8 +67,6 @@ def repair(input_file, output_file, log):
return
if _probably_encrypted(e):
log.error("{0}: this PDF is password-protected - password must "
"be removed for OCR".format(input_file))
raise EncryptedPdfError() from e
elif e.returncode == 2:
log.error("{0}: not a valid PDF, and could not repair it.".format(
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import sys
@@ -158,7 +157,7 @@ def tesseract_log_output(log, stdout, input_file):
try:
text = stdout.decode()
except UnicodeDecodeError as e:
except UnicodeDecodeError:
log.error(prefix + "command line output was not utf-8. " +
"This usually means Tesseract's language packs do not match "
"the installed version of Tesseract.")
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
# unpaper documentation:
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
+21 -7
View File
@@ -1,9 +1,9 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
from functools import partial
from collections.abc import Iterable
from contextlib import suppress, contextmanager
from pathlib import Path
import sys
import os
@@ -63,19 +63,33 @@ def is_file_writable(test_file):
can replace it atomically. Before doing the OCR work, make sure
the location is writable.
"""
if os.path.exists(test_file):
p = Path(test_file)
if p.is_symlink():
# Python 3.5 does not accept parameters for Path.resolve() and behaves
# as if strict=True (throws an exception on failure). Python 3.6
# defaults to strict=False. This implements strict=False like behavior
# for Python 3.5.
if sys.version_info[0:2] <= (3, 5):
resolve = lambda: Path(os.path.realpath(str(p)))
else:
resolve = lambda: p.resolve(strict=False)
p = resolve()
# p.is_file() throws an exception in some cases
if p.exists() and p.is_file():
return os.access(
test_file, os.W_OK,
str(p), os.W_OK,
effective_ids=(os.access in os.supports_effective_ids))
else:
try:
fp = open(test_file, 'wb')
except OSError as e:
fp = p.open('wb')
except OSError:
return False
else:
fp.close()
with suppress(OSError):
os.unlink(test_file)
p.unlink()
return True
@@ -118,4 +132,4 @@ if sys.version_info[0:2] <= (3, 5):
else:
universal_open = open
fspath = os.fspath
fspath = os.fspath
+1 -32
View File
@@ -566,35 +566,4 @@ if __name__ == '__main__':
parser_deskew.set_defaults(func=deskew)
args = parser.parse_args()
if get_leptonica_version() != u'leptonica-1.69':
print("Unexpected leptonica version: %s" % getLeptonicaVersion())
args.func(args)
def test_skew_angle():
from PIL import Image, ImageDraw
from tempfile import NamedTemporaryFile
im = Image.new(mode='1', size=(1000, 1000), color=1)
draw = ImageDraw.Draw(im)
for n in range(20):
draw.line([(50, 25 + 50*n), (950, 25 + 50*n)], width=1)
del draw
test_angles = [0.1 * ang for ang in range(1, 10)] + \
[float(ang) for ang in range(1, 7)]
test_angles += [-ang for ang in test_angles]
test_angles = sorted(test_angles)
for rotate_angle in test_angles:
rotated_im = im.rotate(rotate_angle)
with NamedTemporaryFile(prefix='lept-skew', suffix='.png', delete=True) as tmpfile:
rotated_im.save(tmpfile)
pix = pixRead(tmpfile.name)
angle, confidence = pixFindSkew(pix)
print('{0} {1} {2}'.format(rotate_angle, angle, confidence), file=sys.stderr)
args.func(args)
+1
View File
@@ -1,3 +1,4 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from cffi import FFI
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
#
# Generate a PDFA_def.ps file for Ghostscript >= 9.14
-4
View File
@@ -662,10 +662,6 @@ class PageInfo:
else:
return '1.5'
@property
def images(self):
return self._pageinfo['images']
def __repr__(self):
return (
'<PageInfo '
+13 -9
View File
@@ -1,7 +1,7 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
from contextlib import suppress
from shutil import copyfileobj
import sys
import os
import shutil
@@ -17,7 +17,8 @@ from .pdfinfo import PdfInfo, Encoding, Colorspace
from .pdfa import generate_pdfa_ps
from .helpers import re_symlink, is_iterable_notstr, page_number
from .exec import ghostscript, tesseract, qpdf
from .exceptions import *
from .exceptions import PdfMergeFailedError, UnsupportedImageFormatError, \
DpiError, PriorOcrFoundError, InputFileError
from . import leptonica
from . import PROGRAM_NAME, VERSION
@@ -159,7 +160,7 @@ def _pdf_guess_version(input_file, search_window=1024):
with open(input_file, 'rb') as f:
signature = f.read(1024)
m = re.search(b'%PDF-(\d\.\d)', signature)
m = re.search(br'%PDF-(\d\.\d)', signature)
if m:
return m.group(1)
return ''
@@ -1000,13 +1001,16 @@ def copy_final(
context):
input_file = next((ii for ii in input_files if ii.endswith('.pdf')))
if output_file == '-':
from shutil import copyfileobj
with open(input_file, 'rb') as input_stream:
with open(input_file, 'rb') as input_stream:
if output_file == '-':
copyfileobj(input_stream, sys.stdout.buffer)
sys.stdout.flush()
else:
shutil.copy(input_file, output_file)
else:
# At this point we overwrite the output_file specified by the user
# use copyfileobj because then we use open() to create the file and
# get the appropriate umask, ownership, etc.
with open(output_file, 'wb') as output_stream:
copyfileobj(input_stream, output_stream)
def build_pipeline(options, work_folder, log, context):
@@ -1235,7 +1239,7 @@ def build_pipeline(options, work_folder, log, context):
task_merge_sidecars.active_if(options.sidecar)
# Finalize
task_copy_final = main_pipeline.merge(
main_pipeline.merge(
task_func=copy_final,
input=[task_merge_pages_ghostscript, task_merge_pages_qpdf],
output=options.output_file,
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import sys
+2 -2
View File
@@ -33,7 +33,7 @@ In some cases they were converted from one image format to another without other
- Creative Commons BY-SA 3.0
* - typewriter.png, 2400dpi.pdf
- `Wikimedia: Triumph typewrtier text Linzensoep`_
* Creative Commons BY-SA 2.5
- Creative Commons BY-SA 2.5
* - baiona.png
- `Wikimedia: Baionako udalerri mugakideak`_
- Creative Commons BY-SA 4.0
@@ -119,7 +119,7 @@ These test resources are assemblies or derivatives from other previously mention
- palette.pdf (congress.jpg, converted to a 256-color palette)
- rotated_skew.pdf (a /Rotate'd and skewed document from LinnSequencer.jpg)
- skew.pdf (from LinnSequencer.jpg, skew simulated by adjusting the transformation matrix)
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2)
- skew-encrypted.pdf (skew.pdf with encryption - access supported by PyPDF2, password is "password")
- baiona_gray.png (from baiona.png)
- poster.pdf (from LinnSequencer.jpg)
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# © 2016 James R. Barlow: github.com/jbarlow83
# © 2016-18 James R. Barlow: github.com/jbarlow83
import sys
import os
+1 -1
View File
@@ -40,7 +40,7 @@ def main():
sys.stdout.buffer.write(BAD_UTF8)
sys.exit(1)
elif sys.argv[-1] == 'stdout':
inputf = sys.argv[-2]
# input file is at sys.argv[-2] but we don't look at it
print("""Orientation: 0
Orientation in degrees: 0
Orientation confidence: 100.00
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from ocrmypdf import hocrtransform
+42 -1
View File
@@ -1,6 +1,7 @@
# © 2015-17 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE, check_output, check_call, DEVNULL
from pathlib import Path
import os
import shutil
import resource
@@ -16,6 +17,9 @@ from math import isclose
import PIL
# pytest.helpers is dynamic
# pylint: disable=no-member
# pylint: disable=w0612
check_ocrmypdf = pytest.helpers.check_ocrmypdf
run_ocrmypdf = pytest.helpers.run_ocrmypdf
@@ -480,7 +484,7 @@ def test_encrypted(resources, no_outpdf):
p, out, err = run_ocrmypdf(
resources / 'skew-encrypted.pdf', no_outpdf)
assert p.returncode == ExitCode.encrypted_pdf
assert out.find('password')
assert out.find('encrypted')
@pytest.mark.parametrize('renderer', [
@@ -881,6 +885,8 @@ def test_gs_raster_failure(spoof_no_tess_gs_raster_fail, resources, outpdf):
assert p.returncode == ExitCode.child_process_error
@pytest.mark.skipif('8.0.0' <= qpdf.version() <= '8.0.1',
reason="qpdf regression")
def test_no_contents(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(resources / 'no_contents.pdf', outpdf, '--force-ocr',
env=spoof_tesseract_noop)
@@ -1098,3 +1104,38 @@ def test_text_curves(spoof_tesseract_noop, resources, outpdf):
info = PdfInfo(outpdf)
assert len(info.pages[0].images) != 0, "force did not rasterize"
def test_dev_null(spoof_tesseract_noop, resources):
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
os.devnull,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == 0, "could not send output to /dev/null"
assert len(out) == 0, "wrote to stdout"
def test_output_is_dir(spoof_tesseract_noop, resources, outdir):
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
outdir,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == ExitCode.file_access_error
assert 'is not a writable file' in err
def test_output_is_symlink(spoof_tesseract_noop, resources, outdir):
sym = Path(outdir / 'this_is_a_symlink')
sym.symlink_to(outdir / 'out.pdf')
p, out, err = run_ocrmypdf(
resources / 'trivial.pdf',
sym,
'--force-ocr',
env=spoof_tesseract_noop
)
assert p.returncode == ExitCode.ok, err
assert (outdir / 'out.pdf').stat().st_size > 0, 'target file not created'
+1 -2
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from ocrmypdf import pdfinfo
@@ -122,4 +121,4 @@ def test_no_contents(resources):
def test_oversized_page(resources):
pdf = pdfinfo.PdfInfo(resources / 'poster.pdf')
image = pdf[0].images[0]
assert image.width * image.xres > 200, "this is supposed to be oversized"
assert image.width * image.xres > 200, "this is supposed to be oversized"
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import pytest
+2 -3
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
import pytest
@@ -10,7 +9,7 @@ import os
import PyPDF2 as pypdf
from contextlib import contextmanager
# pylint: disable=no-member
spoof = pytest.helpers.spoof
@@ -128,4 +127,4 @@ def test_content_preservation(ensure_tess4, resources, outpdf):
info = pdfinfo.PdfInfo(outpdf)
page = info[0]
assert len(page.images) > 1, "masks were rasterized"
assert len(page.images) > 1, "masks were rasterized"
-1
View File
@@ -1,4 +1,3 @@
#!/usr/bin/env python3
# © 2017 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE, check_output, check_call, DEVNULL