Compare commits

..
19 Commits
Author SHA1 Message Date
James R. Barlow 79c84eefa3 Fix main.txt 2019-04-23 02:21:31 -07:00
James R. Barlow 5398003160 Fix test.txt 2019-04-23 00:42:40 -07:00
James R. Barlow 58b2bed99d v8.2.4 notes 2019-04-23 00:07:12 -07:00
James R. Barlow 58c29ffb5c weave: use explicit pdf.close(), drastically reduce open file handles
With the new pikepdf 1.2.0 we no longer need to hold file handles
open because of the "copy to memory" functionality. We retain
the behavior of closing/reopening the output PDF every 100 pages as
a way to limit memory usage.
2019-04-18 15:12:48 -07:00
James R. Barlow f615b6f0e8 pdfinfo: be more specific about detecting XFA we can't render 2019-04-18 15:07:25 -07:00
James R. Barlow e0c8dadcce Explicitly close most pikepdf.Pdf when done with them 2019-04-18 15:02:12 -07:00
James R. Barlow 9a86f53109 Ignore pip-wheel-metadata folder
https://github.com/pypa/pip/issues/6213
2019-04-18 10:42:10 -07:00
James R. Barlow 91cb092aa0 Remove PyCharm debugger hack 2019-04-18 10:15:02 -07:00
James R. Barlow 922a107b7f Remove safety traversal of PDF table of contents
qpdf fixed the danging reference issue (qpdf #240) in 8.3.0, which is
required by pikepdf 1.1.0. We no
longer need the workaround.
2019-04-13 00:24:03 -07:00
James R. Barlow 6e49bb3588 v8.2.3 notes 2019-04-03 01:19:12 -07:00
James R. Barlow 427afc0616 Fix LeptonicaErrorTrap when a sys.stderr.fileno() is not available
The LeptonicaErrorTrap was problematic for Celery and other
libraries that mess with stderr.

Closes #359
2019-03-17 14:22:36 -07:00
James R. Barlow 9c7ee2bf23 Better help text for --verbose 2019-03-17 13:29:25 -07:00
James R. Barlow c5cfaa950b readme: tweaks 2019-03-16 14:09:19 -07:00
James R. Barlow 4e2a98ead4 leptonica: fix junkpixt harder 2019-03-16 14:08:58 -07:00
James R. Barlow 210f134b5b Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-03-08 15:38:31 -08:00
James R. Barlow 696c0721a0 docs: fix broken sphinx ref
[ci skip]
2019-03-08 15:38:26 -08:00
James R. Barlow aabab95418 docs: use images folder 2019-03-08 15:38:01 -08:00
James R. Barlow 7d614dd68b docs: explain Automator workflow 2019-03-08 15:37:42 -08:00
jumbliesandjbarlow83 f57dda7939 Update batch.rst (#362)
Added docker instructions for passing "find" filenames into container.  Obviates prior incorrect flag fix.
2019-03-08 12:46:50 -08:00
19 changed files with 164 additions and 99 deletions
+1
View File
@@ -13,6 +13,7 @@
build/ build/
dist/ dist/
wheelhouse/ wheelhouse/
pip-wheel-metadata/
# Automatically generated files # Automatically generated files
docs/_build/ docs/_build/
+9 -4
View File
@@ -38,9 +38,9 @@ Main features
- If requested deskews and/or cleans the image before performing OCR - If requested deskews and/or cleans the image before performing OCR
- Validates input and output files - Validates input and output files
- Distributes work across all available CPU cores - Distributes work across all available CPU cores
- Uses [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) engine - Uses [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) engine to recognize more than [100 languages](https://github.com/tesseract-ocr/tessdata)
- Supports more than [100 languages](https://github.com/tesseract-ocr/tessdata) recognized by Tesseract - Scales properly to handle files with thousands of pages
- Battle-tested on thousands of PDFs, a test suite and continuous integration - Battle-tested on millions of PDFs
For details: please consult the [documentation](https://ocrmypdf.readthedocs.io/en/latest/). For details: please consult the [documentation](https://ocrmypdf.readthedocs.io/en/latest/).
@@ -131,6 +131,11 @@ Press & Media
- [c't 1-2014, page 59](http://heise.de/-2279695): Detailed presentation of OCRmyPDF v1.0 in the leading German IT magazine c't - [c't 1-2014, page 59](http://heise.de/-2279695): Detailed presentation of OCRmyPDF v1.0 in the leading German IT magazine c't
- [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](http://heise.de/-2356670) - [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](http://heise.de/-2356670)
Business enquiries
------------------
OCRmyPDF would not be the software that it is today is without companies and users choosing to provide support for feature development and consulting enquiries. We are happy to discuss all enquiries, whether for extending the existing feature set, or integrating OCRmyPDF into a larger system.
License License
------- -------
@@ -138,7 +143,7 @@ The OCRmyPDF software is licensed under the GNU GPLv3. Certain files are covered
The license for each test file varies, and is noted in tests/resources/README.rst. The documentation is licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA 4.0). The license for each test file varies, and is noted in tests/resources/README.rst. The documentation is licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA 4.0).
OCRmyPDF versions prior to 6.0 were licensed under the MIT License. OCRmyPDF versions prior to 6.0 were distributed under the MIT License.
Disclaimer Disclaimer
---------- ----------
+17 -4
View File
@@ -27,7 +27,13 @@ This will walk through a directory tree and run OCR on all files in place, print
.. code-block:: bash .. code-block:: bash
find . --printf '%p' -name '*.pdf' -exec ocrmypdf '{}' '{}' \; find . -printf '%p' -name '*.pdf' -exec ocrmypdf '{}' '{}' \;
Alternatively, with a docker container (mounts a volume to the container where the PDFs are stored):
.. code-block:: bash
find . -printf '%p' -name '*.pdf' -exec docker run --rm -v <host dir>:<container dir> jbarlow83/ocrmypdf-alpine '<container dir>/{}' '<container dir>/{}' \;
This only runs one ``ocrmypdf`` process at a time. This variation uses ``find`` to create a directory list and ``parallel`` to parallelize runs of ``ocrmypdf``, again updating files in place. This only runs one ``ocrmypdf`` process at a time. This variation uses ``find`` to create a directory list and ``parallel`` to parallelize runs of ``ocrmypdf``, again updating files in place.
@@ -163,14 +169,11 @@ This is only possible for x86-based Synology products. Some Synology products us
shutil.move(full_path, full_path_archive) shutil.move(full_path, full_path_archive)
logging.info('Finished.\n') logging.info('Finished.\n')
Huge batch jobs Huge batch jobs
""""""""""""""" """""""""""""""
If you have thousands of files to work with, contact the author. Consulting work related to OCRmyPDF helps fund this open source project and all inquiries are appreciated. If you have thousands of files to work with, contact the author. Consulting work related to OCRmyPDF helps fund this open source project and all inquiries are appreciated.
Hot (watched) folders Hot (watched) folders
--------------------- ---------------------
@@ -210,3 +213,13 @@ Alternatives
"""""""""""" """"""""""""
* `Watchman <https://facebook.github.io/watchman/>`_ is a more powerful alternative to ``watchmedo``. * `Watchman <https://facebook.github.io/watchman/>`_ is a more powerful alternative to ``watchmedo``.
macOS Automator
---------------
You can use the Automator app with macOS, to create a Workflow or Quick Action. Use a *Run Shell Script* action in your workflow. In the context of Automator, the ``PATH`` may be set differently your Terminal's ``PATH``; you may need to explicitly set the PATH to include ``ocrmypdf``. The following example may serve as a starting point:
.. image:: images/macos-workflow.png
:alt: Example macOS Automator script
You may customize the command sent to ocrmypdf.
+1 -1
View File
@@ -104,7 +104,7 @@ Clients must keep their open connection while waiting for OCR to complete. This
Unlike the rest of OCRmyPDF, this web service is licensed under the Affero GPLv3 (AGPLv3) since Ghostscript, a dependency of OCRmyPDF, is also licensed in this way. Unlike the rest of OCRmyPDF, this web service is licensed under the Affero GPLv3 (AGPLv3) since Ghostscript, a dependency of OCRmyPDF, is also licensed in this way.
In addition to the above, please read our :ref:`general remarks on using OCRmyPDF as a service <ocr-service>`_. In addition to the above, please read our :ref:`general remarks on using OCRmyPDF as a service <ocr-service>`.
Legacy Ubuntu Docker images Legacy Ubuntu Docker images
--------------------------- ---------------------------

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+1 -1
View File
@@ -16,7 +16,7 @@ About PDFs
PDFs are page description files that attempts to preserve a layout exactly. They contain `vector graphics <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`_ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts and text, it is a good formats for exchanging scanned documents. PDFs are page description files that attempts to preserve a layout exactly. They contain `vector graphics <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`_ that can contain raster objects such as scanned images. Because PDFs can contain multiple pages (unlike many image formats) and can contain fonts and text, it is a good formats for exchanging scanned documents.
.. image:: bitmap_vs_svg.svg .. image:: images/bitmap_vs_svg.svg
A PDF page might contain multiple images, even if it only appears to have one image. Some scanners or scanning software will segment pages into monochromatic text and color regions for example, to improve the compression ratio and appearance of the page. A PDF page might contain multiple images, even if it only appears to have one image. Some scanners or scanning software will segment pages into monochromatic text and color regions for example, to improve the compression ratio and appearance of the page.
+20
View File
@@ -13,6 +13,26 @@ Note that it is licensed under GPLv3, so scripts that ``import ocrmypdf`` and ar
find: [^`]\#([0-9]{1,3})[^0-9] find: [^`]\#([0-9]{1,3})[^0-9]
replace: `#$1 <https://github.com/jbarlow83/OCRmyPDF/issues/$1>`_ replace: `#$1 <https://github.com/jbarlow83/OCRmyPDF/issues/$1>`_
v8.2.4
------
- Fixed a false positive while checking for a certain type of PDF that only Acrobat can read. We now more accurately detect Acrobat-only PDFs.
- OCRmyPDF holds fewer open file handles and is more prompt about releasing those it no longer needs.
- Minor optimization: we no longer traverse the table of contents to ensure all references in it are resolved, as changes to libqpdf have made this unnecessary.
- pikepdf 1.2.0 is now required
v8.2.3
------
- Fixed that ``--mask-barcodes`` would occasionally leave a unwanted temporary file named ``junkpixt`` in the current working folder.
- Fixed (hopefully) handling of Leptonica errors in an environment where a non-standard ``sys.stderr`` is present.
- Improved help text for ``--verbose``.
v8.2.2 v8.2.2
------ ------
+1 -1
View File
@@ -5,7 +5,7 @@ chardet == 3.0.4
cffi == 1.12.2 cffi == 1.12.2
img2pdf == 0.3.3 img2pdf == 0.3.3
pdfminer.six == 20181108 pdfminer.six == 20181108
pikepdf == 1.1.0 pikepdf == 1.2.0
Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin" Pillow >= 5.0.0, != 5.1.0 ; sys_platform == "darwin"
pycparser == 2.19 pycparser == 2.19
python-xmp-toolkit == 2.0.1 python-xmp-toolkit == 2.0.1
+2 -2
View File
@@ -1,6 +1,6 @@
pytest == 4.3.0 pytest >= 4.4.1, < 5
pytest-helpers-namespace >= 2019.1.8 pytest-helpers-namespace >= 2019.1.8
pytest-xdist pytest-xdist == 1.28.0
pytest-cov >= 2.6.1 pytest-cov >= 2.6.1
python-xmp-toolkit # requires apt-get install libexempi3 python-xmp-toolkit # requires apt-get install libexempi3
# or brew install exempi # or brew install exempi
+1 -1
View File
@@ -99,7 +99,7 @@ setup(
'cffi >= 1.9.1', # must be a setup and install requirement 'cffi >= 1.9.1', # must be a setup and install requirement
'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely 'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely
'pdfminer.six == 20181108 ; sys_platform != "darwin"', 'pdfminer.six == 20181108 ; sys_platform != "darwin"',
'pikepdf >= 1.1.0, < 2', 'pikepdf >= 1.2.0, < 2',
'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"', 'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"',
# Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3 # Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3
# block 5.1.0, broken wheels # block 5.1.0, broken wheels
+3 -5
View File
@@ -66,10 +66,6 @@ def complain(message):
print(*textwrap.wrap(message), file=sys.stderr) print(*textwrap.wrap(message), file=sys.stderr)
# Hack to help debugger context find /usr/local/bin
if 'IDE_PROJECT_ROOTS' in os.environ:
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
# -------- # --------
# Critical environment tests # Critical environment tests
@@ -231,7 +227,9 @@ jobcontrol.add_argument(
default=[], default=[],
nargs='?', nargs='?',
action="append", action="append",
help="Print more verbose messages for each additional verbose level", help="Print more verbose messages for each additional verbose level. Use "
"`-v 1` typically for much more detailed logging. Higher numbers "
"are probably only useful in debugging.",
) )
metadata = parser.add_argument_group( metadata = parser.add_argument_group(
+11 -10
View File
@@ -816,16 +816,15 @@ def convert_to_pdfa(input_files_groups, output_file, log, context):
# NULs in DocumentInfo seem to be common since older Acrobats included them. # NULs in DocumentInfo seem to be common since older Acrobats included them.
# pikepdf can deal with this, but we make the world a better place by # pikepdf can deal with this, but we make the world a better place by
# stamping them out as soon as possible. # stamping them out as soon as possible.
pdf_layers_file = pikepdf.open(layers_file) with pikepdf.open(layers_file) as pdf_layers_file:
if pdf_layers_file.docinfo: if pdf_layers_file.docinfo:
modified = False modified = False
for k, v in pdf_layers_file.docinfo.items(): for k, v in pdf_layers_file.docinfo.items():
if b'\x00' in bytes(v): if b'\x00' in bytes(v):
pdf_layers_file.docinfo[k] = bytes(v).replace(b'\x00', b'') pdf_layers_file.docinfo[k] = bytes(v).replace(b'\x00', b'')
modified = True modified = True
if modified: if modified:
pdf_layers_file.save(layers_file) pdf_layers_file.save(layers_file)
del pdf_layers_file
ps = next((ii for ii in input_files if ii.endswith('.ps')), None) ps = next((ii for ii in input_files if ii.endswith('.ps')), None)
ghostscript.generate_pdfa( ghostscript.generate_pdfa(
@@ -880,6 +879,8 @@ def metadata_fixup(input_files_groups, output_file, log, context):
compress_streams=True, compress_streams=True,
object_stream_mode=pikepdf.ObjectStreamMode.generate, object_stream_mode=pikepdf.ObjectStreamMode.generate,
) )
original.close()
pdf.close()
def optimize_pdf(input_file, output_file, log, context): def optimize_pdf(input_file, output_file, log, context):
+31 -32
View File
@@ -25,7 +25,7 @@ from .exec import tesseract
from .helpers import flatten_groups, page_number from .helpers import flatten_groups, page_number
MAX_OPEN_PAGE_PDFS = int(os.environ.get('_OCRMYPDF_MAX_OPEN_PAGE_PDFS', 100)) MAX_REPLACE_PAGES = int(os.environ.get('_OCRMYPDF_MAX_REPLACE_PAGES', 100))
def _update_page_resources(*, page, font, font_key, procset): def _update_page_resources(*, page, font, font_key, procset):
@@ -161,6 +161,7 @@ def _weave_layers_graft(
_update_page_resources( _update_page_resources(
page=base_page, font=font, font_key=font_key, procset=procset page=base_page, font=font, font_key=font_key, procset=procset
) )
pdf_text.close()
def _find_font(text, pdf_base): def _find_font(text, pdf_base):
@@ -169,20 +170,23 @@ def _find_font(text, pdf_base):
font, font_key = None, None font, font_key = None, None
possible_font_names = ('/f-0-0', '/F1') possible_font_names = ('/f-0-0', '/F1')
try: try:
pdf_text = pikepdf.open(text) with pikepdf.open(text) as pdf_text:
pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {}) try:
except Exception: pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {})
except (AttributeError, IndexError, KeyError):
return None, None
for f in possible_font_names:
pdf_text_font = pdf_text_fonts.get(f, None)
if pdf_text_font is not None:
font_key = f
break
if pdf_text_font:
font = pdf_base.copy_foreign(pdf_text_font)
return font, font_key
except (FileNotFoundError, pikepdf.PdfError):
# PdfError occurs if a 0-length file is written e.g. due to OCR timeout
return None, None return None, None
for f in possible_font_names:
pdf_text_font = pdf_text_fonts.get(f, None)
if pdf_text_font is not None:
font_key = f
break
if pdf_text_font:
font = pdf_base.copy_foreign(pdf_text_font)
return font, font_key
def _traverse_toc(pdf_base, visitor_fn, log): def _traverse_toc(pdf_base, visitor_fn, log):
""" """
@@ -317,21 +321,16 @@ def weave_layers(infiles, output_file, log, context):
base = list(basegroup)[0] base = list(basegroup)[0]
path_base = Path(base).resolve() path_base = Path(base).resolve()
pdf_base = pikepdf.open(path_base) pdf_base = pikepdf.open(path_base)
keep_open = []
font, font_key, procset = None, None, None font, font_key, procset = None, None, None
pdfinfo = context.get_pdfinfo() pdfinfo = context.get_pdfinfo()
pagerefs = {} pagerefs = {}
# Walk the table of contents first, to trigger pikepdf/qpdf to resolve all
# page references in the table of contents. Some PDF generators put invalid
# references in the ToC, so we want to resolve them to null before we
# create any references, or the ToC will be corrupted
_traverse_toc(pdf_base, None, log)
procset = pdf_base.make_indirect( procset = pdf_base.make_indirect(
pikepdf.Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]') pikepdf.Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]')
) )
replacements = 0
# Iterate rest # Iterate rest
for page_num, layers in groups: for page_num, layers in groups:
layers = list(layers) layers = list(layers)
@@ -354,10 +353,10 @@ def weave_layers(infiles, output_file, log, context):
log.debug("Replace") log.debug("Replace")
old_objgen = pdf_base.pages[page_num - 1].objgen old_objgen = pdf_base.pages[page_num - 1].objgen
pdf_image = pikepdf.open(image) with pikepdf.open(image) as pdf_image:
keep_open.append(pdf_image) replacements += 1
image_page = pdf_image.pages[0] image_page = pdf_image.pages[0]
pdf_base.pages[page_num - 1] = image_page pdf_base.pages[page_num - 1] = image_page
# We're adding a new page, which will get a new objgen number pair, # We're adding a new page, which will get a new objgen number pair,
# so we need to update any references to it. qpdf did not like # so we need to update any references to it. qpdf did not like
@@ -396,20 +395,19 @@ def weave_layers(infiles, output_file, log, context):
content_rotation - autorotate_correction content_rotation - autorotate_correction
) % 360 ) % 360
if len(keep_open) > MAX_OPEN_PAGE_PDFS: if replacements % MAX_REPLACE_PAGES == 0:
# qpdf limitations require us to keep files open when we intend # Periodically save and reload the Pdf object. This will keep a
# to copy content from them before saving. However, we want to keep # lid on our memory usage for very large files. Attach the font to
# a lid on file handles and memory usage, so for big files we're # page 1 even if page 1 doesn't use it, so we have a way to get it
# going to stop and save periodically. Attach the font to page 1 # back.
# even if page 1 doesn't use it, so we have a way to get it back. # TODO refactor this to outside the loop
page0 = pdf_base.pages[0] page0 = pdf_base.pages[0]
_update_page_resources( _update_page_resources(
page=page0, font=font, font_key=font_key, procset=procset page=page0, font=font, font_key=font_key, procset=procset
) )
interim = output_file + f'_working{page_num}.pdf' interim = output_file + f'_working{page_num}.pdf'
pdf_base.save(interim) pdf_base.save(interim)
del pdf_base pdf_base.close()
keep_open = []
pdf_base = pikepdf.open(interim) pdf_base = pikepdf.open(interim)
procset = pdf_base.pages[0].Resources.ProcSet procset = pdf_base.pages[0].Resources.ProcSet
@@ -417,3 +415,4 @@ def weave_layers(infiles, output_file, log, context):
_fix_toc(pdf_base, pagerefs, log) _fix_toc(pdf_base, pagerefs, log)
pdf_base.save(output_file) pdf_base.save(output_file)
pdf_base.close()
+24 -20
View File
@@ -43,11 +43,6 @@ lept = ffi.dlopen(find_library('lept'))
lept.setMsgSeverity(lept.L_SEVERITY_WARNING) lept.setMsgSeverity(lept.L_SEVERITY_WARNING)
def stderr(*objs):
"""Shorthand print to stderr."""
print("leptonica.py:", *objs, file=sys.stderr)
class _LeptonicaErrorTrap: class _LeptonicaErrorTrap:
""" """
Context manager to trap errors reported by Leptonica. Context manager to trap errors reported by Leptonica.
@@ -66,6 +61,7 @@ class _LeptonicaErrorTrap:
def __init__(self): def __init__(self):
self.tmpfile = None self.tmpfile = None
self.copy_of_stderr = -1 self.copy_of_stderr = -1
self.no_stderr = False
def __enter__(self): def __enter__(self):
from io import UnsupportedOperation from io import UnsupportedOperation
@@ -73,33 +69,40 @@ class _LeptonicaErrorTrap:
self.tmpfile = TemporaryFile() self.tmpfile = TemporaryFile()
# Save the old stderr, and redirect stderr to temporary file # Save the old stderr, and redirect stderr to temporary file
sys.stderr.flush() with suppress(AttributeError):
sys.stderr.flush()
try: try:
self.copy_of_stderr = os.dup(sys.stderr.fileno()) self.copy_of_stderr = os.dup(sys.stderr.fileno())
os.dup2(self.tmpfile.fileno(), sys.stderr.fileno(), inheritable=False) os.dup2(self.tmpfile.fileno(), sys.stderr.fileno(), inheritable=False)
except AttributeError:
# We are in some unusual context where our Python process does not
# have a sys.stderr. Leptonica still expects to write to file
# descriptor 2, so we are going to ensure it is redirected.
self.copy_of_stderr = None
self.no_stderr = True
os.dup2(self.tmpfile.fileno(), 2, inheritable=False)
except UnsupportedOperation: except UnsupportedOperation:
self.copy_of_stderr = None self.copy_of_stderr = None
return return
def __exit__(self, exc_type, exc_value, traceback): def __exit__(self, exc_type, exc_value, traceback):
# Restore old stderr # Restore old stderr
sys.stderr.flush() with suppress(AttributeError):
sys.stderr.flush()
if self.copy_of_stderr is not None: if self.copy_of_stderr is not None:
os.dup2(self.copy_of_stderr, sys.stderr.fileno()) os.dup2(self.copy_of_stderr, sys.stderr.fileno())
os.close(self.copy_of_stderr) os.close(self.copy_of_stderr)
if self.no_stderr:
os.close(2)
# Get data from tmpfile (in with block to ensure it is closed) # Get data from tmpfile
with self.tmpfile as tmpfile: self.tmpfile.seek(0) # Cursor will be at end, so move back to beginning
tmpfile.seek(0) # Cursor will be at end, so move back to beginning leptonica_output = self.tmpfile.read().decode(errors='replace')
leptonica_output = tmpfile.read().decode(errors='replace') self.tmpfile.close()
# If there are Python errors, record them
assert self.tmpfile.closed
assert not sys.stderr.closed
# If there are Python errors, let them bubble up
if exc_type: if exc_type:
logger.warning(leptonica_output) logger.warning(leptonica_output)
return False
# If there are Leptonica errors, wrap them in Python excpetions # If there are Leptonica errors, wrap them in Python excpetions
if 'Error' in leptonica_output: if 'Error' in leptonica_output:
@@ -614,9 +617,10 @@ class Pix(LeptonicaObject):
except (LeptonicaError, ValueError, IndexError): except (LeptonicaError, ValueError, IndexError):
return return
finally: finally:
with suppress(FileNotFoundError): leptonica_junk = ('junkpixt.png', 'junkpixt')
os.unlink('junkpixt.png') # leptonica may produce this for junk in leptonica_junk:
os.unlink('junkpixt') with suppress(FileNotFoundError):
os.unlink(junk) # leptonica may produce this
for n, s in enumerate(sarray): for n, s in enumerate(sarray):
decoded = s.decode() decoded = s.decode()
+15 -15
View File
@@ -131,19 +131,19 @@ def file_claims_pdfa(filename):
do full PDF/A validation. do full PDF/A validation.
""" """
pdf = pikepdf.open(filename) with pikepdf.open(filename) as pdf:
pdfmeta = pdf.open_metadata() pdfmeta = pdf.open_metadata()
if not pdfmeta.pdfa_status: if not pdfmeta.pdfa_status:
return { return {
'pass': False, 'pass': False,
'output': 'pdf', 'output': 'pdf',
'conformance': 'No PDF/A metadata in XMP', 'conformance': 'No PDF/A metadata in XMP',
} }
valid_part_conforms = {'1A', '1B', '2A', '2B', '2U', '3A', '3B', '3U'} valid_part_conforms = {'1A', '1B', '2A', '2B', '2U', '3A', '3B', '3U'}
conformance = f'PDF/A-{pdfmeta.pdfa_status}' conformance = f'PDF/A-{pdfmeta.pdfa_status}'
pdfa_dict = {} pdfa_dict = {}
if pdfmeta.pdfa_status in valid_part_conforms: if pdfmeta.pdfa_status in valid_part_conforms:
pdfa_dict['pass'] = True pdfa_dict['pass'] = True
pdfa_dict['output'] = 'pdfa' pdfa_dict['output'] = 'pdfa'
pdfa_dict['conformance'] = conformance pdfa_dict['conformance'] = conformance
return pdfa_dict return pdfa_dict
+9 -2
View File
@@ -618,8 +618,9 @@ def _pdf_get_all_pageinfo(infile, detailed_analysis=False, log=None):
if not log: if not log:
log = Mock() log = Mock()
pdf = pikepdf.open(infile) pdf = pikepdf.open(infile) # Do not close in this function
if pdf.is_encrypted: if pdf.is_encrypted:
pdf.close()
raise EncryptedPdfError() # Triggered by encryption with empty passwd raise EncryptedPdfError() # Triggered by encryption with empty passwd
if detailed_analysis: if detailed_analysis:
pages_xml = None pages_xml = None
@@ -755,7 +756,13 @@ class PdfInfo:
infile, detailed_page_analysis, log=log infile, detailed_page_analysis, log=log
) )
self._needs_rendering = pdf.root.get('/NeedsRendering', False) self._needs_rendering = pdf.root.get('/NeedsRendering', False)
self._has_acroform = '/AcroForm' in pdf.root self._has_acroform = False
if '/AcroForm' in pdf.root:
if len(pdf.root.AcroForm.get('/Fields', [])) > 0:
self._has_acroform = True
elif '/XFA' in pdf.root.AcroForm:
self._has_acroform = True
pdf.close()
@property @property
def pages(self): def pages(self):
+17
View File
@@ -17,7 +17,9 @@
from os import fspath from os import fspath
import os
from pickle import dumps, loads from pickle import dumps, loads
from unittest.mock import patch
import pytest import pytest
from PIL import Image, ImageChops from PIL import Image, ImageChops
@@ -85,3 +87,18 @@ def test_leptonica_compile(tmpdir):
# existing compiled library. Also compile in API mode so that we test # existing compiled library. Also compile in API mode so that we test
# the interfaces, even though we use it ABI mode. # the interfaces, even though we use it ABI mode.
ffibuilder.compile(tmpdir=fspath(tmpdir), target=fspath(tmpdir / 'lepttest.*')) ffibuilder.compile(tmpdir=fspath(tmpdir), target=fspath(tmpdir / 'lepttest.*'))
def test_with_stderr(capsys):
# pytest redirects stderr too; we must disable this for the test to be valid
with capsys.disabled():
with pytest.raises(FileNotFoundError):
lept.Pix.open("does_not_exist1")
def test_without_stderr(capsys):
# pytest redirects stderr too; we must disable this for the test to be valid
with capsys.disabled():
with patch('sys.stderr', new=None):
with pytest.raises(FileNotFoundError):
lept.Pix.open("does_not_exist2")
+1 -1
View File
@@ -53,7 +53,7 @@ def test_no_glyphless_weave(resources, outdir):
pdf.save(outdir / 'test.pdf') pdf.save(outdir / 'test.pdf')
env = os.environ.copy() env = os.environ.copy()
env['_OCRMYPDF_MAX_OPEN_PAGE_PDFS'] = '2' env['_OCRMYPDF_MAX_REPLACE_PAGES'] = '2'
check_ocrmypdf( check_ocrmypdf(
outdir / 'test.pdf', outdir / 'test.pdf',
outdir / 'out.pdf', outdir / 'out.pdf',