Compare commits

...
34 Commits
Author SHA1 Message Date
James R. Barlow 657445fbb6 Fix travis.yml 2018-10-28 18:14:18 -07:00
James R. Barlow 0ff489c755 v6.2.5 notes 2018-10-28 16:21:45 -07:00
James R. Barlow e50721d561 Drop support for PyMuPDF 2018-10-28 16:21:45 -07:00
James R. Barlow 87326cc15e Remove macOS from testing entirely 2018-10-28 16:21:41 -07:00
James R. Barlow d6d2f196d5 Disable failing test for tess 4.0rc1 2018-10-28 15:16:54 -07:00
James R. Barlow a9cd5bf253 v6.2.4 release notes 2018-09-16 15:40:05 -07:00
James R. Barlow 8102ca1075 Backport blacklist of Ghostscript 9.24 2018-09-16 15:06:54 -07:00
James R. Barlow 6b5934ff4e Ghostscript: disable JPEG passthrough for ocrmypdf v6.x
This feature works for gs >= 9.24, but we don't want to change behavior in
a backport, so we're disabling it unconditionally.
2018-09-16 14:56:50 -07:00
James R. Barlow e38b30af1c Ghostscript: fix issues in strict ASCII implementation
(Cherry-pick this back to v7.x)

Ghostscript strict ASCII: Filter out NULs too
2018-09-16 14:56:46 -07:00
James R. Barlow f64712322e Cherrypick Ghostscript 9.25 DOCINFO fixes from 7.x
From 517b385fe5

Partial revert cherrypicking of Ghostscript 9.24 change

To revert use of pikepdf API.
2018-09-16 14:48:44 -07:00
James R. Barlow 1d09061130 Revert previous commit amd reject input images with alpha channel
Decided on this for simplicity of old release branch.

Modifies baiona.png by stripping
alpha, adds baiona_alpha which
includes the alpha.
2018-07-31 23:45:28 -07:00
James R. Barlow a2203b2447 Discard alpha channel when triaging images 2018-07-25 22:23:41 -04:00
James R. Barlow 6a302fdb88 Travis: nevermind xenial, then
Gave a weird build error
2018-07-12 03:27:40 -07:00
James R. Barlow 1d9cc239ee Travis: Fix v6 build failures 2018-07-12 03:22:03 -07:00
James R. Barlow d240fc1ea6 Update release notes for v6.2.2 2018-07-12 03:07:47 -07:00
James R. Barlow e7d21dd826 Skip locale check on Python 3.7 2018-07-12 03:03:34 -07:00
James R. Barlow e774b4650b ocrmypdf.exec: trap FileNotFoundError too 2018-07-12 03:01:01 -07:00
James R. Barlow 8f8e6dcdd4 Fix problem iterating ruffus exceptions and rotate-pages-threshold parameter validation 2018-07-12 02:59:38 -07:00
James R. Barlow 5252b88f0f Travis: modernize with v7.0.0 updates
Travis: add 3.7 testing
Travis: remove deploy to testpypi since it's broken
Travis: cherry-pick change to declarative APT
From efb9572
Removed pngquant
Travis: Remove linux_before_install.sh
Eliminate Homebrew autobrewing
2018-07-12 02:47:29 -07:00
James R. Barlow ea69883386 Tests: Speed up a slow test (cherry-picked from v7) 2018-07-12 02:47:15 -07:00
James R. Barlow eb343b1e37 Tests: Add ability to disable use of cache (cherrypicked from v7) 2018-07-12 02:46:53 -07:00
James R. Barlow 9f02de55be main: do better parameter validation 2018-07-12 02:46:52 -07:00
James R. Barlow 7394a4cf49 Cherrypick warning about --user-words not having any effect
Might be available in full release of Tess4
2018-07-12 02:46:34 -07:00
James R. Barlow ed9fb110b1 Fix a comment about Tesseract behavior in certain versions 2018-07-12 02:46:34 -07:00
James R. Barlow 4650074428 Cherrypick Python 3.7 documentation updates from v7.0.0
From b0eacd6
2018-07-12 02:45:51 -07:00
James R. Barlow 70aa644c10 Backport Python 3.7 fix for ruffus 2.7.0 from ocrmypdf v7.0.0 2018-07-12 02:45:51 -07:00
James R. Barlow 2ccb3edc58 Ignore masks when deciding what color to rasterize at 2018-07-12 02:45:51 -07:00
James R. Barlow 1f40a70554 Use qpdf 8.0.2 backport, force old pytest-timeout to fix build 2018-06-23 03:14:18 -07:00
James R. Barlow e14ffbf03f v6.2.1 release notes 2018-06-23 03:01:54 -07:00
James R. Barlow 25a1dde57c Fix recent versions of tesseract not registering as textonly_pdf
This change happened sometime after the 4.0.0-beta1 release in
Ubuntu 18.04
2018-06-23 02:59:22 -07:00
jbarlow83andGitHub 1d10eac764 Add wiki link to issue template
[ci skip]
2018-06-06 12:59:59 -07:00
jbarlow83andGitHub 3f868118cd Remove gpg
[ci skip]
2018-06-06 12:58:02 -07:00
Fabian Rodriguezandjbarlow83 c7cf041e4a Fixed language option example (French) (#266)
Replace fre to fra.
2018-05-10 00:10:27 -07:00
James R. Barlow 38ab03655b Restore unpaper
It's a suggested/recommended dep not required in Deb/Ubu.
2018-05-06 21:36:12 -07:00
27 changed files with 389 additions and 394 deletions
+1
View File
@@ -4,6 +4,7 @@ FROM ubuntu:18.04
RUN apt-get update && apt-get install -y --no-install-recommends \
ocrmypdf \
unpaper \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-deu \
+1 -8
View File
@@ -2,11 +2,4 @@ Please include the command line and a test file with your issue report.
If possible, please use a test file that we can include in future test cases (no personal information, no copyrighted material).
If you wish to encrypt a test file so that only the maintainer of OCRmyPDF can view it, you may use:
```bash
gpg --recv-keys 4434eb74c4a35f7f --keyserver pgp.mit.edu
gpg --output test.pdf.gpg --encrypt --receipient 4434eb74c4a35f7f test.pdf
```
If you wish to encrypt a test file for the OCRmyPDF maintainer only, see the [Wiki](https://github.com/jbarlow83/OCRmyPDF/wiki).
+4 -1
View File
@@ -2,12 +2,14 @@
*.pyc
*.sublime-*
venv*/
.venv/
pyvenv.cfg
tasks.py
.bash_history
.ruffus_history.sqlite
.idea/
.pytest_cache/
.pylintrc
# Package building
*.egg-info/
@@ -41,4 +43,5 @@ tmp/
pdfbox-app*.jar
.vscode/
IDEAS
_Dockerfile.local
_Dockerfile.local
/scratch.py
+29 -49
View File
@@ -1,47 +1,54 @@
dist: trusty
language: python
cache:
ccache: true
pip: true
directories:
- $HOME/Library/Caches/Homebrew
env:
global:
- secure: "hsf6MT+n2x3OiDM2fQyJZdV0/PWYmv81LdVqC6cfnHBE/8N3DloJRqQ7WfO14TxhiK9PEC7MpyCj0lSabUHEO7gSH6Vks6I1asoSkt8S9/bSMlhT4hei+pwVpeGEiU5xHVATNjY+D919VC3IFvc3XmjT74h/2SLhaZ+jhEmDggM=" # HOMEBREW_OCRMYPDF_TOKEN
addons:
apt:
update: true
sources:
- sourceline: 'ppa:alex-p/tesseract-ocr'
- sourceline: 'ppa:heyarje/libav-11'
- sourceline: 'ppa:vshn/ghostscript'
packages:
- ghostscript
- libavcodec56
- libavformat56
- libavutil54
- libffi-dev
- poppler-utils
- qpdf
- tesseract-ocr
- tesseract-ocr-deu
- tesseract-ocr-eng
- tesseract-ocr-fra
matrix:
include:
- os: linux
sudo: required
language: python
python: 3.5
env: EXTRAS=
python: "3.5"
- os: linux
sudo: required
language: python
python: 3.6
env: EXTRAS=
python: "3.6"
- os: linux
sudo: required
language: python
python: 3.6
env: EXTRAS=[fitz]
- os: osx
osx_image: xcode8
language: generic
env: EXTRAS=
- os: osx
osx_image: xcode8
language: generic
env: EXTRAS=[fitz]
python: "3.7-dev"
before_cache:
- rm -f $HOME/.cache/pip/log/debug.log
before_install: |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
bash .travis/linux_before_install.sh
pip install --upgrade pip
mkdir -p packages
wget -q 'https://www.dropbox.com/s/vaq0kbwi6e6au80/unpaper_6.1-1.deb?raw=1' -O packages/unpaper_6.1-1.deb
sudo dpkg -i packages/unpaper_6.1-1.deb
elif [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
brew update && brew bundle --file=.travis/Brewfile
pip3 install --upgrade pip
@@ -49,7 +56,8 @@ before_install: |
fi
install:
- pip3 install ".$EXTRAS"
- pip3 install pycparser # py3.7 workaround for https://github.com/eliben/pycparser/issues/251
- pip3 install .
- pip3 install -r test_requirements.txt
script:
@@ -71,33 +79,5 @@ deploy:
on:
branch: master
tags: true
condition: $TRAVIS_PYTHON_VERSION == "3.6" && $TRAVIS_OS_NAME == "linux" && $EXTRAS == ""
condition: $TRAVIS_PYTHON_VERSION == "3.6" && $TRAVIS_OS_NAME == "linux"
skip_upload_docs: true
# test pypi
- provider: pypi
server: https://testpypi.pypi.org/legacy/
user: ocrmypdf-travis
password:
secure: "DTFOmmNL6olA0+yXvp4u9jXZlZeqrJsJ0526jzqf4a3gZ6jnGTq5UI6WzRsslSyoMMfXKtHQebqHM6ogSgCZinyZ3ufHJo8fn9brxbEc2gsiWkbj5o3bGwdWMT1vNNE7XW0VCpw87rZ1EEwjl4FJHFudMlPR1yfU5+uq0k0PACo="
distributions: "sdist"
on:
branch: develop
tags: false
condition: $TRAVIS_OS_NAME == "osx"
skip_upload_docs: true
# null deploy for osx
# we really just want to run after_deploy *after* pypi upload is done, but
# after_deploy on runs if a given box deployed
- provider: script
script: /usr/bin/true
on:
branch: master
tags: true
condition: $TRAVIS_OS_NAME == "osx"
after_deploy: |
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
bash .travis/osx_brew.sh
fi
-93
View File
@@ -1,93 +0,0 @@
#!/usr/bin/env python3
# © 2017-18 James R. Barlow: github.com/jbarlow83
from string import Template
from subprocess import run, PIPE
import re
recipe_template = Template("""
class Ocrmypdf < Formula
include Language::Python::Virtualenv
desc "Adds an OCR text layer to scanned PDF files"
homepage "https://github.com/jbarlow83/OCRmyPDF"
${ocrmypdf_url}
${ocrmypdf_sha256}
depends_on "pkg-config" => :build
depends_on "mupdf-tools" => :build # statically links libmupdf.a
depends_on "freetype"
depends_on "ghostscript"
depends_on "jpeg"
depends_on "libpng"
depends_on "python"
depends_on "qpdf"
depends_on "tesseract"
depends_on "unpaper"
${resources}
def install
venv = virtualenv_create(libexec, "python3")
resource("Pillow").stage do
inreplace "setup.py" do |s|
sdkprefix = MacOS::CLT.installed? ? "" : MacOS.sdk_path
s.gsub! "openjpeg.h", "probably_not_a_header_called_this_eh.h"
s.gsub! "ZLIB_ROOT = None", "ZLIB_ROOT = ('#{sdkprefix}/usr/lib', '#{sdkprefix}/usr/include')"
s.gsub! "JPEG_ROOT = None", "JPEG_ROOT = ('#{Formula["jpeg"].opt_prefix}/lib', '#{Formula["jpeg"].opt_prefix}/include')"
s.gsub! "FREETYPE_ROOT = None", "FREETYPE_ROOT = ('#{Formula["freetype"].opt_prefix}/lib', '#{Formula["freetype"].opt_prefix}/include')"
end
# avoid triggering "helpful" distutils code that doesn't recognize Xcode 7 .tbd stubs
ENV.append "CFLAGS", "-I#{MacOS.sdk_path}/System/Library/Frameworks/Tk.framework/Versions/8.5/Headers" unless MacOS::CLT.installed?
venv.pip_install Pathname.pwd
end
res = resources.map(&:name).to_set - ["Pillow"]
res.each do |r|
venv.pip_install resource(r)
end
venv.pip_install_and_link buildpath
end
test do
# Since we use Python 3, we require a UTF-8 locale
ENV["LC_ALL"] = "en_US.UTF-8"
system "#{bin}/ocrmypdf", "-f", "-q", "--deskew",
test_fixtures("test.pdf"), "ocr.pdf"
assert_predicate testpath/"ocr.pdf", :exist?
end
end
""")
def main():
p = run(['poet', '--single', 'ocrmypdf'],
encoding='utf-8', stdout=PIPE, check=True)
ocrmypdf_lines = p.stdout.splitlines()
ocrmypdf_url = ocrmypdf_lines[1].strip()
ocrmypdf_sha256 = ocrmypdf_lines[2].strip()
ocrmypdf_version = re.search(
r'ocrmypdf-(.+)\.tar.*', ocrmypdf_url).group(1)
print(f"Autobrewing {ocrmypdf_version}")
p = run(['poet', '--resources', 'ocrmypdf'],
encoding='utf-8', stdout=PIPE, check=True)
poet_resources = p.stdout
# Remove the duplicate "ocrmypdf" resource block
all_resources = poet_resources.split('resource')
kept_resources = [block for block in all_resources if 'ocrmypdf' not in block]
resources = 'resource'.join(kept_resources)
with open('ocrmypdf.rb', 'w') as out:
out.write(recipe_template.substitute(**locals()))
if __name__ == '__main__':
main()
-42
View File
@@ -1,42 +0,0 @@
#!/bin/bash
# © 2017 James R. Barlow: github.com/jbarlow83
set -euo pipefail
set -x
sudo add-apt-repository ppa:vshn/ghostscript -y
sudo add-apt-repository ppa:heyarje/libav-11 -y
sudo apt-get update -qq
sudo apt-get install -y \
ghostscript \
poppler-utils \
libavformat56 \
libavcodec56 \
libavutil54 \
libffi-dev
sudo add-apt-repository ppa:alex-p/tesseract-ocr -y
sudo apt-get update
sudo apt-get autoremove -y
sudo apt-get install -y --no-install-recommends \
tesseract-ocr \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-deu
pip install --upgrade pip
mkdir -p packages
wget -q 'https://www.dropbox.com/s/vaq0kbwi6e6au80/unpaper_6.1-1.deb?raw=1' -O packages/unpaper_6.1-1.deb
sudo dpkg -i packages/unpaper_6.1-1.deb
if [ ! -f /usr/local/bin/qpdf ]; then
export QPDF_RELEASE='https://github.com/qpdf/qpdf/releases/download/release-qpdf-8.0.2/qpdf-8.0.2.tar.gz'
mkdir qpdf
wget -q $QPDF_RELEASE -O - | tar xz -C qpdf --strip-components=1
cd qpdf/
export PATH="/usr/local/opt/ccache/libexec:$PATH"
./configure --prefix=/usr
make -j 2
sudo make install
cd ..
fi
-23
View File
@@ -1,23 +0,0 @@
#!/bin/bash
# © 2017 James R. Barlow: github.com/jbarlow83
set -uo pipefail
set -x
pip3 install homebrew-pypi-poet
python3 .travis/autobrew.py
cat ocrmypdf.rb
# brew audit crashes Travis
#brew audit ocrmypdf.rb
# Important: disable debug output so token is hidden
set +x
git clone https://$HOMEBREW_OCRMYPDF_TOKEN@github.com/jbarlow83/homebrew-ocrmypdf.git
set -x
pushd homebrew-ocrmypdf
cp ../ocrmypdf.rb Formula/ocrmypdf.rb
git add Formula/ocrmypdf.rb
git commit -m "homebrew-ocrmypdf: automatic release $TRAVIS_BUILD_NUMBER $TRAVIS_TAG"
git push origin master
popd
+7 -9
View File
@@ -4,10 +4,10 @@ OCRmyPDF
.. image:: https://travis-ci.org/jbarlow83/OCRmyPDF.svg?branch=master
:target: https://travis-ci.org/jbarlow83/OCRmyPDF
.. image:: https://img.shields.io/pypi/v/ocrmypdf.svg
.. image:: https://img.shields.io/pypi/v/ocrmypdf.svg
:target: https://pypi.org/project/ocrmypdf/
.. image:: https://img.shields.io/homebrew/v/ocrmypdf.svg
.. image:: https://img.shields.io/homebrew/v/ocrmypdf.svg
:alt: homebrew
:target: http://brewformulas.org/Ocrmypdf
@@ -54,7 +54,7 @@ Motivation
I searched the web for a free command line tool to OCR PDF files on
Linux/UNIX: I found many, but none of them were really satisfying.
- Either they produced PDF files with misplaced text under the image (making copy/paste impossible)
- Either they produced PDF files with misplaced text under the image (making copy/paste impossible)
- Or they did not handle accents and multilingual characters
- Or they changed the resolution of the embedded images
- Or they generated ridiculously large PDF files
@@ -63,7 +63,7 @@ Linux/UNIX: I found many, but none of them were really satisfying.
- On top of that none of them produced PDF/A files (format dedicated for long time storage)
...so I decided to develop my own tool (using various existing scripts
as an inspiration).
as an inspiration).
Installation
------------
@@ -97,7 +97,7 @@ you can often find packages that provide language packs:
# Debian/Ubuntu users
apt-get install tesseract-ocr-chi-sim # Example: Install Chinese Simplified language back
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.
@@ -126,9 +126,7 @@ If you detect an issue, please:
Requirements
------------
Runs on CPython 3.6, and requires external program installations of Ghostscript, Tesseract OCR, QPDF, and Leptonica. ocrmypdf is pure Python, but uses CFFI to portably generate library bindings.
Python 3.5 is also supported.
Runs on CPython 3.5, 3.6 and 3.7. Requires external program installations of Ghostscript, Tesseract OCR, QPDF, and Leptonica. ocrmypdf is pure Python, but uses CFFI to portably generate library bindings.
Press & Media
-------------
@@ -142,7 +140,7 @@ Press & Media
License
-------
The OCRmyPDF software is licensed under the GNU GPLv3. Certain files are covered by other licenses, as noted in their source files.
The OCRmyPDF software is licensed under the GNU GPLv3. Certain files are covered by other licenses, as noted in their source files.
The license for each test file varies, and is noted in tests/resources/README.rst. The documentation is licensed under Creative Commons Attribution-ShareAlike 4.0 (CC-BY-SA 4.0).
+2 -2
View File
@@ -65,8 +65,8 @@ By default OCRmyPDF assumes the document is English.
.. code-block:: bash
ocrmypdf -l fre LeParisien.pdf LeParisien.pdf
ocrmypdf -l eng+fre Bilingual-English-French.pdf Bilingual-English-French.pdf
ocrmypdf -l fra LeParisien.pdf LeParisien.pdf
ocrmypdf -l eng+fra Bilingual-English-French.pdf Bilingual-English-French.pdf
Language packs must be installed for all languages specified. See :ref:`Installing additional language packs <lang-packs>`.
+16 -23
View File
@@ -1,7 +1,7 @@
Installation
============
The easiest way to install OCRmyPDF to follow the steps for your operating system/platform.
The easiest way to install OCRmyPDF to follow the steps for your operating system/platform.
If you want to use the latest version of OCRmyPDF, your best bet is to install the most recent version your platform provides, and then upgrade that version by installing the Python binary wheels.
@@ -23,7 +23,7 @@ To see what versions are available, check the `Debian Package Tracker <https://t
Installing on macOS with Homebrew
---------------------------------
.. image:: https://img.shields.io/homebrew/v/ocrmypdf.svg
.. image:: https://img.shields.io/homebrew/v/ocrmypdf.svg
:alt: homebrew
:target: http://brewformulas.org/Ocrmypdf
@@ -55,7 +55,7 @@ Follow the Docker installation instructions for your platform. If you can run t
.. code-block:: bash
docker run hello-world
OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine instance on Windows and macOS has only a single CPU core enabled. Use the VirtualBox Manager to determine the name of your Docker engine host, and then follow these optional steps to enable multiple CPUs:
.. code-block:: bash
@@ -103,14 +103,14 @@ You can then run ocrmypdf using the command:
.. code-block:: bash
docker run --rm ocrmypdf --help
To execute the OCRmyPDF on a local file, you must `provide a writable volume to the Docker image <https://docs.docker.com/userguide/dockervolumes/>`_, and both the input and output file must be inside the writable volume. This example command uses the current working directory as the writable volume:
.. code-block:: bash
docker run --rm -v "$(pwd):/home/docker" <other docker arguments> ocrmypdf <your arguments to ocrmypdf>
In this worked example, the current working directory contains an input file called ``test.pdf`` and the output will go to ``output.pdf``:
In this worked example, the current working directory contains an input file called ``test.pdf`` and the output will go to ``output.pdf``:
.. code-block:: bash
@@ -167,11 +167,11 @@ Install or upgrade the required Homebrew packages, if any are missing:
brew install python3
brew install libxml2 libffi leptonica
brew install unpaper # optional
Python 3.5 and 3.6 are supported.
Python 3.5, 3.6 and 3.7 are supported.
Install the required Tesseract OCR engine with the language packs you plan to use:
.. code-block:: bash
brew install tesseract # Option 1: for English, French, German, Spanish
@@ -192,15 +192,13 @@ You can then install OCRmyPDF from PyPI, for the current user:
.. code-block:: bash
pip3 install --user ocrmypdf[fitz]
pip3 install --user ocrmypdf
or system-wide:
.. code-block:: bash
pip3 install ocrmypdf[fitz]
``[fitz]`` includes the optional dependency on PyMuPDF, which improves OCRmyPDF's output in many cases.
pip3 install ocrmypdf
The command line program should now be available:
@@ -226,7 +224,7 @@ Then install ocrmypdf 6.1.5 for the local user and set the user's ``PATH`` to ch
.. code-block:: bash
export PATH=$HOME/.local/bin:$PATH
pip3 install --user ocrmypdf[fitz]
pip3 install --user ocrmypdf
Installing on Ubuntu 16.04 LTS
@@ -249,13 +247,13 @@ If you wish install OCRmyPDF for the current user:
.. code-block:: bash
pip3 install --user ocrmypdf[fitz]
pip3 install --user ocrmypdf
Alternately, system-wide. Note that this may modify the system Python environment:
.. code-block:: bash
sudo pip3 install ocrmypdf[fitz]
sudo pip3 install ocrmypdf
If you wish to install OCRmyPDF to a virtual environment to isolate the system Python, you can follow these steps.
@@ -263,9 +261,7 @@ If you wish to install OCRmyPDF to a virtual environment to isolate the system P
python3 -m venv venv-ocrmypdf
source venv-ocrmypdf/bin/activate
pip3 install ocrmypdf[fitz]
``[fitz]`` includes the optional dependency on PyMuPDF, which improves OCRmyPDF's output in many cases.
pip3 install ocrmypdf
Installing on Ubuntu 14.04 LTS
@@ -316,7 +312,7 @@ Now we need to install ``pip`` and let it install ocrmypdf:
.. code-block:: bash
curl https://bootstrap.pypa.io/ez_setup.py -o - | python3.6 && python3.6 -m easy_install pip
pip3.6 install ocrmypdf[fitz]
pip3.6 install ocrmypdf
The ``wget`` command will download a program and run it.
@@ -396,14 +392,11 @@ The following dependencies are recommended:
- Ghostscript 9.22 or newer
- qpdf 8.0.2 or newer
- unpaper 6.1
- PyMuPDF 1.12.5 or newer
These are in addition to the Python packaging dependencies, meaning that unfortunately, the ``pip install`` command cannot satisfy all of them.
These are in addition to the Python packaging dependencies, meaning that unfortunately, the ``pip install`` command cannot satisfy all of them.
Python 3.6 and Tesseract 4.0.0-beta.1 are recommended for best OCR results and best performance.
The library PyMuPDF is not widely available in platform distributions, and it improves OCRmyPDF in certain conditions. Consider installing OCRmyPDF from the Python binary wheels, which include a precompiled version of this library.
Installing HEAD revision from sources
-------------------------------------
+61 -23
View File
@@ -6,9 +6,47 @@ OCRmyPDF uses `semantic versioning <http://semver.org/>`_ for its command line i
The OCRmyPDF package itself does not contain a public API, although it is fairly stable and breaking changes are usually timed with a major release. A future release will clearly define the stable public API.
.. Issue regex
find: [^`]\#([0-9]{1,3})[^0-9]
find: [^`]\#([0-9]{1,3})[^0-9]
replace: `#$1 <https://github.com/jbarlow83/OCRmyPDF/issues/$1>`_
v6.2.5
------
- Disable a failing test due to Tesseract 4.0rc1 behavior change. Previously, Tesseract would exit with an error message if its configuration was invalid, and OCRmyPDF would intercept this message. Now Tesseract issues a warning, which OCRmyPDF v6.2.5 may relay or ignore. (In v7.x, OCRmyPDF will respond to the warning.)
- This release branch no longer supports using the optional PyMuPDF installation, since it was removed in v7.x.
- This release branch no longer supports macOS. macOS users should upgrade to v7.x.
v6.2.4
------
- Backport Ghostscript 9.25 compatibility fixes, which removes support for setting Unicode metadata
- Backport blacklisting Ghostscript 9.24
- Older versions of Ghostscript are still supported
v6.2.3
------
- Fix compatibility with img2pdf >= 0.3.0 by rejecting input images that have an alpha channel
v6.2.2
------
- Backport compatibility fixes for Python 3.7 and ruffus 2.7.0 from v7.0.0
- Backport fix to ignore masks when deciding what colors are on a page
- Backport some minor improvements from v7.0.0: better argument validation and warnings about the Tesseract 4.0.0 ``--user-words`` regression
v6.2.1
------
- Fix recent versions of Tesseract (after 4.0.0-beta1) not being detected as supporting the ``sandwich`` renderer (`#271 <https://github.com/ppjbarlow83/OCRmyPDF/issues/271>`_).
v6.2.0
------
@@ -34,7 +72,7 @@ v6.1.5
v6.1.4
------
- Fix issue `#248 <https://github.com/jbarlow83/OCRmyPDF/issues/248>`_ ``--clean`` argument may remove OCR from left column of text on certain documents. We now set ``--layout none`` to suppress this.
- Fix issue `#248 <https://github.com/jbarlow83/OCRmyPDF/issues/248>`_ ``--clean`` argument may remove OCR from left column of text on certain documents. We now set ``--layout none`` to suppress this.
- The test cache was updated to reflect the change above.
@@ -165,7 +203,7 @@ v5.5
- Add new argument ``--max-image-mpixels``. Pillow 5.0 now raises an exception when images may be decompression bombs. This argument can be used to override the limit Pillow sets.
- Fix output page cropped when using the sandwich renderer and OCR is skipped on a rotated and image-processed page
- A warning is now issued when old versions of Ghostscript are used in cases known to cause issues with non-Latin characters
- Fix a few parameter validation checks for ``-output-type pdfa-1`` and ``pdfa-2``
- Fix a few parameter validation checks for ``-output-type pdfa-1`` and ``pdfa-2``
v5.4.4
@@ -175,7 +213,7 @@ v5.4.4
- Fix issue `#200 <https://github.com/jbarlow83/OCRmyPDF/issues/200>`_: an uncommon syntax for formatting decimal numbers in a PDF would cause qpdf to issue a warning, which ocrmypdf treated as an error. Now this the warning is relayed.
- Fix an issue where intermediate PDFs would be created at version 1.3 instead of the version of the original file. It's possible but unlikely this had side effects.
- A warning is now issued when older versions of qpdf are used since issues like `#200 <https://github.com/jbarlow83/OCRmyPDF/issues/200>`_ cause qpdf to infinite-loop
- Address issue `#140 <https://github.com/jbarlow83/OCRmyPDF/issues/140>`_: if Tesseract outputs invalid UTF-8, escape it and print its message instead of aborting with a Unicode error
- Address issue `#140 <https://github.com/jbarlow83/OCRmyPDF/issues/140>`_: if Tesseract outputs invalid UTF-8, escape it and print its message instead of aborting with a Unicode error
- Adding previously unlisted setup requirement, pytest-runner
- Update documentation: fix an error in the example script for Synology with Docker images, improved security guidance, advised ``pip install --user``
@@ -344,7 +382,7 @@ v4.4.1
- To prevent a `TIFF output error <https://github.com/python-pillow/Pillow/issues/2206>`_ caused by img2pdf >= 0.2.1 and Pillow <= 3.4.2, dependencies have been tightened
- The Tesseract 4.00 simultaneous process limit was increased from 1 to 2, since it was observed that 1 lowers performance
- Documentation improvements to describe the ``--tesseract-config`` feature
- Documentation improvements to describe the ``--tesseract-config`` feature
- Added test cases and fixed error handling for ``--tesseract-config``
- Tweaks to setup.py to deal with issues in the v4.4 release
@@ -407,7 +445,7 @@ v4.3
- New feature ``--remove-background`` to detect and erase the background of color and grayscale images
- Better documentation
- Fixed an issue with PDFs that draw images when the raster stack depth is zero
- Fixed an issue with PDFs that draw images when the raster stack depth is zero
- ocrmypdf can now redirect its output to stdout for use in a shell pipeline
+ This does not improve performance since temporary files are still used for buffering
@@ -475,7 +513,7 @@ v4.2
+ fixes issue `#82 <https://github.com/jbarlow83/OCRmyPDF/issues/82>`_
- Fixes an issue where, with certain settings, monochrome images in PDFs would be converted to 8-bit grayscale, increasing file size (`#79 <https://github.com/jbarlow83/OCRmyPDF/issues/79>`_)
- Support for Ubuntu 12.04 LTS "precise" has been dropped in favor of (roughly) Ubuntu 14.04 LTS "trusty"
- Support for Ubuntu 12.04 LTS "precise" has been dropped in favor of (roughly) Ubuntu 14.04 LTS "trusty"
+ Some Ubuntu "PPAs" (backports) are needed to make it work
@@ -590,7 +628,7 @@ New features
- Automatic page rotation (``-r``) is now available. It uses ignores any prior rotation information
on PDFs and sets rotation based on the dominant orientation of detectable text. This feature is
fairly reliable but some false positives occur especially if there is not much text to work with. (`#4 <https://github.com/jbarlow83/OCRmyPDF/issues/4>`_)
fairly reliable but some false positives occur especially if there is not much text to work with. (`#4 <https://github.com/jbarlow83/OCRmyPDF/issues/4>`_)
- Deskewing is now performed using Leptonica instead of unpaper. Leptonica is faster and more reliable
at image deskewing than unpaper.
@@ -633,7 +671,7 @@ v3.2
New features
^^^^^^^^^^^^
- Lossless reconstruction: when possible, OCRmyPDF will inject text layers without
- Lossless reconstruction: when possible, OCRmyPDF will inject text layers without
otherwise manipulating the content and layout of a PDF page. For example, a PDF containing a mix
of vector and raster content would see the vector content preserved. Images may still be transcoded
during PDF/A conversion. (``--deskew`` and ``--clean-final`` disable this mode, necessarily.)
@@ -668,10 +706,10 @@ Changes
needed to implement support
- Improved some error messages related to missing input files
- Fixed issue `#20 <https://github.com/jbarlow83/OCRmyPDF/issues/20>`_ - uppercase .PDF extension not accepted
- Fixed an issue where OCRmyPDF failed to text that certain pages contained previously OCR'ed text,
- Fixed an issue where OCRmyPDF failed to text that certain pages contained previously OCR'ed text,
such as OCR text produced by Tesseract 3.04
- Inserts /Creator tag into PDFs so that errors can be traced back to this project
- Added new option ``--pdf-renderer=auto``, to let OCRmyPDF pick the best PDF renderer.
- Added new option ``--pdf-renderer=auto``, to let OCRmyPDF pick the best PDF renderer.
Currently it always chooses the 'hocrtransform' renderer but that behavior may change.
- Set up Travis CI automatic integration testing
@@ -681,19 +719,19 @@ v3.0
New features
^^^^^^^^^^^^
- Easier installation with a Docker container or Python's ``pip`` package manager
- Easier installation with a Docker container or Python's ``pip`` package manager
- Eliminated many external dependencies, so it's easier to setup
- Now installs ``ocrmypdf`` to ``/usr/local/bin`` or equivalent for system-wide
access and easier typing
- Improved command line syntax and usage help (``--help``)
- Tesseract 3.03+ PDF page rendering can be used instead for better positioning
of recognized text (``--pdf-renderer tesseract``)
- PDF metadata (title, author, keywords) are now transferred to the
- PDF metadata (title, author, keywords) are now transferred to the
output PDF
- PDF metadata can also be set from the command line (``--title``, etc.)
- Automatic repairs malformed input PDFs if possible
- Added test cases to confirm everything is working
- Added option to skip extremely large pages that take too long to OCR and are
- Added option to skip extremely large pages that take too long to OCR and are
often not OCRable (e.g. large scanned maps or diagrams); other pages are still
processed (``--skip-big``)
- Added option to kill Tesseract OCR process if it seems to be taking too long on
@@ -712,9 +750,9 @@ Changes
available CPUs, increasing performance
- The ``-o DPI`` argument has been phased out, in favor of ``--oversample DPI``, in
case we need ``-o OUTPUTFILE`` in the future
- Removed several dependencies, so it's easier to install. We no
- Removed several dependencies, so it's easier to install. We no
longer use:
- GNU parallel_
- ImageMagick_
- Python 2.7
@@ -730,7 +768,7 @@ Changes
- qpdf_ 5.0.0+
- Unpaper_ 6.1 (optional)
- some automatically managed Python packages
.. _ruffus: http://www.ruffus.org.uk/index.html
.. _parallel: https://www.gnu.org/software/parallel/
.. _ImageMagick: http://www.imagemagick.org/script/index.php
@@ -776,7 +814,7 @@ Release candidates
- fixed some installer issues and errors in installation instructions
- improve performance: run Ghostscript with multithreaded rendering
- improve performance: use multiple cores by default
- bug fix: checking for wrong exception on process timeout
- bug fix: checking for wrong exception on process timeout
- rc3: skipping version number intentionally to avoid confusion with Tesseract
- rc2: first release for public testing to test-PyPI, Github
@@ -799,11 +837,11 @@ where ``settings.txt`` contains *one argument per line*, for example:
::
-l
deu
--author
A. Merkel
--pdf-renderer
-l
deu
--author
A. Merkel
--pdf-renderer
tesseract
+4 -3
View File
@@ -1,10 +1,11 @@
# requirements.txt can be used to replicate the developer's build environment
# setup.py lists a separate set of requirements that are looser to simplify
# installation
ruffus == 2.6.3
Pillow == 5.1.0
ruffus == 2.7.0
Pillow == 5.2.0
reportlab == 3.4.0
PyPDF2 == 1.26.0
img2pdf == 0.2.4
cffi == 1.11.5
PyMuPDF == 1.12.5
PyMuPDF == 1.12.5
defusedxml == 0.5.0
+3 -2
View File
@@ -215,6 +215,7 @@ setup(
classifiers=[
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
@@ -243,12 +244,12 @@ setup(
'cffi >= 1.9.1', # must be a setup and install requirement
'defusedxml >= 0.5.0', # pure Python, so track HEAD closely
'img2pdf >= 0.2.4', # pure Python, so track HEAD closely
'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"',
'Pillow >= 4.0.0, != 5.1.0 ; sys_platform == "darwin"',
# Pillow < 4 has BytesIO/TIFF bug w/img2pdf 0.2.3
# block 5.1.0, broken wheels
'PyPDF2 >= 1.26', # pure Python, so track HEAD closely
'reportlab >= 3.3.0', # oldest released version with sane image handling
'ruffus == 2.6.3', # pinned - ocrmypdf implements a 2.6.3 workaround
'ruffus >= 2.7.0',
],
extras_require={
'fitz': ['PyMuPDF >= 1.12.5'] # for table of contents bug
+64 -39
View File
@@ -70,7 +70,7 @@ def complain(message):
if 'IDE_PROJECT_ROOTS' in os.environ:
os.environ['PATH'] = '/usr/local/bin:' + os.environ['PATH']
# --------
# --------
# Critical environment tests
verify_python3_env()
@@ -85,6 +85,20 @@ if tesseract.version() < MINIMUM_TESS_VERSION:
# -------------
# Parser
def numeric(basetype, min_=None, max_=None):
"Validator for numeric params"
min_ = basetype(min_) if min_ is not None else None
max_ = basetype(max_) if max_ is not None else None
def _numeric(string):
value = basetype(string)
if (min_ is not None and value < min_
or max_ is not None and value > max_):
msg = "%r not in valid range %r" % (string, (min_, max_))
raise argparse.ArgumentTypeError(msg)
return value
return _numeric
parser = argparse.ArgumentParser(
prog=PROGRAM_NAME,
fromfile_prefix_chars='@',
@@ -156,7 +170,7 @@ parser.add_argument(
'--image-dpi', metavar='DPI', type=int,
help="For input image instead of PDF, use this DPI instead of file's.")
parser.add_argument(
'--output-type', choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3'],
'--output-type', choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3'],
default='pdfa',
help="Choose output type. 'pdfa' creates a PDF/A-2b compliant file for "
"long term archiving (default, recommended) but may not suitable "
@@ -233,7 +247,7 @@ preprocessing.add_argument(
help="Clean page as above, and incorporate the cleaned image in the final "
"PDF. Might remove desired content.")
preprocessing.add_argument(
'--oversample', metavar='DPI', type=int, default=0,
'--oversample', metavar='DPI', type=numeric(int, 0, 5000), default=0,
help="Oversample images to at least the specified DPI, to improve OCR "
"results slightly")
@@ -255,7 +269,7 @@ ocrsettings.add_argument(
# "pages")
ocrsettings.add_argument(
'--skip-big', type=float, metavar='MPixels',
'--skip-big', type=numeric(float, 0, 5000), metavar='MPixels',
help="Skip OCR on pages larger than the specified amount of megapixels, "
"but include skipped pages in final output")
@@ -263,7 +277,7 @@ advanced = parser.add_argument_group(
"Advanced",
"Advanced options to control Tesseract's OCR behavior")
advanced.add_argument(
'--max-image-mpixels', action='store', type=float, metavar='MPixels',
'--max-image-mpixels', action='store', type=numeric(float, 0), metavar='MPixels',
help="Set maximum number of pixels to unpack before treating an image as a "
"decompression bomb",
default=128.0)
@@ -296,11 +310,11 @@ advanced.add_argument(
" of Ghostscript; deprecated"
)
advanced.add_argument(
'--tesseract-timeout', default=180.0, type=float, metavar='SECONDS',
'--tesseract-timeout', default=180.0, type=numeric(float, 0), metavar='SECONDS',
help='Give up on OCR after the timeout, but copy the preprocessed page '
'into the final output')
advanced.add_argument(
'--rotate-pages-threshold', default=14.0, type=float, metavar='CONFIDENCE',
'--rotate-pages-threshold', default=14.0, type=numeric(float, max_=1000), metavar='CONFIDENCE',
help="Only rotate pages when confidence is above this value (arbitrary "
"units reported by tesseract)")
advanced.add_argument(
@@ -491,6 +505,10 @@ def check_options_advanced(options, log):
"--output-type is not 'pdfa', 'pdfa-1', or 'pdfa-2'"
)
if tesseract.v4() and (options.user_words or options.user_patterns):
log.warning(
'Tesseract 4.x ignores --user-words, so this has no effect')
def check_options_metadata(options, log):
import unicodedata
@@ -574,7 +592,7 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
if exc_name == 'builtins.SystemExit':
match = re.search(r"\.(.+?)\)", exc_value)
exit_code_name = match.groups()[0]
exit_code = getattr(ExitCode, exit_code_name, 'other_error')
exit_code = getattr(ExitCode, exit_code_name, 'other_error')
elif exc_name == 'ruffus.ruffus_exceptions.MissingInputFileError':
log.error(cleanup_ruffus_error_message(exc_value))
exit_code = ExitCode.input_file
@@ -598,7 +616,7 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
(exc_name == 'ocrmypdf.exceptions.EncryptedPdfError'):
log.error(textwrap.dedent("""\
Input PDF is encrypted. The encryption must be removed to
perform OCR.
perform OCR.
For information about this PDF's security use
qpdf --show-encryption infilename
@@ -607,7 +625,7 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
qpdf --decrypt [--password=[password]] infilename
"""))
exit_code = ExitCode.encrypted_pdf
exit_code = ExitCode.encrypted_pdf
elif exc_name == 'ocrmypdf.exceptions.PdfMergeFailedError':
log.error(textwrap.dedent("""\
Failed to merge PDF image layer with OCR layer
@@ -638,33 +656,31 @@ def do_ruffus_exception(ruffus_five_tuple, options, log):
return ExitCode.other_error
def traverse_ruffus_exception(e_args, options, log):
"""Walk through a RethrownJobError and find the first exception.
def traverse_ruffus_exception(exceptions, options, log):
"""Traverse a RethrownJobError and output the exceptions
Ruffus flattens exception to 5 element tuples. Because of a bug
in <= 2.6.3 it may present either the single:
(task, job, exc, value, stack)
or something like:
[[(task, job, exc, value, stack)]]
Generally cross-process exception marshalling doesn't work well
and ruffus doesn't support because BaseException has its own
implementation of __reduce__ that attempts to reconstruct the
exception based on e.__init__(e.args).
Attempting to log the exception directly marshalls it to the logger
which is probably in another process, so it's better to log only
data from the exception at this point.
Ruffus presents exceptions as 5 element tuples. The RethrownJobException
has a list of exceptions like
e.job_exceptions = [(5-tuple), (5-tuple), ...]
ruffus < 2.7.0 had a bug with exception marshalling that would give
different output whether the main or child process raised the exception.
We no longer support this.
Attempting to log the exception itself will re-marshall it to the logger
which is normally running in another process. It's better to avoid re-
marshalling.
The exit code will be based on this, even if multiple exceptions occurred
at the same time."""
if isinstance(e_args, Sequence) and isinstance(e_args[0], str) and \
len(e_args) == 5:
return do_ruffus_exception(e_args, options, log)
elif is_iterable_notstr(e_args):
for exc in e_args:
return traverse_ruffus_exception(exc, options, log)
exit_codes = []
for exc in exceptions:
exit_code = do_ruffus_exception(exc, options, log)
exit_codes.append(exit_code)
return exit_codes[0] # Multiple codes are rare so take the first one
def check_closed_streams(options):
@@ -749,7 +765,7 @@ def check_environ(options, _log):
for k in old_envvars:
if k in os.environ:
_log.warning(textwrap.dedent("""\
OCRmyPDF no longer uses the environment variable {}.
OCRmyPDF no longer uses the environment variable {}.
Change PATH to select alternate programs.""".format(k)))
@@ -792,14 +808,14 @@ def report_output_file_size(options, _log, input_file, output_file):
ratio = output_size / input_size
if ratio < 1.35 or input_size < 25000:
return # Seems fine
reasons = []
if not fitz:
reasons.append("The optional dependency PyMuPDF is not installed.")
image_preproc = {
'deskew',
'clean_final',
'remove_background',
'deskew',
'clean_final',
'remove_background',
'oversample',
'force_ocr'
}
@@ -847,6 +863,14 @@ def run_pipeline():
"security vulnerabilities with certain malformed PDFs. Consider "
"upgrading to version 7.0.0 or newer.".format(qpdf.version()))
if ghostscript.version() == '9.24':
complain(
"Ghostscript 9.24 contains serious regressions and is not "
"supported. Please upgrade to Ghostscript 9.25 or use an older "
"version."
)
return ExitCode.missing_dependency
# Any changes to options will not take effect for options that are already
# bound to function parameters in the pipeline. (For example
# options.input_file, options.pdf_renderer are already bound.)
@@ -886,7 +910,8 @@ def run_pipeline():
except ruffus_exceptions.RethrownJobError as e:
if options.verbose:
_log.debug(str(e)) # stringify exception so logger doesn't have to
exitcode = traverse_ruffus_exception(e.args, options, _log)
exceptions = e.job_exceptions
exitcode = traverse_ruffus_exception(exceptions, options, _log)
if exitcode is None:
_log.error("Unexpected ruffus exception: " + str(e))
_log.error(repr(e))
@@ -919,7 +944,7 @@ def run_pipeline():
_log.warning('Output file: The generated PDF is INVALID')
return ExitCode.invalid_output_pdf
report_output_file_size(options, _log, start_input_file,
report_output_file_size(options, _log, start_input_file,
options.output_file)
pdfinfo = context.get_pdfinfo()
+5
View File
@@ -40,6 +40,11 @@ import codecs
def verify_python3_env():
"""Ensures that the environment is good for unicode on Python 3."""
# PEP 538 changes in Python 3.7 should make this wrangling unnecessary
if sys.version_info[0:3] >= (3, 7, 0):
return
try:
import locale
fs_enc = codecs.lookup(locale.getpreferredencoding()).name
+5 -1
View File
@@ -25,7 +25,7 @@ from ..exceptions import MissingDependencyError
def get_version(program, *,
def get_version(program, *,
version_arg='--version', regex=r'(\d+(\.\d+)*)'):
"Get the version of the specified program"
args_prog = [
@@ -37,6 +37,10 @@ def get_version(program, *,
args_prog, close_fds=True, universal_newlines=True,
stdout=PIPE, stderr=STDOUT, check=True)
output = proc.stdout
except FileNotFoundError as e:
raise MissingDependencyError(
"Could not find program '{}' on the PATH".format(
program)) from e
except CalledProcessError as e:
if e.returncode < 0:
raise MissingDependencyError(
+25 -15
View File
@@ -34,12 +34,20 @@ def version():
def jpeg_passthrough_available():
"""
Ghostscript 9.23 introduced JPEG passthrough but it seems to corrupt the
last two bytes of certain images, for now we disable it for 9.23 and
do not mention it for < 9.23.
Returns True if the installed version of Ghostscript supports JPEG passthru
Prior to 9.23, Ghostscript decode and re-encoded JPEGs internally. In 9.23
it gained the ability to keep JPEGs unmodified. However, the 9.23
implementation was buggy and would deletes the last two bytes of images in
some cases, as reported here.
https://bugs.ghostscript.com/show_bug.cgi?id=699216
The issue was fixed for 9.24, hence that is the first version we consider
the feature available. (However, we don't use 9.24 at all, so the first
version that allows JPEG passthrough is 9.25.
Regardless, in ocrmypdf 6.x we are ignoring this new feature entirely to
avoid new behavior.
"""
return False
@@ -52,20 +60,20 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
pageno=1, page_dpi=None):
"""
Rasterize one page of a PDF at resolution (xres, yres) in canvas units.
The image is sized to match the integer pixels dimensions implied by
The image is sized to match the integer pixels dimensions implied by
(xres, yres) even if those numbers are noninteger. The image's DPI will
be overridden with the values in page_dpi.
:param input_file: pathlike
:param output_file: pathlike
:param xres: resolution at which to rasterize page
:param yres:
:param raster_device:
:param log:
:param yres:
:param raster_device:
:param log:
:param pageno: page number to rasterize (beginning at page 1)
:param page_dpi: resolution tuple (x, y) overriding output image DPI
:return:
:param page_dpi: resolution tuple (x, y) overriding output image DPI
:return:
"""
res = xres, yres
int_res = round(xres), round(yres)
@@ -85,7 +93,7 @@ def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log,
'-o', tmp.name,
fspath(input_file)
]
p = run(args_gs, stdout=PIPE, stderr=STDOUT,
universal_newlines=True)
if _gs_error_reported(p.stdout):
@@ -139,15 +147,17 @@ def generate_pdfa(pdf_pages, output_file, compression, log,
"-dAutoFilterGrayImages=true",
]
# Older versions of Ghostscript expect a leading slash in
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
# Older versions of Ghostscript expect a leading slash in
# sColorConversionStrategy, newer ones should not have it. See Ghostscript
# git commit fe1c025d.
strategy = 'RGB' if version() >= '9.19' else '/RGB'
if version() == '9.23':
if version() >= '9.23':
# 9.23: new feature JPEG passthrough is broken in some cases, best to
# disable it always
# https://bugs.ghostscript.com/show_bug.cgi?id=699216
# fixed in 9.24, but to avoid changing expected behavior we disable it
# for ocrmypdf 6.x
compression_args.append('-dPassThroughJPEGImages=false')
with NamedTemporaryFile(delete=True) as gs_pdf:
+3 -2
View File
@@ -73,7 +73,8 @@ def has_textonly_pdf():
"""
args_tess = [
'tesseract',
'--print-parameters'
'--print-parameters',
'pdf'
]
params = ''
try:
@@ -159,7 +160,7 @@ def get_orientation(input_file, language: list, engine_mode, timeout: float,
assert 'Rotate' not in osd
angle = -angle % 360
else:
# Tesseract == 3.04.01, hopefully also Tesseract > 3.04.01
# Tesseract >= 3.04.01
# reports "Orientation in degrees" as a clockwise angle
assert 'Rotate' in osd
+3 -1
View File
@@ -15,6 +15,8 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
from PIL import Image
from functools import partial
from collections.abc import Iterable
from contextlib import suppress, contextmanager
@@ -103,7 +105,7 @@ def is_file_writable(test_file):
if p.is_symlink():
# Python 3.5 does not accept parameters for Path.resolve() and behaves
# as if strict=True (throws an exception on failure). Python 3.6
# as if strict=True (throws an exception on failure). Python 3.6
# defaults to strict=False. This implements strict=False like behavior
# for Python 3.5.
if sys.version_info[0:2] <= (3, 5):
+77 -19
View File
@@ -15,7 +15,21 @@
# You should have received a copy of the GNU General Public License
# along with OCRmyPDF. If not, see <http://www.gnu.org/licenses/>.
# Generate a PDFA_def.ps file for Ghostscript >= 9.14
"""
Generate a PDFMARK file for Ghostscript >= 9.14, for PDF/A conversion
pdfmark is an extension to the Postscript language that describes some PDF
features like bookmarks and annotations. It was originally specified Adobe
Distiller, for Postscript to PDF conversion:
https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/pdfmark_reference.pdf
Ghostscript uses pdfmark for PDF to PDF/A conversion as well. To use Ghostscript
to create a PDF/A, we need to create a pdfmark file with the necessary metadata.
This takes care of the many version-specific bugs and pecularities in
Ghostscript's handling of pdfmark.
"""
from string import Template
from binascii import hexlify
@@ -78,7 +92,8 @@ def
def encode_text_string(s: str) -> str:
'''Encode text string to hex string for use in a PDF
"""
Encode text string to hex string for use in a PDF
From PDF 32000-1:2008 a string object may be included in hexademical form
if it is enclosed in angle brackets. For general Unicode the string should
@@ -86,7 +101,7 @@ def encode_text_string(s: str) -> str:
ASCII strings could be encoded as PdfDocEncoding literals provided
that certain Postscript sequences are escaped. But it's far simpler to
encode everything as UTF-16.
'''
"""
# Sometimes lazy C programmers leave their NULs at the end of strings they
# insert into PDFs
@@ -102,10 +117,30 @@ def encode_text_string(s: str) -> str:
return ascii_hex_str
def encode_pdf_date(d: datetime) -> str:
"""Encode Python datetime object as PDF date string
def _encode_ascii(s: str) -> str:
"""
Aggressively strip non-ASCII and PDF escape sequences
From Adobe pdfmark manual:
Ghostscript 9.24+ lost support for UTF-16BE in pdfmark files for reasons
given in GhostPDL commit e997c683. Our temporary workaround is use ASCII
and drop all non-ASCII characters. A slightly improved alternative would
be to implement PdfDocEncoding in pikepdf and encode to that, or handle
metadata there.
"""
trans = str.maketrans({
'(': '',
')': '',
'\\': '',
'\0': ''
})
return s.translate(trans).encode('ascii', errors='replace').decode()
def encode_pdf_date(d: datetime) -> str:
"""
Encode Python datetime object as PDF date string
From Adobe pdfmark manual:
(D:YYYYMMDDHHmmSSOHH'mm')
D: is an optional prefix. YYYY is the year. All fields after the year are
optional. MM is the month (01-12), DD is the day (01-31), HH is the
@@ -137,16 +172,23 @@ def encode_pdf_date(d: datetime) -> str:
def decode_pdf_date(s: str) -> datetime:
"""
Decode a pdfmark date to a Python datetime object
A pdfmark date is a string in a paritcular format. See the pdfmark
Reference for the specification.
"""
if s.startswith('D:'):
s = s[2:]
# Literal Z00'00', is incorrect but found in the wild,
# Literal Z00'00', is incorrect but found in the wild,
# probably made by OS X Quartz -- standardize
if s.endswith("Z00'00'"):
s = s.replace("Z00'00'", '+0000')
elif s.endswith('Z'):
s = s.replace('Z', '+0000')
s = s.replace("'", "") # Remove apos from PDF time strings
return datetime.strptime(s, r'%Y%m%d%H%M%S%z')
@@ -154,7 +196,7 @@ def decode_pdf_date(s: str) -> datetime:
def _get_pdfmark_dates(pdfmark):
"""Encode dates for pdfmark Postscript. The best way to deal with a
missing date entry is set it to null, because if the key is omitted
missing date entry is set it to null, because if the key is omitted
Ghostscript will set it to now - we do not want to erase the fact that
the value was unknown. Setting to an empty string breaks Ghostscript
9.22 as reported here:
@@ -172,30 +214,47 @@ def _get_pdfmark_dates(pdfmark):
date_str = date_str[2:]
try:
yield ' {} (D:{})'.format(
key,
key,
encode_pdf_date(decode_pdf_date(date_str)))
except ValueError:
yield ' {} null'.format(key)
def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
"""Create a Postscript file for Ghostscript. pdfmark contains the various
objects as strings; these must be encoded in ASCII, and dates have a
special format."""
def _get_pdfa_def(icc_profile, icc_identifier, pdfmark, ascii_docinfo=False):
"""Create a Postscript pdfmark file for Ghostscript.
pdfmark contains the various objects as strings; these must be encoded in
ASCII, and dates have a special format.
:param icc_profile: filename of the ICC profile to include in pdfmark
:param icc_identifier: ICC identifier such as 'sRGB'
:param pdfmark: a dictionary containing keys to include the pdfmark
:param ascii_docinfo: if True, the docinfo block must be encoded in pure
ASCII and may not contain UTF-16BE-BOM-hex encoded strings, as
required for Ghostscript 9.24+
:returns: a string containing the entire pdfmark
"""
# Ghostscript <= 9.21 has a bug where null entries in DOCINFO might produce
# ERROR: VMerror (-25) on closing pdfwrite device.
# https://bugs.ghostscript.com/show_bug.cgi?id=697684
# Work around this by only adding keys that have a nontrivial value
docinfo_keys = ('/Title', '/Author', '/Subject', '/Creator', '/Keywords')
docinfo_line_template = ' {key} <{value}>'
def docinfo_gen():
if not ascii_docinfo:
docinfo_line_template = ' {key} <{value}>'
encode = encode_text_string
else:
docinfo_line_template = ' {key} ({value})'
encode = _encode_ascii
yield from _get_pdfmark_dates(pdfmark)
for key in docinfo_keys:
if key in pdfmark and pdfmark[key].strip() != '':
line = docinfo_line_template.format(
key=key, value=encode_text_string(pdfmark[key]))
key=key, value=encode(pdfmark[key]))
yield line
docinfo = '\n'.join(docinfo_gen())
@@ -206,13 +265,13 @@ def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
return result
def generate_pdfa_ps(target_filename, pdfmark, icc='sRGB'):
def generate_pdfa_ps(target_filename, pdfmark, icc='sRGB', ascii_docinfo=False):
if icc == 'sRGB':
icc_profile = SRGB_ICC_PROFILE
else:
raise NotImplementedError("Only supporting sRGB")
ps = _get_pdfa_def(icc_profile, icc, pdfmark)
ps = _get_pdfa_def(icc_profile, icc, pdfmark, ascii_docinfo=ascii_docinfo)
# We should have encoded everything to pure ASCII by this point, and
# to be safe, only allow ASCII in PostScript
@@ -263,4 +322,3 @@ def file_claims_pdfa(filename):
pdfa_dict['conformance'] = conformance
return pdfa_dict
+46 -19
View File
@@ -19,6 +19,7 @@ from contextlib import suppress
from shutil import copyfileobj
from pathlib import Path
from datetime import datetime, timezone
from io import BytesIO
import sys
import os
import shutil
@@ -144,6 +145,13 @@ def triage_image_file(input_file, output_file, log, options):
"image was scanned and specify it using --image-dpi.")
raise DpiError()
if im.mode in ('RGBA', 'LA'):
log.error(
"The input image has an alpha channel. Remove the alpha "
"channel first."
)
raise UnsupportedImageFormatError()
if 'iccprofile' not in im.info:
if im.mode == 'RGB':
log.info('Input image has no ICC profile, assuming sRGB')
@@ -172,7 +180,7 @@ def triage_image_file(input_file, output_file, log, options):
def _pdf_guess_version(input_file, search_window=1024):
"""Try to find version signature at start of file.
Not robust enough to deal with appended files.
Returns empty string if not found, indicating file is probably not PDF.
@@ -231,7 +239,7 @@ def repair_and_parse_pdf(
"output these files.) Use --output-type=pdf instead."
)
raise InputFileError()
if len(pdfinfo.pages) > 2000 and sys.version_info[0:2] <= (3, 5):
log.warning(
"Performance regressions are known occur with Python 3.5 for "
@@ -298,7 +306,7 @@ def is_ocr_required(pageinfo, log, options):
# We found a page with no images and no text. That means it may
# have vector art that the user wants to OCR. If we determined
# lossless reconstruction is not possible then we have to rasterize
# the image. So if OCR is being forced, take that to mean YES, go
# the image. So if OCR is being forced, take that to mean YES, go
# ahead and rasterize. If not forced, then pretend there's no text
# on the page at all so we don't lose anything.
# This could be made smarter by explicitly searching for vector art.
@@ -496,17 +504,23 @@ def rasterize_with_ghostscript(
options = context.get_options()
pageinfo = get_pageinfo(input_file, context)
device = 'png16m' # 24-bit
if pageinfo.images:
if all(image.comp == 1 for image in pageinfo.images):
if all(image.bpc == 1 for image in pageinfo.images):
device = 'pngmono'
elif all(image.bpc > 1 and image.color == Colorspace.index
for image in pageinfo.images):
device = 'png256'
elif all(image.bpc > 1 and image.color == Colorspace.gray
for image in pageinfo.images):
device = 'pnggray'
colorspaces = ['pngmono', 'pnggray', 'png256', 'png16m']
device_idx = 0
def at_least(cs):
return max(device_idx, colorspaces.index(cs))
for image in pageinfo.images:
if image.type_ != 'image':
continue # ignore masks
if image.bpc > 1:
if image.color == Colorspace.index:
device_idx = at_least('png256')
elif image.color == Colorspace.gray:
device_idx = at_least('pnggray')
else:
device_idx = at_least('png16m')
device = colorspaces[device_idx]
log.debug("Rasterize {0} with {1}".format(
os.path.basename(input_file), device))
@@ -878,7 +892,7 @@ def get_pdfmark(base_pdf, options):
except (KeyError, TypeError):
return ''
pdfmark = {k: from_document_info(k) for k in
pdfmark = {k: from_document_info(k) for k in
('/Title', '/Author', '/Keywords', '/Subject', '/CreationDate')}
if options.title:
pdfmark['/Title'] = options.title
@@ -912,7 +926,20 @@ def generate_postscript_stub(
options = context.get_options()
pdf = pypdf.PdfFileReader(input_file)
pdfmark = get_pdfmark(pdf, options)
generate_pdfa_ps(output_file, pdfmark)
ascii_docinfo = False
if ghostscript.version() >= '9.24':
ascii_docinfo = True
try:
for v in pdfmark.values():
v.encode('ascii', errors='strict')
except UnicodeEncodeError:
log.warning(
"Ghostscript 9.24+ does not support Unicode strings in "
" metadata. These will be converted to ASCII if possible."
)
generate_pdfa_ps(output_file, pdfmark, ascii_docinfo=ascii_docinfo)
def skip_page(
@@ -1069,7 +1096,7 @@ def merge_sidecars(
if txt_file:
with open(txt_file, 'r', encoding="utf-8") as in_:
txt = in_.read()
# Tesseract v4 alpha started adding form feeds in
# Tesseract v4 alpha started adding form feeds in
# commit aa6eb6b
# No obvious way to detect what binaries will do this, so
# for consistency just ignore its form feeds and insert our
@@ -1103,7 +1130,7 @@ def copy_final(
sys.stdout.flush()
else:
# At this point we overwrite the output_file specified by the user
# use copyfileobj because then we use open() to create the file and
# use copyfileobj because then we use open() to create the file and
# get the appropriate umask, ownership, etc.
with open(output_file, 'wb') as output_stream:
copyfileobj(input_stream, output_stream)
@@ -1278,7 +1305,7 @@ def build_pipeline(options, work_folder, log, context):
output=os.path.join(work_folder, r'\1.rendered.pdf'),
extras=[log, context])
task_combine_layers.graphviz(fillcolor='"#00cc66"')
task_combine_layers.active_if(options.pdf_renderer == 'hocr' or
task_combine_layers.active_if(options.pdf_renderer == 'hocr' or
options.pdf_renderer == 'sandwich')
# Tesseract OCR+PDF
+1 -1
View File
@@ -2,4 +2,4 @@ pytest >= 3.2
pytest-helpers-namespace
pytest-xdist
pytest-cov
pytest-timeout
pytest-timeout == 1.2.1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 168 KiB

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

+3 -1
View File
@@ -106,6 +106,8 @@ def main():
source = os.environ['_OCRMYPDF_TEST_INFILE'] # required
args = parser.parse_args()
cache_disabled = os.environ.get('_OCRMYPDF_CACHE_DISABLED', False)
if args.imagename == 'stdin':
real_tesseract()
@@ -128,7 +130,7 @@ def main():
print("Tesseract cache folder {} - ".format(cache_folder), end='',
file=sys.stderr)
if (cache_folder / 'stderr.bin').exists():
if (cache_folder / 'stderr.bin').exists() and not cache_disabled:
# Cache hit
print("HIT", file=sys.stderr)
+19 -15
View File
@@ -300,7 +300,8 @@ def test_autorotate_threshold(
@pytest.mark.parametrize('renderer',RENDERERS)
def test_ocr_timeout(renderer, resources, outpdf):
out = check_ocrmypdf(resources / 'skew.pdf', outpdf,
'--tesseract-timeout', '1.0')
'--tesseract-timeout', '0.01',
'--pdf-renderer', renderer)
pdfinfo = PdfInfo(out)
assert not pdfinfo[0].has_text
@@ -472,7 +473,7 @@ def test_tesseract_image_too_big(renderer, spoof_tesseract_big_image_error,
resources, outpdf):
check_ocrmypdf(
resources / 'hugemono.pdf', outpdf, '-r',
'--pdf-renderer', renderer,
'--pdf-renderer', renderer,
'--max-image-mpixels', '0',
env=spoof_tesseract_big_image_error)
@@ -722,6 +723,9 @@ def test_tesseract_config_notfound(renderer, resources, outdir):
@pytest.mark.parametrize('renderer', RENDERERS)
@pytest.mark.skipif(
tesseract.v4(),
reason='not valid due to behavior change in Tesseract >= 4.0-rc1')
def test_tesseract_config_invalid(renderer, resources, outdir):
cfg_file = outdir / 'test.cfg'
with cfg_file.open('w') as f:
@@ -828,6 +832,7 @@ def test_no_contents(spoof_tesseract_noop, resources, outpdf):
@pytest.mark.parametrize('image', [
'baiona.png',
'baiona_gray.png',
'baiona_alpha.png',
'congress.jpg'
])
def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec,
@@ -838,7 +843,6 @@ def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec,
output_file = str(outpdf)
im = Image.open(input_file)
# Runs: ocrmypdf - output.pdf < testfile
with open(input_file, 'rb') as input_stream:
p_args = ocrmypdf_exec + [
@@ -848,7 +852,12 @@ def test_compression_preserved(spoof_tesseract_noop, ocrmypdf_exec,
stdin=input_stream, env=spoof_tesseract_noop)
out, err = p.communicate()
assert p.returncode == ExitCode.ok
if im.mode in ('RGBA', 'LA'):
# If alpha image is input, expect an error
assert p.returncode != ExitCode.ok and b'alpha' in err
return
assert p.returncode == ExitCode.ok, err.decode('utf-8')
pdfinfo = PdfInfo(output_file)
@@ -893,7 +902,7 @@ def test_compression_changed(spoof_tesseract_noop, ocrmypdf_exec,
stdin=input_stream, env=spoof_tesseract_noop)
out, err = p.communicate()
assert p.returncode == ExitCode.ok
assert p.returncode == ExitCode.ok, err
pdfinfo = PdfInfo(output_file)
@@ -902,13 +911,7 @@ def test_compression_changed(spoof_tesseract_noop, ocrmypdf_exec,
if compression == "jpeg":
assert pdfimage.enc == Encoding.jpeg
else:
if ghostscript.jpeg_passthrough_available():
# Ghostscript 9.23 adds JPEG passthrough, which allows a JPEG to be
# copied without transcoding - so report
if image.endswith('jpg'):
assert pdfimage.enc == Encoding.jpeg
else:
assert pdfimage.enc not in (Encoding.jpeg, Encoding.jpeg2000)
assert pdfimage.enc not in (Encoding.jpeg, Encoding.jpeg2000)
if im.mode.startswith('RGB') or im.mode.startswith('BGR'):
assert pdfimage.color == Colorspace.rgb, \
@@ -966,6 +969,7 @@ def test_pdfa_n(spoof_tesseract_cache, pdfa_level, resources, outpdf):
assert pdfa_info['conformance'] == 'PDF/A-{}B'.format(pdfa_level)
@pytest.mark.skipif(sys.version_info >= (3, 7, 0), reason='fixed')
def test_bad_locale():
env = os.environ.copy()
env['LC_ALL'] = 'C'
@@ -993,7 +997,7 @@ def test_bad_utf8(spoof_tess_bad_utf8, renderer, resources, no_outpdf):
@pytest.mark.skipif(
not tesseract.has_textonly_pdf(),
not tesseract.has_textonly_pdf(),
reason="issue only affects sandwich")
def test_rotate_deskew_timeout(resources, outdir):
check_ocrmypdf(
@@ -1043,10 +1047,10 @@ def test_text_curves(spoof_tesseract_noop, resources, outpdf):
check_ocrmypdf(
resources / 'vector.pdf', outpdf, '--force-ocr',
env=spoof_tesseract_noop)
info = PdfInfo(outpdf)
assert len(info.pages[0].images) != 0, "force did not rasterize"
def test_dev_null(spoof_tesseract_noop, resources):
p, out, err = run_ocrmypdf(
+10 -3
View File
@@ -24,6 +24,12 @@ from datetime import timezone
from ocrmypdf.pdfa import file_claims_pdfa, encode_pdf_date, decode_pdf_date
from ocrmypdf.exceptions import ExitCode
from ocrmypdf.lib import fitz
from ocrmypdf.helpers import fspath
from ocrmypdf.pdfa import (
file_claims_pdfa, encode_pdf_date, decode_pdf_date, generate_pdfa_ps,
SRGB_ICC_PROFILE
)
from ocrmypdf.exec import ghostscript
# pytest.helpers is dynamic
# pylint: disable=no-member
@@ -76,6 +82,9 @@ def test_override_metadata(spoof_tesseract_noop, output_type, resources,
before = pypdf.PdfFileReader(str(input_file))
after = pypdf.PdfFileReader(outpdf)
if ghostscript.version() >= '9.24':
pytest.xfail('Ghostscript 9.24+ does not support Unicode DOCINFO')
assert after.documentInfo['/Title'] == german
assert after.documentInfo['/Author'] == chinese
assert after.documentInfo.get('/Keywords', '') == ''
@@ -136,7 +145,7 @@ def test_creation_date_preserved(spoof_tesseract_noop, output_type, resources,
before = pypdf.PdfFileReader(str(input_file)).getDocumentInfo()
check_ocrmypdf(
input_file, outpdf, '--output-type', output_type,
input_file, outpdf, '--output-type', output_type,
env=spoof_tesseract_noop)
after = pypdf.PdfFileReader(str(outpdf)).getDocumentInfo()
@@ -157,5 +166,3 @@ def test_creation_date_preserved(spoof_tesseract_noop, output_type, resources,
date_after = decode_pdf_date(after['/ModDate'])
assert seconds_between_dates(
date_after, datetime.datetime.now(timezone.utc)) < 1000