Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21cacad93b | ||
|
|
3589f4e7d1 | ||
|
|
1cdc2591e5 | ||
|
|
e05f9575a8 | ||
|
|
10c703e119 | ||
|
|
0ac15dd0b2 | ||
|
|
808b24d59f | ||
|
|
c082526dea | ||
|
|
33cdabaf65 | ||
|
|
94f8e36601 | ||
|
|
865002c7be | ||
|
|
5d0cc0a092 | ||
|
|
6c427f82ea | ||
|
|
e7a44ba87a | ||
|
|
c311768452 | ||
|
|
f53fedee63 | ||
|
|
87838127b0 | ||
|
|
4db4df5c72 | ||
|
|
11125c5367 | ||
|
|
e648411067 | ||
|
|
11365575d7 | ||
|
|
845cb5c40c | ||
|
|
b699e158be | ||
|
|
603da52026 | ||
|
|
8d0765a5e0 | ||
|
|
1ca327e13b | ||
|
|
f504fd1875 | ||
|
|
cf7c20ca16 | ||
|
|
b00fe3dc5d | ||
|
|
e6aa3a4299 | ||
|
|
24f1b57288 | ||
|
|
43302d7e12 | ||
|
|
fed0226761 | ||
|
|
27e22b4f07 | ||
|
|
79382a6039 | ||
|
|
7788d94c4a | ||
|
|
33bfba8449 | ||
|
|
1d0584c644 | ||
|
|
84b9d4d021 | ||
|
|
41efd3bf0f | ||
|
|
776ada6713 | ||
|
|
f3593c915d | ||
|
|
dfe31a2f6d | ||
|
|
0c43963d69 | ||
|
|
f29fe7f23e | ||
|
|
04996caac3 | ||
|
|
13917c051c | ||
|
|
8182fe9c92 | ||
|
|
1950acfbda | ||
|
|
fca6403083 | ||
|
|
c4e2fce1ef | ||
|
|
3546479658 | ||
|
|
72442fa3d0 | ||
|
|
8f714b1375 | ||
|
|
cb05c1d122 | ||
|
|
b0ad07bc5f | ||
|
|
514038d4ec | ||
|
|
50d76e7f6c | ||
|
|
6c78a46285 | ||
|
|
863d560632 | ||
|
|
73934c854c | ||
|
|
2be8eeec2c | ||
|
|
3dfde479e2 | ||
|
|
aea1862644 | ||
|
|
3b406112d0 | ||
|
|
fcc4c2d371 | ||
|
|
3de18ed612 | ||
|
|
93cca42e20 | ||
|
|
2d0ac4707c | ||
|
|
7d208175cf | ||
|
|
ea69e868ed | ||
|
|
beea603ab3 | ||
|
|
7966192d6e | ||
|
|
5acbd7a252 | ||
|
|
aed955ca8c | ||
|
|
298bdb8690 | ||
|
|
1a58abcc6a | ||
|
|
dbfceba020 | ||
|
|
0faa618c3c | ||
|
|
7035002c03 | ||
|
|
f8fadaef41 | ||
|
|
ee21bf9ef6 | ||
|
|
190ca81951 | ||
|
|
d48254d477 | ||
|
|
1ec2ccca14 | ||
|
|
e78f0cc56f | ||
|
|
13af3252ff | ||
|
|
0528867e0b | ||
|
|
6910c48b81 | ||
|
|
69aa3981c4 | ||
|
|
9c1e5adfe6 | ||
|
|
e642dd4b35 | ||
|
|
9de06f62ee | ||
|
|
1414a8f5dc | ||
|
|
26badf2882 | ||
|
|
8f873aaa45 | ||
|
|
8fdcb15b4e | ||
|
|
0323738ada | ||
|
|
aae5591f7e | ||
|
|
4c1ff1086c | ||
|
|
f91faf9795 | ||
|
|
793cc33a90 |
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
# OCRmyPDF
|
# OCRmyPDF
|
||||||
#
|
#
|
||||||
|
|
||||||
FROM ubuntu:21.04 as base
|
FROM ubuntu:22.04 as base
|
||||||
|
|
||||||
ENV LANG=C.UTF-8
|
ENV LANG=C.UTF-8
|
||||||
ENV TZ=UTC
|
ENV TZ=UTC
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ Run with verbosity or higher `-v1` to see more detailed logging. This informatio
|
|||||||
**Example file**
|
**Example file**
|
||||||
If your issue is a problem that affects only certain files, and we will require an input file (PDF or image) that demonstrates your issue.
|
If your issue is a problem that affects only certain files, and we will require an input file (PDF or image) that demonstrates your issue.
|
||||||
|
|
||||||
Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/jbarlow83/OCRmyPDF/wiki) for OCRmyPDF's author only.
|
Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/ocrmypdf/OCRmyPDF/wiki) for OCRmyPDF's author only.
|
||||||
|
|
||||||
Links to files hosted elsewhere are perfectly acceptable. You could also look in ``tests/resources`` and see if any of those files reproduce your issue.
|
Links to files hosted elsewhere are perfectly acceptable. You could also look in ``tests/resources`` and see if any of those files reproduce your issue.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ A clear and concise description of any alternative solutions or features you've
|
|||||||
**Example file**
|
**Example file**
|
||||||
If your issue concerns how OCRmyPDF processes certain files, and please provide an example file that helps illustrate how OCRmyPDF's output could be improve.
|
If your issue concerns how OCRmyPDF processes certain files, and please provide an example file that helps illustrate how OCRmyPDF's output could be improve.
|
||||||
|
|
||||||
Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/jbarlow83/OCRmyPDF/wiki) for OCRmyPDF's author only.
|
Please provide an input file with no personal or confidential information. At your option you may [GPG-encrypt the file](https://github.com/ocrmypdf/OCRmyPDF/wiki) for OCRmyPDF's author only.
|
||||||
|
|
||||||
Links to files hosted elsewhere are perfectly acceptable. You could also look in ``tests/resources`` and see if any of those files reproduce your issue.
|
Links to files hosted elsewhere are perfectly acceptable. You could also look in ``tests/resources`` and see if any of those files reproduce your issue.
|
||||||
|
|
||||||
|
|||||||
@@ -21,17 +21,19 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- os: ubuntu-18.04
|
- os: ubuntu-18.04
|
||||||
python: 3.7
|
python: "3.7"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
python: 3.8
|
python: "3.8"
|
||||||
- os: ubuntu-20.04
|
- os: ubuntu-20.04
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
|
- os: ubuntu-20.04
|
||||||
|
python: "3.10"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: "pypy-3.7"
|
python: "pypy-3.8"
|
||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
python: 3.9
|
python: "3.9"
|
||||||
tesseract5: true
|
tesseract5: true
|
||||||
|
|
||||||
env:
|
env:
|
||||||
@@ -120,7 +122,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [macos-latest]
|
os: [macos-latest]
|
||||||
python: ["3.9"]
|
python: ["3.9", "3.10"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
@@ -175,7 +177,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [windows-latest]
|
os: [windows-latest]
|
||||||
python: ["3.9"]
|
python: ["3.9", "3.10"]
|
||||||
|
|
||||||
env:
|
env:
|
||||||
OS: ${{ matrix.os }}
|
OS: ${{ matrix.os }}
|
||||||
@@ -194,7 +196,7 @@ jobs:
|
|||||||
- name: Install system packages
|
- name: Install system packages
|
||||||
run: |
|
run: |
|
||||||
choco install --yes --no-progress --pre tesseract
|
choco install --yes --no-progress --pre tesseract
|
||||||
choco install --yes --no-progress --ignore-checksums ghostscript pngquant
|
choco install --yes --no-progress --ignore-checksums ghostscript
|
||||||
|
|
||||||
- name: Install Python packages
|
- name: Install Python packages
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.0.1
|
rev: v4.2.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: check-case-conflict
|
- id: check-case-conflict
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
@@ -8,26 +8,26 @@ repos:
|
|||||||
- id: check-yaml
|
- id: check-yaml
|
||||||
- id: debug-statements
|
- id: debug-statements
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
rev: 5.9.3
|
rev: 5.10.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: isort
|
- id: isort
|
||||||
args: ["--profile", "black"]
|
args: ["--profile", "black"]
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 21.9b0
|
rev: 22.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python
|
language_version: python
|
||||||
- repo: https://github.com/asottile/setup-cfg-fmt
|
- repo: https://github.com/asottile/setup-cfg-fmt
|
||||||
rev: v1.19.0
|
rev: v1.20.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: setup-cfg-fmt
|
- id: setup-cfg-fmt
|
||||||
- repo: https://github.com/asottile/pyupgrade
|
- repo: https://github.com/asottile/pyupgrade
|
||||||
rev: v2.29.0
|
rev: v2.32.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: pyupgrade
|
- id: pyupgrade
|
||||||
args: ["--py37-plus"]
|
args: ["--py37-plus"]
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: v0.910-1
|
rev: v0.950
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ formats:
|
|||||||
|
|
||||||
# Optionally set the version of Python and requirements required to build your docs
|
# Optionally set the version of Python and requirements required to build your docs
|
||||||
python:
|
python:
|
||||||
version: 3.7
|
version: "3.7"
|
||||||
install:
|
install:
|
||||||
- method: pip
|
- method: pip
|
||||||
path: .
|
path: .
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<img src="docs/images/logo.svg" width="240" alt="OCRmyPDF">
|
<img src="docs/images/logo.svg" width="240" alt="OCRmyPDF">
|
||||||
|
|
||||||
[](https://github.com/jbarlow83/OCRmyPDF/actions/workflows/build.yml) [![PyPI version][pypi]](https://pypi.org/project/ocrmypdf/) ![Homebrew version][homebrew] ![ReadTheDocs][docs] ![Python versions][pyversions]
|
[](https://github.com/ocrmypdf/OCRmyPDF/actions/workflows/build.yml) [![PyPI version][pypi]](https://pypi.org/project/ocrmypdf/) ![Homebrew version][homebrew] ![ReadTheDocs][docs] ![Python versions][pyversions]
|
||||||
|
|
||||||
[azure]: https://dev.azure.com/jim0585/ocrmypdf/_apis/build/status/jbarlow83.OCRmyPDF?branchName=master
|
|
||||||
[travis]: https://travis-ci.org/jbarlow83/OCRmyPDF.svg?branch=master "Travis build status"
|
|
||||||
[pypi]: https://img.shields.io/pypi/v/ocrmypdf.svg "PyPI version"
|
[pypi]: https://img.shields.io/pypi/v/ocrmypdf.svg "PyPI version"
|
||||||
[homebrew]: https://img.shields.io/homebrew/v/ocrmypdf.svg "Homebrew version"
|
[homebrew]: https://img.shields.io/homebrew/v/ocrmypdf.svg "Homebrew version"
|
||||||
[docs]: https://readthedocs.org/projects/ocrmypdf/badge/?version=latest "RTD"
|
[docs]: https://readthedocs.org/projects/ocrmypdf/badge/?version=latest "RTD"
|
||||||
@@ -106,7 +104,7 @@ ocrmypdf --help
|
|||||||
|
|
||||||
Our [documentation is served on Read the Docs](https://ocrmypdf.readthedocs.io/en/latest/index.html).
|
Our [documentation is served on Read the Docs](https://ocrmypdf.readthedocs.io/en/latest/index.html).
|
||||||
|
|
||||||
Please report issues on our [GitHub issues](https://github.com/jbarlow83/OCRmyPDF/issues) page, and follow the issue template for quick response.
|
Please report issues on our [GitHub issues](https://github.com/ocrmypdf/OCRmyPDF/issues) page, and follow the issue template for quick response.
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
Upstream-Name: OCRmyPDF
|
Upstream-Name: OCRmyPDF
|
||||||
Upstream-Contact: James R. Barlow <barlow.jim@gmail.com>
|
Upstream-Contact: James R. Barlow <barlow.jim@gmail.com>
|
||||||
Source: https://github.com/jbarlow83/OCRmyPDF
|
Source: https://github.com/ocrmypdf/OCRmyPDF
|
||||||
|
|
||||||
Files: *
|
Files: *
|
||||||
Copyright:
|
Copyright:
|
||||||
|
|||||||
+5
-11
@@ -67,11 +67,11 @@ without modifying the PDF. This is to ensure that PDFs that were
|
|||||||
previously OCRed or were "born digital" rather than scanned are not
|
previously OCRed or were "born digital" rather than scanned are not
|
||||||
processed.
|
processed.
|
||||||
|
|
||||||
If ``--skip-text`` is issued, then no OCR will be performed on pages
|
If ``--skip-text`` is issued, then no image processing or OCR will be
|
||||||
that already have text. The page will be copied to the output. This may
|
performed on pages that already have text. The page will be copied to
|
||||||
be useful for documents that contain both "born digital" and scanned
|
the output. This may be useful for documents that contain both "born
|
||||||
content, or to use OCRmyPDF to normalize and convert to PDF/A regardless
|
digital" and scanned content, or to use OCRmyPDF to normalize and
|
||||||
of their contents.
|
convert to PDF/A regardless of their contents.
|
||||||
|
|
||||||
If ``--redo-ocr`` is issued, then a detailed text analysis is performed.
|
If ``--redo-ocr`` is issued, then a detailed text analysis is performed.
|
||||||
Text is categorized as either visible or invisible. Invisible text (OCR)
|
Text is categorized as either visible or invisible. Invisible text (OCR)
|
||||||
@@ -236,12 +236,6 @@ PDF.js viewer.
|
|||||||
|
|
||||||
This works in all versions of Tesseract.
|
This works in all versions of Tesseract.
|
||||||
|
|
||||||
The ``tesseract`` renderer
|
|
||||||
--------------------------
|
|
||||||
|
|
||||||
The ``tesseract`` renderer was removed. OCRmyPDF's new approach to text
|
|
||||||
layer grafting makes it functionally equivalent to ``sandwich``.
|
|
||||||
|
|
||||||
Return code policy
|
Return code policy
|
||||||
==================
|
==================
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,14 @@ OCRmyPDF, use processes.
|
|||||||
not take at least one of these steps, process semantics will prevent
|
not take at least one of these steps, process semantics will prevent
|
||||||
OCRmyPDF from working correctly.
|
OCRmyPDF from working correctly.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
On macOS with Python 3.7, you must call
|
||||||
|
:func:`multiprocessing.set_start_method("spawn")`. Without this, multiprocessing
|
||||||
|
will be unstable. From the command line, OCRmyPDF does this automatically,
|
||||||
|
but as an API user you must do this. See Python bpo-33725 for details.
|
||||||
|
Python 3.8+ also resolve this automatically.
|
||||||
|
|
||||||
Logging
|
Logging
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ extensions = [
|
|||||||
# Extension settings
|
# Extension settings
|
||||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||||
napoleon_use_rtype = False
|
napoleon_use_rtype = False
|
||||||
issues_github_path = "jbarlow83/OCRmyPDF"
|
issues_github_path = "ocrmypdf/OCRmyPDF"
|
||||||
|
|
||||||
# Add any paths that contain templates here, relative to this directory.
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
templates_path = ['_templates']
|
templates_path = ['_templates']
|
||||||
|
|||||||
+11
-7
@@ -104,6 +104,9 @@ This produces a file named "output.pdf" and a companion text file named
|
|||||||
because of options like ``--skip-big`` or ``--tesseract-timeout``, those pages
|
because of options like ``--skip-big`` or ``--tesseract-timeout``, those pages
|
||||||
will not be in the sidecar.
|
will not be in the sidecar.
|
||||||
|
|
||||||
|
If you don't want to generate the output PDF, use ``--output-type=none`` to
|
||||||
|
avoid generating one. Set the output filename to ``-`` (i.e. redirect to stdout).
|
||||||
|
|
||||||
To extract all text from a PDF, whether generated from OCR or otherwise,
|
To extract all text from a PDF, whether generated from OCR or otherwise,
|
||||||
use a program like Poppler's ``pdftotext`` or ``pdfgrep``.
|
use a program like Poppler's ``pdftotext`` or ``pdfgrep``.
|
||||||
|
|
||||||
@@ -240,10 +243,11 @@ You can also optimize all images without performing any OCR:
|
|||||||
|
|
||||||
ocrmypdf --tesseract-timeout=0 --optimize 3 --skip-text input.pdf output.pdf
|
ocrmypdf --tesseract-timeout=0 --optimize 3 --skip-text input.pdf output.pdf
|
||||||
|
|
||||||
Perform OCR only certain pages
|
Process only certain pages
|
||||||
------------------------------
|
--------------------------
|
||||||
|
|
||||||
You can ask OCRmyPDF to only apply OCR to certain pages.
|
You can ask OCRmyPDF to only apply `image processing <#image-processing>`__
|
||||||
|
and OCR to certain pages.
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
@@ -257,10 +261,10 @@ overlap pages. OCRmyPDF does not currently account for document page numbers,
|
|||||||
such as an introduction section of a book that uses Roman numerals. It simply
|
such as an introduction section of a book that uses Roman numerals. It simply
|
||||||
counts the number of virtual pieces of paper since the start.
|
counts the number of virtual pieces of paper since the start.
|
||||||
|
|
||||||
Regardless of the argument to ``--pages``, OCRmyPDF will optimize all pages in
|
Regardless of the argument to ``--pages``, OCRmyPDF will optimize all pages/images
|
||||||
the file and convert it to PDF/A, unless you disable those options. In this
|
in the file and convert it to PDF/A, unless you disable those options. Both of these
|
||||||
example, we want to OCR only the title and otherwise change the PDF as little
|
steps are "whole file" operations. In this example, we want to OCR only the title
|
||||||
as possible:
|
and otherwise change the PDF as little as possible:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ Using the Docker image on the command line
|
|||||||
==========================================
|
==========================================
|
||||||
|
|
||||||
**Unlike typical Docker containers**, in this section the OCRmyPDF Docker
|
**Unlike typical Docker containers**, in this section the OCRmyPDF Docker
|
||||||
container is emphemeral – it runs for one OCR job and terminates, just like a
|
container is ephemeral – it runs for one OCR job and terminates, just like a
|
||||||
command line program. We are using Docker to deliver an application (as opposed
|
command line program. We are using Docker to deliver an application (as opposed
|
||||||
to the more conventional case, where a Docker container runs as a server).
|
to the more conventional case, where a Docker container runs as a server).
|
||||||
|
|
||||||
|
|||||||
+5
-68
@@ -211,69 +211,6 @@ user's ``PATH`` to check for the user's Python packages.
|
|||||||
|
|
||||||
To add JBIG2 encoding, see :ref:`jbig2`.
|
To add JBIG2 encoding, see :ref:`jbig2`.
|
||||||
|
|
||||||
Ubuntu 16.04 LTS
|
|
||||||
----------------
|
|
||||||
|
|
||||||
No package is available for Ubuntu 16.04. OCRmyPDF 8.0 and newer require
|
|
||||||
Python 3.7. Ubuntu 16.04 ships Python 3.5, but you can install Python
|
|
||||||
3.7 on it. Or, you can skip Python 3.7 and install OCRmyPDF 7.x or older
|
|
||||||
- for that procedure, please see the installation documentation for the
|
|
||||||
version of OCRmyPDF you plan to use.
|
|
||||||
|
|
||||||
**Install system packages for OCRmyPDF**
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y software-properties-common python-software-properties
|
|
||||||
sudo add-apt-repository -y \
|
|
||||||
ppa:jonathonf/python-3.7 \
|
|
||||||
ppa:alex-p/tesseract-ocr
|
|
||||||
sudo apt-get update
|
|
||||||
sudo apt-get install -y \
|
|
||||||
ghostscript \
|
|
||||||
libexempi3 \
|
|
||||||
libffi6 \
|
|
||||||
pngquant \
|
|
||||||
python3.7 \
|
|
||||||
qpdf \
|
|
||||||
tesseract-ocr \
|
|
||||||
unpaper
|
|
||||||
|
|
||||||
This will install a Python 3.7 binary at ``/usr/bin/python3.7``
|
|
||||||
alongside the system's Python 3.5. Do not remove the system Python. This
|
|
||||||
will also install Tesseract 4.0 from a PPA, since the version available
|
|
||||||
in Ubuntu 16.04 is too old for OCRmyPDF.
|
|
||||||
|
|
||||||
Now install pip for Python 3.7. This will install the Python 3.7 version
|
|
||||||
of ``pip`` at ``/usr/local/bin/pip``.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
curl https://bootstrap.pypa.io/get-pip.py | sudo python3.7
|
|
||||||
|
|
||||||
**Install OCRmyPDF**
|
|
||||||
|
|
||||||
OCRmyPDF requires the locale to be set for UTF-8. **On some minimal
|
|
||||||
Ubuntu installations**, such as the Ubuntu 16.04 Docker images it may be
|
|
||||||
necessary to set the locale.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
# Optional: Only need to set these if they are not already set
|
|
||||||
export LC_ALL=C.UTF-8
|
|
||||||
export LANG=C.UTF-8
|
|
||||||
|
|
||||||
Now install OCRmyPDF for the current user, and ensure that the ``PATH``
|
|
||||||
environment variable contains ``$HOME/.local/bin``.
|
|
||||||
|
|
||||||
.. code-block:: bash
|
|
||||||
|
|
||||||
export PATH=$HOME/.local/bin:$PATH
|
|
||||||
pip3.7 install --user ocrmypdf
|
|
||||||
|
|
||||||
To add JBIG2 encoding, see :ref:`jbig2`.
|
|
||||||
|
|
||||||
Arch Linux (AUR)
|
Arch Linux (AUR)
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
@@ -438,7 +375,7 @@ languages you can optionally install them all:
|
|||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Users who previously installed OCRmyPDF from the private tap should
|
Users who previously installed OCRmyPDF from the private tap should
|
||||||
switch to the mainline version (``brew untap jbarlow83/ocrmypdf``)
|
switch to the mainline version (``brew untap ocrmypdf/OCRmyPDF``)
|
||||||
and install from there.
|
and install from there.
|
||||||
|
|
||||||
Manual installation on macOS
|
Manual installation on macOS
|
||||||
@@ -759,7 +696,7 @@ environment:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pip3 install git+https://github.com/jbarlow83/OCRmyPDF.git
|
pip3 install git+https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
|
|
||||||
Or, to install in `development
|
Or, to install in `development
|
||||||
mode <https://pythonhosted.org/setuptools/setuptools.html#development-mode>`__,
|
mode <https://pythonhosted.org/setuptools/setuptools.html#development-mode>`__,
|
||||||
@@ -767,14 +704,14 @@ allowing customization of OCRmyPDF, use the ``-e`` flag:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
pip3 install -e git+https://github.com/jbarlow83/OCRmyPDF.git
|
pip3 install -e git+https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
|
|
||||||
You may find it easiest to install in a virtual environment, rather than
|
You may find it easiest to install in a virtual environment, rather than
|
||||||
system-wide:
|
system-wide:
|
||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
|
git clone -b master https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
python3 -m venv
|
python3 -m venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
cd OCRmyPDF
|
cd OCRmyPDF
|
||||||
@@ -801,7 +738,7 @@ To install all of the development and test requirements:
|
|||||||
|
|
||||||
.. code-block:: bash
|
.. code-block:: bash
|
||||||
|
|
||||||
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
|
git clone -b master https://github.com/ocrmypdf/OCRmyPDF.git
|
||||||
python3 -m venv
|
python3 -m venv
|
||||||
source venv/bin/activate
|
source venv/bin/activate
|
||||||
cd OCRmyPDF
|
cd OCRmyPDF
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ For all other Linux, you must build a JBIG2 encoder from source:
|
|||||||
|
|
||||||
.. _jbig2-lossy:
|
.. _jbig2-lossy:
|
||||||
|
|
||||||
|
Dependencies include libtoolize and libleptonica, which on Ubuntu systems
|
||||||
|
are packaged as libtool and libleptonica-dev.
|
||||||
|
|
||||||
Lossy mode JBIG2
|
Lossy mode JBIG2
|
||||||
================
|
================
|
||||||
|
|
||||||
|
|||||||
@@ -54,6 +54,33 @@ to what languages it should search for. Multiple languages can be
|
|||||||
requested using either ``-l eng+fra`` (English and French) or
|
requested using either ``-l eng+fra`` (English and French) or
|
||||||
``-l eng -l fra``.
|
``-l eng -l fra``.
|
||||||
|
|
||||||
|
Gentoo users
|
||||||
|
============
|
||||||
|
|
||||||
|
On Gentoo the package ``app-text/tessdata_fast``, which ``app-text/tesseract`` depends on, handles Tesseract languages.
|
||||||
|
It accepts USE flags to select what languages should be installed, these can be set in ``/etc/portage/package.use``.
|
||||||
|
Alternatively one can globally set the `L10N use extension <https://wiki.gentoo.org/wiki/Localization/Guide#L10N>`__ in ``/etc/portage/make.conf``.
|
||||||
|
This enables these languages for all packages (e.g. including aspell).
|
||||||
|
|
||||||
|
.. code-block:: bash
|
||||||
|
|
||||||
|
# Display a list of all Tesseract language packs
|
||||||
|
equery uses app-text/tessdata_fast
|
||||||
|
|
||||||
|
# Add English and German language support for Tesseract only
|
||||||
|
echo 'app-text/tessdata_fast l10n_de l10n_en' >> /etc/portage/package.use
|
||||||
|
|
||||||
|
# Add global English and German language support (the `l10n_` from equery has to be omited)
|
||||||
|
echo L10N="de en" >> /etc/portage/make.conf
|
||||||
|
|
||||||
|
# update system to reflect changed USE flags
|
||||||
|
emerge --update --deep --newuse @world
|
||||||
|
|
||||||
|
You can then pass the ``-l LANG`` argument to OCRmyPDF to give a hint as
|
||||||
|
to what languages it should search for. Multiple languages can be
|
||||||
|
requested using either ``-l eng+fra`` (English and French) or
|
||||||
|
``-l eng -l fra``.
|
||||||
|
|
||||||
macOS users
|
macOS users
|
||||||
===========
|
===========
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Maintainer notes
|
Maintainer notes
|
||||||
================
|
================
|
||||||
|
|
||||||
This is for those who package OCRmyPDF for downstream use. (Thank you
|
This is for those who package OCRmyPDF for downstream use. (Thank you
|
||||||
for your hard work.)
|
for your hard work.)
|
||||||
|
|
||||||
Known ports/packagers
|
Known ports/packagers
|
||||||
@@ -25,7 +25,7 @@ Non-Python dependencies
|
|||||||
|
|
||||||
Note that we have non-Python dependencies. In particular, OCRmyPDF requires
|
Note that we have non-Python dependencies. In particular, OCRmyPDF requires
|
||||||
Ghostscript and Tesseract OCR to be installed and needs to be able to locate their
|
Ghostscript and Tesseract OCR to be installed and needs to be able to locate their
|
||||||
binaries on the system PATH. On Windows, OCRmyPDF will also check the registry
|
binaries on the system PATH. On Windows, OCRmyPDF will also check the registry
|
||||||
for their locations.
|
for their locations.
|
||||||
|
|
||||||
Tesseract OCR relies on SIMD for performance and only has proper support for this
|
Tesseract OCR relies on SIMD for performance and only has proper support for this
|
||||||
@@ -38,13 +38,13 @@ OCRmyPDF uses setuptools-scm for versioning, which derives the version from
|
|||||||
Git as a single source of truth. This may be unsuitable for some distributions, e.g.
|
Git as a single source of truth. This may be unsuitable for some distributions, e.g.
|
||||||
to indicate that your distribution modifies OCRmyPDF in some way.
|
to indicate that your distribution modifies OCRmyPDF in some way.
|
||||||
|
|
||||||
You can patch the ``__version__`` variable in ``src/ocrmypdf/_version.py`` if
|
You can patch the ``__version__`` variable in ``src/ocrmypdf/_version.py`` if
|
||||||
necessary.
|
necessary.
|
||||||
|
|
||||||
OCRmyPDF uses setuptools-scm-git-archive to ensure that tarballs downloaded from
|
OCRmyPDF uses setuptools-scm-git-archive to ensure that tarballs downloaded from
|
||||||
GitHub contain version information. Unfortunately, these tarballs are not always
|
GitHub contain version information. Unfortunately, these tarballs are not always
|
||||||
deterministic. See this
|
deterministic. See this
|
||||||
`issue <https://github.com/jbarlow83/OCRmyPDF/issues/841#issuecomment-936562696>`_.
|
`issue <https://github.com/ocrmypdf/OCRmyPDF/issues/841#issuecomment-936562696>`_.
|
||||||
|
|
||||||
jbig2enc
|
jbig2enc
|
||||||
--------
|
--------
|
||||||
|
|||||||
@@ -152,6 +152,16 @@ hooks. As such, you cannot "chain" a series of plugin filters together in this
|
|||||||
way. Instead, a single hook implementation should be responsible for any such
|
way. Instead, a single hook implementation should be responsible for any such
|
||||||
chaining operations.
|
chaining operations.
|
||||||
|
|
||||||
|
Examples
|
||||||
|
========
|
||||||
|
|
||||||
|
* OCRmyPDF's test suite contains several plugins that are used to simulate certain
|
||||||
|
test conditions.
|
||||||
|
* `ocrmypdf-papermerge <https://github.com/papermerge/OCRmyPDF_papermerge>`_ is
|
||||||
|
a production plugin that integrates OCRmyPDF and the Papermerge document
|
||||||
|
management system.
|
||||||
|
|
||||||
|
|
||||||
Custom command line arguments
|
Custom command line arguments
|
||||||
-----------------------------
|
-----------------------------
|
||||||
|
|
||||||
|
|||||||
+120
-8
@@ -12,11 +12,123 @@ may be unreliable. Use the API to depend on precise behavior.
|
|||||||
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
The public API may be useful in scripts that launch OCRmyPDF processes or that
|
||||||
wish to use some of its features for working with PDFs.
|
wish to use some of its features for working with PDFs.
|
||||||
|
|
||||||
.. note::
|
The most recent release of OCRmyPDF is |OCRmyPDF PyPI|. Any newer versions
|
||||||
|
referred to in these notes may exist the main branch but have not been
|
||||||
|
tagged yet.
|
||||||
|
|
||||||
Python 3.6 reaches end of life on December 23, 2021. We have already ended support
|
.. |OCRmyPDF PyPI| image:: https://img.shields.io/pypi/v/ocrmypdf.svg
|
||||||
for Python 3.6 but might release fixes for critical issues if necessary before that
|
|
||||||
date.
|
v13.4.7
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed PermissionError when cleaning up temporary files in rare cases. :issue:`974`
|
||||||
|
- Fixed PermissionError when calling ``os.nice`` on platforms that lack it. :issue:`973`
|
||||||
|
- Suppressed some warnings from libxmp during tests.
|
||||||
|
|
||||||
|
v13.4.6
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Convert error on corrupt ICC profiles into a warning. Thanks to @oscherler.
|
||||||
|
|
||||||
|
v13.4.5
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Remove upper bound on pdfminer.six version.
|
||||||
|
- Documentation.
|
||||||
|
|
||||||
|
v13.4.4
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Updated pdfminer.six version.
|
||||||
|
- Docker image changed to Ubuntu 22.04 now that it is released and provides the
|
||||||
|
dependencies we need. This seems more consistent than our recent change to
|
||||||
|
Debian.
|
||||||
|
|
||||||
|
v13.4.3
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fix error on pytest.skip() with older versions of pytest.
|
||||||
|
- Documentation updates.
|
||||||
|
|
||||||
|
v13.4.2
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Worked around a
|
||||||
|
`major regression in Ghostscript 9.56.0 <https://bugs.ghostscript.com/show_bug.cgi?id=705187>`__
|
||||||
|
where **all OCR text is stripped out of the PDF**. It simply removes all text,
|
||||||
|
even generated by software other than OCRmyPDF. Fortunately, we can ask
|
||||||
|
Ghostscript 9.56.0 to use its old behavior that worked correctly for our purposes.
|
||||||
|
Users must avoid the combination (Ghostscript 9.56.0, ocrmypdf <13.4.2) since
|
||||||
|
older versions of OCRmyPDF have no way of detecting that this particular
|
||||||
|
version of Ghostscript removes all OCR text.
|
||||||
|
- Marked pdfminer 20220319 as supported.
|
||||||
|
- Fixed some deprecation warnings from recent versions of Pillow and pytest.
|
||||||
|
- Test suite now covers Python 3.10 (Python 3.10 worked fine before, but was not
|
||||||
|
being tested).
|
||||||
|
- Docker image now uses debian:bookworm-slim as the base image to fix the Docker
|
||||||
|
image build.
|
||||||
|
|
||||||
|
v13.4.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Temporarily make threads rather than processes the default executor worker, due
|
||||||
|
to a persistent deadlock issue when processes are used. Add a new command line
|
||||||
|
argument ``--no-use-threads`` to disable this.
|
||||||
|
|
||||||
|
v13.4.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed test failures when using pikepdf 5.0.0.
|
||||||
|
- Various improvements to the optimizer. In particular, we now recognize PDF images
|
||||||
|
that are encoded with both deflate (PNG) and DCT (JPEG), and also produce PDF
|
||||||
|
with images compressed with deflate and DCT, since this often yields file size
|
||||||
|
improvements compared to plain DCT.
|
||||||
|
|
||||||
|
v13.3.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Made a harmless but "scary" exception after failing to optimize an image less scary.
|
||||||
|
- Added a warning if a page image is too large for unpaper to clean. The image is
|
||||||
|
passed through without cleaning. This is due to a hard-coded limitation in a
|
||||||
|
C library used by unpaper so it cannot be rectified easily.
|
||||||
|
- We now use better default settings when calling img2pdf.
|
||||||
|
- We no longer try to optimize images that we failed to save in certain situations.
|
||||||
|
- We now account for some differences in text output from Tesseract 5 compared to
|
||||||
|
Tesseract 4.
|
||||||
|
- Better handling of Ghostscript producing empty images when attempting to rasterize
|
||||||
|
page images.
|
||||||
|
|
||||||
|
v13.2.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Removed all runtime uses of distutils since it is deprecated in standard library. We
|
||||||
|
previous used ``distutils.version`` to examine version numbers of dependencies
|
||||||
|
at run time, and now use ``packaging.version`` for this. This is a new
|
||||||
|
dependency.
|
||||||
|
- Fixed an error message advising the user that Ghostscript was not installed being
|
||||||
|
suppressed when this condition actually happens.
|
||||||
|
- Fixed an issue with incorrect page number and totals being displayed in the progress
|
||||||
|
bar. This was purely a display/presentation issue. :issue:`876`.
|
||||||
|
|
||||||
|
v13.1.1
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Fixed issue with attempting to deskew a blank page on Tesseract 5. :issue:`868`.
|
||||||
|
|
||||||
|
v13.1.0
|
||||||
|
=======
|
||||||
|
|
||||||
|
- Changed to using Python concurrent.futures-based parallel execution instead of
|
||||||
|
pools, since futures have now exceed pools in features.
|
||||||
|
- If a child worker is terminated (perhaps by the operating system or the user
|
||||||
|
killing it in a task manager), the parallel task will fail an error message.
|
||||||
|
Previously, the main ocrmypdf process would "hang" indefinitely, waiting for the
|
||||||
|
child to report.
|
||||||
|
- Added new argument ``--tesseract-thresholding`` to provide control over Tesseract 5's
|
||||||
|
threshold parameter.
|
||||||
|
- Documentation updates and changes. Better documentation for ``--output-type none``,
|
||||||
|
added a few releases ago. Removed some obsolete documentation.
|
||||||
|
- Improved bash completions - thanks to @FPille.
|
||||||
|
|
||||||
v13.0.0
|
v13.0.0
|
||||||
=======
|
=======
|
||||||
@@ -24,7 +136,7 @@ v13.0.0
|
|||||||
**Breaking changes**
|
**Breaking changes**
|
||||||
|
|
||||||
- The deprecated module ``ocrmypdf.leptonica`` has been removed.
|
- The deprecated module ``ocrmypdf.leptonica`` has been removed.
|
||||||
- We no longer depend on Leptonica (``liblept``) or CFFI (``libffi``,
|
- We no longer depend on Leptonica (``liblept``) or CFFI (``libffi``,
|
||||||
``python3-cffi``). (Note that Tesseract still requires Leptonica; OCRmyPDF no longer
|
``python3-cffi``). (Note that Tesseract still requires Leptonica; OCRmyPDF no longer
|
||||||
directly uses this library.)
|
directly uses this library.)
|
||||||
- The argument ``--remove-background`` is temporarily disabled while we search for an
|
- The argument ``--remove-background`` is temporarily disabled while we search for an
|
||||||
@@ -33,7 +145,7 @@ v13.0.0
|
|||||||
Tesseract 5.x has implemented improvements to thresholding, so this feature will be
|
Tesseract 5.x has implemented improvements to thresholding, so this feature will be
|
||||||
redundant anyway.
|
redundant anyway.
|
||||||
- ``--deskew`` was previous calculated by a Leptonica algorithm. We now use a feature
|
- ``--deskew`` was previous calculated by a Leptonica algorithm. We now use a feature
|
||||||
of Tesseract to term the appropriate the angle to deskew a page. The deskew angle
|
of Tesseract to find the appropriate the angle to deskew a page. The deskew angle
|
||||||
according to Tesseract may differ from Leptonica's algorithm. At least in theory,
|
according to Tesseract may differ from Leptonica's algorithm. At least in theory,
|
||||||
Tesseract's deskew angle is informed by a more complex analysis than Leptonica,
|
Tesseract's deskew angle is informed by a more complex analysis than Leptonica,
|
||||||
so this should improve results in general. We also use Pillow to perform the
|
so this should improve results in general. We also use Pillow to perform the
|
||||||
@@ -48,7 +160,7 @@ v13.0.0
|
|||||||
|
|
||||||
**Fixes**
|
**Fixes**
|
||||||
|
|
||||||
- Fixed an issue where OCRmyPDF failed to find Ghostscript on Windows even when
|
- Fixed an issue where OCRmyPDF failed to find Ghostscript on Windows even when
|
||||||
installed, and would exit with an error.
|
installed, and would exit with an error.
|
||||||
- By removing Leptonica, we fixed all issues related to Leptonica on Apple
|
- By removing Leptonica, we fixed all issues related to Leptonica on Apple
|
||||||
Silicon or Leptonica failing to import on Windows.
|
Silicon or Leptonica failing to import on Windows.
|
||||||
@@ -56,7 +168,7 @@ v13.0.0
|
|||||||
v12.7.2
|
v12.7.2
|
||||||
=======
|
=======
|
||||||
|
|
||||||
- Fixed "invalid version number" error for Tesseract packaging with nonstandard
|
- Fixed "invalid version number" error for Tesseract packaging with nonstandard
|
||||||
version "5.0.0-rc1.20211030".
|
version "5.0.0-rc1.20211030".
|
||||||
- Fixed use of deprecated ``importlib.resources.read_binary``.
|
- Fixed use of deprecated ``importlib.resources.read_binary``.
|
||||||
- Replace some uses of string paths with ``pathlib.Path``.
|
- Replace some uses of string paths with ``pathlib.Path``.
|
||||||
|
|||||||
+256
-66
@@ -1,6 +1,6 @@
|
|||||||
# ocrmypdf completion -*- shell-script -*-
|
# ocrmypdf completion -*- shell-script -*-
|
||||||
|
|
||||||
# Copyright 2019 Frank Pille
|
# Copyright 2019, 2021 Frank Pille
|
||||||
# Copyright 2020 Alex Willner
|
# Copyright 2020 Alex Willner
|
||||||
#
|
#
|
||||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
@@ -23,95 +23,285 @@
|
|||||||
|
|
||||||
set -o errexit
|
set -o errexit
|
||||||
|
|
||||||
_ocrmypdf()
|
__ocrmypdf_arguments()
|
||||||
{
|
{
|
||||||
local cur prev cword words split
|
local arguments="--help (show help message)
|
||||||
|
--language (language(s) of the file to be OCRed)
|
||||||
|
--image-dpi (assume this DPI if input image DPI is unknown)
|
||||||
|
--output-type (select PDF output options)
|
||||||
|
--sidecar (write OCR to text file)
|
||||||
|
--version (print program version and exit)
|
||||||
|
--jobs (how many worker processes to use)
|
||||||
|
--quiet (suppress INFO messages)
|
||||||
|
--verbose (set verbosity level)
|
||||||
|
--title (set metadata)
|
||||||
|
--author (set metadata)
|
||||||
|
--subject (set metadata)
|
||||||
|
--keywords (set metadata)
|
||||||
|
--rotate-pages (rotate pages to correct orientation)
|
||||||
|
--remove-background (attempt to remove background from pages)
|
||||||
|
--deskew (fix small horizontal alignment skew)
|
||||||
|
--clean (clean document images before OCR)
|
||||||
|
--clean-final (clean document images and keep result)
|
||||||
|
--unpaper-args (a quoted string of arguments to pass to unpaper)
|
||||||
|
--oversample (oversample images to this DPI)
|
||||||
|
--remove-vectors (don\'t send vector objects to OCR)
|
||||||
|
--threshold (threshold images before OCR)
|
||||||
|
--force-ocr (OCR documents that already have printable text)
|
||||||
|
--skip-text (skip OCR on any pages that already contain text)
|
||||||
|
--redo-ocr (redo OCR on any pages that seem to have OCR already)
|
||||||
|
--skip-big (skip OCR on pages larger than this many MPixels)
|
||||||
|
--optimize (select optimization level)
|
||||||
|
--jpeg-quality (JPEG quality [0..100])
|
||||||
|
--png-quality (PNG quality [0..100])
|
||||||
|
--jbig2-lossy (enable lossy JBIG2 (see docs))
|
||||||
|
--pages (apply OCR to only the specified pages)
|
||||||
|
--max-image-mpixels (image decompression bomb threshold)
|
||||||
|
--pdf-renderer (select PDF renderer options)
|
||||||
|
--rotate-pages-threshold (page rotation confidence)
|
||||||
|
--pdfa-image-compression (set PDF/A image compression options)
|
||||||
|
--fast-web-view (if file size if above this amount in MB linearize PDF)
|
||||||
|
--plugin (name of plugin to import)
|
||||||
|
--keep-temporary-files (keep temporary files (debug)
|
||||||
|
--tesseract-config (set custom tesseract config file)
|
||||||
|
--tesseract-pagesegmode (set tesseract --psm)
|
||||||
|
--tesseract-oem (set tesseract --oem)
|
||||||
|
--tesseract-thresholding (set tesseract image thresholding)
|
||||||
|
--tesseract-timeout (maximum number of seconds to wait for OCR)
|
||||||
|
--user-words (specify location of user words file)
|
||||||
|
--user-patterns (specify location of user patterns file)
|
||||||
|
--no-progress-bar (disable the progress bar)
|
||||||
|
"
|
||||||
|
|
||||||
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include - see #502
|
COMPREPLY=( $( compgen -W "$arguments" -- "$cur") )
|
||||||
if declare -F _init_completions >/dev/null 2>&1; then
|
|
||||||
_init_completion -s || return
|
# Remove description if only one completion exists
|
||||||
else
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
COMPREPLY=()
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
_get_comp_words_by_ref cur prev words cword
|
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
if [[ $cur == -* ]]; then
|
__ocrmypdf_output-type()
|
||||||
COMPREPLY=( $( compgen -W '--language --image-dpi --output-type
|
{
|
||||||
--sidecar --version --jobs --quiet --verbose --title --author
|
local choices="pdfa (output a PDF/A (default))
|
||||||
--subject --keywords --rotate-pages --deskew
|
pdf (output a standard PDF)
|
||||||
--clean --clean-final --unpaper-args --oversample --remove-vectors
|
pdfa-1 (output a PDF/A-1b)
|
||||||
--force-ocr --skip-text --redo-ocr
|
pdfa-2 (output a PDF/A-2b)
|
||||||
--skip-big --jpeg-quality --png-quality --jbig2-lossy
|
pdfa-3 (output a PDF/A-3b)
|
||||||
--max-image-mpixels --tesseract-config --tesseract-pagesegmode
|
none (do not produce an output PDF (for example, if you only care about --sidecar))"
|
||||||
--help --tesseract-oem --pdf-renderer --tesseract-timeout
|
|
||||||
--rotate-pages-threshold --pdfa-image-compression --user-words
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
--user-patterns --keep-temporary-files --output-type
|
|
||||||
--no-progress-bar --pages --fast-web-view' \
|
# Remove description if only one completion exists
|
||||||
-- "$cur" ) )
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
return
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
else
|
|
||||||
_filedir
|
|
||||||
return
|
|
||||||
fi
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_verbose()
|
||||||
|
{
|
||||||
|
local choices="0 (standard output messages)
|
||||||
|
1 (troubleshooting output messages)
|
||||||
|
2 (debugging output messages)"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_optimize()
|
||||||
|
{
|
||||||
|
local choices="0 (do not optimize)
|
||||||
|
1 (do safe, lossless optimizations (default))
|
||||||
|
2 (do some lossy optimizations)
|
||||||
|
3 (do aggressive lossy optimizations (including lossy JBIG2))"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_pdf-renderer()
|
||||||
|
{
|
||||||
|
local choices="auto (auto select PDF renderer)
|
||||||
|
hocr (use hOCR renderer)
|
||||||
|
hocrdebug (uses hOCR renderer in debug mode, showing recognized text)
|
||||||
|
sandwich (use sandwich renderer)"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_pdfa-image-compression()
|
||||||
|
{
|
||||||
|
local choices="auto (let Ghostscript decide how to compress images)
|
||||||
|
jpeg (convert color and grayscale images to JPEG)
|
||||||
|
lossless (convert color and grayscale images to lossless (PNG))"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_tesseract-pagesegmode()
|
||||||
|
{
|
||||||
|
local choices="0 (orientation and script detection (OSD) only)
|
||||||
|
1 (automatic page segmentation with OSD)
|
||||||
|
2 (automatic page segmentation, but no OSD, or OCR)
|
||||||
|
3 (fully automatic page segmentation, but no OSD (default))
|
||||||
|
4 (assume a single column of text of variable sizes)
|
||||||
|
5 (assume a single uniform block of vertically aligned text)
|
||||||
|
6 (assume a single uniform block of text)
|
||||||
|
7 (treat the image as a single text line)
|
||||||
|
8 (treat the image as a single word)
|
||||||
|
9 (treat the image as a single word in a circle)
|
||||||
|
10 (treat the image as a single character)
|
||||||
|
11 (sparse text - find as much text as possible in no particular order)
|
||||||
|
12 (sparse text with OSD)
|
||||||
|
13 (raw line - treat the image as a single text line)"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_tesseract-oem()
|
||||||
|
{
|
||||||
|
local choices="0 (legacy engine only)
|
||||||
|
1 (neural nets LSTM engine only)
|
||||||
|
2 (legacy + LSTM engines)
|
||||||
|
3 (default, based on what is available)"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
__ocrmypdf_tesseract-thresholding()
|
||||||
|
{
|
||||||
|
local choices="auto (let OCRmyPDF pick thresholding - current always uses otsu)
|
||||||
|
otsu (use hOCR renderer)
|
||||||
|
adaptive-otsu (use adaptive Otsu thresholding)
|
||||||
|
sauvola (use Sauvola thresholding)"
|
||||||
|
|
||||||
|
COMPREPLY=( $( compgen -W "$choices" -- "$cur") )
|
||||||
|
# Remove description if only one completion exists
|
||||||
|
if [[ ${#COMPREPLY[*]} -eq 1 ]]; then
|
||||||
|
COMPREPLY=( ${COMPREPLY[0]%% *} )
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__ocrmypdf_check_previous()
|
||||||
|
{
|
||||||
case $prev in
|
case $prev in
|
||||||
--version|-h|--help)
|
-h|--help|--version)
|
||||||
return
|
return 0
|
||||||
;;
|
|
||||||
--user-words|--user-patterns|--tesseract-config)
|
|
||||||
_filedir
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
--output-type)
|
|
||||||
COMPREPLY=( $( compgen -W 'pdfa pdf pdfa-1 pdfa-2 pdfa-3' -- \
|
|
||||||
"$cur" ) )
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
--pdf-renderer)
|
|
||||||
COMPREPLY=( $( compgen -W 'auto hocr sandwich' -- "$cur" ) )
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
--pdfa-image-compression)
|
|
||||||
COMPREPLY=( $( compgen -W 'auto jpeg lossless' -- "$cur" ) )
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
-O|--optimize|--tesseract-oem)
|
|
||||||
COMPREPLY=( $( compgen -W '{0..3}' -- "$cur" ) )
|
|
||||||
return
|
|
||||||
;;
|
|
||||||
--jpeg-quality|--png-quality)
|
|
||||||
COMPREPLY=( $( compgen -W '{0..100}' -- "$cur" ) )
|
|
||||||
return
|
|
||||||
;;
|
;;
|
||||||
-l|--language)
|
-l|--language)
|
||||||
COMPREPLY=$( command tesseract --list-langs 2>/dev/null )
|
COMPREPLY=$( command tesseract --list-langs 2>/dev/null )
|
||||||
COMPREPLY=( $( compgen -W '${COMPREPLY[@]##*:}' -- "$cur" ) )
|
COMPREPLY=( $( compgen -W '${COMPREPLY[@]##*:}' -- "$cur" ) )
|
||||||
return
|
return 0
|
||||||
;;
|
;;
|
||||||
--image-dpi|--oversample|--skip-big|--max-image-mpixels|\
|
--output-type)
|
||||||
--tesseract-timeout|--rotate-pages-threshold)
|
__ocrmypdf_output-type
|
||||||
COMPREPLY=( $( compgen -P "$cur" -W '{0..9}' ) )
|
return 0
|
||||||
return
|
|
||||||
;;
|
;;
|
||||||
-j|--jobs)
|
-j|--jobs)
|
||||||
COMPREPLY=( $( compgen -W '{1..'$( _ncpus )'}' -- "$cur" ) )
|
COMPREPLY=( $( compgen -W '{1..'$( _ncpus )'}' -- "$cur" ) )
|
||||||
return
|
return 0
|
||||||
;;
|
;;
|
||||||
-v|--verbose)
|
-v|--verbose)
|
||||||
COMPREPLY=( $( compgen -W '{0..2}' -- "$cur" ) ) # max level ?
|
__ocrmypdf_verbose
|
||||||
return
|
return 0
|
||||||
|
;;
|
||||||
|
-O|--optimize)
|
||||||
|
__ocrmypdf_optimize
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--pdf-renderer)
|
||||||
|
__ocrmypdf_pdf-renderer
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--pdfa-image-compression)
|
||||||
|
__ocrmypdf_pdfa-image-compression
|
||||||
|
return 0
|
||||||
;;
|
;;
|
||||||
--tesseract-pagesegmode)
|
--tesseract-pagesegmode)
|
||||||
COMPREPLY=( $( compgen -W '{1..13}' -- "$cur" ) )
|
__ocrmypdf_tesseract-pagesegmode
|
||||||
return
|
return 0
|
||||||
;;
|
;;
|
||||||
--sidecar|--title|--author|--subject|--keywords|--unpaper-args|--pages|--fast-web-view)
|
--tesseract-oem)
|
||||||
|
__ocrmypdf_tesseract-oem
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
--tesseract-thresholding)
|
||||||
|
__ocrmypdf_tesseract-thresholding
|
||||||
|
return 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
--title|--author|--subject|--keywords|--unpaper-args|--pages|--plugin|\
|
||||||
|
--jpeg-quality|--png-quality|--image-dpi|--oversample|--skip-big|--max-image-mpixels|\
|
||||||
|
--tesseract-timeout|--rotate-pages-threshold|--fast-web-view)
|
||||||
# argument required but no completions available
|
# argument required but no completions available
|
||||||
return
|
return 0
|
||||||
|
;;
|
||||||
|
--tesseract-config|--user-words|--user-patterns|--sidecar)
|
||||||
|
_filedir
|
||||||
|
return 0
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
$split && return
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
_ocrmypdf()
|
||||||
|
{
|
||||||
|
local OLDIFS="$IFS"
|
||||||
|
local IFS=$'\n'
|
||||||
|
|
||||||
|
local cur prev
|
||||||
|
|
||||||
|
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include - see #502
|
||||||
|
if declare -F _init_completion >/dev/null 2>&1; then
|
||||||
|
_init_completion || return
|
||||||
|
else
|
||||||
|
COMPREPLY=()
|
||||||
|
_get_comp_words_by_ref cur prev
|
||||||
|
fi
|
||||||
|
|
||||||
|
if __ocrmypdf_check_previous -ne 0; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "$cur" == -* ]]; then
|
||||||
|
__ocrmypdf_arguments
|
||||||
|
else
|
||||||
|
_filedir
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS="$OLDIFS"
|
||||||
|
|
||||||
|
return
|
||||||
} &&
|
} &&
|
||||||
complete -F _ocrmypdf ocrmypdf
|
complete -F _ocrmypdf ocrmypdf
|
||||||
|
|
||||||
|
|||||||
@@ -105,20 +105,20 @@ complete -c ocrmypdf -x -l pages -d "apply OCR to only the specified pages"
|
|||||||
complete -c ocrmypdf -x -l tesseract-config -d "set custom tesseract config file"
|
complete -c ocrmypdf -x -l tesseract-config -d "set custom tesseract config file"
|
||||||
|
|
||||||
function __fish_ocrmypdf_tesseract_pagesegmode
|
function __fish_ocrmypdf_tesseract_pagesegmode
|
||||||
echo -e "0\t"(_ "orientation and script detection (OSD) only")
|
echo -e "0\t"(_ "orientation and script detection (OSD) only")
|
||||||
echo -e "1\t"(_ "automatic page segmentation with OSD")
|
echo -e "1\t"(_ "automatic page segmentation with OSD")
|
||||||
echo -e "2\t"(_ "automatic page segmentation, but no OSD, or OCR")
|
echo -e "2\t"(_ "automatic page segmentation, but no OSD, or OCR")
|
||||||
echo -e "3\t"(_ "fully automatic page segmentation, but no OSD (default)")
|
echo -e "3\t"(_ "fully automatic page segmentation, but no OSD (default)")
|
||||||
echo -e "4\t"(_ "assume a single column of text of variable sizes")
|
echo -e "4\t"(_ "assume a single column of text of variable sizes")
|
||||||
echo -e "5\t"(_ "assume a single uniform block of vertically aligned text")
|
echo -e "5\t"(_ "assume a single uniform block of vertically aligned text")
|
||||||
echo -e "6\t"(_ "assume a single uniform block of text")
|
echo -e "6\t"(_ "assume a single uniform block of text")
|
||||||
echo -e "7\t"(_ "treat the image as a single text line")
|
echo -e "7\t"(_ "treat the image as a single text line")
|
||||||
echo -e "8\t"(_ "treat the image as a single word")
|
echo -e "8\t"(_ "treat the image as a single word")
|
||||||
echo -e "9\t"(_ "treat the image as a single word in a circle")
|
echo -e "9\t"(_ "treat the image as a single word in a circle")
|
||||||
echo -e "10\t"(_ "treat the image as a single character")
|
echo -e "10\t"(_ "treat the image as a single character")
|
||||||
echo -e "11\t"(_ "sparse text - find as much text as possible in no particular order")
|
echo -e "11\t"(_ "sparse text - find as much text as possible in no particular order")
|
||||||
echo -e "12\t"(_ "sparse text with OSD")
|
echo -e "12\t"(_ "sparse text with OSD")
|
||||||
echo -e "13\t"(_ "raw line - treat the image as a single text line")
|
echo -e "13\t"(_ "raw line - treat the image as a single text line")
|
||||||
end
|
end
|
||||||
complete -c ocrmypdf -x -l tesseract-pagesegmode -a '(__fish_ocrmypdf_tesseract_pagesegmode)' -d "set tesseract --psm"
|
complete -c ocrmypdf -x -l tesseract-pagesegmode -a '(__fish_ocrmypdf_tesseract_pagesegmode)' -d "set tesseract --psm"
|
||||||
|
|
||||||
@@ -129,6 +129,15 @@ function __fish_ocrmypdf_tesseract_oem
|
|||||||
echo -e "3\t"(_ "default, based on what is available")
|
echo -e "3\t"(_ "default, based on what is available")
|
||||||
end
|
end
|
||||||
complete -c ocrmypdf -x -l tesseract-oem -a '(__fish_ocrmypdf_tesseract_oem)' -d "set tesseract --oem"
|
complete -c ocrmypdf -x -l tesseract-oem -a '(__fish_ocrmypdf_tesseract_oem)' -d "set tesseract --oem"
|
||||||
|
|
||||||
|
function __fish_ocrmypdf_tesseract_thresholding
|
||||||
|
echo -e "auto\t"(_ "let OCRmyPDF pick thresholding (current always uses otsu)")
|
||||||
|
echo -e "otsu\t"(_ "legacy Otsu thresholding")
|
||||||
|
echo -e "adaptive-otsu\t"(_ "use adaptive Otsu thresholding")
|
||||||
|
echo -e "sauvola\t"(_ "use Sauvola thresholding")
|
||||||
|
end
|
||||||
|
complete -c ocrmypdf -x -l tesseract-thresholding -a '(__fish_ocrmypdf_tesseract_thresholding)' -d "set tesseract thresholding method (needs Tesseract 5.x)"
|
||||||
|
|
||||||
complete -c ocrmypdf -x -l tesseract-timeout -d "maximum number of seconds to wait for OCR"
|
complete -c ocrmypdf -x -l tesseract-timeout -d "maximum number of seconds to wait for OCR"
|
||||||
complete -c ocrmypdf -x -l rotate-pages-threshold -d "page rotation confidence"
|
complete -c ocrmypdf -x -l rotate-pages-threshold -d "page rotation confidence"
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ def do_ocrmypdf(file):
|
|||||||
return Response("--sidecar not supported", 501, mimetype='text/plain')
|
return Response("--sidecar not supported", 501, mimetype='text/plain')
|
||||||
|
|
||||||
ocrmypdf_args = ["ocrmypdf", *cmd_args, up_file, down_file]
|
ocrmypdf_args = ["ocrmypdf", *cmd_args, up_file, down_file]
|
||||||
proc = run(ocrmypdf_args, stdout=PIPE, stderr=PIPE, encoding="utf-8")
|
proc = run(ocrmypdf_args, capture_output=True, encoding="utf-8")
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
stderr = proc.stderr
|
stderr = proc.stderr
|
||||||
return Response(stderr, 400, mimetype='text/plain')
|
return Response(stderr, 400, mimetype='text/plain')
|
||||||
|
|||||||
+35
-14
@@ -44,24 +44,38 @@ source = ["src/ocrmypdf"]
|
|||||||
[tool.coverage.report]
|
[tool.coverage.report]
|
||||||
# Regexes for lines to exclude from consideration
|
# Regexes for lines to exclude from consideration
|
||||||
exclude_lines = [
|
exclude_lines = [
|
||||||
# Have to re-enable the standard pragma
|
# Have to re-enable the standard pragma
|
||||||
"pragma: no cover",
|
"pragma: no cover",
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
# Don't complain if tests don't hit defensive assertion code:
|
"raise AssertionError",
|
||||||
"raise AssertionError",
|
"raise NotImplementedError",
|
||||||
"raise NotImplementedError",
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
"if 0:",
|
||||||
# Don't complain if non-runnable code isn't run:
|
"if False:",
|
||||||
"if 0:",
|
"if __name__ == .__main__.:",
|
||||||
"if False:",
|
"if TYPE_CHECKING:"
|
||||||
"if __name__ == .__main__.:",
|
|
||||||
"if TYPE_CHECKING:"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.isort]
|
[tool.isort]
|
||||||
profile = "black"
|
profile = "black"
|
||||||
known_first_party = "ocrmypdf"
|
known_first_party = "ocrmypdf"
|
||||||
known_third_party = ["PIL", "flask", "img2pdf", "ocrmypdf", "pdfminer", "pikepdf", "pkg_resources", "pluggy", "pytest", "reportlab", "setuptools", "sphinx_rtd_theme", "tqdm", "watchdog", "werkzeug"]
|
known_third_party = [
|
||||||
|
"PIL",
|
||||||
|
"flask",
|
||||||
|
"img2pdf",
|
||||||
|
"ocrmypdf",
|
||||||
|
"pdfminer",
|
||||||
|
"pikepdf",
|
||||||
|
"pkg_resources",
|
||||||
|
"pluggy",
|
||||||
|
"pytest",
|
||||||
|
"reportlab",
|
||||||
|
"setuptools",
|
||||||
|
"sphinx_rtd_theme",
|
||||||
|
"tqdm",
|
||||||
|
"watchdog",
|
||||||
|
"werkzeug"
|
||||||
|
]
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
minversion = "6.0"
|
minversion = "6.0"
|
||||||
@@ -75,6 +89,13 @@ filterwarnings = ["ignore:.*XMLParser.*:DeprecationWarning"]
|
|||||||
|
|
||||||
[[tool.mypy.overrides]]
|
[[tool.mypy.overrides]]
|
||||||
module = [
|
module = [
|
||||||
'pluggy', 'tqdm', 'coloredlogs', 'img2pdf', 'pdfminer.*', 'reportlab.*'
|
'pluggy',
|
||||||
|
'tqdm',
|
||||||
|
'coloredlogs',
|
||||||
|
'img2pdf',
|
||||||
|
'pdfminer.*',
|
||||||
|
'reportlab.*',
|
||||||
|
'fitz',
|
||||||
|
'libxmp.utils'
|
||||||
]
|
]
|
||||||
ignore_missing_imports = true
|
ignore_missing_imports = true
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ name = ocrmypdf
|
|||||||
description = OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched
|
description = OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched
|
||||||
long_description = file: README.md
|
long_description = file: README.md
|
||||||
long_description_content_type = text/markdown
|
long_description_content_type = text/markdown
|
||||||
url = https://github.com/jbarlow83/OCRmyPDF
|
url = https://github.com/ocrmypdf/OCRmyPDF
|
||||||
author = James R. Barlow
|
author = James R. Barlow
|
||||||
author_email = james@purplerock.ca
|
author_email = james@purplerock.ca
|
||||||
license = MPL-2.0
|
license = MPL-2.0
|
||||||
@@ -39,8 +39,8 @@ keywords =
|
|||||||
scanning
|
scanning
|
||||||
project_urls =
|
project_urls =
|
||||||
Documentation = https://ocrmypdf.readthedocs.io/
|
Documentation = https://ocrmypdf.readthedocs.io/
|
||||||
Source = https://github.com/jbarlow83/ocrmypdf
|
Source = https://github.com/ocrmypdf/OCRmyPDF
|
||||||
Tracker = https://github.com/jbarlow83/ocrmypdf/issues
|
Tracker = https://github.com/ocrmypdf/OCRmyPDF/issues
|
||||||
|
|
||||||
[options]
|
[options]
|
||||||
packages = find:
|
packages = find:
|
||||||
@@ -48,8 +48,9 @@ install_requires =
|
|||||||
Pillow>=8.2.0
|
Pillow>=8.2.0
|
||||||
coloredlogs>=14.0 # strictly optional
|
coloredlogs>=14.0 # strictly optional
|
||||||
img2pdf>=0.3.0,<0.5 # pure Python
|
img2pdf>=0.3.0,<0.5 # pure Python
|
||||||
pdfminer.six!=20200720,>=20191110,<=20211012
|
packaging>=20
|
||||||
pikepdf>=4.0.0
|
pdfminer.six!=20200720,>=20191110
|
||||||
|
pikepdf!=5.0.0,>=4.0.0
|
||||||
pluggy>=0.13.0,<2
|
pluggy>=0.13.0,<2
|
||||||
reportlab>=3.5.66
|
reportlab>=3.5.66
|
||||||
tqdm>=4
|
tqdm>=4
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
from contextlib import suppress
|
||||||
from multiprocessing import set_start_method
|
from multiprocessing import set_start_method
|
||||||
|
|
||||||
from ocrmypdf import __version__
|
from ocrmypdf import __version__
|
||||||
@@ -34,7 +35,7 @@ def sigbus(*args):
|
|||||||
def run(args=None):
|
def run(args=None):
|
||||||
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
|
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
|
||||||
|
|
||||||
if hasattr(os, 'nice'):
|
with suppress(AttributeError, PermissionError):
|
||||||
os.nice(5)
|
os.nice(5)
|
||||||
|
|
||||||
verbosity = options.verbose
|
verbosity = options.verbose
|
||||||
|
|||||||
@@ -18,12 +18,19 @@ from shutil import which
|
|||||||
from subprocess import PIPE, CalledProcessError
|
from subprocess import PIPE, CalledProcessError
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
|
||||||
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
||||||
from ocrmypdf.helpers import Resolution
|
from ocrmypdf.helpers import Resolution
|
||||||
from ocrmypdf.subprocess import get_version, run, run_polling_stderr
|
from ocrmypdf.subprocess import get_version, run, run_polling_stderr
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
Transpose = Image.Transpose # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
Transpose = Image # type: ignore
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
missing_gs_error = """
|
missing_gs_error = """
|
||||||
@@ -71,7 +78,8 @@ def jpeg_passthrough_available() -> bool:
|
|||||||
|
|
||||||
|
|
||||||
def _gs_error_reported(stream) -> bool:
|
def _gs_error_reported(stream) -> bool:
|
||||||
return True if re.search(r'error', stream, flags=re.IGNORECASE) else False
|
match = re.search(r'error', stream, flags=re.IGNORECASE)
|
||||||
|
return bool(match)
|
||||||
|
|
||||||
|
|
||||||
def rasterize_pdf(
|
def rasterize_pdf(
|
||||||
@@ -124,20 +132,27 @@ def rasterize_pdf(
|
|||||||
if _gs_error_reported(stderr):
|
if _gs_error_reported(stderr):
|
||||||
log.error(stderr)
|
log.error(stderr)
|
||||||
|
|
||||||
with Image.open(BytesIO(p.stdout)) as im:
|
try:
|
||||||
if rotation is not None:
|
with Image.open(BytesIO(p.stdout)) as im:
|
||||||
log.debug("Rotating output by %i", rotation)
|
if rotation is not None:
|
||||||
# rotation is a clockwise angle and Image.ROTATE_* is
|
log.debug("Rotating output by %i", rotation)
|
||||||
# counterclockwise so this cancels out the rotation
|
# rotation is a clockwise angle and Image.ROTATE_* is
|
||||||
if rotation == 90:
|
# counterclockwise so this cancels out the rotation
|
||||||
im = im.transpose(Image.ROTATE_90)
|
if rotation == 90:
|
||||||
elif rotation == 180:
|
im = im.transpose(Transpose.ROTATE_90)
|
||||||
im = im.transpose(Image.ROTATE_180)
|
elif rotation == 180:
|
||||||
elif rotation == 270:
|
im = im.transpose(Transpose.ROTATE_180)
|
||||||
im = im.transpose(Image.ROTATE_270)
|
elif rotation == 270:
|
||||||
if rotation % 180 == 90:
|
im = im.transpose(Transpose.ROTATE_270)
|
||||||
page_dpi = page_dpi.flip_axis()
|
if rotation % 180 == 90:
|
||||||
im.save(fspath(output_file), dpi=page_dpi)
|
page_dpi = page_dpi.flip_axis()
|
||||||
|
im.save(fspath(output_file), dpi=page_dpi)
|
||||||
|
except UnidentifiedImageError:
|
||||||
|
log.error(
|
||||||
|
f"Ghostscript (using {raster_device} at {raster_dpi} dpi) produced "
|
||||||
|
"an invalid page image file."
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
class GhostscriptFollower:
|
class GhostscriptFollower:
|
||||||
@@ -161,8 +176,7 @@ class GhostscriptFollower:
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
m = self.re_page.match(line.strip())
|
if self.re_page.match(line.strip()):
|
||||||
if m:
|
|
||||||
self.progressbar.update()
|
self.progressbar.update()
|
||||||
|
|
||||||
|
|
||||||
@@ -203,14 +217,18 @@ def generate_pdfa(
|
|||||||
# Older versions of Ghostscript expect a leading slash in
|
# Older versions of Ghostscript expect a leading slash in
|
||||||
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
|
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
|
||||||
# git commit fe1c025d.
|
# git commit fe1c025d.
|
||||||
strategy = ('/' + strategy) if version() < '9.19' else strategy
|
gs_version = version()
|
||||||
|
strategy = ('/' + strategy) if gs_version < '9.19' else strategy
|
||||||
|
|
||||||
if version() == '9.23':
|
if gs_version == '9.23':
|
||||||
# 9.23: added JPEG passthrough as a new feature, but with a bug that
|
# 9.23: added JPEG passthrough as a new feature, but with a bug that
|
||||||
# incorrectly formats some images. Fixed as of 9.24. So we disable this
|
# incorrectly formats some images. Fixed as of 9.24. So we disable this
|
||||||
# feature for 9.23.
|
# feature for 9.23.
|
||||||
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
|
||||||
compression_args.append('-dPassThroughJPEGImages=false')
|
compression_args.append('-dPassThroughJPEGImages=false')
|
||||||
|
elif gs_version == '9.56.0':
|
||||||
|
# 9.56.0 breaks our OCR...?
|
||||||
|
compression_args.append('-dNEWPDF=false')
|
||||||
|
|
||||||
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
# nb no need to specify ProcessColorModel when ColorConversionStrategy
|
||||||
# is set; see:
|
# is set; see:
|
||||||
|
|||||||
@@ -9,13 +9,13 @@
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from distutils.version import StrictVersion
|
|
||||||
from math import pi
|
from math import pi
|
||||||
from os import fspath
|
from os import fspath
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
|
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
|
||||||
from typing import Dict, Iterator, List, Optional
|
from typing import Dict, Iterator, List, Optional
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ocrmypdf.exceptions import (
|
from ocrmypdf.exceptions import (
|
||||||
@@ -46,6 +46,13 @@ HOCR_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
|
|||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
TESSERACT_THRESHOLDING_METHODS: Dict[str, int] = {
|
||||||
|
'auto': 0,
|
||||||
|
'otsu': 0,
|
||||||
|
'adaptive-otsu': 1,
|
||||||
|
'sauvola': 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class TesseractLoggerAdapter(logging.LoggerAdapter):
|
class TesseractLoggerAdapter(logging.LoggerAdapter):
|
||||||
def process(self, msg, kwargs):
|
def process(self, msg, kwargs):
|
||||||
@@ -53,25 +60,54 @@ class TesseractLoggerAdapter(logging.LoggerAdapter):
|
|||||||
return '[tesseract] %s' % (msg), kwargs
|
return '[tesseract] %s' % (msg), kwargs
|
||||||
|
|
||||||
|
|
||||||
class TesseractVersion(StrictVersion):
|
TESSERACT_VERSION_PATTERN = r"""
|
||||||
|
v?
|
||||||
version_re = re.compile(
|
(?:
|
||||||
r'''
|
(?:(?P<epoch>[0-9]+)!)? # epoch
|
||||||
^(\d+) \. (\d+) (\. (\d+))? # groups: 1/major, 2/minor, 3/[skip], 4/patch
|
(?P<release>[0-9]+(?:\.[0-9]+)*) # release segment
|
||||||
[-]? # optional hyphen separator
|
(?P<pre> # pre-release
|
||||||
(?: ((?:alpha|beta|rc|dev)\d*)? [.\-\ ]? (\d+)? )? # 5/prerelease, 6/prerelease_num
|
[-_\.]?
|
||||||
(?:(?:-\d+)?-g[0-9a-f]+)? # untagged git version
|
(?P<pre_l>(a|b|c|rc|alpha|beta|pre|preview))
|
||||||
$
|
[-_\.]?
|
||||||
''',
|
(?P<pre_n>[0-9]+)?
|
||||||
re.VERBOSE | re.ASCII,
|
)?
|
||||||
|
(?P<post> # post release
|
||||||
|
(?:-(?P<post_n1>[0-9]+))
|
||||||
|
|
|
||||||
|
(?:
|
||||||
|
[-_\.]?
|
||||||
|
(?P<post_l>post|rev|r)
|
||||||
|
[-_\.]?
|
||||||
|
(?P<post_n2>[0-9]+)?
|
||||||
|
)
|
||||||
|
)?
|
||||||
|
(?P<dev> # dev release
|
||||||
|
[-_\.]?
|
||||||
|
(?P<dev_l>dev)
|
||||||
|
[-_\.]?
|
||||||
|
(?P<dev_n>[0-9]+)?
|
||||||
|
)?
|
||||||
|
(?P<date>
|
||||||
|
[-_\.]
|
||||||
|
(?:20[0-9][0-9] [0-1][0-9] [0-3][0-9]) # yyyy mm dd
|
||||||
|
)?
|
||||||
|
(?P<gitcount>
|
||||||
|
[-_\.]?
|
||||||
|
[0-9]+
|
||||||
|
)?
|
||||||
|
(?P<gitcommit>
|
||||||
|
[-_\.]?
|
||||||
|
g[0-9a-f]{2,10}
|
||||||
|
)?
|
||||||
)
|
)
|
||||||
|
(?:\+(?P<local>[a-z0-9]+(?:[-_\.][a-z0-9]+)*))? # local version
|
||||||
|
"""
|
||||||
|
|
||||||
def parse(self, vstring):
|
|
||||||
try:
|
class TesseractVersion(Version):
|
||||||
super().parse(vstring)
|
_regex = re.compile(
|
||||||
except TypeError as e:
|
r"^\s*" + TESSERACT_VERSION_PATTERN + r"\s*$", re.VERBOSE | re.IGNORECASE
|
||||||
if 'int() argument must be a string' in str(e):
|
)
|
||||||
super().parse(vstring + '-0')
|
|
||||||
|
|
||||||
|
|
||||||
def version() -> str:
|
def version() -> str:
|
||||||
@@ -87,6 +123,11 @@ def has_user_words():
|
|||||||
return version() >= '4.1'
|
return version() >= '4.1'
|
||||||
|
|
||||||
|
|
||||||
|
def has_thresholding():
|
||||||
|
"""Does Tesseract have -c thresholding method capability?"""
|
||||||
|
return version() >= '5.0'
|
||||||
|
|
||||||
|
|
||||||
def get_languages():
|
def get_languages():
|
||||||
def lang_error(output):
|
def lang_error(output):
|
||||||
msg = (
|
msg = (
|
||||||
@@ -188,7 +229,9 @@ def get_deskew(
|
|||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
tesseract_log_output(e.stdout)
|
tesseract_log_output(e.stdout)
|
||||||
tesseract_log_output(e.stderr)
|
tesseract_log_output(e.stderr)
|
||||||
if b'Empty page!!' in e.output: # Not enough info for a skew angle
|
if b'Empty page!!' in e.output or (
|
||||||
|
e.output == b'' and e.returncode == 1
|
||||||
|
): # Not enough info for a skew angle - Tess 4 and 5 return different errors
|
||||||
return 0.0
|
return 0.0
|
||||||
|
|
||||||
raise SubprocessOutputError() from e
|
raise SubprocessOutputError() from e
|
||||||
@@ -265,9 +308,11 @@ def generate_hocr(
|
|||||||
tessconfig: List[str],
|
tessconfig: List[str],
|
||||||
timeout: float,
|
timeout: float,
|
||||||
pagesegmode: int,
|
pagesegmode: int,
|
||||||
|
thresholding: int,
|
||||||
user_words,
|
user_words,
|
||||||
user_patterns,
|
user_patterns,
|
||||||
):
|
):
|
||||||
|
"""Generate a hOCR file, which must be converted to PDF."""
|
||||||
prefix = output_hocr.with_suffix('')
|
prefix = output_hocr.with_suffix('')
|
||||||
|
|
||||||
args_tesseract = tess_base_args(languages, engine_mode)
|
args_tesseract = tess_base_args(languages, engine_mode)
|
||||||
@@ -275,6 +320,9 @@ def generate_hocr(
|
|||||||
if pagesegmode is not None:
|
if pagesegmode is not None:
|
||||||
args_tesseract.extend(['--psm', str(pagesegmode)])
|
args_tesseract.extend(['--psm', str(pagesegmode)])
|
||||||
|
|
||||||
|
if thresholding != 0 and has_thresholding():
|
||||||
|
args_tesseract.extend(['-c', f'thresholding_method={thresholding}'])
|
||||||
|
|
||||||
if user_words:
|
if user_words:
|
||||||
args_tesseract.extend(['--user-words', user_words])
|
args_tesseract.extend(['--user-words', user_words])
|
||||||
|
|
||||||
@@ -296,7 +344,7 @@ def generate_hocr(
|
|||||||
_generate_null_hocr(output_hocr, output_text, input_file)
|
_generate_null_hocr(output_hocr, output_text, input_file)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
tesseract_log_output(e.output)
|
tesseract_log_output(e.output)
|
||||||
if b'Image too large' in e.output:
|
if b'Image too large' in e.output or b'Empty page!!' in e.output:
|
||||||
_generate_null_hocr(output_hocr, output_text, input_file)
|
_generate_null_hocr(output_hocr, output_text, input_file)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -326,20 +374,15 @@ def generate_pdf(
|
|||||||
tessconfig: List[str],
|
tessconfig: List[str],
|
||||||
timeout: float,
|
timeout: float,
|
||||||
pagesegmode: int,
|
pagesegmode: int,
|
||||||
|
thresholding: int,
|
||||||
user_words,
|
user_words,
|
||||||
user_patterns,
|
user_patterns,
|
||||||
):
|
):
|
||||||
"""Use Tesseract to render a PDF.
|
"""Generate a PDF using Tesseract's internal PDF generator.
|
||||||
|
|
||||||
input_file -- image to analyze
|
We specifically a text-only PDF which is more suitable for combining with
|
||||||
output_pdf -- file to generate
|
the input page.
|
||||||
output_text -- OCR text file
|
|
||||||
languages -- list of languages to consider
|
|
||||||
engine_mode -- engine mode argument for tess v4
|
|
||||||
tessconfig -- tesseract configuration
|
|
||||||
timeout -- timeout (seconds)
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args_tesseract = tess_base_args(languages, engine_mode)
|
args_tesseract = tess_base_args(languages, engine_mode)
|
||||||
|
|
||||||
if pagesegmode is not None:
|
if pagesegmode is not None:
|
||||||
@@ -347,6 +390,9 @@ def generate_pdf(
|
|||||||
|
|
||||||
args_tesseract.extend(['-c', 'textonly_pdf=1'])
|
args_tesseract.extend(['-c', 'textonly_pdf=1'])
|
||||||
|
|
||||||
|
if thresholding != 0 and has_thresholding():
|
||||||
|
args_tesseract.extend(['-c', f'thresholding_method={thresholding}'])
|
||||||
|
|
||||||
if user_words:
|
if user_words:
|
||||||
args_tesseract.extend(['--user-words', user_words])
|
args_tesseract.extend(['--user-words', user_words])
|
||||||
|
|
||||||
@@ -370,7 +416,7 @@ def generate_pdf(
|
|||||||
use_skip_page(output_pdf, output_text)
|
use_skip_page(output_pdf, output_text)
|
||||||
except CalledProcessError as e:
|
except CalledProcessError as e:
|
||||||
tesseract_log_output(e.output)
|
tesseract_log_output(e.output)
|
||||||
if b'Image too large' in e.output:
|
if b'Image too large' in e.output or b'Empty page!!' in e.output:
|
||||||
use_skip_page(output_pdf, output_text)
|
use_skip_page(output_pdf, output_text)
|
||||||
return
|
return
|
||||||
raise SubprocessOutputError() from e
|
raise SubprocessOutputError() from e
|
||||||
|
|||||||
@@ -13,69 +13,111 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import sys
|
||||||
|
from contextlib import contextmanager
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, STDOUT
|
from subprocess import PIPE, STDOUT
|
||||||
from tempfile import TemporaryDirectory
|
from typing import Iterator, List, Optional, Tuple, Union
|
||||||
from typing import List, Optional, Tuple, Union
|
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
|
||||||
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
|
||||||
from ocrmypdf.subprocess import get_version
|
from ocrmypdf.subprocess import get_version, run
|
||||||
from ocrmypdf.subprocess import run as external_run
|
|
||||||
|
if sys.version_info >= (3, 10):
|
||||||
|
from tempfile import TemporaryDirectory
|
||||||
|
else:
|
||||||
|
from tempfile import TemporaryDirectory as _TemporaryDirectory
|
||||||
|
|
||||||
|
# Consume the ignore_cleanup_errors kwarg in Python 3.9 and older, without acting
|
||||||
|
# on this keyword. Users who need this issue full resolved should upgrade to Python
|
||||||
|
# 3.10.
|
||||||
|
# See: https://github.com/python/cpython/pull/24793
|
||||||
|
|
||||||
|
class TemporaryDirectory(_TemporaryDirectory):
|
||||||
|
def __init__(self, ignore_cleanup_errors=False, **kwargs):
|
||||||
|
super().__init__(**kwargs)
|
||||||
|
|
||||||
|
del _TemporaryDirectory
|
||||||
|
|
||||||
|
|
||||||
|
UNPAPER_IMAGE_PIXEL_LIMIT = 256 * 1024 * 1024
|
||||||
|
|
||||||
DecFloat = Union[Decimal, float]
|
DecFloat = Union[Decimal, float]
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class UnpaperImageTooLargeError(Exception):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
w,
|
||||||
|
h,
|
||||||
|
message="Image with size {}x{} is too large for cleaning with 'unpaper'.",
|
||||||
|
):
|
||||||
|
self.w = w
|
||||||
|
self.h = h
|
||||||
|
self.message = message.format(w, h)
|
||||||
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
def version() -> str:
|
def version() -> str:
|
||||||
return get_version('unpaper')
|
return get_version('unpaper')
|
||||||
|
|
||||||
|
|
||||||
def _setup_unpaper_io(tmpdir: Path, input_file: Path) -> Tuple[Path, Path]:
|
def _convert_image(im: Image.Image) -> Tuple[Image.Image, bool, str]:
|
||||||
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
|
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
|
||||||
with Image.open(input_file) as im:
|
im_modified = False
|
||||||
im_modified = False
|
|
||||||
if im.mode not in SUFFIXES:
|
if im.mode not in SUFFIXES:
|
||||||
log.info("Converting image to other colorspace")
|
log.info("Converting image to other colorspace")
|
||||||
try:
|
|
||||||
if im.mode == 'P' and len(im.getcolors()) == 2:
|
|
||||||
im = im.convert(mode='1')
|
|
||||||
else:
|
|
||||||
im = im.convert(mode='RGB')
|
|
||||||
except OSError as e:
|
|
||||||
raise MissingDependencyError(
|
|
||||||
"Could not convert image with type " + im.mode
|
|
||||||
) from e
|
|
||||||
else:
|
|
||||||
im_modified = True
|
|
||||||
try:
|
try:
|
||||||
suffix = SUFFIXES[im.mode]
|
if im.mode == 'P' and len(im.getcolors()) == 2:
|
||||||
except KeyError:
|
im = im.convert(mode='1')
|
||||||
|
else:
|
||||||
|
im = im.convert(mode='RGB')
|
||||||
|
except OSError as e:
|
||||||
raise MissingDependencyError(
|
raise MissingDependencyError(
|
||||||
"Failed to convert image to a supported format."
|
"Could not convert image with type " + im.mode
|
||||||
) from None
|
) from e
|
||||||
|
|
||||||
if im_modified or input_file.suffix != '.pnm':
|
|
||||||
input_pnm = tmpdir / 'input.pnm'
|
|
||||||
im.save(input_pnm, format='PPM')
|
|
||||||
else:
|
else:
|
||||||
# No changes, PNG input, just use the file we already have
|
im_modified = True
|
||||||
input_pnm = input_file
|
try:
|
||||||
output_pnm = tmpdir / f'output{suffix}'
|
suffix = SUFFIXES[im.mode]
|
||||||
return input_pnm, output_pnm
|
except KeyError:
|
||||||
|
raise MissingDependencyError(
|
||||||
|
"Failed to convert image to a supported format."
|
||||||
|
) from None
|
||||||
|
return im, im_modified, suffix
|
||||||
|
|
||||||
|
|
||||||
def run(
|
@contextmanager
|
||||||
|
def _setup_unpaper_io(input_file: Path) -> Iterator[Tuple[Path, Path, Path]]:
|
||||||
|
with Image.open(input_file) as im:
|
||||||
|
if im.width * im.height >= UNPAPER_IMAGE_PIXEL_LIMIT:
|
||||||
|
raise UnpaperImageTooLargeError(w=im.width, h=im.height)
|
||||||
|
im, im_modified, suffix = _convert_image(im)
|
||||||
|
|
||||||
|
with TemporaryDirectory(ignore_cleanup_errors=True) as tmpdir:
|
||||||
|
tmppath = Path(tmpdir)
|
||||||
|
if im_modified or input_file.suffix != '.pnm':
|
||||||
|
input_pnm = tmppath / 'input.pnm'
|
||||||
|
im.save(input_pnm, format='PPM')
|
||||||
|
else:
|
||||||
|
# No changes, PNG input, just use the file we already have
|
||||||
|
input_pnm = input_file
|
||||||
|
|
||||||
|
output_pnm = tmppath / f'output{suffix}'
|
||||||
|
yield input_pnm, output_pnm, tmppath
|
||||||
|
|
||||||
|
|
||||||
|
def run_unpaper(
|
||||||
input_file: Path, output_file: Path, *, dpi: DecFloat, mode_args: List[str]
|
input_file: Path, output_file: Path, *, dpi: DecFloat, mode_args: List[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
args_unpaper = ['unpaper', '-v', '--dpi', str(round(dpi, 6))] + mode_args
|
args_unpaper = ['unpaper', '-v', '--dpi', str(round(dpi, 6))] + mode_args
|
||||||
|
|
||||||
with TemporaryDirectory() as tmpdir:
|
with _setup_unpaper_io(input_file) as (input_pnm, output_pnm, tmpdir):
|
||||||
input_pnm, output_pnm = _setup_unpaper_io(Path(tmpdir), input_file)
|
|
||||||
|
|
||||||
# To prevent any shenanigans from accepting arbitrary parameters in
|
# To prevent any shenanigans from accepting arbitrary parameters in
|
||||||
# --unpaper-args, we:
|
# --unpaper-args, we:
|
||||||
# 1) run with cwd set to a tmpdir with only unpaper's files
|
# 1) run with cwd set to a tmpdir with only unpaper's files
|
||||||
@@ -84,7 +126,7 @@ def run(
|
|||||||
# This should ensure that a user cannot clobber some other file with
|
# This should ensure that a user cannot clobber some other file with
|
||||||
# their unpaper arguments (whether intentionally or otherwise)
|
# their unpaper arguments (whether intentionally or otherwise)
|
||||||
args_unpaper.extend([os.fspath(input_pnm), os.fspath(output_pnm)])
|
args_unpaper.extend([os.fspath(input_pnm), os.fspath(output_pnm)])
|
||||||
external_run(
|
run(
|
||||||
args_unpaper,
|
args_unpaper,
|
||||||
close_fds=True,
|
close_fds=True,
|
||||||
check=True,
|
check=True,
|
||||||
@@ -117,7 +159,7 @@ def clean(
|
|||||||
*,
|
*,
|
||||||
dpi: DecFloat,
|
dpi: DecFloat,
|
||||||
unpaper_args: Optional[List[str]] = None,
|
unpaper_args: Optional[List[str]] = None,
|
||||||
):
|
) -> Path:
|
||||||
default_args = [
|
default_args = [
|
||||||
'--layout',
|
'--layout',
|
||||||
'none',
|
'none',
|
||||||
@@ -131,4 +173,9 @@ def clean(
|
|||||||
]
|
]
|
||||||
if not unpaper_args:
|
if not unpaper_args:
|
||||||
unpaper_args = default_args
|
unpaper_args = default_args
|
||||||
run(input_file, output_file, dpi=dpi, mode_args=unpaper_args)
|
try:
|
||||||
|
run_unpaper(input_file, output_file, dpi=dpi, mode_args=unpaper_args)
|
||||||
|
return output_file
|
||||||
|
except UnpaperImageTooLargeError as e:
|
||||||
|
log.warning(str(e))
|
||||||
|
return input_file
|
||||||
|
|||||||
@@ -32,12 +32,19 @@ from ocrmypdf.exceptions import (
|
|||||||
PriorOcrFoundError,
|
PriorOcrFoundError,
|
||||||
UnsupportedImageFormatError,
|
UnsupportedImageFormatError,
|
||||||
)
|
)
|
||||||
from ocrmypdf.helpers import Resolution, safe_symlink
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution, safe_symlink
|
||||||
from ocrmypdf.hocrtransform import HocrTransform
|
from ocrmypdf.hocrtransform import HocrTransform
|
||||||
from ocrmypdf.optimize import optimize
|
from ocrmypdf.optimize import optimize
|
||||||
from ocrmypdf.pdfa import generate_pdfa_ps
|
from ocrmypdf.pdfa import generate_pdfa_ps
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
BICUBIC = Image.Resampling.BICUBIC # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
BICUBIC = Image.BICUBIC # type: ignore
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
VECTOR_PAGE_DPI = 400
|
VECTOR_PAGE_DPI = 400
|
||||||
@@ -98,8 +105,8 @@ def triage_image_file(input_file, output_file, options):
|
|||||||
img2pdf.convert(
|
img2pdf.convert(
|
||||||
os.fspath(input_file),
|
os.fspath(input_file),
|
||||||
layout_fun=layout_fun,
|
layout_fun=layout_fun,
|
||||||
with_pdfrw=False,
|
|
||||||
outputstream=outf,
|
outputstream=outf,
|
||||||
|
**IMG2PDF_KWARGS,
|
||||||
)
|
)
|
||||||
log.info("Successfully converted to PDF, processing...")
|
log.info("Successfully converted to PDF, processing...")
|
||||||
except img2pdf.ImageOpenError as e:
|
except img2pdf.ImageOpenError as e:
|
||||||
@@ -484,7 +491,9 @@ def preprocess_deskew(input_file: Path, page_context: PageContext):
|
|||||||
# According to Pillow docs, .rotate() will automatically use Image.NEAREST
|
# According to Pillow docs, .rotate() will automatically use Image.NEAREST
|
||||||
# resampling if image is mode '1' or 'P'
|
# resampling if image is mode '1' or 'P'
|
||||||
deskewed = im.rotate(
|
deskewed = im.rotate(
|
||||||
deskew_angle_degrees, resample=Image.BICUBIC, fillcolor='white'
|
deskew_angle_degrees,
|
||||||
|
resample=BICUBIC,
|
||||||
|
fillcolor=ImageColor.getcolor('white', mode=im.mode),
|
||||||
)
|
)
|
||||||
deskewed.save(output_file, dpi=dpi)
|
deskewed.save(output_file, dpi=dpi)
|
||||||
|
|
||||||
@@ -494,13 +503,12 @@ def preprocess_deskew(input_file: Path, page_context: PageContext):
|
|||||||
def preprocess_clean(input_file: Path, page_context: PageContext):
|
def preprocess_clean(input_file: Path, page_context: PageContext):
|
||||||
output_file = page_context.get_path('pp_clean.png')
|
output_file = page_context.get_path('pp_clean.png')
|
||||||
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
|
||||||
unpaper.clean(
|
return unpaper.clean(
|
||||||
input_file,
|
input_file,
|
||||||
output_file,
|
output_file,
|
||||||
dpi=dpi.x,
|
dpi=dpi.x,
|
||||||
unpaper_args=page_context.options.unpaper_args,
|
unpaper_args=page_context.options.unpaper_args,
|
||||||
)
|
)
|
||||||
return output_file
|
|
||||||
|
|
||||||
|
|
||||||
def create_ocr_image(image: Path, page_context: PageContext):
|
def create_ocr_image(image: Path, page_context: PageContext):
|
||||||
@@ -613,7 +621,7 @@ def create_pdf_page_from_image(
|
|||||||
|
|
||||||
layout_fun = img2pdf.get_layout_fun(pagesize)
|
layout_fun = img2pdf.get_layout_fun(pagesize)
|
||||||
img2pdf.convert(
|
img2pdf.convert(
|
||||||
imfile, with_pdfrw=False, layout_fun=layout_fun, outputstream=pdf
|
imfile, layout_fun=layout_fun, outputstream=pdf, **IMG2PDF_KWARGS
|
||||||
)
|
)
|
||||||
log.debug('convert done')
|
log.debug('convert done')
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import importlib.util
|
|||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import List, Tuple, Union
|
from typing import List, Sequence, Tuple, Union
|
||||||
|
|
||||||
import pluggy
|
import pluggy
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ def get_plugin_manager(plugins: List[Union[str, Path]], builtins=True):
|
|||||||
|
|
||||||
|
|
||||||
def get_parser_options_plugins(
|
def get_parser_options_plugins(
|
||||||
args,
|
args: Sequence[str],
|
||||||
) -> Tuple[argparse.ArgumentParser, argparse.Namespace, pluggy.PluginManager]:
|
) -> Tuple[argparse.ArgumentParser, argparse.Namespace, pluggy.PluginManager]:
|
||||||
pre_options, _unused = plugins_only_parser.parse_known_args(args=args)
|
pre_options, _unused = plugins_only_parser.parse_known_args(args=args)
|
||||||
plugin_manager = get_plugin_manager(pre_options.plugins)
|
plugin_manager = get_plugin_manager(pre_options.plugins)
|
||||||
|
|||||||
+31
-9
@@ -5,15 +5,18 @@
|
|||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
|
||||||
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import logging.handlers
|
import logging.handlers
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from concurrent.futures.process import BrokenProcessPool
|
||||||
|
from concurrent.futures.thread import BrokenThreadPool
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
from typing import List, NamedTuple, Optional, Tuple
|
from typing import List, NamedTuple, Optional, Tuple, cast
|
||||||
|
|
||||||
import PIL
|
import PIL
|
||||||
|
|
||||||
@@ -46,7 +49,7 @@ from ocrmypdf._pipeline import (
|
|||||||
triage,
|
triage,
|
||||||
validate_pdfinfo_options,
|
validate_pdfinfo_options,
|
||||||
)
|
)
|
||||||
from ocrmypdf._plugin_manager import get_plugin_manager
|
from ocrmypdf._plugin_manager import OcrmypdfPluginManager, get_plugin_manager
|
||||||
from ocrmypdf._validation import (
|
from ocrmypdf._validation import (
|
||||||
check_requested_output_file,
|
check_requested_output_file,
|
||||||
create_input_file,
|
create_input_file,
|
||||||
@@ -165,7 +168,7 @@ def make_intermediate_images(
|
|||||||
return ocr_image, preprocess_out
|
return ocr_image, preprocess_out
|
||||||
|
|
||||||
|
|
||||||
def exec_page_sync(page_context: PageContext):
|
def exec_page_sync(page_context: PageContext) -> PageResult:
|
||||||
options = page_context.options
|
options = page_context.options
|
||||||
tls.pageno = page_context.pageno + 1
|
tls.pageno = page_context.pageno + 1
|
||||||
|
|
||||||
@@ -223,7 +226,7 @@ def exec_page_sync(page_context: PageContext):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def post_process(pdf_file, context: PdfContext, executor: Executor):
|
def post_process(pdf_file: Path, context: PdfContext, executor: Executor) -> Path:
|
||||||
pdf_out = pdf_file
|
pdf_out = pdf_file
|
||||||
if context.options.output_type.startswith('pdfa'):
|
if context.options.output_type.startswith('pdfa'):
|
||||||
ps_stub_out = generate_postscript_stub(context)
|
ps_stub_out = generate_postscript_stub(context)
|
||||||
@@ -233,7 +236,7 @@ def post_process(pdf_file, context: PdfContext, executor: Executor):
|
|||||||
return optimize_pdf(pdf_out, context, executor)
|
return optimize_pdf(pdf_out, context, executor)
|
||||||
|
|
||||||
|
|
||||||
def worker_init(max_pixels: int):
|
def worker_init(max_pixels: int) -> None:
|
||||||
# In Windows, child process will not inherit our change to this value in
|
# In Windows, child process will not inherit our change to this value in
|
||||||
# the parent process, so ensure workers get it set. Not needed when running
|
# the parent process, so ensure workers get it set. Not needed when running
|
||||||
# threaded, but harmless to set again.
|
# threaded, but harmless to set again.
|
||||||
@@ -241,7 +244,7 @@ def worker_init(max_pixels: int):
|
|||||||
pikepdf_enable_mmap()
|
pikepdf_enable_mmap()
|
||||||
|
|
||||||
|
|
||||||
def exec_concurrent(context: PdfContext, executor: Executor):
|
def exec_concurrent(context: PdfContext, executor: Executor) -> None:
|
||||||
"""Execute the pipeline concurrently"""
|
"""Execute the pipeline concurrently"""
|
||||||
|
|
||||||
# Run exec_page_sync on every page context
|
# Run exec_page_sync on every page context
|
||||||
@@ -302,7 +305,9 @@ def exec_concurrent(context: PdfContext, executor: Executor):
|
|||||||
copy_final(pdf, options.output_file, context)
|
copy_final(pdf, options.output_file, context)
|
||||||
|
|
||||||
|
|
||||||
def configure_debug_logging(log_filename: Path, prefix: str = ''):
|
def configure_debug_logging(
|
||||||
|
log_filename: Path, prefix: str = ''
|
||||||
|
) -> logging.FileHandler:
|
||||||
"""
|
"""
|
||||||
Create a debug log file at a specified location.
|
Create a debug log file at a specified location.
|
||||||
|
|
||||||
@@ -321,7 +326,12 @@ def configure_debug_logging(log_filename: Path, prefix: str = ''):
|
|||||||
return log_file_handler
|
return log_file_handler
|
||||||
|
|
||||||
|
|
||||||
def run_pipeline(options, *, plugin_manager, api=False):
|
def run_pipeline(
|
||||||
|
options: argparse.Namespace,
|
||||||
|
*,
|
||||||
|
plugin_manager: Optional[OcrmypdfPluginManager],
|
||||||
|
api: bool = False,
|
||||||
|
) -> ExitCode:
|
||||||
# Any changes to options will not take effect for options that are already
|
# Any changes to options will not take effect for options that are already
|
||||||
# bound to function parameters in the pipeline. (For example
|
# bound to function parameters in the pipeline. (For example
|
||||||
# options.input_file, options.pdf_renderer are already bound.)
|
# options.input_file, options.pdf_renderer are already bound.)
|
||||||
@@ -380,7 +390,7 @@ def run_pipeline(options, *, plugin_manager, api=False):
|
|||||||
hasattr(options.output_file, 'writable') and options.output_file.writable()
|
hasattr(options.output_file, 'writable') and options.output_file.writable()
|
||||||
):
|
):
|
||||||
log.info("Output written to stream")
|
log.info("Output written to stream")
|
||||||
elif samefile(options.output_file, os.devnull):
|
elif samefile(options.output_file, Path(os.devnull)):
|
||||||
pass # Say nothing when sending to dev null
|
pass # Say nothing when sending to dev null
|
||||||
else:
|
else:
|
||||||
if options.output_type.startswith('pdfa'):
|
if options.output_type.startswith('pdfa'):
|
||||||
@@ -407,6 +417,7 @@ def run_pipeline(options, *, plugin_manager, api=False):
|
|||||||
log.error("KeyboardInterrupt")
|
log.error("KeyboardInterrupt")
|
||||||
return ExitCode.ctrl_c
|
return ExitCode.ctrl_c
|
||||||
except (ExitCodeException if not api else NeverRaise) as e:
|
except (ExitCodeException if not api else NeverRaise) as e:
|
||||||
|
e = cast(ExitCodeException, e)
|
||||||
if options.verbose >= 1:
|
if options.verbose >= 1:
|
||||||
log.exception("ExitCodeException")
|
log.exception("ExitCodeException")
|
||||||
elif str(e):
|
elif str(e):
|
||||||
@@ -421,6 +432,17 @@ def run_pipeline(options, *, plugin_manager, api=False):
|
|||||||
"image pixel limit."
|
"image pixel limit."
|
||||||
)
|
)
|
||||||
return ExitCode.other_error
|
return ExitCode.other_error
|
||||||
|
except (
|
||||||
|
BrokenProcessPool if not api else NeverRaise,
|
||||||
|
BrokenThreadPool if not api else NeverRaise,
|
||||||
|
) as e:
|
||||||
|
log.exception(
|
||||||
|
"A worker process was terminated unexpectedly. This is known to occur if "
|
||||||
|
"processing your file takes all available swap space and RAM. It may "
|
||||||
|
"help to try again with a smaller number of jobs, using the --jobs "
|
||||||
|
"argument."
|
||||||
|
)
|
||||||
|
return ExitCode.child_process_error
|
||||||
except (Exception if not api else NeverRaise): # pylint: disable=broad-except
|
except (Exception if not api else NeverRaise): # pylint: disable=broad-except
|
||||||
log.exception("An exception occurred while executing the pipeline")
|
log.exception("An exception occurred while executing the pipeline")
|
||||||
return ExitCode.other_error
|
return ExitCode.other_error
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def check_platform():
|
def check_platform():
|
||||||
if os.name == 'nt' and sys.maxsize <= 2 ** 32: # pragma: no cover
|
if os.name == 'nt' and sys.maxsize <= 2**32: # pragma: no cover
|
||||||
# 32-bit interpreter on Windows
|
# 32-bit interpreter on Windows
|
||||||
log.error(
|
log.error(
|
||||||
"You are running OCRmyPDF in a 32-bit (x86) Python interpreter."
|
"You are running OCRmyPDF in a 32-bit (x86) Python interpreter."
|
||||||
@@ -78,11 +78,11 @@ def check_options_output(options):
|
|||||||
"`--pdf-renderer auto` (the default) to avoid this issue."
|
"`--pdf-renderer auto` (the default) to avoid this issue."
|
||||||
)
|
)
|
||||||
|
|
||||||
if options.output_type == 'none' and options.output_file != os.devnull:
|
if options.output_type == 'none' and options.output_file not in (os.devnull, '-'):
|
||||||
raise BadArgsError(
|
raise BadArgsError(
|
||||||
"Since you specified `--pdf-renderer none`, the output file "
|
"Since you specified `--output-type none`, the output file "
|
||||||
f"{options.output_file} cannot be produced. Set the output file to "
|
f"{options.output_file} cannot be produced. Set the output file to "
|
||||||
f"{os.devnull} to suppress this message."
|
f"`-` to suppress this message."
|
||||||
)
|
)
|
||||||
|
|
||||||
lossless_reconstruction = False
|
lossless_reconstruction = False
|
||||||
|
|||||||
+2
-1
@@ -242,6 +242,7 @@ def ocr( # pylint: disable=unused-argument
|
|||||||
tesseract_config: Iterable[str] = None,
|
tesseract_config: Iterable[str] = None,
|
||||||
tesseract_pagesegmode: int = None,
|
tesseract_pagesegmode: int = None,
|
||||||
tesseract_oem: int = None,
|
tesseract_oem: int = None,
|
||||||
|
tesseract_thresholding: int = None,
|
||||||
pdf_renderer=None,
|
pdf_renderer=None,
|
||||||
tesseract_timeout: float = None,
|
tesseract_timeout: float = None,
|
||||||
rotate_pages_threshold: float = None,
|
rotate_pages_threshold: float = None,
|
||||||
@@ -294,7 +295,7 @@ def ocr( # pylint: disable=unused-argument
|
|||||||
text already, and settings did not tell us to proceed.
|
text already, and settings did not tell us to proceed.
|
||||||
ocrmypdf.InputFileError: Any other problem with the input file.
|
ocrmypdf.InputFileError: Any other problem with the input file.
|
||||||
ocrmypdf.SubprocessOutputError: Any error related to executing a subprocess.
|
ocrmypdf.SubprocessOutputError: Any error related to executing a subprocess.
|
||||||
ocrmypdf.EncryptedPdfERror: If the input PDF is encrypted (password protected).
|
ocrmypdf.EncryptedPdfError: If the input PDF is encrypted (password protected).
|
||||||
OCRmyPDF does not remove passwords.
|
OCRmyPDF does not remove passwords.
|
||||||
ocrmypdf.TesseractConfigError: If Tesseract reported its configuration was not
|
ocrmypdf.TesseractConfigError: If Tesseract reported its configuration was not
|
||||||
valid.
|
valid.
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import queue
|
|||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor, as_completed
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from multiprocessing.pool import Pool, ThreadPool
|
from multiprocessing.pool import Pool, ThreadPool
|
||||||
from typing import Callable, Iterable, Type, Union
|
from typing import Callable, Iterable, Type, Union
|
||||||
@@ -30,7 +31,7 @@ from ocrmypdf._logging import TqdmConsole
|
|||||||
from ocrmypdf.exceptions import InputFileError
|
from ocrmypdf.exceptions import InputFileError
|
||||||
from ocrmypdf.helpers import remove_all_log_handlers
|
from ocrmypdf.helpers import remove_all_log_handlers
|
||||||
|
|
||||||
ProcessPool = Pool
|
FuturesExecutorClass = Union[Type[ThreadPoolExecutor], Type[ProcessPoolExecutor]]
|
||||||
Queue = Union[multiprocessing.Queue, queue.Queue]
|
Queue = Union[multiprocessing.Queue, queue.Queue]
|
||||||
UserInit = Callable[[], None]
|
UserInit = Callable[[], None]
|
||||||
WorkerInit = Callable[[Queue, UserInit, int], None]
|
WorkerInit = Callable[[Queue, UserInit, int], None]
|
||||||
@@ -110,11 +111,11 @@ class StandardExecutor(Executor):
|
|||||||
):
|
):
|
||||||
if use_threads:
|
if use_threads:
|
||||||
log_queue: Queue = queue.Queue(-1)
|
log_queue: Queue = queue.Queue(-1)
|
||||||
pool_class: Type[Pool] = ThreadPool
|
executor_class: FuturesExecutorClass = ThreadPoolExecutor
|
||||||
initializer: WorkerInit = thread_init
|
initializer: WorkerInit = thread_init
|
||||||
else:
|
else:
|
||||||
log_queue = multiprocessing.Queue(-1)
|
log_queue = multiprocessing.Queue(-1)
|
||||||
pool_class = ProcessPool
|
executor_class = ProcessPoolExecutor
|
||||||
initializer = process_init
|
initializer = process_init
|
||||||
|
|
||||||
# Regardless of whether we use_threads for worker processes, the log_listener
|
# Regardless of whether we use_threads for worker processes, the log_listener
|
||||||
@@ -123,39 +124,35 @@ class StandardExecutor(Executor):
|
|||||||
listener = threading.Thread(target=log_listener, args=(log_queue,))
|
listener = threading.Thread(target=log_listener, args=(log_queue,))
|
||||||
listener.start()
|
listener.start()
|
||||||
|
|
||||||
with self.pbar_class(**tqdm_kwargs) as pbar:
|
with self.pbar_class(**tqdm_kwargs) as pbar, executor_class(
|
||||||
pool = pool_class(
|
max_workers=max_workers,
|
||||||
processes=max_workers,
|
initializer=initializer,
|
||||||
initializer=initializer,
|
initargs=(log_queue, worker_initializer, logging.getLogger("").level),
|
||||||
initargs=(log_queue, worker_initializer, logging.getLogger("").level),
|
) as executor:
|
||||||
)
|
futures = [executor.submit(task, args) for args in task_arguments]
|
||||||
try:
|
try:
|
||||||
results = pool.imap_unordered(task, task_arguments)
|
for future in as_completed(futures):
|
||||||
for result in results:
|
result = future.result()
|
||||||
if task_finished:
|
task_finished(result, pbar)
|
||||||
task_finished(result, pbar)
|
|
||||||
else:
|
|
||||||
pbar.update()
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
# Terminate pool so we exit instantly
|
# Terminate pool so we exit instantly
|
||||||
pool.terminate()
|
executor.shutdown(wait=False, cancel_futures=True)
|
||||||
# Don't try listener.join() here, will deadlock
|
|
||||||
raise
|
raise
|
||||||
except Exception:
|
except Exception:
|
||||||
if not os.environ.get("PYTEST_CURRENT_TEST", ""):
|
if not os.environ.get("PYTEST_CURRENT_TEST", ""):
|
||||||
# Unless inside pytest, exit immediately because no one wants
|
# Normally we shutdown without waiting for other child workers
|
||||||
# to wait for child processes to finalize results that will be
|
# on error, because there is no point in waiting for them. Their
|
||||||
# thrown away. Inside pytest, we want child processes to exit
|
# results will be discard. But if the condition above is True,
|
||||||
# cleanly so that they output an error messages or coverage data
|
# then we are running in pytest, and we want everything to exit
|
||||||
# we need from them.
|
# as cleanly as possible so that we get good error messages.
|
||||||
pool.terminate()
|
executor.shutdown(wait=False, cancel_futures=True)
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
# Terminate log listener
|
# Terminate log listener
|
||||||
log_queue.put_nowait(None)
|
log_queue.put_nowait(None)
|
||||||
pool.close()
|
|
||||||
pool.join()
|
|
||||||
|
|
||||||
|
# When the above succeeds, wait for the listener thread to exit. (If
|
||||||
|
# an exception occurs, we don't try to join, in case it deadlocks.)
|
||||||
listener.join()
|
listener.join()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def check_options(options):
|
def check_options(options):
|
||||||
gs_version = ghostscript.version()
|
|
||||||
check_external_program(
|
check_external_program(
|
||||||
program='gs',
|
program='gs',
|
||||||
package='ghostscript',
|
package='ghostscript',
|
||||||
version_checker=gs_version,
|
version_checker=ghostscript.version,
|
||||||
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
|
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
|
||||||
)
|
)
|
||||||
|
gs_version = ghostscript.version()
|
||||||
if gs_version in ('9.24', '9.51'):
|
if gs_version in ('9.24', '9.51'):
|
||||||
raise MissingDependencyError(
|
raise MissingDependencyError(
|
||||||
f"Ghostscript {gs_version} contains serious regressions and is not "
|
f"Ghostscript {gs_version} contains serious regressions and is not "
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import os
|
|||||||
|
|
||||||
from ocrmypdf import hookimpl
|
from ocrmypdf import hookimpl
|
||||||
from ocrmypdf._exec import tesseract
|
from ocrmypdf._exec import tesseract
|
||||||
from ocrmypdf.cli import numeric
|
from ocrmypdf.cli import numeric, str_to_int
|
||||||
from ocrmypdf.helpers import clamp
|
from ocrmypdf.helpers import clamp
|
||||||
from ocrmypdf.pluginspec import OcrEngine
|
from ocrmypdf.pluginspec import OcrEngine
|
||||||
from ocrmypdf.subprocess import check_external_program
|
from ocrmypdf.subprocess import check_external_program
|
||||||
@@ -43,13 +43,27 @@ def add_options(parser):
|
|||||||
metavar='MODE',
|
metavar='MODE',
|
||||||
choices=range(0, 4),
|
choices=range(0, 4),
|
||||||
help=(
|
help=(
|
||||||
"Set Tesseract 4.0 OCR engine mode: "
|
"Set Tesseract 4.0+ OCR engine mode: "
|
||||||
"0 - original Tesseract only; "
|
"0 - original Tesseract only; "
|
||||||
"1 - neural nets LSTM only; "
|
"1 - neural nets LSTM only; "
|
||||||
"2 - Tesseract + LSTM; "
|
"2 - Tesseract + LSTM; "
|
||||||
"3 - default."
|
"3 - default."
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
tess.add_argument(
|
||||||
|
'--tesseract-thresholding',
|
||||||
|
action='store',
|
||||||
|
type=str_to_int(tesseract.TESSERACT_THRESHOLDING_METHODS),
|
||||||
|
default='auto',
|
||||||
|
metavar='METHOD',
|
||||||
|
help=(
|
||||||
|
"Set Tesseract 5.0+ input image thresholding mode. This may improve OCR "
|
||||||
|
"results on low quality images or those that contain high constrast color. "
|
||||||
|
"legacy-otsu is the Tesseract default; adaptive-otsu is an improved Otsu "
|
||||||
|
"algorithm with improved sort for background color changes; sauvola is "
|
||||||
|
"based on local standard deviation."
|
||||||
|
),
|
||||||
|
)
|
||||||
tess.add_argument(
|
tess.add_argument(
|
||||||
'--tesseract-timeout',
|
'--tesseract-timeout',
|
||||||
default=180.0,
|
default=180.0,
|
||||||
@@ -89,8 +103,14 @@ def check_options(options):
|
|||||||
|
|
||||||
if not tesseract.has_user_words() and (options.user_words or options.user_patterns):
|
if not tesseract.has_user_words() and (options.user_words or options.user_patterns):
|
||||||
log.warning(
|
log.warning(
|
||||||
"Tesseract 4.0 ignores --user-words and --user-patterns, so these "
|
"Tesseract 4.0 (which you have installed) ignores --user-words and "
|
||||||
"arguments have no effect."
|
"--user-patterns, so these arguments have no effect."
|
||||||
|
)
|
||||||
|
if not tesseract.has_thresholding() and options.tesseract_thresholding != 0:
|
||||||
|
log.warning(
|
||||||
|
"The installed version of Tesseract does not support changes to its "
|
||||||
|
"thresholding method. The --tesseract-threshold argument will be "
|
||||||
|
"ignored."
|
||||||
)
|
)
|
||||||
if options.tesseract_pagesegmode in (0, 2):
|
if options.tesseract_pagesegmode in (0, 2):
|
||||||
log.warning(
|
log.warning(
|
||||||
@@ -162,6 +182,7 @@ class TesseractOcrEngine(OcrEngine):
|
|||||||
tessconfig=options.tesseract_config,
|
tessconfig=options.tesseract_config,
|
||||||
timeout=options.tesseract_timeout,
|
timeout=options.tesseract_timeout,
|
||||||
pagesegmode=options.tesseract_pagesegmode,
|
pagesegmode=options.tesseract_pagesegmode,
|
||||||
|
thresholding=options.tesseract_thresholding,
|
||||||
user_words=options.user_words,
|
user_words=options.user_words,
|
||||||
user_patterns=options.user_patterns,
|
user_patterns=options.user_patterns,
|
||||||
)
|
)
|
||||||
@@ -177,6 +198,7 @@ class TesseractOcrEngine(OcrEngine):
|
|||||||
tessconfig=options.tesseract_config,
|
tessconfig=options.tesseract_config,
|
||||||
timeout=options.tesseract_timeout,
|
timeout=options.tesseract_timeout,
|
||||||
pagesegmode=options.tesseract_pagesegmode,
|
pagesegmode=options.tesseract_pagesegmode,
|
||||||
|
thresholding=options.tesseract_thresholding,
|
||||||
user_words=options.user_words,
|
user_words=options.user_words,
|
||||||
user_patterns=options.user_patterns,
|
user_patterns=options.user_patterns,
|
||||||
)
|
)
|
||||||
|
|||||||
+29
-8
@@ -6,12 +6,12 @@
|
|||||||
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
from typing import Any, Callable, Optional, TypeVar
|
from typing import Any, Callable, Mapping, Optional, TypeVar
|
||||||
|
|
||||||
from ocrmypdf._version import PROGRAM_NAME as _PROGRAM_NAME
|
from ocrmypdf._version import PROGRAM_NAME as _PROGRAM_NAME
|
||||||
from ocrmypdf._version import __version__ as _VERSION
|
from ocrmypdf._version import __version__ as _VERSION
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T', int, float)
|
||||||
|
|
||||||
|
|
||||||
def numeric(
|
def numeric(
|
||||||
@@ -21,17 +21,32 @@ def numeric(
|
|||||||
min_ = basetype(min_) if min_ is not None else None
|
min_ = basetype(min_) if min_ is not None else None
|
||||||
max_ = basetype(max_) if max_ is not None else None
|
max_ = basetype(max_) if max_ is not None else None
|
||||||
|
|
||||||
def _numeric(string):
|
def _numeric(s: str) -> T:
|
||||||
value = basetype(string)
|
value = basetype(s)
|
||||||
if (min_ is not None and value < min_) or (max_ is not None and value > max_):
|
if (min_ is not None and value < min_) or (max_ is not None and value > max_):
|
||||||
msg = f"{string!r} not in valid range {(min_, max_)!r}"
|
raise argparse.ArgumentTypeError(
|
||||||
raise argparse.ArgumentTypeError(msg)
|
f"{s!r} not in valid range {(min_, max_)!r}"
|
||||||
|
)
|
||||||
return value
|
return value
|
||||||
|
|
||||||
_numeric.__name__ = basetype.__name__
|
_numeric.__name__ = basetype.__name__
|
||||||
return _numeric
|
return _numeric
|
||||||
|
|
||||||
|
|
||||||
|
def str_to_int(mapping: Mapping[str, int]):
|
||||||
|
"""Accept text on command line and convert to integer."""
|
||||||
|
|
||||||
|
def _str_to_int(s: str) -> int:
|
||||||
|
try:
|
||||||
|
return mapping[s]
|
||||||
|
except KeyError:
|
||||||
|
raise argparse.ArgumentTypeError(
|
||||||
|
f"{s!r} must be one of: {', '.join(mapping.keys())}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return _str_to_int
|
||||||
|
|
||||||
|
|
||||||
class ArgumentParser(argparse.ArgumentParser):
|
class ArgumentParser(argparse.ArgumentParser):
|
||||||
"""Override parser's default behavior of calling sys.exit()
|
"""Override parser's default behavior of calling sys.exit()
|
||||||
|
|
||||||
@@ -127,7 +142,7 @@ Online documentation is located at:
|
|||||||
'output_file',
|
'output_file',
|
||||||
metavar="output_pdf",
|
metavar="output_pdf",
|
||||||
help="Output searchable PDF file (or '-' to write to standard output). "
|
help="Output searchable PDF file (or '-' to write to standard output). "
|
||||||
"Existing files will be ovewritten. If same as input file, the "
|
"Existing files will be overwritten. If same as input file, the "
|
||||||
"input file will be updated only if processing is successful.",
|
"input file will be updated only if processing is successful.",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
@@ -213,7 +228,13 @@ Online documentation is located at:
|
|||||||
help=argparse.SUPPRESS,
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
jobcontrol.add_argument(
|
jobcontrol.add_argument(
|
||||||
'--use-threads', action='store_true', help=argparse.SUPPRESS
|
'--use-threads', action='store_true', default=True, help=argparse.SUPPRESS
|
||||||
|
)
|
||||||
|
jobcontrol.add_argument(
|
||||||
|
'--no-use-threads',
|
||||||
|
action='store_false',
|
||||||
|
dest='use_threads',
|
||||||
|
help=argparse.SUPPRESS,
|
||||||
)
|
)
|
||||||
|
|
||||||
metadata = parser.add_argument_group(
|
metadata = parser.add_argument_group(
|
||||||
|
|||||||
+20
-1
@@ -19,10 +19,21 @@ from math import isclose, isfinite
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Sequence
|
from typing import Any, Sequence
|
||||||
|
|
||||||
|
import img2pdf
|
||||||
import pikepdf
|
import pikepdf
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
if Version(img2pdf.__version__) < Version('0.4.0'):
|
||||||
|
IMG2PDF_KWARGS = dict(without_pdfw=True)
|
||||||
|
elif Version(img2pdf.__version__) < Version('0.4.3'):
|
||||||
|
IMG2PDF_KWARGS = dict(engine=img2pdf.Engine.pikepdf)
|
||||||
|
else:
|
||||||
|
IMG2PDF_KWARGS = dict(
|
||||||
|
engine=img2pdf.Engine.pikepdf, rotation=img2pdf.Rotation.ifvalid
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Resolution(namedtuple('Resolution', ('x', 'y'))):
|
class Resolution(namedtuple('Resolution', ('x', 'y'))):
|
||||||
"""The number of pixels per inch in each 2D direction.
|
"""The number of pixels per inch in each 2D direction.
|
||||||
@@ -209,11 +220,19 @@ def check_pdf(input_file: Path) -> bool:
|
|||||||
else:
|
else:
|
||||||
with pdf:
|
with pdf:
|
||||||
messages = pdf.check()
|
messages = pdf.check()
|
||||||
|
success = True
|
||||||
for msg in messages:
|
for msg in messages:
|
||||||
if 'error' in msg.lower():
|
if 'error' in msg.lower():
|
||||||
log.error(msg)
|
log.error(msg)
|
||||||
|
success = False
|
||||||
|
elif (
|
||||||
|
"/DecodeParms: operation for dictionary attempted on object "
|
||||||
|
"of type null" in msg
|
||||||
|
):
|
||||||
|
pass # Ignore/spurious warning
|
||||||
else:
|
else:
|
||||||
log.warning(msg)
|
log.warning(msg)
|
||||||
|
success = False
|
||||||
|
|
||||||
sio = StringIO()
|
sio = StringIO()
|
||||||
linearize_msgs = ''
|
linearize_msgs = ''
|
||||||
@@ -228,7 +247,7 @@ def check_pdf(input_file: Path) -> bool:
|
|||||||
if linearize_msgs:
|
if linearize_msgs:
|
||||||
log.warning(linearize_msgs)
|
log.warning(linearize_msgs)
|
||||||
|
|
||||||
if not messages and not linearize_msgs:
|
if success and not linearize_msgs:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
+112
-31
@@ -8,6 +8,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
|
import threading
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from os import fspath
|
from os import fspath
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -23,6 +24,7 @@ from typing import (
|
|||||||
Sequence,
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
)
|
)
|
||||||
|
from zlib import compress
|
||||||
|
|
||||||
import img2pdf
|
import img2pdf
|
||||||
from pikepdf import (
|
from pikepdf import (
|
||||||
@@ -31,6 +33,7 @@ from pikepdf import (
|
|||||||
Object,
|
Object,
|
||||||
ObjectStreamMode,
|
ObjectStreamMode,
|
||||||
Pdf,
|
Pdf,
|
||||||
|
PdfError,
|
||||||
PdfImage,
|
PdfImage,
|
||||||
Stream,
|
Stream,
|
||||||
UnsupportedImageTypeError,
|
UnsupportedImageTypeError,
|
||||||
@@ -41,7 +44,7 @@ from ocrmypdf._concurrent import Executor, SerialExecutor
|
|||||||
from ocrmypdf._exec import jbig2enc, pngquant
|
from ocrmypdf._exec import jbig2enc, pngquant
|
||||||
from ocrmypdf._jobcontext import PdfContext
|
from ocrmypdf._jobcontext import PdfContext
|
||||||
from ocrmypdf.exceptions import OutputFileAccessError
|
from ocrmypdf.exceptions import OutputFileAccessError
|
||||||
from ocrmypdf.helpers import safe_symlink
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, safe_symlink
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -78,33 +81,49 @@ def extract_image_filter(
|
|||||||
if image.Subtype != Name.Image:
|
if image.Subtype != Name.Image:
|
||||||
return None
|
return None
|
||||||
if image.Length < 100:
|
if image.Length < 100:
|
||||||
log.debug(f"Skipping small image, xref {xref}")
|
log.debug(f"xref {xref}: skipping image with small stream size")
|
||||||
return None
|
return None
|
||||||
if image.Width < 8 or image.Height < 8: # Issue 732
|
if image.Width < 8 or image.Height < 8: # Issue 732
|
||||||
log.debug(f"Skipping oddly sized image, xref {xref}")
|
log.debug(f"xref {xref}: skipping image with unusually small dimensions")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
pim = PdfImage(image)
|
pim = PdfImage(image)
|
||||||
|
|
||||||
if len(pim.filter_decodeparms) > 1:
|
if len(pim.filter_decodeparms) > 1:
|
||||||
log.debug(f"Skipping multiply filtered image, xref {xref}")
|
first_filtdp = pim.filter_decodeparms[0]
|
||||||
return None
|
second_filtdp = pim.filter_decodeparms[1]
|
||||||
filtdp = pim.filter_decodeparms[0]
|
if (
|
||||||
|
len(pim.filter_decodeparms) == 2
|
||||||
|
and first_filtdp[0] == Name.FlateDecode
|
||||||
|
and first_filtdp[1].get(Name.Predictor, 1) == 1
|
||||||
|
and second_filtdp[0] == Name.DCTDecode
|
||||||
|
and not second_filtdp[1]
|
||||||
|
):
|
||||||
|
log.debug(
|
||||||
|
f"xref {xref}: found image compressed as /FlateDecode /DCTDecode, "
|
||||||
|
"marked for JPEG optimization"
|
||||||
|
)
|
||||||
|
filtdp = pim.filter_decodeparms[1]
|
||||||
|
else:
|
||||||
|
log.debug(f"xref {xref}: skipping image with multiple compression filters")
|
||||||
|
return None
|
||||||
|
else:
|
||||||
|
filtdp = pim.filter_decodeparms[0]
|
||||||
|
|
||||||
if pim.bits_per_component > 8:
|
if pim.bits_per_component > 8:
|
||||||
log.debug(f"Skipping wide gamut image, xref {xref}")
|
log.debug(f"xref {xref}: skipping wide gamut image")
|
||||||
return None # Don't mess with wide gamut images
|
return None # Don't mess with wide gamut images
|
||||||
|
|
||||||
if filtdp[0] == Name.JPXDecode:
|
if filtdp[0] == Name.JPXDecode:
|
||||||
log.debug(f"Skipping JPEG2000 image, xref {xref}")
|
log.debug(f"xref {xref}: skipping JPEG2000 image")
|
||||||
return None # Don't do JPEG2000
|
return None # Don't do JPEG2000
|
||||||
|
|
||||||
if filtdp[0] == Name.CCITTFaxDecode and filtdp[1].get('/K', 0) >= 0:
|
if filtdp[0] == Name.CCITTFaxDecode and filtdp[1].get('/K', 0) >= 0:
|
||||||
log.debug(f"Skipping CCITT Group 3 image, xref {xref}")
|
log.debug(f"xref {xref}: skipping CCITT Group 3 image")
|
||||||
return None # pikepdf doesn't support Group 3 yet
|
return None # pikepdf doesn't support Group 3 yet
|
||||||
|
|
||||||
if Name.Decode in image:
|
if Name.Decode in image:
|
||||||
log.debug(f"Skipping image with Decode table, xref {xref}")
|
log.debug(f"xref {xref}: skipping image with Decode table")
|
||||||
return None # Don't mess with custom Decode tables
|
return None # Don't mess with custom Decode tables
|
||||||
|
|
||||||
return pim, filtdp
|
return pim, filtdp
|
||||||
@@ -172,14 +191,6 @@ def extract_image_generic(
|
|||||||
# jpeg_quality_estimate = 117.0 * (bytes_per_pixel ** 0.213)
|
# jpeg_quality_estimate = 117.0 * (bytes_per_pixel ** 0.213)
|
||||||
# if jpeg_quality_estimate < 65:
|
# if jpeg_quality_estimate < 65:
|
||||||
# return None
|
# return None
|
||||||
|
|
||||||
# We could get the ICC profile here, but there's no need to look at it
|
|
||||||
# for quality transcoding
|
|
||||||
# if icc:
|
|
||||||
# stream = BytesIO(raw_jpeg.read_raw_bytes())
|
|
||||||
# iccbytes = icc.read_bytes()
|
|
||||||
# with Image.open(stream) as im:
|
|
||||||
# im.save(jpg_name(root, xref), icc_profile=iccbytes)
|
|
||||||
try:
|
try:
|
||||||
imgname = root / f'{xref:08d}'
|
imgname = root / f'{xref:08d}'
|
||||||
with imgname.open('wb') as f:
|
with imgname.open('wb') as f:
|
||||||
@@ -200,7 +211,11 @@ def extract_image_generic(
|
|||||||
elif not pim.indexed and pim.colorspace in pim.SIMPLE_COLORSPACES:
|
elif not pim.indexed and pim.colorspace in pim.SIMPLE_COLORSPACES:
|
||||||
# An optimization opportunity here, not currently taken, is directly
|
# An optimization opportunity here, not currently taken, is directly
|
||||||
# generating a PNG from compressed data
|
# generating a PNG from compressed data
|
||||||
pim.as_pil_image().save(png_name(root, xref))
|
try:
|
||||||
|
pim.as_pil_image().save(png_name(root, xref))
|
||||||
|
except NotImplementedError:
|
||||||
|
log.warning("PDF contains an atypical image that cannot be optimized.")
|
||||||
|
return None
|
||||||
return XrefExt(xref, '.png')
|
return XrefExt(xref, '.png')
|
||||||
elif (
|
elif (
|
||||||
not pim.indexed
|
not pim.indexed
|
||||||
@@ -255,9 +270,9 @@ def extract_images(
|
|||||||
# Ignore soft masks
|
# Ignore soft masks
|
||||||
smask_xref = Xref(image.SMask.objgen[0])
|
smask_xref = Xref(image.SMask.objgen[0])
|
||||||
exclude_xrefs.add(smask_xref)
|
exclude_xrefs.add(smask_xref)
|
||||||
log.debug(f"Skipping image {smask_xref} because it is an SMask")
|
log.debug(f"xref {smask_xref}: skipping image because it is an SMask")
|
||||||
include_xrefs.add(xref)
|
include_xrefs.add(xref)
|
||||||
log.debug(f"Treating {xref} as an optimization candidate")
|
log.debug(f"xref {xref}: treating as an optimization candidate")
|
||||||
if xref not in pageno_for_xref:
|
if xref not in pageno_for_xref:
|
||||||
pageno_for_xref[xref] = pageno
|
pageno_for_xref[xref] = pageno
|
||||||
|
|
||||||
@@ -269,7 +284,9 @@ def extract_images(
|
|||||||
pike=pike, root=root, image=image, xref=xref, options=options
|
pike=pike, root=root, image=image, xref=xref, options=options
|
||||||
)
|
)
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
log.exception(f"While extracting image xref {xref}, an error occurred")
|
log.exception(
|
||||||
|
f"xref {xref}: While extracting this image, an error occurred"
|
||||||
|
)
|
||||||
errors += 1
|
errors += 1
|
||||||
else:
|
else:
|
||||||
if result:
|
if result:
|
||||||
@@ -290,7 +307,7 @@ def extract_images_generic(
|
|||||||
pngs.append(xref_ext.xref)
|
pngs.append(xref_ext.xref)
|
||||||
elif xref_ext.ext == '.jpg':
|
elif xref_ext.ext == '.jpg':
|
||||||
jpegs.append(xref_ext.xref)
|
jpegs.append(xref_ext.xref)
|
||||||
log.debug("Optimizable images: JPEGs: %s PNGs: %s", len(jpegs), len(pngs))
|
log.debug(f"Optimizable images: JPEGs: {len(jpegs)} PNGs: {len(pngs)}")
|
||||||
return jpegs, pngs
|
return jpegs, pngs
|
||||||
|
|
||||||
|
|
||||||
@@ -302,7 +319,7 @@ def extract_images_jbig2(pike: Pdf, root: Path, options) -> Dict[int, List[XrefE
|
|||||||
group = pageno // options.jbig2_page_group_size
|
group = pageno // options.jbig2_page_group_size
|
||||||
jbig2_groups[group].append(xref_ext)
|
jbig2_groups[group].append(xref_ext)
|
||||||
|
|
||||||
log.debug("Optimizable images: JBIG2 groups: %s", (len(jbig2_groups),))
|
log.debug(f"Optimizable images: JBIG2 groups: {len(jbig2_groups)}")
|
||||||
return jbig2_groups
|
return jbig2_groups
|
||||||
|
|
||||||
|
|
||||||
@@ -401,15 +418,11 @@ def convert_to_jbig2(
|
|||||||
def _optimize_jpeg(args: Tuple[Xref, Path, Path, int]) -> Tuple[Xref, Optional[Path]]:
|
def _optimize_jpeg(args: Tuple[Xref, Path, Path, int]) -> Tuple[Xref, Optional[Path]]:
|
||||||
xref, in_jpg, opt_jpg, jpeg_quality = args
|
xref, in_jpg, opt_jpg, jpeg_quality = args
|
||||||
|
|
||||||
# This may produce a debug warning from PIL
|
|
||||||
# DEBUG:PIL.Image:Error closing: 'NoneType' object has no attribute
|
|
||||||
# 'close'. Seems to be mostly harmless
|
|
||||||
# https://github.com/python-pillow/Pillow/issues/1144
|
|
||||||
with Image.open(in_jpg) as im:
|
with Image.open(in_jpg) as im:
|
||||||
im.save(opt_jpg, optimize=True, quality=jpeg_quality)
|
im.save(opt_jpg, optimize=True, quality=jpeg_quality)
|
||||||
|
|
||||||
if opt_jpg.stat().st_size > in_jpg.stat().st_size:
|
if opt_jpg.stat().st_size > in_jpg.stat().st_size:
|
||||||
log.debug("xref %s, jpeg, made larger - skip", xref)
|
log.debug(f"xref {xref}, jpeg, made larger - skip")
|
||||||
opt_jpg.unlink()
|
opt_jpg.unlink()
|
||||||
return xref, None
|
return xref, None
|
||||||
return xref, opt_jpg
|
return xref, opt_jpg
|
||||||
@@ -436,7 +449,7 @@ def transcode_jpegs(
|
|||||||
use_threads=True, # Processes are significantly slower at this task
|
use_threads=True, # Processes are significantly slower at this task
|
||||||
max_workers=options.jobs,
|
max_workers=options.jobs,
|
||||||
tqdm_kwargs=dict(
|
tqdm_kwargs=dict(
|
||||||
desc="JPEGs",
|
desc="Recompressing JPEGs",
|
||||||
total=len(jpegs),
|
total=len(jpegs),
|
||||||
unit='image',
|
unit='image',
|
||||||
disable=not options.progress_bar,
|
disable=not options.progress_bar,
|
||||||
@@ -447,10 +460,77 @@ def transcode_jpegs(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _find_deflatable_jpeg(
|
||||||
|
*, pike: Pdf, root: Path, image: Stream, xref: Xref, options
|
||||||
|
) -> Optional[XrefExt]:
|
||||||
|
result = extract_image_filter(pike, root, image, xref)
|
||||||
|
if result is None:
|
||||||
|
return None
|
||||||
|
pim, filtdp = result
|
||||||
|
|
||||||
|
if filtdp[0] == Name.DCTDecode and not filtdp[1] and options.optimize >= 1:
|
||||||
|
return XrefExt(xref, '.memory')
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _deflate_jpeg(args: Tuple[Pdf, threading.Lock, Xref, int]) -> Tuple[Xref, bytes]:
|
||||||
|
pike, lock, xref, complevel = args
|
||||||
|
with lock:
|
||||||
|
xobj = pike.get_object(xref, 0)
|
||||||
|
try:
|
||||||
|
data = xobj.read_raw_bytes()
|
||||||
|
except PdfError:
|
||||||
|
return xref, b''
|
||||||
|
compdata = compress(data, complevel)
|
||||||
|
if len(compdata) >= len(data):
|
||||||
|
return xref, b''
|
||||||
|
return xref, compdata
|
||||||
|
|
||||||
|
|
||||||
|
def deflate_jpegs(pike: Pdf, root: Path, options, executor: Executor) -> None:
|
||||||
|
jpegs = []
|
||||||
|
for _pageno, xref_ext in extract_images(pike, root, options, _find_deflatable_jpeg):
|
||||||
|
xref = xref_ext.xref
|
||||||
|
log.debug(f'xref {xref}: marking this JPEG as deflatable')
|
||||||
|
jpegs.append(xref)
|
||||||
|
|
||||||
|
complevel = 9 if options.optimize == 3 else 6
|
||||||
|
|
||||||
|
# Our calls to xobj.write() in finish() need coordination
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
|
def deflate_args() -> Iterator:
|
||||||
|
for xref in jpegs:
|
||||||
|
yield pike, lock, xref, complevel
|
||||||
|
|
||||||
|
def finish(result, pbar):
|
||||||
|
xref, compdata = result
|
||||||
|
if len(compdata) > 0:
|
||||||
|
with lock:
|
||||||
|
xobj = pike.get_object(xref, 0)
|
||||||
|
xobj.write(compdata, filter=[Name.FlateDecode, Name.DCTDecode])
|
||||||
|
pbar.update()
|
||||||
|
|
||||||
|
executor(
|
||||||
|
use_threads=True, # We're sharing the pdf directly, must use threads
|
||||||
|
max_workers=options.jobs,
|
||||||
|
tqdm_kwargs=dict(
|
||||||
|
desc="Deflating JPEGs",
|
||||||
|
total=len(jpegs),
|
||||||
|
unit='image',
|
||||||
|
disable=not options.progress_bar,
|
||||||
|
),
|
||||||
|
task=_deflate_jpeg,
|
||||||
|
task_arguments=deflate_args(),
|
||||||
|
task_finished=finish,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _transcode_png(pike: Pdf, filename: Path, xref: Xref) -> bool:
|
def _transcode_png(pike: Pdf, filename: Path, xref: Xref) -> bool:
|
||||||
output = filename.with_suffix('.png.pdf')
|
output = filename.with_suffix('.png.pdf')
|
||||||
with output.open('wb') as f:
|
with output.open('wb') as f:
|
||||||
img2pdf.convert(fspath(filename), outputstream=f)
|
img2pdf.convert(fspath(filename), outputstream=f, **IMG2PDF_KWARGS)
|
||||||
|
|
||||||
with Pdf.open(output) as pdf_image:
|
with Pdf.open(output) as pdf_image:
|
||||||
foreign_image = next(iter(pdf_image.pages[0].images.values()))
|
foreign_image = next(iter(pdf_image.pages[0].images.values()))
|
||||||
@@ -560,6 +640,7 @@ def optimize(
|
|||||||
|
|
||||||
jpegs, pngs = extract_images_generic(pike, root, options)
|
jpegs, pngs = extract_images_generic(pike, root, options)
|
||||||
transcode_jpegs(pike, jpegs, root, options, executor)
|
transcode_jpegs(pike, jpegs, root, options, executor)
|
||||||
|
deflate_jpegs(pike, root, options, executor)
|
||||||
# if options.optimize >= 2:
|
# if options.optimize >= 2:
|
||||||
# Try pngifying the jpegs
|
# Try pngifying the jpegs
|
||||||
# transcode_pngs(pike, jpegs, jpg_name, root, options)
|
# transcode_pngs(pike, jpegs, jpg_name, root, options)
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ from typing import (
|
|||||||
Mapping,
|
Mapping,
|
||||||
NamedTuple,
|
NamedTuple,
|
||||||
Optional,
|
Optional,
|
||||||
|
Sequence,
|
||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
@@ -36,6 +37,7 @@ from pikepdf import (
|
|||||||
PdfImage,
|
PdfImage,
|
||||||
PdfInlineImage,
|
PdfInlineImage,
|
||||||
PdfMatrix,
|
PdfMatrix,
|
||||||
|
UnsupportedImageTypeError,
|
||||||
parse_content_stream,
|
parse_content_stream,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -349,13 +351,20 @@ class ImageInfo:
|
|||||||
|
|
||||||
if self._color == Colorspace.icc:
|
if self._color == Colorspace.icc:
|
||||||
# Check the ICC profile to determine actual colorspace
|
# Check the ICC profile to determine actual colorspace
|
||||||
pim_icc = pim.icc
|
try:
|
||||||
if pim_icc.profile.xcolor_space == 'GRAY':
|
pim_icc = pim.icc
|
||||||
self._comp = 1
|
if pim_icc.profile.xcolor_space == 'GRAY':
|
||||||
elif pim_icc.profile.xcolor_space == 'CMYK':
|
self._comp = 1
|
||||||
self._comp = 4
|
elif pim_icc.profile.xcolor_space == 'CMYK':
|
||||||
else:
|
self._comp = 4
|
||||||
self._comp = 3
|
else:
|
||||||
|
self._comp = 3
|
||||||
|
except UnsupportedImageTypeError as ex:
|
||||||
|
self._comp = None
|
||||||
|
logger.warning(
|
||||||
|
f"An image with a corrupt or unreadable ICC profile was found. "
|
||||||
|
f"The output PDF may not match the input PDF visually: {ex}. {self}"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
if isinstance(self._color, Colorspace):
|
if isinstance(self._color, Colorspace):
|
||||||
self._comp = FRIENDLY_COMP.get(self._color)
|
self._comp = FRIENDLY_COMP.get(self._color)
|
||||||
@@ -408,15 +417,10 @@ class ImageInfo:
|
|||||||
return _get_dpi(self._shorthand, (self._width, self._height))
|
return _get_dpi(self._shorthand, (self._width, self._height))
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
class_locals = {
|
|
||||||
attr: getattr(self, attr, None)
|
|
||||||
for attr in dir(self)
|
|
||||||
if not attr.startswith('_')
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
"<ImageInfo '{name}' {type_} {width}x{height} {color} "
|
f"<ImageInfo '{self.name}' {self.type_} {self.width}x{self.height} "
|
||||||
"{comp} {bpc} {enc} {dpi}>"
|
f"{self.color} {self.comp} {self.bpc} {self.enc} {self.dpi}>"
|
||||||
).format(**class_locals)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _find_inline_images(contentsinfo: ContentsInfo) -> Iterator[ImageInfo]:
|
def _find_inline_images(contentsinfo: ContentsInfo) -> Iterator[ImageInfo]:
|
||||||
@@ -646,8 +650,8 @@ def _pdf_pageinfo_concurrent(
|
|||||||
max_workers,
|
max_workers,
|
||||||
check_pages,
|
check_pages,
|
||||||
detailed_analysis=False,
|
detailed_analysis=False,
|
||||||
):
|
) -> Sequence[Optional['PageInfo']]:
|
||||||
pages = [None] * len(pdf.pages)
|
pages: Sequence[Optional['PageInfo']] = [None] * len(pdf.pages)
|
||||||
|
|
||||||
def update_pageinfo(result, pbar):
|
def update_pageinfo(result, pbar):
|
||||||
page = result
|
page = result
|
||||||
@@ -918,17 +922,17 @@ class PdfInfo:
|
|||||||
self._has_acroform = True
|
self._has_acroform = True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pages(self):
|
def pages(self) -> Sequence[Optional[PageInfo]]:
|
||||||
return self._pages
|
return self._pages
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def min_version(self) -> str:
|
def min_version(self) -> str:
|
||||||
# The minimum PDF is the maximum version that any particular page needs
|
# The minimum PDF is the maximum version that any particular page needs
|
||||||
return max(page.min_version for page in self.pages)
|
return max(page.min_version for page in self.pages if page)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_userunit(self) -> bool:
|
def has_userunit(self) -> bool:
|
||||||
return any(page.userunit != 1.0 for page in self.pages)
|
return any(page.userunit != 1.0 for page in self.pages if page)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_acroform(self) -> bool:
|
def has_acroform(self) -> bool:
|
||||||
|
|||||||
@@ -132,6 +132,7 @@ def get_progressbar_class():
|
|||||||
Here is how OCRmyPDF will use the progress bar:
|
Here is how OCRmyPDF will use the progress bar:
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
pbar_class = pm.hook.get_progressbar_class()
|
pbar_class = pm.hook.get_progressbar_class()
|
||||||
with pbar_class(**tqdm_kwargs) as pbar:
|
with pbar_class(**tqdm_kwargs) as pbar:
|
||||||
...
|
...
|
||||||
@@ -235,9 +236,9 @@ def filter_page_image(page: 'PageContext', image_filename: Path) -> Path:
|
|||||||
``image_filename``. The hook may overwrite ``image_filename`` with a new file.
|
``image_filename``. The hook may overwrite ``image_filename`` with a new file.
|
||||||
|
|
||||||
The output image should preserve the same physical unit dimensions, that is
|
The output image should preserve the same physical unit dimensions, that is
|
||||||
(width * dpi_x, height * dpi_y). That is, if the image is resized, the DPI
|
``(width * dpi_x, height * dpi_y)``. That is, if the image is resized, the DPI
|
||||||
must be adjusted by the reciprocal. If this is not preserved, the PDF page
|
must be adjusted by the reciprocal. If this is not preserved, the PDF page
|
||||||
will be resized and the OCR layer misaligned. OCRmyPDF does not nothing
|
will be resized and the OCR layer misaligned. OCRmyPDF does nothing
|
||||||
to enforce these constraints; it is up to the plugin to do sensible things.
|
to enforce these constraints; it is up to the plugin to do sensible things.
|
||||||
|
|
||||||
OCRmyPDF will create the PDF page based on the image format used (unless the
|
OCRmyPDF will create the PDF page based on the image format used (unless the
|
||||||
@@ -399,8 +400,7 @@ def get_ocr_engine() -> OcrEngine:
|
|||||||
"""Returns an OcrEngine to use for processing this file.
|
"""Returns an OcrEngine to use for processing this file.
|
||||||
|
|
||||||
The OcrEngine may be instantiated multiple times, by both the main process
|
The OcrEngine may be instantiated multiple times, by both the main process
|
||||||
and child process. As such, it must be obtain store any state in ``options``
|
and child process.
|
||||||
or some common location.
|
|
||||||
|
|
||||||
Note:
|
Note:
|
||||||
This is a :ref:`firstresult hook<firstresult>`.
|
This is a :ref:`firstresult hook<firstresult>`.
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ import re
|
|||||||
import sys
|
import sys
|
||||||
from collections.abc import Mapping
|
from collections.abc import Mapping
|
||||||
from contextlib import suppress
|
from contextlib import suppress
|
||||||
from distutils.version import LooseVersion, Version
|
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
from subprocess import PIPE, STDOUT, CalledProcessError, CompletedProcess, Popen
|
||||||
from subprocess import run as subprocess_run
|
from subprocess import run as subprocess_run
|
||||||
from typing import Callable, Optional, Type, Union
|
from typing import Callable, Optional, Type, Union
|
||||||
|
|
||||||
|
from packaging.version import Version
|
||||||
|
|
||||||
from ocrmypdf.exceptions import MissingDependencyError
|
from ocrmypdf.exceptions import MissingDependencyError
|
||||||
|
|
||||||
# pylint: disable=logging-format-interpolation
|
# pylint: disable=logging-format-interpolation
|
||||||
@@ -266,11 +267,11 @@ def check_external_program(
|
|||||||
*,
|
*,
|
||||||
program: str,
|
program: str,
|
||||||
package: str,
|
package: str,
|
||||||
version_checker: Union[str, Callable],
|
version_checker: Callable,
|
||||||
need_version: str,
|
need_version: str,
|
||||||
required_for: Optional[str] = None,
|
required_for: Optional[str] = None,
|
||||||
recommended=False,
|
recommended=False,
|
||||||
version_parser: Type[Version] = LooseVersion,
|
version_parser: Type[Version] = Version,
|
||||||
):
|
):
|
||||||
"""Check for required version of external program and raise exception if not.
|
"""Check for required version of external program and raise exception if not.
|
||||||
|
|
||||||
@@ -291,7 +292,7 @@ def check_external_program(
|
|||||||
try:
|
try:
|
||||||
if callable(version_checker):
|
if callable(version_checker):
|
||||||
found_version = version_checker()
|
found_version = version_checker()
|
||||||
else:
|
else: # deprecated
|
||||||
found_version = version_checker
|
found_version = version_checker
|
||||||
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
|
except (CalledProcessError, FileNotFoundError, MissingDependencyError):
|
||||||
_error_missing_program(program, package, required_for, recommended)
|
_error_missing_program(program, package, required_for, recommended)
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
# type: ignore
|
||||||
|
# Non-Windows mypy now breaks when trying to typecheck winreg
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import shutil
|
import shutil
|
||||||
import sys
|
import sys
|
||||||
from distutils.version import LooseVersion
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Iterable, Iterator, Set, Tuple, TypeVar
|
from typing import Any, Callable, Iterable, Iterator, Set, Tuple, TypeVar
|
||||||
@@ -18,11 +20,23 @@ try:
|
|||||||
except ModuleNotFoundError as e:
|
except ModuleNotFoundError as e:
|
||||||
raise ModuleNotFoundError("This module is for Windows only") from e
|
raise ModuleNotFoundError("This module is for Windows only") from e
|
||||||
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
T = TypeVar('T')
|
T = TypeVar('T')
|
||||||
|
|
||||||
|
|
||||||
|
def ghostscript_version_key(s: str) -> Tuple[int, int, int]:
|
||||||
|
"""Compare Ghostscript version numbers."""
|
||||||
|
try:
|
||||||
|
release = [int(elem) for elem in s.split('.', maxsplit=3)]
|
||||||
|
while len(release) < 3:
|
||||||
|
release.append(0)
|
||||||
|
return (release[0], release[1], release[2])
|
||||||
|
except ValueError:
|
||||||
|
return (0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
def registry_enum(
|
def registry_enum(
|
||||||
key: winreg.HKEYType, enum_fn: Callable[[winreg.HKEYType, int], T]
|
key: winreg.HKEYType, enum_fn: Callable[[winreg.HKEYType, int], T]
|
||||||
) -> Iterator[T]:
|
) -> Iterator[T]:
|
||||||
@@ -51,7 +65,9 @@ def registry_path_ghostscript(env=None) -> Iterator[Path]:
|
|||||||
with winreg.OpenKey(
|
with winreg.OpenKey(
|
||||||
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Artifex\GPL Ghostscript"
|
winreg.HKEY_LOCAL_MACHINE, r"SOFTWARE\Artifex\GPL Ghostscript"
|
||||||
) as k:
|
) as k:
|
||||||
latest_gs = max(registry_subkeys(k), key=LooseVersion, default='0')
|
latest_gs = max(
|
||||||
|
registry_subkeys(k), key=ghostscript_version_key, default=(0, 0, 0)
|
||||||
|
)
|
||||||
with winreg.OpenKey(
|
with winreg.OpenKey(
|
||||||
winreg.HKEY_LOCAL_MACHINE, fr"SOFTWARE\Artifex\GPL Ghostscript\{latest_gs}"
|
winreg.HKEY_LOCAL_MACHINE, fr"SOFTWARE\Artifex\GPL Ghostscript\{latest_gs}"
|
||||||
) as k:
|
) as k:
|
||||||
|
|||||||
Vendored
+84
-77
@@ -1,77 +1,84 @@
|
|||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000003_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000003_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000003_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000003_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_pp_rm_bg.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_pp_rm_bg.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_pp_rm_bg.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/graph_ocred.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/graph_ocred.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000005_ocr.png__000005_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000005_ocr.png", "$TMPDIR/000005_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_tess__pdf__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000006_ocr.png__000006_ocr_hocr__hocr__txt", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "$TMPDIR/000006_ocr.png", "$TMPDIR/000006_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__7__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/skew.pdf", "args": ["-l", "eng", "--psm", "7", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/lichtenstein.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/3small.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/palette.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/aspect.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/ccitt.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000003_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000003_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000004_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000004_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000002_rasterize_preview.jpg__stdout", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000002_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/jbig2.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000004_ocr.png__000004_ocr_tess__pdf__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000004_ocr.png", "$TMPDIR/000004_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000003_ocr.png__000003_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000003_ocr.png", "$TMPDIR/000003_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_hocr", "hocr", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000002_ocr.png__000002_ocr_hocr__hocr__txt", "sourcefile": "resources/cardinal.pdf", "args": ["-l", "eng", "$TMPDIR/000002_ocr.png", "$TMPDIR/000002_ocr_hocr", "hocr", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/2400dpi.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/2400dpi.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/poster.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/poster.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/poster.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__osd__--psm__0__000001_rasterize_preview.jpg__stdout", "sourcefile": "resources/poster.pdf", "args": ["-l", "osd", "--psm", "0", "$TMPDIR/000001_rasterize_preview.jpg", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000003_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000003_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000003_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000003_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000005_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000005_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000004_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000004_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000001_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000001_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_rasterize.png", "stdout"]}
|
||||||
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "platform": "Linux-5.10.16.3-microsoft-standard-WSL2-x86_64-with-glibc2.29", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_rasterize.png", "stdout"]}
|
{"tesseract_version": "5.0.0-beta-20210916-12-g19cc9", "system": "Linux", "python": "3.8.10", "argv_slug": "__-l__eng__--psm__2__000006_rasterize.png__stdout", "sourcefile": "resources/multipage.pdf", "args": ["-l", "eng", "--psm", "2", "$TMPDIR/000006_rasterize.png", "stdout"]}
|
||||||
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "4.1.1", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__--oem__1__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "--oem", "1", "-c", "textonly_pdf=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "5.0.0", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__thresholding_method=1__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "-c", "thresholding_method=1", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
{"tesseract_version": "5.0.0", "system": "Linux", "python": "3.9.5", "argv_slug": "__-l__eng__thresholding_method=2__000001_ocr.png__000001_ocr_tess__pdf__txt", "sourcefile": "resources/trivial.pdf", "args": ["-l", "eng", "-c", "textonly_pdf=1", "-c", "thresholding_method=2", "$TMPDIR/000001_ocr.png", "$TMPDIR/000001_ocr_tess", "pdf", "txt"]}
|
||||||
|
|||||||
BIN
Binary file not shown.
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
|
||||||
Vendored
+1
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
BIN
Binary file not shown.
+1
@@ -0,0 +1 @@
|
|||||||
|
Tesseract Open Source OCR Engine v4.1.1 with Leptonica
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
+36
-31
@@ -9,17 +9,15 @@ import os
|
|||||||
import platform
|
import platform
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, run
|
from subprocess import PIPE, CompletedProcess, run
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from ocrmypdf import api, pdfinfo
|
from ocrmypdf import api, pdfinfo
|
||||||
from ocrmypdf._exec import unpaper
|
from ocrmypdf._exec import unpaper
|
||||||
from ocrmypdf._plugin_manager import get_parser_options_plugins
|
from ocrmypdf._plugin_manager import get_parser_options_plugins
|
||||||
|
from ocrmypdf.exceptions import ExitCode
|
||||||
if sys.version_info < (3, 5):
|
|
||||||
print("Requires Python 3.5+")
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
def is_linux():
|
def is_linux():
|
||||||
@@ -46,36 +44,35 @@ def have_unpaper():
|
|||||||
|
|
||||||
TESTS_ROOT = Path(__file__).parent.resolve()
|
TESTS_ROOT = Path(__file__).parent.resolve()
|
||||||
PROJECT_ROOT = TESTS_ROOT
|
PROJECT_ROOT = TESTS_ROOT
|
||||||
OCRMYPDF = [sys.executable, '-m', 'ocrmypdf']
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def resources():
|
def resources() -> Path:
|
||||||
return Path(TESTS_ROOT) / 'resources'
|
return Path(TESTS_ROOT) / 'resources'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def ocrmypdf_exec():
|
def ocrmypdf_exec() -> List[str]:
|
||||||
return OCRMYPDF
|
return [sys.executable, '-m', 'ocrmypdf']
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def outdir(tmp_path):
|
def outdir(tmp_path) -> Path:
|
||||||
return tmp_path
|
return tmp_path
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def outpdf(tmp_path):
|
def outpdf(tmp_path) -> Path:
|
||||||
return tmp_path / 'out.pdf'
|
return tmp_path / 'out.pdf'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def outtxt(tmp_path):
|
def outtxt(tmp_path) -> Path:
|
||||||
return tmp_path / 'out.txt'
|
return tmp_path / 'out.txt'
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def no_outpdf(tmp_path):
|
def no_outpdf(tmp_path) -> Path:
|
||||||
"""This just documents the fact that a test is not expected to produce
|
"""This just documents the fact that a test is not expected to produce
|
||||||
output. Unfortunately an assertion failure inside a test fixture produces
|
output. Unfortunately an assertion failure inside a test fixture produces
|
||||||
an error rather than a test failure, so no testing is done. It's up to
|
an error rather than a test failure, so no testing is done. It's up to
|
||||||
@@ -83,13 +80,13 @@ def no_outpdf(tmp_path):
|
|||||||
return tmp_path / 'no_output.pdf'
|
return tmp_path / 'no_output.pdf'
|
||||||
|
|
||||||
|
|
||||||
def check_ocrmypdf(input_file, output_file, *args):
|
def check_ocrmypdf(input_file: Path, output_file: Path, *args) -> Path:
|
||||||
"""Run ocrmypdf and confirmed that a valid file was created"""
|
"""Run ocrmypdf and confirm that a valid plausible PDF was created."""
|
||||||
args = [str(input_file), str(output_file)] + [
|
api_args = [str(input_file), str(output_file)] + [
|
||||||
str(arg) for arg in args if arg is not None
|
str(arg) for arg in args if arg is not None
|
||||||
]
|
]
|
||||||
|
|
||||||
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
|
_parser, options, plugin_manager = get_parser_options_plugins(args=api_args)
|
||||||
api.check_options(options, plugin_manager)
|
api.check_options(options, plugin_manager)
|
||||||
result = api.run_pipeline(options, plugin_manager=plugin_manager, api=True)
|
result = api.run_pipeline(options, plugin_manager=plugin_manager, api=True)
|
||||||
|
|
||||||
@@ -100,44 +97,52 @@ def check_ocrmypdf(input_file, output_file, *args):
|
|||||||
return output_file
|
return output_file
|
||||||
|
|
||||||
|
|
||||||
def run_ocrmypdf_api(input_file, output_file, *args):
|
def run_ocrmypdf_api(input_file: Path, output_file: Path, *args) -> ExitCode:
|
||||||
"""Run ocrmypdf via API and let caller deal with results
|
"""Run ocrmypdf via its API in-process, and let test deal with results.
|
||||||
|
|
||||||
Does not currently have a way to manipulate the PATH except for Tesseract.
|
This simulates calling the command line interface in a subprocess, but
|
||||||
|
is easier for debuggers and code coverage to follow.
|
||||||
|
|
||||||
|
Any exception raised will be trapped and converted to an exit code.
|
||||||
|
The return code must always be checked or the test may declare a failure
|
||||||
|
to be pass.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
args = [str(input_file), str(output_file)] + [
|
api_args = [str(input_file), str(output_file)] + [
|
||||||
str(arg) for arg in args if arg is not None
|
str(arg) for arg in args if arg is not None
|
||||||
]
|
]
|
||||||
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
|
_parser, options, plugin_manager = get_parser_options_plugins(args=api_args)
|
||||||
|
|
||||||
api.check_options(options, plugin_manager)
|
api.check_options(options, plugin_manager)
|
||||||
return api.run_pipeline(options, plugin_manager=None, api=False)
|
return api.run_pipeline(options, plugin_manager=None, api=False)
|
||||||
|
|
||||||
|
|
||||||
def run_ocrmypdf(input_file, output_file, *args, text=True):
|
def run_ocrmypdf(
|
||||||
"Run ocrmypdf and let caller deal with results"
|
input_file: Path, output_file: Path, *args, text: bool = True
|
||||||
|
) -> CompletedProcess:
|
||||||
|
"""Run ocrmypdf in a subprocess and let test deal with results.
|
||||||
|
|
||||||
|
If an exception is thrown this fact will be returned as part of the result
|
||||||
|
text and return code rather than exception objects.
|
||||||
|
"""
|
||||||
|
|
||||||
p_args = (
|
p_args = (
|
||||||
OCRMYPDF
|
[sys.executable, '-m', 'ocrmypdf']
|
||||||
+ [str(arg) for arg in args if arg is not None]
|
+ [str(arg) for arg in args if arg is not None]
|
||||||
+ [str(input_file), str(output_file)]
|
+ [str(input_file), str(output_file)]
|
||||||
)
|
)
|
||||||
|
|
||||||
env = os.environ.copy()
|
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
text=text,
|
text=text,
|
||||||
env=env,
|
|
||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
# print(p.stderr)
|
# print(p.stderr)
|
||||||
return p, p.stdout, p.stderr
|
return p
|
||||||
|
|
||||||
|
|
||||||
def first_page_dimensions(pdf):
|
def first_page_dimensions(pdf: Path):
|
||||||
info = pdfinfo.PdfInfo(pdf)
|
info = pdfinfo.PdfInfo(pdf)
|
||||||
page0 = info[0]
|
page0 = info[0]
|
||||||
return (page0.width_inches, page0.height_inches)
|
return (page0.width_inches, page0.height_inches)
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import logging
|
|||||||
import platform
|
import platform
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import threading
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from subprocess import PIPE, CalledProcessError, CompletedProcess
|
from subprocess import PIPE, CalledProcessError, CompletedProcess
|
||||||
@@ -158,7 +159,7 @@ def cached_run(options, run_args, **run_kwargs):
|
|||||||
|
|
||||||
manifest = {}
|
manifest = {}
|
||||||
manifest['tesseract_version'] = TesseractOcrEngine.version().replace('\n', ' ')
|
manifest['tesseract_version'] = TesseractOcrEngine.version().replace('\n', ' ')
|
||||||
manifest['platform'] = platform.platform()
|
manifest['system'] = platform.system()
|
||||||
manifest['python'] = platform.python_version()
|
manifest['python'] = platform.python_version()
|
||||||
manifest['argv_slug'] = cache_folder.name
|
manifest['argv_slug'] = cache_folder.name
|
||||||
manifest['sourcefile'] = str(Path(source_file).relative_to(TESTS_ROOT))
|
manifest['sourcefile'] = str(Path(source_file).relative_to(TESTS_ROOT))
|
||||||
@@ -176,26 +177,40 @@ def cached_run(options, run_args, **run_kwargs):
|
|||||||
|
|
||||||
|
|
||||||
class CacheOcrEngine(TesseractOcrEngine):
|
class CacheOcrEngine(TesseractOcrEngine):
|
||||||
|
# Concurrent threads (with --use-threads) might try to use different parts
|
||||||
|
# of the OcrEngine, so we need a lock to protect the state of patched
|
||||||
|
# module whenever it's patched. Should refactor ocrmypdf._exec.tesseract so that
|
||||||
|
# it does not to be patched at all for testing.
|
||||||
|
lock = threading.Lock()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_orientation(input_file, options):
|
def get_orientation(input_file, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch(
|
||||||
|
'ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)
|
||||||
|
):
|
||||||
return TesseractOcrEngine.get_orientation(input_file, options)
|
return TesseractOcrEngine.get_orientation(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_deskew(input_file, options) -> float:
|
def get_deskew(input_file, options) -> float:
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch(
|
||||||
|
'ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)
|
||||||
|
):
|
||||||
return TesseractOcrEngine.get_deskew(input_file, options)
|
return TesseractOcrEngine.get_deskew(input_file, options)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_hocr(input_file, output_hocr, output_text, options):
|
def generate_hocr(input_file, output_hocr, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch(
|
||||||
|
'ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)
|
||||||
|
):
|
||||||
TesseractOcrEngine.generate_hocr(
|
TesseractOcrEngine.generate_hocr(
|
||||||
input_file, output_hocr, output_text, options
|
input_file, output_hocr, output_text, options
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_pdf(input_file, output_pdf, output_text, options):
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
with patch('ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)):
|
with CacheOcrEngine.lock, patch(
|
||||||
|
'ocrmypdf._exec.tesseract.run', new=partial(cached_run, options)
|
||||||
|
):
|
||||||
TesseractOcrEngine.generate_pdf(
|
TesseractOcrEngine.generate_pdf(
|
||||||
input_file, output_pdf, output_text, options
|
input_file, output_pdf, output_text, options
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
# © 2021 James R. Barlow: github.com/jbarlow83
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
# distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
# permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
# the following conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
# IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
# CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
# TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
# type: ignore
|
||||||
|
|
||||||
|
"""Tesseract no-op plugin that simulates the OOM killer on page 4.
|
||||||
|
|
||||||
|
OCRmyPDF can use a lot of memory, even that it might trigger the
|
||||||
|
OOM killer on Linux or similar features on other platforms. We want to
|
||||||
|
ensure we fail with an error rather than deadlock in such cases.
|
||||||
|
|
||||||
|
Page 4 was chosen because of this number's association with bad luck
|
||||||
|
in many East Asian cultures.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import signal
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ocrmypdf import hookimpl
|
||||||
|
|
||||||
|
# Ugly hack that let us use the NoopOcrEngine without setting up packaging for our
|
||||||
|
# tests.
|
||||||
|
# This hack also requires us to set type: ignore
|
||||||
|
parent_file = Path(__file__).with_name('tesseract_noop.py')
|
||||||
|
parent = compile(parent_file.read_text(), parent_file, mode='exec')
|
||||||
|
exec(parent)
|
||||||
|
NoopOcrEngine = locals()['NoopOcrEngine']
|
||||||
|
|
||||||
|
|
||||||
|
class Page4Engine(NoopOcrEngine):
|
||||||
|
def __str__(self):
|
||||||
|
return f"NO-OP Page 4 {NoopOcrEngine.version()}"
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_hocr(input_file: Path, output_hocr, output_text, options):
|
||||||
|
if input_file.stem.startswith('000004'):
|
||||||
|
# Suicide
|
||||||
|
os.kill(os.getpid(), signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
return NoopOcrEngine.generate_hocr(
|
||||||
|
input_file, output_hocr, output_text, options
|
||||||
|
)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def generate_pdf(input_file, output_pdf, output_text, options):
|
||||||
|
if input_file.stem.startswith('000004'):
|
||||||
|
# Suicide
|
||||||
|
os.kill(os.getpid(), signal.SIGKILL)
|
||||||
|
else:
|
||||||
|
return NoopOcrEngine.generate_pdf(
|
||||||
|
input_file, output_pdf, output_text, options
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def check_options(options):
|
||||||
|
if options.use_threads:
|
||||||
|
raise ValueError("I'm not compatible with use_threads")
|
||||||
|
|
||||||
|
|
||||||
|
@hookimpl
|
||||||
|
def get_ocr_engine():
|
||||||
|
return Page4Engine()
|
||||||
@@ -24,8 +24,7 @@ def test_fish():
|
|||||||
['fish', '-n', 'misc/completion/ocrmypdf.fish'],
|
['fish', '-n', 'misc/completion/ocrmypdf.fish'],
|
||||||
check=True,
|
check=True,
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
)
|
)
|
||||||
assert proc.stderr == '', proc.stderr
|
assert proc.stderr == '', proc.stderr
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
@@ -41,8 +40,7 @@ def test_bash():
|
|||||||
['bash', '-n', 'misc/completion/ocrmypdf.bash'],
|
['bash', '-n', 'misc/completion/ocrmypdf.bash'],
|
||||||
check=True,
|
check=True,
|
||||||
encoding='utf-8',
|
encoding='utf-8',
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
)
|
)
|
||||||
assert proc.stderr == '', proc.stderr
|
assert proc.stderr == '', proc.stderr
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# © 2021 James R. Barlow: github.com/jbarlow83
|
||||||
|
#
|
||||||
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from ocrmypdf import ExitCode
|
||||||
|
|
||||||
|
from .conftest import run_ocrmypdf_api
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(True, reason="--use-threads is currently default")
|
||||||
|
@pytest.mark.skipif(os.name == 'nt', reason="Windows doesn't have SIGKILL")
|
||||||
|
def test_simulate_oom_killer(resources, no_outpdf):
|
||||||
|
exitcode = run_ocrmypdf_api(
|
||||||
|
resources / 'multipage.pdf',
|
||||||
|
no_outpdf,
|
||||||
|
'--force-ocr',
|
||||||
|
'--plugin',
|
||||||
|
'tests/plugins/tesseract_simulate_oom_killer.py',
|
||||||
|
)
|
||||||
|
assert exitcode == ExitCode.child_process_error
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
|
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import subprocess
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pikepdf
|
import pikepdf
|
||||||
import pytest
|
import pytest
|
||||||
from PIL import Image
|
from PIL import Image, UnidentifiedImageError
|
||||||
|
|
||||||
from ocrmypdf._exec.ghostscript import rasterize_pdf
|
from ocrmypdf._exec.ghostscript import rasterize_pdf
|
||||||
from ocrmypdf.exceptions import ExitCode
|
from ocrmypdf.exceptions import ExitCode
|
||||||
@@ -76,7 +78,7 @@ def test_rasterize_rotated(francais, outdir, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_gs_render_failure(resources, outpdf):
|
def test_gs_render_failure(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'blank.pdf',
|
resources / 'blank.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
@@ -84,12 +86,12 @@ def test_gs_render_failure(resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/gs_render_failure.py',
|
'tests/plugins/gs_render_failure.py',
|
||||||
)
|
)
|
||||||
assert 'Casper is not a friendly ghost' in err
|
assert 'Casper is not a friendly ghost' in p.stderr
|
||||||
assert p.returncode == ExitCode.child_process_error
|
assert p.returncode == ExitCode.child_process_error
|
||||||
|
|
||||||
|
|
||||||
def test_gs_raster_failure(resources, outpdf):
|
def test_gs_raster_failure(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'francais.pdf',
|
resources / 'francais.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
@@ -97,12 +99,12 @@ def test_gs_raster_failure(resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/gs_raster_failure.py',
|
'tests/plugins/gs_raster_failure.py',
|
||||||
)
|
)
|
||||||
assert 'Ghost story archive not found' in err
|
assert 'Ghost story archive not found' in p.stderr
|
||||||
assert p.returncode == ExitCode.child_process_error
|
assert p.returncode == ExitCode.child_process_error
|
||||||
|
|
||||||
|
|
||||||
def test_ghostscript_pdfa_failure(resources, outpdf):
|
def test_ghostscript_pdfa_failure(resources, outpdf):
|
||||||
p, _out, _err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'francais.pdf',
|
resources / 'francais.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
@@ -124,3 +126,20 @@ def test_ghostscript_feature_elision(resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/gs_feature_elision.py',
|
'tests/plugins/gs_feature_elision.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rasterize_pdf_errors(resources, no_outpdf, caplog):
|
||||||
|
with patch('ocrmypdf._exec.ghostscript.run') as mock:
|
||||||
|
# ghostscript can produce
|
||||||
|
mock.return_value = subprocess.CompletedProcess(
|
||||||
|
['fakegs'], returncode=0, stdout=b'', stderr=b'error this is an error'
|
||||||
|
)
|
||||||
|
with pytest.raises(UnidentifiedImageError):
|
||||||
|
rasterize_pdf(
|
||||||
|
resources / 'francais.pdf',
|
||||||
|
no_outpdf,
|
||||||
|
raster_device='pngmono',
|
||||||
|
raster_dpi=Resolution(100, 100),
|
||||||
|
)
|
||||||
|
assert "this is an error" in caplog.text
|
||||||
|
assert "invalid page image file" in caplog.text
|
||||||
|
|||||||
+114
-44
@@ -203,7 +203,7 @@ def test_force_ocr_on_pdf_with_no_images(resources, no_outpdf):
|
|||||||
# As a correctness test, make sure that --force-ocr on a PDF with no
|
# As a correctness test, make sure that --force-ocr on a PDF with no
|
||||||
# content still triggers tesseract. If tesseract crashes, then it was
|
# content still triggers tesseract. If tesseract crashes, then it was
|
||||||
# called.
|
# called.
|
||||||
p, _, _ = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'blank.pdf',
|
resources / 'blank.pdf',
|
||||||
no_outpdf,
|
no_outpdf,
|
||||||
'--force-ocr',
|
'--force-ocr',
|
||||||
@@ -241,7 +241,7 @@ def test_german(resources, outdir):
|
|||||||
|
|
||||||
|
|
||||||
def test_klingon(resources, outpdf):
|
def test_klingon(resources, outpdf):
|
||||||
p, _, _ = run_ocrmypdf(resources / 'francais.pdf', outpdf, '-l', 'klz')
|
p = run_ocrmypdf(resources / 'francais.pdf', outpdf, '-l', 'klz')
|
||||||
assert p.returncode == ExitCode.missing_dependency
|
assert p.returncode == ExitCode.missing_dependency
|
||||||
|
|
||||||
|
|
||||||
@@ -316,9 +316,45 @@ def test_pagesegmode(renderer, resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tesseract_oem(resources, outpdf):
|
||||||
|
check_ocrmypdf(
|
||||||
|
resources / 'trivial.pdf',
|
||||||
|
outpdf,
|
||||||
|
'--tesseract-oem',
|
||||||
|
'1',
|
||||||
|
'--plugin',
|
||||||
|
'tests/plugins/tesseract_cache.py',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value', ['auto', 'otsu', 'adaptive-otsu', 'sauvola'])
|
||||||
|
def test_tesseract_thresholding(value, resources, outpdf):
|
||||||
|
check_ocrmypdf(
|
||||||
|
resources / 'trivial.pdf',
|
||||||
|
outpdf,
|
||||||
|
'--tesseract-thresholding',
|
||||||
|
value,
|
||||||
|
'--plugin',
|
||||||
|
'tests/plugins/tesseract_cache.py',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('value', ['abcxyz'])
|
||||||
|
def test_tesseract_thresholding_invalid(value, resources, no_outpdf):
|
||||||
|
with pytest.raises(SystemExit, match='2'):
|
||||||
|
run_ocrmypdf_api(
|
||||||
|
resources / 'trivial.pdf',
|
||||||
|
no_outpdf,
|
||||||
|
'--tesseract-thresholding',
|
||||||
|
value,
|
||||||
|
'--plugin',
|
||||||
|
'tests/plugins/tesseract_cache.py',
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||||
def test_tesseract_crash(renderer, resources, no_outpdf):
|
def test_tesseract_crash(renderer, resources, no_outpdf):
|
||||||
p, _, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'ccitt.pdf',
|
resources / 'ccitt.pdf',
|
||||||
no_outpdf,
|
no_outpdf,
|
||||||
'-v',
|
'-v',
|
||||||
@@ -330,11 +366,11 @@ def test_tesseract_crash(renderer, resources, no_outpdf):
|
|||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.child_process_error
|
assert p.returncode == ExitCode.child_process_error
|
||||||
assert not no_outpdf.exists()
|
assert not no_outpdf.exists()
|
||||||
assert "SubprocessOutputError" in err
|
assert "SubprocessOutputError" in p.stderr
|
||||||
|
|
||||||
|
|
||||||
def test_tesseract_crash_autorotate(resources, no_outpdf):
|
def test_tesseract_crash_autorotate(resources, no_outpdf):
|
||||||
p, out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'ccitt.pdf',
|
resources / 'ccitt.pdf',
|
||||||
no_outpdf,
|
no_outpdf,
|
||||||
'-r',
|
'-r',
|
||||||
@@ -343,9 +379,9 @@ def test_tesseract_crash_autorotate(resources, no_outpdf):
|
|||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.child_process_error
|
assert p.returncode == ExitCode.child_process_error
|
||||||
assert not no_outpdf.exists()
|
assert not no_outpdf.exists()
|
||||||
assert "uncaught exception" in err
|
assert "uncaught exception" in p.stderr
|
||||||
print(out)
|
print(p.stdout)
|
||||||
print(err)
|
print(p.stderr)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||||
@@ -365,7 +401,7 @@ def test_tesseract_image_too_big(renderer, resources, outpdf):
|
|||||||
|
|
||||||
|
|
||||||
def test_algo4(resources, outpdf):
|
def test_algo4(resources, outpdf):
|
||||||
p, _, _ = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'encrypted_algo4.pdf',
|
resources / 'encrypted_algo4.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
@@ -429,13 +465,19 @@ def test_overlay(resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_destination_not_writable(resources, outdir):
|
@pytest.fixture
|
||||||
if os.name != 'nt' and (os.getuid() == 0 or os.geteuid() == 0):
|
def protected_file(outdir):
|
||||||
pytest.xfail(reason="root can write to anything")
|
|
||||||
protected_file = outdir / 'protected.pdf'
|
protected_file = outdir / 'protected.pdf'
|
||||||
protected_file.touch()
|
protected_file.touch()
|
||||||
protected_file.chmod(0o400) # Read-only
|
protected_file.chmod(0o400) # Read-only
|
||||||
p, _out, _err = run_ocrmypdf(
|
yield protected_file
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(
|
||||||
|
os.name == 'nt' or os.geteuid() == 0, reason="root can write to anything"
|
||||||
|
)
|
||||||
|
def test_destination_not_writable(resources, protected_file):
|
||||||
|
p = run_ocrmypdf(
|
||||||
resources / 'jbig2.pdf',
|
resources / 'jbig2.pdf',
|
||||||
protected_file,
|
protected_file,
|
||||||
'--plugin',
|
'--plugin',
|
||||||
@@ -444,7 +486,8 @@ def test_destination_not_writable(resources, outdir):
|
|||||||
assert p.returncode == ExitCode.file_access_error, "Expected error"
|
assert p.returncode == ExitCode.file_access_error, "Expected error"
|
||||||
|
|
||||||
|
|
||||||
def test_tesseract_config_valid(resources, outdir):
|
@pytest.fixture
|
||||||
|
def valid_tess_config(outdir):
|
||||||
cfg_file = outdir / 'test.cfg'
|
cfg_file = outdir / 'test.cfg'
|
||||||
with cfg_file.open('w') as f:
|
with cfg_file.open('w') as f:
|
||||||
f.write(
|
f.write(
|
||||||
@@ -454,20 +497,22 @@ language_model_penalty_non_dict_word 0
|
|||||||
language_model_penalty_non_freq_dict_word 0
|
language_model_penalty_non_freq_dict_word 0
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
yield cfg_file
|
||||||
|
|
||||||
|
|
||||||
|
def test_tesseract_config_valid(resources, valid_tess_config, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / '3small.pdf',
|
resources / '3small.pdf',
|
||||||
outdir / 'out.pdf',
|
outpdf,
|
||||||
'--tesseract-config',
|
'--tesseract-config',
|
||||||
cfg_file,
|
valid_tess_config,
|
||||||
'--pages',
|
'--pages',
|
||||||
'1',
|
'1',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow # This test sometimes times out in CI
|
@pytest.fixture
|
||||||
@pytest.mark.parametrize('renderer', RENDERERS)
|
def invalid_tess_config(outdir):
|
||||||
def test_tesseract_config_invalid(renderer, resources, outdir):
|
|
||||||
cfg_file = outdir / 'test.cfg'
|
cfg_file = outdir / 'test.cfg'
|
||||||
with cfg_file.open('w') as f:
|
with cfg_file.open('w') as f:
|
||||||
f.write(
|
f.write(
|
||||||
@@ -475,18 +520,23 @@ def test_tesseract_config_invalid(renderer, resources, outdir):
|
|||||||
THIS FILE IS INVALID
|
THIS FILE IS INVALID
|
||||||
'''
|
'''
|
||||||
)
|
)
|
||||||
|
yield cfg_file
|
||||||
|
|
||||||
p, _out, err = run_ocrmypdf(
|
|
||||||
|
@pytest.mark.slow # This test sometimes times out in CI
|
||||||
|
@pytest.mark.parametrize('renderer', RENDERERS)
|
||||||
|
def test_tesseract_config_invalid(renderer, resources, invalid_tess_config, outpdf):
|
||||||
|
p = run_ocrmypdf(
|
||||||
resources / 'ccitt.pdf',
|
resources / 'ccitt.pdf',
|
||||||
outdir / 'out.pdf',
|
outpdf,
|
||||||
'--pdf-renderer',
|
'--pdf-renderer',
|
||||||
renderer,
|
renderer,
|
||||||
'--tesseract-config',
|
'--tesseract-config',
|
||||||
cfg_file,
|
invalid_tess_config,
|
||||||
)
|
)
|
||||||
assert (
|
assert (
|
||||||
"parameter not found" in err.lower()
|
"parameter not found" in p.stderr.lower()
|
||||||
or "error occurred while parsing" in err.lower()
|
or "error occurred while parsing" in p.stderr.lower()
|
||||||
), "No error message"
|
), "No error message"
|
||||||
assert p.returncode == ExitCode.invalid_config
|
assert p.returncode == ExitCode.invalid_config
|
||||||
|
|
||||||
@@ -594,8 +644,7 @@ def test_compression_preserved(ocrmypdf_exec, resources, image, outpdf):
|
|||||||
]
|
]
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
stdin=input_stream,
|
stdin=input_stream,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
@@ -655,8 +704,7 @@ def test_compression_changed(ocrmypdf_exec, resources, image, compression, outpd
|
|||||||
]
|
]
|
||||||
p = run(
|
p = run(
|
||||||
p_args,
|
p_args,
|
||||||
stdout=PIPE,
|
capture_output=True,
|
||||||
stderr=PIPE,
|
|
||||||
stdin=input_stream,
|
stdin=input_stream,
|
||||||
text=True,
|
text=True,
|
||||||
check=False,
|
check=False,
|
||||||
@@ -745,15 +793,13 @@ def test_pdfa_n(pdfa_level, resources, outpdf):
|
|||||||
|
|
||||||
|
|
||||||
def test_decompression_bomb_error(resources, outpdf):
|
def test_decompression_bomb_error(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(resources / 'hugemono.pdf', outpdf)
|
p = run_ocrmypdf(resources / 'hugemono.pdf', outpdf)
|
||||||
assert 'decompression bomb' in err and '--max-image-mpixels' in err
|
assert 'decompression bomb' in p.stderr and '--max-image-mpixels' in p.stderr
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
@pytest.mark.slow
|
||||||
def test_decompression_bomb_succeeds(resources, outpdf):
|
def test_decompression_bomb_succeeds(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(resources / 'hugemono.pdf', outpdf, '--max-image-mpixels', '2000')
|
||||||
resources / 'hugemono.pdf', outpdf, '--max-image-mpixels', '2000'
|
|
||||||
)
|
|
||||||
assert p.returncode == 0
|
assert p.returncode == 0
|
||||||
|
|
||||||
|
|
||||||
@@ -769,6 +815,9 @@ def test_text_curves(resources, outpdf):
|
|||||||
info = PdfInfo(outpdf)
|
info = PdfInfo(outpdf)
|
||||||
assert len(info.pages[0].images) == 0, "added images to the vector PDF"
|
assert len(info.pages[0].images) == 0, "added images to the vector PDF"
|
||||||
|
|
||||||
|
|
||||||
|
def test_text_curves_force(resources, outpdf):
|
||||||
|
with patch('ocrmypdf._pipeline.VECTOR_PAGE_DPI', 100):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / 'vector.pdf',
|
resources / 'vector.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
@@ -782,7 +831,7 @@ def test_text_curves(resources, outpdf):
|
|||||||
|
|
||||||
|
|
||||||
def test_output_is_dir(resources, outdir):
|
def test_output_is_dir(resources, outdir):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
outdir,
|
outdir,
|
||||||
'--force-ocr',
|
'--force-ocr',
|
||||||
@@ -790,28 +839,28 @@ def test_output_is_dir(resources, outdir):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.file_access_error
|
assert p.returncode == ExitCode.file_access_error
|
||||||
assert 'is not a writable file' in err
|
assert 'is not a writable file' in p.stderr
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(os.name == 'nt', reason="symlink needs admin permissions")
|
@pytest.mark.skipif(os.name == 'nt', reason="symlink needs admin permissions")
|
||||||
def test_output_is_symlink(resources, outdir):
|
def test_output_is_symlink(resources, outdir):
|
||||||
sym = Path(outdir / 'this_is_a_symlink')
|
sym = Path(outdir / 'this_is_a_symlink')
|
||||||
sym.symlink_to(outdir / 'out.pdf')
|
sym.symlink_to(outdir / 'out.pdf')
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
sym,
|
sym,
|
||||||
'--force-ocr',
|
'--force-ocr',
|
||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.ok, err
|
assert p.returncode == ExitCode.ok, p.stderr
|
||||||
assert (outdir / 'out.pdf').stat().st_size > 0, 'target file not created'
|
assert (outdir / 'out.pdf').stat().st_size > 0, 'target file not created'
|
||||||
|
|
||||||
|
|
||||||
def test_livecycle(resources, no_outpdf):
|
def test_livecycle(resources, no_outpdf):
|
||||||
p, _, err = run_ocrmypdf(resources / 'livecycle.pdf', no_outpdf)
|
p = run_ocrmypdf(resources / 'livecycle.pdf', no_outpdf)
|
||||||
|
|
||||||
assert p.returncode == ExitCode.input_file, err
|
assert p.returncode == ExitCode.input_file, p.stderr
|
||||||
|
|
||||||
|
|
||||||
def test_version_check():
|
def test_version_check():
|
||||||
@@ -868,7 +917,7 @@ def test_image_dpi_not_image(caplog, resources, outpdf):
|
|||||||
|
|
||||||
|
|
||||||
def test_outputtype_none_bad_setup(resources, outpdf):
|
def test_outputtype_none_bad_setup(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
outpdf,
|
outpdf,
|
||||||
'--output-type=none',
|
'--output-type=none',
|
||||||
@@ -876,13 +925,13 @@ def test_outputtype_none_bad_setup(resources, outpdf):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.bad_args
|
assert p.returncode == ExitCode.bad_args
|
||||||
assert 'Set the output file to' in err
|
assert 'Set the output file to' in p.stderr
|
||||||
|
|
||||||
|
|
||||||
def test_outputtype_none(resources, outtxt):
|
def test_outputtype_none(resources, outtxt):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
os.devnull,
|
'-',
|
||||||
'--output-type=none',
|
'--output-type=none',
|
||||||
'--sidecar',
|
'--sidecar',
|
||||||
outtxt,
|
outtxt,
|
||||||
@@ -890,4 +939,25 @@ def test_outputtype_none(resources, outtxt):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == ExitCode.ok
|
assert p.returncode == ExitCode.ok
|
||||||
assert outtxt.exists()
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def graph_bad_icc(resources, outdir):
|
||||||
|
synth_input_file = outdir / 'graph-bad-icc.pdf'
|
||||||
|
with pikepdf.open(resources / 'graph.pdf') as pdf:
|
||||||
|
icc = pdf.make_stream(
|
||||||
|
b'invalid icc profile', N=3, Alternate=pikepdf.Name.DeviceRGB
|
||||||
|
)
|
||||||
|
pdf.pages[0].Resources.XObject['/Im0'].ColorSpace = pikepdf.Array(
|
||||||
|
[pikepdf.Name.ICCBased, icc]
|
||||||
|
)
|
||||||
|
pdf.save(synth_input_file)
|
||||||
|
yield synth_input_file
|
||||||
|
|
||||||
|
|
||||||
|
def test_corrupt_icc(graph_bad_icc, outpdf, caplog):
|
||||||
|
result = run_ocrmypdf_api(graph_bad_icc, outpdf)
|
||||||
|
assert result == ExitCode.ok
|
||||||
|
assert any(
|
||||||
|
'corrupt or unreadable ICC profile' in rec.message for rec in caplog.records
|
||||||
|
)
|
||||||
|
|||||||
+23
-13
@@ -6,10 +6,10 @@
|
|||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import warnings
|
||||||
from datetime import timezone
|
from datetime import timezone
|
||||||
from os import fspath
|
from os import fspath
|
||||||
from shutil import copyfile
|
from shutil import copyfile
|
||||||
from unittest.mock import patch
|
|
||||||
|
|
||||||
import pikepdf
|
import pikepdf
|
||||||
import pytest
|
import pytest
|
||||||
@@ -62,7 +62,7 @@ def test_override_metadata(output_type, resources, outpdf):
|
|||||||
german = 'Du siehst den Wald vor lauter Bäumen nicht.'
|
german = 'Du siehst den Wald vor lauter Bäumen nicht.'
|
||||||
chinese = '孔子'
|
chinese = '孔子'
|
||||||
|
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
input_file,
|
input_file,
|
||||||
outpdf,
|
outpdf,
|
||||||
'--title',
|
'--title',
|
||||||
@@ -75,7 +75,7 @@ def test_override_metadata(output_type, resources, outpdf):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert p.returncode == ExitCode.ok, err
|
assert p.returncode == ExitCode.ok, p.stderr
|
||||||
|
|
||||||
before = pikepdf.open(input_file)
|
before = pikepdf.open(input_file)
|
||||||
after = pikepdf.open(outpdf)
|
after = pikepdf.open(outpdf)
|
||||||
@@ -99,7 +99,7 @@ def test_high_unicode(resources, no_outpdf):
|
|||||||
input_file = resources / 'c02-22.pdf'
|
input_file = resources / 'c02-22.pdf'
|
||||||
high_unicode = 'U+1030C is: 𐌌'
|
high_unicode = 'U+1030C is: 𐌌'
|
||||||
|
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
input_file,
|
input_file,
|
||||||
no_outpdf,
|
no_outpdf,
|
||||||
'--subject',
|
'--subject',
|
||||||
@@ -110,7 +110,7 @@ def test_high_unicode(resources, no_outpdf):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
assert p.returncode == ExitCode.bad_args, err
|
assert p.returncode == ExitCode.bad_args, p.stderr
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not fitz, reason="test uses fitz")
|
@pytest.mark.skipif(not fitz, reason="test uses fitz")
|
||||||
@@ -173,6 +173,19 @@ def test_creation_date_preserved(output_type, resources, infile, outpdf):
|
|||||||
assert seconds_between_dates(date_after, datetime.datetime.now(timezone.utc)) < 1000
|
assert seconds_between_dates(date_after, datetime.datetime.now(timezone.utc)) < 1000
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def libxmp_file_to_dict():
|
||||||
|
try:
|
||||||
|
with warnings.catch_warnings():
|
||||||
|
warnings.simplefilter("ignore", DeprecationWarning)
|
||||||
|
from libxmp.utils import (
|
||||||
|
file_to_dict, # pylint: disable=import-outside-toplevel
|
||||||
|
)
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
pytest.skip("libxmp not available or libexempi3 not installed")
|
||||||
|
return file_to_dict
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
'test_file,output_type',
|
'test_file,output_type',
|
||||||
[
|
[
|
||||||
@@ -182,15 +195,12 @@ def test_creation_date_preserved(output_type, resources, infile, outpdf):
|
|||||||
('3small.pdf', 'pdfa'),
|
('3small.pdf', 'pdfa'),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_xml_metadata_preserved(test_file, output_type, resources, outpdf):
|
def test_xml_metadata_preserved(
|
||||||
|
libxmp_file_to_dict, test_file, output_type, resources, outpdf
|
||||||
|
):
|
||||||
input_file = resources / test_file
|
input_file = resources / test_file
|
||||||
|
|
||||||
try:
|
before = libxmp_file_to_dict(str(input_file))
|
||||||
from libxmp.utils import file_to_dict # pylint: disable=import-outside-toplevel
|
|
||||||
except Exception: # pylint: disable=broad-except
|
|
||||||
pytest.skip("libxmp not available or libexempi3 not installed")
|
|
||||||
|
|
||||||
before = file_to_dict(str(input_file))
|
|
||||||
|
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
input_file,
|
input_file,
|
||||||
@@ -202,7 +212,7 @@ def test_xml_metadata_preserved(test_file, output_type, resources, outpdf):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
|
|
||||||
after = file_to_dict(str(outpdf))
|
after = libxmp_file_to_dict(str(outpdf))
|
||||||
|
|
||||||
equal_properties = [
|
equal_properties = [
|
||||||
'dc:contributor',
|
'dc:contributor',
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from PIL import Image, ImageDraw
|
|||||||
from ocrmypdf import optimize as opt
|
from ocrmypdf import optimize as opt
|
||||||
from ocrmypdf._exec import jbig2enc, pngquant
|
from ocrmypdf._exec import jbig2enc, pngquant
|
||||||
from ocrmypdf._exec.ghostscript import rasterize_pdf
|
from ocrmypdf._exec.ghostscript import rasterize_pdf
|
||||||
from ocrmypdf.helpers import Resolution
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution
|
||||||
|
|
||||||
from .conftest import check_ocrmypdf
|
from .conftest import check_ocrmypdf
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ def test_jbig2_lossy(lossy, resources, outpdf):
|
|||||||
'--image-dpi',
|
'--image-dpi',
|
||||||
'200',
|
'200',
|
||||||
'--optimize',
|
'--optimize',
|
||||||
3,
|
'3',
|
||||||
'--jpg-quality',
|
'--jpg-quality',
|
||||||
'50',
|
'50',
|
||||||
'--png-quality',
|
'--png-quality',
|
||||||
@@ -139,8 +139,8 @@ def test_multiple_pngs(resources, outdir):
|
|||||||
img2pdf.convert(
|
img2pdf.convert(
|
||||||
fspath(resources / 'baiona_colormapped.png'),
|
fspath(resources / 'baiona_colormapped.png'),
|
||||||
fspath(resources / 'baiona_gray.png'),
|
fspath(resources / 'baiona_gray.png'),
|
||||||
with_pdfrw=False,
|
|
||||||
outputstream=inpdf,
|
outputstream=inpdf,
|
||||||
|
**IMG2PDF_KWARGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
def mockquant(input_file, output_file, *_args):
|
def mockquant(input_file, output_file, *_args):
|
||||||
|
|||||||
+16
-8
@@ -4,23 +4,31 @@
|
|||||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
import pikepdf
|
import pikepdf
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from ocrmypdf.exceptions import MissingDependencyError
|
||||||
|
|
||||||
from .conftest import check_ocrmypdf
|
from .conftest import check_ocrmypdf
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('optimize', (0, 3))
|
@pytest.mark.parametrize('optimize', (0, 3))
|
||||||
@pytest.mark.parametrize('pdfa_level', (1, 2, 3))
|
@pytest.mark.parametrize('pdfa_level', (1, 2, 3))
|
||||||
def test_pdfa(resources, outpdf, optimize, pdfa_level):
|
def test_pdfa(resources, outpdf, optimize, pdfa_level):
|
||||||
check_ocrmypdf(
|
try:
|
||||||
resources / 'francais.pdf',
|
check_ocrmypdf(
|
||||||
outpdf,
|
resources / 'francais.pdf',
|
||||||
'--plugin',
|
outpdf,
|
||||||
'tests/plugins/tesseract_noop.py',
|
'--plugin',
|
||||||
f'--output-type=pdfa-{pdfa_level}',
|
'tests/plugins/tesseract_noop.py',
|
||||||
f'--optimize={optimize}',
|
f'--output-type=pdfa-{pdfa_level}',
|
||||||
)
|
f'--optimize={optimize}',
|
||||||
|
)
|
||||||
|
except MissingDependencyError as e:
|
||||||
|
if 'pngquant' in str(e) and optimize in (2, 3) and os.name == 'nt':
|
||||||
|
pytest.xfail("pngquant currently not available on Windows")
|
||||||
if pdfa_level in (2, 3):
|
if pdfa_level in (2, 3):
|
||||||
# PDF/A-2 allows ObjStm
|
# PDF/A-2 allows ObjStm
|
||||||
assert b'/ObjStm' in outpdf.read_bytes()
|
assert b'/ObjStm' in outpdf.read_bytes()
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from reportlab.pdfgen.canvas import Canvas
|
|||||||
|
|
||||||
from ocrmypdf import pdfinfo
|
from ocrmypdf import pdfinfo
|
||||||
from ocrmypdf.exceptions import InputFileError
|
from ocrmypdf.exceptions import InputFileError
|
||||||
from ocrmypdf.helpers import Resolution
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution
|
||||||
from ocrmypdf.pdfinfo import Colorspace, Encoding
|
from ocrmypdf.pdfinfo import Colorspace, Encoding
|
||||||
from ocrmypdf.pdfinfo.layout import PDFPage
|
from ocrmypdf.pdfinfo.layout import PDFPage
|
||||||
|
|
||||||
@@ -67,9 +67,9 @@ def test_single_page_image(eight_by_eight, outpdf):
|
|||||||
img2pdf.convert(
|
img2pdf.convert(
|
||||||
bio,
|
bio,
|
||||||
producer="img2pdf",
|
producer="img2pdf",
|
||||||
with_pdfrw=False,
|
|
||||||
layout_fun=layout_fun,
|
layout_fun=layout_fun,
|
||||||
outputstream=f,
|
outputstream=f,
|
||||||
|
**IMG2PDF_KWARGS,
|
||||||
)
|
)
|
||||||
info = pdfinfo.PdfInfo(outpdf)
|
info = pdfinfo.PdfInfo(outpdf)
|
||||||
|
|
||||||
@@ -173,17 +173,15 @@ def test_stack_abuse():
|
|||||||
p = pikepdf.Pdf.new()
|
p = pikepdf.Pdf.new()
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q ' * 35)
|
stream = pikepdf.Stream(p, b'q ' * 35)
|
||||||
with pytest.warns(None) as record:
|
with pytest.warns(UserWarning, match="overflowed"):
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
assert 'overflowed' in str(record[0].message)
|
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q Q Q Q Q')
|
stream = pikepdf.Stream(p, b'q Q Q Q Q')
|
||||||
with pytest.warns(None) as record:
|
with pytest.warns(UserWarning, match="underflowed") as record:
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
assert 'underflowed' in str(record[0].message)
|
|
||||||
|
|
||||||
stream = pikepdf.Stream(p, b'q ' * 135)
|
stream = pikepdf.Stream(p, b'q ' * 135)
|
||||||
with pytest.warns(None):
|
with pytest.warns(UserWarning):
|
||||||
with pytest.raises(RuntimeError):
|
with pytest.raises(RuntimeError):
|
||||||
pdfinfo.info._interpret_contents(stream)
|
pdfinfo.info._interpret_contents(stream)
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ def test_deskew(resources, outdir):
|
|||||||
assert -0.5 < skew_angle < 0.5, "Deskewing failed"
|
assert -0.5 < skew_angle < 0.5, "Deskewing failed"
|
||||||
|
|
||||||
|
|
||||||
|
def test_deskew_blank_page(resources, outpdf):
|
||||||
|
# Tesseract doesn't like blank pages - make sure we can get through
|
||||||
|
check_ocrmypdf(resources / 'blank.pdf', outpdf, '--deskew')
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="remove background disabled")
|
@pytest.mark.xfail(reason="remove background disabled")
|
||||||
def test_remove_background(resources, outdir):
|
def test_remove_background(resources, outdir):
|
||||||
# Ensure the input image does not contain pure white/black
|
# Ensure the input image does not contain pure white/black
|
||||||
|
|||||||
+11
-4
@@ -18,13 +18,19 @@ from reportlab.pdfgen.canvas import Canvas
|
|||||||
|
|
||||||
from ocrmypdf._exec import ghostscript
|
from ocrmypdf._exec import ghostscript
|
||||||
from ocrmypdf._plugin_manager import get_plugin_manager
|
from ocrmypdf._plugin_manager import get_plugin_manager
|
||||||
from ocrmypdf.helpers import Resolution
|
from ocrmypdf.helpers import IMG2PDF_KWARGS, Resolution
|
||||||
from ocrmypdf.pdfinfo import PdfInfo
|
from ocrmypdf.pdfinfo import PdfInfo
|
||||||
|
|
||||||
from .conftest import check_ocrmypdf, run_ocrmypdf
|
from .conftest import check_ocrmypdf, run_ocrmypdf
|
||||||
|
|
||||||
# pylintx: disable=unused-variable
|
# pylintx: disable=unused-variable
|
||||||
|
|
||||||
|
# Remove this workaround when we require Pillow >= 10
|
||||||
|
try:
|
||||||
|
Transpose = Image.Transpose # type: ignore
|
||||||
|
except AttributeError:
|
||||||
|
# Pillow 9 shim
|
||||||
|
Transpose = Image # type: ignore
|
||||||
|
|
||||||
RENDERERS = ['hocr', 'sandwich']
|
RENDERERS = ['hocr', 'sandwich']
|
||||||
|
|
||||||
@@ -222,7 +228,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
|
|||||||
with Image.open(fspath(resources / 'typewriter.png')) as im:
|
with Image.open(fspath(resources / 'typewriter.png')) as im:
|
||||||
if image_angle != 0:
|
if image_angle != 0:
|
||||||
ccw_angle = -image_angle % 360
|
ccw_angle = -image_angle % 360
|
||||||
im = im.transpose(getattr(Image, f'ROTATE_{ccw_angle}'))
|
im = im.transpose(getattr(Transpose, f'ROTATE_{ccw_angle}'))
|
||||||
im.save(memimg, format='PNG')
|
im.save(memimg, format='PNG')
|
||||||
memimg.seek(0)
|
memimg.seek(0)
|
||||||
mempdf = BytesIO()
|
mempdf = BytesIO()
|
||||||
@@ -230,6 +236,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
|
|||||||
memimg.read(),
|
memimg.read(),
|
||||||
layout_fun=img2pdf.get_fixed_dpi_layout_fun((200, 200)),
|
layout_fun=img2pdf.get_fixed_dpi_layout_fun((200, 200)),
|
||||||
outputstream=mempdf,
|
outputstream=mempdf,
|
||||||
|
**IMG2PDF_KWARGS,
|
||||||
)
|
)
|
||||||
mempdf.seek(0)
|
mempdf.seek(0)
|
||||||
pike = pikepdf.open(mempdf)
|
pike = pikepdf.open(mempdf)
|
||||||
@@ -242,7 +249,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
|
|||||||
test = make_rotate_test('test', image_angle, page_angle)
|
test = make_rotate_test('test', image_angle, page_angle)
|
||||||
out = test.with_suffix('.out.pdf')
|
out = test.with_suffix('.out.pdf')
|
||||||
|
|
||||||
p, _, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
test,
|
test,
|
||||||
out,
|
out,
|
||||||
'-O0',
|
'-O0',
|
||||||
@@ -251,7 +258,7 @@ def test_rotate_page_level(image_angle, page_angle, resources, outdir):
|
|||||||
'0.001',
|
'0.001',
|
||||||
text=False,
|
text=False,
|
||||||
)
|
)
|
||||||
err = err.decode('utf-8', errors='replace')
|
err = p.stderr.decode('utf-8', errors='replace')
|
||||||
assert p.returncode == 0, err
|
assert p.returncode == 0, err
|
||||||
|
|
||||||
assert compare_images_monochrome(outdir, reference, 1, out, 1) > 0.2
|
assert compare_images_monochrome(outdir, reference, 1, out, 1) > 0.2
|
||||||
|
|||||||
+8
-8
@@ -30,12 +30,12 @@ def test_stdin(ocrmypdf_exec, resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
]
|
]
|
||||||
run(p_args, stdout=PIPE, stderr=PIPE, stdin=input_stream, check=True)
|
run(p_args, capture_output=True, stdin=input_stream, check=True)
|
||||||
|
|
||||||
|
|
||||||
def test_stdout(ocrmypdf_exec, resources, outpdf):
|
def test_stdout(ocrmypdf_exec, resources, outpdf):
|
||||||
if 'COV_CORE_DATAFILE' in os.environ:
|
if 'COV_CORE_DATAFILE' in os.environ:
|
||||||
pytest.skip(msg="Coverage uses stdout")
|
pytest.skip("Coverage uses stdout")
|
||||||
|
|
||||||
input_file = str(resources / 'francais.pdf')
|
input_file = str(resources / 'francais.pdf')
|
||||||
output_file = str(outpdf)
|
output_file = str(outpdf)
|
||||||
@@ -60,10 +60,10 @@ def test_stdout(ocrmypdf_exec, resources, outpdf):
|
|||||||
@pytest.mark.skipif(os.name == 'nt', reason="invalid test on Windows")
|
@pytest.mark.skipif(os.name == 'nt', reason="invalid test on Windows")
|
||||||
def test_bad_locale(monkeypatch):
|
def test_bad_locale(monkeypatch):
|
||||||
monkeypatch.setenv('LC_ALL', 'C')
|
monkeypatch.setenv('LC_ALL', 'C')
|
||||||
p, out, err = run_ocrmypdf('a', 'b')
|
p = run_ocrmypdf('a', 'b')
|
||||||
assert out == '', "stdout not clean"
|
assert p.stdout == '', "stdout not clean"
|
||||||
assert p.returncode != 0
|
assert p.returncode != 0
|
||||||
assert 'configured to use ASCII as encoding' in err, "should whine"
|
assert 'configured to use ASCII as encoding' in p.stderr, "should whine"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.xfail(
|
@pytest.mark.xfail(
|
||||||
@@ -72,9 +72,9 @@ def test_bad_locale(monkeypatch):
|
|||||||
)
|
)
|
||||||
def test_dev_null(resources):
|
def test_dev_null(resources):
|
||||||
if 'COV_CORE_DATAFILE' in os.environ:
|
if 'COV_CORE_DATAFILE' in os.environ:
|
||||||
pytest.skip(msg="Coverage uses stdout")
|
pytest.skip("Coverage uses stdout")
|
||||||
|
|
||||||
p, out, _err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / 'trivial.pdf',
|
resources / 'trivial.pdf',
|
||||||
os.devnull,
|
os.devnull,
|
||||||
'--force-ocr',
|
'--force-ocr',
|
||||||
@@ -82,4 +82,4 @@ def test_dev_null(resources):
|
|||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert p.returncode == 0, "could not send output to /dev/null"
|
assert p.returncode == 0, "could not send output to /dev/null"
|
||||||
assert len(out) == 0, "wrote to stdout"
|
assert len(p.stdout) == 0, "wrote to stdout"
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ def test_image_too_large_hocr(monkeypatch, resources, outdir):
|
|||||||
tessconfig=[],
|
tessconfig=[],
|
||||||
timeout=180.0,
|
timeout=180.0,
|
||||||
pagesegmode=None,
|
pagesegmode=None,
|
||||||
|
thresholding=0,
|
||||||
user_words=None,
|
user_words=None,
|
||||||
user_patterns=None,
|
user_patterns=None,
|
||||||
)
|
)
|
||||||
@@ -102,6 +103,7 @@ def test_image_too_large_pdf(monkeypatch, resources, outdir):
|
|||||||
tessconfig=[],
|
tessconfig=[],
|
||||||
timeout=180.0,
|
timeout=180.0,
|
||||||
pagesegmode=None,
|
pagesegmode=None,
|
||||||
|
thresholding=0,
|
||||||
user_words=None,
|
user_words=None,
|
||||||
user_patterns=None,
|
user_patterns=None,
|
||||||
)
|
)
|
||||||
|
|||||||
+26
-8
@@ -5,19 +5,24 @@
|
|||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
|
||||||
|
import logging
|
||||||
from os import fspath
|
from os import fspath
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
from ocrmypdf._exec import unpaper
|
||||||
from ocrmypdf._plugin_manager import get_parser_options_plugins
|
from ocrmypdf._plugin_manager import get_parser_options_plugins
|
||||||
from ocrmypdf._validation import check_options
|
from ocrmypdf._validation import check_options
|
||||||
from ocrmypdf.exceptions import ExitCode, MissingDependencyError
|
from ocrmypdf.exceptions import ExitCode, MissingDependencyError
|
||||||
|
|
||||||
from .conftest import check_ocrmypdf, have_unpaper, run_ocrmypdf
|
from .conftest import check_ocrmypdf, have_unpaper, ocrmypdf_exec, run_ocrmypdf
|
||||||
|
|
||||||
# pylint: disable=redefined-outer-name
|
# pylint: disable=redefined-outer-name
|
||||||
|
|
||||||
|
needs_unpaper = pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
||||||
|
|
||||||
|
|
||||||
def test_no_unpaper(resources, no_outpdf):
|
def test_no_unpaper(resources, no_outpdf):
|
||||||
input_ = fspath(resources / "c02-22.pdf")
|
input_ = fspath(resources / "c02-22.pdf")
|
||||||
@@ -45,7 +50,7 @@ def test_old_unpaper(resources, no_outpdf):
|
|||||||
mock.assert_called()
|
mock.assert_called()
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
@needs_unpaper
|
||||||
def test_clean(resources, outpdf):
|
def test_clean(resources, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / "skew.pdf",
|
resources / "skew.pdf",
|
||||||
@@ -56,7 +61,7 @@ def test_clean(resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
@needs_unpaper
|
||||||
def test_unpaper_args_valid(resources, outpdf):
|
def test_unpaper_args_valid(resources, outpdf):
|
||||||
check_ocrmypdf(
|
check_ocrmypdf(
|
||||||
resources / "skew.pdf",
|
resources / "skew.pdf",
|
||||||
@@ -69,9 +74,9 @@ def test_unpaper_args_valid(resources, outpdf):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
@needs_unpaper
|
||||||
def test_unpaper_args_invalid_filename(resources, outpdf):
|
def test_unpaper_args_invalid_filename(resources, outpdf):
|
||||||
p, _out, err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / "skew.pdf",
|
resources / "skew.pdf",
|
||||||
outpdf,
|
outpdf,
|
||||||
"-c",
|
"-c",
|
||||||
@@ -80,13 +85,13 @@ def test_unpaper_args_invalid_filename(resources, outpdf):
|
|||||||
'--plugin',
|
'--plugin',
|
||||||
'tests/plugins/tesseract_noop.py',
|
'tests/plugins/tesseract_noop.py',
|
||||||
)
|
)
|
||||||
assert "No filenames allowed" in err
|
assert "No filenames allowed" in p.stderr
|
||||||
assert p.returncode == ExitCode.bad_args
|
assert p.returncode == ExitCode.bad_args
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skipif(not have_unpaper(), reason="requires unpaper")
|
@needs_unpaper
|
||||||
def test_unpaper_args_invalid(resources, outpdf):
|
def test_unpaper_args_invalid(resources, outpdf):
|
||||||
p, _out, _err = run_ocrmypdf(
|
p = run_ocrmypdf(
|
||||||
resources / "skew.pdf",
|
resources / "skew.pdf",
|
||||||
outpdf,
|
outpdf,
|
||||||
"-c",
|
"-c",
|
||||||
@@ -98,3 +103,16 @@ def test_unpaper_args_invalid(resources, outpdf):
|
|||||||
# Can't tell difference between unpaper choking on bad arguments or some
|
# Can't tell difference between unpaper choking on bad arguments or some
|
||||||
# other unpaper failure
|
# other unpaper failure
|
||||||
assert p.returncode == ExitCode.child_process_error
|
assert p.returncode == ExitCode.child_process_error
|
||||||
|
|
||||||
|
|
||||||
|
@needs_unpaper
|
||||||
|
def test_unpaper_image_too_big(resources, outdir, caplog):
|
||||||
|
with patch('ocrmypdf._exec.unpaper.UNPAPER_IMAGE_PIXEL_LIMIT', 42):
|
||||||
|
infile = resources / 'crom.png'
|
||||||
|
unpaper.clean(infile, outdir / 'out.png', dpi=300) == infile
|
||||||
|
|
||||||
|
assert any(
|
||||||
|
'too large for cleaning' in rec.message
|
||||||
|
for rec in caplog.get_records('call')
|
||||||
|
if rec.levelno == logging.WARNING
|
||||||
|
)
|
||||||
|
|||||||
@@ -103,13 +103,19 @@ def test_user_words(caplog):
|
|||||||
opts = make_opts(user_words='foo')
|
opts = make_opts(user_words='foo')
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
plugin_manager = get_plugin_manager(opts.plugins)
|
||||||
vd._check_options(opts, plugin_manager, set())
|
vd._check_options(opts, plugin_manager, set())
|
||||||
assert '4.0 ignores --user-words' in caplog.text
|
assert (
|
||||||
|
'Tesseract 4.0 (which you have installed) ignores --user-words'
|
||||||
|
in caplog.text
|
||||||
|
)
|
||||||
caplog.clear()
|
caplog.clear()
|
||||||
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=True):
|
with patch('ocrmypdf._exec.tesseract.has_user_words', return_value=True):
|
||||||
opts = make_opts(user_patterns='foo')
|
opts = make_opts(user_patterns='foo')
|
||||||
plugin_manager = get_plugin_manager(opts.plugins)
|
plugin_manager = get_plugin_manager(opts.plugins)
|
||||||
vd._check_options(opts, plugin_manager, set())
|
vd._check_options(opts, plugin_manager, set())
|
||||||
assert '4.0 ignores --user-words' not in caplog.text
|
assert (
|
||||||
|
'Tesseract 4.0 (which you have installed) ignores --user-words'
|
||||||
|
not in caplog.text
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_pillow_options():
|
def test_pillow_options():
|
||||||
|
|||||||
Reference in New Issue
Block a user