Compare commits

...
2218 Commits
Author SHA1 Message Date
James R. Barlow e6a7b58863 Merge branch 'de-gpl' 2020-08-12 12:20:38 -07:00
James R. Barlow 56184a762f Issue template:Give stronger hints about sample input files 2020-08-12 12:12:37 -07:00
James R. Barlow 07ab98f5af docs: mention that Ghostscript PDF/A can swallow hyperlinks
Addresses #605
2020-08-12 12:12:00 -07:00
James R. Barlow 04fb1892b4 Don't ask for sample files anymore 2020-08-12 11:40:42 -07:00
James R. Barlow 173ce2f215 v10.3.3 release notes 2020-08-07 02:23:21 -07:00
James R. Barlow 9b641055e1 Fix KeyError: 'dpi' when using --threshold on image to PDF
Fixes #607
2020-08-07 02:21:02 -07:00
James R. Barlow 4fa28d7e74 v10.3.2 release notes 2020-08-05 01:36:56 -07:00
James R. Barlow bed74501fc Fix test breakage in validation
Broken in commit 4cc0dc
2020-08-05 01:35:26 -07:00
James R. Barlow 8c90f7c972 Replace GPLv3-derived PDF/A template with PostScript generator 2020-08-05 01:30:45 -07:00
James R. Barlow aa0ec40102 Change license of all GPLv3 files to MPL-2.0
https://github.com/jbarlow83/OCRmyPDF/issues/600
2020-08-05 00:44:42 -07:00
James R. Barlow 12c567ee10 Copyright cleanup: relicense example_plugin.py
The author is relicensing this file to MIT.
2020-08-05 00:15:33 -07:00
James R. Barlow d39778ce3a Clarify copyright status of misc/batch.py, synology.py
At the time these files were contributed there was no discussion
of the license that the authors wanted to use, but the project was
MIT licensed at the time. As such, these files deemed to be MIT licensed.

https://github.com/jbarlow83/OCRmyPDF/issues/600
2020-08-05 00:12:44 -07:00
James R. Barlow e824cdbc4e Change license of misc/watcher.py to MIT
The authors of this file all agreed to relicense it under the MIT license.

https://github.com/jbarlow83/OCRmyPDF/issues/600
2020-08-04 23:57:41 -07:00
James R. Barlow 1d91c09963 Clarify license status of misc/completion/* files
These files were contributed when the project license was GPLv3. On
discussion, all known authors of these files agreed to place them
under MIT license.

See https://github.com/jbarlow83/OCRmyPDF/issues/600
2020-08-04 23:53:56 -07:00
James R. Barlow e821ca46d5 Approve pdfminer.six 20200726 2020-08-03 16:04:16 -07:00
James R. Barlow a29e4952fb Document use of mmap 2020-08-03 16:03:54 -07:00
James R. Barlow 4cc0dc6b4a Additional size increase reasons 2020-08-03 16:03:29 -07:00
James R. Barlow 7263702de9 Remove gs.py (spoofers entirely removed) and update copyright 2020-07-29 16:31:47 -07:00
James R. Barlow 0287d91874 v10.3.1 release notes 2020-07-26 21:53:08 -07:00
James R. Barlow 436af55050 Approve pdfminer.six 20200720 2020-07-26 21:51:49 -07:00
James R. Barlow d6128e6937 Fix support for older versions of pdfminer.six (boxes_flow error) 2020-07-26 21:51:25 -07:00
James R. Barlow 642437e804 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-07-22 00:34:33 -07:00
James R. Barlow 4ce802fdb2 v10.3.0 release notes 2020-07-22 00:34:27 -07:00
James R. Barlow a672422b0b Enable pikepdf mmap in other contexts 2020-07-22 00:20:07 -07:00
James R. Barlow addc2cbad0 Enable pikepdf mmap and set up signal handlers 2020-07-22 00:19:50 -07:00
James R. Barlow 5f45f77b4e docs: plugins update 2020-07-21 23:53:30 -07:00
James R. Barlow 93f9bffb37 Merge branch 'feature/leptonica-179' 2020-07-20 21:23:53 -07:00
James R. Barlow 44149ad319 Disable test_error_trap for Leptonica < 1.79
Old error trap seems unreliable in the first place so difficult to set up
a test.
2020-07-20 21:12:00 -07:00
fcatusandGitHub d80d963cea pdfinfo: Replace list comp with gen expr'n 2020-07-20 02:21:58 -07:00
James R. Barlow 4da33b8050 Update debian/copyright from Debian, with fixes 2020-07-19 03:50:43 -07:00
James R. Barlow 5cbbff8472 For Leptonica 1.79+ use leptSetStderrHandler
Lock free and considerably less dangerous to stderr messages.
2020-07-19 03:40:33 -07:00
James R. Barlow fa6e47c277 Merge branch 'feature/optimize-cleanup' 2020-07-19 01:53:11 -07:00
James R. Barlow 4ea9cffebd Add locking to Leptonica error trap
To protect another thread from interfering with our redirection of
stderr.
2020-07-19 01:51:58 -07:00
James R. Barlow ae68edefc5 pipelines: fix Python 3.7/3.8 on macOS 2020-07-19 01:51:58 -07:00
James R. Barlow 1558e068f1 docs: explain firstresult hook behavior 2020-07-16 00:01:59 -07:00
James R. Barlow d2a9c413f8 docs: install notes for ARM64 2020-07-14 01:25:16 -07:00
James R. Barlow a510b21b20 optimize: add typing for Xref, remove fspath()'s 2020-07-09 14:06:41 -07:00
James R. Barlow e33ba07aa4 Update pre-commit settings 2020-07-08 23:45:53 -07:00
James R. Barlow 373f27832b optimize: improve typing of xref_exts 2020-07-07 22:41:29 -07:00
James R. Barlow b20a6e4c5d optimize: add type hints 2020-07-07 22:18:50 -07:00
James R. Barlow 49734d5456 optimize: fix incorrect to prevent re-optimizing JBIG2s 2020-07-07 21:52:11 -07:00
James R. Barlow 26a415c5dd docs: Note usage of OCR_JSON_SETTINGS for watcher 2020-07-07 21:26:57 -07:00
James R. Barlow 60be64a5f1 Fix debug.log missing pageno handler 2020-07-04 03:59:38 -07:00
James R. Barlow 190294634c docs: edit plugins 2020-07-03 16:16:01 -07:00
James R. Barlow 1722cb579d v10.2.1 release notes 2020-07-01 03:26:57 -07:00
James R. Barlow dc42beb6a8 More typing improvements
Typing fixes bugs.
2020-06-30 15:02:30 -07:00
James R. Barlow 378f543619 TextPositionTracker: set boxes_flow=None
We don't care about the order of lines in our analysis, and this is an
expensive calculation in pdfminer.
2020-06-30 04:20:58 -07:00
James R. Barlow 62924ee280 Improve API documentation 2020-06-30 04:20:14 -07:00
James R. Barlow 86a73191b0 Plugin manager: accept Path(plugin) 2020-06-30 04:17:30 -07:00
James R. Barlow 86875997b8 Fix more mypy errors 2020-06-29 02:17:14 -07:00
James R. Barlow b939584c7a quality: fixing typing issues 2020-06-29 01:45:45 -07:00
James R. Barlow bbd174071d readme: markdown cleanup 2020-06-29 01:45:27 -07:00
James R. Barlow e5b6fe1317 pyproject.toml: weird line wrapping? 2020-06-29 01:45:12 -07:00
James R. Barlow f15d9049eb install: add Mageia
Closes #586. Thanks to @yannick56
2020-06-26 23:28:26 -07:00
James R. Barlow 7630c93e5b install: drop Ubuntu 14.04 steps
Bit rot must have set in.
2020-06-26 23:27:42 -07:00
James R. Barlow 638d68aa8a docs: move Windows ahead of FreeBSD 2020-06-26 22:49:34 -07:00
James R. Barlow a92dde058a docs: promote one liner installs, reorg Windows 2020-06-26 22:47:44 -07:00
James R. Barlow 580f2ebb4b Python 3.9beta is now known to work (Fedora) 2020-06-26 00:06:58 -07:00
James R. Barlow 01cae7a584 docs: Update Fedora versions 2020-06-23 02:08:24 -07:00
James R. Barlow 66337813e6 Spell runslow correctly 2020-06-22 23:32:09 -07:00
James R. Barlow eb5a211e72 New hocrtransform test isn't platform stable - mark runslow 2020-06-22 16:59:59 -07:00
James R. Barlow 5142933120 v10.2.0 release notes
Closes #582, #584, #545
2020-06-22 16:37:51 -07:00
James R. Barlow 06ab114aa8 Update test cache 2020-06-22 16:31:34 -07:00
James R. Barlow 1257419465 test_hocrtransform: this test is worth not caching 2020-06-22 16:31:06 -07:00
James R. Barlow 30404f53f0 Add test to sanity check our pdf renderers 2020-06-22 16:18:38 -07:00
James R. Barlow 1ce8edbdfe hocrtransform: some text not included in output after Tesseract changes 2020-06-22 15:48:23 -07:00
James R. Barlow d4b704a0ae hocrtransform: refactor colors 2020-06-22 15:22:48 -07:00
James R. Barlow 2d64e1536d hocrtransform: refactor xpath manipulations 2020-06-22 14:44:34 -07:00
James R. Barlow c8b581ac31 hoctransform: remove deprecated element.getchildren()
Breaks Python 3.9.
2020-06-22 14:28:18 -07:00
James R. Barlow ad8dead7df Document that API accepts streams now 2020-06-22 14:27:27 -07:00
James R. Barlow c9bd87254e A few minor typing issues 2020-06-22 02:31:53 -07:00
James R. BarlowandJames R. Barlow f4cb424451 Support input/output streams at API level 2020-06-22 02:02:18 -07:00
James R. Barlow fef14778d5 Fix missing f-string in log message 2020-06-22 01:17:16 -07:00
James R. Barlow 86ec63f215 Decouple plugin manager forking from PdfContext/Pagecontext 2020-06-22 01:16:59 -07:00
James R. Barlow 5b10ec9d39 jobcontext.PdfContext: remove dead code, add annotations 2020-06-22 00:34:58 -07:00
James R. Barlow 800c75c4e5 Bump requirements (mainly for Docker's benefit) 2020-06-21 01:58:53 -07:00
James R. Barlow 24d64b04c3 Update Docker to Ubuntu 20.04 and jbig2-latest 2020-06-21 01:48:31 -07:00
James R. Barlow 48e2750551 Fix some tests that were failing in Docker 2020-06-21 01:48:13 -07:00
James R. Barlow e182c5f63e Update and sync .dockerignore, .gitignore
Also blacklist .* and whitelist the ones we want.
2020-06-21 01:25:59 -07:00
James R. Barlow 06d52326db Fix deleted path in .coveragerc 2020-06-21 01:24:23 -07:00
James R. Barlow ebfe4f0d29 Fix issue #582 - PDF/A acquires title "Untitled" after conversion 2020-06-20 02:01:16 -07:00
James R. Barlow ad22977c84 v10.1.1 release notes 2020-06-17 14:45:32 -07:00
James R. Barlow 6ac50646f0 Fix OMP_THREAD_LIMIT rounded down to 0 in some cases 2020-06-17 14:43:19 -07:00
James R. Barlow 24b6a4ad50 v10.1.0 notes 2020-06-16 00:55:28 -07:00
James R. Barlow e802896d4d unpaper: use PNG input where possible
Unpaper accepts PNG as input now, so avoid generating a huge
temporary PPM file if we can. If we
must create a PNG, compress it lightly to keep our temp usage down.
2020-06-16 00:50:18 -07:00
James R. Barlow 0b5a20e593 coverage: ignore type checking 2020-06-15 15:55:39 -07:00
James R. Barlow 642998ead6 sync: refactor preprocess image filtering 2020-06-15 15:26:41 -07:00
James R. Barlow 698aab4f75 Add a lot of type annotations 2020-06-15 15:20:50 -07:00
James R. Barlow 34231ac667 sync: refactor intermediate image production 2020-06-15 15:02:28 -07:00
James R. Barlow ddedf7cd2e For --clean-final, use same image as --clean if possible 2020-06-15 13:48:49 -07:00
James R. Barlow 9d127d354c docs: improve description of plugins 2020-06-15 12:51:49 -07:00
James R. Barlow 2d2a4894ab Some corrections to release notes 2020-06-15 12:51:28 -07:00
James R. Barlow 862861e3ca Fix error message in logging from repeated filtering
If logging somehow triggers PageNumberFilter multiple times, it would fail on the second occurrence.
2020-06-13 14:50:58 -07:00
James R. Barlow 892db88f0e test_two_languages: use narrower test 2020-06-12 14:33:02 -07:00
James R. Barlow eeb44f78cc Fix tests that failed on other platforms from previous fix 2020-06-12 12:59:46 -07:00
James R. Barlow 863835f660 v10.0.1 release notes 2020-06-12 12:11:21 -07:00
James R. Barlow 393c5a9ea4 Fix error on -l lang1+lang2 2020-06-12 12:10:29 -07:00
James R. Barlow c6b9a49cbb Fix tests that fail in CI 2020-06-10 17:08:00 -07:00
James R. Barlow 17a4831745 v10 release notes and dependencies 2020-06-10 14:27:47 -07:00
James R. Barlow 7caf1e85ff info: change "Scan" message 2020-06-10 12:11:37 -07:00
James R. Barlow f59a757e8b info: tidy handling of content streams 2020-06-10 12:09:24 -07:00
James R. Barlow 872bafad4b Reinstate quick test for text/no text
Partial revert of commit 991db17
2020-06-10 12:00:52 -07:00
James R. Barlow 8599400445 Only do page analysis on pages we will do OCR on 2020-06-10 11:33:27 -07:00
James R. Barlow b6eebadf05 Use pikepdf.open with block to manage PdfInfo 2020-06-10 11:32:46 -07:00
James R. Barlow a4e88eb8f0 Simplify plugin_manager pickling 2020-06-10 00:41:19 -07:00
James R. Barlow f6257c2183 subprocess: lru_cache version checks 2020-06-10 00:32:06 -07:00
James R. Barlow 64891c2fc3 Pre-release delinting 2020-06-09 15:27:14 -07:00
James R. Barlow fe156db41d Merge branch 'release/v10' into trialmerge 2020-06-09 15:12:56 -07:00
James R. Barlow 0f942fb714 Rename ocrmypdf.exec -> ocrmypdf._exec 2020-06-09 14:59:09 -07:00
James R. Barlow be8ca589d4 Move ocrmypdf.exec.run and friends to ocrmypdf.subprocess 2020-06-09 14:53:10 -07:00
James R. Barlow 3b6f6782f0 Remove tesseract_env, --tesseract-env 2020-06-09 00:39:53 -07:00
James R. Barlow 21c0e045cb Remove _OCRMYPDF_TEST_PATH environment variable 2020-06-09 00:30:13 -07:00
James R. Barlow ebbf68bd08 The big payoff: abolishing spoofing machinery 2020-06-09 00:08:20 -07:00
James R. Barlow 2059e916da Convert all ghostscript spoofs to test plugins 2020-06-09 00:00:25 -07:00
James R. Barlow c22f245606 Plugins must return not-None if they intend to stop builtin 2020-06-08 23:48:45 -07:00
James R. Barlow 7b9025f397 Convert generate_pdfa to plugin 2020-06-08 22:28:38 -07:00
James R. Barlow b109445215 Move Ghostscript rasterize_pdf to plugin 2020-06-08 17:10:27 -07:00
James R. Barlow fd1cd8e50a docs: explain --rotate-pages-threshold 2020-06-08 07:46:55 -07:00
James R. Barlow c6c70c2171 docs: Ubuntu 20.04 install instructions 2020-06-08 07:42:13 -07:00
James R. Barlow a9a473f2e5 Convert all tesseract cache usages to plugin 2020-06-05 17:55:18 -07:00
James R. Barlow 6268e2faff Begin replacing tests/spoof/tesseract_cache with plugin 2020-06-05 17:27:10 -07:00
James R. Barlow ec3f506500 Convert tesseract_badutf8 to plugin 2020-06-05 16:38:19 -07:00
James R. Barlow 00daa51a73 v9.8.2 release notes 2020-06-03 13:28:35 -07:00
James R. Barlow e60f4d3f43 docs: tidy Cygwin install 2020-06-03 13:27:05 -07:00
James R. Barlow 7460745f80 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-06-03 13:25:12 -07:00
James R. Barlow 5e14d5b0dd Fix test_report_file_size
Use more realistic test data
2020-06-03 13:24:55 -07:00
James R. Barlow d118132fa6 layout: look for text in XObjects too 2020-06-03 13:16:55 -07:00
5f47aac36f Add installation instructions for Windows/Cygwin64 (#571)
Co-authored-by: Jim Garrison <bitbucket@jhmg.net>
2020-06-03 13:16:23 -07:00
James R. Barlow c6b2fa8851 Remove unpaper spoof; no plugin needed 2020-06-02 02:42:14 -07:00
James R. Barlow 1b92f447c3 Convert tesseract_crash to plugin 2020-06-02 02:36:41 -07:00
James R. Barlow 82e7eb91d2 Tidy tesseract_noop 2020-06-02 01:50:02 -07:00
James R. Barlow 4f4ad0fb76 Convert tesseract_big_image_error to plugin 2020-06-02 01:49:47 -07:00
James R. Barlow 1d0b8641a0 Improve file size increase warning to account for changes to small files
Fixes #569
2020-06-02 00:35:59 -07:00
James R. Barlow daca919775 Mark pdfminer.six 20200517 as supported 2020-06-02 00:11:02 -07:00
James R. Barlow 1598f2f0e5 Abolish spoof_tesseract_noop 2020-06-01 03:07:53 -07:00
James R. Barlow 2b23f7ec73 tesseract_noop: begin implementing with plugin 2020-06-01 02:45:49 -07:00
James R. Barlow 6528234608 Fix tesseract_ocr.py errors 2020-06-01 02:27:27 -07:00
James R. Barlow 642ebc6098 Fix test that failed on Windows 2020-05-28 15:52:00 -07:00
James R. Barlow 74fdfeea3f v9.8.1 notes 2020-05-28 15:04:23 -07:00
James R. Barlow 3754185f56 Mark pdfminer.six 20200517 as supported 2020-05-28 15:01:51 -07:00
James R. Barlow df9f5157bd Fix shim_paths to account for unexpected files in Program Files\gs
Fixes #565
2020-05-28 14:58:41 -07:00
James R. Barlow aa060db5bc Refactor tesseract_env variable into the plugin
Removed all cases except one in api.py, which isn't worth solving because
it should be removed anyway.

This also fixes a logic error in the OMP_THREAD_LIMIT decision, api.py
did not use pass kwargs correctly so they never worked before.
2020-05-26 02:14:06 -07:00
James R. Barlow d43212d30b Refactor --language argument into set 2020-05-25 03:20:10 -07:00
James R. Barlow a0f9ca3a30 Move Tesseract options validation into plugin 2020-05-25 01:31:46 -07:00
James R. Barlow 0cefe886ec Update email 2020-05-19 16:12:36 -07:00
James R. Barlow f656c00f41 docs: Note about OCRmyPDF speed 2020-05-18 01:27:45 -07:00
James R. Barlow 03da34ee24 Test files needed! 2020-05-16 17:04:44 -07:00
James R. Barlow 9bccff4f88 Move Tesseract specific arguments to plugin 2020-05-16 03:24:31 -07:00
James R. Barlow 2bd586e093 Compare requested languages to OCR engine instead of tesseract directly
Also refactoring to facilitating validation needing the plugin manager.
2020-05-16 01:50:37 -07:00
James R. Barlow 9af94ac9b7 pipeline: use OCR engine abstraction instead of Tesseract 2020-05-16 01:28:56 -07:00
James R. Barlow 8174089c8b Begin transforming Tesseract into pluggable OCR engine 2020-05-14 03:54:21 -07:00
James R. Barlow 41eb54cc0a Standardize tesseract.generate_hocr and _pdf parameters 2020-05-14 03:23:25 -07:00
James R. Barlow 12a2f78c4d Fix validation of languages not using tesseract_env
And some related issues.
2020-05-14 03:19:22 -07:00
James R. Barlow d372f1f7fa Remove "skip page" from tesseract interface
Breaks tests/test_main.py::test_tesseract_missing_tessdata because
conftest.py does not update options.tesseract_env before testing options
for some reason, and tesseract.has_textonly_pdf raises an exception
instead of returning False as the test assumes.
2020-05-12 04:09:42 -07:00
James R. Barlow 6f5b75bcd0 Remove lru_cache on get_version
Does not play well with forking.
2020-05-12 03:51:48 -07:00
James R. Barlow a2d3e0b53e Convert remaining imports to absolute 2020-05-12 02:12:08 -07:00
James R. Barlow 7f67556995 ocrmypdf.__init__: Hide _HookimplMarker 2020-05-12 01:35:45 -07:00
James R. Barlow db8c37e58c Refactor ocrmypdf.exec.__init__.py 2020-05-12 01:34:10 -07:00
James R. Barlow a87c81a64f helpers: remove unnecessary isinstance test 2020-05-12 01:28:50 -07:00
James R. Barlow 4b986a5943 cli: make ArgumentParser._api_mode private 2020-05-12 01:28:36 -07:00
James R. Barlow 2fae9b655e Remove **kwargs from check_external_program; deprecated 2020-05-12 01:07:01 -07:00
James R. Barlow 2541f6cf89 Fix missing jbig2enc reported as error with -O3 instead of warning
Fixes #558
2020-05-12 01:05:57 -07:00
James R. Barlow 33b68454f3 watcher: cleanup getenv casting 2020-05-08 03:49:49 -07:00
James R. Barlow 977665d2b6 Delint some tests 2020-05-08 03:49:33 -07:00
James R. Barlow fd7497f00d Remove old function tesseract.v4() 2020-05-08 03:44:39 -07:00
James R. Barlow 790ff58f67 Add fix for bug in Windows Python 3.6/3.7
TypeError: argument of type 'WindowsPath' is not iterable
2020-05-07 22:19:21 -07:00
James R. Barlow 4b98ce391b docs: rename security->pdfsecurity so github won't misinterpret it 2020-05-07 03:54:27 -07:00
James R. Barlow 417dbd43f6 docs: plugin documentation 2020-05-07 03:53:37 -07:00
James R. Barlow 7a12908db9 Relocate example plugin 2020-05-07 03:27:39 -07:00
James R. Barlow 9462f0a28f graft: more refactoring 2020-05-07 02:59:24 -07:00
James R. Barlow e760622a5c graft: refactor 2020-05-07 02:03:42 -07:00
James R. Barlow 1b086f60a9 tesseract.py: api cleanup 2020-05-06 12:37:44 -07:00
James R. Barlow 85cbf94a6e Convert many uses of str paths to Path 2020-05-06 02:53:47 -07:00
James R. Barlow 6f4286e1b1 New hook: filter_page_image 2020-05-06 02:24:07 -07:00
James R. Barlow 39888ae8c9 Rename install_cli to add_options 2020-05-06 01:10:09 -07:00
James R. Barlow dd361ecd05 Support importing plugin by filename 2020-05-06 00:44:40 -07:00
James R. Barlow 32759c9025 Change argument from --plugins to --plugin 2020-05-06 00:43:40 -07:00
James R. Barlow 59440448ee Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-05-04 01:38:26 -07:00
Peter HoggandGitHub 51b54893ce docs: update Arch Linux install instructions (#540)
The python-pdfminer.six package is now available in the official Arch
repositories. The dependency will be automatically resolved when
installing the OCRmyPDF AUR package.
2020-05-04 01:37:58 -07:00
James R. Barlow 1f3665f614 docs: remove reference to brewfile 2020-05-03 16:10:26 -07:00
James R. Barlow 75c34b873a optimize: convert from executor to progress pool 2020-05-03 02:04:57 -07:00
James R. Barlow fe4296c53b safe_symlink: remove deprecated params 2020-05-03 00:53:47 -07:00
James R. Barlow c85278b31d Delinting 2020-05-03 00:53:29 -07:00
James R. Barlow 5dbc080fa0 Rename PDFContext->PdfContext 2020-05-02 04:32:46 -07:00
James R. Barlow e02f6c1e97 Support plugin invocation with API 2020-05-02 03:34:31 -07:00
James R. Barlow 8c9a8fc85c pluginspec: avoid circular reference 2020-05-02 03:32:55 -07:00
James R. Barlow 23d558ad8c Allow plugins to add command line arguments 2020-05-02 01:37:24 -07:00
James R. Barlow be107b4fed Set up filter_ocr_image hook 2020-05-01 02:56:41 -07:00
James R. Barlow 8d2535e327 Get pluggy to work with forking workers 2020-05-01 02:39:50 -07:00
James R. Barlow 5eb4fe0052 Refactor plugin setup to get_plugin_manager 2020-05-01 02:18:31 -07:00
James R. Barlow d8ff4485f8 Move samefile to helpers 2020-05-01 02:18:11 -07:00
James R. Barlow 82bce463ae Start pluggy-based plugin system 2020-05-01 02:15:23 -07:00
James R. Barlow 016dfd420c Add warning if problematic --tesseract-pagesegmode is selected
Fixes #549
2020-04-30 04:12:11 -07:00
James R. Barlow b59e761a14 v9.8.0 release notes 2020-04-28 02:40:17 -07:00
James R. Barlow 17cd655752 Don't utf-8 decode tesseract --print-parameters
Output not guaranteed to be UTF-8.

Fixes #543.
2020-04-28 02:37:17 -07:00
James R. Barlow b840b16c82 Remove tesseract_badutf8.py
Should have been removed in 9db01c7
2020-04-28 02:35:23 -07:00
James R. Barlow 8f5c95f0f4 Remove last vestiges of command line usage of qpdf - change to check_pdf 2020-04-26 05:33:26 -07:00
James R. Barlow 168fc60774 Update release notes with v10 changes 2020-04-26 05:14:59 -07:00
James R. Barlow c84d0f606d ghostscript: remove deprecated argument from generate_pdfa 2020-04-26 05:11:11 -07:00
James R. Barlow 8b54ce338f setup: remove deprecated message about removeal of --force parameter 2020-04-26 05:09:42 -07:00
James R. Barlow 18c4aa10bf Adjust number of workers for concurrent page scanning 2020-04-26 04:21:15 -07:00
James R. Barlow 991db17fde Remove Ghostscript-based text extraction
While faster than Python based methods, we've outgrown the limited
amount of information Ghostscript provides with this feature, and it
repeats an analysis we have to do anyway to learn what images are
present.
2020-04-26 04:02:07 -07:00
James R. Barlow 2c07515907 macOS - use spawn for multiprocessing
See bpo-33725. This is the default for 3.8, opt-in for 3.7 and older.
2020-04-26 03:49:40 -07:00
James R. Barlow 27a3b80376 Use once-per-worker pikepdf init 2020-04-26 03:49:20 -07:00
James R. Barlow 8c381a0227 Replace task_initargs with use of partial() 2020-04-26 03:49:20 -07:00
James R. Barlow 86145a8c76 Some wrong with forking worker_pdf, just open it once per page for now 2020-04-26 03:49:20 -07:00
James R. Barlow 7513f5425c Fix some broken tests 2020-04-26 03:49:20 -07:00
James R. Barlow af3c3c6466 Further refactoring of concurrency concerns 2020-04-26 03:49:20 -07:00
James R. Barlow db3e75e33e Refactor multiprocessing pool 2020-04-26 03:49:13 -07:00
James R. Barlow ce49fc26dd Do pikepdf.open() once instead of per worker 2020-04-26 03:42:13 -07:00
James R. Barlow d0d0a98dca First cut at concurrent page scan
Improvement appears on 168 page file. Needs refactoring
2020-04-26 03:42:13 -07:00
James R. Barlow 3834d1a0bf azure: use brew python instead 2020-04-26 00:58:38 -07:00
James R. Barlow 33e982b3fd azure: add certifi, openssl for macOS 2020-04-26 00:37:14 -07:00
James R. Barlow 43d650e78c Fix issue where only first PNG-style image would be optimized 2020-04-25 03:50:11 -07:00
James R. Barlow b4c65c5781 Update requirements 2020-04-25 03:49:34 -07:00
James R. Barlow d96867e6ab watcher: add polling and log level adjustment 2020-04-24 04:14:44 -07:00
James R. Barlow 0a5108e704 install: clarify that old ocrmypdf should be removed from Ubuntu 18.04
Closes #526
2020-04-24 04:14:19 -07:00
James R. Barlow 94c52a6fa3 Refactor 'xyres' into Resolution 2020-04-24 04:12:05 -07:00
James R. Barlow 57771f06a3 Refactor xy-pair for resolution to tuple 2020-04-16 15:38:33 -07:00
James R. Barlow 58abb5785c pytest picky about list vs tuple 2020-04-15 03:16:51 -07:00
James R. Barlow 509e75eaff v9.7.2 release notes 2020-04-15 02:56:46 -07:00
James R. Barlow 0c50eedb2a Support pdfminer.six 20200402 2020-04-15 02:55:22 -07:00
James R. Barlow 4581027246 Drop support for pdfminer.six 20181108
This version required a patch that has since been mainlined, and also did not
declare its dependency on chardet
correctly. We can remove both hacks now.
2020-04-15 02:50:36 -07:00
James R. Barlow 31b5f63f85 hocrtransform: cleanup/PEP8
Some API breaking changes.
2020-04-15 02:48:56 -07:00
James R. Barlow 957fb1494e pytest picky about list vs tuple 2020-04-15 02:26:20 -07:00
James R. Barlow 9e3e4f2687 Improve help text about aborting due to text 2020-04-15 02:17:55 -07:00
James R. Barlow 2155bcacb4 Loosen test language requirements - eng/deu 2020-04-15 00:30:38 -07:00
James R. Barlow 346da95899 Suppress loglevel since we have color now 2020-04-15 00:09:36 -07:00
James R. Barlow f4f7946a0c Add colored logs 2020-04-15 00:05:38 -07:00
James R. Barlow c2919f2e1c Reinstate logging of page numbers 2020-04-15 00:05:23 -07:00
James R. Barlow a63d624052 Improve logging of subprocess output 2020-04-15 00:04:43 -07:00
James R. Barlow af91489376 Remove safe_symlink log= warning 2020-04-14 23:59:33 -07:00
James R. Barlow d146d2b65c The Great Logging Refactor
Remove all instances of logger object being passed as parameters.
This was a holdover from ruffus, and complicated a lot of simple things.
2020-04-14 23:59:33 -07:00
James R. Barlow 4ff4ed24a8 Refactor Windows executable shims 2020-04-14 23:59:33 -07:00
James R. Barlow c38ff90081 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-04-14 23:55:01 -07:00
James R. Barlow 4c029e973f Fix isinstance(..,str) 2020-04-14 23:53:52 -07:00
Lars K.W. GohlkeandGitHub 21cf9029e8 docs: Set ownership when using docker image (#518) 2020-04-14 23:32:01 -07:00
James R. Barlow 4a640b8dcd Fix language argument not working as list
Fixes #523
2020-04-14 23:18:52 -07:00
James R. Barlow 9471bc8921 Fix versions with leading v, e.g. v5.0 2020-04-10 13:42:33 -07:00
James R. Barlow 7fe06c64fc v9.7.1 release notes 2020-04-10 13:00:19 -07:00
James R. Barlow d13d70fd56 Fix version checker failing for qpdf 10.0.0
Fixes #527
2020-04-10 13:00:19 -07:00
James R. Barlow 58ec56180a Add a few more type annotations to public APIs 2020-04-10 13:00:19 -07:00
James R. Barlow 32a88f1bad docs: warn that AWS Lambda doesn't work 2020-04-10 13:00:19 -07:00
James R. Barlow 99ef42940c docs: warn that Windows users should use an ifmain guard 2020-04-10 13:00:19 -07:00
jbarlow83andGitHub c152710617 Update issue templates 2020-04-04 15:41:53 -07:00
James R. Barlow 8de0f9b86f v9.7.0 release notes 2020-03-29 22:45:25 -07:00
James R. Barlow 23bc3d3a29 tests: workaround for Ghostscript 9.52 txtwrite problem 2020-03-29 22:45:16 -07:00
James R. Barlow 8307832ce9 tests: add force OCR to a file with text that Ghostscript doesn't see
For gs 9.52 support.

Also refactor use of pikepdf.open() to use with blocks.
2020-03-29 22:44:27 -07:00
James R. Barlow dd1cf567db watcher: Fix JSONDecodeError if OCR_JSON_SETTINGS not set
Fixes #516
2020-03-29 21:58:37 -07:00
James R. Barlow 2490be8490 Fix debug.log not being deleted on Windows (probably)
Fixes #515
2020-03-29 21:53:56 -07:00
James R. Barlow 85e6c6669a docs: Add username to WSL instructions
Fixes #519
2020-03-29 21:16:24 -07:00
James R. Barlow 00498282f5 validation: blacklist Ghostscript 9.51 too 2020-03-24 21:27:18 -07:00
James R. Barlow e4cc9fcba7 Wrong number of threads to use shown when OMP_THREAD_LIMIT is defined 2020-03-23 01:06:55 -07:00
James R. Barlow a4555b1dae Add halftone mask to leptonica 2020-03-18 23:09:39 -07:00
James R. Barlow f35a2303bb info.py: linearize O(n^2) search for use images on a page 2020-03-18 22:59:18 -07:00
James R. Barlow 82142fe5ef Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-03-16 04:11:03 -07:00
James R. Barlow 9be533b5f4 watcher: allow all parameters to ocrmypdf.pdf to be passed by JSON 2020-03-15 21:45:51 -07:00
James R. Barlow 99653fcd32 optimize: consider ICCBased 1 bit for optimization 2020-03-15 02:20:44 -07:00
James R. Barlow 5442c97ed8 Consult ICC profile when determining image colorspace 2020-03-11 04:03:09 -07:00
tlwhitecandGitHub 0165255bd9 fix install instructions for Ubunti 16.04 (#507)
`pip3` defaults to the system's outdated version which downloads wrong qpdf package.
2020-03-11 02:57:37 -07:00
James R. Barlow 378e4dae3b Expand documentation for subprocess.run() from test 2020-03-04 13:37:44 -08:00
James R. Barlow cdf5afa753 reqs: update pikepdf version 2020-03-03 11:56:10 -08:00
James R. Barlow a2deee4920 v9.6.1 release notes 2020-03-03 03:40:33 -08:00
James R. Barlow 1efa79cce2 Remove potentially non-free file logo.afdesign 2020-03-03 03:40:33 -08:00
James R. Barlow b3b61c152c Handle malformed DocumentInfo (#497)
User submitted a PDF in which /Trailer /Info pointed to the XMP metadata
block instead of a DocumentInfo dictionary. Fix and add test.
2020-03-03 03:27:01 -08:00
James R. Barlow e429c3d729 docs: install cleanup 2020-03-03 03:25:43 -08:00
James R. Barlow 8308b20096 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-03-03 02:25:58 -08:00
James R. Barlow 8b41f60b6e docs: docker prefers .yml not .yaml 2020-03-03 02:25:21 -08:00
AlexandGitHub d56f749017 Improve ocrmypdf.bash completions on macOS (#504)
Fixes #502
2020-03-03 02:22:01 -08:00
James R. Barlow 9f31774aa9 docs: document --pages 2020-03-03 02:15:48 -08:00
James R. Barlow 7d55f6e01f docs: extract example files from batch.rst 2020-03-03 02:15:35 -08:00
James R. Barlow c3bd2f296d docs: fix Docker syntax to use stdin/stdout properly 2020-03-03 02:14:50 -08:00
James R. Barlow e40c60d4d8 watcher: add self to copyright 2020-03-03 00:59:57 -08:00
James R. Barlow 3960232ae0 docs: more clarifications 2020-03-03 00:59:48 -08:00
James R. Barlow 5fbb3fc6ac Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-03-01 23:05:10 -08:00
deisiandGitHub 0b1db8fccd Fixes docker-compose.yaml file (#499)
Fixes https://github.com/jbarlow83/OCRmyPDF/issues/498
2020-03-01 23:01:39 -08:00
James R. Barlow 0417610f9b docs: some mild improvements 2020-02-25 22:23:58 -08:00
James R. Barlow 43a23e3695 Disable Travis 2020-02-25 22:22:57 -08:00
James R. Barlow c4ca572afd Merge branch 'pigmonkey-aur-manual' 2020-02-25 22:21:35 -08:00
Pig Monkey e04e4565a9 Demonstrate installing the AUR package without a helper
This describes how to use the AUR package on a minimal install, as per
the discussion in #494.

There may be formatting mistakes. I don't use RST myself, so I wrote the
instructions in Markdown, converted via Pandoc, and gave the output a
quick comparison against the rest of the installation docs.
2020-02-25 18:59:49 -08:00
knobixandGitHub 2391fb0be0 Update installation instructions for FreeBSD (#493)
Python 3.7 is the new default version since 2020Q1 which is reflected in
the new prefix (= py37-).

Also update the current available FreeBSD versions:

* FreeBSD 11.2-RELEASE has reached its End-of-Life in 2019Q4
* FreeBSD 12.1-RELEASE was also introduced in 2019Q4
2020-02-24 23:40:25 -08:00
James R. Barlow c16f79d51b docs: add Docker compose configuration for watchdog 2020-02-18 02:50:57 -08:00
James R. Barlow e3e888efde Readme: Add another heise article 2020-02-18 02:41:28 -08:00
James R. Barlow 84f7e34ace Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-02-18 02:11:40 -08:00
Ivan KuchinandGitHub 32e2175891 Docker image includes also French, Portuguese and Spanish (#491) 2020-02-18 02:10:01 -08:00
James R. Barlow 975abfde9a docs: archlinux install - yaourt is gone 2020-02-18 02:08:58 -08:00
James R. Barlow d929ed76c2 docs: typo 2020-02-12 00:07:24 -08:00
James R. Barlow 4a27124eab Simplify metadata for invalid xml in output
Removes possibly non-free resource enron1.pdf.
2020-02-12 00:07:18 -08:00
James R. Barlow 683ffb84e8 Update reqs 2020-02-10 01:20:33 -08:00
James R. Barlow 2f2602357b v9.6.0 notes updated 2020-02-10 01:20:12 -08:00
James R. Barlow a5f429f499 Merge branch 'pr479' 2020-02-10 01:10:30 -08:00
James R. Barlow bdb7f92131 ifmain -> main() 2020-02-10 01:10:12 -08:00
James R. Barlow 09f15ac4c0 v9.6.0 notes 2020-02-10 01:01:49 -08:00
James R. Barlow 4fdbf55c11 setup: approve pdfminer.six 20200124 2020-02-09 23:50:56 -08:00
James R. Barlow fe2b07652b docs: simplify/fix Ubuntu 18.04 install instructions 2020-02-09 23:48:53 -08:00
James R. Barlow f6d7aa6e33 Refactor page rotation and re-enable message at info level 2020-01-30 17:35:20 -08:00
James R. Barlow a005d14f91 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2020-01-30 16:24:16 -08:00
Matthias BraunandGitHub 6f66232d44 Fix typos, add instructions for training data (#477) 2020-01-30 16:24:41 -08:00
James R. Barlow b8a780d684 Wait for file based on pikepdf 2020-01-30 12:40:48 -08:00
James R. Barlow 82f393dd09 Order of events 2020-01-30 12:40:19 -08:00
James R. Barlow 4952af1604 watcher: some refactoring 2020-01-28 12:56:19 -08:00
James R. Barlow bcf77375c0 Fix grammar in output message 2020-01-28 07:33:28 -08:00
Ian Alexander 3eab161771 Update logging and env var extensibility 2020-01-20 10:45:28 -08:00
Ian Alexander b7f38e976b Watched folder bug fixes, new flags, and docs updates. 2020-01-20 00:20:29 -08:00
James R. Barlow a6567f2ae4 v9.5.0 release notes revised 2020-01-18 01:48:33 -08:00
James R. Barlow e860c56b75 Fix regression: metadata updates not taking effect 2020-01-17 23:01:37 -08:00
James R. Barlow 2e15d52895 v9.5.0 release notes 2020-01-17 03:11:33 -08:00
James R. Barlow ce97af5a79 Add OCR quality measurement API 2020-01-17 03:10:27 -08:00
James R. Barlow 3831c4cd4d Refactor metadata_fixup 2020-01-14 01:10:15 -08:00
James R. Barlow 61a2674317 Skip test that needs chmod when on Windows 2020-01-06 02:36:04 -08:00
James R. Barlow 9ad8cbf1f6 Fix assert that depends on POSIX-y file handling 2020-01-06 02:02:05 -08:00
James R. Barlow 123fde174d Don't use debug.log in pytest
pytest does not reset the state of logging if we install a file handler,
which will cause FileNotFoundError after the temporary folder is removed.

Semi-related:
https://github.com/pytest-dev/pytest/issues/5502
2020-01-06 01:46:19 -08:00
James R. Barlow fd991a2380 Allow pdfminer.six 20200104 and update recommended versions 2020-01-05 21:37:28 -08:00
James R. Barlow 6f5d77d930 Also generate log file in temp folder on verbose mode 2020-01-05 21:33:32 -08:00
James R. Barlow 5169ac633b docs: mention pdfgrep too 2020-01-05 21:32:36 -08:00
James R. Barlow 5b6ab1e003 lept: improve lib not found error message
Closes #471
2020-01-05 01:05:53 -08:00
James R. Barlow 8f984bf958 docs: add note on limitations of sidecar file 2020-01-04 16:43:13 -08:00
James R. Barlow 9c5f0d0ec6 Eliminate last use of PyPDF2 from test suite 2020-01-04 16:32:01 -08:00
James R. Barlow 32041c43e1 tests: improve tesseract coverage 2020-01-04 02:35:14 -08:00
James R. Barlow 599028bebb tesseract: don't explicitly set lstm_use_matrix
Apparently tesseract does this own its own as needed.
2020-01-04 01:17:33 -08:00
James R. Barlow 6faa8f7221 logging: always log process arguments and stderr when at debug
Also remove ad-hoc logging of this information.
2020-01-01 16:48:48 -08:00
James R. Barlow a4dc5e365f logging: fix incorrect usage: logging.Logger() 2020-01-01 16:47:36 -08:00
James R. Barlow e2a563cc76 logging: create a debug log when -k parameter is issued 2020-01-01 16:47:15 -08:00
James R. Barlow 1037d73efb tests: use smaller files for ghostscript 2019-12-31 17:20:28 -08:00
James R. Barlow aeb7b142a9 tests: skip tests not compatible with coverage
For reasons not entirely clear, stdout will get some data injected when
pytest-cov is running. Our tests that
check for clean stdout need to ignore this.

We check for an environment variable that is defined only when coverage is
running.
2019-12-31 17:10:51 -08:00
James R. Barlow 422ea9777e Remove session scope from fixtures
pytest seems to prepare os.environ in complex ways, so we want to ensure
these fixtures are not reused.
2019-12-31 17:09:23 -08:00
James R. Barlow 2f1c743227 Rewrite main pool loop
pytest-cov documentation recommends using explicit
management of multiprocessing.Pool rather than the context manager.
This is supposed to work better for collecting coverage data, particularly
on Windows.
2019-12-31 16:23:41 -08:00
James R. Barlow 96ee21aee9 Try to set up subprocess coverage better 2019-12-31 15:39:45 -08:00
James R. Barlow 4b759af6ff tests: fix problems with ghostscript spoofers 2019-12-31 15:33:03 -08:00
James R. Barlow 25d2b0cda4 test: environment warnings/cleanup 2019-12-30 22:38:50 -08:00
James R. Barlow 16dd8b54a8 ghostscript: don't delete output_file that will never exist
We stream output now, so no point in deleting.
2019-12-30 22:38:38 -08:00
James R. Barlow c4dc5269d2 tests: remove some obscure things from coverage 2019-12-30 21:16:16 -08:00
James R. Barlow c36e9950ae tests: test TqdmConsole 2019-12-30 17:51:09 -08:00
James R. Barlow 0c0d53b10f tests: AcroForm test case did not work correctly; fixed 2019-12-30 17:50:32 -08:00
James R. Barlow 63de7e1677 Improve error message for unreadable input files 2019-12-30 16:14:52 -08:00
James R. Barlow b0e92760a2 tests: add coverage for helpers 2019-12-30 15:52:10 -08:00
James R. Barlow 054c0773a3 Update completions 2019-12-29 02:40:55 -08:00
James R. Barlow 89aa78b724 docs: fix obsolete statement to "brew install tesseract-lang"
Closes #469
2019-12-29 02:37:10 -08:00
James R. Barlow 708113a514 Windows: Remove Program Files cache from ocrmypdf.exec
@lru_cache doesn't work here, so let's just remove it.
2019-12-29 02:36:20 -08:00
James R. Barlow 95ef5410c2 azure: tweak windows script 2019-12-28 16:12:53 -08:00
James R. Barlow 868b3b4abd exec/init: os.get_exec_path() returns list not str 2019-12-28 16:11:08 -08:00
James R. Barlow 045bdff95a azure: homebrew broke something to do with python@2? 2019-12-28 16:10:43 -08:00
James R. Barlow d12b27ac1d v9.3.0 release notes 2019-12-28 15:42:24 -08:00
James R. Barlow e4e00de79f Add improved example demonstrating watched folder functionality
Closes #466
2019-12-28 15:37:42 -08:00
James R. Barlow a53a3937c2 Fix exception on parsing Ghostscript error messages 2019-12-20 11:25:45 -08:00
James R. Barlow 343424b4d2 azure: only publish code coverage for macOS
macOS (due to Homebrew) currently has the most comprehensive code
coverage. Azure's code coverage feature does not merge code coverage,
so last task to finish wins.
2019-12-20 10:56:10 -08:00
James R. Barlow c5edff2c2f Sort imports 2019-12-19 15:31:18 -08:00
James R. Barlow 8c5f8b8ddd Add isort to precommit 2019-12-19 15:29:38 -08:00
James R. Barlow 39da931a56 Look in Program Files for executables and liblept5.dll 2019-12-19 12:11:32 -08:00
James R. Barlow 9fe354359b Generally update documentation about available platforms 2019-12-19 00:27:37 -08:00
James R. Barlow facc4750bc Remove command line qpdf from azure and travis 2019-12-12 00:14:05 -08:00
James R. Barlow 437c235738 v9.2.0 release notes and docs 2019-12-11 13:13:51 -08:00
James R. Barlow 9559b0b186 Use pikepdf to perform qpdf.check() 2019-12-11 01:21:15 -08:00
James R. Barlow 91456e19a4 pdfa.py: Fix misleading comment 2019-12-11 01:05:47 -08:00
James R. Barlow a2d89f67c4 Improve help messages for Windows 2019-12-10 01:44:00 -08:00
James R. Barlow f34130d193 Fixed case where page image was not converted to JPEG
If a preprocessing option was used,
and all original images on the page were JPEGs,
and --output-type=pdf, then
images would saved as Flate instead
of converted to JPEG.
2019-12-10 01:07:59 -08:00
James R. Barlow c5571388e2 Improve test coverage of _sync.py 2019-12-10 01:06:27 -08:00
James R. Barlow 9af59c0d6d docs: improvements for Windows 2019-12-09 21:39:01 -08:00
James R. Barlow 55ae838cb7 azure: fix extra build step 2019-12-09 21:11:50 -08:00
James R. Barlow c434b97f55 docs: more install notes 2019-12-09 16:18:01 -08:00
James R. Barlow 607eee198d tests: split out preprocessing tests 2019-12-09 16:18:01 -08:00
James R. Barlow 5e2a7f8a56 tests: speed up several slow tests 2019-12-09 16:17:57 -08:00
James R. Barlow fd9550acda Add Azure Pipelines CI/CD 2019-12-09 16:17:33 -08:00
James R. Barlow b354511ac9 ghostscript: document need to write to stdout when using txtwrite 2019-12-09 16:17:10 -08:00
James R. Barlow 7be293f628 Address tests that fail on Windows with Python 3.7 or 3.6 2019-12-09 16:17:10 -08:00
James R. Barlow 65855dc14c Fix close_fds=True on Windows Python 3.6 2019-12-09 16:17:10 -08:00
James R. Barlow cac4a8b9b6 Suppress duplicate error messages from Ghostscript 2019-12-06 15:14:15 -08:00
James R. Barlow 17d97b354a Ignore mypy cache 2019-12-06 15:11:10 -08:00
James R. Barlow 1c1b60fa9f Add typing hints for ocr() function 2019-12-06 15:10:54 -08:00
James R. Barlow 6b745d892f Merge branch 'windows' 2019-12-06 15:09:09 -08:00
James R. Barlow fbf271a3ec Remove Tesseract < 4.0 specific check 2019-12-06 15:06:02 -08:00
James R. Barlow 8077718804 Possible fix to loss of log adapter state 2019-12-06 15:05:58 -08:00
James R. Barlow 66bda3420a docs: cause about using Windows in production 2019-12-06 15:03:20 -08:00
James R. Barlow f6510e2b15 Document function of symlink shim 2019-12-06 15:00:12 -08:00
James R. Barlow 51abd79136 Tesseract no longer posts an error message if config file not found 2019-12-04 21:35:28 -08:00
James R. Barlow 5607429d9a tests: error message from tesseract change 2019-12-04 21:31:01 -08:00
James R. Barlow b8b7ecfe7f Fix DecompressionBomb related errors due to Windows process differences 2019-12-04 21:10:27 -08:00
James R. Barlow d4abe88452 docs: sketch Windows install procedure 2019-12-04 21:01:09 -08:00
James R. Barlow cb3cfaa055 Add Windows install advice 2019-12-04 21:01:09 -08:00
James R. Barlow 9db01c7ff5 Remove test_bad_utf8
Due to difficulties of getting this to work on Python 3.8, Windows, and
high probability that this behavior is now gone from Tesseract 4.0+.

Originally added in 2017.
2019-12-04 21:01:09 -08:00
James R. Barlow d0301813cc ghosttext: mention page number differences 2019-12-04 21:01:09 -08:00
James R. Barlow cff37bf681 Make test_german more Windows-friendly 2019-12-04 21:01:09 -08:00
James R. Barlow 66d04dd6e3 Don't expect filenames to be replicated on NT 2019-12-04 21:01:09 -08:00
James R. Barlow 06a1f987d4 Use _OCRMYPDF_TEST_PATH for testing and .py stubs to simulate symlinks 2019-12-04 21:01:06 -08:00
James R. Barlow e51e21c6b6 ghostscript: Refactor checking for executable name on Windows 2019-12-04 21:01:06 -08:00
James R. Barlow c5fa72bd4e ghostscript: use run(check=True) for more consistent error handling 2019-12-04 21:01:00 -08:00
James R. Barlow bf99587aa1 ghostscript: use correct executable name on Windows 2019-12-04 17:37:38 -08:00
James R. Barlow d249aef57d ghostscript: don't use NamedTemporaryFile
Temporary files are more awkward for Windows.
2019-12-04 17:37:38 -08:00
James R. Barlow 43ab7c88d7 Remove os_environ() context manager 2019-12-04 17:37:38 -08:00
James R. Barlow ca9669742d Move gs tests to test_ghostscript 2019-12-04 17:14:27 -08:00
James R. Barlow 8a1dddc3ee Don't worry about closed streams on Windows 2019-12-04 17:14:27 -08:00
James R. Barlow 0cd424ffcb Enforce str-only environment for Windows since it's more strict 2019-12-04 17:14:27 -08:00
James R. Barlow fde550f9a7 test: Replace many instances of run_ocrmypdf in subprocess with inline 2019-12-04 17:14:27 -08:00
James R. Barlow a3726e4ce3 Fix test_metadata: use mmap in a Windows and POSIX compatible way 2019-12-04 17:13:52 -08:00
James R. Barlow 4ab0a8ff35 Fix test_single_page_inline_image - remove temp file 2019-12-04 17:13:51 -08:00
James R. Barlow 37f6f72df3 tests: a few Windows fixes 2019-12-04 17:13:51 -08:00
James R. Barlow 3f92867ae6 Fix TypeError "environment can only contain strings"
Apparently Windows Python doesn't coerce pathlib.Path to str.
2019-12-04 17:13:51 -08:00
James R. Barlow e63503d64b Fix difference in Windows error message breaking test_no_languages 2019-12-04 17:13:51 -08:00
James R. Barlow 17d20309c7 leptonica: fix missing Leptonica error message for Windows
Since it has the unintuitive fix of adding Tesseract to PATH.
2019-12-04 17:13:51 -08:00
James R. Barlow fe7c69ce95 leptonica: don't open files by name; use memory buffers
Avoids encoding issues and makes error trap unnecessary in some cases.
2019-12-04 17:13:51 -08:00
James R. Barlow 9baccee8c5 leptonica: Handle API change for pixFindPageForeground 2019-12-04 17:13:51 -08:00
James R. Barlow d5bb9929f3 leptonica: Use Windows name for DLL
Thanks to @dibu28
2019-12-04 17:13:51 -08:00
James R. Barlow 72d3ee3a87 Refactor symlink usage to support Windows 2019-12-04 17:13:51 -08:00
James R. Barlow 17c419dfcb compile_leptonica: move to correct location 2019-12-04 17:04:08 -08:00
James R. Barlow 84cc49b14b black: don't reformat _leptonica.py 2019-12-04 17:04:08 -08:00
James R. Barlow b7f63bc93d Make devnull check compatible with Windows 2019-12-04 17:04:08 -08:00
James R. Barlow ad9a3b5302 Update version of pdfminer.six supported 2019-12-04 17:04:08 -08:00
James R. Barlow 4e4bcaf243 Improve pre-commit checks 2019-11-25 14:38:23 -08:00
James R. Barlow 11afe3507f black: don't reformat _leptonica.py 2019-11-25 14:34:36 -08:00
James R. Barlow 7691ba8535 v9.1.1 release notes 2019-11-18 15:17:00 -08:00
James R. Barlow b787a369ee Fix reference to Alpine apk add 2019-11-18 15:13:42 -08:00
James R. Barlow 9fb8b267af docker: use get-pip to install pip
Smaller download, needed for manylinux2010.
2019-11-16 15:21:45 -08:00
James R. Barlow 0a08d6ce1f Update version of pdfminer.six supported 2019-11-13 01:45:06 -08:00
James R. Barlow f517efe819 docs: wsl - get-pip.py 2019-11-12 15:01:15 -08:00
James R. Barlow 5f5421f23d test: further fixes to test_report_file_size 2019-11-12 01:14:21 -08:00
James R. Barlow 703b6db95c test: fix test_report_file_size 2019-11-11 23:00:08 -08:00
James R. Barlow 000040d497 v9.1.0 release notes 2019-11-11 22:39:33 -08:00
James R. Barlow 5bd6665b49 Use pikepdf 1.7.0 to improve Python 3.8 support 2019-11-11 22:36:38 -08:00
James R. Barlow 1c303afe21 docs: fix installation instructions for pikepdf manylinux2010 wheels 2019-11-11 22:22:30 -08:00
James R. Barlow 11a5c80917 travis: enable Py 3.8 2019-11-11 22:19:15 -08:00
James R. Barlow 9b2ab92913 tesseract: fix exception when logger is RootLogger 2019-11-11 22:19:01 -08:00
James R. Barlow 0c4b69ec5a Fix lint warning about missing cur_item 2019-11-09 14:56:43 -08:00
James R. Barlow 45bea1c0e0 Import and docstring cleanup 2019-11-09 14:56:30 -08:00
James R. Barlow db914d4cd1 Report missing optional dependencies as possible cause of file size increase 2019-11-09 14:21:57 -08:00
James R. Barlow df4a8faecd docs: mention systemd for batches 2019-11-08 03:24:54 -08:00
James R. Barlow 1273e7aeda docs: document optimization 2019-11-08 03:22:28 -08:00
James R. Barlow e13a673b1a docs: mention how to suppress progbar 2019-11-08 02:59:02 -08:00
James R. Barlow 979b0bcaed tesseract: refactor logging 2019-11-05 15:38:09 -08:00
James R. Barlow 3438afaffe Support pdfminer.six 20191020 2019-11-04 03:15:59 -08:00
James R. Barlow 681fa039cc Update release notes; disable Py3.8 test again 2019-11-04 03:00:15 -08:00
James R. Barlow 69e80f1545 docker-compose.test does not seem to be ready for production use 2019-11-04 02:58:57 -08:00
James R. Barlow 983835cce4 docs: add remark about optimizing without OCR 2019-11-04 02:32:29 -08:00
James R. Barlow 6c23b137e2 Docker: relocate dockerfile 2019-11-04 02:27:30 -08:00
James R. Barlow d656b2b3f2 docs: remove comment about Ubuntu image
[ci skip]
2019-11-04 02:08:42 -08:00
James R. Barlow 031b800aac Docker autotest: fix, maybe? 2019-11-04 02:04:07 -08:00
James R. Barlow 05eb85ee77 Docker: try adding automated test 2019-11-04 01:23:54 -08:00
James R. Barlow 4da5214ca9 Drop support for unpaper 6.1 on Ubuntu 14.04 2019-11-04 00:09:04 -08:00
James R. Barlow 1ee829dd59 Travis: enable Python 3.8 testing 2019-11-04 00:05:18 -08:00
James R. Barlow 99db5d91ae Fix issue "MANIFEST.in exists" by removing MANIFEST.in
MANIFEST.in is always an issue
2019-11-04 00:03:49 -08:00
James R. Barlow 3a4490ee36 Dockerfile: fix jbig2 not copied over 2019-11-03 23:52:08 -08:00
James R. Barlow a492e3b472 Dockerfile: fix errors are trying to build unneeded cached wheels 2019-11-03 23:51:55 -08:00
James R. Barlow c3719d3b72 Dockerfile: remove venv from Ubuntu image; tweak reqs 2019-11-03 23:39:40 -08:00
James R. Barlow ad48fc6415 Remove Alpine Docker image 2019-11-03 22:35:15 -08:00
James R. Barlow 7f8018ffde Mention that v9.0.4 requires a source install for Py3.8 for now, due to lack of CI availability 2019-11-03 01:49:36 -08:00
James R. Barlow 80651fe12c Fix test suite error 2019-10-24 18:17:03 -07:00
James R. Barlow a58209e895 Disable Py3.8 for now 2019-10-24 18:16:47 -07:00
James R. Barlow 775b958c55 Update release notes 2019-10-24 16:58:39 -07:00
James R. Barlow cdcdd16865 Require Pillow 6.2.0 based on security vulnerability report in older versions 2019-10-23 12:27:29 -07:00
James R. Barlow b332d76782 Mention when we default to English and the system locale is not English
Closes #337
2019-10-22 01:49:38 -07:00
James R. Barlow 3660007fc8 travis: Python 3.8, osx_image 2019-10-20 04:06:13 -07:00
James R. Barlow b55d7e57af Python 3.8 updates 2019-10-20 03:20:54 -07:00
James R. Barlow 6e99e7b346 Use lstm_use_matrix for --user-words,patterns 2019-10-20 00:49:11 -07:00
James R. Barlow 4d26867dee Delinting 2019-09-20 17:17:11 -07:00
James R. Barlow 78e8bf9cbf Use at most 3 Tesseract threads
Based on a user suggestion and
tesseract-ocr/tesseract#2611, I reviewed thread limits and found that
thread limit of 3 is still beneficial, but not 4.

> time env OMP_THREAD_LIMIT=2 tesseract omp4.png stdout >/dev/null
Warning: Invalid resolution 0 dpi. Using 70 instead.
Estimating resolution as 143
116.67user 1.67system 1:26.26elapsed 137%CPU (0avgtext+0avgdata 356752maxresident)k
2213inputs+0outputs (18major+131059minor)pagefaults 0swaps
> time env OMP_THREAD_LIMIT=3 tesseract omp4.png stdout >/dev/null
Warning: Invalid resolution 0 dpi. Using 70 instead.
Estimating resolution as 143
136.89user 1.63system 1:19.56elapsed 174%CPU (0avgtext+0avgdata 356784maxresident)k
821inputs+0outputs (0major+131080minor)pagefaults 0swaps
> time env OMP_THREAD_LIMIT=4 tesseract omp4.png stdout >/dev/null
Warning: Invalid resolution 0 dpi. Using 70 instead.
Estimating resolution as 143
161.31user 1.51system 1:18.80elapsed 206%CPU (0avgtext+0avgdata 356632maxresident)k
8477inputs+0outputs (12major+131074minor)pagefaults 0swaps
> time env OMP_THREAD_LIMIT=8 tesseract omp4.png stdout >/dev/null
Warning: Invalid resolution 0 dpi. Using 70 instead.
Estimating resolution as 143
160.30user 1.62system 1:18.01elapsed 207%CPU (0avgtext+0avgdata 356640maxresident)k
821inputs+0outputs (0major+131078minor)pagefaults 0swaps
2019-09-20 17:12:36 -07:00
James R. Barlow de61530d4d docs: fix intermediate file list for v9 2019-09-20 17:02:35 -07:00
James R. Barlow c149f860b5 Add contributing guide 2019-09-20 17:02:22 -07:00
James R. Barlow 68c852acec Remove test_tesseract_config_invalid from suite
Also causes problems in CI
2019-09-18 13:28:02 -07:00
James R. Barlow a8565bac6e Fix any False in the ocrmypdf.ocr() API being set to True 2019-09-15 01:47:31 -07:00
James R. Barlow 6e8b0c3194 Fix py36 test including 37 2019-09-15 01:47:10 -07:00
James R. Barlow ff860e8362 Fix black settings in pyproject.toml 2019-09-15 01:46:13 -07:00
James R. Barlow cf4b04c5d1 optimize: work around pikepdf 1.6.3 limitation with indexed ICCbased colorspaces 2019-09-11 12:56:27 -07:00
James R. Barlow 078bc2abe9 pdfa: assume 3 RGB channels always 2019-09-11 12:55:38 -07:00
James R. Barlow d7b7ca0574 v9.0.3 notes; Remove test_tesseract_config_notfound from suite 2019-09-05 13:39:43 -07:00
James R. Barlow 17ac9d7a9a Embed ICC profile in .ps (fixing Ghostscript 9.28 compatibility)
Previously we included the
   filename, which required Postscript to run with file access enabled. For
   security, Ghostscript 9.28 enables ``-dSAFER`` and as such, no longer
   permits access to any file by default. This fix is necessary for
   compatibility with Ghostscript 9.28.

We use ASCII85 for a slightly more compact representation.
2019-09-05 13:17:26 -07:00
James R. Barlow a2a197ce4c v9.0.2 release notes 2019-09-04 02:34:21 -07:00
James R. Barlow 944d59e5ad Fix --print-parameters issue when chi_sim is not installed 2019-09-04 01:17:52 -07:00
James R. Barlow 1c3e90a892 optimize: solve monochrome by converting to G4 2019-09-04 00:51:47 -07:00
James R. Barlow c728836956 Adjust test requirements 2019-09-04 00:50:48 -07:00
James R. Barlow 0d80fab339 Remove restriction on pytest < 5 2019-09-03 23:47:55 -07:00
James R. Barlow a650caa599 optimize: don't consider 1bpp images for PNG optimization 2019-09-03 23:47:20 -07:00
James R. Barlow c6caff90a1 optimize: only re-insert pngs after pngquant
Previously we attempted to reinsert all PNGs, but it appears to be
unlikely that Leptonica's API is actually capable of optimizing the PNG
before it inserts it.

In any event qpdf has gained image optimization capabilities as well
which we coudld borrow.
2019-09-03 23:46:25 -07:00
James R. Barlow 671c88d3b5 optimize: exclude images with custom Decode tables 2019-09-03 23:37:23 -07:00
James R. Barlow b2cfaedf91 optimize: Don't reinsert 1bpp images
There seems to be version to version inconsistencies between
Leptonica's photometric interpretation of 1bpp images, in
particular commit a0692307 introduces a change to force transcoding
in this situation.

However, I never entirely got to the bottom of where the problem
is, and in any event 1bpp images are probably better optimized
by JBIG2 than pngquant, so we're going to stop running them through
pngquant.
2019-09-03 23:26:13 -07:00
James R. Barlow 19ba3ae011 Allow test_german to xfail if deu language is not installed 2019-09-03 17:38:54 -07:00
James R. Barlow feff1e38bb Use context managers to ensure Pillow images are closed 2019-09-03 17:19:12 -07:00
James R. Barlow c8d6ea6b10 Fix tests broken by --print-parameters change 2019-09-03 17:17:24 -07:00
James R. Barlow b0d9775343 Attempt to resolve black-inversion issue 2019-08-31 01:25:36 -07:00
James R. Barlow 462bfb84fb install: affirm that we now require Tesseract beta 2019-08-31 01:24:31 -07:00
James R. Barlow 11ef78a891 Fix running without eng.traineddata installed raises exception 2019-08-27 14:54:03 -07:00
James R. Barlow 638eb556ef Reactivate user-words test that was always skipped 2019-08-27 14:52:59 -07:00
James R. Barlow fdefcd8af2 travis: Make 3.7 the build leader/deployer 2019-08-26 13:30:07 -07:00
James R. Barlow 09457edad3 alpine: use jbig2enc@community 2019-08-26 12:49:47 -07:00
James R. Barlow 6460a7eb3e docs: leptonica.com -> .org 2019-08-26 12:07:34 -07:00
James R. Barlow 707ebeb151 docs: installation updates 2019-08-11 18:48:56 -07:00
James R. Barlow e9bc093842 v9.0.1 release notes 2019-08-11 17:14:11 -07:00
James R. Barlow 2eeaca1168 travis: make minimal config even more minimal 2019-08-11 17:13:55 -07:00
James R. Barlow 7755c5c5a7 tests: fix interpretation of None as omitted argument 2019-08-11 16:58:22 -07:00
James R. Barlow 793348a47c tests: mark test as requiring pngquant 2019-08-11 16:58:22 -07:00
James R. Barlow b241f66919 travis: Add a minimal Ubuntu config 2019-08-11 16:58:06 -07:00
James R. Barlow 8ad034a678 docs: update install on FreeBSD to point to ports 2019-08-11 15:50:52 -07:00
James R. Barlow a1a7b973e9 tests: split out stdin/stdout tests 2019-08-09 01:23:49 -07:00
James R. Barlow 7bfcd0a9d5 Use pikepdf 1.6.1 2019-08-09 01:12:13 -07:00
James R. Barlow f276c4ef1e Alpine Docker: jbig2enc moved from testing to community 2019-08-09 01:09:18 -07:00
James R. Barlow 77bbc22c50 Ensure --image-dpi on non-image produces a warning 2019-08-09 01:08:16 -07:00
James R. Barlow a6805ed343 Travis: remove vestiges of pdfminer being optional on osx 2019-07-30 00:42:38 -07:00
James R. Barlow c4afc5c242 Add missing item from v9.0.0 release notes 2019-07-30 00:39:14 -07:00
James R. Barlow df32008667 Ensure test_optimize passes
Linearization sends it over the edge
2019-07-27 16:47:53 -07:00
James R. Barlow db4598f76a Add PDF linearization 2019-07-27 16:15:48 -07:00
James R. Barlow ce13431ecf Remove experimental filters.py 2019-07-27 15:47:09 -07:00
James R. Barlow eb104b405d Avoid circular imports for __version__ 2019-07-27 05:02:19 -07:00
James R. Barlow 5f00e4f9d8 Sort imports 2019-07-27 04:51:52 -07:00
James R. Barlow 1a91cd4652 pikepdf 1.6 2019-07-27 04:36:48 -07:00
James R. Barlow b0f1a55537 completions: --pages 2019-07-27 04:26:38 -07:00
James R. Barlow 0c066d1d53 Expand scope of --pages testing 2019-07-27 04:26:23 -07:00
James R. Barlow e4cfcec5f3 docs: some cleanup 2019-07-27 04:04:33 -07:00
James R. Barlow 85c90404d7 Update release notes 2019-07-27 03:23:56 -07:00
James R. Barlow 4d011c28ea Improve completions 2019-07-27 03:02:04 -07:00
James R. Barlow 5304c631ec Don't warn about --user-words in Tesseract 4.1 or later 2019-07-27 02:14:59 -07:00
James R. Barlow 6fbeb6347d Merge api (without plugins) 2019-07-27 02:04:01 -07:00
James R. Barlow f83de20c37 Remove plugins (for now)
It's holding up too many other useful,
releaseable changes.
2019-07-27 01:41:14 -07:00
James R. Barlow 423324af01 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-07-15 12:08:59 -07:00
James R. Barlow 016a2a01d9 docs: Notes on WSL 2019-07-13 02:06:11 -07:00
James R. Barlow 6189910c74 Fix text-image registration when mediabox contains an offset
Cropbox, trimbox not addressed... should look at those. Also rotation.
2019-07-11 02:20:04 -07:00
James R. Barlow 7117dc10de Suppress noisy empty debug messages 2019-07-11 01:23:01 -07:00
jbarlow83andGitHub b601cb0cba Fix funding.yml 2019-07-11 00:36:09 -07:00
jbarlow83andGitHub 0c781faf89 Create funding.yml
[ci skip]
2019-07-11 00:35:39 -07:00
James R. Barlow 12769b96e5 Drop support for omitting pdfminer.six 2019-07-10 13:37:01 -07:00
James R. Barlow a7b4ed9688 Double vertical bars for logo 2019-07-08 22:20:23 -07:00
James R. Barlow ee92ce8717 gitattributes: ensure afdesign is okay 2019-07-08 22:16:47 -07:00
James R. Barlow cbeddab35f rename ocrmypdf.run -> ocrmypdf.ocr 2019-07-07 02:11:44 -07:00
James R. Barlow 98050534d6 logo: once more 2019-07-07 01:19:23 -07:00
James R. Barlow 9850231e91 (Hopefully) fix logo text invisible on Github 2019-07-07 01:12:52 -07:00
James R. Barlow fd810239b5 Add a logo 2019-07-07 01:07:48 -07:00
James R. Barlow 2cff6ad2d1 Fixed blank pages produced when NULs removed from metadata 2019-07-03 02:22:50 -07:00
James R. Barlow 3ee306184b Don't overwrite input PDF when fixing NULs in metadata 2019-07-03 01:57:58 -07:00
James R. Barlow 1cc4c45b7e docs: mention WSL works
[ci skip]
2019-07-03 00:49:06 -07:00
James R. Barlow a86cb8148a Fix jbig2 not checked for special colorspaces 2019-07-02 13:50:29 -07:00
James R. Barlow eeae6f8292 test: Add syntax checks for shell completions 2019-07-02 13:49:17 -07:00
James R. Barlow 4dab299619 Fix parameterization of --verbose 2019-07-02 13:27:07 -07:00
James R. Barlow 340e2bbac6 Drop --mask-barcodes from completions 2019-07-02 13:10:05 -07:00
James R. Barlow 187283192b Fix reporting output file size skipped
Due to change to using finally for clean up
2019-06-30 15:08:10 -07:00
James R. Barlow f855bdd36b Docker: Ubuntu image should be manylinux1 compatible 2019-06-24 01:32:42 -07:00
James R. Barlow 9873d51f58 release notes: add next 2019-06-23 16:54:53 -07:00
James R. Barlow 11a57c7a17 Drop --mask-barcodes feature 2019-06-23 16:54:43 -07:00
James R. Barlow 8aa678859d Use pandoc to rewrite .rst files
Fixes all of the long lines, mainly.
2019-06-22 17:29:26 -07:00
James R. Barlow 1beb7dfd37 helpers: don't expect psutil will be installed
It's not in stdlib
2019-06-22 02:36:06 -07:00
James R. Barlow 9b60d3e285 Improve testing of _validation.py 2019-06-22 02:33:04 -07:00
James R. Barlow 3331a686fa Fix tess_threads clamped to 1 2019-06-22 00:59:33 -07:00
James R. Barlow c32ea3b374 If a page have vector content, promote to full color 2019-06-22 00:59:04 -07:00
James R. Barlow c357d4146e Restructure ocrmypdf.pdfinfo 2019-06-20 03:10:41 -07:00
James R. Barlow f47cb2fade docs: update ocrmypdf.ocrmypdf to .run 2019-06-20 02:45:14 -07:00
James R. Barlow 9c4b1aeb8d docs: plugin; renaming 2019-06-20 02:44:54 -07:00
James R. Barlow 51ed381bfc Rename weave -> graft 2019-06-13 01:16:56 -07:00
James R. Barlow 5ee45411c9 Decide on OMP_THREAD_LIMIT more intelligently 2019-06-13 01:02:07 -07:00
James R. Barlow 16990890d8 Remove "from ocrmypdf import ocrmypdf"
Messes up future imports from ocrmypdf, so don't do it.
2019-06-12 17:52:25 -07:00
James R. Barlow cfb11559d5 logging: capture warnings too 2019-06-12 17:28:02 -07:00
James R. Barlow 8b8de7cc1d Add new --pages feature to limit OCR to only specific pages 2019-06-12 17:27:47 -07:00
James R. Barlow aba293fd80 Change "Temporary working files" output message 2019-06-12 13:56:02 -07:00
James R. Barlow 066a293462 If verbose, print stacktrace on KeyboardInterrupt 2019-06-12 13:55:43 -07:00
James R. Barlow 0bbd6885e2 Make the go/no-go decision pluggable 2019-06-06 23:07:46 -07:00
James R. Barlow 5dd10c961c Docker: prefer streaming 2019-06-05 03:14:36 -07:00
James R. Barlow 81fc95556c Add progress bar for PdfInfo step 2019-06-05 03:08:04 -07:00
James R. Barlow 20ad032977 Fix some error messages that printed directly to sys.stderr instead of logging 2019-06-05 03:07:48 -07:00
James R. Barlow 93f1b73579 Fix --remove-vectors which was broken in API migration
It got dropped during the change. This feature has also been altered so that
the final visual appearance of the file is not affected, only the OCR image.
2019-06-05 02:04:45 -07:00
James R. Barlow fd427a8ec1 plugins: replace path manipulation 2019-06-05 01:46:56 -07:00
James R. Barlow 9444cf357b optimize: add divide by zero check 2019-06-04 02:01:53 -07:00
James R. Barlow 5ab69153ee Fix .coveragerc 2019-06-03 02:26:49 -07:00
James R. Barlow eb5200d26a Change most tests to use ocrmypdf API instead of subprocess
The main benefit of this is code coverage gains can actually follow it.
Also removes most ugly os.environ hacks.
2019-06-03 01:45:27 -07:00
James R. Barlow 98a3fda1f5 Drop support for Tesseract 4 alpha releases without textonly_pdf (mostly)
hocr renderer can still be used
2019-06-03 01:39:41 -07:00
James R. Barlow e73740ae9d test: remove test code that support tess3 or tess4 testing 2019-06-03 01:33:24 -07:00
James R. Barlow fb933edc0f Use newer pytest tmp_path API 2019-06-01 01:55:51 -07:00
James R. Barlow ba41ccae1b conftest: don't modify PYTEST_CURRENT_TEST when manipulating os.environ
It confuses pytest.
2019-06-01 01:41:39 -07:00
James R. Barlow df9e286e9c Make bypassed exception clearer 2019-06-01 01:35:15 -07:00
James R. Barlow b9d6e46572 shutil.rmtree: use builtin error suppression 2019-05-31 15:12:46 -07:00
James R. Barlow 8347c0d662 validation: remove dead code check_input_file 2019-05-31 01:57:08 -07:00
James R. Barlow 45a361d112 Add option to use threads instead of processes
Mainly since they are more convenient for debugging
2019-05-31 01:56:16 -07:00
James R. Barlow 522e1e948b ghostscript: don't use threads= for generate_pdfa
Not supported for pdfwrite
2019-05-31 01:55:29 -07:00
James R. Barlow 8ed4e229f3 ghostscript: avoid log=None construct 2019-05-30 13:57:38 -07:00
James R. Barlow db29cae177 Docker docs: Remove legacy images, revive Ubuntu 2019-05-28 21:36:45 -07:00
James R. Barlow d5b6cbb95e Update Ubuntu dockerfile 2019-05-28 15:36:50 -07:00
James R. Barlow 396c39978a Reorganize .docker folder so we don't have to rebuild as much 2019-05-28 14:18:54 -07:00
James R. Barlow 9d5f23e961 Rename filters to plugins 2019-05-28 02:39:25 -07:00
James R. Barlow 26a6232e1c Ignore DSStore 2019-05-28 02:33:35 -07:00
James R. Barlow 7566d4b768 Introduce plugins/filters 2019-05-27 16:55:04 -07:00
James R. Barlow 5c4c32ab3c Remove multiprocessing tests - no longer valid 2019-05-27 12:07:20 -07:00
James R. Barlow 692f7b3151 Dockerfile: with newer pip
Newer pip seems to install ocrmypdf-*.dist-info and has no problem reporting
installed version unlike -egg-info, so
skip copying.

Also move WORKDIR
2019-05-26 04:31:53 -07:00
James R. Barlow 8d0958d7ea Dockerfile: qpdf-dev needs to be requested explicitly 2019-05-26 04:30:34 -07:00
James R. Barlow e9731b6bac Docker: upgrade pip, temporarily enable community repository for qpdf 2019-05-26 04:00:24 -07:00
James R. Barlow 0628a89041 docs: mention how to use Docker image shell 2019-05-26 00:20:40 -07:00
James R. Barlow c14f62752b Tests: add an API test 2019-05-25 16:24:09 -07:00
James R. Barlow 24855045e1 Provisionally add filters 2019-05-25 16:23:39 -07:00
James R. Barlow ed236e0c27 Begin API documentation 2019-05-24 01:05:32 -07:00
James R. Barlow db6aa22eae Progress bar: unit types 2019-05-23 02:00:47 -07:00
James R. Barlow 805aa776ad Re-disable progress bar when not connected to tty 2019-05-23 02:00:35 -07:00
James R. Barlow d0efdf643c Cleanup working files when done with a particular file, rather than end of process 2019-05-23 01:25:08 -07:00
James R. Barlow 22298b31be Fix distinction between clean and clean_final lost in API refactor 2019-05-23 01:19:58 -07:00
James R. Barlow 5cecb3ecb4 Convert one test to use API 2019-05-22 23:53:48 -07:00
James R. Barlow a139e64c67 api: short-circuit exception handler, as caller should provide their own 2019-05-22 18:30:30 -07:00
James R. Barlow db69b4d11a Improve argparse behavior for its role in making the API work 2019-05-22 15:55:48 -07:00
James R. Barlow 8bcb85720c release notes: clarify 2019-05-22 15:34:23 -07:00
James R. Barlow 09ca1bee97 Add progress bar to optimize and add option to disable it 2019-05-22 15:31:48 -07:00
James R. Barlow 23dd77ce0f api: fix progress_bar_friendly=False 2019-05-22 15:31:03 -07:00
James R. Barlow 32a076c039 Refactor validation and exceptions
CLI now tracks check_options exceptions. API now works more like
an API, without an exception handler,
because the caller should provide one.
2019-05-20 18:01:17 -07:00
James R. Barlow e4baa8c0dd Remove sys.exit() calls so we don't terminate caller application 2019-05-20 15:08:20 -07:00
James R. Barlow 2fdaa76a0d Refactor configure_logging 2019-05-20 14:54:34 -07:00
James R. Barlow 7ee0c52a57 Refactor cli into basic high level api 2019-05-19 22:34:45 -07:00
James R. Barlow ac2fc9c2a0 Explain picklable logger 2019-05-19 15:06:47 -07:00
James R. Barlow 188e08e98b docs: Remove discussion of ruffus 2019-05-17 22:28:28 -07:00
James R. Barlow ef1ef1cdf0 Fix test invalidated by Python 3.6 logging fixes 2019-05-17 15:20:07 -07:00
James R. Barlow 24da92d39e Fix extra blank lines in output messages in Python 3.6 2019-05-17 14:56:33 -07:00
James R. Barlow 61afef549e Remove some now-unused code; etc 2019-05-17 14:25:17 -07:00
James R. Barlow cfd67ab6aa Fixing threading._RLock exception on Python 3.6
Issue was the usual business: objects that cross process boundaries need
to be picklable and Python 3.6 is more strict about this. The logger
object in particular interfered, so now we suppress it and rebuild it
in process.
2019-05-17 14:24:47 -07:00
James R. Barlow 56067b590b Make re_symlink() not require a log object 2019-05-17 01:59:36 -07:00
James R. Barlow 4340ad9f12 Update test cache 2019-05-17 01:45:06 -07:00
James R. Barlow 70def4a0d0 validation: eliminate print() 2019-05-17 01:43:26 -07:00
James R. Barlow 8df1ea2754 Mark some slow tests 2019-05-17 01:42:27 -07:00
James R. Barlow e528adc603 pylint removal 2019-05-17 01:09:06 -07:00
James R. Barlow c1af0fb18d Cleanup ghostscript error output 2019-05-17 00:50:29 -07:00
James R. Barlow 0cb4e854e5 Replace ProcessPoolExecutor with multiprocessing.Pool
There seems to be no reasonable way to handle Ctrl-C with a
ProcessPoolExecutor. Or at least you have to press it several times to
actually kill. Pool does the job.
2019-05-17 00:48:06 -07:00
James R. Barlow 13ab23ba54 Refactor weave_layers, introduce progress bar
Fixes a bug in this branch where --sidecar would fail by trying to iterator
the executor futures twice.
2019-05-16 14:57:31 -07:00
James R. Barlow 19263f00c6 Additional logging fixes; silence extremely verbose pdfminer logging 2019-05-16 13:44:44 -07:00
James R. Barlow 50bd129d7a logging: don't pass log object to validation 2019-05-16 01:58:48 -07:00
James R. Barlow 471cdea232 Move app specific settings a library may not want to __main__ 2019-05-16 01:29:26 -07:00
James R. Barlow 9d750828c7 Make logging format consistent with v8.3.0 2019-05-16 00:48:40 -07:00
James R. Barlow 5e025c3382 Reinstate log level in messages to be closer to old behavior 2019-05-15 15:46:36 -07:00
James R. Barlow 4410503349 More fixes to logging and disabled tests 2019-05-15 03:08:09 -07:00
James R. Barlow 486f73d5d6 Remove custom logger 2019-05-15 02:28:13 -07:00
James R. Barlow b10285d11b Fix warnings 2019-05-14 16:34:42 -07:00
James R. Barlow c904b430b6 Merge master into api branch; all test pass 2019-05-14 16:33:02 -07:00
James R. Barlow 07d4fff3d4 docs: mention FreeBSD works 2019-05-14 02:13:56 -07:00
James R. Barlow 7e388f59af Move completions to better location/Homebrew compat 2019-05-13 00:22:52 -07:00
James R. Barlow 84ec83151d v8.3.0 notes: clarify 2019-05-12 03:36:41 -07:00
James R. Barlow 0a72c12ff0 weave: add new test for link consistency 2019-05-12 03:36:33 -07:00
James R. Barlow 5adbddd1f9 ghostscript: rendering threads has no effect on pdfwrite, so remove it
See
https://www.ghostscript.com/doc/current/Language.htm
2019-05-12 03:36:07 -07:00
James R. Barlow c2fecffdb4 Require pikepdf 1.3.0 2019-05-12 02:16:05 -07:00
James R. Barlow 7dea0a8362 v8.3.0 release notes in progress 2019-05-11 12:50:44 -07:00
James R. Barlow 482cb788ed Don't use MagicMock() as a dummy logger in pytest 2019-05-11 12:44:17 -07:00
James R. Barlow 15a988b999 weave: use emplacement method, scrap TOC repair
The new emplacement method updates page objects in place without
generating new objgen numbers, meaning we no longer need to update the table
of contents to preserve links.
2019-05-11 12:40:25 -07:00
James R. Barlow 83398e54ea weave: fix corruption of certain high page count files
Corruption occurred when replacements was not incremented for multiple
consecutive pages.
2019-05-11 12:22:21 -07:00
James R. Barlow bcdd196699 ghostscript: remove unnecessary post-render resizing step 2019-05-11 12:10:50 -07:00
James R. Barlow 0cd576e701 Rename bash completions file 2019-05-11 10:52:42 -07:00
James R. Barlow 4d5e0eb749 docs: mention completions 2019-05-06 18:07:41 -07:00
Frankandjbarlow83 7ed0f8f50e Add bash completion (#384)
* Add bash completion

file must be copied to completion folder e.g. /usr/share/bash-completion/completions
2019-05-06 15:18:15 -07:00
James R. Barlow 79c84eefa3 Fix main.txt 2019-04-23 02:21:31 -07:00
James R. Barlow 5398003160 Fix test.txt 2019-04-23 00:42:40 -07:00
James R. Barlow 58b2bed99d v8.2.4 notes 2019-04-23 00:07:12 -07:00
James R. Barlow 58c29ffb5c weave: use explicit pdf.close(), drastically reduce open file handles
With the new pikepdf 1.2.0 we no longer need to hold file handles
open because of the "copy to memory" functionality. We retain
the behavior of closing/reopening the output PDF every 100 pages as
a way to limit memory usage.
2019-04-18 15:12:48 -07:00
James R. Barlow f615b6f0e8 pdfinfo: be more specific about detecting XFA we can't render 2019-04-18 15:07:25 -07:00
James R. Barlow e0c8dadcce Explicitly close most pikepdf.Pdf when done with them 2019-04-18 15:02:12 -07:00
James R. Barlow 9a86f53109 Ignore pip-wheel-metadata folder
https://github.com/pypa/pip/issues/6213
2019-04-18 10:42:10 -07:00
James R. Barlow 91cb092aa0 Remove PyCharm debugger hack 2019-04-18 10:15:02 -07:00
James R. Barlow f4b87915df Fix --redo-ocr 2019-04-15 13:11:26 -07:00
James R. Barlow 922a107b7f Remove safety traversal of PDF table of contents
qpdf fixed the danging reference issue (qpdf #240) in 8.3.0, which is
required by pikepdf 1.1.0. We no
longer need the workaround.
2019-04-13 00:24:03 -07:00
mawi 1c44fd4f3b fix: typo 2019-04-08 15:01:04 +02:00
mawi c92ccc6134 fix: tests 2019-04-08 14:57:42 +02:00
mawi 1137534e97 fix: update pytest version
Solves install error: pkg_resources.ContextualVersionConflict: (pytest 4.3.0 (/app/.eggs/pytest-4.3.0-py3.6.egg), Requirement.parse('pytest>=4.4.0'), {'pytest-xdist'})
2019-04-08 11:08:29 +02:00
mawi 39617dd739 fix: remove ruffus 2019-04-08 11:07:32 +02:00
mawi 6590875756 feat: add triage step
remove tqdm demo
2019-04-08 10:26:56 +02:00
mawi 01bbf064e0 feat: add tqdm progress bar
This is just a POC. Will be removed.
2019-04-05 19:52:38 +02:00
mawi fc1c4f12f5 feat: add concurrent.futures pipeline 2019-04-05 18:48:34 +02:00
mawi 2647382cf6 fix: most of the tests (37 failed, 133 passed, 28 skipped) 2019-04-05 14:06:07 +02:00
mawi 783a128bd1 feat: move to sync (none ETL) implementation - remove ruffus 2019-04-04 21:02:38 +02:00
Martin Wind b214aa5b38 feat: move to sync (none ETL) implementation 2019-04-03 19:59:43 +02:00
James R. Barlow 6e49bb3588 v8.2.3 notes 2019-04-03 01:19:12 -07:00
Martin Wind aa512b6181 feat: move to sync (none ETL) implementation (WIP) 2019-04-02 20:03:09 +02:00
Martin Wind a4667b5656 refactor: move ruffus related code to one file 2019-03-28 20:16:10 +01:00
Martin Wind f65a3d3762 fix import in unpaper test 2019-03-26 10:04:26 +01:00
Martin Wind 2fa43ecf09 refactor: split argparse and run_pipline 2019-03-26 08:10:20 +01:00
James R. Barlow 427afc0616 Fix LeptonicaErrorTrap when a sys.stderr.fileno() is not available
The LeptonicaErrorTrap was problematic for Celery and other
libraries that mess with stderr.

Closes #359
2019-03-17 14:22:36 -07:00
James R. Barlow 9c7ee2bf23 Better help text for --verbose 2019-03-17 13:29:25 -07:00
James R. Barlow c5cfaa950b readme: tweaks 2019-03-16 14:09:19 -07:00
James R. Barlow 4e2a98ead4 leptonica: fix junkpixt harder 2019-03-16 14:08:58 -07:00
James R. Barlow 210f134b5b Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-03-08 15:38:31 -08:00
James R. Barlow 696c0721a0 docs: fix broken sphinx ref
[ci skip]
2019-03-08 15:38:26 -08:00
James R. Barlow aabab95418 docs: use images folder 2019-03-08 15:38:01 -08:00
James R. Barlow 7d614dd68b docs: explain Automator workflow 2019-03-08 15:37:42 -08:00
jumbliesandjbarlow83 f57dda7939 Update batch.rst (#362)
Added docker instructions for passing "find" filenames into container.  Obviates prior incorrect flag fix.
2019-03-08 12:46:50 -08:00
James R. Barlow 1b4542aa77 Further fixes to external program version testing 2019-03-07 14:27:16 -08:00
James R. Barlow 6c7fca57ec v8.2.1 notes 2019-03-06 22:22:50 -08:00
James R. Barlow 486dc7e22c Fix some test failures missed in prev commit 2019-03-06 13:28:50 -08:00
James R. Barlow dc616bb507 Fix test suite so --clean is not requested when unpaper is not installed 2019-03-05 22:33:13 -08:00
James R. Barlow 902bda43e3 main: fix version testing unnecessarily throwing exception to itself 2019-03-05 22:32:06 -08:00
James R. Barlow f7da63f68b main: fix redundant argument test 2019-03-05 22:29:29 -08:00
James R. Barlow 5da26e4c9c Convert most uses of subprocess.Popen to subprocess.run in test suite 2019-03-05 22:25:22 -08:00
James R. Barlow c19c852705 Fix exception while attempting to print error message for missing program 2019-03-05 16:32:48 -08:00
James R. Barlow a27ee3ee8c optimize: use Decode to invert 1bpp PNGs for now 2019-03-03 17:50:12 -08:00
James R. Barlow c2f316c2c5 v8.2.0 release notes: optimizer 2019-03-03 15:26:01 -08:00
James R. Barlow 974979b0a0 Merge branch 'feature/optimization-fixes' 2019-03-03 15:00:20 -08:00
James R. Barlow 66586bdaab optimize: Disable jpg->png migration
Needs more testing before release
2019-03-03 14:59:59 -08:00
James R. Barlow 01d2ea309f Fix Predictor name and photometric flip 2019-03-03 14:57:15 -08:00
James R. Barlow e918480351 v8.2.0 release notes 2019-03-03 14:15:20 -08:00
James R. Barlow 52fd84fa95 Remove debug message 2019-03-03 13:31:10 -08:00
James R. Barlow 2c56b0935c docs: minor 2019-03-03 03:28:17 -08:00
James R. Barlow 4f69ace868 optimize: fix all JBIG2 images binned on last page
During some past refactor it appears we now end up treating
all JBIG2 images as if they appeared on the last page in the
file. This bug had no visual side ffects but probably led to
suboptimal JBIG2 encoding.
2019-03-03 03:28:17 -08:00
James R. Barlow 497c531112 optimize: update comments 2019-03-03 03:28:17 -08:00
James R. Barlow b27b92fbf3 optimize: on aggressive settings try JPG to PNG transcoding
If the color count of an image is low such as when black and white
documents are scanned in color, PNG with lossy quantization may
produce a superior encoding to JPEG. This is expensive to test however.
2019-03-03 03:28:17 -08:00
James R. Barlow 2e6ba2df8c optimize: fix recoding of PNGs
Previously we opened pngquant-compressed PNGs with transcoding
because the transcode free function in Leptonica didn't seem to
work. This mean Leptonica may have thrown away the hard of
pngquant if didn't understand the encoding.

This change resolves the issue and allows us to open PNG encoded
data and insert it into a PDF without transcoding. Should improve
encoding quality.
2019-03-03 03:28:17 -08:00
James R. Barlow 67a405c6b7 Move install-time external program checks out of setup.py
We did runtime tests for several of them anyway, and it's better to do
at runtime since config may change after installation.
2019-03-03 03:26:56 -08:00
James R. Barlow 58e6663806 Update test cache for french->german change 2019-03-03 03:23:59 -08:00
James R. Barlow 602570fcf9 Update requirements 2019-03-03 02:27:56 -08:00
James R. Barlow 691f8ce254 Docs: reorganize for new docker-alpine image 2019-03-01 23:15:32 -08:00
James R. Barlow 22812e74b9 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-02-26 13:01:59 -08:00
Martin Windandjbarlow83 9d824e723d Add Dockerfile based on alpine:3.9 (#354)
* Do not exclude .git from docker build

* Use multi-stage builds to keep the image size down

* Copy project files to get the test suite.

* Add webservice

* Add tesseract language data for German and Chinese Simplified
2019-02-26 13:01:38 -08:00
James R. Barlow 5dad800d85 Add version to build-system declaration 2019-02-26 12:58:44 -08:00
James R. Barlow 56a56a4dcb docs: avoid importing ocrmypdf 2019-02-26 12:57:50 -08:00
James R. Barlow 3f1d9ef99c Fix tests for move to Alpine dockerfile 2019-02-26 12:30:21 -08:00
James R. Barlow 92c8a5885e Declare build system in pyproject.toml 2019-02-26 12:23:33 -08:00
James R. Barlow 7749d14252 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-02-24 01:56:47 -08:00
Julien Maandjbarlow83 9b92af5aed README: install other language packs on macOS (#352)
The default homebrew formula installs only the English language pack.
Another brew formula exists to install all other language packs.
This makes it easier than having to do the whole install manually.
2019-02-19 10:13:36 -08:00
James R. Barlow 0bf26b03ae optimize: Modernize pikepdf usage 2019-02-16 14:03:10 -08:00
James R. Barlow e2847ea4c3 v8.1.0 release notes 2019-02-10 02:10:48 -08:00
James R. Barlow 19e35db2b7 Fix issue when weave handoff occurs with no OCR font present
If using --tesseract-timeout 0 and any image processing on a file with
more than 100 pages, the weave handoff will occur. Ensure this
works correctly even if no Glyphless font is present.

Closes #347
2019-02-10 02:05:59 -08:00
James R. Barlow df688742d5 Fix exception on traversing corrupt ToC entries 2019-02-10 00:50:21 -08:00
James R. Barlow 42c2925f9d Activate black precommit 2019-02-08 14:09:08 -08:00
James R. Barlow 933f0b8f9b docs: more unpaper details 2019-02-08 13:05:09 -08:00
James R. Barlow 03ab5a8ee2 If --tesseract-timeout 0, say nothing when we time out
This is our "don't actually OCR" mode. No need to mention it.
2019-02-08 13:04:48 -08:00
James R. Barlow 4f06920224 Be os.nice()-r 2019-02-07 17:24:47 -08:00
James R. Barlow a733b09623 webservice: add an optional config and larger upload limit 2019-02-07 17:24:17 -08:00
James R. Barlow 5483dacf52 Fuzz 2019-02-07 17:09:47 -08:00
James R. Barlow ae7844ad88 --clean-final implies --clean
It's never made sense to leave it out before; might as well introduce it.
2019-02-07 17:08:08 -08:00
James R. Barlow a6e7485da6 docs: --unpaper-args 2019-02-07 17:06:51 -08:00
James R. Barlow 3bcc6d6121 Merge 'feature/unpaper-args' 2019-02-07 17:06:28 -08:00
James R. Barlow 9fe067bbd9 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2019-02-07 16:31:06 -08:00
James R. Barlow f095e91cb4 unpaper-args: add test case and harden feature 2019-02-07 16:21:02 -08:00
Charles Forceyandjbarlow83 66c8d4b47a Adjust the docker pull command for webservice (#346)
Not completely sure this is correct, but I think `docker pull jbarlow83/ocrmypdf-webservice` might be the correct command for getting the web service version.  It installs as expected:

```
docker pull jbarlow83/ocrmypdf-webservice
Using default tag: latest
latest: Pulling from jbarlow83/ocrmypdf-webservice
38e2e6cd5626: Already exists 
705054bc3f5b: Already exists 
c7051e069564: Already exists 
7308e914506c: Already exists 
3977c3cd82d1: Already exists 
ec01b9573956: Already exists 
b508b5192a3c: Already exists 
ace6e737fffb: Already exists 
0a453ee84e11: Already exists 
f8cb8b66151b: Already exists 
f53c3b27b23f: Already exists 
22df51ea5473: Already exists 
e38d932f9f30: Already exists 
b9d3c1d5b53b: Already exists 
68be2088ada3: Already exists 
8b17945ab41b: Pull complete 
59c4aae491bd: Pull complete 
19dce698a07e: Pull complete 
Digest: sha256:0cc9433d490c9a65389403757bf6081a30bcd248055340a8789c23d9cdf9ac8a
Status: Downloaded newer image for jbarlow83/ocrmypdf-webservice:latest
```
2019-01-25 10:41:42 -08:00
James R. Barlow 721489a06c docs: remove reference to --skip-repair since the argument was removed 2019-01-18 05:44:11 -08:00
James R. Barlow 9a4493f211 Add --unpaper-args
Needs test code and stricter validation
2019-01-18 05:33:28 -08:00
James R. Barlow edb4d6c586 docs: Clarify ArchLinux edition is in AUR 2019-01-18 05:29:37 -08:00
James R. Barlow b8cd3acd9e v8.0.1 notes 2019-01-17 00:57:28 -08:00
James R. Barlow 03779e33da docs: Update some install procedures for v8 changes
[ci skip]
2019-01-12 00:33:36 -08:00
James R. Barlow c466483e82 docs: Explain intermediate files 2019-01-11 14:52:05 -08:00
James R. Barlow e3a58219d1 Ensure XObjects with no subtype don't cause an exception
Closes #325
2019-01-08 16:46:08 -08:00
James R. Barlow 72337094ca v8.0.0 release notes 2019-01-05 23:35:47 -08:00
James R. Barlow f472587d22 Bump pikepdf version, point to release notes 2019-01-05 16:48:13 -08:00
James R. Barlow f34b3015b2 Prevent Ghostscript from generating invalid XMP metadata
If DocumentInfo contains NULs Ghostscript will generate XMP with
NULs which is not allowed. Repair DocumentInfo before Ghostscript sees it.
2019-01-04 13:20:41 -08:00
James R. Barlow 089ece2715 use pikepdf 0.10.2 2019-01-03 12:08:43 -08:00
James R. Barlow 6438465e3f Add fish completions 2019-01-02 17:08:30 -08:00
James R. Barlow 7d330afd81 Delinting 2019-01-02 13:34:45 -08:00
James R. Barlow 68fbd9fcc9 pikepdf: version bump 2018-12-31 15:37:31 -08:00
James R. Barlow c771938907 Convert to f-strings where it makes sense 2018-12-31 15:01:19 -08:00
James R. Barlow c2a947acf4 travis: fix 2018-12-31 01:18:30 -08:00
James R. Barlow 8c0009c5c8 Make pdfminer.six optional
Mainly since the current release of pdfminer.six lacks a sdist, blocking
homebrew packaging. Also in case other distros don't accept pdfminer.six.
2018-12-31 01:08:43 -08:00
James R. Barlow cfc5cdf47d pdfa: remove a pile of deprecated code
It's now handled in pikepdf.
2018-12-31 00:05:13 -08:00
James R. Barlow 05152a8af9 Remove always-false Tess v3 tests 2018-12-30 02:01:05 -08:00
James R. Barlow 0880b16491 Sort imports with isort 2018-12-30 01:28:15 -08:00
James R. Barlow 06308a22ce Reformat with black 2018-12-30 01:27:49 -08:00
James R. Barlow 80bd7de580 Generate test cache 2018-12-30 01:02:37 -08:00
James R. Barlow 8b90c45437 Drop support for Tesseract 3 2018-12-30 00:47:12 -08:00
James R. Barlow 72b920eb16 Drop support for Python 3.5 2018-12-30 00:23:26 -08:00
James R. Barlow b4a51907d6 Detect when metadata is dropped during PDF/A conversion 2018-12-30 00:13:25 -08:00
James R. Barlow 1ca1221432 leptonica.py: Fix exception on certain types of barcode failures
Closes #322
2018-12-19 17:23:23 -08:00
James R. Barlow 40b72b0fa8 v7.4.1 release notes 2018-12-19 16:41:09 -08:00
James R. Barlow 0e55b4ad52 Travis: remove Brewfile 2018-12-19 16:40:48 -08:00
James R. Barlow 7b4f5a8fc4 docs: try to fix readthedocs
[ci skip]
2018-12-19 15:30:07 -08:00
James R. Barlow 9261a38493 Readme: more media 2018-12-19 15:27:54 -08:00
James R. Barlow cc8ff318ed New issue template 2018-12-19 15:27:44 -08:00
James R. Barlow ad15e845f9 docs: Ghostscript PDF/A XMP metadata loss; ocrmypdf-webservice
[ci skip]
2018-12-17 23:20:49 -08:00
James R. Barlow ab632f57cd v7.4.0 release notes 2018-12-15 15:27:23 -08:00
James R. Barlow 13d20bd993 pdfinfo: tolerate PDFs that overflow and underflow the graphics stack 2018-12-15 15:10:29 -08:00
James R. Barlow b973208137 Require pikepdf 0.9.1 2018-12-15 14:23:10 -08:00
James R. Barlow 942abf8074 Fix reqs/main.txt for pikepdf 0.9.0 2018-12-14 23:29:26 -08:00
James R. Barlow ed9bb985e2 Fix pikepdf 0.9.0 2018-12-14 23:21:13 -08:00
James R. Barlow 5a7a8e573b Require pikepdf 0.9.0 2018-12-14 23:06:57 -08:00
James R. Barlow ce878db913 Rename to polyglot.dockerfile 2018-12-14 23:06:29 -08:00
James R. Barlow a3d58683b2 Update webservice.py with separate license 2018-12-14 23:05:54 -08:00
James R. Barlow 039e8ca7e7 Merge branches 'feature/newer-pike' and 'feature/webapp' 2018-12-14 18:08:31 -08:00
James R. Barlow 0ebbd4e21b Don't open encrypted files, even if password is empty 2018-12-13 22:48:00 -08:00
James R. Barlow 2cb75f6076 Refactor pipeline to make PDF/A conversion a separate step 2018-12-13 20:48:48 -08:00
James R. Barlow 857d871364 Fix regression on Ghostscript path 2018-12-13 20:36:41 -08:00
James R. Barlow 632dab2cc0 Replace Ghostscript DOCINFO and fix 9.25 metadata date regression
We no longer use Ghostscript to manage PDF metadata, instead
omitting the DOCINFO segment from the pdfmark file we generate.

Instead all of the relevant metadata code has been migrated to pikepdf,
and we use that API. This should be more consistent and fixes the
Ghostscript version-depedent quirks.

Also removes our python-xmp-toolkit dependency, except for
testing.
2018-12-13 18:13:30 -08:00
James R. Barlow 7647918f2d setup: suppress XMLParser() warning - defusedxml related 2018-12-12 22:13:32 -08:00
James R. Barlow 75c5d8055c pdfinfo: fix FutureWarning 2018-12-12 22:12:14 -08:00
James R. Barlow a938bbea55 Remove more libxmp dependencies 2018-12-12 22:02:35 -08:00
James R. Barlow 414407fbd6 Deprecate encode/decode_pdf_date and remap to pikepdf version 2018-12-12 22:01:21 -08:00
James R. Barlow 076fc717df pdfa: replace PDF/A checking with pikepdf implementation 2018-12-12 21:41:16 -08:00
James R. Barlow 2a04b2d82b Rename webapp to webservice 2018-12-12 21:29:05 -08:00
James R. Barlow 065db414c0 webapp docker: Build from polyglot 2018-12-12 21:24:04 -08:00
James R. Barlow 19a054a78b Add webapp stuff 2018-12-10 20:03:52 -08:00
James R. Barlow 9df24a81b7 Fix comment in layout.py 2018-11-28 15:16:34 -08:00
James R. Barlow 40c0acd3f2 Support using --force-ocr and --threshold or --mask-barcodes together 2018-11-28 15:16:24 -08:00
James R. Barlow 20db7f0a8f leptonica: delete file junkpixt.png if created 2018-11-28 13:47:55 -08:00
James R. Barlow e54f6ee37f v7.3.1 release notes 2018-11-16 02:13:41 -08:00
James R. Barlow 2da556bf79 Fix unsupported operand Decimal, float 2018-11-16 02:13:25 -08:00
James R. Barlow b183ad8167 Fix barcodes error handling 2018-11-16 02:08:16 -08:00
James R. Barlow 9e6b54c7ed Add test case for Type3 fonts with no Unicode mapping 2018-11-15 21:54:26 -08:00
James R. Barlow d3b334c10f Test case: true type font without Unicode mapping 2018-11-15 16:22:53 -08:00
James R. Barlow 622f2c4bab More argument checking 2018-11-15 15:59:38 -08:00
James R. Barlow 07b638a394 pdfminer: detect TrueType fonts with no valid encoding information 2018-11-15 13:44:11 -08:00
James R. Barlow 9bee2405d8 Leptonica: make threshold functions more flexible 2018-11-15 13:43:34 -08:00
James R. Barlow 8f040491bf Fix erasure of undetectable barcodes 2018-11-15 12:03:51 -08:00
James R. Barlow 8a18988706 Fix 'del draw' exception 2018-11-15 12:02:53 -08:00
James R. Barlow 47a954514b Fix name2unicode ignoring certain markers 2018-11-15 12:02:30 -08:00
James R. Barlow e3b65d4288 Fix detailed page analysis enabled at wrong time 2018-11-15 12:02:08 -08:00
James R. Barlow 4704f7ed1d Add ReadTheDocs yml so we can build with Py3.6 2018-11-12 13:43:17 -08:00
James R. Barlow 3a2745445a Fix docs build 2018-11-12 13:26:04 -08:00
James R. Barlow 12e15bab15 v7.3.0 release notes 2018-11-11 02:05:52 -08:00
James R. Barlow 9593aa4fb9 Merge v7.3.0 development 2018-11-11 01:38:42 -08:00
James R. Barlow 817d520e63 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2018-11-11 01:34:00 -08:00
James R. Barlow 700abbb8a5 Documentation for OCR quality features 2018-11-10 15:48:41 -08:00
James R. Barlow 701ef1df3f Add threshold function to work around Tesseract's poor thresholding of bright backgrounds 2018-11-10 15:34:37 -08:00
James R. Barlow 0f5c484b62 Travis: only need to specify chardet because we use pip install --no-deps 2018-11-10 13:57:04 -08:00
James R. Barlow cc7f2a3f02 Fix Python 3.5 pathlib regressions 2018-11-10 02:11:23 -08:00
James R. Barlow 755b5d87e3 Add missing chardet, implied by pdfminer.six? 2018-11-10 01:50:51 -08:00
James R. Barlow e55a4115e1 Travis: pytest 3.10.0 internal error? 2018-11-10 01:44:05 -08:00
James R. Barlow 16a6fd2ea9 Update docs for --redo-ocr and --mask-barcodes 2018-11-10 01:34:33 -08:00
James R. Barlow e3fce112ed main.txt: wrong pdfminer 2018-11-10 01:32:27 -08:00
James R. Barlow eacd26a68b Mention v6.2.5 release 2018-11-10 01:10:45 -08:00
James R. Barlow 0e88b3c38a Update v7.3.0 release notes 2018-11-10 01:09:19 -08:00
James R. Barlow a2170ef8d6 test: test version check code 2018-11-10 00:56:22 -08:00
James R. Barlow eed0424390 Update requirements 2018-11-10 00:56:04 -08:00
James R. Barlow 5ed05e08b1 Fix "no languages" test and misuse of os.environ 2018-11-09 01:57:11 -08:00
James R. Barlow 58b26f6715 Leptonica: learn to despeckle 1bpp images 2018-11-07 01:49:13 -08:00
James R. Barlow 806daf4284 leptonica: reduce boilerplate for PIX (2/2) 2018-11-06 20:33:40 -08:00
James R. Barlow c64bc9329e leptonica: reduce boilerplate for wrapper classes (except PIX) 2018-11-06 20:12:09 -08:00
James R. Barlow dd01745519 Leptonica: add masked threshold fn 2018-11-06 19:31:06 -08:00
James R. Barlow 501ce726e7 Fix two failing tests 2018-11-06 11:16:08 -08:00
James R. Barlow 03076e89ce Leptonica: reduce verbosity, more error trapping, more garbage collection 2018-11-06 11:10:59 -08:00
James R. Barlow 02f37293ee Integrate barcode masking 2018-11-05 13:01:13 -08:00
James R. Barlow 590942ad14 Leptonica: Add barcode API 2018-11-05 01:48:38 -08:00
James R. Barlow 2ac028c759 test: Add a basic redo OCR test 2018-11-04 15:54:41 -08:00
James R. Barlow 2125b5bfab Remove text detection from our parser interpret_contents
It's redundant now
2018-11-04 15:47:55 -08:00
James R. Barlow b96532caa4 Only do detailed page analysis when needed by --redo-ocr 2018-11-04 15:40:49 -08:00
James R. Barlow 995fc58466 Move Ghostscript text analysis into its own module 2018-11-04 14:55:48 -08:00
James R. Barlow c023cae299 Make pdfminer Type3 patch conditional on PScript5.dll
It appears that PDFs created by this software have a bug in their BBox
which will cause us to misjudge the space occupied by the font.

Other programs probably work around this by ignoring BBox and reading
each character procedure.
2018-11-04 01:53:53 -07:00
James R. Barlow 237eaf9130 Exception message not printed in some cases
Closes #310
2018-11-03 17:10:24 -07:00
James R. Barlow 8b9ab25125 coverage: test compile leptonica 2018-11-02 01:55:25 -07:00
James R. Barlow 77e87abe8f coverage: ensure get_orientation is checked 2018-11-02 01:32:20 -07:00
James R. Barlow 3be02e1e8d coverage: improve leptonic; don't create objects with null pointers 2018-11-02 01:10:10 -07:00
James R. Barlow 64c9ede979 leptonica: barcodes, BOXA 2018-11-02 00:42:01 -07:00
James R. Barlow 5b8d197812 coverage: make it more likely timeout is tested 2018-11-02 00:41:15 -07:00
James R. Barlow 2cba62dc4f coverage: ensure rotation is actually tested 2018-11-02 00:40:56 -07:00
James R. Barlow 288e28328f coverage: add qpdf 2018-11-02 00:37:33 -07:00
James R. Barlow b8214b3c49 coverage: exclude unicodefun.py 2018-11-02 00:33:08 -07:00
James R. Barlow 8681693994 Set up code coverage (it works with multiprocessing now!) 2018-11-02 00:31:50 -07:00
James R. Barlow 1364c63b7c Fix failure to pickle file with AcroForm 2018-11-01 20:07:53 -07:00
James R. Barlow 4ba9e8fe25 Add AcroForm detection 2018-10-30 22:28:44 -07:00
James R. Barlow a195713bb4 Throw exception on corrupt text 2018-10-30 16:35:09 -07:00
James R. Barlow 600d31a907 Require pikepdf 0.3.7 2018-10-30 16:22:05 -07:00
James R. Barlow be31cec332 Add corrupt text warning (when using --redo-ocr) 2018-10-30 16:19:58 -07:00
James R. Barlow 22a7cd3421 Add argument checks for --redo-ocr 2018-10-30 16:19:13 -07:00
James R. Barlow 8b61d2d521 pdfminer: If font descent claims to be positive, treat it as negative 2018-10-30 14:40:53 -07:00
James R. Barlow 559e5269d2 Ensure inline image is parsed correctly
Requires pikepdf > 0.3.6
2018-10-29 23:30:53 -07:00
James R. Barlow ebf6acb318 pdfminer patch: Type3 font height calculation is incorrect
Not sure where it goes wrong or why it needs special treatment, but
this does address it.
2018-10-29 22:27:25 -07:00
James R. Barlow 7acd75f013 pipeline: fix bbox coordinates 2018-10-29 22:26:37 -07:00
James R. Barlow 93623b2226 Refactor TextboxInfo 2018-10-29 14:46:40 -07:00
James R. Barlow d71fd089cb layout: allow names beginning with /i0123 for now
Showed up in GGastro2.pdf. Need to check if this pattern has valid
Unicode mappings but allow for now.
2018-10-29 14:45:59 -07:00
James R. Barlow 05aa43c856 Require pdfminer 2018-10-29 12:45:15 -07:00
James R. Barlow de80fb6bc8 Fix some failing tests after --redo-ocr changes 2018-10-29 11:49:38 -07:00
James R. Barlow 8e396f4be2 Document --redo-ocr more accurately 2018-10-29 02:03:58 -07:00
James R. Barlow efec6da377 Fix error on serializing bad character markers
(Since they held a reference to their font, which in turn, had an
open file handle.)
2018-10-29 02:02:00 -07:00
James R. Barlow 00ef53195e Fix corrupt Unicode mapping detection's false positives 2018-10-29 01:30:19 -07:00
James R. Barlow f564aaf485 Remove only_ocr_text 2018-10-28 22:41:18 -07:00
James R. Barlow 5ac2d31d0d Redo OCR can now handle visible and invisible text, so adjust accordingly
Still can't filter out corrupt text
2018-10-28 14:06:25 -07:00
James R. Barlow fda890ab47 pdfinfo: further layout improvements
Rather than grouping visible/invisible in a custom analysis step,
use pdfminer's analysis and iterate.
Make iteration predicate and return more generic.
2018-10-28 14:05:50 -07:00
Stefan Weilandjbarlow83 a873278c2a Fix some recommendations from LGTM (#309)
* Fix unreachable code

This fixes an issue reported by LGTM.

Signed-off-by: Stefan Weil <sw@weilnetz.de>

* Remove unused imports

This fixes several recommendations from LGTM.

Signed-off-by: Stefan Weil <sw@weilnetz.de>
2018-10-28 13:59:58 -07:00
James R. Barlow e6d64be890 pdfinfo: formatting 2018-10-27 23:22:44 -07:00
James R. Barlow 0e4d978d20 pdfinfo: all -> not any 2018-10-27 23:22:28 -07:00
James R. Barlow b12c2cfedf Fix handling of Type3 fonts with no ToUnicode mapping 2018-10-27 01:24:48 -07:00
James R. Barlow 58cc70725e Reorganize around getting bboxes for visible/invisible text 2018-10-26 01:07:02 -07:00
James R. Barlow 339afb02aa --redo-ocr now works in the presence of printable text 2018-10-25 16:53:47 -07:00
James R. Barlow 7ba0ff5c36 Fix strip invisible text bug: missing BT operator 2018-10-25 16:52:23 -07:00
James R. Barlow ff41fbf673 Add pdfminer based layout analysis 2018-10-25 12:42:35 -07:00
James R. Barlow 2435cd23ce Move pdfinfo into a package 2018-10-25 00:37:38 -07:00
James R. Barlow a063cff720 Rename/expose strip_invisible_text 2018-10-24 21:53:24 -07:00
James R. Barlow 0d396e1ac0 option check: Remove always-True condition
Both renderers are now lossless reconstruction-capable. (Have
been since 7.0)
2018-10-22 22:13:59 -07:00
James R. Barlow f5807a2053 Require pikepdf 0.3.5 2018-10-21 21:37:15 -07:00
James R. Barlow eb4938a36f Fix KeyError 'has_vector' 2018-10-20 01:20:22 -07:00
James R. Barlow c5ad530bbf pdfinfo: reminder about 'INLINE IMAGE' sentinel 2018-10-20 01:17:08 -07:00
James R. Barlow d11c428407 Redo OCR: disallow in cases that will damage the output PDF 2018-10-20 01:14:33 -07:00
James R. Barlow 6182b1f53e Merge branch 'feature/remove-vectors' into feature/redo-ocr 2018-10-20 01:13:24 -07:00
James R. Barlow 00fc1a12e2 optimize: should remove unreference resources too 2018-10-19 00:03:56 -07:00
James R. Barlow 16af753206 Add functional "redo OCR" feature
Needs argument validation and some other changes. Needs testing
with mixed-content PDFs.

Only really works for pure invisible text at the moment.
2018-10-19 00:02:19 -07:00
James R. Barlow fa48205bb8 Add feature to remove vector graphics objects 2018-10-18 21:46:08 -07:00
James R. Barlow f7dbf94071 pipeline: if vector graphic objects exist, ensure the DPI is reasonable 2018-10-18 01:23:31 -07:00
James R. Barlow b18e66e2ca pdfinfo: learn to detect vector graphic objects 2018-10-18 01:21:51 -07:00
James R. Barlow 7a5504dfa5 pdfinfo: fix terminology (operands, command) -> (operands, operator) 2018-10-18 01:18:30 -07:00
James R. Barlow d1cad7bc68 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2018-10-16 01:28:17 -07:00
Elliott Sales de Andradeandjbarlow83 c58d5c097c Add Fedora install instructions. (#304)
* Add Fedora install instructions.

* Fix path to fedora_rawhide badget
2018-10-14 13:28:50 -07:00
James R. Barlow 46157ca94e docs: some redundancies 2018-10-12 21:29:27 -07:00
jbarlow83andGitHub dd99511bcc Fix broken badges in README 2018-10-12 21:16:08 -07:00
M.Yasoob Ullah Khalid ☺andjbarlow83 5bc2efd3c7 Removed extra word from docs (#303) 2018-10-12 21:02:16 -07:00
James R. Barlow 1b18dbecf5 Fix filename test.txt 2018-10-11 16:03:25 -07:00
James R. Barlow 9f82c0eb6e v7.2.1 release notes 2018-10-11 15:55:01 -07:00
James R. Barlow 68bac1b177 Fix compatibility with pikepdf 0.3.5 API change 2018-10-11 15:51:34 -07:00
James R. Barlow 1495b78330 Remove cruft to support leptonica < 1.72 in test suite 2018-10-11 01:37:32 -07:00
James R. Barlow 6f777d2848 Include Debian copyright file 2018-10-10 23:55:48 -07:00
James R. Barlow 5650eba848 Cleanup MANIFEST.in, reorg requirements/*.txt, fix non-Unicode readme 2018-10-10 23:53:08 -07:00
James R. Barlow 5bc5dc93f3 v7.2.0 release notes update 2018-10-05 01:27:00 -07:00
James R. Barlow c1e18bb825 optimize: Exclude soft masks (SMasks) from optimization
Soft masks are only allowed to be of colorspace DeviceGray so we
shouldn't use pngquant on them. For now, avoid this exceptional
case by excluded soft masks from optimization.
2018-10-05 01:23:26 -07:00
James R. Barlow 58282ea0fb optimize: more refactoring
Now properly generalized/specialized where it should be
2018-10-04 13:44:51 -07:00
James R. Barlow 891da7834c optimize: refactor image extraction 2018-10-04 12:34:22 -07:00
James R. Barlow 5c229d48d5 optimize: Reorganize so JBIG2 can be performed on images reduced to 1bpp
Closes #297
2018-10-04 11:53:11 -07:00
James R. Barlow 53f660cf35 Travis: use newer macos image 2018-10-04 08:59:40 -07:00
James R. Barlow 7b66ca68f2 ...and document lossy JBIG2 2018-10-04 01:31:53 -07:00
James R. Barlow ba71c3ffbd requirements: request pikepdf 0.3.4 2018-10-04 01:22:03 -07:00
James R. Barlow 6707ad427a v7.2.0 release notes 2018-10-04 01:21:17 -07:00
James R. Barlow 5b84549716 Change JBIG2 lossy mode to require --jbig2-lossy 2018-10-04 01:20:49 -07:00
James R. Barlow c74f2ee6e8 Refactor the detailed error messages 2018-10-04 00:10:59 -07:00
James R. Barlow b32dd9f9d3 Fix lossless JBIG2 when there are multiple JBIG2 images on a single page 2018-10-03 17:40:26 -07:00
James R. Barlow fb8b161f6c Fix suppression of tesseract config error messages 2018-10-03 17:39:50 -07:00
James R. Barlow baddd6d233 Remove libtiff from Brewfile
For some reason, brew complains about it now.
2018-10-03 16:17:59 -07:00
James R. Barlow 6f554c6ae8 tesseract: account for behavior changes when params are missing
Tesseract 4.0-rc1 now accepts invalid parameters in config and
won't return an error anymore. We prefer to raise an error if this
occurs.

See: https://github.com/tesseract-ocr/tesseract/commit/741ea00d7059d8ff7c55797ffc525a461d7f3ced
2018-10-03 15:11:34 -07:00
James R. Barlow a71e4488b3 test: fix pytest warning about direct use of a fixture 2018-10-03 15:04:46 -07:00
James R. Barlow 72156b5653 Degrade more gracefully when --optimize is set but JBIG2 is not present 2018-10-03 14:24:20 -07:00
James R. Barlow 9fa471e053 Test: send stderr to stderr, why don't we? 2018-10-03 14:23:34 -07:00
James R. Barlow 31ef2fe907 test: this error message changed case in newer Tesseract 2018-10-03 13:58:20 -07:00
James R. Barlow 9a8ec4b210 optimize: only enable lossy JBIG2 for -O3 2018-10-03 00:38:58 -07:00
James R. Barlow 75aad4cc79 optimize: Refactor convert_to_jbig2 2018-10-02 23:42:12 -07:00
James R. Barlow 4b27feca98 optimize: Disable JBIG2 lossy mode, use lossless instead 2018-10-01 12:28:54 -07:00
James R. Barlow 45522cd15f weave: clarify comment about garbage data in ToC 2018-09-27 13:48:35 -07:00
James R. Barlow 677d9a4e76 Remove some unhelpful lambdas 2018-09-27 13:48:12 -07:00
James R. Barlow efa7ea4fde Fix log.error where log is None 2018-09-19 23:01:27 -07:00
James R. Barlow 137a6e45f5 ghostscript: fix missing fspath for py3.5 2018-09-19 22:57:20 -07:00
James R. Barlow 29116e1dec Change to README.md 2018-09-19 21:01:24 -07:00
James R. Barlow 87193335b9 v7.1.0 notes 2018-09-19 20:57:18 -07:00
James R. Barlow cfd4f8a850 Improve error handling for improvements to Ghostscript text extraction 2018-09-19 20:29:18 -07:00
James R. Barlow eaa324939f Upgrade to pikepdf 0.3.3
Closes #231
2018-09-19 15:30:54 -07:00
James R. Barlow ef70e538f7 Improve error message on handling KeyboardInterrupt
Closes #301
2018-09-19 01:40:26 -07:00
James R. Barlow b7b912e56a Fix test suite and blank pages 2018-09-17 01:12:58 -07:00
James R. Barlow 4615cf2f1e First cut at improving text extraction speed 2018-09-16 23:34:18 -07:00
James R. Barlow eaf772f80a Merge v6.2.4 release notes 2018-09-16 15:45:38 -07:00
James R. Barlow 96ba75eabd Ghostscript: fix issues in strict ASCII implementation 2018-09-16 15:41:54 -07:00
James R. Barlow fdfe52c1ad main: add debug option to force threads 2018-09-15 00:01:45 -07:00
James R. Barlow 932b2e2a29 main: print Ghostscript version too 2018-09-14 23:58:06 -07:00
James R. Barlow 57e489c957 main: Cleanup; support overriding sys.args in run_pipeline 2018-09-14 23:57:35 -07:00
James R. Barlow 17a3fa671c ghostscript: API docs update 2018-09-14 23:51:52 -07:00
James R. Barlow 2659afb4f6 Cleanup gitignore 2018-09-14 21:02:22 -07:00
James R. Barlow 7392115507 Blacklist Ghostscript 9.24 due to regressions
As per issue #291. Forced push to remove a copyrighted test file that was
accidentally included.
2018-09-14 20:41:13 -07:00
James R. Barlow c54d0c7eaa v7.0.5 release notes 2018-09-13 23:29:54 -07:00
James R. Barlow b95eefc65f Fix pikepdf version for Travis 2018-09-13 22:08:19 -07:00
James R. Barlow 686207ab7f Check for and reject Adobe LiveCycle Designer PDFs
These are the ones that display a "Please wait..." message.

Closes #296
2018-09-13 21:50:51 -07:00
James R. Barlow 517b385fe5 Work around loss of Unicode DOCINFO in Ghostscript 9.24+
Ghostscript no longer supports UTF-16-BE-hex strings as a way of
supplying Unicode data in pdfmark so we have lost this functionality too:
http://git.ghostscript.com/?p=ghostpdl.git;a=commit;h=e997c6836d243ab37fe3a5f0d57974af95eb5eac

For users this means setting --title, --author, etc. will not work if gs
9.24 is installed, but if the file has existing metadata it might work.

For now we enforce police-state-strict ASCII, until there's time to
implement proper metadata editing. Relevant tests set to xfail.
2018-09-13 21:33:39 -07:00
James R. Barlow 795019b0c1 Work around invalid TOC entries
Kodak Capture Desktop and probably other software creates a
/Outlines entry with /First being set to an invalid indirect reference to
an object that hasn't been created. This is legal in the PDF spec but
problematic for qpdf. The objgen will be (max valid object ID + 1, 0).
Because we create new objects in _weave, some TOC entries will end
up assigned to new objects we create. Typically /ProcSet.

We solve the issue by refactoring page traversal and then doing it
twice, once to resolve all references (eliminating the null
reference problem) and a second pass to make our changes.
2018-09-11 14:44:16 -07:00
James R. Barlow 3127a73822 Ghostscript: no need to specify ProcessColorModel when ColorConversionStrategy 2018-09-11 11:56:05 -07:00
James R. Barlow 069ee6c91f ghostscript: fix for 9.24 having jpeg passthrough available 2018-09-10 23:09:51 -07:00
James R. Barlow 3aac3a98ca tests: Migrate metadata tests to pikepdf
For some reason PyPDF2 has begun to trigger internal errors in
pytest on macOS alone. Not sure why, but nothing is wrong that I can
see. Seemed like an opportune time to switch to pikepdf; found some
new issues in the process anyway.
2018-09-10 16:06:01 -07:00
James R. Barlow 268859a304 Merge branch 'master' of github.com:jbarlow83/OCRmyPDF: docs 2018-09-10 11:52:04 -07:00
James R. Barlow a96710aa7b leptonica: update comments 2018-09-10 11:47:38 -07:00
James R. Barlow edcc58826a pdfinfo: remove some dead code 2018-09-10 11:47:00 -07:00
James R. Barlow 7077c8220a Fix rst formatting in release notes 2018-09-10 11:46:17 -07:00
Mateus Seenem Tavaresandjbarlow83 f7cbf68edd Updating Arch Linux instalation (#288)
* Updating Arch Linux instalation

And adding a workaround to a wrong dependencies definition on https://aur.archlinux.org/packages/python-pikepdf/

* Remove comment about temporary workaround
2018-08-31 12:30:18 -07:00
James R. Barlow 68a58ee8a5 docs: fix hyperlinking of jbig2 page (again) and cleanup release notes 2018-08-27 01:25:30 -07:00
James R. Barlow 3109ec5091 v7.0.4 notes 2018-08-24 12:41:53 -07:00
James R. Barlow e0599fe8d7 Require pikepdf 0.3.2 2018-08-24 12:41:43 -07:00
James R. Barlow a749240589 docs: mention pikepdf install more clearly 2018-08-22 03:19:46 -07:00
James R. Barlow 6decdaa062 Try setuptools_scm_git_archive again 2018-08-20 15:45:51 -07:00
James R. Barlow 4d5c9b8cdf Fix error in optimize.py on PNGs at -O2
Error was
TypeError: unsupported operand type(s) for -: 'tuple' and 'int'
2018-08-20 15:45:34 -07:00
James R. Barlow 1e23ea5364 Remove pikepdf < 0.3 compatibility shims since > 0.3.1 is now required 2018-08-10 17:01:03 -07:00
James R. Barlow cf9a8a91b5 Require pikepdf 0.3.1 2018-08-10 16:59:08 -07:00
James R. Barlow 05d3a65e94 docs: Fix links to JBIG2 encoder page
[ci skip]
2018-08-09 21:14:50 -07:00
James R. Barlow c043552f8b Fix travis.yml syntax 2018-08-03 14:02:20 -07:00
James R. Barlow 487ee2b6c9 Notes for v7.0.2 2018-08-03 13:37:18 -07:00
James R. Barlow 8013fd50da Draw preview image at full resolution
As reported in #281 and confirmed by the test file in #279, downsampling
the preview adversely affects quality of image rotation especially for
small font sizes and marginal scans.

Full size gets rotation accuracy. This makes rotation a little inefficient
since it rasterizes twice - to be addressed later.
2018-08-03 13:32:10 -07:00
James R. Barlow 4ec9ec12e3 docs: Describe PDF optimization 2018-08-03 13:10:18 -07:00
James R. Barlow ed96594727 Regroup installation page content around platforms
Also separate out JBIG2 encoder instructions so that distributions that
delete installation.rst won't omit this information.
2018-08-03 12:47:25 -07:00
James R. Barlow 91b7193249 Travis: use xenial for Python 3.7 2018-08-03 11:52:23 -07:00
James R. Barlow 008f65c6a9 Update pinned requirements 2018-08-03 01:15:21 -07:00
James R. Barlow 7aa4e60af2 Explain pytest --runslow 2018-08-03 00:57:59 -07:00
James R. Barlow 6ef26516d1 pipeline: revise logic of rotations to fix pages with nonzero /Rotate 2018-08-03 00:53:29 -07:00
James R. Barlow bb50501369 ghostscript: never use autorotatepages
It looks like from the gs manual that this parameter is only respected for
vector devices so it never had any effect, but to stay on the safe side,
which it off.
2018-08-03 00:44:34 -07:00
James R. Barlow 55eb481f30 Add intensive (optional) rotation test 2018-08-03 00:42:59 -07:00
James R. Barlow 4509ca6e75 pipeline: remove unused function 2018-08-03 00:41:08 -07:00
James R. Barlow c61fe9af61 Fix release notes typos 2018-08-03 00:40:44 -07:00
James R. Barlow 4181a712d1 Update setup.py for version changes 2018-08-01 15:17:49 -07:00
James R. Barlow c171cb7286 Merge img2pdf 0.3.0 fix from v6.2.3 2018-08-01 15:17:33 -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 755f6e0979 optimize: Use new pikepdf Object.write API 2018-07-13 23:52:36 -07:00
James R. Barlow 95b2830d17 Support removal of Null from pikepdf 2018-07-13 23:50:35 -07:00
James R. Barlow 2669d8332e Add more guidance about using Docker 2018-07-13 12:39:47 -07:00
James R. Barlow 11fbd32e6e Merge v6.2.2 (mainly to get release notes) 2018-07-13 12:38:15 -07: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 a12a5f11ff More doc updates for 7.0.0 2018-07-12 01:52:49 -07:00
James R. Barlow f46e4067f4 Dockerfile: should work now
[ci skip]
2018-07-11 00:57:20 -07:00
James R. Barlow 47bf5585e8 Dockerfile: needs leptonica 2018-07-11 00:17:04 -07:00
James R. Barlow 72fb4d39be Dockerfile: fix missing mkdir 2018-07-10 21:56:49 -07:00
James R. Barlow 9681c0e05a Eliminate Homebrew autobrewing
The Homebrew-core does such a good job of keeping things up to
date there is little to point to a private infrastructure.
2018-07-10 21:37:38 -07:00
James R. Barlow 2628575dab Adjust Dockerfile again 2018-07-10 21:14:41 -07:00
James R. Barlow f7d59068e5 Adjust Dockerfile for 7.0.0 2018-07-10 18:20:33 -07:00
James R. Barlow 4cf10d62ca More work on installation steps
[ci skip]
2018-07-10 18:20:22 -07:00
James R. Barlow 2f65e3aac8 (Hopefully) fix build on ReadTheDocs
[ci skip]
2018-07-10 13:20:33 -07:00
James R. Barlow e494cd7aa6 Update installation notes
Closes #276
2018-07-10 12:24:01 -07:00
James R. Barlow 809880f46d Brew: jbig2dec and libffi are not required 2018-07-10 11:05:36 -07:00
James R. Barlow 5925ebe31c Merge branch 'develop' (7.0.0) into master
# Conflicts:
#	.gitignore
#	.travis/linux_before_install.sh
#	docs/release_notes.rst
#	test_requirements.txt
2018-07-09 21:15:13 -07:00
James R. Barlow 316709c291 Fix typo introduced in
338593d
2018-07-09 21:07:58 -07:00
James R. Barlow 0d6a2ba554 Declare certain APIs public 2018-07-09 14:28:37 -07:00
James R. Barlow 1fcb06b1aa Update readme 2018-07-09 14:02:12 -07:00
James R. Barlow 36138c7c06 Update release notes 2018-07-09 13:56:30 -07:00
James R. Barlow 338593d9e7 Remove ruffus 2.6.3 exception special casing 2018-07-09 13:56:23 -07:00
James R. Barlow d6eb1f9578 Remove dependency on private fork of ruffus, change to official 2.7 2018-07-09 12:51:56 -07:00
James R. Barlow 1cc9d2d3d1 Fix path error on Py3.5 2018-07-08 01:01:06 -07:00
James R. Barlow 58642aa98b Fix issue #275: doesn't work when installed in non-Unicode path
Closes #275
2018-07-07 01:35:05 -07:00
James R. Barlow 7baaf00a38 Fix wrong return code tested 2018-07-05 13:49:22 -07:00
James R. Barlow 5cc23dbf24 pdfinfo: more robustness 2018-07-04 17:12:30 -07:00
James R. Barlow 216d60ea2c pdfinfo: improve the regex 2018-07-04 00:59:32 -07:00
James R. Barlow 8b0496d35e Fix invalid XML characters choking parser 2018-07-03 22:51:59 -07:00
James R. Barlow e44001641c Return a distinct error code if PDF/A fails 2018-07-03 16:59:03 -07:00
James R. Barlow 47885f4230 Remove initial qpdf.repair
Since pikepdf is doing the work the initial repair takes time and gives
little benefit.

It turns out to not be worthwhile to
save the results of PdfInfo parsing,
since the time to save this seems to exceed the costs of recalculating
it since the "weave" code. At least
for small files.
2018-07-03 16:50:05 -07:00
James R. Barlow 921767e82e ocrmypdf.exec: trap FileNotFoundError too 2018-07-03 00:05:49 -07:00
James R. Barlow 85f96b7fb0 Add test to optimize if jbig2 is present 2018-07-02 23:49:11 -07:00
James R. Barlow 890c7fd0f6 optimize: allow modification of quality settings in command line mode 2018-07-02 23:48:51 -07:00
James R. Barlow 39c44bdd2f Don't use --optimize in test since jbig2enc is not always installed 2018-07-02 23:48:23 -07:00
James R. Barlow 5f99f7f6ca Upgrade to Py3.7 locally and resolve a few issues 2018-07-02 23:47:51 -07:00
James R. Barlow 4f864bce98 Update macOS Brewfile 2018-07-02 22:25:46 -07:00
James R. Barlow 2974929b26 Make jpeg/png quality tunable args 2018-07-02 22:22:59 -07:00
James R. Barlow db837aa55c Improve release notes 2018-07-02 16:48:33 -07:00
James R. Barlow 7200623007 Fix installation for Python 3.7
Need to use private fork of ruffus for Python 3.7. Backward compatible with Python 3.6 for ruffus 2.6.3

Disable locale checking for 3.7 since the various fixes in that release should make it unnecessary.
2018-07-02 16:47:14 -07:00
James R. Barlow 73e02ae4ea Hopefully workaround Py3.5 marshal error
https://github.com/eliben/pycparser/issues/251
2018-06-29 12:54:48 -07:00
James R. Barlow d4cbef9457 Update test cache with naming rule change 2018-06-29 12:04:20 -07:00
James R. Barlow ed8ff79e10 Optimize some of our bigger test files
Only partially optimize multipage.pdf so that it hopefully
improves speed of test suite without being useless as an
optimization test.
2018-06-29 00:35:49 -07:00
James R. Barlow e725f64b6a Add test case to ensure mono is not inverted 2018-06-29 00:25:11 -07:00
James R. Barlow 0029cc4fe7 optimize: fix PNGs that were reduced to 1-bit being inverted
At some point the color gets flipped, we have to flip it again,
for mono.

Incidentally this exposed an unused
optimization. Should change the
first past to scan all images and
record monochrome xrefs, then optimize
JPEG and PNG, possibly adding
mono images to the monochrome
queue. Finally, do JBIG2 optimization.
2018-06-29 00:09:20 -07:00
James R. Barlow 9637696a54 Fix test resources naming inconsistency 2018-06-28 23:37:14 -07:00
James R. Barlow 02b3ca6862 Compress test images more heavily 2018-06-28 21:40:12 -07:00
James R. Barlow bc90f40a8f Replace all Pix.read with Pix.open 2018-06-28 15:13:26 -07:00
James R. Barlow 3d727ff4c0 Fix leptonica remove_colormap was replaced with a no-op at some point 2018-06-28 15:11:51 -07:00
James R. Barlow b0eacd6586 Add Python 3.7 support 2018-06-28 13:57:45 -07:00
James R. Barlow 7795701595 Merge branch 'test/ignore-masks' 2018-06-28 13:05:45 -07:00
James R. Barlow bf214eecb3 Use newer pikepdf API for objgen 2018-06-28 12:59:01 -07:00
James R. Barlow 434b96d734 optimize: skip incremental images if any
These are fairly rare
2018-06-24 00:18:48 -07:00
James R. Barlow b9dc109892 optimize: use new pikepdf api for objgen 2018-06-24 00:16:28 -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
James R. Barlow bf96171b65 Ignore whether or not textonly_pdf was used in cache
The difference doesn't matter in 7.0.0 anymore.
2018-06-23 02:58:26 -07:00
James R. Barlow b7ff821fa3 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:55:58 -07:00
James R. Barlow b81daf71d1 Regenerate test cache 2018-06-23 02:02:58 -07:00
James R. Barlow faad1fc58a Reactivate two tests that weren't using their fixtures properly 2018-06-23 01:54:09 -07:00
James R. Barlow 6f48181a56 Disable a pylint 2018-06-23 01:53:04 -07:00
James R. Barlow f1305e5a37 pdfa: fix function using closure when it shouldn't 2018-06-23 01:52:36 -07:00
James R. Barlow f0e0f92776 leptonica: fix variables defined on class outside __init__ 2018-06-23 01:51:55 -07:00
James R. Barlow 807c8b0726 Trailing whitespace 2018-06-23 01:51:19 -07:00
James R. Barlow 6333ec928c Cleanup some cases where log was lazy and should be 2018-06-23 01:50:27 -07:00
James R. Barlow cd220d9ed9 pipeline: search_window variable not actually used 2018-06-23 01:48:57 -07:00
James R. Barlow 76532649b8 tesseract.get_orientation: removed unused language parameter 2018-06-23 01:48:24 -07:00
James R. Barlow b0dbaeafc5 Cleanup unused imports 2018-06-23 01:47:53 -07:00
James R. Barlow 2530d1791b Fix several pylint errors and warnings 2018-06-23 00:54:22 -07:00
James R. Barlow 94150f414a Remove qpdf.merge
We no longer need to merge pages this way. Much of the functionality
was there to implement page splitting without hitting ulimit which
will be fixed in qpdf > 8.0.2. The tests were expensive to run.

Also remove pytest-timeout since it breaks the Linux build.
2018-06-23 00:45:03 -07:00
James R. Barlow 54e74f84cc Remove special of TypeError from ruffus
split_pages would still run if repair_pdf failed, for some reason.
Since we are no longer splitting pages this is vestigial.
2018-06-23 00:41:20 -07:00
James R. Barlow 76e7e8dbbb Replace several uses of str(path) with fspath(path)
Helps make it more explicit. Did not do this to tests because use of paths
is more involved there.
2018-06-22 21:00:47 -07:00
James R. Barlow 324598e992 Remove helpers.universal_open()
This helper function only had a single usage, this was always an awkward
way to support Python 3.5 that I'd forget to use.
2018-06-22 17:56:20 -07:00
James R. Barlow 9e765ddf46 Rename _optimize to optimize.py 2018-06-22 17:51:57 -07:00
James R. Barlow 6ac9e92f17 Fix PEP8 docstring convention misuse in a few places 2018-06-22 17:51:25 -07:00
James R. Barlow faaa4a1def Ghostscript, PDF/A: support pathlib 2018-06-22 17:45:10 -07:00
James R. Barlow 0aa51f0f3a Remove fitz from Travis 2018-06-18 15:38:41 -07:00
James R. Barlow 73431d9761 Remove obsolete _naive_find_text 2018-06-13 14:00:50 -07:00
James R. Barlow 45cb4525cf Remove other references to PyMuPDF 2018-06-13 01:02:53 -07:00
James R. Barlow 8c84c515b6 Use Ghostscript for text region detection
Ghostscript txtwrite seems to be quite effective at the task.

Eliminates dependency on fitz
2018-06-13 00:58:09 -07:00
James R. Barlow 1dfbbdebf4 Adjust for pikepdf API change 2018-06-08 22:47:56 -07:00
James R. Barlow 740918daee Create debug envvar to override Creator or Producer
Note that Ghostscript always overrides Producer
2018-06-06 23:17:28 -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
James R. Barlow 04d79b15b4 optimize: fix error in Py3.5 2018-06-06 12:25:32 -07:00
James R. Barlow a13c398c06 Suppress some spurious tesseract errors 2018-06-05 23:26:28 -07:00
James R. Barlow e3b3f716ee optimize: use tempdir for cmdline invocation 2018-06-05 21:20:54 -07:00
James R. Barlow cf43c06f46 Use python-xmp-toolkit for xmp check
Eliminates PyPDF2 and defusedxml as dependencies.
2018-05-29 22:00:52 -07:00
James R. Barlow 74a5a18607 Tweak release notes 2018-05-28 14:52:06 -07:00
James R. Barlow 44241c6dd5 Travis: remove deploy to testpypi since it's broken 2018-05-27 01:49:18 -07:00
James R. Barlow 8fff496ffd Fix Py3.5 not understanding os.path.exists(Path(...)) 2018-05-26 22:55:22 -07:00
James R. Barlow edf75c519c Update v7 release notes 2018-05-26 02:08:49 -07:00
James R. Barlow 9608b22d34 Remove all uses of PyPDF2 except PDF/A check
Leave PDF/A check alone for now, since pikepdf has no equivalent.
2018-05-26 02:07:18 -07:00
James R. Barlow 8ba4968c48 pdfinfo: more robustness 2018-05-26 01:54:25 -07:00
James R. Barlow ffdd78f1a5 pdfinfo: Fix text_operators type not changed in related commit 2018-05-25 02:10:39 -07:00
James R. Barlow ad9f8ca78e pdfinfo: reinstate stack normalization for q/Q 2018-05-25 01:28:26 -07:00
James R. Barlow 78a686ecb4 Consider qpdf behavior on algo4 a pass
qpdf opens files with null user password, so do the same.
2018-05-25 00:33:31 -07:00
James R. Barlow 59e786eb3c Remove old code to deal with single page only things 2018-05-25 00:32:55 -07:00
James R. Barlow 6d0461435f Use OperandGrouper whitelist 2018-05-24 22:52:33 -07:00
James R. Barlow 0a04a60f69 Document need for pdfinfo to be pickleable 2018-05-24 22:24:13 -07:00
James R. Barlow 68d8642988 Found out this test was extremely slow - no reason to actual use a large file 2018-05-24 22:22:51 -07:00
James R. Barlow 16f70ff054 Main changeset for pikepdf-based refactor pdfinfo 2018-05-24 22:22:01 -07:00
James R. Barlow c00aeafff0 Add scratch file 2018-05-24 22:20:15 -07:00
James R. Barlow 83f35e00f3 Start removing PyPDF2 2018-05-21 01:28:21 -07:00
James R. Barlow 786a2ad65a Make optimize test do a little more 2018-05-18 17:50:39 -07:00
James R. Barlow 9425506c2a Use pikepdf to handle paletted images
Removes all use of PyMuPDF in optimize
2018-05-18 17:44:29 -07:00
James R. Barlow 93b858afd1 Remove qpdf appimage support for now, check for pngquant 2018-05-18 16:24:33 -07:00
James R. Barlow 7b0a3ec365 Add notes for v7 2018-05-18 00:20:45 -07:00
James R. Barlow 083d442529 main: wording change 2018-05-18 00:20:24 -07:00
James R. Barlow b52eb95cf8 optimize: use pikepdf to save PIL images
Eliminates another usage of PyMuPDF in the main path.
2018-05-18 00:18:44 -07:00
James R. Barlow f4571e2508 Ensure we try compress anything that's not compressed when saving 2018-05-17 22:05:01 -07:00
James R. Barlow b06ef03aac pipeline: use the resolution of the OCR image rather than recalculating
(Recalculating would fail if the image is not centered.)
2018-05-17 16:51:53 -07:00
James R. Barlow 1d1962a106 weave: fix rescaling logic
rotation % 90 == 0 is always true.
2018-05-17 16:50:01 -07:00
James R. Barlow 4b98e9ff08 weave: if we don't have textonly_pdf, delete instruction to draw image 2018-05-17 16:49:20 -07:00
James R. Barlow f83ca5d8ac weave: whitespace 2018-05-17 16:06:36 -07:00
James R. Barlow 95cb4d22d7 pipeline: make /Info from indirect object as required 2018-05-17 16:06:13 -07:00
James R. Barlow 0c279b01a4 Fix test failure on missing JobContext 2018-05-17 01:16:58 -07:00
James R. Barlow 3b820ffa7b test_metadata: change from xfail to skipif without fitz 2018-05-17 00:14:57 -07:00
James R. Barlow 35cb416563 pipeline: remove fitz-based attempt to repair table of contents
Prior to unsplit, if we were rebuilding the PDF we'd lose the
table of contents. With unsplit we keep the original file and patch
the table of contents as necessary, adn that works fine.
This remaining bit of code from PyMuPDF actually damages the
table of contents and removing it fixes the test suite. G'bye.
2018-05-16 23:24:57 -07:00
James R. Barlow cdb737259c pipeline: remove old page merge strategies 2018-05-16 22:16:54 -07:00
James R. Barlow 0843b5939c pipeline: Move weave* to its own file 2018-05-16 22:08:31 -07:00
James R. Barlow 2b5f23a2d1 Add code to repair ToC with pikepdf 2018-05-16 21:39:23 -07:00
James R. Barlow 5e20d1d554 metadata: Fix failing test on __getitem__['/CreationDate'] 2018-05-16 13:46:07 -07:00
James R. Barlow 18595ca86a Use pikepdf for get_pdfmark
It does fine.
2018-05-16 12:24:35 -07:00
James R. Barlow 3e269fa188 Ubuntu 14.04 has a qpdf 8.0.2 backport, making life easier 2018-05-15 21:43:19 -07:00
James R. Barlow 65405c2cb9 Try getting qpdf from Ubuntu 18.04 2018-05-15 21:27:27 -07:00
James R. Barlow 442cf8897a Travis: maybe upgrading wheel? 2018-05-15 18:12:35 -07:00
James R. Barlow d5fb275e9e Travis: hack in qpdf appimage version
qpdf from appimage does not report its version with --version if renamed
or accessed via symlink. Use an environment variable to supply it
where needed.
2018-05-15 17:45:58 -07:00
James R. Barlow e60aec81ca Travis: why can't we use qpdf appimage? 2018-05-15 16:59:16 -07:00
James R. Barlow 398e9e535e optimize: Changed pikepdf API 2018-05-15 16:29:57 -07:00
James R. Barlow 08bf651ef2 Refactor JBIG2 path for non-CCITT monochrome images 2018-05-15 15:32:15 -07:00
James R. Barlow 6171de41bf optimize: move a lot of image scanning code to pikepdf 2018-05-14 22:21:53 -07:00
James R. Barlow f0a56592e2 Pull JobContext out of pipeline.py to avoid circular reference 2018-05-14 14:01:25 -07:00
James R. Barlow 87a7d4d1a8 Another fitz failure - incorrect object reference introduced
MuPDF/fitz changed some font references to point to table of contents
entries, corrupting the page.  It no longer gets to save.
2018-05-14 13:58:49 -07:00
James R. Barlow 05287902a2 Travis: again 2018-05-13 11:02:25 -07:00
James R. Barlow 96e453feb6 Travis: Tweak setup so it can run 2018-05-13 01:21:24 -07:00
James R. Barlow 9c0fa9fc04 Travis: again 2018-05-13 01:17:04 -07:00
James R. Barlow 3bde0715b0 Move qpdf to before_script 2018-05-13 01:01:48 -07:00
James R. Barlow e2ec3d8b9b Travis: adjust qpdf appimage 2018-05-13 00:53:31 -07:00
James R. Barlow ad91eaf8a7 Travis: try using qpdf appimage to speed up build 2018-05-13 00:42:48 -07:00
James R. Barlow b6d30214fd PyMuPDF 1.13.4 looks good, use it 2018-05-12 12:35:46 -07:00
James R. Barlow c4ab01d63d Fix "AttributeError: 'ImageInfo' object has no attribute '_type'"
Also deal with 'fixme' imagemask comment.

Also fix bpc incorrectly set to 8 by default on stencil masks.
2018-05-12 12:14:57 -07:00
James R. Barlow 4ba3b3f55a Fix rotate_pages_threshold test failure 2018-05-12 11:47:46 -07:00
James R. Barlow 52d2706a9e optimize: Fix error causing many images to be skipped 2018-05-12 01:37:30 -07:00
James R. Barlow 964afc69f6 leptonica: ErrorTrap is an implementation detail 2018-05-12 01:21:45 -07:00
James R. Barlow 3ddf545ccd optimize: leptonica can fail to open PNG
ERROR - Info in pixReadStreamPng: converting (cmap + alpha) ==> RGBA
Error in pixReadStreamPng: spp == 1, cmap, trans array, invalid depth: 4

To investigate later....
2018-05-12 01:21:19 -07:00
James R. Barlow f9374733bb optimize: process ICCBased images that declare an /Alternate we recognize 2018-05-12 00:43:36 -07:00
James R. Barlow 5930135f45 optimize: Refactor naming helpers 2018-05-12 00:42:24 -07:00
James R. Barlow f03f6bc128 optimize: document problem with transcode free compressed image data 2018-05-11 23:43:06 -07:00
James R. Barlow 6c50c70235 Try to optimize paletted images 2018-05-11 23:42:26 -07:00
James R. Barlow 8790fc2c1b optimize: add knobs to control image quality but don't show the user yet 2018-05-11 23:41:49 -07:00
James R. Barlow f86c4fccf4 optimize: don't alter >8 bpc images 2018-05-11 22:31:24 -07:00
James R. Barlow 7d0785e9ed main: do better parameter validation 2018-05-11 22:31:09 -07:00
James R. Barlow 2cac88162c Ignore masks when deciding what color to rasterize at 2018-05-11 21:27:57 -07:00
James R. Barlow 4809627d8a Fix jbig2enc name 2018-05-11 17:51:08 -07:00
James R. Barlow 871979abd6 Temporarily unbreak without fitz mode 2018-05-11 17:32:15 -07:00
James R. Barlow efb95722ca Travis: Use declarative APT for Tesseract too 2018-05-11 12:46:10 -07:00
James R. Barlow d9bbb80a6b Don't try to run jbig2 when not available 2018-05-11 12:42:00 -07:00
James R. Barlow 3254315127 Update test cache 2018-05-11 12:19:50 -07:00
James R. Barlow ca297fd26b Update tests 2018-05-11 02:33:44 -07:00
James R. Barlow ac36a43cef Warn about --user-words not having any effect
Might be available in full release of Tess4
2018-05-11 02:31:07 -07:00
James R. Barlow f00183115d Update our dependencies 2018-05-11 02:11:55 -07:00
James R. Barlow 161b29a899 Check jbig2 when optimizing is requested 2018-05-11 02:11:01 -07:00
James R. Barlow 72253d09fa Add arguments to control optimization 2018-05-10 22:23:24 -07:00
James R. Barlow 40d09ddb23 Fix merge error in Leptonica 2018-05-10 21:17:47 -07:00
James R. Barlow 3026d86a9e Remove jbig2enc.py 2018-05-10 21:15:07 -07:00
James R. Barlow 0661a7edc3 Merge optimize 2018-05-10 21:05:32 -07:00
James R. Barlow 24b0adfacc Merge branch 'master' into develop 2018-05-10 20:54:55 -07:00
James R. Barlow acc6698ab3 Make XML metadata test actually work 2018-05-10 20:37:10 -07:00
James R. Barlow 606d3e6aa1 Remove tests that exercise obsolete features (tesseract, -g) 2018-05-10 20:33:32 -07:00
James R. Barlow 687a7954d6 test_main: uses leptonica 2018-05-10 19:05:31 -07:00
James R. Barlow 36a53a7b37 Weave: Unconditionally rotate and scale the text layerThis solves two issues. First, the text layer can end up being adifferent size, probably if the DPI is not an integer; scaling helps itfit slightly better. Second, other printable text on the page can end uphorizontally scaled or misaligned if we don't all of our drawing in aq/Q pair. 2018-05-10 19:03:31 -07:00
James R. Barlow 0a5982a902 PyMuPDF tweaks: don't clean
In MuPDF 1.13 clean might be unreliable, so explicitly don't do it,
even though it doesn't cause trouble in 1.12.
2018-05-10 18:50:52 -07:00
James R. Barlow 601863f9e9 Return to PyMuPDF 1.12.5 2018-05-10 18:47:10 -07:00
James R. Barlow c9ce731119 Fix DPI mismatch between OCR page and source page 2018-05-10 17:34:08 -07:00
James R. Barlow abed8e034e Add metadata preservation test from stash 2018-05-10 16:43:28 -07:00
James R. Barlow 63032d304d Revert "Since PyMuPDF 1.13.3 corrupts text, pin 1.12.5 and work around it"
This reverts commit b0ce7c63dd.
2018-05-10 16:27:17 -07:00
James R. Barlow a57ecede78 Refactor textareas to remove duplicate code 2018-05-10 16:26:52 -07:00
James R. Barlow b0ce7c63dd Since PyMuPDF 1.13.3 corrupts text, pin 1.12.5 and work around it 2018-05-10 16:10:24 -07:00
James R. Barlow d139a11c16 Weave: periodically save to prevent indefinite growth of open file list 2018-05-10 15:08:57 -07:00
James R. Barlow aef043db0b Revise parameter validation for output-type, pdf-renderer, lang 2018-05-10 14:53:22 -07:00
James R. Barlow b8f3ead541 Remove tesseract renderer entirely
Grafting lets us work with older Tesseract versions as if they could use
sandwich, so there is no point in keeping it. It's been deprecated for a
long time now anyway.
2018-05-10 14:06:13 -07:00
James R. Barlow e0bb898f29 Remove hocr debug renderer (-g)
The fact that this produces additional pages makes it a maintenance
burden. hocr can be debugged using hocrtransform.
2018-05-10 13:48:39 -07:00
James R. Barlow 45336c7c28 textareas: filter out images 2018-05-10 01:17:28 -07:00
James R. Barlow 20aabb2e83 When deciding if there is a text on a page, ignore the margins
Margins may include watermarks or digital stamps on otherwise
text-free pages.
2018-05-10 01:16:11 -07:00
James R. Barlow 1539e24d61 Ignore masks when deciding what color to rasterize at 2018-05-10 00:49:36 -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 da80d3f354 Add unconditional (for now) whiteout of text areas 2018-05-07 17:37:46 -07:00
James R. Barlow 001c8d7678 Upgrade PyMuPDF version 2018-05-07 16:24:26 -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
James R. Barlow 9226f8a5d1 Trap PDF/A-3 errors on old Ghostscript 2018-05-04 15:29:43 -07:00
James R. Barlow 5c8a007f3e Fix failure to prevent use of Ghostscript on /UserUnit files 2018-05-04 13:34:34 -07:00
James R. Barlow b3ad3e297d v6.2.0 fixes 2018-05-03 17:04:23 -07:00
James R. Barlow d607553e48 v6.2.0 Release notes 2018-05-03 16:47:21 -07:00
James R. Barlow 7cf83c77ca Merge branch 'feature/pdfa3' 2018-05-03 16:45:57 -07:00
James R. Barlow 8a9f174f63 Fix XMP validation issue with /CreationDate
Related to previous validation issue. If the /CreationDate had no
timezone, Ghostscript also creates invalid metadata. Work around this.
Also fix up PDF date decoding, and transcode dates to standardize them.
2018-05-03 16:30:20 -07:00
James R. Barlow 98a0786c32 Add 18.04 update procedure 2018-05-03 13:55:16 -07:00
James R. Barlow df1129724c Update Dockerfile for Ubuntu 18.04 2018-05-03 01:27:13 -07:00
James R. Barlow 423cef08bf Handle procset properly 2018-05-02 14:48:02 -07:00
James R. Barlow 04580accb4 Document aliasing of tesseract renderer 2018-05-02 14:47:38 -07:00
James R. Barlow 6376f77b8c Refactor, remove trigonometry 2018-05-02 12:30:34 -07:00
James R. Barlow e27e614ed9 Fixed rotation hard case 2018-05-02 01:32:11 -07:00
James R. Barlow b0c04704a1 Fixed all but one rotation case 2018-05-02 01:24:21 -07:00
James R. Barlow 6bb6bf8323 Fix correction angle used from wrong page 2018-05-02 01:00:30 -07:00
James R. Barlow e22fe8aefc Silence debug messages 2018-05-01 23:51:54 -07:00
James R. Barlow 76276f61e5 Split out rotation related tests 2018-05-01 23:51:35 -07:00
James R. Barlow bfd26e6ec6 Tests: confirm OCR layer copied 2018-05-01 23:16:41 -07:00
James R. Barlow d787e1ea0f ghostscript.py not saved in last commit
Given importance of last one, confirmed that when the file is saved all tests pass too.
Passing is invariant with this change.
2018-05-01 22:59:22 -07:00
James R. Barlow b5d7e9cbb0 Fix all issues with rotations
All tests now pass
2018-05-01 22:50:20 -07:00
James R. Barlow f3b6d9dcdf Fix a comment about Tesseract behavior in certain versions 2018-05-01 21:31:09 -07:00
James R. Barlow a9abe13185 Remove the old tesseract pdf_renderer 2018-05-01 17:31:34 -07:00
James R. Barlow 6b315e8315 Add ability to disable cache 2018-05-01 15:52:00 -07:00
James R. Barlow 37677de884 Fix regressions: pdfa.ps not used, PDF/A failures, handling of text layers with no font 2018-05-01 15:51:46 -07:00
James R. Barlow c7387de325 Fix auto rotate 2018-05-01 15:18:28 -07:00
James R. Barlow 2495b1e038 Refactor find font, get test cases working again 2018-05-01 14:48:41 -07:00
James R. Barlow 073ee52ce7 Use hocr and weave; eliminate old combine layers and merge pages 2018-05-01 14:21:53 -07:00
James R. Barlow 54150a14e9 Further elimination of tesseract renderer special casing
We don't need to keep a "skip page" around anymore since
skipping means just not grafting on the text layer.
2018-05-01 13:36:20 -07:00
James R. Barlow 88ff091cce Unify tesseract and sandwich renderer paths
Since the new weaving method copies the font and content
stream from the Tesseract PDF, it doesn't matter if Tesseract
happens to have an image or not.
If Tesseract is text-only capable we use that feature for efficiency,
but ignore the image either way.
2018-05-01 13:24:20 -07:00
James R. Barlow e87a5776f1 Remove now-unnecessary code to rotate pages
Track only the decision to change rotation.
2018-05-01 13:01:25 -07:00
James R. Barlow 0806ce6406 Fix rotation for unsplit (modulo --rotate-pages) 2018-04-30 20:58:42 -07:00
James R. Barlow 6409894a71 feature/unsplit-try-imagerotate 2018-04-30 20:48:59 -07:00
James R. Barlow e7286f6129 Unsplit now works with multipage, --force-ocr 2018-04-30 14:46:20 -07:00
James R. Barlow 2ab94b3151 unsplit: it's alive
First successful file output.
2018-04-28 01:57:41 -07:00
James R. Barlow 7ee90890ec Add copying of essential information from Tesseract textonly 2018-04-27 23:19:08 -07:00
James R. Barlow 383e726d65 Expand size growth reasons to other arguments that trigger transcoding 2018-04-27 19:34:57 -07:00
James R. Barlow e046f70642 Set OMP_THREAD_LIMIT unconditionally, for pngquant 2018-04-27 19:19:30 -07:00
James R. Barlow 2131ad4670 Fix --remove-background error on PDFs with colormapped images
It's unclear how exactly a
colormapped image gets to this
spot given the tendency of other
image processing tools to flatten
such images, but someone made it happen, so now we make sure
the image is okay.

Closes #262
2018-04-27 17:21:01 -07:00
James R. Barlow 219fe2155b test_pageinfo: remove duplicate import 2018-04-27 17:16:42 -07:00
James R. Barlow 4209034d20 Add gpg key to issue template 2018-04-27 15:51:26 -07:00
James R. Barlow abcae0c2a4 Fix helpers.py again 2018-04-25 22:10:51 -07:00
James R. Barlow 0934905493 Don't suppress error message from config_notfound
Since it showed up in s390x bionic
2018-04-25 21:58:18 -07:00
James R. Barlow 11cd6201d9 helpers: fix missing call to complain()
In practice this is probably unreachable.
2018-04-25 21:57:50 -07:00
James R. Barlow 8d2a917676 Page unsplit, development 2018-04-25 21:56:43 -07:00
James R. Barlow 44b4afa534 Begin conversion from page splititng to page markers 2018-04-23 22:57:50 -07:00
James R. Barlow 775be3933c Cherrypick merge_pages unification 2018-04-20 23:08:15 -07:00
James R. Barlow df87e21c85 Add support for PDF/A-3
No ability to attach files however
2018-04-20 00:06:55 -07:00
Hugoandjbarlow83 d761d80750 Use more standard __version__ rather than PILLOW_VERSION (#257) 2018-04-19 23:35:32 -07:00
James R. Barlow 8052019dde optimize: fix reporting of jbig2 groups 2018-04-19 01:54:44 -07:00
James R. Barlow a3d8950088 optimize: Don't save JPEGs if larger 2018-04-19 01:25:49 -07:00
James R. Barlow 004f5d3bf1 optimize: further improve decodeparms handling 2018-04-18 15:52:25 -07:00
James R. Barlow f5d308a156 optimize: refactor tricky /Filter and /DecodeParms handling 2018-04-18 15:30:21 -07:00
James R. Barlow 3869996758 optimize: jbig2 error 2018-04-18 01:36:38 -07:00
James R. Barlow cdb2107c4e optimize: jbigs2 fix 2018-04-18 01:31:35 -07:00
James R. Barlow 4db2b3413b optimize: more robustness 2018-04-18 01:25:34 -07:00
James R. Barlow b2f31bec79 Make optimize a lot safer 2018-04-18 00:20:06 -07:00
James R. Barlow 78f9f4a266 Be more defensive about accessing 2018-04-18 00:11:39 -07:00
James R. Barlow ad6087c342 optimize: more fixes 2018-04-17 23:58:10 -07:00
James R. Barlow 0d6ef430de optimize: fix "length not defined" 2018-04-17 23:38:00 -07:00
James R. Barlow a5942209e8 optimize: fix error on missing /Filter 2018-04-17 23:27:56 -07:00
James R. Barlow 9a60694cfc optimize: ccitt header fixes
Changed to match TIFF spec's use of unsigned types, eliminated check for
/Columns.

There is some complex behavior for /Width != /Columns and
(/Width, /Columns) mod 8 != 0
that is not described well in the PDF spec.
2018-04-17 23:27:25 -07:00
James R. Barlow 4bf13f4737 optimize: be less chatty 2018-04-17 23:25:41 -07:00
James R. Barlow 9e89b75186 Merge v6.1.5 2018-04-17 22:51:13 -07:00
James R. Barlow 0b10db91be Fix regression: Disable Ghostscript JPEG passthrough entirely 2018-04-17 17:00:24 -07:00
James R. Barlow 1a516b2af9 Fix regression: time stamp test suite failures 2018-04-17 16:59:21 -07:00
James R. Barlow 076363d78e Disable JPEG passthrough for Ghostscript 9.23
Seems to corrupt JPEGs involved in image masks?
2018-04-17 16:31:03 -07:00
James R. Barlow 5fde214290 Update notes for v6.1.5 2018-04-17 15:23:35 -07:00
James R. Barlow a620724d6a Fix PDF/A validation failure due to timezone being omitted from /ModDate 2018-04-17 15:16:48 -07:00
James R. Barlow 640b953ec7 Fix PDF/A validation failure due to timezone being omitted from /ModDate 2018-04-17 14:55:32 -07:00
James R. Barlow a009ca7597 Disable JPEG passthrough for Ghostscript 9.23
Seems to corrupt JPEGs involved in image masks?
2018-04-17 13:54:34 -07:00
James R. Barlow 7368399f8b Clarify license of two test files - https://github.com/jbarlow83/OCRmyPDF/issues/254 2018-04-17 11:56:36 -07:00
James R. Barlow c974aec934 Search for image masks too 2018-04-17 02:06:08 -07:00
James R. Barlow 3033f03f64 Iterate images with pikepdf / fix mono PNG corruption
To work around PNG corruption problem in PyMuPDF for monochrome images,
extract and save monochrome CCITT with synthetic TIFF header.

Works better but currently skips /ImageMask due to qpdf
implementation, which affects many files.
2018-04-17 01:50:37 -07:00
James R. Barlow 72723e0bb5 optimize: be quieter 2018-04-16 18:06:02 -07:00
James R. Barlow 2fb6ab3939 Trap writePNG error 2018-04-16 17:29:10 -07:00
James R. Barlow 25c1c160b8 Move optimize to new file 2018-04-16 17:22:06 -07:00
James R. Barlow 7e92895471 Parallelize pngquant 2018-04-16 12:37:51 -07:00
James R. Barlow d291d48991 PNG palette: parse PDF string from leptonica instead
Seems better to accept whatever leptonica rather than make detailed
assumptions about how it encodes the palette.

Experimented with setting FlateDecode on the palette but it seems to
expand it.
2018-04-16 12:16:13 -07:00
James R. Barlow 0e6b8042b0 Implement PNG palettization 2018-04-16 11:18:52 -07:00
James R. Barlow 34c78a892a Fix list table for tests/resources
[ci skip]
2018-04-15 23:52:19 -07:00
James R. Barlow 9d28879505 Update Ubuntu 14.04 instructions
Closes #252
2018-04-14 17:30:33 -07:00
James R. Barlow 2482296e2b hocr: avoid division by zero
Issue #253 - PDF that produces the error is not available, but if font_width
is zero, chances are the text is nonprinting characters, so suppress it.
2018-04-14 17:24:21 -07:00
James R. Barlow f755fb76ee Try pngquant 2018-04-14 01:37:14 -07:00
James R. Barlow c61b5dcb62 Fix PDF/A validation error from setting /Predictor 0 2018-04-14 01:36:46 -07:00
James R. Barlow fae893b9d9 Reinstate transcoding of PNG 2018-04-14 00:19:24 -07:00
James R. Barlow 10aadefd6a Document return codes 2018-04-14 00:18:58 -07:00
James R. Barlow e75b6280fd Try reading compressed data directly to see if Leptonica will add predictor
Turns out it does not transcode at all in this case, so probably going
to revert to transcoding PNG -> PNG. However if pngquant or similar is
done, this API will still be useful.
2018-04-13 23:55:23 -07:00
James R. Barlow 8c4023165a Release L_COMP_DATA properly 2018-04-13 23:53:41 -07:00
James R. Barlow b7d403f106 Deprecate Pix.read() behaving as an open function 2018-04-13 23:52:46 -07:00
James R. Barlow b069de0caa Use Leptonica to rewrite all PNGs with predictor
Leptonica does a better job of encoding them than Ghostscript, about -15%.
For a test file 450k worth of
PNGs was reduced to 388k with no loss of quality.
2018-04-13 16:35:50 -07:00
James R. Barlow 136da74bfa Update branch with v6.1.4 2018-04-13 12:57:21 -07:00
James R. Barlow 7fc897e6dc Fix NameError 'ghostscript' 2018-04-12 21:24:05 -07:00
James R. Barlow 9b731d63b8 Set Ghostscript -sColorConversionStrategy the way old/new versions expect 2018-04-12 16:28:48 -07:00
James R. Barlow 10aa59f674 v6.1.4 fix test suite regression with Ghostscript 9.23 2018-04-12 15:16:54 -07:00
James R. Barlow 1f7837e7b1 v6.1.4 release notes update 2018-04-12 00:55:45 -07:00
James R. Barlow ba0535e3fb Update test cache to account for unpaper --layout none change 2018-04-12 00:48:21 -07:00
James R. Barlow 49fa7f6b5c tesseract_cache: don't reveal host system file paths in manifest file 2018-04-12 00:47:28 -07:00
James R. Barlow c95db246d4 v6.1.4 merge 2018-04-11 15:58:00 -07:00
James R. Barlow 1ba93371ce docs: Update installation to reflect qpdf 7.0.0 requirement 2018-04-11 15:40:50 -07:00
James R. Barlow fedbbdb575 Travis: compile qpdf from source
The older version in Travis's Ubuntu 14.04 can't pass the test suite anymore.
2018-04-11 15:40:45 -07:00
James R. Barlow 85ebba72bc Fix setup.py syntax 2018-04-10 18:30:48 -07:00
James R. Barlow b6cd436d5d setup: Blacklist Pillow 5.1.0 on macos
https://github.com/python-pillow/Pillow/issues/3068
2018-04-10 18:15:37 -07:00
James R. Barlow ec170c7e1e Travis: use setup.py for requirements, don't override with .txt 2018-04-10 17:52:19 -07:00
James R. Barlow f6399eb90f optimize: use Leptonica to compact JPEGs
Pillow could do it too, but Leptonica is somewhat more PDF aware.
2018-04-10 17:45:05 -07:00
James R. Barlow 77f2448e59 Leptonica: add L_COMP_DATA compressed data manager 2018-04-10 17:44:03 -07:00
James R. Barlow 3d69b46fca Release notes 2018-04-10 15:53:02 -07:00
James R. Barlow 4b6153ad18 Use defusedxml for XML parsing when reading XMP 2018-04-10 14:25:13 -07:00
James R. Barlow 75d37eb103 docs: expand ocr of image usage 2018-04-09 13:06:09 -07:00
James R. Barlow 11b6f77df0 unpaper: close images on error paths 2018-04-09 13:05:12 -07:00
James R. Barlow db8b0319dd get_version: repeat system error messages if the process exists with a signal 2018-04-09 13:04:51 -07:00
James R. Barlow c9dd330766 JBIG2: refactor, don't recompress existing JBIG2 2018-04-09 13:04:10 -07:00
James R. Barlow e40228102c JBIG2: Streams created in this manner are already indirect objects 2018-04-06 17:11:17 -07:00
James R. Barlow 7889c6fb4c Parallelize JBIG2 execution with thread pools 2018-04-06 17:00:23 -07:00
James R. Barlow 6eb1773110 Fix JBIG2Globals included multiple times in output 2018-04-06 17:00:03 -07:00
James R. Barlow 1d25823746 Implement functional, single threaded optimize
Passes verapdf
2018-04-06 15:49:16 -07:00
James R. Barlow d1d4f1e198 Add issue links to release notes 2018-04-06 14:52:40 -07:00
James R. Barlow 709c01c7a1 Regroup three merge steps into a single step
All take the same inputs and deliver similar outputs, so it makes sense.
2018-04-06 01:07:02 -07:00
James R. Barlow 4a341c9034 Merge branch 'master' into feature/jbig2-2018 2018-04-05 21:29:39 -07:00
James R. Barlow be41ff6d54 Update flowchart
[ci skip]
2018-04-05 21:26:37 -07:00
James R. Barlow 1dbb6f1746 Notes on relevant envvars, repology 2018-04-05 02:15:01 -07:00
James R. Barlow 753e6274ab Tell unpaper to use --layout none so it won't blank out multi column text 2018-04-05 02:14:33 -07:00
James R. Barlow 7f462c618b v6.1.3 notes 2018-04-03 00:11:20 -07:00
James R. Barlow d8ac6e28ab Convert monochrome images to JBIG2
Awkwardly using fitz and pikepdf, transcode monochrome to CCITT.
This requires _OCRMYPDF_NO_FITZ=1.

00000x.opt.pdf can be checked for JBIG2 to confirm, but this file is
not passed to the output since it's not all wired up yet.
2018-04-03 00:00:53 -07:00
James R. Barlow a95ffcdc46 Experimental add jbig2
It appears that fitz forces conversion of jbig2 to ccitt no matter what,
so pikepdf will be needed to patch jbig2 images.
2018-04-03 00:00:53 -07:00
James R. Barlow 1b01d45dd2 Warn about Python 3.5 page count issue 2018-04-02 19:29:17 -07:00
James R. Barlow 7a1cd39b21 Fix creation date metadata lost from input
Closes #247
2018-04-02 17:53:39 -07:00
James R. Barlow 1c1fd9616a Don't depend on pytest-xdist in setup.cfg 2018-04-02 11:45:03 -07:00
Sean Whittonandjbarlow83 11e19e4085 remove addopts key from tool:pytest section of setup.cfg (#246)
The '-n' command line argument is not supported by recent pytest.
2018-04-02 14:43:38 -04:00
James R. Barlow 2a43f73228 Update installation.rst, further info on fitz 2018-04-02 11:32:57 -07:00
James R. Barlow b1d1310a75 Dockerfile: use fitz 2018-04-02 11:08:03 -07:00
James R. Barlow 0e7fa78e65 Remove inaccurate statement from setup.py 2018-04-01 13:20:17 -07:00
James R. Barlow 4032570d97 Change docs for fitz/PyMuPDF 2018-04-01 13:19:57 -07:00
James R. Barlow 90644a3017 pipeline: refactoring, use with block for images 2018-03-31 13:26:40 -07:00
James R. Barlow 4f6bffb477 Update copyrights 2018-03-31 11:54:38 -07:00
James R. Barlow 158f902c3b Fixed setup.py syntax error 2018-03-30 14:00:36 -07:00
James R. Barlow 6dc25ddc6e v6.1.2: add license to wheels, depend on defusedxml 2018-03-30 13:22:35 -07:00
James R. Barlow 7f6aaeaecf v6.1.2 2018-03-30 12:39:33 -07:00
James R. Barlow ace439910e Remove PyMuPDF 1.12.4 shim 2018-03-30 12:33:27 -07:00
James R. Barlow 7f038568de Add envvar to ease testing without PyMuPDF 2018-03-30 12:32:48 -07:00
James R. Barlow af777c0b6a Test macos without fitz too 2018-03-30 00:13:09 -07:00
James R. Barlow fc299032a4 v6.1.1 release notes
Better get the last one out
2018-03-30 00:11:52 -07:00
James R. Barlow e0f3f07907 Fix text reported as found on all pages when PyMuPDF is not available 2018-03-30 00:10:53 -07:00
James R. Barlow b36df9cf9e pdfa: codecs.encode -> hexlify (simpler) 2018-03-29 22:17:23 -07:00
James R. Barlow 81c3f780d4 Travis: Should test 3.6 Linux without fitz too 2018-03-28 23:54:43 -07:00
James R. Barlow b51efdd3e3 Travis: don't upload to legacy PyPI anymore, it will stop working soon 2018-03-28 23:40:29 -07:00
James R. Barlow 610b769df9 Update release notes 2018-03-28 23:33:34 -07:00
James R. Barlow 527f4d0101 Workaround fitz not escaping parentheses
Closes #239
2018-03-28 23:23:34 -07:00
James R. Barlow 8d9be43c60 test_bookmarks_preserved won't raise ImportError any more
Due to trapping this in ocrmypdf.lib
2018-03-28 23:22:55 -07:00
James R. Barlow 40ef4f0bbe Add new argument --skip-repair to skip the repair step 2018-03-28 00:54:58 -07:00
James R. Barlow d0271d5049 More debug messages on repair; update notes 2018-03-28 00:39:38 -07:00
James R. Barlow 5becfcf8ea Refactor fitz ImportError trap 2018-03-27 21:38:02 -07:00
James R. Barlow 112e8d6c18 Fix regression: PDF/A broken without fitz 2018-03-27 21:33:10 -07:00
James R. Barlow 1d8d49a01d Add PyMuPDF to preamble 2018-03-27 21:32:38 -07:00
James R. Barlow 5050155685 Add warning for large file size increases 2018-03-27 15:49:16 -07:00
James R. Barlow a9bd494cc0 Merge branch 'optional-fitz' 2018-03-27 13:36:33 -07:00
James R. Barlow 6a4df78bc0 Add _naive_find_text to search for text when fitz is not available 2018-03-27 13:36:17 -07:00
James R. Barlow 530eae3898 Fix test_main missing file_claims_pdfa 2018-03-26 15:33:53 -07:00
James R. Barlow 3e444f6a90 Make fitz optional 2018-03-26 13:22:09 -07:00
James R. Barlow 45dbff6401 Fix table of contents not preserved in PDF/A 2018-03-26 02:23:19 -07:00
James R. Barlow bc56b8e058 Move metadata tests to new test_metadata 2018-03-26 01:49:25 -07:00
James R. Barlow d86e315c48 v6.0.1 start release notes 2018-03-26 01:44:01 -07:00
James R. Barlow 746969207a Remove deprecated --pdf-renderer tess4, which was renamed to sandwich
Should have been cut in v6.0.0
2018-03-26 01:17:22 -07:00
James R. Barlow 1caebaefb5 tesseract: Fix FileExistsError on if output file was created at timeout 2018-03-25 21:38:20 -07:00
James R. Barlow 2d10fdcf0f Fix typo in release notes 2018-03-25 21:37:06 -07:00
James R. Barlow 355ec70a80 Note other web frontends 2018-03-25 21:36:39 -07:00
James R. Barlow a2f499de01 Remove pageinfo.py which release notes said was gone for v6 2018-03-25 12:16:56 -07:00
James R. Barlow f4bca89722 Remove Tesseract 4 message 2018-03-25 12:16:31 -07:00
James R. Barlow 9fbc69df3f v6.0.0 release 2018-03-25 01:34:26 -07:00
James R. Barlow 230d301268 conftest: py3.5 path issue 2018-03-25 00:52:45 -07:00
James R. Barlow 1ce7b02d94 Travis: don't cache tests/cache anymore, you get it with git 2018-03-25 00:52:19 -07:00
James R. Barlow a2d00f5f1d tess cache: fix tess3 error for -psm instead of --psm 2018-03-25 00:43:02 -07:00
James R. Barlow f68eaa3b46 Fix PyMuPDF version for Travis 2018-03-25 00:36:26 -07:00
James R. Barlow 0199ab220e Tweak Manifest and .travis once more
Travis "do_not_include" moving around no longer needed, thankfully.
Manifest needed LICENSE.
2018-03-25 00:19:45 -07:00
James R. Barlow 656045610a Update release notes 2018-03-25 00:17:23 -07:00
James R. Barlow 8c1c61f207 test cache: fix Path + str error 2018-03-25 00:02:03 -07:00
James R. Barlow af085b79dd Move ocrmypdf to src/ocrmypdf 2018-03-24 23:59:08 -07:00
James R. Barlow 77476965ae test cache: use .bin extension, fix .gitignore .gitattributes 2018-03-24 23:54:16 -07:00
James R. Barlow 961c1365f9 Update manifest.in 2018-03-24 23:50:58 -07:00
James R. Barlow ca51514046 Add test cache 2018-03-24 23:50:41 -07:00
James R. Barlow 8975b72a01 Fix test_testonly_pdf generating an output file in pwd 2018-03-24 22:34:35 -07:00
James R. Barlow 874ec6a87f Add missing fixture to test_unpaper 2018-03-24 22:24:14 -07:00
James R. Barlow 909eaeeead spoof: Allow tesseract cache to share cache
Previous incarnation was only suitable for generating a local cache
where the suite was executed repeatedly. Now the cache ignores
differences, so it can be checked into Github and shared.
2018-03-24 22:17:36 -07:00
James R. Barlow c138161fae Tests: more cleanup 2018-03-24 15:35:57 -07:00
James R. Barlow e48590d66c Refactor out unpaper-specific tests 2018-03-24 15:21:44 -07:00
James R. Barlow 5b1c8541fc Review some skipped tests to make sure reasons still valid 2018-03-24 15:13:23 -07:00
James R. Barlow e5e011021b Remove the OCRMYPDF_program environment variables
Really, this was just replicating the functionality of the PATH
environment variable, and users probably do that anyway.
2018-03-24 15:09:08 -07:00
James R. Barlow 11d74dea09 Remove the OCRMYPDF_program environment variables
Really, this was just replicating the functionality of the PATH
environment variable, and users probably do that anyway.
2018-03-24 15:07:02 -07:00
James R. Barlow cbdf9c88c5 Update requirements 2018-03-24 14:03:34 -07:00
James R. Barlow 46601b1350 setup: skip 1.12.4.1 since it does not provide wheels 2018-03-24 02:59:58 -07:00
James R. Barlow 6f1a40b2ca v6.0.0 notes, build machinery changes 2018-03-24 02:52:56 -07:00
James R. Barlow a2b1f54eb2 Update documentation license info 2018-03-24 02:33:24 -07:00
James R. Barlow 6756016572 Add license notice to all files
Source files to GPL3

Exceptions:
-tests/spoof/* to MIT
-hocrtransform.py
-_unicodefun.py

Test resources to CC BY-SA 4.0 except when otherwise noted.

Add GPL license.
2018-03-24 02:33:24 -07:00
James R. Barlow f42123afc3 pipeline: make removal of merge_qpdf more explicit 2018-03-24 02:30:05 -07:00
James R. Barlow 1425ffd274 pipeline: Merge branch 'feature/mumerge' into test
Replaces qpdf page merging
2018-03-24 02:26:01 -07:00
James R. Barlow d700154e0e Fix regressions after --skip-text improvements 2018-03-24 02:24:45 -07:00
James R. Barlow efecf42566 Add PyMuPDF and use to detect text on pages 2018-03-24 02:16:53 -07:00
James R. Barlow 74bdfc07fb mumerge: fix regressions 2018-03-24 01:18:22 -07:00
James R. Barlow 376dfdba1c Fix text/image files not closed in combine_layers 2018-03-23 13:48:37 -07:00
James R. Barlow 3795d6720f Try out pymupdf merging
With garbage collection it reduces waste on the worst case file.
That's nice. 1 MB -> 105 MB -> 1.5 MB.

Indicates really problem is using PyPDF2 to watermark.

Currently hacked into --output-type pdf.
2018-03-23 13:39:16 -07:00
James R. Barlow 537aaf56d7 Remove duplication between page merge functions 2018-03-23 13:38:53 -07:00
James R. Barlow 34d51b5d3d Merge branch 'feature/faster-split' 2018-03-23 13:10:53 -07:00
James R. Barlow dea8fcfb5b Optimize page splitting by multiprocessing
Previously page splitting occurred in a single process because it was
not believed to affect performance much. It turned out to be an expensive
operation.

It now scales better with large page sizes although this has a negative
effect on small files.

Overall time changes as follows:

7 page file, 9.02s -> 9.56s
731 page file, 213s -> 97s

WITH --tesseract-timeout 0 --output-type pdf --skip-text

i.e. you don't get a 2.2x speed gain when OCR is available.

Squashed a commit to ix test suite failure on --rotate-pages
Squashed a commit to remove debug code
2018-03-23 13:07:51 -07:00
James R. Barlow 4f1f3b9b51 Move available_cpu_count to helpers 2018-03-23 13:07:51 -07:00
James R. Barlow dfeb8812ad Document some instances of 0 vs 1-based page numbering, import cleanup 2018-03-23 13:07:31 -07:00
James R. Barlow 63e2b4273a Travis: avoid using set -e since it interferes with Travis
https://github.com/travis-ci/docs-travis-ci-com/issues/1672
2018-03-23 12:49:12 -07:00
James R. Barlow 5790dbc085 Merge commit '9e2105e08d5fc765dbf636d108809bb66ab562a5' 2018-03-20 18:15:57 -07:00
jbarlow83andGitHub 9e2105e08d Update readme shields
Drop Docker Hub for now, add homebrew
2018-03-20 17:16:28 -07:00
James R. Barlow 22582bbd1c Travis: don't trigger Docker Hub anymore
Docker Cloud is set up to build on pushes to master and tagged releases.
Hopefully that will work out.
2018-03-19 21:09:31 -07:00
James R. Barlow e5f27b7a12 Solve text detection issue with PyMuPDF 2018-03-15 22:29:56 -07:00
James R. Barlow e88ec9822b Tweak release notes 2018-03-15 17:09:43 -07:00
James R. Barlow 5ffd2f5c96 Not ending Py3.5 support just yet 2018-03-15 17:06:11 -07:00
James R. Barlow 11fdb4c5d8 Update release notes for v5.7.0 2018-03-15 17:06:04 -07:00
James R. Barlow 319aff6d09 Merge better-hocr 2018-03-15 16:59:59 -07:00
James R. Barlow a614fa3400 hocr: simplify some math expressions and add comments 2018-03-14 17:05:40 -07:00
endolithandjbarlow83 8d691391ac Fix typos in advanced.rst (#228) 2018-03-14 15:54:55 -04:00
James R. Barlow 0089a84c94 hocr: Make interword spaces default and non-optional for hocr
Update documentation to match.
2018-03-13 14:51:47 -07:00
James R. Barlow 90676e1c6a hocr: Remove baseline dashes 2018-03-13 14:45:31 -07:00
James R. Barlow 062901be43 Some cleanup and variable renaming 2018-03-13 14:29:22 -07:00
James R. Barlow b195d79b50 Refactoring 2018-03-13 11:04:34 -07:00
James R. Barlow 6d7ee98721 Force Tesseract 4 to be single threaded
Gives better performance (throughput basis) than the existing solution
and scales better on powerful boxes.
2018-03-13 08:54:52 -07:00
James R. Barlow fc0800ed5d v5.6.3 notes 2018-03-12 03:41:12 -07:00
James R. Barlow f4e3a0e5b2 v5.6.2 notes 2018-03-09 15:37:08 -08:00
James R. Barlow d631c80024 Suppress debug message when merging large files 2018-03-09 11:10:45 -08:00
James R. Barlow f1f0033875 Suppress spurious debug message in --output-type pdf 2018-03-09 11:07:28 -08:00
James R. Barlow 84d120e850 v5.6.1 notes 2018-03-09 08:00:42 -08:00
James R. Barlow 8159cc6b88 Skip one test that fails for qpdf 8.0.[0,1], due to qpdf regression 2018-03-09 07:57:22 -08:00
James R. Barlow 995f8c106b hocr: account for baseline offset to position text more accurately 2018-03-09 07:45:41 -08:00
Jim Barlow 7cc104b138 hocr: account for skewed baseline 2018-03-05 11:16:40 -05:00
James R. Barlow b3a7299a62 hocr: refactor/improve PEP8 a bit 2018-03-05 10:47:36 -05:00
James R. Barlow 0e7a4deaec hocr: add baseline function, hocr doc link 2018-03-05 10:47:36 -05:00
James R. Barlow b4d66650bd hocr: adjust text cursor with relative moves 2018-03-05 10:47:36 -05:00
James R. Barlow 4986afca28 hocr: Refactor use of text object
We don't need to declare the font on each word.

No improvement for removing trailing space or adding \n
2018-03-05 10:47:36 -05:00
James R. Barlow 2b6004a82b hocr: Make words on line use the line height
Seems to improve the behavior and appearance
of selected text a fair bit.
2018-03-05 10:47:36 -05:00
James R. Barlow 04c54a7c31 Suppress spurious debug message in --output-type pdf 2018-03-05 10:47:36 -05:00
James R. Barlow 7ae6c5ae87 Trial merge interword-spaces 2018-03-02 23:47:06 -08:00
James R. Barlow 4046766ca5 Fix Python 3.5 test suite failure on symlinks
Did not account for API difference in pathlib
2018-03-02 16:57:46 -08:00
James R. Barlow 810390df0b pipeline: Refactor duplicate with clause 2018-03-02 16:57:15 -08:00
James R. Barlow de749bc7ae Fix regression - output to stdout broken 2018-03-02 15:35:51 -08:00
James R. Barlow 9965b8800c Move Dockerfiles out of the way 2018-03-02 15:26:41 -08:00
James R. Barlow ab870fddd6 Dockerignore: glob supported now 2018-03-02 15:24:42 -08:00
James R. Barlow a79d6807cf Dockerfiles: remove deprecated MAINTAINER tag 2018-03-02 15:24:28 -08:00
James R. Barlow 8185fb7e43 Migrate Travis CI setup to Brewfile 2018-03-02 15:16:05 -08:00
Tucker Barbourandjbarlow83 4b10929b25 Fix Homebrew python package (#227)
Homebrew removed python3 and python now defaults to version 3. Here we
use `brew upgrade python` to upgrade the pre-installed version of
python to python3.
2018-03-02 14:59:23 -08:00
Tucker Barbour f6c70312c9 Fix Homebrew python package
Homebrew removed python3 and python now defaults to version 3. Here we
use `brew upgrade python` to upgrade the pre-installed version of
python to python3.
2018-03-02 14:26:13 +00:00
Tucker Barbour 9fd9c7a51f Scale BoundingBox and Text elements to account for additional space.
Here we are manually scaling the pt width used for the BoundingBox and
the Text element when manually adding whitespace to account for
limitations of the PDF.js viewer. This fixes an initial regression
noticed when selecting text elements in Chrome and PDFium. The width
of the Text element and BoundBox had not been adjusted for the
additional whitespace so the highlighting was offset slightly.
2018-03-02 11:18:47 +00:00
Charles Forcey 422e619978 Add a note to the documentation about interword-spaces 2018-03-01 13:15:03 -05:00
Tucker BarbourandCharles Forcey e6e34251c6 Add option to explicitly add interword spaces to HOCR pdf-renderer
This commit includes an optional work around for limitations of the
PDF.js viewer described in
https://github.com/jbarlow83/OCRmyPDF/issues/133. Here is explicitly
add an addition space to text elements before drawing them on the PDF
canvas when using the HOCR renderer. This option does not apply to
other pdf renderers in OCRmyPDF and is turned off by default.
2018-03-01 13:15:03 -05:00
James R. Barlow 2d8aad1086 Improve docs 2018-03-01 00:24:38 -08:00
James R. Barlow 74ca736333 Issue #223: improve text of encrypted PDF error message 2018-02-27 15:08:22 -08:00
James R. Barlow 5e4fd8b0b9 compile_leptonica should have shebang 2018-02-24 12:49:36 -08:00
James R. Barlow 8ab8132411 lint: unused variables, wildcard imports 2018-02-24 12:48:52 -08:00
James R. Barlow 6899dd46e4 lint: remove extraneous backslash 2018-02-24 12:42:37 -08:00
James R. Barlow 8ad0697a20 lint: remove duplicate property definition 2018-02-24 12:42:03 -08:00
James R. Barlow b47e5672e6 Remove old test case that no longer works 2018-02-24 12:40:14 -08:00
James R. Barlow 45c7bd9a60 lint: Remove shebangs from non-executable files 2018-02-24 12:38:58 -08:00
James R. Barlow e7bcb95635 Fix pylint errors 2018-02-24 11:59:01 -08:00
James R. Barlow 3de83627a9 Handle output to /dev/null or directory (#219)
Previously we threw an exception if the output name was a directory (only after doing OCR) and would trigger a PermissionError on trying to flip permission bits of /dev/null due to shutil.copyfile implementation. Instead of copying file use shutil.copyfileobj which should also respect umask etc.
2018-02-19 22:15:07 -08:00
James R. Barlow f248576994 Change instructions to point away from private tap 2018-02-19 17:33:58 -08:00
James R. Barlow aac5b6de3b Update autobrew script to match final changes 2018-02-17 00:12:03 -08:00
James R. Barlow 24435f11e0 We are now in homebrew 2018-02-15 17:42:16 -08:00
James R. Barlow a9da839c39 Add vector-only PDF test case 2018-02-08 00:17:35 -08:00
James R. Barlow fa2c0296d6 v5.6.0 release notes, docs 2018-02-07 16:48:04 -08:00
James R. Barlow 1dfc32d7e6 Preserve "text as curves" vector content
Never updated the checking logic to deal with a pure vector file with no text that needs an OCR layer. This is doable, so allow it.
2018-02-07 16:05:48 -08:00
James R. Barlow 4a61beae41 autobrew: use homebrew's built-in test fixture 2018-02-05 11:10:33 -08:00
James R. Barlow bd30587bf1 Update depends_on order 2018-01-29 13:16:07 -08:00
James R. Barlow e0070e3e18 Update Dockerfile to use Ubuntu 17.10 (issue #214) 2018-01-28 15:30:41 -08:00
James R. Barlow 019513696b Ghostscript spoof scripts did not report their --version correctly 2018-01-10 17:08:14 -08:00
James R. Barlow ad7a4476db hugemono.pdf needs --max-image-mpixels to pass with Pillow 5.0 2018-01-10 16:55:18 -08:00
James R. Barlow 4812b20fb2 Fix tesseract_noop.py generating wrong size of output PDF in tests
This caused trouble before with test_deskew
2018-01-10 16:35:31 -08:00
James R. Barlow f5e07c9427 Fix Ghostscript parameter order 2018-01-10 16:33:26 -08:00
James R. Barlow 75dcb90621 Niceties: when environment variable overload is used clarify we're not checking the PATH 2018-01-10 16:33:03 -08:00
James R. Barlow dfc0434cc2 Update requirements to set Pillow to 5.0 2018-01-10 15:45:23 -08:00
James R. Barlow 882fc2257c Add --max-image-mpixels argument to support Pillow 5.0 2018-01-10 15:43:59 -08:00
James R. Barlow 41e83b52fc Document process for redoing OCR 2018-01-10 15:39:58 -08:00
James R. Barlow 47758b4d8f Reactivate autobrew 2018-01-10 15:39:36 -08:00
James R. Barlow 6bf1f970a0 Fix some parameter validation for --output-type pdfa-1 and pdfa-2 2018-01-10 11:50:08 -08:00
James R. Barlow 7d451f101f Detect old versions of Ghostscript and warn about them (#208) 2018-01-10 11:47:39 -08:00
James R. Barlow 7edbfe0e40 Update autobrew template 2018-01-09 12:36:14 -08:00
James R. Barlow 91b42cbfa8 Fix issue in sandwich renderer when skipping OCR on a rotated and deskewed page
If OCR is skipped due to --tesseract-timeout or similar, and the skip page is rotated with /Rotate, and the skip page was deskewed or had other image processing, then the skip page was created with the wrong dimensions causing the output page to be cropped.
2018-01-09 00:17:53 -08:00
James R. Barlow 6907df41b4 Disable autobrew until homebrew accepts the official release 2018-01-08 23:26:58 -08:00
James R. Barlow 2cebd90cbd Fix brew audit --strict warnings 2017-12-09 12:14:28 -08:00
James R. Barlow 376a121aaa Re-enable macos 2017-11-29 15:06:19 -08:00
James R. Barlow da11fd17ee qpdf dummy: needs to return version now 2017-11-29 14:35:37 -08:00
James R. Barlow a40689a0ff tesseract: handle return of bytes properly in error cases 2017-11-29 14:35:26 -08:00
James R. Barlow 44a45fc3fb Add "bad UTF8 output from Tesseract" test 2017-11-29 14:08:07 -08:00
James R. Barlow ec4bb5359a Read tesseract's output as binary to avoid UnicodeDecodeErrors if it messes up 2017-11-29 13:44:40 -08:00
James R. Barlow d2217632df Rename _verify_python3_env 2017-11-29 13:43:18 -08:00
James R. Barlow 2cc044feed Move qpdf complaint to after options checking so that it won't break ocrmypdf --version 2017-11-29 13:42:55 -08:00
James R. Barlow 64fd0cb54f Remove test_qpdf.py only from travis 2017-11-29 12:54:48 -08:00
James R. Barlow c5a1d22e81 That fixed it. Complain about old versions of qpdf now 2017-11-29 12:53:34 -08:00
James R. Barlow a7b307af04 Looks like issue was negzero.pdf with qpdf 5.1.1 on travis, which is why osx passes
Reorganize and see if this is better now
2017-11-29 12:47:09 -08:00
James R. Barlow 3269eba16c Is it negzero.pdf? 2017-11-29 12:02:37 -08:00
James R. Barlow d472860e3b Try to diagnose travis-only failure of qpdf test 2017-11-29 11:41:06 -08:00
James R. Barlow 731c9ea55e Set timeouts on the tests that seem to be stalling on travis (but not elsewhere) 2017-11-27 14:46:10 -08:00
James R. Barlow 0b6af8d965 Clarify hocrtransform license/copyright 2017-11-27 13:41:46 -08:00
James R. Barlow 92ca9e954c Fix test warning/failures, hopefully 2017-11-27 13:41:32 -08:00
James R. Barlow 67aa00e2c5 Remove py3.5 deprecation
It'll be in Debian stable for a long time, so maybe not such a good idea
2017-11-27 01:10:34 -08:00
James R. Barlow a1bf161eb7 Update v5.4.4 release notes 2017-11-27 01:09:35 -08:00
James R. Barlow 56614fcaa4 Add support and tests for handling page count > ulimit - fixes issue #181 2017-11-27 00:32:35 -08:00
James R. Barlow 4d9169e15f Add merge ulimit test case 2017-11-26 23:34:36 -08:00
James R. Barlow 3a167af2c4 Nearly smallest possible PDF-1.3 with all required fields 2017-11-26 23:32:21 -08:00
James R. Barlow 64b9005030 v5.4.4 release notes; mention deprecation of 3.5 2017-11-26 23:08:55 -08:00
James R. Barlow 965de3a235 Test case for issue #200 2017-11-26 22:52:53 -08:00
James R. Barlow 2040ae4856 Fix issue #200, uncommon but valid decimal syntax treated as error
Also replace check_output() calls with run() in qpdf.py
2017-11-26 22:52:43 -08:00
James R. Barlow 31a1f9f5ea pytest-runner should be a setup requirement 2017-11-26 22:51:51 -08:00
James R. Barlow 7eadd82048 Update docker script based on uid issue
issue #180, fix reported by @Atredis76
2017-11-26 15:11:17 -08:00
James R. Barlow 31d0eaac8e Ensure intermediate metadata holder PDF has same version as its input file
While not known to cause problems, the absence of this could confuse parsers
2017-11-24 00:09:13 -08:00
James R. Barlow f45d012635 Update docs: security, pip install --user 2017-11-21 16:50:14 -08:00
James R. Barlow 5a777ee9bc v5.4.3 release notes 2017-11-17 02:28:02 -08:00
James R. Barlow 7bbf6bc7f4 Travis didn't like LANG, use LC_ALL 2017-11-16 20:37:30 -08:00
James R. Barlow 9bfc45702d Update copyright in license 2017-11-16 17:18:29 -08:00
James R. Barlow 40aa82ab41 Check that the locale is sane before allowing OCR to proceed 2017-11-16 17:18:02 -08:00
James R. Barlow 5d2c67c62b Declare in yet another place that this shouldn't run on py2 2017-11-16 14:54:28 -08:00
James R. Barlow a00ef4836d autobrew: declare conflict with mactex
As reported in https://github.com/jbarlow83/homebrew-ocrmypdf/issues/4#issuecomment-345029899
2017-11-16 14:53:42 -08:00
James R. Barlow 3ef766bb93 Remove bare 'except:' 2017-11-01 01:52:13 -07:00
James R. Barlow 44b5a18462 Declare our __version__ properly 2017-11-01 01:49:51 -07:00
James R. Barlow 4df716f0e8 Update requirements.txt 2017-11-01 01:49:35 -07:00
James R. Barlow fcbf34a4d3 Refactor obtaining version from subprocesses
Issue #196 raised the need to deal with linker warnings on --version.
2017-11-01 01:44:36 -07:00
James R. Barlow c7b8b6e18b Fix issue #194 - --sidecar creates blank txt file 2017-10-26 18:15:31 -07:00
James R. Barlow 9fd165bf4b Add 'zlib' to travis recipe 2017-10-26 18:06:12 -07:00
James R. Barlow 2061092ec5 Describe how to stream with Docker
[ci skip]
2017-10-18 12:37:27 -07:00
James R. Barlow 57466a31ba v5.4.1 release notes 2017-10-12 14:04:45 -07:00
James R. Barlow 4213c69bef Try to make travis actually deploy macOS 2017-10-12 12:45:40 -07:00
James R. Barlow cc5578488a Remove workaround for OSD crash and use explicit -l osd
Suggested by amitdo in https://github.com/tesseract-ocr/tesseract/issues/1167
2017-10-12 12:45:18 -07:00
James R. Barlow 4b7135f0e5 Add option to produce PDF/A-1B 2017-10-11 14:32:58 -07:00
James R. Barlow 984df023b1 Fixup manifest 2017-10-11 13:12:14 -07:00
James R. Barlow 6f8682f558 Track .coveragerc for future reference 2017-10-11 13:05:19 -07:00
James R. Barlow 51defa6d66 Import cleanup and some pylint fixes 2017-10-11 12:58:35 -07:00
James R. Barlow 7d73098d6e ghostscript.py: fix missing imports 2017-10-11 12:39:16 -07:00
James R. Barlow c580aa4683 Revert to pre-build stages travis.yml
It's still in beta for a reason apparently
2017-10-11 11:32:47 -07:00
James R. Barlow 47d1199360 Fix travis.yml typo 2017-10-11 11:26:08 -07:00
James R. Barlow 61ab435b31 Try disabling the matrix and osx for build stages 2017-10-11 11:18:32 -07:00
James R. Barlow aa8f534b45 pipeline: fix variable not defined in __init__ 2017-10-11 11:17:33 -07:00
James R. Barlow dc9589aca8 travis: Explicitly set python versions 2017-10-11 00:37:13 -07:00
James R. Barlow dd6eaacc6b More travis build tweaks 2017-10-11 00:28:33 -07:00
James R. Barlow 37dc03eec6 Add workaround for tess4 form feed behavior change 2017-10-10 23:33:54 -07:00
James R. Barlow 6b478172f6 Try using plain tessdata instead of tessdata_best 2017-10-10 20:52:03 -07:00
James R. Barlow c6e73bcfd6 Still failing, did the cp work at all? 2017-10-10 15:15:26 -07:00
James R. Barlow a2d62938ce cp overwrite needs sudo 2017-10-10 14:54:52 -07:00
James R. Barlow d7ae1f3cca travis: Replacement problematic traineddate file 2017-10-10 14:39:05 -07:00
James R. Barlow 70219581c4 Disable tesseract 4 so tests can succeed
tess4 -psm 0 is broken right now
2017-10-10 14:18:02 -07:00
James R. Barlow f70ac9fb89 Workaround travis issues in build stages... maybe 2017-10-10 12:36:50 -07:00
James R. Barlow 235b9fbaf0 Resolve merge conflicts 2017-10-10 12:24:10 -07:00
James R. Barlow ebda7f42db travis: need script for each stage 2017-10-10 12:22:23 -07:00
James R. Barlow 0b04e4b977 Try out travis build matrix 2017-10-10 12:14:50 -07:00
James R. Barlow 9498601a37 Add docs on adding to docker iamge 2017-10-10 12:13:20 -07:00
James R. Barlow ef5d320e06 Ignore .vscode too 2017-10-09 16:19:41 -07:00
James R. Barlow b00c9a562d Remove meaningless version from Dockerfile.polyglot 2017-10-09 16:18:02 -07:00
James R. Barlow 5372656893 Don't say tess4 support is experimental - it's pretty good now 2017-10-09 16:17:42 -07:00
James R. Barlow 571de0e368 Update release notes 2017-10-08 12:41:03 -07:00
James R. Barlow 82cea2fd85 Update batch processing docs to include Synology script 2017-10-08 12:34:36 -07:00
James R. Barlow aed9814345 Use Ubuntu 17.04 instead of 16.10 for Docker image (issue #191)
Due to 16.10 PPAs no longer being generated by alex-p
2017-10-08 12:13:20 -07:00
James R. Barlow 34fc1f5fd7 Add reminder that blank.pdf is not trivial 2017-09-13 01:19:18 -07:00
James R. Barlow 87c2ed8b27 Improve clarity of --pdf-renderer=tesseract deprecation warning 2017-09-12 14:34:53 -07:00
James R. Barlow 1467d118ab Add more leptonica functions 2017-09-06 00:27:02 -07:00
James R. Barlow 922dbe83c3 Update MANIFEST rules 2017-09-02 20:05:57 -07:00
James R. Barlow 6af7d61ee5 Fix CI failure due to spoofers not being updated to Tesseract 3.05 strings 2017-09-01 16:17:26 -07:00
James R. Barlow bafd08391d Update release notes 2017-09-01 12:50:45 -07:00
James R. Barlow 82ebd8ef1a Fix missing error message about trying to use sandwich on old tesseract 2017-09-01 12:50:36 -07:00
James R. Barlow 4ed1aa4d23 Release notes: fix indentation 2017-09-01 12:47:22 -07:00
James R. Barlow d04e43d46d Update copyright info for test files
[ci skip]
2017-09-01 01:00:32 -07:00
James R. Barlow 952f0cca15 Dockerfiles: set LANG=C.UTF-8
Issue #184 to avoid issue with printing UTF-8 text to sidecar
2017-08-30 13:25:54 -07:00
James R. Barlow f6a4d8f1f8 Fix Ubuntu 14.04 install instructions to account for dropping Py3.4 support
[ci skip]
2017-08-27 13:53:36 -07:00
James R. Barlow b3097a2384 Fix broken test case related to language packs 2017-08-24 13:01:02 -07:00
James R. Barlow 6d9ddbe98b v5.3.1 notes 2017-08-24 01:09:19 -07:00
James R. Barlow 9bb42c0229 Wrong error type used for missing language 2017-08-24 01:07:23 -07:00
James R. Barlow bd7226b27a Merge branch 'master' of github.com:jbarlow83/OCRmyPDF 2017-08-23 23:30:19 -07:00
James R. Barlow 5b413e3873 Cookbook: add "don't OCR" examples 2017-08-23 23:29:41 -07:00
James R. Barlow be5831a629 Offer the readme as a long description for new PyPI 2017-08-23 23:29:21 -07:00
jbarlow83andGitHub 084d2bf8e2 More badges 2017-08-23 23:19:29 -07:00
James R. Barlow da79e6bac7 macos: Skip brew audit because it seems to crash ruby on travis 2017-07-27 16:00:41 -07:00
James R. Barlow c4831ac00c v5.3 release notes 2017-07-27 00:11:12 -07:00
James R. Barlow 93a954ef9f Fix missing import for Py3.5 2017-07-26 23:40:01 -07:00
James R. Barlow f7ce8f44e9 Weaken the --user-words test so it will pass on Travis 2017-07-26 21:03:51 -07:00
James R. Barlow 0b012697e5 Whitelist the Latin-1 languages that work with HOCR
Omitted French because the rare 'oe' and 'ÿ' glyphs are not in Latin-1.
Basically steer people away from HOCR renderer but avoid a potential
disruptive behavior change.
2017-07-26 21:03:18 -07:00
James R. Barlow 58e357c992 Report location of attempted output_file that fails to write 2017-07-22 17:49:56 -07:00
James R. Barlow 71fbad83ad Fix py3.5 test 2017-07-21 17:01:06 -07:00
James R. Barlow 52483072dc Add a differential test that checks tesseract uses supplied word list 2017-07-21 16:40:20 -07:00
James R. Barlow 7f0b8621f3 Tests: accept rich path objects without having to str() everything 2017-07-21 16:39:22 -07:00
James R. Barlow cd8db60b06 Crash test all renderers, not just two 2017-07-21 14:10:02 -07:00
James R. Barlow 1aa34f5d2e Make some interfaces accepting of both str-paths and Path objects 2017-07-21 13:28:30 -07:00
James R. Barlow dfa1d88ce9 Fix missing user_words/user_patterns from textonly_pdf case 2017-07-20 17:14:04 -07:00
James R. Barlow dd38519f07 Merge branch 'feature/user-words' into develop
# Conflicts:
#	ocrmypdf/exec/tesseract.py
2017-07-20 16:25:20 -07:00
James R. Barlow 098f5d4f0b docs: remove deprecated example of pdftotext 2017-07-20 16:20:17 -07:00
James R. Barlow ffc685d536 docs: envvar markup 2017-07-20 16:19:57 -07:00
James R. Barlow cd1a99a0de Refactor int(os.path.basename(s)[0:6]) -> page_number(s) 2017-06-26 13:29:40 -07:00
James R. Barlow 48e3b267fc Accept PDFs with whitespace ahead of %PDF marker
Noticed in @aagahi 's fork
2017-06-26 13:17:47 -07:00
James R. Barlow 3a7c3417bb Don’t check tags and branch at the same time as Travis doesn’t get this
Travis is weird
2017-06-13 13:14:34 -07:00
James R. Barlow d792ef7222 Give the ‘auto’ renderer setting more test covfefe 2017-06-13 13:13:58 -07:00
James R. Barlow 2c24f67deb Rename “tess4” renderer to “sandwich” and make it default in Tess 3.05.01
Tesseract 3.05.01 backported the textonly_pdf=1 which allows the use
of this superior PDF renderer prior to 4.00 alpha. This means that
the tess4 name is no longer accurate, so call it a sandwich because of
its merge-preserve characteristic. Preserve the tess4 name. Fix the
documentation and tests to reflect this.

Make it the default, because it’s better. It does not have the issues
the “tesseract” renderer does prior to Tess 3.05.00 with rendering
PDFs that Ghostscript corrupts, and it produces better output without
re-rastering.

Deprecate some old stuff to avoid the test suite growing obscenely
large.
2017-06-13 13:09:12 -07:00
James R. Barlow 9e75e28d0c Homebrew needs x11 to compile Pillow 2017-06-13 11:03:26 -07:00
James R. Barlow 3232643809 Support “textonly PDF” renderer in Tesseract 3.05.01 2017-06-13 10:18:08 -07:00
James R. Barlow f7ee9e90ce Document what is meant by the ocrmypdf “API” 2017-06-13 10:15:11 -07:00
James R. Barlow 47298be132 Remove Python <3.5 test 2017-06-13 10:14:28 -07:00
James R. Barlow a88fa83515 Travis: fix deploy conditions for homebrew autobrew 2017-05-31 02:29:32 -07:00
James R. Barlow 12bfe20385 v5.1 release notes 2017-05-29 14:36:50 -07:00
James R. Barlow 3d2f6f0772 Fix tess4 test using old-style pageinfo API 2017-05-29 13:51:21 -07:00
James R. Barlow 1cb607f64b Merge UserUnit 2017-05-29 13:22:55 -07:00
James R. Barlow d3c54fbbde For —rotate-pages, rasterize preview at half DPI instead of 200 DPI
Ensures that time is not wasted on previews at higher resolution than
the input as was sometimes the case
2017-05-29 13:01:18 -07:00
James R. Barlow 28341b755f Refactor common test fixtures 2017-05-29 12:47:55 -07:00
James R. Barlow 4b5cd420e1 Add new test file 2017-05-29 12:16:08 -07:00
James R. Barlow 1d57bcc99e Fix Ghostscript rasterizing of UserUnit pages and related sizing issues 2017-05-29 12:14:10 -07:00
James R. Barlow facdd13879 Ghostscript: refactor image output resizing 2017-05-29 11:42:27 -07:00
James R. Barlow 6e891f91d3 ghostscript, qpdf: Restore API backward compatibility 2017-05-29 11:13:06 -07:00
James R. Barlow 9b50ede977 Partially solve ghostscript rasterize_pdf producing wrong file size
Kludge. Assumes JPEG for now. Messy.
2017-05-25 01:17:43 -07:00
James R. Barlow 82cf010333 Error out if trying to produce PDF/A >200” due to Ghostscript limitation 2017-05-25 00:07:29 -07:00
James R. Barlow 6ff6c8614f —output-type=pdf now outputs /UserUnit PDFs at the correct size
This currently distorts the output size because Tesseract assumes it
 knows the DPI better than we do.

Does not work for Ghostscript, because it emerges that Ghostscript
honors /UserUnit for rasterizing but not in pdfwrite (resolve/wontfix).

https://bugs.ghostscript.com/show_bug.cgi?id=690781

Ghostscript’s output would need to be patched in a PDF/A safe way for
this to work. Temporary route may be to block Ghostscript if
/UserUnit.
2017-05-24 23:26:07 -07:00
James R. Barlow eb1cd38f6c Add an open helper that is compatible with pathlib 2017-05-24 16:19:15 -07:00
James R. Barlow 148b632b4f Prove multiprocessing works, although it is still racy in some places 2017-05-23 16:32:13 -07:00
James R. Barlow 591e213713 Add more dependencies for autobrew 2017-05-23 13:52:28 -07:00
James R. Barlow 75f2262659 Ensure JobContext stuff is actually tested for IPC consistency 2017-05-19 17:57:07 -07:00
James R. Barlow d9005a1074 pdfinfo: replace most remaining dict-style access 2017-05-19 16:17:36 -07:00
James R. Barlow 3e73fa81bf pageinfo: deprecation warning 2017-05-19 16:17:07 -07:00
James R. Barlow ba6e290231 Restore old pageinfo.py to avoid breaking compatibility 2017-05-19 15:49:23 -07:00
James R. Barlow 08e47117a3 Rename pageinfo to pdfinfo 2017-05-19 15:48:23 -07:00
James R. Barlow 532ef38157 /UserUnit is a scalar, not an array 2017-05-19 14:19:50 -07:00
James R. Barlow 4c09875890 docs: upload unpaper Dropbox link, .rst typo blocking macOS install
[ci skip]
2017-05-19 12:18:09 -07:00
James R. Barlow 0e98139712 Upload to upload.pypi.org/legacy as recommend by PyPA
https://github.com/pypa/warehouse/issues/1996#issuecomment-302784126
2017-05-19 12:06:24 -07:00
James R. Barlow 4c04d802d7 Introduce /UserUnit checking 2017-05-19 12:01:19 -07:00
James R. Barlow b3dc404571 Update unpaper.deb link (fixes #171)
*Shakes fist a Dropbox*
2017-05-19 11:28:45 -07:00
James R. Barlow 8694f8d2eb Replace magic strings colorspace and encoding with Enums 2017-05-18 22:32:27 -07:00
James R. Barlow 263f9b79f4 pageinfo: debug stuff 2017-05-18 21:52:55 -07:00
James R. Barlow 56d2aae963 Refactor from ImageInfo index to attribute accessing 2017-05-18 18:39:14 -07:00
James R. Barlow 127706153d Refactor dictionary based image info to ImageInfo 2017-05-18 18:26:31 -07:00
James R. Barlow caee5b1428 Access PageInfo instance variables instead of dictionary 2017-05-18 17:12:04 -07:00
James R. Barlow 6c12e7e944 Refactor pageinfo dictionary to PageInfo() 2017-05-18 16:53:38 -07:00
James R. Barlow cd04ae6949 Refactor PdfInfo(str(filename)) -> PdfInfo(filename) 2017-05-18 16:43:50 -07:00
James R. Barlow 6a0b68298f Refactor pdf_get_all_pageinfo to PdfInfo 2017-05-18 16:31:18 -07:00
James R. Barlow 0a2f732267 docs: Fix restructured text typos 2017-05-16 23:27:10 -07:00
James R. Barlow 4bade99f27 docs: Remark that someone got bash on Windows working 2017-05-16 23:24:34 -07:00
James R. Barlow 0b048cd24e Join the build badge club 2017-05-16 23:24:05 -07:00
James R. Barlow c69ee63d82 Travis, true is a program, not a keyword 2017-05-15 15:12:14 -07:00
James R. Barlow 744fa104d7 v5.0.1 release notes (anticipating) 2017-05-14 23:59:09 -07:00
James R. Barlow e24ff0fd64 Travis: don’t update the homebrew version because we pushed to testpypi 2017-05-14 23:55:40 -07:00
James R. Barlow 5de107d44c tesseract_cache: update explanatory notes 2017-05-14 23:54:09 -07:00
James R. Barlow 131a5b741d tesseract.py: update canned HOCR template to tess 3.05 output
Seems better to not claim the existence of several entities that don’t
exist as the older one does
2017-05-14 23:40:09 -07:00
James R. Barlow 65b89687a9 ghostscript: fix missing “import sys”, only applicable for an exception 2017-05-14 23:38:52 -07:00
James R. Barlow 048ae40e75 Update copyrights 2017-05-14 23:38:28 -07:00
James R. Barlow 234183ecd2 Fix: Tesseract 3.04 is sensitive to order of configuration commands
“txt hocr” is not acceptable and does not produce expected output .txt
while “hocr text” works fine, so switch the order everywhere.

Should fix #169
2017-05-14 23:27:46 -07:00
James R. Barlow fb067dc97b cookbook: more on improving OCR 2017-05-14 23:16:47 -07:00
James R. Barlow a1fea0ce16 docs: link to OCRmyPDF-web 2017-05-14 23:16:30 -07:00
James R. Barlow e1e9135e93 Test suite: tidy up imports 2017-05-14 23:15:29 -07:00
James R. Barlow aff982036b autobrew: fix brew audit error on double blank line 2017-05-14 23:15:02 -07:00
James R. Barlow d087649eab Remove “null deploy script” since “/usr/bin/true” is equivalent 2017-05-12 15:37:02 -07:00
James R. Barlow 7f3fa46a40 v5.0 release notes 2017-05-12 14:14:28 -07:00
James R. Barlow b1f79e4d97 Disable other use redo_ocr 2017-05-12 13:24:30 -07:00
James R. Barlow 115d6df94f Warn user when —image-dpi is supplied but ignored 2017-05-12 12:09:53 -07:00
James R. Barlow 559af9635f —redo-ocr is not implemented, so disable 2017-05-12 12:08:16 -07:00
James R. Barlow cb06359c0b Turn on Tesseract 4 cache in test suite
Travis is too slow without it, and perhaps it’s overly paranoid to
never cache Tess4. Maybe nuke the cache occasionally to be safe…
2017-05-12 11:42:27 -07:00
James R. Barlow 5e26bb29d9 Update requirements files 2017-05-12 11:41:15 -07:00
James R. Barlow b0e95842b8 Fix Travis CI errors while looking around for Tess4 2017-05-12 00:40:00 -07:00
James R. Barlow 08e678f21f rst: Clean up indentation 2017-05-12 00:12:06 -07:00
James R. Barlow c17817810f Update documentation for 3.03 support removal 2017-05-12 00:08:22 -07:00
James R. Barlow ff5c38b1f7 Tell Travis to download Tesseract 4.00 from a PPA for testing 2017-05-11 23:52:13 -07:00
James R. Barlow 64314c1b82 Insist on Python 3.5 wherever we check for it 2017-05-11 23:51:45 -07:00
James R. Barlow 83230097ae Insist on Tesseract 3.04 wherever we check for it 2017-05-11 23:51:28 -07:00
James R. Barlow 8f91acf956 Remove Tesseract 3.02 and 3.03 compatibility shims 2017-05-11 23:50:52 -07:00
James R. Barlow d211722a2f .gitignore the docs Makefile 2017-05-11 23:28:52 -07:00
James R. Barlow 56e6ed1249 Fix missing import; all tests passing! 2017-05-11 23:28:05 -07:00
James R. Barlow 21982cf1cb baiona_gray remove alpha channel 2017-05-11 23:23:37 -07:00
James R. Barlow edc01408da Update the .png files, again, hopefully without corruption 2017-05-11 23:20:50 -07:00
James R. Barlow aee33c87ed Merge release notes 2017-05-11 23:11:12 -07:00
James R. Barlow 0dae1602c7 Fix missing import PIPE 2017-05-11 23:07:20 -07:00
James R. Barlow d926f07ac1 Stop git from corrupting .pngs
Grrr.
2017-05-11 23:07:06 -07:00
James R. Barlow 96045e98f4 Update develop with master changes
We’re well out of the “trivial updates” zone
2017-05-11 22:54:27 -07:00
James R. Barlow 01b7205e2c Ensure skipped pages are explained in sidecars 2017-05-11 00:43:36 -07:00
James R. Barlow c8a4cbcf17 Fix test suite breakage after sidecar feature added
Forgot to update tesseract spoofers to account for change in tesseract
parameters.  Also the change to outputting multiple files in the collate
steps affected how ruffus passes information into downstream consumers
of those files.
2017-05-11 00:17:24 -07:00
James R. Barlow 16b6442b23 Add changes to __main__.py that should have been in last commit 2017-05-10 17:55:42 -07:00
James R. Barlow 183eafa587 Implement sidecar text files (#126) 2017-05-10 15:22:44 -07:00
James R. Barlow 47a2997538 Reorganize —help text 2017-05-10 12:19:56 -07:00
James R. Barlow 37ebcadfa1 Implement —user-words, —user-patterns 2017-05-09 17:54:56 -07:00
James R. Barlow 74d98216f1 Update documentation for Ghostscript behavior 2017-05-09 17:43:39 -07:00
James R. Barlow 4bdebf573e Tell Travis CI to use multiple cores
Let’s see if this helps the build go faster
2017-05-09 17:24:32 -07:00
James R. Barlow 1606b6a383 Add —quiet (fixes #143), stop using ruffus to partially generate argparser 2017-05-09 17:24:06 -07:00
James R. Barlow 2a61902df5 Merge commit 'c4f01de231d22da5cea02c25aa581a965a37640b' 2017-05-09 16:37:55 -07:00
James R. Barlow 01a1c2b576 Implement —pdfa-image-compression to control Ghostscript’s compression
Fixes #163
2017-05-09 16:37:29 -07:00
Ingo Feinererandjbarlow83 c4f01de231 Fix typo "cutput" -> "output" (#164)
[ci skip]
2017-05-09 16:22:10 -07:00
James R. Barlow 63a4a761dd Revert "v4.5.7 release notes"
The change introduced regressions, so find another way to fix.

This reverts commit d077c03686.

[ci skip]
2017-05-08 14:38:04 -07:00
James R. Barlow d077c03686 v4.5.7 release notes 2017-05-06 22:34:54 -07:00
James R. Barlow c97ea1f2a9 Update high DPI test case to confirm the output image is not downsampled 2017-05-06 22:34:01 -07:00
James R. Barlow fd27df2abb Update documentation to warn that transparency is not tested 2017-05-06 22:33:24 -07:00
James R. Barlow bf04f03c4c Fix corrupt test file “typewriter.png”
This file is not currently used in any tests, but could be, so replace
corrupt version with a useful one.
2017-05-06 22:28:34 -07:00
James R. Barlow 93e802f473 Fix issue #163, color and grayscale images JPEG compressed when not needed 2017-05-06 22:27:25 -07:00
James R. Barlow 1464b9087a Try Travis again with null deploy for OSX 2017-05-01 17:37:59 -07:00
James R. Barlow e8cc8fc879 Add travis null_deploy for osx 2017-05-01 17:24:43 -07:00
James R. Barlow fae2119b1e v4.5.6 release notes 2017-05-01 15:55:02 -07:00
James R. Barlow aa859a4139 Fix #156 - NoneType has no ‘getObject’ for pages with no /Contents 2017-05-01 15:46:15 -07:00
James R. Barlow b9b12e2879 Ensure that ocrmypdf stops and reports an error if Ghostscript fails
Past behavior was to continue and let ruffus puke eventually
2017-05-01 15:44:21 -07:00
James R. Barlow cf643c9f43 Fix argparse.ArgumentError needs two positional args 2017-05-01 14:55:47 -07:00
James R. Barlow 5b1a7880a9 Switch to Travis triggering Docker build to skip race condition with PyPI 2017-04-28 17:18:01 -07:00
James R. Barlow 474b6b0500 v4.5.5 release notes 2017-04-28 15:27:41 -07:00
James R. Barlow 6c8c1d8173 Fix #154: KeyError ‘/Contents’ on blank pages with /Contents record 2017-04-28 15:22:05 -07:00
James R. Barlow 6a91fa637f Squash merge improvements to auto-homebrewing macOS version 2017-04-28 15:17:38 -07:00
Jakub Wilkandjbarlow83 2846fb4e31 Remove misplaced flags from re.sub() call (#153)
The 4th argument of re.sub() is maximum number of substitutions,
not flags.

Moreover, re.MULTILINE affects only semantics of ^ and $,
so it wouldn't have any effect on this regular expression.
2017-04-20 00:38:23 -07:00
James R. Barlow a1033cdc64 osx_brew: show output before letting “brew audit” check it 2017-04-18 23:38:07 -07:00
James R. Barlow 204336e1a5 Move release notes into the rest of documentation 2017-04-18 18:26:31 -07:00
James R. Barlow 8954e6c3b9 v4.5.4 Update release notes 2017-04-18 18:09:15 -07:00
James R. Barlow fee22b6b0b Fix #151, cannot write mode P as JPEG
all(<empty generator>) is True.
2017-04-18 18:08:45 -07:00
James R. Barlow 2b82c31b85 Update documentation 2017-04-18 18:07:19 -07:00
James R. Barlow 9a4813089c autobrew: remove homebrew dependency “zlib”, causes audit failure 2017-04-18 15:28:30 -07:00
James R. Barlow 554fcc8b9d Add test case for #152 2017-04-18 15:20:25 -07:00
Tomandjbarlow83 345256ee99 Fix --skip-big when there are no images in pdf (#152)
* fixed skip-big when there are no images in pdf

* added only_text pdf

* updated only_text pdf

* Delete only_text.pdf
2017-04-18 15:19:24 -07:00
James R. Barlow 58d1042147 v4.5.3 release notes update 2017-03-29 23:50:31 -07:00
James R. Barlow 7b7e3a3e03 Enable lossless reconstruction for —pdf-renderer tess4 where appropriate 2017-03-29 23:44:12 -07:00
James R. Barlow 1e7fbd4202 Fix issues with —pdf-renderer tess4 page skipping
If tess4 renderer needed to skip OCR on a page it would end up
duplicating the page contents onto the new page, rather than creating
a blank OCR layer and placing it on the output page. This created
duplicated content in output files.
2017-03-29 23:43:26 -07:00
James R. Barlow 6e907856f2 Refresh requirements 2017-03-29 18:15:29 -07:00
James R. Barlow 8bc6019172 Begin adding new option to redo ocr 2017-03-29 18:15:20 -07:00
James R. Barlow 059f79242e Phase out subprocess.Popen 2017-03-29 18:15:02 -07:00
James R. Barlow 89599b4812 Drop Python 3.4 compatibility 2017-03-29 15:46:53 -07:00
James R. Barlow a9f4047a97 Remove backward compatible API deprecations from v4.x 2017-03-29 15:42:54 -07:00
James R. Barlow 23227ae763 Deprecate old files 2017-03-29 15:41:52 -07:00
James R. Barlow 4a9e9e9db2 v4.5.3 release notes 2017-03-29 13:19:34 -07:00
James R. Barlow 88ef2718f1 Reject high Unicode metadata at command line
Ghostscript 9.21 does not seem to accept Unicode above U+FFFF. Previous
versions did, but it now exits with a rangecheck error (-15).

Reject on the command line for now. Complete fix would also need to
check input PDF’s metadata.
2017-03-28 11:08:38 -07:00
James R. Barlow e71e8ca3ad Workaround for GS VMerror -25 bug
Avoid inserting docinfo keys that would be translated to null strings,
to avoid running afoul of
https://bugs.ghostscript.com/show_bug.cgi?id=697684
2017-03-28 11:05:43 -07:00
James R. Barlow 45e9257d6e Don’t use filename “pdfa_def.ps” for GS file
At recommendation of Artifex people, don’t use the filename pdfa_def.ps
because if given without a full path Ghostscript will prioritize
searching its own resources over the current directory, and will pick up
 the example file pdfa_def.ps instead.  We used the full path anyway,
but this is a prudent defensive change to avoid odd Ghostscript behavior.
2017-03-28 11:02:46 -07:00
James R. Barlow 2954e72652 Some examples of Ghostscript and Tesseract warnings/errors were not tagged properly 2017-03-28 10:59:53 -07:00
James R. Barlow 199de96cff Ghostcript 9.21 seems to have a regression related to Unicode metadata 2017-03-24 15:15:46 -07:00
James R. Barlow 8ddbe81513 Fix issue #147: unpaper loses DPI information, affects —pdf-renderer tess4 2017-03-24 13:23:03 -07:00
James R. Barlow a3e26e0498 Make —pdf-renderer tess4 more informative, less FUD 2017-03-24 12:58:40 -07:00
James R. Barlow 4ad129d8d8 docs: Don't recommend system-site-packages anymore (Ubuntu 16.04)
Not needed since reportlab 3.4 comes with a wheel, and that was the main difficulty.
2017-03-23 08:13:28 -07:00
James R. Barlow dfb9fa0736 autobrew: missing deps 2017-03-16 00:18:07 -07:00
James R. Barlow eb036898e9 Update documentation with macOS homebrew tap
[ci skip]
2017-03-13 22:25:25 -07:00
James R. Barlow 7c6aa76a2a Fix brew audit failure 2017-03-13 17:58:31 -07:00
James R. Barlow f035cb1088 Fixed issue #142 — closed streams raise an exception on fork attempt 2017-03-13 15:52:57 -07:00
James R. Barlow 35162166c5 Fix UnboundLocalError in autobrew.py 2017-03-13 15:51:39 -07:00
James R. Barlow 107f6abcb1 Fix autobrew build issues - missing deps 2017-03-12 17:07:38 -07:00
James R. Barlow 760a939e7d Further autobrew tweaks 2017-03-11 17:05:26 -08:00
James R. Barlow 72660d0dec MacOS skip the one test that needs poppler, to save installing poppler 2017-03-11 17:03:26 -08:00
James R. Barlow 8444a8f211 before_deploy doesn’t run unless something is going to be deployed 2017-03-11 17:03:20 -08:00
James R. Barlow 4a1fec8328 Improvements to macOS test and work on homebrew tap autobrew
Squashed commits:
[3f06c1e] Try setting up homebrew tap autobuilding
[01532f1] Strict mode error in brew
2017-03-11 17:00:54 -08:00
James R. Barlow 42547f6017 Revert "Finalize Dockerfile move; unfortunately not supported by Docker Hub"
Unfortunately because of this issue
https://github.com/docker/hub-feedback/issues/292

Docker Hub effectively automatically changes directory to the location
of Dockerfile, meaning needed files are not in the right places, so
this has to be reverted.

This reverts commit 65c9a07dde.

[ci skip]
2017-03-10 22:49:08 -08:00
James R. Barlow 0ccf564f03 Revert "Move Dockerfiles out of root"
This reverts commit 3d3b3abc1b.
2017-03-10 22:46:03 -08:00
James R. Barlow 65c9a07dde Finalize Dockerfile move; unfortunately not supported by Docker Hub
Unfortunately because of this issue
https://github.com/docker/hub-feedback/issues/292

Docker Hub effectively automatically changes directory to the location
of Dockerfile, meaning needed files are not in the right places, so
this has to be reverted.
2017-03-10 22:45:31 -08:00
James R. Barlow 4700a19322 Move pipeline.svg out of root 2017-03-10 17:18:38 -08:00
James R. Barlow 3d3b3abc1b Move Dockerfiles out of root 2017-03-10 17:17:36 -08:00
James R. Barlow 7cd2770a13 Fix issue #137 - proportions of non-square resolution distorted
Distortion mainly affected —force-ocr
2017-02-26 17:13:16 -08:00
James R. Barlow 7b94129d9e v4.5 notes 2017-02-14 13:03:48 -08:00
James R. Barlow d1a0065ef8 Create test case for Form XObjects 2017-02-14 12:51:15 -08:00
James R. Barlow 5a817370fd Warn more strongly about —pdf-renderer tesseract until fix is widely propagated 2017-02-14 11:33:07 -08:00
James R. Barlow ab0a210763 Update dockerfile.tess4 yet again
Installing Tess4 PPA over Tess3 proved too much pain, so sever the link
between this and the jbarlow83/ocrmypdf image, starting each from
scratch. Also the complete set of language packs proves too much - the
build seems likely to fail when trying to install so many.
2017-02-14 11:32:05 -08:00
James R. Barlow 9f800736bc Fix running_in_docker() check failing on newer Docker
This test has to work to ensure spoof/tesseract_cache.py has a writable
directory to put cache into. Otherwise those tests fail.
2017-02-13 02:16:06 -08:00
James R. Barlow c9a83afad6 Improve batch processing examples 2017-02-13 02:14:32 -08:00
James R. Barlow 5e14274f10 pageinfo: learn to extract image information from Form XObjects 2017-02-11 16:48:59 -08:00
James R. Barlow 167470b4bd Re-fix Dockerfile.tess4
[ci skip]
2017-02-10 15:43:49 -08:00
James R. Barlow f06d3c2ec2 Fix tesseract 3.04 on tesseract 4 on image
[skip ci]
2017-02-10 08:38:22 -08:00
James R. Barlow 74c99a8a77 v4.4.2 release notes 2017-02-06 21:56:55 -08:00
James R. Barlow 0e4d312ee2 Adjust Travis deploy to PyPI settings
-only on master branch
-only Python 3.6 build uploads, so the others don’t compete
-don’t upload docs to PyPI
2017-02-06 21:27:59 -08:00
James R. Barlow 589f19559d Rewrite Dockerfiles to use ubuntu 16.10 base system
Debian now has a few disadvantages:
-there is no convenient PPA for Debian tesseract 4.0, but there is for
Ubuntu
-Ubuntu sets locale to UTF-8 automatically removing the need to do this

All three ocrmypdf docker images are now based on a common Ubuntu
16.10 image, derived from the one used to build ocrmypdf-tess4.
-polyglot now differs from -tess4 only by opting into the tess4 PPA.

Both Ubuntu 16.10 and Debian stretch use tesseract 3.04.01 now making
the sharp.ttf patch unnecessary. /etc/apt/sources has been unused for a
while now both have newer Ghostscripts.
2017-02-06 14:39:29 -08:00
James R. Barlow f28bc25dc0 Configure travis to handle deployment to PyPI; also lint .travis.yml 2017-02-06 13:50:53 -08:00
James R. Barlow a0657ad937 Prevent use of —pdf-renderer tess4 on tesseract 3 2017-02-06 13:49:43 -08:00
James R. Barlow 5b8d88af4c Suggest use of aliases to hide docker run 2017-01-30 15:08:02 -08:00
James R. Barlow fa82b50340 Adding missing file Dockerfile.tess4 2017-01-29 18:34:01 -08:00
James R. Barlow 005216bc57 Support ocrmypdf-tess4 2017-01-29 18:26:52 -08:00
James R. Barlow e748fdcf6f v4.4.1 release notes 2017-01-28 22:23:35 -08:00
James R. Barlow 8c17c9918e Add documentation and test cases for —tesseract-config
This parameter has existed for along time but never really got any
attention.
2017-01-28 22:06:51 -08:00
James R. Barlow ea0dd99d0b More documentation updates 2017-01-28 15:35:59 -08:00
James R. Barlow e0cc67afae docs: suggest —oem 1 2017-01-28 14:58:25 -08:00
James R. Barlow 04f9cbe364 Describe how to use tesseract 4.0 while 3.04 is installed 2017-01-27 18:13:59 -08:00
James R. Barlow 99afebd033 tesseract jobs_limit(2)
At least on macOS with my quadcore performance improves with two
tesseracts in parallel (20% gain). Hard to say how this will affect
Linux, but stepping up to 2 jobs seems justifiable.
2017-01-27 18:13:12 -08:00
James R. Barlow a6feacc810 travis: fix ‘pip install’ by moving working code out of the way 2017-01-27 14:33:23 -08:00
James R. Barlow 65e4b1672f cffi: verbose=True 2017-01-27 14:17:13 -08:00
James R. Barlow 46cc0dd190 Revert "Do we need to exclude ocrmypdf.lib?"
This reverts commit 678b9fb603.
2017-01-27 13:51:30 -08:00
James R. Barlow 678b9fb603 Do we need to exclude ocrmypdf.lib? 2017-01-27 13:49:11 -08:00
James R. Barlow 49ab0c1f0b setup.py: cffi is definitely needed in setup_requires 2017-01-27 13:43:47 -08:00
James R. Barlow ab490a7736 Experiment: update *requirements.txt, use more current travis build steps
Perhaps this works around the pip/setup.py asymmetry that broke the
4.4 release.
2017-01-27 13:13:14 -08:00
James R. Barlow e4ce1dae35 setup.py: for some reason, subpackages must be explicitly specified 2017-01-27 00:37:05 -08:00
James R. Barlow 179b812acb Fix readthedocs build error 2017-01-26 23:57:51 -08:00
jbarlow83andGitHub 7f170517ec Note about pytest-helpers-namespace 2017-01-26 23:15:32 -08:00
James R. Barlow 5480da4f04 Additional docs updates for v4.4 2017-01-26 23:02:44 -08:00
James R. Barlow 9a15a4db10 Ensure specified destination is writable before starting pipeline process 2017-01-26 22:08:24 -08:00
James R. Barlow 55aeaec293 Autorotation check: Replace duplicated tests with parameterized test 2017-01-26 18:07:59 -08:00
James R. Barlow f6df1fb40c Fix test suite regression: output files dumped in tests/resources 2017-01-26 18:07:09 -08:00
James R. Barlow b889a89c36 Fix remaining 3.4/3.5 regressions 2017-01-26 17:53:27 -08:00
James R. Barlow 1976dc6f30 Fix issue #121 “pop from empty list” (content stream parsing error) 2017-01-26 17:24:40 -08:00
James R. Barlow e864c65d26 (Hopefully) Fix Path <-> py.path conversion on Py3.4/3.5 2017-01-26 17:19:15 -08:00
James R. Barlow 02fba02d31 Refactor test suite to use fixtures to manage paths 2017-01-26 16:38:59 -08:00
James R. Barlow fb9e7c82f6 Move duplicate test code into common namespace 2017-01-26 13:36:52 -08:00
James R. Barlow 77d31bf646 Add renderers page (missed from previous) 2017-01-26 13:20:44 -08:00
James R. Barlow 29ca799bcf Move pytest.ini into setup.cfg 2017-01-26 12:45:38 -08:00
James R. Barlow 467b7f0163 Update docs for eventual v4.4 release 2017-01-26 12:29:11 -08:00
James R. Barlow bad67c6dc5 Rename ‘tesstop’ to ‘tess4’
There’s no reason text-only PDF shouldn’t become the default for
tesseract 4.
2017-01-26 12:28:51 -08:00
James R. Barlow ac40426971 Implement “tesstop” (tesseract v4 text-only pages - working name) 2017-01-20 17:16:01 -08:00
James R. Barlow 7acfaf6d34 pipeline: rename some of the stages, for clarity 2017-01-20 17:15:00 -08:00
James R. Barlow 99e47c9c04 tesseract: add support for using v4 textonly_pdf feature 2017-01-20 17:06:23 -08:00
James R. Barlow d7904e2251 Travis now has Python 3.6, test against it 2017-01-20 14:26:17 -08:00
James R. Barlow 68aef489de Merge branch 'master' (4.3.5, Python 3.6 support) into develop
# Conflicts:
#	dev_requirements.txt
#	requirements.txt
2017-01-20 14:25:28 -08:00
James R. Barlow 3f9adcd5e0 Document idea for producing companion text files 2017-01-19 16:48:05 -08:00
James R. Barlow 6cc5135d2d Output to stdout: ensure stdout is flushed to prevent truncation errors 2017-01-19 16:41:10 -08:00
James R. Barlow d4c72b371f Forward --oem argument to tesseract 4 2017-01-18 21:37:50 -08:00
James R. Barlow 18b6f05657 Resolve issue #124 - poor performance with Tesseract v4
It seems that Tesseract v4 on a platform with OpenMP working correctly
while perform poorly with ocrmypdf because each will also soak up all
available CPUs. Running N^2 processes/threads on a N-core CPU where
each wants 100% of CPU turns out to be detrimental.

So, we restrict ocrmypdf w/tessv4 to a single Tesseract process at a
time, for now. Alternative may be to limit OpenMP threads if throughput
is higher.
2017-01-18 17:52:12 -08:00
James R. Barlow c42d9baa26 tesseract: for v4, use --psm while keeping -psm for v3
At the moment v4 accepts both but who knows if this will get dropped,
so do as document for each version.
2017-01-18 17:43:47 -08:00
James R. Barlow 6e27ecd2b9 Finalize ‘exec’ migration and make it backward compatibility for now 2017-01-18 17:40:50 -08:00
James R. Barlow 482692396e Add installation instructions for Ubuntu 16.04 2017-01-17 08:17:56 -08:00
James R. Barlow c48acf165a v4.3.5: Python 3.6 compatibility 2017-01-03 00:45:33 -08:00
James R. Barlow 9e004c3ec0 Another attempt at py 3.4/3.5
Revert to exactly what the previous passing build specified.
2017-01-03 00:34:26 -08:00
James R. Barlow 7be4e9c919 fix setuptools-scm for py 3.4, 3.5 2017-01-03 00:25:57 -08:00
James R. Barlow 5ec38a4bed Update requirements files and documentation for Python 3.6 - no code changes 2017-01-03 00:11:34 -08:00
James R. Barlow f246779b8e pdfa: documentation, remove from __future__ 2016-12-12 15:10:10 -08:00
James R. Barlow a7d8cdf061 Don’t copy pageinfo - job manager already provides a copy of real pdfinfo 2016-12-12 15:09:41 -08:00
James R. Barlow 620745c812 pipeline: don’t use qpdf to check page count again
We already know the number of pages at this stage.
2016-12-12 15:09:11 -08:00
James R. Barlow b8767e5ba9 Rename exe -> exec, more Unix-y and suggestive 2016-12-10 15:34:00 -08:00
James R. Barlow d33a50660d Replace most sys.exit() with raising exceptions
Because ruffus doesn’t handle exceptions well I tended to call sys.exit
to make sure we got out of dodge when needed.  However, sys.exit is not
ideal for the Python API this is moving towards, so this introduces
proper exceptions for the various cases that retain suggested error
codes. Only __main__.py should call sys.exit now, everyone else has to
throw an exception.

For now the worker raising a fatal exception is logging messages rather
than passing an exception object with the fatal error message, mainly
because ruffus doesn’t properly marshall the exception object so we
just check “what is the name of the exception class that caused ruffus
to thrown an RethrownJobError”?

Also fixed along the way was the wrong return code being shown for
encrypted PDF checking, and incorrect use of str.find (e.output.find)
in boolean logic (str.find returns -1 on failure to find, which is True).
2016-12-10 15:24:24 -08:00
James R. Barlow 4ee9658e97 Move external program wrappers to ocrmypdf.exe package 2016-12-09 16:54:24 -08:00
James R. Barlow dd1b84e7ba More refactoring - helpers.py 2016-12-09 16:31:08 -08:00
James R. Barlow 4c677e6c47 Extract pipeline out of __main__.py and into pipeline.py
This leaves __main__.py to handle command line arguments while pipeline.py
runs the pipeline - mostly. They are still somewhat intertwined, with
__main__.py doing essential things for pipeline.py, etc., and some
helper functions that could go in their own module.

All tests pass after this major refactor.
2016-12-09 16:17:12 -08:00
James R. Barlow f0f889440b Merge branch 'master' into feature/ooruffus 2016-12-08 16:36:03 -08:00
James R. Barlow cc9ceaeb74 v4.3.4: release notes 2016-12-08 16:34:09 -08:00
James R. Barlow ad2fa8d1d7 Fix MANIFEST for .png 2016-12-08 16:25:04 -08:00
James R. Barlow adc1580742 Help py.test collect output in more cases 2016-12-08 16:21:07 -08:00
James R. Barlow 4d3b44d6df ghostscript: cleanup harmless error message printed for overprint
Redirect stderr->stdout to hopefully make GS output easier to work with
overall, since the previous code didn’t seem to account for mixed used
properly.
2016-12-08 16:19:15 -08:00
James R. Barlow e57aa0eee2 pageinfo: fix “decimal.InvalidOperation: quantize result has too many digits”
And add new test case for this.
2016-12-08 16:06:53 -08:00
James R. Barlow 1ae1d116c7 Make setup.py license internally consistent 2016-12-08 16:06:31 -08:00
James R. Barlow 097a69d07f pageinfo: fix “decimal.InvalidOperation: quantize result has too many digits”
And add new test case for this.
2016-12-08 16:04:14 -08:00
James R. Barlow a81ce87a50 Remove non-reentrant options checking and logging setup 2016-12-05 14:13:36 -08:00
James R. Barlow 88be0d43a0 Make setup.py license internally consistent 2016-12-03 21:37:24 -08:00
James R. Barlow ff16a00a3d Remove test for Pillow JPEG and PNG
As of 3.1.1, our minimum version, these codecs are now required by
default for a successful installation, effectively solving the problem
of Pillow installed without libjpeg/libpng.
2016-12-03 14:25:46 -08:00
James R. Barlow 8982b3e1e2 Update requirements
-update requirements.txt and dev_requirements.txt to more recent version
-setup.py updated to Ubuntu 14.04 rather than 12.04 backports
-request at least Pillow 3.1.1 now (since this makes jpeg/png mandatory)
2016-12-03 14:14:07 -08:00
James R. Barlow be0fa35d14 Merge branch 'master' into feature/ooruffus 2016-12-03 14:02:43 -08:00
James R. Barlow 9f51ed9d01 Finalize v4.3.3 release notes 2016-12-03 00:39:24 -08:00
James R. Barlow 731e6792c7 Add test cases for Ghostscript PDF/A warnings 2016-12-03 00:32:09 -08:00
James R. Barlow c35ec0b4aa ghostscript: more effort at error logging 2016-12-03 00:22:03 -08:00
James R. Barlow 03aaf575dc v4.3.3 release notes, fix more gs 9.20 issues 2016-12-02 16:26:34 -08:00
James R. Barlow 9a060579ba Move work_folder into multiprocessing manager 2016-12-02 01:39:17 -08:00
James R. Barlow d40a5c4f7a Remove all remaining traces of ‘options’ global state from task runners 2016-12-02 01:31:57 -08:00
James R. Barlow 21f7dc3377 Distribute ‘options’ to worker processes via the multiprocessing manager 2016-12-02 01:06:11 -08:00
James R. Barlow 43c13a1ed9 Replace pdfinfo, pdfinfo_lock with multiprocessing manager
Using a context manager to guard the pdfinfo list makes the lock
unnecessary. (Although it was probably unnecessary in the first place
anyway.)
2016-12-01 23:36:30 -08:00
James R. Barlow 6bc3f189e1 Remove “WrappedLogger” - does not do anything useful
Never really investigated the reason why ruffus returns a mutex to go
along with its logger. It seems that the mutex is only needed if one
wanted to make multiple successive calls to a log function and have
them appear appear atomically. It is not needed to protect the logger
proxy because accessing the proxy triggers IPC in the child process
that handles the multiprocessing.Manager() object.

The logging wrapper only logs one line at a time, so the mutex does not
actually protect logging sequence. Cut it.

Also manager.Lock() returns a threading.Lock object so the purpose of it
is actually to help processes share a thread-level lock. It would be
more appropriate to use a semaphore based multiprocessing.Lock.
2016-12-01 15:27:07 -08:00
James R. Barlow 2c5437135c Remove temporary re_symlink logging shim 2016-12-01 00:31:42 -08:00
James R. Barlow 444da02523 Fix mistake made in converting pipeline; incredibly, all tests pass now 2016-12-01 00:30:19 -08:00
James R. Barlow 00e8af2381 Reactivate the pipeline; surprisingly works in quick test 2016-12-01 00:03:03 -08:00
James R. Barlow 401b21864f Convert to object oriented ruffus syntax (does not run)
I experimented with the idea of using asyncio-based processing but
realized that that does not solve the import time binding problem
that is the real issue. Therefore the simpler refactoring is to convert
to ruffus-oo syntax and get things working again.

build_pipeline() is really ugly at the moment. The old syntax had its
advantages.

This test reproduces the complete pipeline graph but does not work
otherwise.
2016-11-30 23:58:26 -08:00
James R. Barlow de939951d4 Record version in debug log 2016-11-29 15:30:50 -08:00
James R. Barlow 7725d16a26 Fix exception on inline stencil masks with no /CS attribute 2016-11-24 22:37:00 -08:00
James R. Barlow 8a74408d83 Add security suggestions 2016-11-21 20:58:31 -08:00
James R. Barlow 3d0dc95a06 Moved venvs 2016-11-21 20:40:22 -08:00
James R. Barlow 04a57a3cc2 OS X -> macOS 2016-11-21 20:40:06 -08:00
James R. Barlow d0c22ce01d v4.3.2 release notes 2016-11-10 23:16:08 -08:00
James R. Barlow 23c95e9660 ghostscript: elide overprinting to fix PDF/A errors in GS 9.20
It looks like GS 9.19 can incorrectly set overprinting for the text layer
even though this makes no sense in PDF/A, or at least someone produced
PDFs that have this after a Tesseract PDF -> GS PDF/A conversion. GS 9.20
complains about this. Instead of aborting, elide the feature.

See
http://git.ghostscript.com/?p=ghostpdl.git;a=commitdiff;h=094d5a1880f1cb9ed320ca9353eb69436e09b594
and
issue #107.

It looks like it is better to elide features and warn about elision rather
than abort with an error.
2016-11-10 14:48:02 -08:00
James R. Barlow eecab9b95d pdfa: fix KeyError on pdfa_dict if document has some xmp metadata but
not exactly what we’re looking for
2016-11-09 05:41:12 -08:00
James R. Barlow 8abc2f113c Merge branch 'develop' 2016-11-07 14:36:50 -08:00
James R. Barlow 949d2ff1c2 v4.3.1 release notes 2016-11-07 14:36:08 -08:00
James R. Barlow 1c8b763d53 test_pageinfo: Remove bits per component test
The behavior of this test will ultimately depend on what version of
img2pdf is installed, since after my patch it will be able to produce
1bpp images.
2016-11-07 14:35:54 -08:00
James R. Barlow bb91393b85 Fix “deskew-rotate” bug.
Turns out this occurred in any case where pdf-renderer hocr was used
and a tesseract timeout or error occurred. We created a replacement
page based on the unrotated page dimensions instead of the input image’s
dimensions.
2016-11-07 14:17:31 -08:00
James R. Barlow cc9c0d819e Add test case for documents that get rotated incorrectly after deskew 2016-11-07 14:15:03 -08:00
James R. Barlow a72b8caf47 Update documentation on other languages, multilingual documents 2016-11-07 14:14:06 -08:00
James R. Barlow fdd9b8b8ce Optimize some of the test resources to reduce file sizes
Mostly by reducing RGB -> monochrome and applying JBIG2 compression
2016-11-07 14:01:23 -08:00
James R. Barlow c096b4ca8c Make debug dump of pageinfo at the end of processing readable 2016-11-04 02:23:02 -07:00
James R. Barlow 427add3008 Add @posttask debug hooks 2016-11-03 18:15:21 -07:00
James R. Barlow c45871700d Fix bug: LeptonicaErrorTrap() leaks file handles 2016-11-03 15:51:27 -07:00
Sean Whittonandjbarlow83 6821e8eeb2 disable mathjax sphinx extension (#103)
Mathjax isn't actually needed for OCRmyPDF's docs, but enabling this
extension causes the browser to download a copy of mathjax.js from
cdn.mathjax.org anyway.

I have to disable this for the offline docs bundled with Debian, but
since you're not using mathjax, it would be nice to have the diff merged
upstream.
2016-11-01 21:56:57 -07:00
James R. Barlow a4f07756a5 tesseract caching: don't transcode tesseract's output, hash source file
For sanity's sake, deal with tesseract streams in binary without
transcoding (via universal_newlines, etc.). The only differences are
printing messages regarding spoofing.

Also hash the source file so that changes to the cache mechanism
invalidate old cache automatically. That is probably too aggressive,
but simple and safer than the previous approach.
2016-10-28 16:44:12 -07:00
James R. Barlow f24fb0e0c5 Obligatory MANIFEST.in repair 2016-10-28 01:28:46 -07:00
James R. Barlow 73b88a0a6f More work on documentation 2016-10-28 01:22:40 -07:00
James R. Barlow c42f39e2d4 Update README to point to ReadTheDocs 2016-10-28 00:33:17 -07:00
James R. Barlow 5e5fe3175f docs: OS X -> macOS branding change 2016-10-28 00:32:57 -07:00
James R. Barlow cab65d1f11 pageinfo: add a python3.4 implementation of isclose() 2016-10-28 00:31:04 -07:00
James R. Barlow 245f05d5f4 docs: allow python setup.py install --force to bypass checks
ReadTheDocs needs this.
2016-10-28 00:07:26 -07:00
James R. Barlow dda751f9e3 Merge branch 'feature/docs' into develop
# Conflicts:
#	ocrmypdf/__main__.py
2016-10-27 23:50:08 -07:00
James R. Barlow 3d37ae988a Update release notes for 4.3 2016-10-27 23:48:12 -07:00
James R. Barlow 717acd9855 Prevent dumping binary PDFs to stdout 2016-10-27 16:20:53 -07:00
James R. Barlow 2e4431cc63 Allow piping output to stdout 2016-10-27 16:14:42 -07:00
James R. Barlow f7387b0859 test_stdin: simplify this test
No need to involve 'cat', just hook the file up to stdin.
2016-10-27 16:01:07 -07:00
James R. Barlow a09f6b8977 Test cases: check that stdout is clear of output
To ensure piping to stdout is possible.
2016-10-27 15:58:24 -07:00
James R. Barlow d63449c214 main: don't print output file location to stdout, use stderr 2016-10-27 15:57:33 -07:00
James R. Barlow a86805f0d9 Remove possibly non-free page from "multipage.pdf" 2016-10-27 15:56:43 -07:00
James R. Barlow 7d2009ccef ghostscript: log errors from stdout 2016-10-27 15:36:20 -07:00
James R. Barlow 18ae5db06d ghostscript: ensure raster resolution is specified in integer units 2016-10-27 15:35:33 -07:00
James R. Barlow 9a1838f102 pageinfo: accept "cm/Do" image drawing without the usual "q/Q" wrapper
Some PDFs omit the traditional q/Q wrapper and alter ctm with a stack
depth of zero, so make our test for stack depth specifically test for
the case where the PDF calls for rendering to an uninitialized ctm.

Probably related to #97.
2016-10-27 15:35:00 -07:00
James R. Barlow e20346032d leptonica: add color testing functions for future experiments 2016-10-27 14:49:49 -07:00
James R. Barlow 693a27d76c leptonica: add iPython display hook and equality test 2016-10-26 14:44:41 -07:00
James R. Barlow 203966d86b leptonica: fix Pillow conversion for 1-bit and 8-bit gray images 2016-10-26 13:10:13 -07:00
James R. Barlow 7eca8508fd Implement new preprocessing feature, background removal 2016-10-14 17:23:34 -07:00
James R. Barlow b85270df1c Merge branch 'master' into develop 2016-10-14 15:56:58 -07:00
James R. Barlow aff597cef4 v4.2.5: update release notes, fix silly typo in pageinfo.py 2016-10-13 13:26:39 -07:00
James R. Barlow 61b05b3dee Fix issue: BitsPerComponent is an optional field, sometimes omitted 2016-10-13 13:15:27 -07:00
Julian Kahnertandjbarlow83 453c4ef602 Update README.rst (#98)
`brew install tesseract` just installed the english language pack not French, German or Spanish
2016-10-12 11:20:58 -07:00
James R. Barlow cf4b04f92d The main 'quick' test should be a file that OCRs to recognizable text 2016-10-07 16:25:34 -07:00
James R. Barlow 06c6999987 Merge commit '07891d994aab92e7a14aebe1ac509aab2d4f170c' 2016-10-07 12:45:56 -07:00
James R. Barlow 013c5a369f Replace redacted file with an OCR-able file 2016-10-07 12:45:22 -07:00
James R. Barlow 07891d994a Replace redacted file with an OCR-able file 2016-10-07 12:44:49 -07:00
James R. Barlow 6baf8668a6 Replace with non-free file milk.pdf with free equivalent 2016-10-06 13:10:28 -07:00
James R. Barlow 4ba2962c56 Comment on non-free files 2016-10-05 16:48:16 -07:00
James R. Barlow 7ad92f5db4 Merge branch 'master' of https://github.com/jbarlow83/OCRmyPDF 2016-10-05 16:39:00 -07:00
James R. Barlow 4dad09cc91 resources/README: replace the other large table with a list table 2016-10-05 16:38:51 -07:00
Sean Whittonandjbarlow83 7b2e0c7a7a also exclude .git in pytest.ini (#94) 2016-09-15 08:56:14 -07:00
Sean Whittonandjbarlow83 7f08f15fc9 pytest skipif for milk.pdf test (#95)
Skip the test if the fair use restricted milk.pdf is not present.
2016-09-15 08:55:31 -07:00
James R. Barlow 825c0f8b2a Note that milk.pdf is non-free, start using list-tables 2016-09-10 14:44:00 -07:00
James R. Barlow dbe880bc41 Update tesseract supported languages 2016-09-09 12:55:07 -07:00
James R. Barlow 2ec516b6ff leptonica: learn a few new tricks
Found some interesting options for background norm.
2016-09-09 12:54:36 -07:00
James R. Barlow 7942a01e50 leptonica: This is not a Py2 module anymore 2016-09-08 20:59:38 -07:00
James R. Barlow df684f9344 Update tesseract supported languages 2016-09-08 15:52:25 -07:00
James R. Barlow ae16e95e42 leptonica: scale should be a tuple for consistency 2016-09-08 11:38:10 -07:00
James R. Barlow 2ac8e8a0cc More doc tweaks, mainly introduction 2016-09-08 11:29:53 -07:00
James R. Barlow 0a0ceda71f Start the documentation 2016-09-06 13:52:40 -07:00
James R. Barlow 220f1ce161 tasks.py: stop tracking this file for now
This helper script is still in development and needs to be changed each
release, which breaks the release.

It shouldn't be in MANIFEST.in at all because it's not part of a
distribution.
2016-09-04 10:55:57 -07:00
James R. Barlow c62a8a97c9 v4.2.4 release notes 2016-09-01 21:33:38 -07:00
James R. Barlow f8a1136979 tasks: show logging info 2016-09-01 21:24:13 -07:00
James R. Barlow 9ca29c787b Update description of masks.pdf to reflect what it actually tests 2016-09-01 21:21:14 -07:00
James R. Barlow 6af748a251 pageinfo: regression - didn't add inline images to list 2016-09-01 15:27:51 -07:00
James R. Barlow 9041867f86 pageinfo: exclude images from DPI calculation if drawn at stack depth 0
More thorough testing showed that Acrobat do not presume that images
fill the page if the CTM is unspecified, as tests/resources/masks.pdf
seems to want.  Instead they treat it literally and draw the image
as 1x1 PDF units or 1/72" square in the bottom left corner of the page.

Seems like the best thing to do is ignore any such images for the purpose
of DPI calculation.  masks.pdf still works out okay because it has
other images.

For more robustness we could consider invalidating any DPI above some
limit, or warning the user about these microdot thumbnails.
2016-09-01 14:23:31 -07:00
James R. Barlow 04099b087c pageinfo: handle stencil masks when stack depth > 0 2016-09-01 14:03:30 -07:00
James R. Barlow 6d6234714c tasks: fix logic error and make magic numbers disappear 2016-09-01 14:03:08 -07:00
James R. Barlow 520be23481 Add release helper script 2016-08-31 20:33:04 -07:00
James R. Barlow 346c3c8dd3 Start tracking development requirements 2016-08-31 20:31:31 -07:00
James R. Barlow bd534c3313 main.py -> __main__.py
Executing a package with python -m packagename will check for
__main__.py inside the package.  In other words main.py should have
always been named __main__.py.

In the unlikely event that someone depends on "import ocrmypdf.main"
being meaningful, main.py continues to exist and replicates the
behavior of __main__.  (It's unlikely because import ocrmypdf.main does
unpythonic ruffus-related things at things import time, essentially
configuring itself to work with sys.argv.  To fix another day.)

This should solve the problem of Debian needing to run test suites
before installation and afterwards for continuous integration without
having to patch either file, as python -m ocrmypdf will follow import
order.  That is, if the current directory contains "ocrmypdf/" (e.g.
staging a new version) then that will be tested, else sys.path will
be checked.
2016-08-31 17:01:42 -07:00
James R. Barlow 2625368aed link: more MANIFEST.in tweaks 2016-08-31 16:28:39 -07:00
James R. Barlow 8ac94879f1 lint: no need to check for DEVNULL; all supported versions have it 2016-08-31 16:28:18 -07:00
James R. Barlow dd8c0f3756 Merge branch 'master' of https://github.com/jbarlow83/OCRmyPDF 2016-08-31 13:19:46 -07:00
James R. Barlow 010f353a5e v4.2.3 release notes 2016-08-31 13:19:27 -07:00
James R. Barlow e0a18edb92 Fix MANIFEST.in, as Python packages require 2016-08-31 13:19:17 -07:00
James R. Barlow c6f2eea058 Reinstate OCRmyPDF.sh with a deprecation warning 2016-08-31 11:57:02 -07:00
James R. Barlow bf89e38c69 Add milk.pdf test case 2016-08-31 11:42:21 -07:00
jbarlow83andGitHub e1f0640d42 Create issue template 2016-08-31 11:26:29 -07:00
James R. Barlow 71b54035ba Bug fix issue #89: trying to perform arithmetic on IndirectObject
TypeError: bad operand type for unary -: 'IndirectObject'
2016-08-31 10:25:58 -07:00
James R. Barlow 325cc0beca Allow test cases to run without installing first
As @spwhitton found:

The test suite needs to call "python3 -m ocrmypdf.main" instead of
just "ocrmypdf" because this /usr/bin/ocrmypdf script has not yet been
generated when dh runs the test suite.

---

Seems reasonable to perform in-place testing independent of installation.

Source:
https://sources.debian.net/src/ocrmypdf/4.2.1%2Bgit.20160824.1.5d67cc7-1/debian/patches/0001-patch-test-suite-executable.patch/
2016-08-26 15:23:26 -07:00
James R. Barlow 1a9f09c4d5 Remove OCRmyPDF.sh and its usage in all test cases 2016-08-26 15:18:38 -07:00
James R. Barlow 4fed4e2af3 tests: don't try to pass Unicode arguments on command line on Linux
Depends on locale being configured properly, and it's not necessary
to be able to do this.
2016-08-26 15:08:56 -07:00
James R. Barlow 74cc2346a5 pytest.ini: apply patch from Debian to exclude .pc dir
https://sources.debian.net/src/ocrmypdf/4.2.1%2Bgit.20160824.1.5d67cc7-1/debian/patches/0003-pytest-exclude-.pc-dir.patch/

From: Sean Whitton <spwhitton@spwhitton.name>
Date: Thu, 12 May 2016 08:21:39 -0700
Subject: pytest exclude .pc dir
Forwarded: not-needed

Patches to the test suite present in .pc confuse pytest.
2016-08-26 15:07:11 -07:00
James R. Barlow cc7e328358 Improve some documentation for tests 2016-08-26 15:04:08 -07:00
James R. Barlow d25397e2b0 Add test case for PDFs with masks and stencil masks 2016-08-26 15:03:27 -07:00
James R. Barlow bc11454e1c Help text: example of shell pipeline with img2pdf 2016-08-25 14:58:25 -07:00
James R. Barlow 2025a096c3 Test case for stdin streaming 2016-08-25 14:46:54 -07:00
James R. Barlow 38fe14b108 Make final PDF/A output message less obtuse 2016-08-25 14:46:40 -07:00
James R. Barlow 1b7b2f3695 v4.2.2 release notes, documentation improvements 2016-08-25 14:46:09 -07:00
James R. Barlow 5d67cc76cc Update 4.2.1 release notes 2016-08-24 14:16:22 -07:00
James R. Barlow 27a3813207 Recover input filename from symlink on error message
The recent commit to accept files from stdin broken the feature of
returning the input filename on an error, returning the temp filename
instead, which is confusing.
2016-08-23 17:38:28 -07:00
James R. Barlow b06e0bfdcd Merge branch 'develop' 2016-08-23 16:03:07 -07:00
James R. Barlow d616f25324 Implement DPI checking for stencil masks 2016-08-23 15:59:34 -07:00
James R. Barlow b03028e31f setup.py -> license is MIT 2016-08-19 10:14:33 -07:00
James R. Barlow e08c42fd3d Tweak pipeline again 2016-08-09 22:40:29 -07:00
James R. Barlow 16901f7134 Accept input from stdin if input filename is '-' 2016-08-09 15:46:24 -07:00
James R. Barlow dffceedd85 Update the pipeline image 2016-08-09 15:45:19 -07:00
James R. Barlow e5541e435c New test to confirm we can emit JBIG2 with appropriate settings 2016-08-03 11:35:48 -07:00
James R. Barlow b969aad67b Tweak release notes 2016-08-03 03:36:45 -07:00
James R. Barlow e70387b1af Add a simple test for image to PDF 2016-08-03 03:35:30 -07:00
James R. Barlow 44f47fba21 PDF/A: handle case of no XMP metadata gracefully 2016-08-03 02:57:25 -07:00
James R. Barlow 02584094a1 Suppress NUL bytes in metadata from input files 2016-08-03 02:47:44 -07:00
James R. Barlow 91d715ac93 Add test cases for --output-type 2016-08-03 02:47:18 -07:00
James R. Barlow 35addb8a33 Complain if Chinese is requested with settings known to not work
Should extend test for other Asian languages
2016-08-03 01:29:12 -07:00
James R. Barlow d32ea8d0dd Remove dead code from qpdf merge + PyPDF2 metadata patching
I tried "qpdf merge + PyPDF2 metadata patching" first. The problem is
that PyPDF2 produces a 1.3 by default and generally I have less
confidence it.

New approach is to stuff the Document Info metadata in the first page
with PyPdf2, cross fingers and use qpdf to merge. It's not quite as
clean and might harm the first page, but it's better than shipping
files produced by PyPDF2.
2016-08-03 01:28:27 -07:00
James R. Barlow 12575d594a Improve PDF/A validity checking at end 2016-08-03 01:26:16 -07:00
James R. Barlow 0746083301 Fix failing test case - unbound local variable in finally block 2016-08-03 01:00:38 -07:00
James R. Barlow 5c99acf6d1 Experimental change to use qpdf to merge files (disables Ghostscript)
All but one tests pass, test_input_file_not_a_pdf

Not sure if PyPDF2 metadata generation will mangle the first page.
2016-08-03 00:56:44 -07:00
James R. Barlow 2b10df7b74 leptonica: note about when it may be safe to drop <1.72 workaround 2016-08-03 00:54:37 -07:00
James R. Barlow ebe68de4ff Functional qpdfmerge with PyPDF2 for DocumentInfo block
Tests mostly passing. For the moment this is the new default.

Although PyPDF2 produces a PDF-1.3 which will be wrong for some contents
and possible should be repaired with qpdf. Again.

Looks like it could work better to merge PyPDF2 and fix everything
with qpdf.
2016-08-02 16:48:13 -07:00
James R. Barlow b17c6a146d Experimental qpdf merging
Does not copy /Catalog metadata, but otherwise functional
2016-08-02 02:19:02 -07:00
James R. Barlow 46d837c866 Clarify trusty/precise stuff 2016-08-02 01:29:33 -07:00
James R. Barlow 24856b61e4 Fix typo in readme 2016-08-02 01:29:22 -07:00
James R. Barlow 8d0c6ff616 pyvenv -> python3 -m venv
Sadly the Python developers are removing this script
2016-08-02 01:27:50 -07:00
James R. Barlow 0b24f971cd ocrmyimage: complain about ICC profiles being presumed 2016-08-02 01:22:36 -07:00
James R. Barlow bc5d3824bd Don't overload --oversample, use --image-dpi instead for images 2016-07-31 02:09:30 -07:00
James R. Barlow 4356983707 Suppress overly long stack traces on traverse_ruffus_exception 2016-07-31 02:06:44 -07:00
James R. Barlow 2414b79ee6 More cleanup of exception related errors 2016-07-31 01:48:13 -07:00
James R. Barlow 968e1546f0 Refactor image file triage 2016-07-31 01:47:57 -07:00
James R. Barlow 48213c9c3f Update release notes and readme 2016-07-29 15:25:16 -07:00
James R. Barlow f385772d21 Refactor "is this an iterable that's not a string?" test 2016-07-29 15:25:02 -07:00
James R. Barlow d257c83520 Most tests were failing at split_pages()
It seems that ruffus sometimes decides to send a ['inputfile.pdf']
instead of a bare string.
2016-07-29 14:59:17 -07:00
James R. Barlow 7b72ffec4f ocrmyimage: better handling of missing/invalid DPI 2016-07-29 14:38:07 -07:00
James R. Barlow 757f6826dc ocrmyimage - Attempt conversion to PDF if input file is not a PDF
First cut.

May have broken ruffus errors again too.
2016-07-29 14:03:19 -07:00
James R. Barlow 5df83a0d30 Travis: use Python 3.5 too 2016-07-29 13:31:40 -07:00
James R. Barlow d70e3d3753 ruffus exceptions: for clarity only, don't iterate strings
It's a good habit to ensure any iterator test is explicit about
allowing or disallowing strings.
2016-07-29 13:31:24 -07:00
James R. Barlow 0dfceedcfb Remove old OCRmyPDF 2.x from release notes; update 4.2 notes 2016-07-29 03:08:59 -07:00
James R. Barlow 2c30f4bfc5 Travis: build partly working on trusty; tweak requirements again
The build is #122
https://travis-ci.org/jbarlow83/OCRmyPDF/builds/148255615

Errors seem to be related to either Ghostscript or leptonica? Maybe
-dSAFER?
2016-07-29 03:08:01 -07:00
James R. Barlow 9e7fb52b47 Travis: add PPA to support unpaper 2016-07-29 01:57:12 -07:00
James R. Barlow bb5fd38e38 Remove additional PPA's and try again 2016-07-29 01:47:56 -07:00
James R. Barlow 7c8cf5cfa2 Try travis-trusty
This removes some backports for packages that Ubuntu trusty offers but
for which Ubuntu precise needed help.
2016-07-29 01:44:57 -07:00
James R. Barlow fef35e4eb2 Fix handling of DPI for rare case of JPEG recompression after deskew/clean
This test is exercised by page 4 of multipage.pdf. If all images are
JPEGs, and one of deskew/clean removes DPI information, make sure that
we can get the right information back and that the DPI stays square.
2016-07-29 01:34:52 -07:00
James R. Barlow 8f77576dc4 Fix non-square image resolution for "hocr" case; use img2pdf 0.2.1
Tesseract renderer not immediately fixable.
2016-07-28 16:43:51 -07:00
James R. Barlow b3fcf24a26 Refactor DPI: fix regressions in test suite
Some called functions are particular about the data format of DPI and
don't like to deal with the Decimal() returned by PyPDF2. Convert to
float and int where needed.
2016-07-28 00:19:32 -07:00
James R. Barlow 16e4d342d2 Bug fix: --force-ocr should still run on pages with no images
Useful for people who want to reprocess text.

This also requires --oversample because DPI is undefined. To be fixed
in next commit.
2016-07-27 15:06:49 -07:00
James R. Barlow 8458a51860 Tighten requirements and dependencies 2016-07-27 14:47:59 -07:00
James R. Barlow 636d1903b3 Ghostscript: do raster output with -dSAFER
-dSAFER does not work when rendering PDF/A, because that needs to load
the ICC file, and -dSAFER prevents access to external files.
2016-07-27 00:54:40 -07:00
jbarlow83andGitHub 514efa36fc Readme: Add table of contents, brew install tesseract --with-language packs 2016-07-24 11:21:46 -07:00
James R. Barlow bd48f40d3d v4.1.4 release notes 2016-07-17 00:35:06 -07:00
James R. Barlow c02dbc809a Merge commit '68cf9cbd87c188823027f9d1bfe9029017e7281f' into develop 2016-07-17 00:29:48 -07:00
James R. Barlow 410111d6fb Bug fix: Monochrome images with ICC treated as full color images
Issue #79.
User submitted PDF with ICC profile attached to the monochrome image
in the input file, which is not common but useful for PDFs that want to
define how light the paper is or how dark the black is. The code was
written to assume unusual images are full color unless it can prove
otherwise. Handle this simple case. Other ICC cases should be tested.
2016-07-17 00:29:32 -07:00
jbarlow83andGitHub 68cf9cbd87 .rst: add code-block markup 2016-07-05 14:03:55 -07:00
jbarlow83andGitHub c9b2540d9d Fix some .rst formatting errors 2016-07-05 13:48:19 -07:00
jbarlow83andGitHub 1bacf35a2c Update license information for encrypted_algo4.pdf 2016-06-24 14:25:15 -07:00
jbarlow83andGitHub 8aef0d9277 Merge pull request #76 from Jmuccigr/patch-2
Adding explicit reference to help
2016-06-24 14:21:23 -07:00
John MuccigrossoandGitHub b2fa8645ba Adding explicit reference to help 2016-06-24 13:44:12 -05:00
James R. Barlow c96823a648 v4.1.3 release notes 2016-06-23 13:47:56 -07:00
James R. Barlow 3807b7d655 Merge branch 'feature/leptfun' into develop 2016-06-23 13:45:35 -07:00
James R. Barlow a45505cf1d Fix order of operations in matrix multiplication
Issue #73. The order of operations happens to not matter for scaling
but does matter for translation. We only need scaling to find the DPI,
so the error was not noticed. Mainly useful to other uses of this
library.
2016-06-23 13:36:23 -07:00
James R. Barlow b4a734fc0d Test case for "algorithm 4" test
Algorithm 4 -> PDF version 1.6
2016-06-23 13:21:26 -07:00
James R. Barlow bbd02926e1 Add helpful error message for PDFs that use algorithm 4 2016-06-23 13:13:17 -07:00
jbarlow83andGitHub 5022ded276 Update Windows directions 2016-06-16 15:15:46 -07:00
James R. Barlow c7612152ef leptonica: pillow interop 2016-06-06 23:55:23 -07:00
James R. Barlow af91642cd1 lept: fix __getstate/__setstate 2016-06-06 23:55:23 -07:00
James R. Barlow 9c66334c38 Leptonica - ortho rotate, background norm 2016-06-06 23:55:23 -07:00
James R. Barlow 8d79b94b84 cpix -> _pix 2016-06-06 23:55:23 -07:00
James R. Barlow d7f60b96c1 More leptonica functions for page manipulation 2016-06-06 23:55:23 -07:00
James R. Barlow b964999427 Update filename references from sRGB_IEC to sRGB 2016-05-10 21:58:04 -07:00
James R. Barlow 3473345ea6 Replace sRGB_IEC with MIT license compatible sRGB
New file is from Debian package icc-profiles-free
2016-05-10 21:48:32 -07:00
James R. Barlow 349ec5c81f Provide more helpful error message if pypdf can't merge pages 2016-04-28 14:02:12 -07:00
James R. Barlow ff78d7c56c v4.1 release notes 2016-04-28 00:46:16 -07:00
James R. Barlow ff092c8629 Fix race condition between these tests when run in parallel 2016-04-28 00:39:15 -07:00
James R. Barlow fe14cb57c0 Fix ruffus exception output
I found this issue in ruffus 2.6.3
https://github.com/bunbun/ruffus/issues/65
also discussed here
https://github.com/bunbun/ruffus/pull/67

ruffus 2.6.3 RethrownJobError don't follow the normal conventions and
so its exception causes problems when they cross process boundaries.
This change carefully examines the various forms of ruffus exception
objects that can appear in 2.6.3 and parses them more carefully. It
also removes any direct posting of the exception to the logger because
this triggers another serializing of the exception object, mutating it
further.
2016-04-28 00:38:50 -07:00
James R. Barlow 507fbc01d5 Refactor _find_page_images 2016-04-27 13:13:43 -07:00
James R. Barlow 325479e5be Fix test failure: inline images with multiple image filters specified 2016-04-27 13:09:41 -07:00
James R. Barlow e926ecb8b2 Fuzzing: check for graphics stack overflow
Very unlikely to occur
2016-04-18 13:08:35 -07:00
James R. Barlow d0cb6c0e92 Replace private hypotenuse formula with hypot() 2016-04-18 13:07:53 -07:00
James R. Barlow 5b7c8cf5d3 Remove check for /ImageMask
/ImageMask means the the image is a stencil mask for a grayscale or
color image. From issue #63 a user has a PDF apparently with only a
stencil mask and no other images. According to PDF spec you're supposed
to use masks to help draw other images rather than draw masks
explicitly, although drawing masks is not forbidden.

In the event that the image mask is higher resolution than the input
 image, the composite image should be drawn at the highest DPI to a
ccurately capture the effect of the mask. It looks like the right to
 do is take masks into account rather than ignore them (a holdover
from earlier, less robust versions), or don't treat it different.
2016-04-14 14:24:33 -07:00
James R. Barlow 40baab32ac Remove dead code "import stuff in testcase" 2016-04-14 14:22:34 -07:00
James R. Barlow e877d37ac8 --rotate-pages: Only apply rotation if we're reasonable confident
Take the threshold from tesseract's default value for -psm 1.
2016-04-14 13:49:44 -07:00
James R. Barlow 5a9f77e438 Merge commit '1605408c23fa1b9252c5d3f10f279b43733b0728' into develop 2016-04-14 13:07:10 -07:00
James R. Barlow 8ddd67d1e2 Check encoding of inline images 2016-03-24 15:11:10 -07:00
jbarlow83 1605408c23 README: add libffi-dev 2016-03-22 23:39:02 -07:00
James R. Barlow 2d3b1ebf6e Simplify DPI calculation with algebraic derivation
Needs testing
2016-03-20 12:35:41 -07:00
James R. Barlow c74eaab7f5 Update license: sRGB ICC 2016-03-20 12:34:34 -07:00
James R. Barlow c21d231388 Merge commit 'a73afc4e769202b916d35dee481d741cf6bb7224' 2016-03-20 12:33:15 -07:00
jbarlow83 a73afc4e76 Merge pull request #59 from spwhitton/apt-get
README: Debian and Ubuntu installation option
2016-03-14 00:19:59 -07:00
Sean Whitton 76c364150d README: Debian and Ubuntu installation option 2016-03-13 23:15:00 -07:00
James R. Barlow 94a3e447cc Add otsu threshold to leptonica 2016-03-12 00:09:20 -08:00
James R. Barlow 12868b461a Travis: install unpaper.deb instead of compiling from source 2016-03-11 21:16:01 -08:00
James R. Barlow 322085933b unpaper: fix check for missing and old versions, add test case 2016-03-10 15:37:09 -08:00
James R. Barlow 3fed94bb79 v4.0.7 2016-03-02 06:27:01 -08:00
James R. Barlow 8c877482bd Fix leptonica initializers 2016-03-02 06:26:25 -08:00
James R. Barlow b17d589e84 Don't set -sOutputICCProfile
Ghostscript dev advised against. It appears that this is for
creating target for a device that colors in a particular format.
2016-03-02 06:25:34 -08:00
James R. Barlow 368252a243 setuptools_scm_git_archive seems suddenly broken 2016-03-01 02:09:45 -08:00
James R. Barlow ccefda1bee v4.0.6 notes 2016-03-01 01:58:32 -08:00
James R. Barlow 3d0e8c9629 Provide our own sRGB profile instead of Ghostscript's 2016-03-01 01:27:40 -08:00
James R. Barlow 313bbbb94c setup_scm_git_archive: add additional files 2016-02-29 12:46:27 -08:00
James R. Barlow 0360f078de get_postscript_icc_path: don't check the same path multiple times 2016-02-29 12:45:58 -08:00
James R. Barlow c8901666c4 Merge branch 'master' of https://github.com/jbarlow83/OCRmyPDF 2016-02-29 00:06:07 -08:00
James R. Barlow 7430006596 Improve install instructions for OS X (unpaper) 2016-02-29 00:05:31 -08:00
James R. Barlow f3e06b2dbd Add bookmarks to file for more testing 2016-02-29 00:05:07 -08:00
jbarlow83 e97df307ff Merge pull request #54 from stweil/master
Replace broken link to c't article by permalink
2016-02-28 07:18:40 -08:00
Stefan Weil 1443354aa2 Replace broken link to c't article by permalink
Update also the 2nd article link to use a permalink, too.

Signed-off-by: Stefan Weil <sw@weilnetz.de>
2016-02-28 13:57:42 +01:00
James R. Barlow 250e68c1cd v4.0.5 release notes 2016-02-27 01:01:38 -08:00
James R. Barlow 6a380ee99c Fix temporary file placed in wrong folder 2016-02-27 00:51:47 -08:00
James R. Barlow 3c90bd96a9 Remove extraneous debug print() messages 2016-02-27 00:50:58 -08:00
James R. Barlow 06a7ceb25a v4.0.4 Updates release notes 2016-02-27 00:22:37 -08:00
James R. Barlow 733a8e7d58 Merge branch 'feature/parsecontent' 2016-02-27 00:19:19 -08:00
James R. Barlow 570bbe9a05 Add comments and remove debugging, improve inline handling
Squashed commits:
[bfff3c9] pageinfo, have a main()
2016-02-27 00:18:36 -08:00
James R. Barlow 5cc3adb39a Add support for inline images 2016-02-27 00:18:36 -08:00
James R. Barlow 3957a0606c Compute image pixel density without performing rectangle intersection (+5 squashed commits)
Squashed commits:
[0e27904] Partially implement DPI calculation with rotation of the image

Fixes test suite
[a64f662] pageinfo: all tests pass
[c5b811a] Fix typos
[cdd2286] Can now find inline images for efficiently
[60dde8d] First cut at implementing intelligent DPI detection based on content stream

Broke many of the test cases
2016-02-27 00:18:36 -08:00
James R. Barlow 11a561dbce v4.0.3 release notes 2016-02-26 01:12:15 -08:00
James R. Barlow dad2198394 Log information about detected page orientations in a summary line 2016-02-26 01:07:59 -08:00
James R. Barlow e40fdc502d Always dump stack trace for unexpected errors 2016-02-26 01:06:59 -08:00
James R. Barlow d446fe5922 Fix "too few characters" reported as error by tesseract -psm 0 2016-02-21 08:53:34 -08:00
James R. Barlow 4ca90c106d Docker: fix blank JPEG2000 PDF issue 2016-02-21 04:24:21 -08:00
James R. Barlow 7c5e58a497 Fix test cases that break in Docker, improve test for running in Docker 2016-02-20 23:47:37 -08:00
James R. Barlow 323b9a5f8e Add other missing files 2016-02-20 05:34:21 -08:00
James R. Barlow cab381a339 Add JPEG 2000 test case 2016-02-20 05:13:19 -08:00
James R. Barlow fe4d4c39cd Merge commit '6f3ac46b1c176d48782347cfa14d9ef6ce773f37' into develop 2016-02-20 04:56:12 -08:00
James R. Barlow ad188d7ae1 Docker: supply openjpeg to address JPXDecode errors 2016-02-20 04:54:55 -08:00
James R. Barlow 8246cc0538 Gracefully recover from tesseract's failure to process very large images
And test cases to check this
2016-02-20 04:53:23 -08:00
James R. Barlow 6f3ac46b1c Gracefully recover from tesseract's failure to process very large images
And test cases to check this
2016-02-20 04:53:02 -08:00
James R. Barlow ac71c3be63 4.0.2rc1 - release notes, add missing file caught by Travis 2016-02-20 03:36:37 -08:00
James R. Barlow ecc0ac9b19 Fix error on --tesseract-timeout timing out 2016-02-20 03:13:23 -08:00
James R. Barlow ea4e6bf67d leptonica: serialization tweaks, memory handling 2016-02-20 02:54:53 -08:00
James R. Barlow 46c204f533 Fix leptonica pickling 2016-02-20 02:35:34 -08:00
James R. Barlow 71fbda8bf6 Adjust page orientation parsing to deal with change in Tess 3.04.01 2016-02-20 01:32:56 -08:00
James R. Barlow 9b79b4a7c8 Leptonica: documentation, helper functions 2016-02-20 01:20:06 -08:00
James R. Barlow c04cc853d7 leptonica: remove special PNM handling
We no longer use PNM as an intermediate format, so there's no need to
handle leptonica's PNM quirks.
2016-02-19 15:13:14 -08:00
James R. Barlow dd41e70ccc leptonica: nit 2016-02-19 15:11:48 -08:00
James R. Barlow 4206e74f42 tests: also check that monochrome correlation correctly detects matches 2016-02-19 14:35:31 -08:00
James R. Barlow 68c3ce56a9 Don't do chmod unless necessarily (breaks py.test on Docker) 2016-02-19 14:09:56 -08:00
James R. Barlow ab0e5fa425 Improve error checking for tesseract -psm 0 (orientation) errors 2016-02-19 03:58:39 -08:00
James R. Barlow f3b0434a87 Improve ability to capture error messages from tesseract on a crash 2016-02-19 03:48:49 -08:00
James R. Barlow aa394440db Just use the PyPI version of ocrmypdf in dockerfile
Apparently setuptools_scm_git_archive is ineffective on hub.docker.com
automatic build, it still can't find a version.
2016-02-17 15:14:23 -08:00
James R. Barlow 3b98a1a04b Fix KeyError on unexpected tess output 2016-02-17 06:05:27 -08:00
James R. Barlow fcb89b0c58 Forgot to save release notes 2016-02-17 01:48:25 -08:00
James R. Barlow ac65d6a03a v4.0: release notes 2016-02-17 01:21:17 -08:00
James R. Barlow 2103f60906 Merge branch 'release/v4.0.0' 2016-02-17 01:13:24 -08:00
James R. Barlow e3c3d848c1 Save Dockerfile comment 2016-02-17 01:11:41 -08:00
James R. Barlow d4ef3411e0 Suppress --pdf-renderer tesseract warning in Docker image
Since the corrected font is provided in the Docker image, there's no
reason to show the warning.
2016-02-17 01:03:20 -08:00
James R. Barlow 71d616e413 Restore Dockerfile on local and probably on automated build as well 2016-02-17 00:13:45 -08:00
James R. Barlow fe651d1bf5 Overwrite Tesseract 3.04 default pdf font with better pdf font 2016-02-16 21:45:44 -08:00
James R. Barlow 582ba8cfad Provide sharp2.ttf for Docker images 2016-02-16 21:45:17 -08:00
James R. Barlow d23291650a Remove duplicate line from documentation 2016-02-16 14:30:15 -08:00
James R. Barlow 812fd745b6 Remove redundant line from resources 2016-02-16 14:29:56 -08:00
James R. Barlow a87aa71d85 Remove old documentation about Pillow not linking jpeg, zlib
As of Pillow 3.0.0 this is fixed, so make Pillow 3 a requirement
2016-02-16 14:29:31 -08:00
James R. Barlow 60b2eb1455 Fix JPEG DPI: Pillow expects dpi=(x,y) 2016-02-16 07:29:20 -08:00
James R. Barlow ab3c1988c1 Work around Leptonica < 1.72 bug that breaks Travis 2016-02-16 07:03:50 -08:00
James R. Barlow ee5223eea8 Travis again: are invalid correlation measurements a use-after-free?
Try explicitly casting the value to a float.
2016-02-16 06:09:48 -08:00
James R. Barlow edd2185268 Fix pytest-runner not understanding 'norecursedirs'
As discussed here
https://github.com/pytest-dev/pytest-runner/issues/7
and sort of
https://github.com/pytest-dev/pytest/issues/567
2016-02-16 05:44:55 -08:00
James R. Barlow 35b1ca2be2 Travis: try replacing non-standard invocation of py.test
It seems the normal thing to wire up python setup.py test to invoke
the test suite rather than py.test. This may be the reason for the
past chain of cffi-related commits.
2016-02-16 05:36:14 -08:00
James R. Barlow 71e493a810 Fix case of JPEG missing DPI field 2016-02-16 05:29:32 -08:00
James R. Barlow 6178e22e7f Travis: force compile leptonica? 2016-02-16 05:24:46 -08:00
James R. Barlow ef0aab060a Make debug output more verbose on failure 2016-02-16 05:17:18 -08:00
James R. Barlow d70ce61cfd Travis: maybe it's just the missing __init__.py? 2016-02-16 05:09:12 -08:00
James R. Barlow 8cd84afac8 Revert "Try moving leptonica build script, playing with wheels a bit"
This reverts commit ec2c6c312b.
2016-02-16 05:04:20 -08:00
James R. Barlow ec2c6c312b Try moving leptonica build script, playing with wheels a bit 2016-02-16 04:05:58 -08:00
James R. Barlow 3946bba318 Too soon, try again 2016-02-16 03:51:08 -08:00
James R. Barlow 2ed0b78a7b Travis: are you creating _leptonica.py? 2016-02-16 03:47:35 -08:00
James R. Barlow ed346d032c Does Travis need explicit install libffi-dev? 2016-02-16 03:41:11 -08:00
James R. Barlow acd645f192 Fix travis syntax error 2016-02-16 02:42:47 -08:00
James R. Barlow 88433e4c34 Fiddle with travis, try to get better debug output
Essentially cffi failed somehow, not clear how
2016-02-16 02:12:14 -08:00
James R. Barlow 1224af1780 Update test resources to address files with unknown source
-Remove Test_Issue_28.pdf (inherited from fritz-hh, source unknown)
-Replace missing_docinfo.pdf (received from user, but it's a printout of
a website; unclear status, so created a new PDF with the same effect)
-Others are okay
2016-02-16 00:28:28 -08:00
James R. Barlow ab13342931 Revise rotation tests in prep for adding a few more 2016-02-15 17:17:43 -08:00
James R. Barlow d7913da484 Test case: remove filename conflict 2016-02-15 16:49:28 -08:00
James R. Barlow c50e3f1329 Complain about older tesseracts that don't have sharp2.ttf installed 2016-02-15 16:43:41 -08:00
James R. Barlow a62f86dbd7 Update release notes 2016-02-15 16:43:14 -08:00
James R. Barlow 33b88b18db Update the notes 2016-02-15 14:03:59 -08:00
James R. Barlow 7c691c21ab Fix image layer rotation for pages with nonzero crop boxes 2016-02-10 17:48:33 -08:00
James R. Barlow 4ec51729d8 Partial fix for images not anchored to (0, 0) 2016-02-10 17:14:48 -08:00
James R. Barlow 07b41e479a Cleaner access to mediabox 2016-02-09 02:19:05 -08:00
James R. Barlow 6510bcad19 DPI information not transferred automatically from PNG to JPEG 2016-02-09 02:18:54 -08:00
James R. Barlow 265d2ce39b Better skewed image 2016-02-08 23:44:46 -08:00
James R. Barlow 1928a64cae Better logging output for autorotation 2016-02-08 23:42:25 -08:00
James R. Barlow 11e575a5a3 leptonica: suppress debug output 2016-02-08 23:41:45 -08:00
James R. Barlow 7fbc0d6460 tesseract: unify logging function 2016-02-08 23:40:36 -08:00
James R. Barlow 1ba8b1aa4b unpaper is lousy at deskewing, so let leptonica do it 2016-02-08 15:26:33 -08:00
James R. Barlow 3569c76c0f Also include cardinal.pdf 2016-02-08 15:23:04 -08:00
James R. Barlow 16c7ac2582 Fix test_deskew for new Leptonica API 2016-02-08 15:20:01 -08:00
James R. Barlow 4ceb59215f Leptonica: classes are better 2016-02-08 15:14:44 -08:00
James R. Barlow 2e6879ee51 Introduce Leptonica class for Pix 2016-02-08 14:52:01 -08:00
James R. Barlow 66fc2e9d7d Add rotate 180 correlation sanity check 2016-02-08 13:10:11 -08:00
James R. Barlow 2c7a6e574f Shorten names of _make_input/output 2016-02-08 12:57:26 -08:00
James R. Barlow 78c3bf5dba Check autorotate using leptonica correlation 2016-02-08 12:55:50 -08:00
James R. Barlow 98c115e3bb Cache wasn't enabled properly for test_autorotate 2016-02-08 12:55:28 -08:00
James R. Barlow 2752bda80b Merge branch 'feature/leptdeskew' into feature/logging
Need leptonica for testing now, I think
# Conflicts:
#	ocrmypdf/tesseract.py
#	requirements.txt
#	setup.py
2016-02-08 12:34:48 -08:00
James R. Barlow 7c0940609a Take a stab at writing test case for autorotate 2016-02-08 12:32:39 -08:00
James R. Barlow d30a879e2d Fix test suite by running select_image_for_pdf unconditionally
The purpose of this change that caused the problem was a minor
optimization for the tesseract renderer path that had it pull an image
from select_image_for_pdf so that it could use a JPEG instead of PNG,
instead of taking it from preprocess_clean where it would only get a PNG
and make large files.
2016-02-08 02:33:03 -08:00
James R. Barlow b907234d5c Update tesseract spoofing to cache orientation and script detection checks
No cache: 269 s
With cache: 144 s

test_oversample[tesseract] now fails, all others good
2016-02-08 02:21:56 -08:00
James R. Barlow b0114c9174 More logging improvements 2016-02-08 01:31:15 -08:00
James R. Barlow d2ba8c501f Restore invisibletext for normal output 2016-02-08 01:14:39 -08:00
James R. Barlow 6a7ed7d359 Make logging output a lot more useful 2016-02-08 00:58:14 -08:00
James R. Barlow 6289afa1a6 Better: custom logging factory to avoid whatever ruffus is doing 2016-02-08 00:18:52 -08:00
James R. Barlow 9bb6fa04cb Return logging to a semblance of normalcy 2016-02-08 00:09:31 -08:00
James R. Barlow afb6f6f5c9 Render preview as .jpg instead of .png
Smaller file size of JPEG seems to help performance, although the
difference is only about 1%.
2016-02-07 15:49:10 -08:00
James R. Barlow 8a69671dbd Suppress debug message 2016-02-07 15:43:57 -08:00
James R. Barlow 178aee4687 Make rotation optional (for now it's off, possibly should be on) 2016-02-07 15:43:45 -08:00
James R. Barlow 8484caddfb Tweak pipeline, allowing --pdf-renderer to use JPEGs instead of PNGs 2016-02-07 15:36:51 -08:00
James R. Barlow 08313316de Cleanup auto-rotation 2016-02-07 15:06:54 -08:00
James R. Barlow 1d0eca5c63 All four rotation directions working 2016-02-07 06:09:01 -08:00
James R. Barlow fe89232a30 Fix autorotate for some lossless cases 2016-02-07 05:59:46 -08:00
James R. Barlow 4b51b521e2 Implement autorotate (provided lossless reconstruction is disabled)
Works for a single page file, probably

Although arguably rotation is not quite lossless, and the two could be
mutually exclusive anyway, so maybe this is it. Did not check in some
debugging changes (lossless=False, text debugging=True)

PyPDF seems to get merging wrong when one of the pages is rotated.
2016-02-07 03:27:33 -08:00
James R. Barlow e9ec458304 tesseract: add command to access OSD values 2016-02-07 03:21:32 -08:00
James R. Barlow 54b0ddd787 ghostscript: don't try to "help" autorotation
It uses text direction alone -- unreliable guide.
2016-02-07 03:20:42 -08:00
jbarlow83 93bec22f9c README: mention polyglot, fix container vs image 2016-02-07 00:32:20 -08:00
James R. Barlow 0dc96442d8 Fix img2pdf usage in test case (to make Travis CI happy again) 2016-02-06 23:41:32 -08:00
James R. Barlow 58f4582517 More Dockerfile repair
I'm not fully happy with this arrangement, as it effectively downloads
OCRmyPDF twice, not to mention the lengthy setup time overall.

Will need to try separate build/run images in the future, but now just
get it working again.
2016-02-06 23:13:16 -08:00
James R. Barlow 2d15c09cca Merge branch 'develop' 2016-02-06 18:18:49 -08:00
James R. Barlow 04cb8865b0 Fetch application from PyPI instead of local
setuptools_scm barfs because it can't find the version, because Docker hub
retrieves the application from Github in a way that omits the necessary
details.

I suppose there is a certain logic to Docker only using the tagged
released versions from PyPI, so go with it.  The other attractive option
is to nix setuptools_scm.
2016-02-06 18:18:30 -08:00
James R. Barlow 6fe32bbaf7 v3.2.1 2016-02-05 16:10:18 -08:00
James R. Barlow 4abb20390d Bump Dockerfile versions 2016-02-05 16:08:26 -08:00
James R. Barlow daa3916430 Fix img2pdf 0.2 usage
All tests pass when forced to rely on img2pdf, so seems okay
2016-02-05 15:13:26 -08:00
James R. Barlow e9b87cefcc Try img2pdf 0.2 2016-02-05 14:38:37 -08:00
James R. Barlow 60593b5ad3 Tighten up package requirements to deal with incompatible img2pdf 0.2 release 2016-02-05 14:37:05 -08:00
James R. Barlow f708b11ea4 Fix Python 2.7 warning 2016-02-05 02:34:49 -08:00
James R. Barlow 7982f58b2e Try tweaking Dockerfile for automated build again 2016-02-05 01:38:59 -08:00
James R. Barlow e805c1908a Minor fix for Dockerfile polyglot 2016-02-05 00:52:27 -08:00
James R. Barlow cb3ba8e973 Merge branch 'release/v3.2' into develop 2016-02-05 00:10:41 -08:00
James R. Barlow 344fc40cbc Merge branch 'release/v3.2' 2016-02-05 00:10:41 -08:00
James R. Barlow 7e5c37137b Merge branch 'develop' into release/v3.2 2016-02-04 23:42:06 -08:00
James R. Barlow 1aae11714b Update release notes for v3.2 2016-02-04 23:41:33 -08:00
James R. Barlow d82f14a7aa Update .gitignore 2016-02-04 18:51:41 -08:00
James R. Barlow 4b65e0b093 Set JPEG output quality to 95 for better transcoding 2016-02-04 18:49:09 -08:00
James R. Barlow 43b0faa830 Bug in tesseract_noop spoof: produced wrong page sizes
Now checks input image to ensure the implied page size of its .hocr file
matches the rest of the PDF.
2016-02-04 18:48:22 -08:00
James R. Barlow 8674c9fb20 Merge commit 'ccfbb54e8c26784e438ba2fcac2179f21e7d857b' into release/v3.2 2016-02-04 17:39:36 -08:00
jbarlow83 ccfbb54e8c Update release notes for v3.2
Fix the notes
2016-02-04 17:37:30 -08:00
James R. Barlow 9893ebf889 Suppress tesseract argument printout 2016-02-04 17:26:36 -08:00
James R. Barlow 303eb3e93a Merge commit 'ca546d70e5bff9e9b115371f7813f3c326822bd8' into release/v3.2 2016-02-04 17:25:56 -08:00
jbarlow83 ca546d70e5 Merge pull request #45 from spwhitton/hocrtransform-shebang-fix
fix shebang in hocrtransform.py
2016-02-04 17:21:33 -08:00
Sean Whitton 6a5ea2d64a fix shebang in hocrtransform.py 2016-02-03 17:48:35 -07:00
James R. Barlow ec3d92ad8e Reorg gitignore 2016-01-30 15:28:24 -08:00
James R. Barlow 66a095d7de Improve organization of CFFI setup 2016-01-30 15:19:40 -08:00
James R. Barlow 411981efbc Experiment with CFFI instead of ctypes 2016-01-30 15:06:25 -08:00
James R. Barlow 350ad5210e Leptonica: convert to CFFI 2016-01-20 15:03:07 -08:00
James R. Barlow f3b588764e Suppress tesseract argument printout 2016-01-20 15:02:48 -08:00
James R. Barlow b49f5a7d77 Support optionally using leptonica to deskew
unpaper doesn't seem to be good at deskewing. It fails on test case
with a lot of italics. I think it also struggles on pages with a lot
of whitespace. Leptonica continues to shine here.

However, this is only a first crack at Leptonica. The leptonica module
should be redone to use cffi (more extensible).

Also considering the possibility of making all Lept calls in a forked
process to insulate the calling process from C code crashes and the
messy redirect of stdout/stderr to read Leptonica's errors.

I don't think the redirect is a huge problem as long as multiprocesses
rather than multithreads are used. The ruffus child process that is
handling a page is single threaded and will not be affected by the
redirection. It just feels dirty. The main reason to consider a child
process is crash isolation.
2016-01-19 17:43:40 -08:00
James R. Barlow bacbcba58a Merge branch 'release/v3.2-rc1' 2016-01-19 16:58:37 -08:00
James R. Barlow 52e8aa434f Update release notes for v3.2-rc1 2016-01-19 16:49:49 -08:00
James R. Barlow 37c508f3f8 Better versioning: no silly version files, but wrong ver in development
Small price to pay.
2016-01-19 16:07:52 -08:00
James R. Barlow 26e36422cc More fiddling with version 2016-01-19 15:07:21 -08:00
James R. Barlow f82cb002bc Try automatic versioning with setuptools_scm 2016-01-19 13:27:18 -08:00
James R. Barlow c1eb047a4b Fix name of pdfa_def.ps
Used to include a copy of the parent dir's name.
2016-01-19 13:11:03 -08:00
James R. Barlow 626ca18f5c Remove stale comment 2016-01-19 13:02:35 -08:00
James R. Barlow 9058dedfbe New tests for ccitt, jbig2 encodings 2016-01-19 13:01:56 -08:00
James R. Barlow a0952bfca3 Optimize: use img2pdf stream instead of repeated copies 2016-01-18 20:24:46 -08:00
James R. Barlow 354e61946e Use os.makedirs for test output directories
Broke Travis
2016-01-16 02:47:56 -08:00
James R. Barlow fd6d1d748a Merge branch 'feature/pypdf-page-merge' into develop 2016-01-16 02:33:23 -08:00
James R. Barlow 360acd1e2c Adjust test_oversample test case
Add -f to force generation of the background image at the desired
oversample resolution.  Our new behavior is to only send the oversampled
image to Tesseract while leaving the main page intact unless asked to
deskew, clean, etc.
2016-01-15 15:55:23 -08:00
James R. Barlow fc0479f110 Fix all but test_oversample[hocr] 2016-01-15 15:46:47 -08:00
James R. Barlow 62728205b6 Implement image+text merging in other cases
5 failed, 28 passed

failures:
test_oversample[hocr], test_skip_ocr, test_skip_big, test_maximum_options[hocr],
test_blank_input_pdf,
2016-01-15 15:38:08 -08:00
James R. Barlow dc0fb25e64 Render hocr page: no longer needs an image as input 2016-01-15 15:16:47 -08:00
James R. Barlow f3e04cce56 Update pipeline.svg 2016-01-15 14:56:16 -08:00
James R. Barlow 7067110308 Add safety check to prevent merge from running when not sensible 2016-01-15 14:54:45 -08:00
James R. Barlow 599d889703 Implement "perfect reconstruction" - transfer page and watermark OCR layer
Works, does not account for changes to clean/deskew, etc.
Surprisingly, it works. PyPDF2 fixes since last attempt?
2016-01-15 14:39:12 -08:00
James R. Barlow 2fa8366632 Merge branch 'feature/test-pageinfo-cleanup' into develop 2016-01-15 14:18:01 -08:00
James R. Barlow c368c51bad New hocrtransform test 2016-01-15 14:14:08 -08:00
James R. Barlow 7c558b3713 Move pageinfo test into tests folder 2016-01-11 17:40:44 -08:00
James R. Barlow 8d323ae510 Merge branch 'feature/pagesegmode' into develop 2016-01-11 17:23:00 -08:00
James R. Barlow 3b53e9adac Use tesseract cache for -psm 2016-01-11 17:22:50 -08:00
James R. Barlow 074c1d71b4 Activate --tesseract-pagesegmode 2016-01-11 17:19:32 -08:00
James R. Barlow 1fca9a004d Adjust command line parameters
Was splitting each argument to --tesseract-config into a list of single
character strings
2016-01-11 16:57:19 -08:00
James R. Barlow b485a1ef78 Override ruffus' handling of --jobs
Ruffus treats omitted parameter as -j1. For our purposes it makes more
sense for omitting the parameter to mean "use all CPUs". As such we
must be able to distinguish -j1 from the parameter -j being omitted.

Telling ruffus to ignore the argument actually just makes it not auto
generate the argument. We can add an argument back with the same name.
2016-01-09 19:07:48 -08:00
James R. Barlow 326ef7a3ac Merge branch 'hotfix/v3.1.1' into develop
# Conflicts:
#	RELEASE_NOTES.rst
2016-01-09 18:55:04 -08:00
James R. Barlow 12bc58b5b6 Merge branch 'hotfix/v3.1.1' 2016-01-09 18:45:40 -08:00
James R. Barlow 6af0815681 Bump version 2016-01-09 18:45:06 -08:00
James R. Barlow 66c2b9b78e Merge branch 'hotfix/v3.1.1' into develop 2016-01-09 18:38:09 -08:00
James R. Barlow d03c056cb1 Supporting all languages bloats the image by an extra 1 GB
Make it a special image
2016-01-04 16:49:06 -08:00
James R. Barlow 3f94d628fa Dockerfile: remove manual build of unpaper
Fortunately unpaper now exists as binary package, eliminating the need
to install all of the build machinery and build it from source.
2016-01-04 15:07:12 -08:00
James R. Barlow a64c7dbe99 Update dockerfile: include all languages
Also update ignore files
2016-01-04 14:27:16 -08:00
James R. Barlow 61b3ccb57c Place ruffus database in temporary folder
Because we don't really use ruffus checkpoint feature, putting the
database in a permanent location does not help anything, but does cause
large database files and problems if the .ruffus_history.sqlite wanted
to be in a writable location.
2016-01-04 13:23:47 -08:00
James R. Barlow 424b4b33b1 Just go right ahead and demand Python 3.4 2016-01-04 12:56:51 -08:00
James R. Barlow e510f89792 Python 2 warning message 2015-12-21 09:38:38 -08:00
James R. Barlow 49cd6cc619 Off by one error in page info calculation 2015-12-21 09:35:02 -08:00
James R. Barlow 9aa3d340d4 Tell Travis about the cache 2015-12-17 14:02:13 -08:00
James R. Barlow 09782242c8 Adjust test cases to use cache and noop more effectively
This reduces total execution time to 164s on my machine, down from
about double that.
2015-12-17 14:00:17 -08:00
James R. Barlow 9ec4aa039d Add tesseract caching to speed up tests 2015-12-17 12:52:12 -08:00
James R. Barlow ecebe2f24b Let some tests use the spoofed tesseract
Where getting OCR doesn't matter
2015-12-17 11:56:09 -08:00
James R. Barlow 7313a77c2a Implement pdf renderer side of tess spoof 2015-12-17 11:41:54 -08:00
James R. Barlow 45113676a3 Add Tesseract spoofing 2015-12-17 11:36:47 -08:00
James R. Barlow 102bd07019 Check for encrypted PDF and complain appropriately 2015-12-17 10:37:54 -08:00
James R. Barlow 9622e31da9 Use envvars in a new test case
And get rid of the messy binary replacement spoofing
2015-12-17 09:29:01 -08:00
James R. Barlow 1731ce2a44 Environment variables can now override default programs 2015-12-17 09:05:10 -08:00
James R. Barlow 276f421c44 Did a quick test of Ghostscript vs QPDF at PDF page splitting
qpdf won so hard it wasn't funny, even though it must be called once
per page to do the job. Perhaps Ghostscript interprets it as a call to
render the page?

time bash qpdf-test.fish ../tests/resources/multipage.pdf
        0.07 real         0.02 user         0.03 sys

time gs -sDEVICE=pdfwrite -dSAFER -o '%06d.pdf' ../tests/resources/multipage.pdf
        5.12 real         5.06 user         0.04 sys
2015-12-17 08:49:08 -08:00
James R. Barlow 133357779a All subprocess invocations refactored out of main.py 2015-12-17 08:31:18 -08:00
James R. Barlow 5d8167b232 Move PDF validation check to qpdf.py 2015-12-17 08:28:00 -08:00
James R. Barlow e76ae8c46c Move more qpdf calls into qpdf.py 2015-12-17 08:24:48 -08:00
James R. Barlow 53a7c0e668 Refactor qpdf subprocess calls into module 2015-12-17 08:19:53 -08:00
James R. Barlow 4ca243e490 Merge commit '9f374461559460527e47237323e511123f31b6b0' into feature/envvars 2015-12-17 07:27:26 -08:00
jbarlow83 9f37446155 Merge pull request #34 from shemgp/master
Don't exit when qpdf repairs the file successfully but displays warning
2015-12-16 20:46:47 -08:00
Shem Pasamba d7c7559b05 Use boolean instead of integers 2015-12-17 11:23:27 +08:00
Shem Pasamba b2b66d1344 Don't exit when qpdf repair was successful 2015-12-17 11:20:20 +08:00
James R. Barlow 5d111a3c04 Refactor tesseract --pdfrenderer calls to tesseract.py 2015-12-16 17:48:26 -08:00
James R. Barlow 10416f847f Migrate tesseract-hocr code to tesseract module, because modularity 2015-12-16 17:36:11 -08:00
James R. Barlow 79b3472b26 All tests passed, bump version 2015-12-04 04:31:01 -08:00
James R. Barlow f1b2f1ae08 Merge branch 'feature/pdfa-2' into develop 2015-12-04 04:04:08 -08:00
James R. Barlow ee7d97ae8c Trivial 2015-12-04 04:03:38 -08:00
James R. Barlow 7d9f473bb1 Remove eval() call by introspecting ExitCode 2015-12-04 03:34:53 -08:00
James R. Barlow e77a5e5e75 We don't want threads. Really. Do. Not. Want. 2015-12-04 03:11:38 -08:00
James R. Barlow 6ab19af122 Comments 2015-12-04 03:09:39 -08:00
James R. Barlow 276fe49867 Better error messages for input file not found or invalid
Not as good finding a general way to deal with ruffus exceptions, but
better than nil.
2015-12-04 03:07:53 -08:00
James R. Barlow acb31abe86 Fix issue #20 - fails on uppercase .PDF 2015-12-04 02:14:09 -08:00
James R. Barlow 4f964a3c8a Introduce --pdf-renderer auto
Tess 3.03's has various quality problems like wrong DPI that are fixed
in Tess 3.04. Idea here is to introduce an option to let OCRmyPDF
select the rendering backend based on the options and system.

However, we're not ready for tesseract as the main renderer.
Setting pdf-renderer to tesseract does not pass all test cases, mainly
the one where --tesseract-timeout is triggered, and some others.
2015-12-02 23:20:31 -08:00
James R. Barlow df1fda7438 pageinfo: workaround PyPDF extractText limitations on hidden text
It appears that extractText() does not find all text. At a glance it
may be that Tesseract's PDF renderer generates a font and uses glyphs
that map to different Unicode code points that PyPDF expects, so it
discards the content and finds nothing. As a proxy in lieu of better
PDF parsing, assume that a "GlyphLessFont" means there is a text there.

I had previously found it does not work to check for the presence of a
font on page. Some PDF generators create a font resource entry even if
the font is never called for.
2015-12-02 23:16:36 -08:00
James R. Barlow d6124c1787 pageinfo: improve robustness of text test for Tesseract produced PDFs 2015-12-02 03:12:52 -08:00
James R. Barlow 80d89b5420 Set /Creator metadata to OCRmyPDF
with reference to Tess version and settings
2015-12-02 02:19:39 -08:00
James R. Barlow 74059eecf1 Choose PDF/A-2b by default instead of A-1b 2015-12-02 01:48:10 -08:00
James R. Barlow 78697341a2 pytest: don't run tests that happened to be part of pyvenv 2015-12-02 01:19:43 -08:00
James R. Barlow cfb56dd8ff Merge commit 'b1769cbe18e6380ddfe96b3b22e6d02cb603338b' into develop 2015-12-01 00:40:43 -08:00
jbarlow83 b1769cbe18 README: El Capitan supported now, Py3.5 supported 2015-11-26 16:31:33 -08:00
James R. Barlow 955b801e7f Merge branch 'master' into develop 2015-09-14 00:34:21 -07:00
James R. Barlow 3cea3f1afe Try to work around git binary file bug again 2015-09-14 00:34:16 -07:00
James R. Barlow fd4a227ccb Force this file to stop thinking it was modified 2015-09-13 17:53:01 -07:00
James R. Barlow 19c3097483 Update notes 2015-09-13 17:51:18 -07:00
James R. Barlow cdd1a6d03c Suppress failing test 2015-09-10 07:01:14 -07:00
James R. Barlow 5fb8411571 Try new PPA for libav 2015-09-10 06:01:59 -07:00
James R. Barlow 334a15b8c7 typo fix 2015-09-10 05:01:44 -07:00
James R. Barlow 6390736577 ffmpeg-dev instead? 2015-09-10 04:27:57 -07:00
James R. Barlow d55a214516 Autoreconf? 2015-09-10 04:10:12 -07:00
James R. Barlow 0994164b9a travis: apt-get install in wrong place 2015-09-06 01:43:47 -07:00
James R. Barlow 54ee0dd147 travis: fix typo 2015-09-06 01:39:54 -07:00
James R. Barlow 47c7990fb3 travis: build unpaper with cache 2015-09-06 01:38:01 -07:00
James R. Barlow 997e95de4d travis: build unpaper 2015-09-06 01:29:07 -07:00
James R. Barlow 44204be256 Fix order of PPAs 2015-09-06 00:54:50 -07:00
James R. Barlow 9b1d9aa88a travis: improve, add new PPA, etc. 2015-09-06 00:41:23 -07:00
James R. Barlow b775762f6a travis: doesn't like gcc-4.8, try just gcc 2015-09-06 00:23:05 -07:00
James R. Barlow df1a28e319 Travis needs sudo mode 2015-09-06 00:21:20 -07:00
James R. Barlow c300b2802a travis: tabs -> spaces 2015-09-06 00:08:25 -07:00
James R. Barlow 01040ace4c More complete travis.yml 2015-09-06 00:02:58 -07:00
James R. Barlow 8367172e0b Start setting up Travis CI 2015-09-05 23:44:43 -07:00
361 changed files with 36107 additions and 39874 deletions
+24
View File
@@ -0,0 +1,24 @@
[paths]
source =
src
*/site-packages
[run]
branch = true
parallel = true
concurrency =
thread
multiprocessing
source =
src/ocrmypdf
[report]
exclude_lines =
pragma: no cover
def __repr__
raise AssertionError
raise NotImplementedError
if 0:
if False:
if __name__ == .__main__.:
if TYPE_CHECKING:
+81
View File
@@ -0,0 +1,81 @@
# OCRmyPDF
#
FROM ubuntu:20.04 as base
FROM base as builder
ENV LANG=C.UTF-8
RUN apt-get update && apt-get install -y --no-install-recommends \
build-essential autoconf automake libtool \
libleptonica-dev \
zlib1g-dev \
python3 \
python3-distutils \
ca-certificates \
curl \
git
# Get the latest pip (Ubuntu version doesn't support manylinux2010)
RUN \
curl https://bootstrap.pypa.io/get-pip.py | python3
# Compile and install jbig2
# Needs libleptonica-dev, zlib1g-dev
RUN \
mkdir jbig2 \
&& curl -L https://github.com/agl/jbig2enc/archive/ea6a40a.tar.gz | \
tar xz -C jbig2 --strip-components=1 \
&& cd jbig2 \
&& ./autogen.sh && ./configure && make && make install \
&& cd .. \
&& rm -rf jbig2
COPY . /app
WORKDIR /app
RUN pip3 install --no-cache-dir \
-r requirements/main.txt \
-r requirements/webservice.txt \
-r requirements/test.txt \
-r requirements/watcher.txt \
.
FROM base
ENV LANG=C.UTF-8
RUN apt-get update && apt-get install -y --no-install-recommends \
ghostscript \
img2pdf \
liblept5 \
libsm6 libxext6 libxrender-dev \
zlib1g \
pngquant \
python3 \
qpdf \
tesseract-ocr \
tesseract-ocr-chi-sim \
tesseract-ocr-deu \
tesseract-ocr-eng \
tesseract-ocr-fra \
tesseract-ocr-por \
tesseract-ocr-spa \
unpaper
WORKDIR /app
COPY --from=builder /usr/local/lib/ /usr/local/lib/
COPY --from=builder /usr/local/bin/ /usr/local/bin/
COPY --from=builder /app/misc/webservice.py /app/
COPY --from=builder /app/misc/watcher.py /app/
# Copy minimal project files to get the test suite.
COPY --from=builder /app/setup.cfg /app/setup.py /app/README.md /app/
COPY --from=builder /app/requirements /app/requirements
COPY --from=builder /app/tests /app/tests
COPY --from=builder /app/src /app/src
ENTRYPOINT ["/usr/local/bin/ocrmypdf"]
+40 -17
View File
@@ -1,21 +1,44 @@
bin/
# dotfiles
.*
!.coveragerc
!.dockerignore
!.git_archival.txt
!.gitattributes
!.gitignore
!.pre-commit-config.yaml
!.readthedocs.yml
# Dev scratch
*.ipynb
**/*.pyc
/*.pdf
/*.qdf
/*.png
/scratch.py
IDEAS
log/
tests/resources/private/
tmp/
venv*/
/debug_tests.py
*.traineddata
/private
# Package building
*.egg-info/
build/
dist/
wheelhouse/
pip-wheel-metadata/
# Code coverage
htmlcov/
# Docker specific
bin/
docs/
include/
lib/
ocrmypdf.egg-info/
staging/
.git/
.ruffus_history.sqlite
MANIFEST.in
*.sublime*
*.pdf
*.rst
*.pyc
*/*.pyc
*/*/*.pyc
*/*/*/*.pyc
*/*/*/*/*.pyc
*/*/*/*/*/*.pyc
*/*/*/*/*/*/*.pyc
*/*/*/*/*/*/*/*.pyc
# Docker include .git/
!.git/
+1
View File
@@ -0,0 +1 @@
ref-names: $Format:%D$
+7 -1
View File
@@ -5,4 +5,10 @@
# (binary is a macro for -text -diff)
*.jar binary
*.pdf binary
*.PDF binary
*.PDF binary
*.png binary
*.jpg binary
*.bin binary
*.afdesign binary
.git_archival.txt export-subst
+12
View File
@@ -0,0 +1,12 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: james-barlow
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: # Replace with a single Liberapay username
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
+40
View File
@@ -0,0 +1,40 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
What command line or API call were you trying to run?
```bash
ocrmypdf ...arguments... input.pdf output.pdf
```
Run with verbosity or higher `-v1` to see more detailed logging. This information may be helpful.
**Example file**
Include an input 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.
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.
(Exceptions: Issues with installation, command line argument parsing, test suite failures.Issues without example files usually cannot be resolved.)
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**System**
- OS: [e.g. Linux, Windows, macOS]
- OCRmyPDF Version: ``ocrmypdf --version``
- How did you install ocrmypdf? Did you use a system package manager, `pip`, or a Docker image?
+17
View File
@@ -0,0 +1,17 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Additional context**
Add any other context or screenshots about the feature request here.
+32
View File
@@ -0,0 +1,32 @@
**Describe the issue**
A clear and concise description of what the issue is.
**To Reproduce**
What command line were you trying to run?
```bash
ocrmypdf ...arguments... input.pdf output.pdf
```
**Example file**
Please include an example *input* PDF (or image). You could also try to use of the files in ``tests/resources/`` to illustrate your issue.
Please check any or all that apply about the example file:
- [ ] This is the input file
- [ ] The file contains no personal or confidential information
Please do *not* submit files with confidential information. At your option you may encrypt files for OCRmyPDF's author only.
Issues submitted without an example input file are less likely to be resolved. The output file is generally not helpful.
**Expected behavior**
A clear and concise description of what you expected to happen. Include screenshots if applicable.
**System:**
- OS: [e.g. Linux, macOS]
- OCRmyPDF Version: [e.g. v10.3.0]
**Additional context**
Add any other context about the problem here.
+39 -14
View File
@@ -1,17 +1,42 @@
tmp/
log/
*.pyc
tests/output/
.ruffus_history.sqlite
*.sublime-*
# dotfiles
.*
!.coveragerc
!.dockerignore
!.git_archival.txt
!.gitattributes
!.gitignore
!.pre-commit-config.yaml
!.readthedocs.yml
# Dev scratch
*.ipynb
**/*.pyc
/*.pdf
/*.qdf
/*.png
/scratch.py
IDEAS
log/
tests/resources/private/
tmp/
venv*/
/debug_tests.py
*.traineddata
/private
# Package building
*.egg-info/
build/
dist/
*.egg-info/
venv/
*/test/output
bin/
include/
lib/
pip-selfcheck.json
pyvenv.cfg
wheelhouse/
pip-wheel-metadata/
# Code coverage
htmlcov/
# Automatically generated files
docs/_build/
docs/_static/
docs/_templates/
docs/Makefile
ocrmypdf/lib/_*.py
+23
View File
@@ -0,0 +1,23 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.1.0
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: debug-statements
- repo: https://github.com/asottile/seed-isort-config
rev: v2.2.0
hooks:
- id: seed-isort-config
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.0.5 # pick the isort version you'd like to use from https://github.com/pre-commit/mirrors-isort/releases
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 19.10b0
hooks:
- id: black
language_version: python3.8
exclude: ^src/ocrmypdf/lib/_leptonica.py
+10
View File
@@ -0,0 +1,10 @@
build:
image: latest
python:
version: 3.6
formats:
- pdf
requirements_file: requirements/main.txt
-87
View File
@@ -1,87 +0,0 @@
# OCRmyPDF
#
# VERSION 3.0.0
FROM debian:stretch
MAINTAINER James R. Barlow <jim@purplerock.ca>
# Add unprivileged user
RUN useradd docker \
&& mkdir /home/docker \
&& chown docker:docker /home/docker
# Update system and install our dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
locales \
ghostscript \
tesseract-ocr \
tesseract-ocr-deu tesseract-ocr-spa tesseract-ocr-eng tesseract-ocr-fra \
qpdf \
poppler-utils \
python3 \
python3-pip \
python3-venv \
python3-reportlab \
python3-pil
# Enforce UTF-8
# Borrowed from https://index.docker.io/u/crosbymichael/python/
RUN dpkg-reconfigure locales && \
locale-gen C.UTF-8 && \
/usr/sbin/update-locale LANG=C.UTF-8
ENV LC_ALL C.UTF-8
# Build unpaper 6.1
RUN apt-get install -y \
wget \
gcc \
libavformat-dev \
libavcodec-dev \
libavutil-dev \
autoconf \
automake \
make \
pkg-config \
xsltproc
WORKDIR /root
RUN wget https://github.com/Flameeyes/unpaper/archive/unpaper-6.1.tar.gz
RUN tar xf unpaper-6.1.tar.gz
WORKDIR /root/unpaper-unpaper-6.1
RUN autoreconf -i
RUN ./configure CFLAGS="-O2 -march=native -pipe -flto"
RUN make -j install
RUN apt-get remove -y \
gcc \
autoconf \
automake \
pkg-config \
xsltproc \
make
RUN apt-get autoremove -y && apt-get clean -y
RUN rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Set up a Python virtualenv and take all of the system packages, so we can
# rely on the platform packages rather than importing GCC and compiling them
RUN pyvenv /appenv \
&& pyvenv --system-site-packages /appenv
COPY . /application/
# Install application and dependencies
# In this arrangement Pillow and reportlab will be provided by the system
RUN . /appenv/bin/activate; \
pip install --upgrade pip \
&& pip install --no-cache-dir /application \
&& pip install --no-cache-dir -r /application/test_requirements.txt
USER docker
WORKDIR /home/docker
ENV DEFAULT_RUFFUS_HISTORY_FILE=/tmp/.{basename}.ruffus_history.sqlite
ENV OCRMYPDF_TEST_OUTPUT=/tmp/test-output
ENV OCRMYPDF_IN_DOCKER=1
# Must use array form of ENTRYPOINT
# Non-array form does not append other arguments, because that is "intuitive"
ENTRYPOINT ["/application/docker-wrapper.sh"]
+373
View File
@@ -0,0 +1,373 @@
Mozilla Public License Version 2.0
==================================
1. Definitions
--------------
1.1. "Contributor"
means each individual or legal entity that creates, contributes to
the creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used
by a Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached
the notice in Exhibit A, the Executable Form of such Source Code
Form, and Modifications of such Source Code Form, in each case
including portions thereof.
1.5. "Incompatible With Secondary Licenses"
means
(a) that the initial Contributor has attached the notice described
in Exhibit B to the Covered Software; or
(b) that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the
terms of a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in
a separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible,
whether at the time of the initial grant or subsequently, any and
all of the rights conveyed by this License.
1.10. "Modifications"
means any of the following:
(a) any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered
Software; or
(b) any new file in Source Code Form that contains any Covered
Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the
License, by the making, using, selling, offering for sale, having
made, import, or transfer of either its Contributions or its
Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU
Lesser General Public License, Version 2.1, the GNU Affero General
Public License, Version 3.0, or any later versions of those
licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that
controls, is controlled by, or is under common control with You. For
purposes of this definition, "control" means (a) the power, direct
or indirect, to cause the direction or management of such entity,
whether by contract or otherwise, or (b) ownership of more than
fifty percent (50%) of the outstanding shares or beneficial
ownership of such entity.
2. License Grants and Conditions
--------------------------------
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
(a) under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
(b) under Patent Claims of such Contributor to make, use, sell, offer
for sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
(a) for any code that a Contributor has removed from Covered Software;
or
(b) for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
(c) under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights
to grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
in Section 2.1.
3. Responsibilities
-------------------
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
(a) such Covered Software must also be made available in Source Code
Form, as described in Section 3.1, and You must inform recipients of
the Executable Form how they can obtain a copy of such Source Code
Form by reasonable means in a timely manner, at a charge no more
than the cost of distribution to the recipient; and
(b) You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter
the recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty,
or limitations of liability) contained within the Source Code Form of
the Covered Software, except that You may alter any license notices to
the extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
---------------------------------------------------
If it is impossible for You to comply with any of the terms of this
License with respect to some or all of the Covered Software due to
statute, judicial order, or regulation then You must: (a) comply with
the terms of this License to the maximum extent possible; and (b)
describe the limitations and the code they affect. Such description must
be placed in a text file included with all distributions of the Covered
Software under this License. Except to the extent prohibited by statute
or regulation, such description must be sufficiently detailed for a
recipient of ordinary skill to be able to understand it.
5. Termination
--------------
5.1. The rights granted under this License will terminate automatically
if You fail to comply with any of its terms. However, if You become
compliant, then the rights granted under this License from a particular
Contributor are reinstated (a) provisionally, unless and until such
Contributor explicitly and finally terminates Your grants, and (b) on an
ongoing basis, if such Contributor fails to notify You of the
non-compliance by some reasonable means prior to 60 days after You have
come back into compliance. Moreover, Your grants from a particular
Contributor are reinstated on an ongoing basis if such Contributor
notifies You of the non-compliance by some reasonable means, this is the
first time You have received notice of non-compliance with this License
from such Contributor, and You become compliant prior to 30 days after
Your receipt of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
end user license agreements (excluding distributors and resellers) which
have been validly granted by You or Your distributors under this License
prior to termination shall survive termination.
************************************************************************
* *
* 6. Disclaimer of Warranty *
* ------------------------- *
* *
* Covered Software is provided under this License on an "as is" *
* basis, without warranty of any kind, either expressed, implied, or *
* statutory, including, without limitation, warranties that the *
* Covered Software is free of defects, merchantable, fit for a *
* particular purpose or non-infringing. The entire risk as to the *
* quality and performance of the Covered Software is with You. *
* Should any Covered Software prove defective in any respect, You *
* (not any Contributor) assume the cost of any necessary servicing, *
* repair, or correction. This disclaimer of warranty constitutes an *
* essential part of this License. No use of any Covered Software is *
* authorized under this License except under this disclaimer. *
* *
************************************************************************
************************************************************************
* *
* 7. Limitation of Liability *
* -------------------------- *
* *
* Under no circumstances and under no legal theory, whether tort *
* (including negligence), contract, or otherwise, shall any *
* Contributor, or anyone who distributes Covered Software as *
* permitted above, be liable to You for any direct, indirect, *
* special, incidental, or consequential damages of any character *
* including, without limitation, damages for lost profits, loss of *
* goodwill, work stoppage, computer failure or malfunction, or any *
* and all other commercial damages or losses, even if such party *
* shall have been informed of the possibility of such damages. This *
* limitation of liability shall not apply to liability for death or *
* personal injury resulting from such party's negligence to the *
* extent applicable law prohibits such limitation. Some *
* jurisdictions do not allow the exclusion or limitation of *
* incidental or consequential damages, so this exclusion and *
* limitation may not apply to You. *
* *
************************************************************************
8. Litigation
-------------
Any litigation relating to this License may be brought only in the
courts of a jurisdiction where the defendant maintains its principal
place of business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions.
Nothing in this Section shall prevent a party's ability to bring
cross-claims or counter-claims.
9. Miscellaneous
----------------
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides
that the language of a contract shall be construed against the drafter
shall not be used to construe this License against a Contributor.
10. Versions of the License
---------------------------
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses
If You choose to distribute Source Code Form that is Incompatible With
Secondary Licenses under the terms of this version of the License, the
notice described in Exhibit B of this License must be attached.
Exhibit A - Source Code Form License Notice
-------------------------------------------
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/.
If it is not possible or desirable to put the notice in a particular
file, then You may include the notice in a location (such as a LICENSE
file in a relevant directory) where a recipient would be likely to look
for such a notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
---------------------------------------------------------
This Source Code Form is "Incompatible With Secondary Licenses", as
defined by the Mozilla Public License, v. 2.0.
-20
View File
@@ -1,20 +0,0 @@
Copyright (c) 2013-2015, The OCRmyPDF Authors
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.
-3
View File
@@ -1,3 +0,0 @@
recursive-exclude tests/output *
include requirements.txt
include test_requirements.txt
-6
View File
@@ -1,6 +0,0 @@
#!/bin/sh
##############################################################################
# Copyright (c) 2013-14: fritz-hh from Github (https://github.com/fritz-hh)
##############################################################################
python3 -m ocrmypdf.main "$@"
+142
View File
@@ -0,0 +1,142 @@
<img src="docs/images/logo.svg" width="240" alt="OCRmyPDF">
[![Build Status][azure]](https://dev.azure.com/jim0585/ocrmypdf/_build/latest?definitionId=2&branchName=master) [![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"
[homebrew]: https://img.shields.io/homebrew/v/ocrmypdf.svg "Homebrew version"
[docs]: https://readthedocs.org/projects/ocrmypdf/badge/?version=latest "RTD"
[pyversions]: https://img.shields.io/pypi/pyversions/ocrmypdf "Supported Python versions"
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched or copy-pasted.
```bash
ocrmypdf # it's a scriptable command line program
-l eng+fra # it supports multiple languages
--rotate-pages # it can fix pages that are misrotated
--deskew # it can deskew crooked PDFs!
--title "My PDF" # it can change output metadata
--jobs 4 # it uses multiple cores by default
--output-type pdfa # it produces PDF/A by default
input_scanned.pdf # takes PDF input (or images)
output_searchable.pdf # produces validated PDF output
```
[See the release notes for details on the latest changes](https://ocrmypdf.readthedocs.io/en/latest/release_notes.html).
## Main features
- Generates a searchable [PDF/A](https://en.wikipedia.org/?title=PDF/A) file from a regular PDF
- Places OCR text accurately below the image to ease copy / paste
- Keeps the exact resolution of the original embedded images
- When possible, inserts OCR information as a "lossless" operation without disrupting any other content
- Optimizes PDF images, often producing files smaller than the input file
- If requested, deskews and/or cleans the image before performing OCR
- Validates input and output files
- Distributes work across all available CPU cores
- Uses [Tesseract OCR](https://github.com/tesseract-ocr/tesseract) engine to recognize more than [100 languages](https://github.com/tesseract-ocr/tessdata)
- Scales properly to handle files with thousands of pages
- Battle-tested on millions of PDFs
For details: please consult the [documentation](https://ocrmypdf.readthedocs.io/en/latest/).
## Motivation
I searched the web for a free command line tool to OCR PDF files: 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)
- 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
- Or they crashed when trying to OCR
- Or they did not produce valid PDF files
- 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.
## Installation
Linux, Windows, macOS and FreeBSD are supported. Docker images are also available.
Users of Debian 9 or later or Ubuntu 16.10 or later may simply
```bash
apt-get install ocrmypdf
```
and users of Fedora 29 or later may simply
```bash
dnf install ocrmypdf
```
and Homebrew users (macOS, Linux, Windows Subsystem for Linux) may simply
```bash
brew install ocrmypdf
```
For everyone else, [see our documentation](https://ocrmypdf.readthedocs.io/en/latest/installation.html) for installation steps.
## Languages
OCRmyPDF uses Tesseract for OCR, and relies on its language packs. For Linux users, you can often find packages that provide language packs:
```bash
# Display a list of all Tesseract language packs
apt-cache search tesseract-ocr
# Debian/Ubuntu users
apt-get install tesseract-ocr-chi-sim # Example: Install Chinese Simplified language pack
# Arch Linux users
pacman -S tesseract-data-eng tesseract-data-deu # Example: Install the English and German language packs
```
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.
## Documentation and support
Once OCRmyPDF is installed, the built-in help which explains the command syntax and options can be accessed via:
```bash
ocrmypdf --help
```
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.
## Requirements
In addition to the required Python version (3.6+), OCRmyPDF requires external program installations of Ghostscript, Tesseract OCR, QPDF, and Leptonica. OCRmyPDF is pure Python, but uses CFFI to portably generate library bindings. OCRmyPDF works on pretty much everything: Linux, macOS, Windows and FreeBSD.
## Press & Media
- [Going paperless with OCRmyPDF](https://medium.com/@ikirichenko/going-paperless-with-ocrmypdf-e2f36143f46a)
- [Converting a scanned document into a compressed searchable PDF with redactions](https://medium.com/@treyharris/converting-a-scanned-document-into-a-compressed-searchable-pdf-with-redactions-63f61c34fe4c)
- [c't 1-2014, page 59](https://heise.de/-2279695): Detailed presentation of OCRmyPDF v1.0 in the leading German IT magazine c't
- [heise Open Source, 09/2014: Texterkennung mit OCRmyPDF](https://heise.de/-2356670)
- [heise Durchsuchbare PDF-Dokumente mit OCRmyPDF erstellen](https://www.heise.de/ratgeber/Durchsuchbare-PDF-Dokumente-mit-OCRmyPDF-erstellen-4607592.html)
- [Excellent Utilities: OCRmyPDF](https://www.linuxlinks.com/excellent-utilities-ocrmypdf-add-ocr-text-layer-scanned-pdfs/)
## Business enquiries
OCRmyPDF would not be the software that it is today without companies and users choosing to provide support for feature development and consulting enquiries. We are happy to discuss all enquiries, whether for extending the existing feature set, or integrating OCRmyPDF into a larger system.
## License
The OCRmyPDF software is licensed under the Mozilla Public License 2.0
(MPL-2.0). This license permits integration of OCRmyPDF with other code,
included commercial and closed source, but asks you to publish source-level
modifications you make to OCRmyPDF.
Some components of OCRmyPDF have other licenses, as noted in those files and the
``debian/copyright`` file. Most files in ``misc/`` use the MIT license, and the
documentation and test files are generally licensed under Creative Commons
ShareAlike 4.0 (CC-BY-SA 4.0).
## Disclaimer
The software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-245
View File
@@ -1,245 +0,0 @@
OCRmyPDF
========
OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to
be searched.
Main features
-------------
- Generates a searchable
`PDF/A <https://en.wikipedia.org/?title=PDF/A>`__ file from a regular PDF
only containing images
- Places OCRed text accurately below the image to ease copy / paste
- Keeps the exact resolution of the original embedded images
- or if requested oversamples the images before OCRing so as to get
better results
- When possible, copies input images directly to output without transcoding them,
to preserve image quality
- Keeps file size about the same
- If requested deskews and/or cleans the image before performing OCR
- Validates input and output files
- Provides debug mode to enable easy verification of the OCR results
- Processes several pages in parallel when more than one CPU core is
available
- Uses Tesseract OCR engine
For details: please consult the `release notes <RELEASE_NOTES.rst>`__
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)
- Or they did not display correctly some escaped HTML characters located in the hOCR file produced by the OCR engine
- Or they changed the resolution of the embedded images
- Or they generated PDF files having a ridiculous big size
- Or they crashed when trying to OCR some of my PDF files
- Or they did not produce valid PDF files (even though they were readable with my current PDF reader)
- 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)
Installation
------------
Download OCRmyPDF here: https://github.com/jbarlow83/OCRmyPDF/releases
You can install it to a Python virtual environment or system-wide.
Installing the Docker container
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For many users, installing the Docker container will be easier than installing all of OCRmyPDF's dependencies. For Windows, it is the only option.
If you have `Docker <https://docs.docker.com/>`__ installed on your system, you can install
a Docker container of the latest release.
Follow the Docker installation instructions for your platform. If you can run this command
successfully, your system is ready to download and execute the image::
docker run hello-world
OCRmyPDF will use all available CPU cores. By default, the VirtualBox machine instance on Windows and OS X has only a single CPU core enabled. Use the VirtualBox Manager to determine the name of your Docker container host, and then follow these optional steps to enable multiple CPUs::
# Optional
docker-machine stop "yourVM"
VBoxManage modifyvm "yourVM" --cpus 2 # or whatever number of core is desired
docker-machine start "yourVM"
eval $(docker-machine env "yourVM")
Assuming you have a Docker engine running somewhere, you can run these commands to download
the image::
docker pull jbarlow83/ocrmypdf
Then tag it to give a more convenient name, just ocrmypdf::
docker tag jbarlow83/ocrmypdf ocrmypdf
You can then run using the command::
docker run 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/>`__, such as this in this template::
docker run -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`::
docker run -v "$(pwd):/home/docker" ocrmypdf --skip-text test.pdf output.pdf
Note that `ocrmypdf` has its own separate -v argument to control debug verbosity. All Docker arguments should before the `ocrmypdf` container name and all arguments to `ocrmypdf` should be listed after.
Installing on Mac OS X Yosemite
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
If it's not already present, `install Homebrew <http://brew.sh/>`__
Update Homebrew::
brew update
Install or upgrade the required Homebrew packages, if any are missing::
brew install libpng openjpeg jbig2dec # image libraries
brew install qpdf
brew install ghostscript
brew install python3
brew install libxml2
brew install leptonica
brew install tesseract
It is also recommended that install Pillow and confirm it can read and write JPEG and PNG files::
pip3 install --upgrade pip
pip3 install --upgrade pillow
To test that your Python imaging library (Pillow) can access JPEG and PNG files, try this command::
python3 -c "from PIL import Image; im = Image.new('1', (1, 1)); im.save('test.png'); im.save('test.jpg')"
If you have trouble getting Pillow to access JPEG and PNG files, `review the installation instructions <https://pillow.readthedocs.org/installation.html>`__.
You can then install OCRmyPDF from PyPI::
pip3 install ocrmypdf
The command line program should now be available::
ocrmypdf --help
Installing on Ubuntu 14.04 LTS
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Installing on Ubuntu 14.04 LTS (trusty) is more difficult than other options, because of certain bugs in package installation.
Update apt-get::
sudo apt-get update
sudo apt-get upgrade
Install system dependencies::
sudo apt-get install \
zlib1g-dev \
libjpeg-dev \
ghostscript \
tesseract-ocr \
qpdf \
unpaper \
python3-pip \
python3-pil \
python3-pytest \
python3-reportlab
If you wish install OCRmyPDF to the system Python, then install as follows (note this installs new packages
into your system Python, which could interfere with other programs)::
sudo pip3 install ocrmypdf
If you wish to install OCRmyPDF to a virtual environment to isolate system Python from modified, you can
follow these steps. This includes a workaround `for a known, unresolved issue in Ubuntu 14.04's ensurepip
package <http://www.thefourtheye.in/2014/12/Python-venv-problem-with-ensurepip-in-Ubuntu.html>`__::
sudo apt-get install python3-venv
python3 -m venv venv-ocrmypdf --without-pip
source venv-ocrmypdf/bin/activate
wget -O - -o /dev/null https://bootstrap.pypa.io/get-pip.py | python
deactivate
pyvenv --system-site-packages venv-ocrmypdf
source venv-ocrmypdf/bin/activate
pip install ocrmypdf
Ubuntu 14.04 only installs `unpaper` version 0.4.2, which is not supported by OCRmyPDF because it is produces invalid output. This program is an optional dependency, and provides page deskewing and cleaning. See `Dockerfile <Dockerfile>`__ for an example of how to building unpaper 6.1 from source. If you choose to install unpaper later, OCRmyPDF will use the foremost version on the system PATH.
Installing HEAD revision from sources
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
To install the HEAD revision from sources in development mode::
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
cd OCRmyPDF
pip3 install -e .
On certain Linux/UNIX platforms such as Ubuntu, you may need to use
run the install command as superuser::
sudo pip3 install -e .
Note that this will alter your system's Python distribution. If you prefer
to not install as superuser, you can install the package in a Python virtual environment::
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
pyvenv venv
source venv/bin/activate
cd OCRmyPDF
pip3 install -e .
If your platform does not have ``pip3``, make sure that Python 3.4+ and the `pip`
package are installed.
To run the program::
ocrmypdf --help
If not yet installed, the script will notify you about dependencies that
need to be installed. The script requires specific versions of the
dependencies. Older version than the ones mentioned in the release notes
are likely not to be compatible to OCRmyPDF.
Support
-------
In case you detect an issue, please:
- Check if your issue is already known
- If no problem report exists on github, please create one here:
https://github.com/fritz-hh/OCRmyPDF/issues
- Describe your problem thoroughly
- Append the console output of the script when running the debug mode
(-v 1 option)
- If possible provide your input PDF file as well as the content of the
temporary folder (using a file sharing service like
www.file-upload.net)
Press & Media
-------------
- `c't 1-2014, page 59 <http://www.heise.de/ct/inhalt/2014/1/58/>`__:
Detailed presentation of OCRmyPDF v1.0 in the leading German IT
magazine c't
- `heise Open Source, 09/2014: Texterkennung mit
OCRmyPDF <http://www.heise.de/-2356670>`__
Disclaimer
----------
The software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
-518
View File
@@ -1,518 +0,0 @@
RELEASE NOTES
=============
Please always read this file before installing the package
Download software here: https://github.com/jbarlow83/OCRmyPDF/tags
v3.0:
=====
New features
------------
- 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
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
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
a page, while still processing other pages (``--tesseract-timeout``)
- Less common colorspaces (CMYK, palette) are now supported by conversion to RGB
- Multiple images on the same PDF page are now supported
Changes
-------
- New, robust rewrite in Python 3.4+ with ruffus_ pipelines
- Now uses Ghostscript 9.14's improved color conversion model to preserve PDF colors
- All "tasks" in the pipeline can be executed in parallel on any
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
longer use:
- GNU parallel_
- ImageMagick_
- Python 2.7
- Poppler
- MuPDF_ tools
- shell scripts
- Java and JHOVE_
- libxml2
- Some new external dependencies are required or optional, compared to v2.x:
- Ghostscript 9.14+
- 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
.. _MuPDF: http://mupdf.com/docs/
.. _qpdf: http://qpdf.sourceforge.net/
.. _Unpaper: https://github.com/Flameeyes/unpaper
.. _JHOVE: http://jhove.sourceforge.net/
Release candidates
------------------
- rc9:
- fix issue #118: report error if ghostscript iccprofiles are missing
- fixed another issue related to #111: PDF rasterized to palette file
- add support image files with a palette
- don't try to validate PDF file after an exception occurs
- rc8:
- fix issue #111: exception thrown if PDF is missing DocumentInfo dictionary
- rc7:
- fix error when installing direct from pip, "no such file 'requirements.txt'"
- rc6:
- dropped libxml2 (Python lxml) since Python 3's internal XML parser is sufficient
- set up Docker container
- fix Unicode errors if recognized text contains Unicode characters and system locale is not UTF-8
- rc5:
- dropped Java and JHOVE in favour of qpdf
- improved command line error output
- additional tests and bug fixes
- tested on Ubuntu 14.04 LTS
- rc4:
- dropped MuPDF in favour of qpdf
- 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
- rc3: skipping version number intentionally to avoid confusion with Tesseract
- rc2: first release for public testing to test-PyPI, Github
- rc1: testing release process
Compatibility notes
-------------------
- ``./OCRmyPDF.sh`` script is still available for now
- Stacking the verbosity option like ``-vvv`` is no longer supported
- The configuration file ``config.sh`` has been removed. Instead, you can
feed a file to the arguments for common settings:
::
ocrmypdf input.pdf output.pdf @settings.txt
where ``settings.txt`` contains *one argument per line*, for example:
::
-l
deu
--author
A. Merkel
--pdf-renderer
tesseract
Fixes
-----
- Handling of filenames containing spaces: fixed
Notes and known issues
----------------------
- Some dependencies may work with lower versions than tested, so try
overriding dependencies if they are "in the way" to see if they work.
- ``--pdf-renderer tesseract`` will output files with an incorrect page size in Tesseract 3.03,
due to a bug in Tesseract.
- PDF files containing "inline images" are not supported and won't be for the 3.0 release. Scanned
images almost never contain inline images.
v2.2-stable (2014-09-29):
=========================
New features
------------
- None
Changes
-------
- Update to jhove v1.11
- Request the python library reportlab v3.0 or newer (So that we could remove a patch to the previous version of reportlab leading to issues for some users)
Fixes
-----
- Fix bug on Mac OS X (resolution of simlink to OCRmyPDF.sh script) (thanks to jbarlow83)
- Check if the input pdf file exists before to continue
Tested with
-----------
- Operating system: FreeBSD 9.2
- Dependencies:
- parallel 20140822
- poppler-utils 0.24.5
- ImageMagick 6.8.9-4 2014-09-17
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.8
- ghostcript (gs): 9.06
- java: openjdk version "1.7.0_65"
v2.1-stable (2014-09-20):
=========================
New features
------------
- None
Changes
-------
- None
Fixes
-----
- Allow execution via simlink
- Add support for tesseract 3.03
- Add support for newer version of reportlab
- Lowered minimum version of gnu parallel
- Various typo
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- parallel 20130222
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v2.0-stable (2014-01-25):
=========================
New features
------------
- Check if the language(s) passed using the -l option is supported by
tesseract (fixes #60)
Changes
-------
- Allow OCRmyPDF to be used with tesseract 3.02.01, even though OCR
might fail for few PDF file (see issue #28). Rationale: For some
linux distribution, no newer version than tesseract 3.02.01 is
available
Fixes
-----
- More robust algorithm for checking the version of the installed
tesseract package
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- parallel 20130222
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v2.0-rc2 (2014-01-16):
======================
New features
------------
- None
Changes
-------
- Size reduction of final PDF file: (fixes #50)
- Support for monochrome (Black&White) images (massive size reduction
in final PDF: >80%)
- Reduced size of grayscale images (by 13% on test PDF file)
- Preventing fi, fl ligatures does not require anymore to pass an
additional config file to tesseract using the -C option (fixes #58)
- Location of temporary folder according to content of environment
variable TMPDIR.
- Dependency to pdftk removed
- Check for compatible versions of dependencies: (fixes #51)
- parallel and tesseract
- python libraries reportlab and lxml
Fixes
-----
- Improved portability with various shells (dash, bash, tcsh) and OS
(FreeBSD, MAC OSX, Linux) (fixes #59)
- Corrected bug in case the input PDF file contains a space character
(fixes #48)
- Prevent spurious error message in case there is no image in a PDF
page
- Prevent collision of temporary folder names (fixes #57)
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- parallel 20130222
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v2.0-rc1 (2014-01-07):
======================
New features
------------
- Huge performance improvement on machines having multiple CPU/cores
(processing of several pages concurrently) (fixes #18)
- By default prevent from processing a PDF file already containing
fonts (i.e. text)(it can be overridden with the -f flag) (fixes #16)
- Warn if the resolution is too low to get reasonable OCR results
(fixes #37)
- New option (-o) to perform automatic oversampling if the image
resolution is too low. This can improve OCR results.
- Warn if using a tesseract version older than v3.02.02 (as older
versions are known to produce invalid output) (fixes #41)
- Echo version of the installed dependencies (e.g. tesseract) in debug
mode in order to ease support (fixes #35)
- Echo the arguments passed to the script in debug mode to ease support
Changes
-------
- In debug mode: The debug page is now placed after the respective
"normal" page
- Reduced disk space usage in temporary folder if -d (deskew) or -c
(cleanup) options are not selected
- New file src/config.sh containing various configuration parameters
- Documentation of the tesseract config file "tess-cfg/no\_ligature"
improved
- Improved consistency of the temporary file names
Fixes
-----
- Improved robustness:
- in case vertical resolution differs from horizontal resolution (fixes
#38)
- in case a PDF page contains more than one image (fixes #36)
- Fix a problem occurring if python 3 is the standard interpreter
(fixes #33)
- Fix a problem occurring if the input PDF file contains special
characters like "#" (fixes #34)
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- parallel 20130222
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v1.1-stable (2014-01-06):
=========================
New features
------------
- N/A
Changes
-------
- N/A
Fixes
-----
- Fixed syntax error (bashism) leading to an error message on certain
systems (fixes #42)
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v1.0-stable (2013-05-06):
=========================
New features
------------
- In debug mode: compute and echo time required for processing (fixes
#26)
Changes
-------
- Removed feature to add metadata in final pdf file (because it lead to
to final PDF file that does not comply to the PDF/A-1 format)
- Removed feature to set same owner & permissions in final PDF file
than in input file
- Removed many unused jhove files (e.g. documentation, \*.java and
\*.class files)
Fixes
-----
- Correction to handle correctly path and input PDF files having spaces
(fixes #31)
- Resolutions (x/y) that are nearly equal are now supported (fixes #25)
- Fix compatibility issue with Ubuntu server 12.04 / Ubuntu server
10.04 / Linux Mint 13 Maya and probably other Linux distributions
(fixes #27)
- Commit missing jhove files (\*.jar mainly) due to wrong .gitignore
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v1.0-rc2 (2013-04-29):
======================
New features
------------
- Keep temporary files if debug mode is set (fixes #22)
- Set same owner & permissions in final PDF file than in input file
(fixes #9)
- Added metadata in final pdf file (fixes #4)
Changes
-------
- N/A
Fixes
-----
- Fixed wrong image cropping when deskew option is activated
- Exit with error message if page size is not found in hocr file (fixes
#21)
- Various minor fixes in log messages
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
v1.0-rc1 (2013-04-26):
======================
New features
------------
- First release candidate
Changes
-------
- N/A
Fixes
-----
- N/A
Tested with
-----------
- Operating system: FreeBSD 9.1
- Dependencies:
- poppler-utils 0.22.2
- ImageMagick 6.8.0-7 2013-03-30
- Unpaper 0.3
- tesseract 3.02.02
- Python 2.7.3
- pdftk 1.45
- ghoscript (gs): 9.06
- java: openjdk version "1.7.0\_17"
+259
View File
@@ -0,0 +1,259 @@
trigger:
tags:
include:
- v*
branches:
include:
- "*"
exclude:
- "travis"
stages:
- stage: "Test"
jobs:
- job: Windows
pool:
vmImage: "vs2017-win2016"
strategy:
matrix:
Python36:
python.version: "3.6"
Python37:
python.version: "3.7"
Python38:
python.version: "3.8"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "$(python.version)"
- pwsh: |
choco install --yes --no-progress --pre tesseract
choco install --yes --no-progress python3
choco install --yes --no-progress ghostscript
choco install --yes --no-progress pngquant
displayName: "Install system packages"
- pwsh: |
refreshenv
python -m pip install --upgrade pip wheel
python -m pip install -r requirements/main.txt -r requirements/test.txt .
displayName: "Install Python packages"
- pwsh: |
refreshenv
$env:pathext += ';.py'
# -n auto helps Windows
python -m pytest -n auto --junitxml=test.xml --cov=ocrmypdf --cov-report=xml
displayName: "Test"
- task: PublishTestResults@2
inputs:
testResultsFiles: "test.xml"
testRunTitle: "$(Agent.OS) - $(Build.DefinitionName) - Python $(python.version)"
condition: succeededOrFailed()
- job: "Ubuntu_1804"
pool:
vmImage: "ubuntu-18.04"
strategy:
matrix:
Python36:
python.version: "3.6"
Python37:
python.version: "3.7"
Python38:
python.version: "3.8"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "$(python.version)"
- bash: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
python3-software-properties \
curl \
ghostscript \
img2pdf \
libexempi3 \
libffi-dev \
liblept5 \
libsm6 libxext6 libxrender-dev \
pngquant \
poppler-utils \
tesseract-ocr \
tesseract-ocr-deu \
tesseract-ocr-eng \
unpaper \
zlib1g
displayName: "Install system packages"
- bash: |
curl https://bootstrap.pypa.io/get-pip.py | python3
pip3 install -r requirements/main.txt -r requirements/test.txt .
displayName: "Install Python packages"
- bash: |
tesseract --version
displayName: "Record versions"
- bash: |
# -n auto is slower on Linux and breaks on Python 3.8
pytest -n0 --junitxml=test.xml --cov=ocrmypdf --cov-report=xml
displayName: "Test"
- task: PublishTestResults@2
inputs:
testResultsFiles: "test.xml"
testRunTitle: "$(Agent.OS) - $(Build.DefinitionName) - Python $(python.version)"
condition: succeededOrFailed()
- job: "Ubuntu_1604"
pool:
vmImage: "ubuntu-16.04"
strategy:
matrix:
Python36:
python.version: "3.6"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "$(python.version)"
- bash: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
software-properties-common
sudo add-apt-repository -y ppa:alex-p/tesseract-ocr
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
ghostscript \
img2pdf \
libexempi3 \
libffi-dev \
liblept5 \
libsm6 libxext6 libxrender-dev \
pngquant \
poppler-utils \
tesseract-ocr \
tesseract-ocr-deu \
tesseract-ocr-eng \
unpaper \
zlib1g
displayName: "Install system packages"
- bash: |
curl https://bootstrap.pypa.io/get-pip.py | python3
pip3 install -r requirements/main.txt -r requirements/test.txt .
displayName: "Install Python packages"
- bash: |
tesseract --version
displayName: "Record versions"
- bash: |
# -n auto is slower on Linux and breaks on Python 3.8
pytest -n0 --junitxml=test.xml --cov=ocrmypdf --cov-report=xml
displayName: "Test"
- task: PublishTestResults@2
inputs:
testResultsFiles: "test.xml"
testRunTitle: "$(Agent.OS) - $(Build.DefinitionName) - Python $(python.version)"
condition: succeededOrFailed()
- job: "macOS_Mojave"
pool:
vmImage: "macos-10.14"
strategy:
matrix:
Python37:
python.version: ""
Python38:
python.version: "python@3.8"
steps:
# https://github.com/actions/virtual-environments/issues/664
# - task: UsePythonVersion@0
# inputs:
# versionSpec: "$(python.version)"
- bash: |
brew update
brew unlink python@2
if [ "$(python.version)" != "" ]; then
brew upgrade $(python.version)
else
echo "Using Python `python3 --version`"
fi
displayName: "Update brew and Python"
- bash: |
brew install \
exempi \
ghostscript \
jbig2enc \
leptonica \
openjpeg \
pngquant \
tesseract \
unpaper
displayName: "Install system packages"
- bash: |
pip3 install --upgrade pip
pip3 install -r requirements/main.txt -r requirements/test.txt .
displayName: "Install Python packages"
- bash: |
tesseract --version
displayName: "Record versions"
- bash: pytest -nauto --junitxml=test.xml --cov=ocrmypdf --cov-report=xml
displayName: "Test"
- task: PublishTestResults@2
inputs:
testResultsFiles: "test.xml"
testRunTitle: "$(Agent.OS) - $(Build.DefinitionName) - Python $(python.version)"
condition: succeededOrFailed()
- task: PublishCodeCoverageResults@1
inputs:
codeCoverageTool: Cobertura
summaryFileLocation: "$(System.DefaultWorkingDirectory)/**/coverage.xml"
- stage: "Artifacts"
jobs:
- job: "sdist_wheel"
pool:
vmImage: "ubuntu-18.04"
steps:
- task: UsePythonVersion@0
inputs:
versionSpec: "3.7"
- bash: |
python -m pip install --upgrade pip wheel
python setup.py sdist bdist_wheel
- publish: dist
artifact: sdist_wheel
- stage: "Deploy"
jobs:
- deployment: "PyPI"
pool:
vmImage: "ubuntu-18.04"
environment: "deploy"
strategy:
runOnce:
deploy:
steps:
- download: current
artifact: sdist_wheel
- script: |
mkdir -p dist
mv $(Pipeline.Workspace)/sdist_wheel/* dist
displayName: "Move dist files"
- task: UsePythonVersion@0
inputs:
versionSpec: "3.8"
architecture: x64
- script: |
pip install --upgrade twine
displayName: "Generate artifacts"
- script: |
cat <<FILE >.pypirc
[distutils]
index-servers =
pypi
[pypi]
username: __token__
password: $(TOKEN_PYPI)
FILE
displayName: "Generate PyPI auth file"
- script: |
python -m twine upload --config-file .pypirc dist/*
displayName: "Upload to PyPI"
condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags/'))
- script: |
curl -X POST -d "token=$(TOKEN_RTD)" https://readthedocs.org/api/v2/webhook/pikepdf/39557/
displayName: "Trigger ReadTheDocs"
condition: and(succeeded(), or(startsWith(variables['Build.SourceBranch'], 'refs/tags/'), startsWith(variables['Build.SourceBranch'], 'refs/heads/master')))
+2210
View File
File diff suppressed because it is too large Load Diff
-5
View File
@@ -1,5 +0,0 @@
#!/bin/bash
. /appenv/bin/activate
cd /home/docker
exec ocrmypdf "$@"
+345
View File
@@ -0,0 +1,345 @@
=================
Advanced features
=================
Control of unpaper
==================
OCRmyPDF uses ``unpaper`` to provide the implementation of the
``--clean`` and ``--clean-final`` arguments.
`unpaper <https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md>`__
provides a variety of image processing filters to improve images.
By default, OCRmyPDF uses only ``unpaper`` arguments that were found to
be safe to use on almost all files without having to inspect every page
of the file afterwards. This is particularly true when only ``--clean``
is used, since that instructs OCRmyPDF to only clean the image before
OCR and not the final image.
However, if you wish to use the more aggressive options in ``unpaper``,
you may use ``--unpaper-args '...'`` to override the OCRmyPDF's defaults
and forward other arguments to unpaper. This option will forward
arguments to ``unpaper`` without any knowledge of what that program
considers to be valid arguments. The string of arguments must be quoted
as shown in the examples below. No filename arguments may be included.
OCRmyPDF will assume it can append input and output filename of
intermediate images to the ``--unpaper-args`` string.
In this example, we tell ``unpaper`` to expect two pages of text on a
sheet (image), such as occurs when two facing pages of a book are
scanned. ``unpaper`` uses this information to deskew each independently
and clean up the margins of both.
.. code-block:: bash
ocrmypdf --clean --clean-final --unpaper-args '--layout double' input.pdf output.pdf
ocrmypdf --clean --clean-final --unpaper-args '--layout double --no-noisefilter' input.pdf output.pdf
.. warning::
Some ``unpaper`` features will reposition text within the image.
``--clean-final`` is recommended to avoid this issue.
.. warning::
Some ``unpaper`` features cause multiple input or output files to be
consumed or produced. OCRmyPDF requires ``unpaper`` to consume one
file and produce one file. An deviation from that condition will
result in errors.
.. note::
``unpaper`` uses uncompressed PBM/PGM/PPM files for its intermediate
files. For large images or documents, it can take a lot of temporary
disk space.
Control of OCR options
======================
OCRmyPDF provides many features to control the behavior of the OCR
engine, Tesseract.
When OCR is skipped
-------------------
If a page in a PDF seems to have text, by default OCRmyPDF will exit
without modifying the PDF. This is to ensure that PDFs that were
previously OCRed or were "born digital" rather than scanned are not
processed.
If ``--skip-text`` is issued, then no OCR will be performed on pages
that already have text. The page will be copied to the output. This may
be useful for documents that contain both "born digital" and scanned
content, or to use OCRmyPDF to normalize and convert to PDF/A regardless
of their contents.
If ``--redo-ocr`` is issued, then a detailed text analysis is performed.
Text is categorized as either visible or invisible. Invisible text (OCR)
is stripped out. Then an image of each page is created with visible text
masked out. The page image is sent for OCR, and any additional text is
inserted as OCR. If a file contains a mix of text and bitmap images that
contain text, OCRmyPDF will locate the additional text in images without
disrupting the existing text.
If ``--force-ocr`` is issued, then all pages will be rasterized to
images, discarding any hidden OCR text, and rasterizing any printable
text. This is useful for redoing OCR, for fixing OCR text with a damaged
character map (text is selectable but not searchable), and destroying
redacted information. Any forms and vector graphics will be rasterized
as well.
Time and image size limits
--------------------------
By default, OCRmyPDF permits tesseract to run for three minutes (180
seconds) per page. This is usually more than enough time to find all
text on a reasonably sized page with modern hardware.
If a page is skipped, it will be inserted without OCR. If preprocessing
was requested, the preprocessed image layer will be inserted.
If you want to adjust the amount of time spent on OCR, change
``--tesseract-timeout``. You can also automatically skip images that
exceed a certain number of megapixels with ``--skip-big``. (A 300 DPI,
8.5×11" page is 8.4 megapixels.)
.. code-block:: bash
# Allow 300 seconds for OCR; skip any page larger than 50 megapixels
ocrmypdf --tesseract-timeout 300 --skip-big 50 bigfile.pdf output.pdf
Overriding default tesseract
----------------------------
OCRmyPDF checks the system ``PATH`` for the ``tesseract`` binary.
Some relevant environment variables that influence Tesseract's behavior
include:
.. envvar:: TESSDATA_PREFIX
Overrides the path to Tesseract's data files. This can allow
simultaneous installation of the "best" and "fast" training data
sets. OCRmyPDF does not manage this environment variable.
.. envvar:: OMP_THREAD_LIMIT
Controls the number of threads Tesseract will use. OCRmyPDF will
manage this environment if it is not already set. (Currently, it will
set it to 1 because this gives the best results in testing.)
For example, if you have a development build of Tesseract don't wish to
use the system installation, you can launch OCRmyPDF as follows:
.. code-block:: bash
env \
PATH=/home/user/src/tesseract/api:$PATH \
TESSDATA_PREFIX=/home/user/src/tesseract \
ocrmypdf input.pdf output.pdf
In this example ``TESSDATA_PREFIX`` is required to redirect Tesseract to
an alternate folder for its "tessdata" files.
Overriding other support programs
---------------------------------
In addition to tesseract, OCRmyPDF uses the following external binaries:
- ``gs`` (Ghostscript)
- ``unpaper``
- ``pngquant``
- ``jbig2``
In each case OCRmyPDF will search the ``PATH`` environment variable to
locate the binaries.
Changing tesseract configuration variables
------------------------------------------
You can override tesseract's default `control
parameters <https://github.com/tesseract-ocr/tesseract/wiki/ControlParams>`__
with a configuration file.
As an example, this configuration will disable Tesseract's dictionary
for current language. Normally the dictionary is helpful for
interpolating words that are unclear, but it may interfere with OCR if
the document does not contain many words (for example, a list of part
numbers).
Create a file named "no-dict.cfg" with these contents:
::
load_system_dawg 0
language_model_penalty_non_dict_word 0
language_model_penalty_non_freq_dict_word 0
then run ocrmypdf as follows (along with any other desired arguments):
.. code-block:: bash
ocrmypdf --tesseract-config no-dict.cfg input.pdf output.pdf
.. warning::
Some combinations of control parameters will break Tesseract or break
assumptions that OCRmyPDF makes about Tesseract's output.
Changing the PDF renderer
=========================
rasterizing
Converting a PDF to an image for display.
rendering
Creating a new PDF from other data (such as an existing PDF).
OCRmyPDF has these PDF renderers: ``sandwich`` and ``hocr``. The
renderer may be selected using ``--pdf-renderer``. The default is
``auto`` which lets OCRmyPDF select the renderer to use. Currently,
``auto`` always selects ``sandwich``.
The ``sandwich`` renderer
-------------------------
The ``sandwich`` renderer uses Tesseract's new text-only PDF feature,
which produces a PDF page that lays out the OCR in invisible text. This
page is then "sandwiched" onto the original PDF page, allowing lossless
application of OCR even to PDF pages that contain other vector objects.
Currently this is the best renderer for most uses, however it is
implemented in Tesseract so OCRmyPDF cannot influence it. Currently some
problematic PDF viewers like Mozilla PDF.js and macOS Preview have
problems with segmenting its text output, and
mightrunseveralwordstogether.
When image preprocessing features like ``--deskew`` are used, the
original PDF will be rendered as a full page and the OCR layer will be
placed on top.
The ``hocr`` renderer
---------------------
The ``hocr`` renderer works with older versions of Tesseract. The image
layer is copied from the original PDF page if possible, avoiding
potentially lossy transcoding or loss of other PDF information. If
preprocessing is specified, then the image layer is a new PDF.
Unlike ``sandwich`` this renderer is implemented within OCRmyPDF; anyone
looking to customize how OCR is presented should look here. A major
disadvantage of this renderer is it not capable of correctly handling
text outside the Latin alphabet. Pull requests to improve the situation
are welcome.
Currently, this renderer has the best compatibility with Mozilla's
PDF.js viewer.
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
==================
OCRmyPDF writes all messages to ``stderr``. ``stdout`` is reserved for
piping output files. ``stdin`` is reserved for piping input files.
The return codes generated by the OCRmyPDF are considered part of the
stable user interface. They may be imported from
``ocrmypdf.exceptions``.
.. list-table:: Return codes
:widths: 5 35 60
:header-rows: 1
* - Code
- Name
- Interpretation
* - 0
- ``ExitCode.ok``
- Everything worked as expected.
* - 1
- ``ExitCode.bad_args``
- Invalid arguments, exited with an error.
* - 2
- ``ExitCode.input_file``
- The input file does not seem to be a valid PDF.
* - 3
- ``ExitCode.missing_dependency``
- An external program required by OCRmyPDF is missing.
* - 4
- ``ExitCode.invalid_output_pdf``
- An output file was created, but it does not seem to be a valid PDF. The file will be available.
* - 5
- ``ExitCode.file_access_error``
- The user running OCRmyPDF does not have sufficient permissions to read the input file and write the output file.
* - 6
- ``ExitCode.already_done_ocr``
- The file already appears to contain text so it may not need OCR. See output message.
* - 7
- ``ExitCode.child_process_error``
- An error occurred in an external program (child process) and OCRmyPDF cannot continue.
* - 8
- ``ExitCode.encrypted_pdf``
- The input PDF is encrypted. OCRmyPDF does not read encrypted PDFs. Use another program such as ``qpdf`` to remove encryption.
* - 9
- ``ExitCode.invalid_config``
- A custom configuration file was forwarded to Tesseract using ``--tesseract-config``, and Tesseract rejected this file.
* - 10
- ``ExitCode.pdfa_conversion_failed``
- A valid PDF was created, PDF/A conversion failed. The file will be available.
* - 15
- ``ExitCode.other_error``
- Some other error occurred.
* - 130
- ``ExitCode.ctrl_c``
- The program was interrupted by pressing Ctrl+C.
Debugging the intermediate files
================================
OCRmyPDF normally saves its intermediate results to a temporary folder
and deletes this folder when it exits, whether it succeeded or failed.
If the ``-k`` argument is issued on the command line, OCRmyPDF will keep
the temporary folder and print the location, whether it succeeded or
failed (provided the Python interpreter did not crash). An example
message is:
.. code-block:: none
Temporary working files retained at:
/tmp/com.github.ocrmypdf.u20wpz07
The organization of this folder is an implementation detail and subject
to change between releases. However the general organization is that
working files on a per page basis have the page number as a prefix
(starting with page 1), an infix indicates the processing stage, and a
suffix indicates the file type. Some important files include:
- ``_rasterize.png`` - what the input page looks like
- ``_ocr.png`` - the file that is sent to Tesseract for OCR; depending
on arguments this may differ from the presentation image
- ``_pp_deskew.png`` - the image, after deskewing
- ``_pp_clean.png`` - the image, after cleaning with unpaper
- ``_ocr_tess.pdf`` - the OCR file; appears as a blank page with invisible
text embedded
- ``_ocr_tess.txt`` - the OCR text (not necessarily all text on the page,
if the page is mixed format)
- ``fix_docinfo.pdf`` - a temporary file created to fix the PDF DocumentInfo
data structure
- ``graft_layers.pdf`` - the rendered PDF with OCR layers grafted on
- ``pdfa.pdf`` - ``graft_layers.pdf`` after conversion to PDF/A
- ``pdfa.ps`` - a PostScript file used by Ghostscript for PDF/A conversion
- ``optimize.pdf`` - the PDF generated before optimization
- ``optimize.out.pdf`` - the PDF generated by optimization
- ``origin`` - the input file
- ``origin.pdf`` - the input file or the input image converted to PDF
- ``images/*`` - images extracted during the optimization process; here
the prefix indicates a PDF object ID not a page number
+112
View File
@@ -0,0 +1,112 @@
======================
Using the OCRmyPDF API
======================
OCRmyPDF originated as a command line program and continues to have this
legacy, but parts of it can be imported and used in other Python
applications.
Some applications may want to consider running ocrmypdf from a
subprocess call anyway, as this provides isolation of its activities.
Example
=======
OCRmyPDF one high-level function to run its main engine from an
application. The parameters are symmetric to the command line arguments
and largely have the same functions.
.. code-block:: python
import ocrmypdf
ocrmypdf.ocr('input.pdf', 'output.pdf', deskew=True)
With a few exceptions, all of the command line arguments are available
and may be passed as equivalent keywords.
A few differences are that ``verbose`` and ``quiet`` are not available.
Instead, output should be managed by configuring logging.
Parent process requirements
---------------------------
The :func:`ocrmypdf.ocr` function runs OCRmyPDF similar to command line
execution. To do this, it will:
- create a monitoring thread
- create worker processes (forking itself)
- manage the signal flags of worker processes
- execute other subprocesses (forking and executing other programs)
The Python process that calls ``ocrmypdf.ocr()`` must be sufficiently
privileged to perform these actions. If it is not, ``ocrmypdf()`` will
fail.
There is no currently no option to manage how jobs are scheduled other
than the argument ``jobs=`` which will limit the number of worker
processes.
Forking a child process to call ``ocrmypdf.ocr()`` is suggested. That
way your application will survive and remain interactive even if
OCRmyPDF does not.
Programs that call ``ocrmypdf.ocr()`` should also install a SIGBUS signal
handler (except on Windows), to raise an exception if access to a memory
mapped file fails. OCRmyPDF may use memory mapping.
.. warning::
On Windows, the script that calls ``ocrmypdf.ocr()`` must be protected
by an "ifmain" guard (``if __name__ == '__main__'``) or you must use
``ocrmypdf.ocr(...use_threads=True)``. If you do not take at least one
of these steps, Windows process semantics will prevent OCRmyPDF from working
correctly.
Logging
-------
OCRmyPDF will log under loggers named ``ocrmypdf``. In addition, it
imports ``pdfminer`` and ``PIL``, both of which post log messages under
those logging namespaces.
You can configure the logging as desired for your application or call
:func:`ocrmypdf.configure_logging` to configure logging the same way
OCRmyPDF itself does. The command line parameters such as ``--quiet``
and ``--verbose`` have no equivalents in the API; you must use the
provided configuration function or do configuration in a way that suits
your use case.
Progress monitoring
-------------------
OCRmyPDF uses the ``tqdm`` package to implement its progress bars.
:func:`ocrmypdf.configure_logging` will set up logging output to
``sys.stderr`` in a way that is compatible with the display of the
progress bar. Use ``ocrmypdf.ocr(...progress_bar=False)`` to disable
the progress bar.
Exceptions
----------
OCRmyPDF may throw standard Python exceptions, ``ocrmypdf.exceptions.*``
exceptions, some exceptions related to multiprocessing, and
``KeyboardInterrupt``. The parent process should provide an exception
handler. OCRmyPDF will clean up its temporary files and worker processes
automatically when an exception occurs.
Programs that call OCRmyPDF should consider trapping KeyboardInterrupt
so that they allow OCR to terminate with the whole program terminating.
When OCRmyPDF succeeds conditionally, it returns an integer exit code.
Reference
---------
.. autofunction:: ocrmypdf.ocr
.. autoclass:: ocrmypdf.Verbosity
:members:
:undoc-members:
.. autofunction:: ocrmypdf.configure_logging
+43
View File
@@ -0,0 +1,43 @@
=============
API Reference
=============
This page summarizes the rest of the public API. Generally speaking this
should mainly of interest to plugin developers.
ocrmypdf.exceptions
===================
.. automodule:: ocrmypdf.exceptions
:members:
:undoc-members:
ocrmypdf.helpers
================
.. automodule:: ocrmypdf.helpers
:members:
ocrmypdf.hocrtransform
======================
.. automodule:: ocrmypdf.hocrtransform
:members:
ocrmypdf.pdfa
=============
.. automodule:: ocrmypdf.pdfa
:members:
ocrmypdf.quality
================
.. automodule:: ocrmypdf.quality
:members:
ocrmypdf.subprocess
===================
.. automodule:: ocrmypdf.subprocess
:members:
+224
View File
@@ -0,0 +1,224 @@
================
Batch processing
================
This article provides information about running OCRmyPDF on multiple
files or configuring it as a service triggered by file system events.
Batch jobs
==========
Consider using the excellent `GNU
Parallel <https://www.gnu.org/software/parallel/>`__ to apply OCRmyPDF
to multiple files at once.
Both ``parallel`` and ``ocrmypdf`` will try to use all available
processors. To maximize parallelism without overloading your system with
processes, consider using ``parallel -j 2`` to limit parallel to running
two jobs at once.
This command will run all ocrmypdf all files named ``*.pdf`` in the
current directory and write them to the previous created ``output/``
folder. It will not search subdirectories.
The ``--tag`` argument tells parallel to print the filename as a prefix
whenever a message is printed, so that one can trace any errors to the
file that produced them.
.. code-block:: bash
parallel --tag -j 2 ocrmypdf '{}' 'output/{}' ::: *.pdf
OCRmyPDF automatically repairs PDFs before parsing and gathering
information from them.
Directory trees
===============
This will walk through a directory tree and run OCR on all files in
place, printing the output in a way that makes
.. code-block:: bash
find . -printf '%p' -name '*.pdf' -exec ocrmypdf '{}' '{}' \;
Alternatively, with a docker container (mounts a volume to the container
where the PDFs are stored):
.. code-block:: bash
find . -printf '%p' -name '*.pdf' -exec docker run --rm -v <host dir>:<container dir> jbarlow83/ocrmypdf '<container dir>/{}' '<container dir>/{}' \;
This only runs one ``ocrmypdf`` process at a time. This variation uses
``find`` to create a directory list and ``parallel`` to parallelize runs
of ``ocrmypdf``, again updating files in place.
.. code-block:: bash
find . -name '*.pdf' | parallel --tag -j 2 ocrmypdf '{}' '{}'
In a Windows batch file, use
.. code-block:: bat
for /r %%f in (*.pdf) do ocrmypdf %%f %%f
Sample script
-------------
This user contributed script also provides an example of batch
processing.
.. literalinclude:: ../misc/batch.py
:caption: misc/batch.py
Synology DiskStations
---------------------
Synology DiskStations (Network Attached Storage devices) can run the
Docker image of OCRmyPDF if the Synology `Docker
package <https://www.synology.com/en-global/dsm/packages/Docker>`__ is
installed. Attached is a script to address particular quirks of using
OCRmyPDF on one of these devices.
This is only possible for x86-based Synology products. Some Synology
products use ARM or Power processors and do not support Docker. Further
adjustments might be needed to deal with the Synology's relatively
limited CPU and RAM.
.. literalinclude:: ../misc/synology.py
:caption: misc/synology.py - Sample script for Synology DiskStations
Huge batch jobs
---------------
If you have thousands of files to work with, contact the author.
Consulting work related to OCRmyPDF helps fund this open source project
and all inquiries are appreciated.
Hot (watched) folders
=====================
Watched folders with watcher.py
-------------------------------
OCRmyPDF has a folder watcher called watcher.py, which is currently included in source
distributions but not part of the main program. It may be used natively or may run
in a Docker container. Native instances tend to give better performance. watcher.py
works on all platforms.
Users may need to customize the script to meet their requirements.
.. code-block:: bash
pip3 install -r requirements/watcher.txt
env OCR_INPUT_DIRECTORY=/mnt/input-pdfs \
OCR_OUTPUT_DIRECTORY=/mnt/output-pdfs \
OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1 \
python3 watcher.py
.. csv-table:: watcher.py environment variables
:header: "Environment variable", "Description"
:widths: 50, 50
"OCR_INPUT_DIRECTORY", "Set input directory to monitor (recursive)"
"OCR_OUTPUT_DIRECTORY", "Set output directory (should not be under input)"
"OCR_ON_SUCCESS_DELETE", "This will delete the input file if the exit code is 0 (OK)"
"OCR_OUTPUT_DIRECTORY_YEAR_MONTH", "This will place files in the output in ``{output}/{year}/{month}/{filename}``"
"OCR_DESKEW", "Apply deskew to crooked input PDFs"
"OCR_JSON_SETTINGS", "A JSON string specifying any other arguments for ``ocrmypdf.ocr``, e.g. ``'OCR_JSON_SETTINGS={"rotate_pages": true}'``.
"OCR_POLL_NEW_FILE_SECONDS", "Polling interval"
"OCR_LOGLEVEL", "Level of log messages to report"
One could configure a networked scanner or scanning computer to drop files in the
watched folder.
Watched folders with Docker
---------------------------
The watcher service is included in the OCRmyPDF Docker image. To run it:
.. code-block:: bash
docker run \
-v <path to files to convert>:/input \
-v <path to store results>:/output \
-e OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1 \
-e OCR_ON_SUCCESS_DELETE=1 \
-e OCR_DESKEW=1 \
-e PYTHONUNBUFFERED=1 \
-it --entrypoint python3 \
jbarlow83/ocrmypdf \
watcher.py
This service will watch for a file that matches ``/input/\*.pdf`` and will
convert it to a OCRed PDF in ``/output/``. The parameters to this image are:
.. csv-table:: watcher.py parameters for Docker
:header: "Parameter", "Description"
:widths: 50, 50
"``-v <path to files to convert>:/input``", "Files placed in this location will be OCRed"
"``-v <path to store results>:/output``", "This is where OCRed files will be stored"
"``-e OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1``", "Define environment variable OCR_OUTPUT_DIRECTORY_YEAR_MONTH=1"
"``-e OCR_ON_SUCCESS_DELETE=1``", "Define environment variable"
"``-e OCR_DESKEW=1``", "Define environment variable"
"``-e PYTHONBUFFERED=1``", "This will force STDOUT to be unbuffered and allow you to see messages in docker logs"
This service relies on polling to check for changes to the filesystem. It
may not be suitable for some environments, such as filesystems shared on a
slow network.
A configuration manager such as Docker Compose could be used to ensure that the
service is always available.
.. literalinclude:: ../misc/docker-compose.example.yml
:language: yaml
:caption: misc/docker-compose.example.yml
Caveats
-------
- ``watchmedo`` may not work properly on a networked file system,
depending on the capabilities of the file system client and server.
- This simple recipe does not filter for the type of file system event,
so file copies, deletes and moves, and directory operations, will all
be sent to ocrmypdf, producing errors in several cases. Disable your
watched folder if you are doing anything other than copying files to
it.
- If the source and destination directory are the same, watchmedo may
create an infinite loop.
- On BSD, FreeBSD and older versions of macOS, you may need to increase
the number of file descriptors to monitor more files, using
``ulimit -n 1024`` to watch a folder of up to 1024 files.
Alternatives
------------
- On Linux, `systemd user services <https://wiki.archlinux.org/index.php/Systemd/User>`__
can be configured to automatically perform OCR on a collection of files.
- `Watchman <https://facebook.github.io/watchman/>`__ is a more
powerful alternative to ``watchmedo``.
AWS Lambda is not viable
------------------------
AWS Lambda and its equivalents have low limits on execution time and payload
size, relative to OCRmyPDF's needs. As of this writing, the request/response
payload for AWS Lambda was 6 MB, which means many PDFs will not fit.
macOS Automator
===============
You can use the Automator app with macOS, to create a Workflow or Quick
Action. Use a *Run Shell Script* action in your workflow. In the context
of Automator, the ``PATH`` may be set differently your Terminal's
``PATH``; you may need to explicitly set the PATH to include
``ocrmypdf``. The following example may serve as a starting point:
.. figure:: images/macos-workflow.png
:alt: Example macOS Automator workflow
You may customize the command sent to ocrmypdf.
Executable
+373
View File
@@ -0,0 +1,373 @@
#!/usr/bin/env python3
#
# ocrmypdf documentation build configuration file, created by
# sphinx-quickstart on Sun Sep 4 14:29:43 2016.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath('.'))
"""isort:skip_file"""
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = ['sphinx.ext.napoleon']
napoleon_use_rtype = False
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
#
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = 'ocrmypdf'
copyright = (
'2020, James R. Barlow. Licensed under Creative Commons Attribution-ShareAlike 4.0.'
)
author = 'James R. Barlow'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
import os
on_rtd = os.environ.get('READTHEDOCS') == 'True'
if on_rtd:
# Help ReadTheDocs avoid having to install any binary extension modules
import sys
from unittest.mock import MagicMock
class Mock(MagicMock):
@classmethod
def __getattr__(cls, name):
return MagicMock()
MOCK_MODULES = [
'pikepdf',
'pikepdf.models',
'pikepdf.models.metadata',
'ocrmypdf.leptonica',
]
sys.modules.update((mod_name, Mock()) for mod_name in MOCK_MODULES)
from pkg_resources import get_distribution, DistributionNotFound
# The full version, including alpha/beta/rc tags.
release = get_distribution('ocrmypdf').version
version = '.'.join(release.split('.')[:2])
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#
# today = ''
#
# Else, today_fmt is used as the format for a strftime call.
#
today_fmt = '%Y-%m-%d'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
# The reST default role (used for this markup: `text`) to use for all
# documents.
#
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
# modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
# keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
import sphinx_rtd_theme
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#
html_theme_options = {'display_version': False}
# Add any paths that contain custom themes here, relative to this directory.
# html_theme_path = []
# The name for this set of Sphinx documents.
# "<project> v<release> documentation" by default.
#
# html_title = 'ocrmypdf v4.2'
# A shorter title for the navigation bar. Default is the same as html_title.
#
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#
# html_logo = "images/logo.svg" # looks bad
# The name of an image file (relative to this directory) to use as a favicon of
# the docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#
# html_extra_path = []
# If not None, a 'Last updated on:' timestamp is inserted at every page
# bottom, using the given strftime format.
# The empty string is equivalent to '%b %d, %Y'.
#
# html_last_updated_fmt = None
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#
# html_additional_pages = {}
# If false, no module index is generated.
#
# html_domain_indices = True
# If false, no index is generated.
#
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
# html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr', 'zh'
#
# html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# 'ja' uses this config value.
# 'zh' user can custom change `jieba` dictionary path.
#
# html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#
# html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'ocrmypdfdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'ocrmypdf.tex', 'ocrmypdf Documentation', 'James R. Barlow', 'manual')
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#
# latex_use_parts = False
# If true, show page references after internal links.
#
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#
# latex_show_urls = False
# Documents to append as an appendix to all manuals.
#
# latex_appendices = []
# It false, will not define \strong, \code, itleref, \crossref ... but only
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
# packages.
#
# latex_keep_old_macro_names = True
# If false, no module index is generated.
#
# latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [(master_doc, 'ocrmypdf', 'ocrmypdf Documentation', [author], 1)]
# If true, show URL addresses after external links.
#
# man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(
master_doc,
'ocrmypdf',
'ocrmypdf Documentation',
author,
'ocrmypdf',
'One line description of project.',
'Miscellaneous',
)
]
# Documents to append as an appendix to all manuals.
#
# texinfo_appendices = []
# If false, no module index is generated.
#
# texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#
# texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#
# texinfo_no_detailmenu = False
+67
View File
@@ -0,0 +1,67 @@
=======================
Contributing guidelines
=======================
Contributions are welcome!
Big changes
===========
Please open a new issue to discuss or propose a major change. Not only is it fun
to discuss big ideas, but we might save each other's time too. Perhaps some of the
work you're contemplating is already half-done in a development branch.
Code style
==========
We use PEP8, ``black`` for code formatting and ``isort`` for import sorting. The
settings for these programs are in ``pyproject.toml`` and ``setup.cfg``. Pull
requests should follow the style guide. One difference we use from "black" style
is that strings shown to the user are always in double quotes (``"``) and strings
for internal uses are in single quotes (``'``).
Tests
=====
New features should come with tests that confirm their correctness.
New Python dependencies
=======================
If you are proposing a change that will require a new Python dependency, we
prefer dependencies that are already packaged by Debian or Red Hat. This makes
life much easier for our downstream package maintainers.
Python dependencies must also be license-compatible. GPLv3 or AGPLv3 are likely
incompatible with the project's license, but LGPLv3 is compatible.
New non-Python dependencies
===========================
OCRmyPDF uses several external programs (Tesseract, Ghostscript and others) for
its functionality. In general we prefer to avoid adding new external programs.
Style guide: Is it OCRmyPDF or ocrmypdf?
========================================
The program/project is OCRmyPDF and the name of the executable or library is ocrmypdf.
Known ports/packagers
=====================
OCRmyPDF has been ported to many platforms already. If you are interesting in
porting to a new platform, check with
`Repology <https://repology.org/projects/?search=ocrmypdf>`__ to see the status
of that platform.
Packager maintainers, please ensure that the command line completion scripts in
``misc/`` are installed.
Copyright and license
=====================
For contributions over 10 lines of code, please include your name to list of
copyright holders for that file. The core program is licensed under MPL-2.0,
test files and documentation under CC-BY-SA 4.0, and miscellaneous files under
MIT. Please contribute code only that you wrote and you have the permission to
contribute or license to us.
+359
View File
@@ -0,0 +1,359 @@
========
Cookbook
========
Basic examples
==============
Help!
-----
ocrmypdf has built-in help.
.. code-block:: bash
ocrmypdf --help
Add an OCR layer and convert to PDF/A
-------------------------------------
.. code-block:: bash
ocrmypdf input.pdf output.pdf
Add an OCR layer and output a standard PDF
------------------------------------------
.. code-block:: bash
ocrmypdf --output-type pdf input.pdf output.pdf
Create a PDF/A with all color and grayscale images converted to JPEG
--------------------------------------------------------------------
.. code-block:: bash
ocrmypdf --output-type pdfa --pdfa-image-compression jpeg input.pdf output.pdf
Modify a file in place
----------------------
The file will only be overwritten if OCRmyPDF is successful.
.. code-block:: bash
ocrmypdf myfile.pdf myfile.pdf
Correct page rotation
---------------------
OCR will attempt to automatic correct the rotation of each page. This
can help fix a scanning job that contains a mix of landscape and
portrait pages.
.. code-block:: bash
ocrmypdf --rotate-pages myfile.pdf myfile.pdf
You can increase (decrease) the parameter ``--rotate-pages-threshold``
to make page rotation more (less) aggressive. The threshold number is the ratio
of how confidence the OCR engine is that the document image should be changed,
compared to kept the same. A value of ``15.0`` is the default, and is fairly
conservative. A value of ``2.0`` will produce more rotations, and more false
positives.
If the page is "just a little off horizontal", like a crooked picture,
then you want ``--deskew``. ``--rotate-pages`` is for when the cardinal
angle is wrong.
OCR languages other than English
--------------------------------
OCRmyPDF assumes the document is in English unless told otherwise. OCR
quality may be poor if the wrong language is used.
.. code-block:: bash
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>`.
Unfortunately, the Tesseract OCR engine has no ability to detect the
language when it is unknown.
Produce PDF and text file containing OCR text
---------------------------------------------
This produces a file named "output.pdf" and a companion text file named
"output.txt".
.. code-block:: bash
ocrmypdf --sidecar output.txt input.pdf output.pdf
.. note::
The sidecar file contains the **OCR text** found by OCRmyPDF. If the document
contains pages that already have text, that text will not appear in the
sidecar. If the option ``--pages`` is used, only those pages on which OCR
was performed will be included in the sidecar. If certain pages were skipped
because of options like ``--skip-big`` or ``--tesseract-timeout``, those pages
will not be in the sidecar.
To extract all text from a PDF, whether generated from OCR or otherwise,
use a program like Poppler's ``pdftotext`` or ``pdfgrep``.
OCR images, not PDFs
--------------------
Option: use Tesseract
~~~~~~~~~~~~~~~~~~~~~
If you are starting with images, you can just use Tesseract directly to
convert images to PDFs:
.. code-block:: bash
tesseract my-image.jpg output-prefix pdf
.. code-block:: bash
# When there are multiple images
tesseract text-file-containing-list-of-image-filenames.txt output-prefix pdf
Tesseract's PDF output is quite good  OCRmyPDF uses it internally, in
some cases. However, OCRmyPDF has many features not available in
Tesseract like image processing, metadata control, and PDF/A generation.
Option: use img2pdf
~~~~~~~~~~~~~~~~~~~
You can also use a program like
`img2pdf <https://gitlab.mister-muffin.de/josch/img2pdf>`__ to convert
your images to PDFs, and then pipe the results to run ocrmypdf. The
``-`` tells ocrmypdf to read standard input.
.. code-block:: bash
img2pdf my-images*.jpg | ocrmypdf - myfile.pdf
``img2pdf`` is recommended because it does an excellent job at
generating PDFs without transcoding images.
Option: use OCRmyPDF (single images only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For convenience, OCRmyPDF can also convert single images to PDFs on its
own. If the resolution (dots per inch, DPI) of an image is not set or is
incorrect, it can be overridden with ``--image-dpi``. (As 1 inch is 2.54
cm, 1 dpi = 0.39 dpcm).
.. code-block:: bash
ocrmypdf --image-dpi 300 image.png myfile.pdf
If you have multiple images, you must use ``img2pdf`` to convert the
images to PDF.
Not recommended
~~~~~~~~~~~~~~~
We caution against using ImageMagick or Ghostscript to convert images to
PDF, since they may transcode images or produce downsampled images,
sometimes without warning.
Image processing
================
OCRmyPDF perform some image processing on each page of a PDF, if
desired. The same processing is applied to each page. It is suggested
that the user review files after image processing as these commands
might remove desirable content, especially from poor quality scans.
- ``--rotate-pages`` attempts to determine the correct orientation for
each page and rotates the page if necessary.
- ``--remove-background`` attempts to detect and remove a noisy
background from grayscale or color images. Monochrome images are
ignored. This should not be used on documents that contain color
photos as it may remove them.
- ``--deskew`` will correct pages were scanned at a skewed angle by
rotating them back into place. Skew determination and correction is
performed using `Postl's variance of line
sums <http://www.leptonica.org/skew-measurement.html>`__ algorithm as
implemented in `Leptonica <http://www.leptonica.org/index.html>`__.
- ``--clean`` uses
`unpaper <https://www.flameeyes.eu/projects/unpaper>`__ to clean up
pages before OCR, but does not alter the final output. This makes it
less likely that OCR will try to find text in background noise.
- ``--clean-final`` uses unpaper to clean up pages before OCR and
inserts the page into the final output. You will want to review each
page to ensure that unpaper did not remove something important.
.. note::
In many cases image processing will rasterize PDF pages as images,
potentially losing quality.
.. warning::
``--clean-final`` and ``-remove-background`` may leave undesirable
visual artifacts in some images where their algorithms have
shortcomings. Files should be visually reviewed after using these
options.
Example: OCR and correct document skew (crooked scan)
-----------------------------------------------------
Deskew:
.. code-block:: bash
ocrmypdf --deskew input.pdf output.pdf
Image processing commands can be combined. The order in which options
are given does not matter. OCRmyPDF always applies the steps of the
image processing pipeline in the same order (rotate, remove background,
deskew, clean).
.. code-block:: bash
ocrmypdf --deskew --clean --rotate-pages input.pdf output.pdf
Don't actually OCR my PDF
=========================
If you set ``--tesseract-timeout 0`` OCRmyPDF will apply its image
processing without performing OCR, if all you want to is to apply image
processing or PDF/A conversion.
.. code-block:: bash
ocrmypdf --tesseract-timeout=0 --remove-background input.pdf output.pdf
Optimize images without performing OCR
--------------------------------------
You can also optimize all images without performing any OCR:
.. code-block:: bash
ocrmypdf --tesseract-timeout=0 --optimize 3 --skip-text input.pdf output.pdf
Perform OCR only certain pages
------------------------------
You can ask OCRmyPDF to only apply OCR to certain pages.
.. code-block:: bash
ocrmypdf --pages 2,3,13-17 input.pdf output.pdf
Hyphens denote a range of pages and commas separate page numbers. If you prefer
to use spaces, quote all of the page numbers: ``--pages '2, 3, 5, 7'``.
OCRmyPDF will warn if your list of page numbers contains duplicates or
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
counts the number of virtual pieces of paper since the start.
Regardless of the argument to ``--pages``, OCRmyPDF will optimize all pages in
the file and convert it to PDF/A, unless you disable those options. In this
example, we want to OCR only the title and otherwise change the PDF as little
as possible:
.. code-block:: bash
ocrmypdf --pages 1 --output-type pdf --optimize 0 input.pdf output.pdf
Redo existing OCR
=================
To redo OCR on a file OCRed with other OCR software or a previous
version of OCRmyPDF and/or Tesseract, you may use the ``--redo-ocr``
argument. (Normally, OCRmyPDF will exit with an error if asked to modify
a file with OCR.)
This may be helpful for users who want to take advantage of accuracy
improvements in Tesseract 4.0 for files they previously OCRed with an
earlier version of Tesseract and OCRmyPDF.
.. code-block:: bash
ocrmypdf --redo-ocr input.pdf output.pdf
This method will replace OCR without rasterizing, reducing quality or
removing vector content. If a file contains a mix of pure digital text
and OCR, digital text will be ignored and OCR will be replaced. As such
this mode is incompatible with image processing options, since they
alter the appearance of the file.
In some cases, existing OCR cannot be detected or replaced. Files
produced by OCRmyPDF v2.2 or earlier, for example, are internally
represented as having visible text with an opaque image drawn on top.
This situation cannot be detected.
If ``--redo-ocr`` does not work, you can use ``--force-ocr``, which will
force rasterization of all pages, potentially reducing quality or losing
vector content.
Improving OCR quality
=====================
The `Image processing <#image-processing>`__ features can improve OCR
quality.
Rotating pages and deskewing helps to ensure that the page orientation
is correct before OCR begins. Removing the background and/or cleaning
the page can also improve results. The ``--oversample DPI`` argument can
be specified to resample images to higher resolution before attempting
OCR; this can improve results as well.
OCR quality will suffer if the resolution of input images is not correct
(since the range of pixel sizes that will be checked for possible fonts
will also be incorrect).
PDF optimization
================
By default OCRmyPDF will attempt to perform lossless optimizations on
the images inside PDFs after OCR is complete. Optimization is performed
even if no OCR text is found.
The ``--optimize N`` (short form ``-O``) argument controls optimization,
where ``N`` ranges from 0 to 3 inclusive, analogous to the optimization
levels in the GCC compiler.
.. list-table::
:widths: auto
:header-rows: 1
* - Level
- Comments
* - ``--optimize 0``
- Disables optimization.
* - ``--optimize 1``
- Enables lossless optimizations, such as transcoding images to more
efficient formats. Also compress other uncompressed objects in the
PDF and enables the more efficient "object streams" within the PDF.
* - ``--optimize 2``
- All of the above, and enables lossy optimizations and color quantization.
* - ``--optimize 3``
- All of the above, and enables more aggressive optimizations and targets lower image quality.
Optimization is improved when a JBIG2 encoder is available and when
``pngquant`` is installed. If either of these components are missing,
then some types of images cannot be optimized.
The types of optimization available may expand over time. By default,
OCRmyPDF compresses data streams inside PDFs, and will change
inefficient compression modes to more modern versions. A program like
``qpdf`` can be used to change encodings, e.g. to inspect the internals
fo a PDF.
.. code-block:: bash
ocrmypdf --optimize 3 in.pdf out.pdf # Make it small
Some users may consider enabling lossy JBIG2. See: :ref:`jbig2-lossy`.
+169
View File
@@ -0,0 +1,169 @@
=====================
OCRmyPDF Docker image
=====================
OCRmyPDF is also available in a Docker image that packages recent
versions of all dependencies.
For users who already have Docker installed this may be an easy and
convenient option. However, it is less performant than a system
installation and may require Docker engine configuration.
OCRmyPDF needs a generous amount of RAM, CPU cores, temporary storage
space, whether running in a Docker container or on its own. It may be
necessary to ensure the container is provisioned with additional
resources.
.. _docker-install:
Installing the Docker image
===========================
If you have `Docker <https://docs.docker.com/>`__ installed on your
system, you can install a Docker image of the latest release.
If you can run this command successfully, your system is ready to download and
execute the image:
.. code-block:: bash
docker run hello-world
The recommended OCRmyPDF Docker image is currently named ``ocrmypdf``:
.. code-block:: bash
docker pull jbarlow83/ocrmypdf
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
# Optional step for Mac OS X users
docker-machine stop "yourVM"
VBoxManage modifyvm "yourVM" --cpus 2 # or whatever number of core is desired
docker-machine start "yourVM"
eval $(docker-machine env "yourVM")
See the Docker documentation for
`adjusting memory and CPU on other platforms <https://docs.docker.com/config/containers/resource_constraints/>`__.
Using the Docker image on the command line
==========================================
**Unlike typical Docker containers**, in this section the OCRmyPDF Docker
container is emphemeral it runs for one OCR job and terminates, just like a
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 start a Docker container (instance of the image):
.. code-block:: bash
docker tag jbarlow83/ocrmypdf ocrmypdf
docker run --rm -i ocrmypdf (... all other arguments here...) - -
For convenience, create a shell alias to hide the Docker command. It is
easier to send the input file as stdin and read the output from
stdout **this avoids the messy permission issues with Docker entirely**.
.. code-block:: bash
alias docker_ocrmypdf='docker run --rm -i ocrmypdf'
docker_ocrmypdf --version # runs docker version
docker_ocrmypdf - - <input.pdf >output.pdf
Or in the wonderful `fish shell <https://fishshell.com/>`__:
.. code-block:: fish
alias docker_ocrmypdf 'docker run --rm ocrmypdf'
funcsave docker_ocrmypdf
Alternately, you could mount the local current working directory as a
Docker volume:
.. code-block:: bash
alias docker_ocrmypdf='docker run --rm -i --user "$(id -u):$(id -g)" --workdir /data -v "$PWD:/data" ocrmypdf'
docker_ocrmypdf /data/input.pdf /data/output.pdf
.. _docker-lang-packs:
Adding languages to the Docker image
====================================
By default the Docker image includes English, German, Simplified Chinese,
French, Portuguese and Spanish, the most popular languages for OCRmyPDF
users based on feedback. You may add other languages by creating a new
Dockerfile based on the public one:
.. code-block:: dockerfile
FROM jbarlow83/ocrmypdf
# Add French
RUN apt install tesseract-ocr-fra
You can also copy training data to ``/usr/share/tesseract-ocr/<tesseract version>/tessdata``.
Executing the test suite
========================
The OCRmyPDF test suite is installed with image. To run it:
.. code-block:: bash
docker run --entrypoint python3 jbarlow83/ocrmypdf -m pytest
Accessing the shell
===================
To use the bash shell in the Docker image:
.. code-block:: bash
docker run -it --entrypoint bash jbarlow83/ocrmypdf
Using the OCRmyPDF web service wrapper
======================================
The OCRmyPDF Docker image includes an example, barebones HTTP web
service. The webservice may be launched as follows:
.. code-block:: bash
docker run --entrypoint python3 -p 5000:5000 jbarlow83/ocrmypdf webservice.py
This will configure the machine to listen on port 5000. On Linux machines
this is port 5000 of localhost. On macOS or Windows machines running
Docker, this is port 5000 of the virtual machine that runs your Docker
images. You can find its IP address using the command ``docker-machine ip``.
Unlike command line usage this program will open a socket and wait for
connections.
.. warning::
The OCRmyPDF web service wrapper is intended for demonstration or
development. It provides no security, no authentication, no
protection against denial of service attacks, and no load balancing.
The default Flask WSGI server is used, which is intended for
development only. The server is single-threaded and so can respond to
only one client at a time. While running OCR, it cannot respond to
any other clients.
Clients must keep their open connection while waiting for OCR to
complete. This may entail setting a long timeout; this interface is more
useful for internal HTTP API calls.
Unlike the rest of OCRmyPDF, this web service is licensed under the
Affero GPLv3 (AGPLv3) since Ghostscript is also licensed in this way.
In addition to the above, please read our
:ref:`general remarks on using OCRmyPDF as a service <ocr-service>`.
+53
View File
@@ -0,0 +1,53 @@
=====================
Common error messages
=====================
Page already has text
=====================
.. code-block::
ERROR - 1: page already has text! aborting (use --force-ocr to force OCR)
You ran ocrmypdf on a file that already contains printable text or a
hidden OCR text layer (it can't quite tell the difference). You probably
don't want to do this, because the file is already searchable.
As the error message suggests, your options are:
- ``ocrmypdf --force-ocr`` to :ref:`rasterize <raster-vector>` all
vector content and run OCR on the images. This is useful if a
previous OCR program failed, or if the document contains a text
watermark.
- ``ocrmypdf --skip-text`` to skip OCR and other processing on any
pages that contain text. Text pages will be copied into the output
PDF without modification.
- ``ocrmypdf --redo-ocr`` to scan the file for any existing OCR
(non-printing text), remove it, and do OCR again. This is one way
to take advantage of improvements in OCR accuracy. Printable vector
text is excluded from OCR, so this can be used on files that contain
a mix of digital and scanned files.
Input file 'filename' is not a valid PDF
========================================
OCRmyPDF checks files with pikepdf, a library that in turn uses libqpdf to fixes
errors in PDFs, before it tries to work on them. In most cases this happens
because the PDF is corrupt and truncated (incomplete file copying) and not much
can be done.
You can try rewriting the file with Ghostscript:
.. code-block:: bash
gs -o output.pdf -dSAFER -sDEVICE=pdfwrite input.pdf
``pdftk`` can also rewrite PDFs:
.. code-block:: bash
pdftk input.pdf cat output output.pdf
Sometimes Acrobat can repair PDFs with its `Preflight
tool <https://helpx.adobe.com/acrobat/using/correcting-problem-areas-preflight-tool.html>`__.
+35
View File
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 915 585" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<linearGradient id="b" y2="445" gradientUnits="userSpaceOnUse" y1="179" gradientTransform="translate(0 -2.06)" x2="-29.7" x1="322">
<stop stop-color="#333" offset="0"/>
<stop stop-color="#fff" stop-opacity="0" offset="1"/>
</linearGradient>
<linearGradient id="a" y2="414" gradientUnits="userSpaceOnUse" y1="159" x2="490" x1="815">
<stop stop-color="#33f" offset="0"/>
<stop stop-color="#3f3fff" stop-opacity="0" offset="1"/>
</linearGradient>
</defs>
<path fill="url(#b)" d="m403 247c-12 115-135 122-368 123-4.3-1.07-7.32-7.33-6-41 76-37 151-124 167-236 93 123 201 40.9 207 154z"/>
<g stroke-width="3.45" fill="none">
<path stroke="#000" d="m11.8 11.8h411v411l-411 0.01v-411z"/>
<path stroke="#448" d="m489 11.7h415v411h-415v-411z"/>
</g>
<path d="m876 244c-12 115-133 120-366 121-6-14-10-40-3-43 76-37.3 136-106 152-218 38 48 209 101 217 140z" fill="url(#a)"/>
<g id="RasterLarge" transform="matrix(1.36 0 0 1.28 -161 -636)">
<path fill="#999" d="m287 730h-30v-60h20v20h10v10h10v10h20v10h40v-10h20v-20h10v-20h-10v-10h-10v-10h-40v-10h-30v-10h-20v-10h-10v-10h-10v-60h10v-10h10v-10h10v-10h90v10h30v60h-20v-10h-10v-20h-10v-10h-20v-10h-30v10h-20v10h-10v20h10v20h30v10h30v10h30v10h10v10h10v10h10v60h-10v10h-10v10h-10v10h-100v-10z"/>
<path fill="#555" d="m297 730h-30v-10h-10v-50h20v20h10v20h10 10v10h60v-10h10v-10h10v-40h-20v-10h-20v-10h-40v-10h-20v-10h-20v-20h-10v-50h10v-10h10v-10h20v-10h70v10h30v10h10v50h-20v-20h-10v-20h-20v-10h-50v10h-10v10h-10v30h10v10h20v10h30v10h30v10h20v10h10v10h10v50h-10v20h-10v10h-20v10h-80z"/>
<path d="m307 730h-30v-10h-20v-50h20v30h10v10h10v10h70v-10h20v-50h-10v-10h-20v-10h-40v-10h-30v-10h-10v-10h-10v-20h-10v-30h10v-20h20v-10h20v-10h50v10h30v10h20v50h-20v-30h-10v-10h-10v-10h-70v10h-10v40h10v10h10v10h30v10h40v10h20v10h10v20h10v40h-10v20h-20v10h-30v10h-50v-10z"/>
</g>
<g font-size="40" font-family="sans-serif" text-anchor="middle">
<g font-size="100">
<text y="518" x="210">Raster</text>
<text y="518" x="695" fill="#338">Vector</text>
</g>
<text y="563" x="210">.jpeg .gif .png</text>
<text y="563" x="696" fill="#338">.svg</text>
</g>
<path id="VectorLarge" fill="#005" d="m661 294v-62.5l23.4 0.184c0.678 20.8 7.32 36.3 19.9 46.4 12.7 9.93 32.1 14.9 57.9 14.9 24.1 0 42.5-4.29 55.1-12.9 12.7-8.71 19.1-21.3 19.1-37.9 0-13.2-3.86-23.4-11.6-30.5-7.59-7.11-23.7-14-48.4-20.8l-40.1-10.9c-29-7.97-49.5-17.9-61.4-29.8-11.8-11.9-17.7-28.2-17.7-48.9 0-23.3 9.15-41.4 27.4-54.3 18.3-12.9 44-19.3 77.1-19.3 14.1 0 29.6 1.41 46.4 4.23 16.8 2.7 34.7 6.68 53.7 12v58.5h-23c-2.3-19.4-9.49-33.4-21.6-41.9-11.9-8.71-30.2-13.1-54.7-13.1-21.4 0-37.7 3.99-49 12-11.1 7.85-16.7 19.3-16.7 34.4 0 13.1 4.2 23.4 12.6 30.9 8.4 7.48 26.2 14.9 53.5 22.3l37.6 10.1c27.5 7.48 47.1 17 58.8 28.7 11.8 11.5 17.7 27 17.7 46.5 0 26.6-9.42 46.7-28.3 60.2s-46.9 20.2-84.2 20.2c-16.7 0-33.7-1.53-51-4.6-18-3-35-7-53-13z"/>
<use xlink:href="#VectorLarge" transform="matrix(.17 0 0 .17 392 313)"/>
<use xlink:href="#RasterLarge" transform="matrix(.173 0 0 .173 -8.25 314)" height="100%" width="100%" y="0" x="0"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

+75
View File
@@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 503 227" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;">
<g id="svg" transform="matrix(0.965977,0,0,0.807602,0,0)">
<rect x="0" y="0" width="520" height="280" style="fill:white;"/>
<g transform="matrix(1.03522,0,0,1.23823,-69.7528,-83.422)">
<g transform="matrix(1,0,0,1,243.977,20.0703)">
<g id="Page">
<g transform="matrix(0.961773,0,0,1.05962,6.19811,-3.01071)">
<path d="M328.5,97.682C328.5,96.465 327.983,95.296 327.056,94.418C320.026,87.758 289.442,58.78 282.228,51.944C281.251,51.019 279.901,50.496 278.49,50.496C264.493,50.496 188.083,50.496 167.339,50.496C164.468,50.496 162.141,52.609 162.141,55.214C162.141,83.051 162.141,225.565 162.141,253.4C162.141,256.005 164.468,258.117 167.338,258.117C192.242,258.117 299.159,258.117 323.538,258.117C326.278,258.117 328.5,256.101 328.5,253.613C328.5,229.268 328.5,113.896 328.5,97.682Z" style="fill:rgb(253,253,253);stroke:rgb(51,51,51);stroke-width:3.95px;"/>
</g>
<g id="Dog-ear" serif:id="Dog ear" transform="matrix(1,0,0,1,-4,2)">
<path d="M277.072,48.496L277.072,93.848C277.072,95.172 277.598,96.441 278.534,97.377C279.47,98.313 280.739,98.839 282.063,98.839C294.548,98.839 326.141,98.839 326.141,98.839" style="fill:rgb(245,245,245);stroke:rgb(51,51,51);stroke-width:4px;"/>
</g>
</g>
<g transform="matrix(1,0,0,1,-29.6816,-0.395178)">
<g transform="matrix(1.00243,0,0,1.11818,-144.72,-8.80181)">
<path d="M465.73,119.654C465.73,117.605 463.874,115.941 461.588,115.941L310.259,115.941C307.973,115.941 306.117,117.605 306.117,119.654L306.117,183.108C306.117,185.157 307.973,186.821 310.259,186.821L461.588,186.821C463.874,186.821 465.73,185.157 465.73,183.108L465.73,119.654Z" style="fill:rgb(248,0,0);stroke:white;stroke-width:3.77px;"/>
</g>
<g transform="matrix(1.24571,0,0,1.35864,116.812,84.3924)">
<g transform="matrix(64,0,0,64,42.1437,77.6203)">
<path d="M0.084,0L0.084,-0.68L0.297,-0.68C0.371,-0.68 0.434,-0.663 0.487,-0.63C0.54,-0.596 0.566,-0.54 0.566,-0.462C0.566,-0.385 0.538,-0.328 0.481,-0.292C0.424,-0.255 0.36,-0.237 0.288,-0.237L0.213,-0.237L0.213,0L0.084,0ZM0.293,-0.572L0.213,-0.572L0.213,-0.344L0.295,-0.344C0.334,-0.344 0.365,-0.353 0.389,-0.371C0.413,-0.388 0.426,-0.416 0.429,-0.454C0.429,-0.498 0.417,-0.529 0.393,-0.546C0.369,-0.563 0.336,-0.572 0.293,-0.572Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(64,0,0,64,79.7117,77.6203)">
<path d="M0.332,0L0.084,0L0.084,-0.68L0.336,-0.68C0.441,-0.68 0.518,-0.648 0.569,-0.585C0.62,-0.522 0.645,-0.441 0.645,-0.344C0.645,-0.239 0.618,-0.155 0.563,-0.093C0.508,-0.031 0.431,0 0.332,0ZM0.337,-0.57L0.213,-0.57L0.213,-0.109L0.33,-0.109C0.385,-0.109 0.429,-0.127 0.462,-0.163C0.495,-0.199 0.511,-0.259 0.511,-0.344C0.511,-0.415 0.497,-0.47 0.469,-0.51C0.441,-0.55 0.397,-0.57 0.337,-0.57Z" style="fill:white;fill-rule:nonzero;"/>
</g>
<g transform="matrix(64,0,0,64,123.424,77.6203)">
<path d="M0.405,-0.288L0.213,-0.288L0.213,0L0.084,0L0.084,-0.68L0.469,-0.68L0.489,-0.578L0.213,-0.578L0.213,-0.389L0.386,-0.389L0.405,-0.288Z" style="fill:white;fill-rule:nonzero;"/>
</g>
</g>
</g>
</g>
<g transform="matrix(1,0,0,1.52217,67.3796,10.7507)">
<rect x="23.501" y="81.3" width="162.305" height="61.77" style="fill:rgb(180,213,255);"/>
</g>
<g transform="matrix(0.967536,0,0,0.961535,5.90498,47.9703)">
<g transform="matrix(90.4804,0,0,90.4804,82.6698,167.705)">
<path d="M0.057,-0.337C0.057,-0.442 0.084,-0.527 0.139,-0.594C0.194,-0.66 0.271,-0.694 0.37,-0.696C0.477,-0.696 0.556,-0.662 0.607,-0.593C0.658,-0.524 0.684,-0.441 0.684,-0.344C0.684,-0.239 0.657,-0.153 0.602,-0.086C0.547,-0.019 0.469,0.014 0.37,0.014C0.264,0.014 0.185,-0.02 0.134,-0.089C0.083,-0.157 0.057,-0.24 0.057,-0.337ZM0.192,-0.338C0.192,-0.267 0.206,-0.208 0.235,-0.163C0.264,-0.118 0.308,-0.095 0.369,-0.095C0.424,-0.095 0.467,-0.115 0.5,-0.156C0.533,-0.197 0.549,-0.259 0.549,-0.344C0.549,-0.415 0.535,-0.473 0.506,-0.518C0.477,-0.563 0.433,-0.586 0.372,-0.586C0.319,-0.586 0.275,-0.564 0.242,-0.519C0.209,-0.474 0.192,-0.414 0.192,-0.338Z" style="fill:rgb(51,51,51);fill-rule:nonzero;"/>
</g>
<g transform="matrix(90.4804,0,0,90.4804,147.906,167.705)">
<path d="M0.505,-0.557C0.473,-0.567 0.448,-0.574 0.429,-0.579C0.41,-0.583 0.388,-0.585 0.361,-0.585C0.307,-0.585 0.265,-0.563 0.236,-0.519C0.207,-0.475 0.192,-0.415 0.192,-0.338C0.192,-0.272 0.204,-0.215 0.229,-0.167C0.254,-0.119 0.295,-0.095 0.353,-0.095C0.382,-0.095 0.409,-0.098 0.434,-0.104C0.459,-0.11 0.481,-0.117 0.502,-0.126L0.551,-0.03C0.525,-0.017 0.494,-0.006 0.457,0.002C0.42,0.01 0.388,0.014 0.36,0.014C0.254,0.014 0.177,-0.02 0.129,-0.088C0.081,-0.156 0.057,-0.239 0.057,-0.337C0.057,-0.442 0.084,-0.527 0.137,-0.594C0.19,-0.661 0.266,-0.694 0.365,-0.694C0.385,-0.694 0.413,-0.691 0.448,-0.684C0.483,-0.677 0.516,-0.666 0.545,-0.65L0.505,-0.557Z" style="fill:rgb(51,51,51);fill-rule:nonzero;"/>
</g>
<g transform="matrix(90.4804,0,0,90.4804,199.751,167.705)">
<path d="M0.293,-0.572L0.213,-0.572L0.213,-0.364L0.295,-0.364C0.334,-0.364 0.366,-0.372 0.391,-0.388C0.416,-0.403 0.429,-0.429 0.429,-0.465C0.429,-0.503 0.417,-0.53 0.393,-0.547C0.369,-0.564 0.336,-0.572 0.293,-0.572ZM0.479,0L0.335,-0.26C0.328,-0.259 0.32,-0.259 0.312,-0.259C0.304,-0.258 0.296,-0.258 0.288,-0.258L0.213,-0.258L0.213,0L0.084,0L0.084,-0.68L0.297,-0.68C0.371,-0.68 0.434,-0.663 0.487,-0.629C0.54,-0.595 0.566,-0.542 0.566,-0.471C0.566,-0.429 0.555,-0.393 0.534,-0.363C0.512,-0.332 0.484,-0.309 0.45,-0.292L0.617,0L0.479,0Z" style="fill:rgb(51,51,51);fill-rule:nonzero;"/>
</g>
</g>
<g transform="matrix(0.916882,0,0,1,121.475,-32.6535)">
<g transform="matrix(86.953,0,0,86.953,152.996,241.878)">
<path d="M0.479,-0.428C0.5,-0.451 0.527,-0.47 0.562,-0.484C0.596,-0.497 0.627,-0.504 0.654,-0.504C0.72,-0.504 0.767,-0.485 0.795,-0.446C0.822,-0.407 0.836,-0.36 0.836,-0.304L0.836,0L0.705,0L0.705,-0.298C0.705,-0.329 0.698,-0.352 0.683,-0.369C0.668,-0.385 0.647,-0.393 0.619,-0.393C0.6,-0.393 0.581,-0.388 0.56,-0.378C0.539,-0.368 0.521,-0.357 0.504,-0.344C0.505,-0.337 0.505,-0.331 0.506,-0.324C0.507,-0.317 0.507,-0.311 0.507,-0.304L0.507,0L0.376,0L0.376,-0.298C0.376,-0.329 0.369,-0.352 0.354,-0.369C0.339,-0.385 0.318,-0.393 0.291,-0.393C0.274,-0.393 0.258,-0.39 0.241,-0.383C0.224,-0.376 0.207,-0.367 0.192,-0.356L0.192,0L0.062,0L0.062,-0.485L0.13,-0.485L0.162,-0.441C0.184,-0.461 0.211,-0.476 0.242,-0.488C0.273,-0.499 0.3,-0.504 0.325,-0.504C0.363,-0.504 0.395,-0.497 0.42,-0.484C0.445,-0.47 0.465,-0.451 0.479,-0.428Z" style="fill:rgb(51,51,51);fill-rule:nonzero;"/>
</g>
<g transform="matrix(86.953,0,0,86.953,228.906,241.878)">
<path d="M0.156,0.023L0.179,-0.034L0.006,-0.467L0.14,-0.485L0.252,-0.191L0.358,-0.485L0.495,-0.485L0.278,0.064C0.263,0.103 0.236,0.137 0.197,0.165C0.158,0.193 0.118,0.212 0.075,0.222L0.029,0.115C0.052,0.105 0.077,0.093 0.104,0.079C0.13,0.064 0.147,0.046 0.156,0.023Z" style="fill:rgb(51,51,51);fill-rule:nonzero;"/>
</g>
</g>
<g id="Selectors" transform="matrix(0.965977,0,0,0.807602,67.3796,67.3718)">
<g id="Right-selector" serif:id="Right selector">
<g transform="matrix(1.03522,0,0,1.23823,2.07044,0)">
<path d="M185.806,161.156L185.806,67.132" style="fill:none;stroke:rgb(76,159,255);stroke-width:4px;stroke-linecap:butt;"/>
</g>
<g transform="matrix(1.03522,0,0,1.23823,161.788,169.469)">
<circle cx="31.523" cy="34.314" r="10.021" style="fill:rgb(76,159,255);stroke:rgb(76,159,255);stroke-width:4px;stroke-linecap:butt;"/>
</g>
</g>
<g id="Left-selector" serif:id="Left selector">
<g transform="matrix(1.03522,0,0,1.23823,-170.092,0)">
<path d="M185.806,161.156L185.806,67.132" style="fill:none;stroke:rgb(76,159,255);stroke-width:4px;stroke-linecap:butt;"/>
</g>
<g transform="matrix(1.03522,0,0,1.23823,-10.3742,28.2274)">
<circle cx="31.523" cy="34.314" r="10.021" style="fill:rgb(76,159,255);stroke:rgb(76,159,255);stroke-width:4px;stroke-linecap:butt;"/>
</g>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

+47
View File
@@ -0,0 +1,47 @@
OCRmyPDF documentation
======================
OCRmyPDF adds an optical charcter recognition (OCR) text layer to scanned PDF
files, allowing them to be searched.
PDF is the best format for storing and exchanging scanned documents.
Unfortunately, PDFs can be difficult to modify. OCRmyPDF makes it easy to apply
image processing and OCR to existing PDFs.
.. toctree::
:maxdepth: 1
introduction
release_notes
installation
optimizer
languages
jbig2
.. toctree::
:caption: Usage
:maxdepth: 2
cookbook
docker
advanced
batch
performance
pdfsecurity
errors
.. toctree::
:caption: Developers
:maxdepth: 2
api
plugins
apiref
contributing
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
+802
View File
@@ -0,0 +1,802 @@
===================
Installing OCRmyPDF
===================
.. |latest| image:: https://img.shields.io/pypi/v/ocrmypdf.svg
:alt: OCRmyPDF latest released version on PyPI
|latest|
The easiest way to install OCRmyPDF is to follow the steps for your operating
system/platform. This version may be out of date, however.
These platforms have one-liner installs:
+-----------------------------+-------------------------------+
| Debian, Ubuntu | ``apt install ocrmypdf`` |
+-----------------------------+-------------------------------+
| Windows Subsystem for Linux | ``apt install ocrmypdf`` |
+-----------------------------+-------------------------------+
| Fedora | ``dnf install ocrmypdf`` |
+-----------------------------+-------------------------------+
| macOS | ``brew install ocrmypdf`` |
+-----------------------------+-------------------------------+
| LinuxBrew | ``brew install ocrmypdf`` |
+-----------------------------+-------------------------------+
| FreeBSD | ``pkg install py37-ocrmypdf`` |
+-----------------------------+-------------------------------+
More detailed procedures are outlined below. If you want to do a manual
install, or install a more recent version than your platform provides, read on.
.. contents:: Platform-specific steps
:depth: 2
:local:
Installing on Linux
===================
Debian and Ubuntu 18.04 or newer
--------------------------------
.. |deb-stable| image:: https://repology.org/badge/version-for-repo/debian_stable/ocrmypdf.svg
:alt: Debian 9 stable ("stretch")
.. |deb-testing| image:: https://repology.org/badge/version-for-repo/debian_testing/ocrmypdf.svg
:alt: Debian 10 testing ("buster")
.. |deb-unstable| image:: https://repology.org/badge/version-for-repo/debian_unstable/ocrmypdf.svg
:alt: Debian unstable
.. |ubu-1804| image:: https://repology.org/badge/version-for-repo/ubuntu_18_04/ocrmypdf.svg
:alt: Ubuntu 18.04 LTS
.. |ubu-2004| image:: https://repology.org/badge/version-for-repo/ubuntu_20_04/ocrmypdf.svg
:alt: Ubuntu 20.04 LTS
+-----------------------------------------------+
| **OCRmyPDF versions in Debian & Ubuntu** |
+-----------------------------------------------+
| |latest| |
+-----------------------------------------------+
| |deb-stable| |deb-testing| |deb-unstable| |
+-----------------------------------------------+
| |ubu-1804| |ubu-2004| |
+-----------------------------------------------+
Users of Debian 9 ("stretch") or later, or Ubuntu 18.04 or later, including users
of Windows Subsystem for Linux, may simply
.. code-block:: bash
apt-get install ocrmypdf
As indicated in the table above, Debian and Ubuntu releases may lag
behind the latest version. If the version available for your platform is
out of date, you could opt to install the latest version from source.
See `Installing HEAD revision from
sources <#installing-head-revision-from-sources>`__. Ubuntu 16.10 to 17.10
inclusive also had ocrmypdf, but these versions are end of life.
For full details on version availability for your platform, check the
`Debian Package Tracker <https://tracker.debian.org/pkg/ocrmypdf>`__ or
`Ubuntu launchpad.net <https://launchpad.net/ocrmypdf>`__.
.. note::
OCRmyPDF for Debian and Ubuntu currently omit the JBIG2 encoder.
OCRmyPDF works fine without it but will produce larger output files.
If you build jbig2enc from source, ocrmypdf 7.0.0 and later will
automatically detect it (specifically the ``jbig2`` binary) on the
``PATH``. To add JBIG2 encoding, see :ref:`jbig2`.
Fedora 29 or newer
------------------
.. |fedora-31| image:: https://repology.org/badge/version-for-repo/fedora_31/ocrmypdf.svg
:alt: Fedora 31
.. |fedora-32| image:: https://repology.org/badge/version-for-repo/fedora_32/ocrmypdf.svg
:alt: Fedora 32
.. |fedora-rawhide| image:: https://repology.org/badge/version-for-repo/fedora_rawhide/ocrmypdf.svg
:alt: Fedore Rawhide
+-----------------------------------------------+
| **OCRmyPDF version** |
+-----------------------------------------------+
| |latest| |
+-----------------------------------------------+
| |fedora-31| |fedora-32| |fedora-rawhide| |
+-----------------------------------------------+
Users of Fedora 29 or later may simply
.. code-block:: bash
dnf install ocrmypdf
For full details on version availability, check the `Fedora Package
Tracker <https://apps.fedoraproject.org/packages/ocrmypdf>`__.
If the version available for your platform is out of date, you could opt
to install the latest version from source. See `Installing HEAD revision
from sources <#installing-head-revision-from-sources>`__.
.. note::
OCRmyPDF for Fedora currently omits the JBIG2 encoder due to patent
issues. OCRmyPDF works fine without it but will produce larger output
files. If you build jbig2enc from source, ocrmypdf 7.0.0 and later
will automatically detect it on the ``PATH``. To add JBIG2 encoding,
see `Installing the JBIG2 encoder <jbig2>`__.
.. _ubuntu-lts-latest:
Installing the latest version on Ubuntu 20.04 LTS
-------------------------------------------------
Ubuntu 20.04 includes ocrmypdf 9.6.0 - you can install that with ``apt``. To
install a more recent version, uninstall the system-provided version of
ocrmypdf, and install the following dependencies:
.. code-block:: bash
sudo apt-get -y remove ocrmypdf # remove system ocrmypdf, if installed
sudo apt-get -y update
sudo apt-get -y install \
ghostscript \
icc-profiles-free \
liblept5 \
libxml2 \
pngquant \
python3-pip \
tesseract-ocr \
zlib1g
To install ocrmypdf for the system:
.. code-block:: bash
sudo pip3 install ocrmypdf
To install for the current user only:
.. code-block:: bash
export PATH=$HOME/.local/bin:$PATH
pip3 install --user ocrmypdf
Ubuntu 18.04 LTS
----------------
Ubuntu 18.04 includes ocrmypdf 6.1.2 - you can install that with ``apt``, but
it is quite old now. To install a more recent version, uninstall the old version
of ocrmypdf, and install the following dependencies:
.. code-block:: bash
sudo apt-get -y remove ocrmypdf
sudo apt-get -y update
sudo apt-get -y install \
ghostscript \
icc-profiles-free \
liblept5 \
libxml2 \
pngquant \
python3-cffi \
python3-distutils \
python3-pkg-resources \
python3-reportlab \
qpdf \
tesseract-ocr \
zlib1g
We will need a newer version of ``pip`` then was available for Ubuntu 18.04:
.. code-block:: bash
wget https://bootstrap.pypa.io/get-pip.py && python3 get-pip.py
Then install the most recent ocrmypdf for the local user and set the
user's ``PATH`` to check for the user's Python packages.
.. code-block:: bash
export PATH=$HOME/.local/bin:$PATH
python3 -m pip install --user ocrmypdf
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.6. Ubuntu 16.04 ships Python 3.5, but you can install Python
3.6 on it. Or, you can skip Python 3.6 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.6 \
ppa:alex-p/tesseract-ocr
sudo apt-get update
sudo apt-get install -y \
ghostscript \
libexempi3 \
libffi6 \
pngquant \
python3.6 \
qpdf \
tesseract-ocr \
unpaper
This will install a Python 3.6 binary at ``/usr/bin/python3.6``
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.6. This will install the Python 3.6 version
of ``pip`` at ``/usr/local/bin/pip``.
.. code-block:: bash
curl https://bootstrap.pypa.io/get-pip.py | sudo python3.6
**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.6 install --user ocrmypdf
To add JBIG2 encoding, see :ref:`jbig2`.
Arch Linux (AUR)
----------------
.. image:: https://repology.org/badge/version-for-repo/aur/ocrmypdf.svg
:alt: ArchLinux
:target: https://repology.org/metapackage/ocrmypdf
There is an `Arch User Repository (AUR) package for OCRmyPDF
<https://aur.archlinux.org/packages/ocrmypdf/>`__.
Installing AUR packages as root is not allowed, so you must first `setup a
non-root user
<https://wiki.archlinux.org/index.php/Users_and_groups#User_management>`__ and
`configure sudo <https://wiki.archlinux.org/index.php/Sudo#Configuration>`__.
The standard Docker image, ``archlinux/base:latest``, does **not** have a
non-root user configured, so users of that image must follow these guides. If
you are using a VM image, such as `the official Vagrant image
<https://app.vagrantup.com/archlinux/boxes/archlinux>`__, this work may already
be completed for you.
Next you should install the `base-devel package group
<https://www.archlinux.org/groups/x86_64/base-devel/>`__. This includes the
standard tooling needed to build packages, such as a compiler and binary tools.
.. code-block:: bash
sudo pacman -S base-devel
Now you are ready to install the OCRmyPDF package.
.. code-block:: bash
curl -O https://aur.archlinux.org/cgit/aur.git/snapshot/ocrmypdf.tar.gz
tar xvzf ocrmypdf.tar.gz
cd ocrmypdf
makepkg -sri
At this point you will have a working install of OCRmyPDF, but the Tesseract
install wont include any OCR language data. You can install `the
tesseract-data package group
<https://www.archlinux.org/groups/any/tesseract-data/>`__ to add all supported
languages, or use that package listing to identify the appropriate package for
your desired language.
.. code-block:: bash
sudo pacman -S tesseract-data-eng
As an alternative to this manual procedure, consider using an `AUR helper
<https://wiki.archlinux.org/index.php/AUR_helpers>`__. Such a tool will
automatically fetch, build and install the AUR package, resolve dependencies
(including dependencies on AUR packages), and ease the upgrade procedure.
If you have any difficulties with installation, check the repository package
page.
.. note::
The OCRmyPDF AUR package currently omits the JBIG2 encoder. OCRmyPDF works
fine without it but will produce larger output files. The encoder is
available from `the jbig2enc-git AUR package
<https://aur.archlinux.org/packages/jbig2enc-git/>`__ and may be installed
using the same series of steps as for the installation OCRmyPDF AUR
package. Alternatively, it may be built manually from source following the
instructions in `Installing the JBIG2 encoder <jbig2>`__. If JBIG2 is
installed, OCRmyPDF 7.0.0 and later will automatically detect it.
Alpine Linux
------------
.. image:: https://repology.org/badge/version-for-repo/alpine_edge/ocrmypdf.svg
:alt: Alpine Linux
:target: https://repology.org/metapackage/ocrmypdf
To install OCRmyPDF for Alpine Linux:
.. code-block:: bash
apk add ocrmypdf
Mageia 7
--------
Install the following dependencies:
.. code-block:: bash
# As root user
urpmi.update -a
urpmi \
ghostscript \
icc-profiles-openicc \
jbig2dec \
lib64leptonica5 \
pngquant \
python3-pip \
python3-cffi \
python3-distutils-extra \
python3-pkg-resources \
python3-reportlab \
qpdf \
tesseract \
tesseract-osd \
tesseract-eng \
tesseract-fra
To install ocrmypdf for the system:
# As root user
pip3 install ocrmypdf
ldconfig
Or, to install for the current user only:
export PATH=$HOME/.local/bin:$PATH
pip3 install --user ocrmypdf
Other Linux packages
--------------------
See the
`Repology <https://repology.org/metapackage/ocrmypdf/versions>`__ page.
In general, first install the OCRmyPDF package for your system, then
optionally use the procedure `Installing with Python
pip <#installing-with-python-pip>`__ to install a more recent version.
Installing on macOS
===================
Homebrew
--------
.. image:: https://img.shields.io/homebrew/v/ocrmypdf.svg
:alt: homebrew
:target: http://brewformulas.org/Ocrmypdf
OCRmyPDF is now a standard `Homebrew <https://brew.sh>`__ formula. To
install on macOS:
.. code-block:: bash
brew install ocrmypdf
This will include only the English language pack. If you need other
languages you can optionally install them all:
.. code-block:: bash
brew install tesseract-lang # Optional: Install all language packs
.. note::
Users who previously installed OCRmyPDF on macOS using
``pip install ocrmypdf`` should remove the pip version
(``pip3 uninstall ocrmypdf``) before switching to the Homebrew
version.
.. note::
Users who previously installed OCRmyPDF from the private tap should
switch to the mainline version (``brew untap jbarlow83/ocrmypdf``)
and install from there.
Manual installation on macOS
----------------------------
These instructions probably work on all macOS supported by Homebrew, and are
for installing a more current version of OCRmyPDF than is available from
Homebrew. Note that the Homebrew versions usually track the release versions
fairly closely.
If it's not already present, `install Homebrew <http://brew.sh/>`__.
Update Homebrew:
.. code-block:: bash
brew update
Install or upgrade the required Homebrew packages, if any are missing.
To do this, use ``brew edit ocrmypdf`` to obtain a recent list of Homebrew
dependencies. You could also check the ``azure-pipelines.yml``.
This will include the English, French, German and Spanish language
packs. If you need other languages you can optionally install them all:
.. _macos-all-languages:
.. code-block:: bash
brew install tesseract-lang # Option 2: for all language packs
Update the homebrew pip:
.. code-block:: bash
pip3 install --upgrade pip
You can then install OCRmyPDF from PyPI, for the current user:
.. code-block:: bash
pip3 install --user ocrmypdf
or system-wide:
.. code-block:: bash
pip3 install ocrmypdf
The command line program should now be available:
.. code-block:: bash
ocrmypdf --help
Installing on Windows
=====================
Native Windows
--------------
.. note::
It is easier to install OCRmyPDF on Windows Subsystem for Linux.
.. note::
Administrator privileges will be required for some of these steps.
You must install the following for Windows:
* Python 3.7 (64-bit) or later
* Tesseract 4.0 or later
* Ghostscript 9.50 or later
You can install these with the Chocolatey package manager:
* ``choco install python3``
* ``choco install --pre tesseract``
* ``choco install ghostscript``
Also consider adding:
* ``choco install pngquant``
Windows 10 64-bit and 64-bit versions of applications are recommended. Earlier
versions of Windows and 32-bit versions of these programs are not tested, and not
supported at this time.
OCRmyPDF will check for Tesseract-OCR and Ghostscript in your Program Files folder.
If they are in some other location, you may need to modify the ``PATH``
environment variable so Tesseract, Ghostscript, and other any optional executables can
be found. You can enter it in the command line or
`follow these directions <https://www.computerhope.com/issues/ch000549.htm#dospath>`_
to make the change persistent and system-wide.
You may then use pip to install ocrmypdf:
* ``pip install ocrmypdf``
Windows Subsystem for Linux
---------------------------
#. Install Ubuntu 18.04 for Windows Subsystem for Linux, if not already installed.
#. Follow the procedure to install :ref:`OCRmyPDF on Ubuntu 18.04 <ubuntu-lts-latest>`.
#. Open the Windows command prompt and create a symlink:
.. code-block:: powershell
wsl sudo ln -s /home/$USER/.local/bin/ocrmypdf /usr/local/bin/ocrmypdf
Then confirm that the expected version from PyPI (|latest|) is installed:
.. code-block:: powershell
wsl ocrmypdf --version
You can then run OCRmyPDF in the Windows command prompt or Powershell, prefixing
``wsl``, and call it from Windows programs or batch files.
Cygwin64
--------
First install the the following prerequisite Cygwin packages using ``setup-x86_64.exe``::
python36 (or later)
python3?-devel
python3?-pip
python3?-lxml
python3?-imaging
(where 3? means match the version of python3 you installed)
gcc-g++
ghostscript (<=9.50 or >=9.52-2 see note below)
libexempi3
libexempi-devel
libffi6
libffi-devel
pngquant
qpdf
libqpdf-devel
tesseract-ocr
tesseract-ocr-devel
.. note::
The Cygwin package for Ghostscript in versions 9.52 and
9.52-1 contained a bug that caused an exception to occur when
ocrmypdf invoked gs. Make sure you have either 9.50 (or earlier)
or 9.52-2 (or later).
Then open a Cygwin terminal (i.e. ``mintty``), run the following commands. Note
that if you are using the version of ``pip`` that was installed with the Cygwin
Python package, the command name will be ``pip3``. If you have since updated
``pip`` (with, for instance ``pip3 install --upgrade pip``) the the command is
likely just ``pip`` instead of ``pip3``:
.. code-block:: bash
pip3 install wheel
pip3 install ocrmypdf
The optional dependency "unpaper" that is currently not available under Cygwin.
Without it, certain options such as ``--clean`` will produce an error message.
However, the OCR-to-text-layer functionality is available.
Docker
------
You can also :ref:`Install the Docker <docker-install>` container on Windows. Ensure that
your command prompt can run the docker "hello world" container.
Installing on FreeBSD
=====================
.. image:: https://repology.org/badge/version-for-repo/freebsd/python:ocrmypdf.svg
:alt: FreeBSD
:target: https://repology.org/project/python:ocrmypdf/versions
FreeBSD 11.3, 12.0, 12.1-RELEASE and 13.0-CURRENT are supported. Other
versions likely work but have not been tested.
.. code-block:: bash
pkg install py37-ocrmypdf
To install a more recent version, you could attempt to first install the system
version with ``pkg``, then use ``pip install --user ocrmypdf``.
Installing the Docker image
===========================
For some users, installing the Docker image will be easier than
installing all of OCRmyPDF's dependencies.
See `OCRmyPDF Docker Image <docker>`__ for more information.
Installing with Python pip
==========================
OCRmyPDF is delivered by PyPI because it is a convenient way to install
the latest version. However, PyPI and ``pip`` cannot address the fact
that ``ocrmypdf`` depends on certain non-Python system libraries and
programs being instsalled.
For best results, first install `your platform's
version <https://repology.org/metapackage/ocrmypdf/versions>`__ of
``ocrmypdf``, using the instructions elsewhere in this document. Then
you can use ``pip`` to get the latest version if your platform version
is out of date. Chances are that this will satisfy most dependencies.
Use ``ocrmypdf --version`` to confirm what version was installed.
Then you can install the latest OCRmyPDF from the Python wheels. First
try:
.. code-block:: bash
pip3 install --user ocrmypdf
You should then be able to run ``ocrmypdf --version`` and see that the
latest version was located.
Since ``pip3 install --user`` does not work correctly on some platforms,
notably Ubuntu 16.04 and older, and the Homebrew version of Python,
instead use this for a system wide installation:
.. code-block:: bash
pip3 install ocrmypdf
.. note::
AArch64 (ARM64) users: this process will be difficult because most
Python packages are not available as binary wheels for your platform.
You're probably better off using a platform install on Debian, Ubuntu,
or Fedora.
Requirements for pip and HEAD install
-------------------------------------
OCRmyPDF currently requires these external programs and libraries to be
installed, and must be satisfied using the operating system package
manager. ``pip`` cannot provide them.
- Python 3.6 or newer
- Ghostscript 9.15 or newer
- qpdf 8.1.0 or newer
- Tesseract 4.0.0-beta or newer
As of ocrmypdf 7.2.1, the following versions are recommended:
- Python 3.7 or 3.8
- Ghostscript 9.23 or newer
- qpdf 8.2.1
- Tesseract 4.0.0 or newer
- jbig2enc 0.29 or newer
- pngquant 2.5 or newer
- unpaper 6.1
jbig2enc, pngquant, and unpaper are optional. If missing certain
features are disabled. OCRmyPDF will discover them as soon as they are
available.
**jbig2enc**, if present, will be used to optimize the encoding of
monochrome images. This can significantly reduce the file size of the
output file. It is not required.
`jbig2enc <https://github.com/agl/jbig2enc>`__ is not generally
available for Ubuntu or Debian due to lingering concerns about patent
issues, but can easily be built from source. To add JBIG2 encoding, see
:ref:`jbig2`.
**pngquant**, if present, is optionally used to optimize the encoding of
PNG-style images in PDFs (actually, any that are that losslessly
encoded) by lossily quantizing to a smaller color palette. It is only
activated then the ``--optimize`` argument is ``2`` or ``3``.
**unpaper**, if present, enables the ``--clean`` and ``--clean-final``
command line options.
These are in addition to the Python packaging dependencies, meaning that
unfortunately, the ``pip install`` command cannot satisfy all of them.
Installing HEAD revision from sources
=====================================
If you have ``git`` and Python 3.6 or newer installed, you can install
from source. When the ``pip`` installer runs, it will alert you if
dependencies are missing.
If you prefer to build every from source, you will need to `build
pikepdf from
source <https://pikepdf.readthedocs.io/en/latest/installation.html#building-from-source>`__.
First ensure you can build and install pikepdf.
To install the HEAD revision from sources in the current Python 3
environment:
.. code-block:: bash
pip3 install git+https://github.com/jbarlow83/OCRmyPDF.git
Or, to install in `development
mode <https://pythonhosted.org/setuptools/setuptools.html#development-mode>`__,
allowing customization of OCRmyPDF, use the ``-e`` flag:
.. code-block:: bash
pip3 install -e git+https://github.com/jbarlow83/OCRmyPDF.git
You may find it easiest to install in a virtual environment, rather than
system-wide:
.. code-block:: bash
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
python3 -m venv
source venv/bin/activate
cd OCRmyPDF
pip3 install .
However, ``ocrmypdf`` will only be accessible on the system PATH when
you activate the virtual environment.
To run the program:
.. code-block:: bash
ocrmypdf --help
If not yet installed, the script will notify you about dependencies that
need to be installed. The script requires specific versions of the
dependencies. Older version than the ones mentioned in the release notes
are likely not to be compatible to OCRmyPDF.
For development
---------------
To install all of the development and test requirements:
.. code-block:: bash
git clone -b master https://github.com/jbarlow83/OCRmyPDF.git
python3 -m venv
source venv/bin/activate
cd OCRmyPDF
pip install -e .
pip install -r requirements/dev.txt -r requirements/test.txt
To add JBIG2 encoding, see :ref:`jbig2`.
Shell completions
=================
Completions for ``bash`` and ``fish`` are available in the project's
``misc/completion`` folder. The ``bash`` completions are likely ``zsh``
compatible but this has not been confirmed. Package maintainers, please
install these at the appropriate locations for your system.
To manually install the ``bash`` completion, copy
``misc/completion/ocrmypdf.bash`` to ``/etc/bash_completion.d/ocrmypdf``
(rename the file).
To manually install the ``fish`` completion, copy
``misc/completion/ocrmypdf.fish`` to
``~/.config/fish/completions/ocrmypdf.fish``.
+233
View File
@@ -0,0 +1,233 @@
============
Introduction
============
OCRmyPDF is a Python 3 application and library that adds OCR layers to PDFs.
About OCR
=========
`Optical character
recognition <https://en.wikipedia.org/wiki/Optical_character_recognition>`__
is technology that converts images of typed or handwritten text, such as
in a scanned document, to computer text that can be selected, searched and copied.
OCRmyPDF uses
`Tesseract <https://github.com/tesseract-ocr/tesseract>`__, the best
available open source OCR engine, to perform OCR.
.. _raster-vector:
About PDFs
==========
PDFs are page description files that attempts to preserve a layout
exactly. They contain `vector
graphics <http://vector-conversions.com/vectorizing/raster_vs_vector.html>`__
that can contain raster objects such as scanned images. Because PDFs can
contain multiple pages (unlike many image formats) and can contain fonts
and text, it is a good formats for exchanging scanned documents.
|image|
A PDF page might contain multiple images, even if it only appears to
have one image. Some scanners or scanning software will segment pages
into monochromatic text and color regions for example, to improve the
compression ratio and appearance of the page.
Rasterizing a PDF is the process of generating an image suitable for
display or analyzing with an OCR engine. OCR engines like Tesseract work
with images, not vector objects.
About PDF/A
===========
`PDF/A <https://en.wikipedia.org/wiki/PDF/A>`__ is an ISO-standardized
subset of the full PDF specification that is designed for archiving (the
'A' stands for Archive). PDF/A differs from PDF primarily by omitting
features that would make it difficult to read the file in the future,
such as embedded Javascript, video, audio and references to external
fonts. All fonts and resources needed to interpret the PDF must be
contained within it. Because PDF/A disables Javascript and other types
of embedded content, it is probably more secure.
There are various conformance levels and versions, such as "PDF/A-2b".
Generally speaking, the best format for scanned documents is PDF/A. Some
governments and jurisdictions, US Courts in particular, `mandate the use
of PDF/A <https://pdfblog.com/2012/02/13/what-is-pdfa/>`__ for scanned
documents.
Since most people who scan documents are interested in reading them
indefinitely into the future, OCRmyPDF generates PDF/A-2b by default.
PDF/A has a few drawbacks. Some PDF viewers include an alert that the
file is a PDF/A, which may confuse some users. It also tends to produce
larger files than PDF, because it embeds certain resources even if they
are commonly available. PDF/A files can be digitally signed, but may not
be encrypted, to ensure they can be read in the future. Fortunately,
converting from PDF/A to a regular PDF is trivial, and any PDF viewer
can view PDF/A.
What OCRmyPDF does
==================
OCRmyPDF analyzes each page of a PDF to determine the colorspace and
resolution (DPI) needed to capture all of the information on that page
without losing content. It uses
`Ghostscript <http://ghostscript.com/>`__ to rasterize the page, and
then performs on OCR on the rasterized image to create an OCR "layer".
The layer is then grafted back onto the original PDF.
While one can use a program like Ghostscript or ImageMagick to get an
image and put the image through Tesseract, that actually creates a new
PDF and many details may be lost. OCRmyPDF can produce a minimally
changed PDF as output.
OCRmyPDF also some image processing options like deskew which improve
the appearance of files and quality of OCR. When these are used, the OCR
layer is grafted onto the processed image instead.
By default, OCRmyPDF produces archival PDFs PDF/A, which are a
stricter subset of PDF features designed for long term archives. If
regular PDFs are desired, this can be disabled with
``--output-type pdf``.
Why you shouldn't do this manually
==================================
A PDF is similar to an HTML file, in that it contains document structure
along with images. Sometimes a PDF does nothing more than present a full
page image, but often there is additional content that would be lost.
A manual process could work like either of these:
1. Rasterize each page as an image, OCR the images, and combine the
output into a PDF. This preserves the layout of each page, but
resamples all images (possibly losing quality, increasing file size,
introducing compression artifacts, etc.).
2. Extract each image, OCR, and combine the output into a PDF. This
loses the context in which images are used in the PDF, meaning that
cropping, rotation and scaling of pages may be lost. Some scanned
PDFs use multiple images segmented into black and white, grayscale
and color regions, with stencil masks to prevent overlap, as this can
enhance the appearance of a file while reducing file size. Clearly,
reassembling these images will be easy. This also loses and text or
vector art on any pages in a PDF with both scanned and pure digital
content.
In the case of a PDF that is nothing other than a container of images
(no rotation, scaling, cropping, one image per page), the second
approach can be lossless.
OCRmyPDF uses several strategies depending on input options and the
input PDF itself, but generally speaking it rasterizes a page for OCR
and then grafts the OCR back onto the original. As such it can handle
complex PDFs and still preserve their contents as much as possible.
OCRmyPDF also supports a many, many edge cases that have cropped over
several years of development. We support PDF features like images inside
of Form XObjects, and pages with UserUnit scaling. We support rare image
formats like non-monochrome 1-bit images. We warn about files you may
not to OCR. Thanks to pikepdf and QPDF, we auto-repair PDFs that are
damaged. (Not that you need to know what any of these are! You should be
able to throw any PDF at it.)
Limitations
===========
OCRmyPDF is limited by the Tesseract OCR engine. As such it experiences
these limitations, as do any other programs that rely on Tesseract:
- The OCR is not as accurate as commercial solutions such as Abbyy.
- It is not capable of recognizing handwriting.
- It may find gibberish and report this as OCR output.
- If a document contains languages outside of those given in the
``-l LANG`` arguments, results may be poor.
- It is not always good at analyzing the natural reading order of
documents. For example, it may fail to recognize that a document
contains two columns, and may try to join text across columns.
- Poor quality scans may produce poor quality OCR. Garbage in, garbage
out.
- It does not expose information about what font family text belongs
to.
OCRmyPDF is also limited by the PDF specification:
- PDF encodes the position of text glyphs but does not encode document
structure. There is no markup that divides a document in sections,
paragraphs, sentences, or even words (since blank spaces are not
represented). As such all elements of document structure including
the spaces between words must be derived heuristically. Some PDF
viewers do a better job of this than others.
- Because some popular open source PDF viewers have a particularly hard
time with spaces between words, OCRmyPDF appends a space to each text
element as a workaround (when using ``--pdf-renderer hocr``). While
this mixes document structure with graphical information that ideally
should be left to the PDF viewer to interpret, it improves
compatibility with some viewers and does not cause problems for
better ones.
Ghostscript also imposes some limitations:
- PDFs containing JBIG2-encoded content will be converted to CCITT
Group4 encoding, which has lower compression ratios, if Ghostscript
PDF/A is enabled.
- PDFs containing JPEG 2000-encoded content will be converted to JPEG
encoding, which may introduce compression artifacts, if Ghostscript
PDF/A is enabled.
- Ghostscript may transcode grayscale and color images, either lossy to
lossless or lossless to lossy, based on an internal algorithm. This
behavior can be suppressed by setting ``--pdfa-image-compression`` to
``jpeg`` or ``lossless`` to set all images to one type or the other.
Ghostscript has no option to maintain the input image's format.
(Ghostscript 9.25+ can copy JPEG images without transcoding them;
earlier versions will transcode.)
- Ghostscript's PDF/A conversion removes any XMP metadata that is not
one of the standard XMP metadata namespaces for PDFs. In particular,
PRISM Metdata is removed.
- Ghostscript's PDF/A conversion seems to remove or deactivate
hyperlinks and other active content.
You can use ``--output-type pdf`` to disable PDF/A conversion and produce
a standard, non-archival PDF.
Regarding OCRmyPDF itself:
- PDFs that use transparency are not currently represented in the test
suite
Similar programs
================
To the author's knowledge, OCRmyPDF is the most feature-rich and
thoroughly tested command line OCR PDF conversion tool. If it does not
meet your needs, contributions and suggestions are welcome. If not,
consider one of these similar open source programs:
- pdf2pdfocr
- pdfsandwich
- pypdfocr
- pdfbeads
Web front-ends
==============
The Docker image ``ocrmypdf`` provides a web service front-end
that allows files to submitted over HTTP and the results "downloaded".
This is an HTTP server intended to simplify web services deployments; it
is not intended to be deployed on the public internet and no real
security measures to speak of.
In addition, the following third-party integrations are available:
- `Nextcloud OCR <https://github.com/janis91/ocr>`__ is a free software
plugin for the Nextcloud private cloud software
OCRmyPDF is not designed to be secure against malware-bearing PDFs (see
`Using OCRmyPDF online <ocr-service>`__). Users should ensure they
comply with OCRmyPDF's licenses and the licenses of all dependencies. In
particular, OCRmyPDF requires Ghostscript, which is licensed under
AGPLv3.
.. |image| image:: images/bitmap_vs_svg.svg
+55
View File
@@ -0,0 +1,55 @@
.. _jbig2:
============================
Installing the JBIG2 encoder
============================
Most Linux distributions do not include a JBIG2 encoder since JBIG2
encoding was patented for a long time. All known JBIG2 US patents have
expired as of 2017, but it is possible that unknown patents exist.
JBIG2 encoding is recommended for OCRmyPDF and is used to losslessly
create smaller PDFs. If JBIG2 encoding not available, lower quality
encodings will be used.
JBIG2 decoding is not patented and is performed automatically by most
PDF viewers. It is widely supported has been part of the PDF
specification since 2001.
On macOS, Homebrew packages jbig2enc and OCRmyPDF includes it by
default. The Docker image for OCRmyPDF also builds its own JBIG2 encoder
from source.
For all other Linux, you must build a JBIG2 encoder from source:
.. code-block:: bash
git clone https://github.com/agl/jbig2enc
cd jbig2enc
./autogen.sh
./configure && make
[sudo] make install
.. _jbig2-lossy:
Lossy mode JBIG2
================
OCRmyPDF provides lossy mode JBIG2 as an advanced feature. Users should
`review the technical concerns with JBIG2 in lossy
mode <https://abbyy.technology/en:kb:tip:jbig2_compression_and_ocr>`__
and decide if this feature is acceptable for their use case.
JBIG2 lossy mode does achieve higher compression ratios than any other
monochrome (bitonal) compression technology; for large text documents
the savings are considerable. JBIG2 lossless still gives great
compression ratios and is a major improvement over the older CCITT G4
standard. As explained above, there is some risk of substitution errors.
To turn on JBIG2 lossy mode, add the argument ``--jbig2-lossy``.
``--optimize {1,2,3}`` are necessary for the argument to take effect
also required. Also, a JBIG2 encoder must be installed as described in
the previous section.
*Due to an oversight, ocrmypdf v7.0 and v7.1 used lossy mode by
default.*
+73
View File
@@ -0,0 +1,73 @@
.. _lang-packs:
====================================
Installing additional language packs
====================================
OCRmyPDF uses Tesseract for OCR, and relies on its language packs for all languages.
On most platforms, English is installed with Tesseract by default, but not always.
Tesseract supports `most
languages <https://github.com/tesseract-ocr/tesseract/blob/master/doc/tesseract.1.asc#languages>`__.
Languages are identified by standardized three-letter codes (called ISO 639-2 Alpha-3).
Tesseract's documentation also lists the three-letter code for your language.
Some are anglicized, e.g. Spanish is ``spa`` rather than ``esp``, while others
are not, e.g. German is ``deu``.
After you have installed a language pack, you can use it ``ocrmypdf -l <language>``,
for example ``ocrmypdf -l spa``. For multilingual documents, you can specify
all languages to be expected, e.g. ``ocrmypdf -l eng+fra`` for English and French.
English is assumed by default unless other language(s) are specified.
For Linux users, you can often find packages that provide language
packs:
Debian and Ubuntu users
=======================
.. code-block:: bash
# Display a list of all Tesseract language packs
apt-cache search tesseract-ocr
# Install Chinese Simplified language pack
apt-get install tesseract-ocr-chi-sim
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+fre`` (English and French) or
``-l eng -l fre``.
Fedora users
============
.. code-block:: bash
# Display a list of all Tesseract language packs
dnf search tesseract
# Install Chinese Simplified language pack
dnf install tesseract-langpack-chi_sim
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+fre`` (English and French) or
``-l eng -l fre``.
macOS users
===========
You can install additional language packs by
:ref:`installing Tesseract using Homebrew with all language packs <macos-all-languages>`.
Docker users
============
Users of the OCRmyPDF Docker image should install language packs into a
derived Docker image as
:ref:`described in that section <docker-lang-packs>`.
Windows users
=============
The Tesseract installer provided by Chocolatey already includes 100 languages.
+75
View File
@@ -0,0 +1,75 @@
================
PDF optimization
================
OCRmyPDF includes an image-oriented PDF optimizer. By default, the optimizer
runs with safe settings with the goal of improving compression at no loss of
quality. At higher optimization levels, lossy optimizations may be applied and
tuned. Optimization occurs after OCR, and only if OCR succeeded. It does not
perform other possible optimizations such as deduplicating resources,
consolidating fonts, simplifying vector drawings, or anything of that nature.
Optimization ranges from ``-O0`` through ``-O3``, where ``0`` disables
optimization and ``3`` implements all options. ``1``, the default, performs only
safe and lossless optimizations. (This is similar to GCC's optimization
parameter.) The exact type of optimizations performed will vary over time.
PDF optimization requires third-party, optional tools for certain optimizations.
If these are not installed or cannot be found by OCRmyPDF, optimization will not
be as good.
Optimizations that always occurs
================================
OCRmyPDF will automatically replace obsolete or inferior compression schemes
such as RLE or LZW with superior schemes such as Deflate and converting
monochrome images to CCITT G4. Since this is harmless it always occurs and there
is no way to disable it. Other non-image compressed objects are compressed as
well.
Fast web view
=============
OCRmyPDF automatically optimizes PDFs for "fast web view" in Adobe Acrobat's
parlance, or equivalently, linearizes PDFs so that the resources they reference
are presented in the order a viewer needs them for sequential display. This
reduces the latency of viewing a PDF both online and from local storage. This
actually slightly increases the file size.
To disable this optimization and all others, use ``ocrmypdf --optimize 0 ...``
or the shorthand ``-O0``.
Lossless optimizations
======================
At optimization level ``-O1`` (the default), OCRmyPDF will also attempt lossless
image optimization.
If a JBIG2 encoder is available, then monochrome images will be converted to
JBIG2, with the potential for huge savings on large black and white images,
since JBIG2 is far more efficient than any other monochrome (bi-level)
compression. (All known US patents related to JBIG2 have probably expired, but
it remains the responsibility of the user to supply a JBIG2 encoder such as
`jbig2enc <https://github.com/agl/jbig2enc>`__. OCRmyPDF does not implement
JBIG2 encoding on its own.)
OCRmyPDF currently does not attempt to recompress losslessly compressed objects
more aggressively.
Lossy optimizations
===================
At optimization level ``-O2`` and ``-O3``, OCRmyPDF will some attempt lossy
image optimization.
If ``pngquant`` is installed, OCRmyPDF will use it to perform quantize paletted
images to reduce their size.
The quality of JPEGs may be lowered, on the assumption that a lower quality
image may be suitable for storage after OCR.
It is not possible to optimize all image types. Uncommon image types may be
skipped by the optimizer.
OCRmyPDF provides :ref:`lossy mode JBIG2 <jbig2-lossy>` as an advanced feature
that additional requires the argument ``--jbig2-lossy``.
+161
View File
@@ -0,0 +1,161 @@
===================
PDF security issues
===================
OCRmyPDF should only be used on PDFs you trust. It is not designed to
protect you against malware.
Recognizing that many users have an interest in handling PDFs and
applying OCR to PDFs they did not generate themselves, this article
discusses the security implications of PDFs and how users can protect
themselves.
The disclaimer applies: this software has no warranties of any kind.
PDFs may contain malware
========================
PDF is a rich, complex file format. The official PDF 1.7 specification,
ISO 32000:2008, is hundreds of pages long and references several annexes
each of which are similar in length. PDFs can contain video, audio, XML,
JavaScript and other programming, and forms. In some cases, they can
open internet connections to pre-selected URLs. All of these possible
attack vectors.
In short, PDFs `may contain
viruses <https://security.stackexchange.com/questions/64052/can-a-pdf-file-contain-a-virus>`__.
This
`article <https://theinvisiblethings.blogspot.ca/2013/02/converting-untrusted-pdfs-into-trusted.html>`__
describes a high-paranoia method which allows potentially hostile PDFs
to be viewed and rasterized safely in a disposable virtual machine. A
trusted PDF created in this manner is converted to images and loses all
information making it searchable and losing all compression. OCRmyPDF
could be used restore searchability.
How OCRmyPDF processes PDFs
===========================
OCRmyPDF must open and interpret your PDF in order to insert an OCR
layer. First, it runs all PDFs through
`pikepdf <https://github.com/pikepdf/pikepdf>`__, a library based on
`qpdf <https://github.com/qpdf/qpdf>`__, a program that repairs PDFs
with syntax errors. This is done because, in the author's experience, a
significant number of PDFs in the wild especially those created by
scanners are not well-formed files. qpdf makes it more likely that
OCRmyPDF will succeed, but offers no security guarantees. qpdf is also
used to split the PDF into single page PDFs.
Finally, OCRmyPDF rasterizes each page of the PDF using
`Ghostscript <http://ghostscript.com/>`__ in ``-dSAFER`` mode.
Depending on the options specified, OCRmyPDF may graft the OCR layer
into the existing PDF or it may essentially reconstruct ("re-fry") a
visually identical PDF that may be quite different at the binary level.
That said, OCRmyPDF is not a tool designed for sanitizing PDFs.
.. _ocr-service:
Using OCRmyPDF online or as a service
=====================================
OCRmyPDF is not designed for use as a public web service where a
malicious user could upload a chosen PDF. In particular, it is not
necessarily secure against PDF malware or PDFs that cause denial of
service. OCRmyPDF relies on Ghostscript, and therefore, if deployed
online one should be prepared to comply with Ghostscript's Affero GPL
license, and any other licenses.
Setting aside these concerns, a side effect of OCRmyPDF is it may
incidentally sanitize PDFs that contain certain types of malware. It
repairs the PDF with pikepdf/libqpdf, which could correct malformed PDF
structures that are part of an attack. When PDF/A output is selected
(the default), the input PDF is partially reconstructed by Ghostscript.
When ``--force-ocr`` is used, all pages are rasterized and reconverted
to PDF, which could remove malware in embedded images.
OCRmyPDF should be relatively safe to use in a trusted intranet, with
some considerations:
Limiting CPU usage
------------------
OCRmyPDF will attempt to use all available CPUs and storage, so
executing ``nice ocrmypdf`` or limiting the number of jobs with the
``-j`` argument may ensure the server remains available. Another option
would be run OCRmyPDF jobs inside a Docker container, a virtual machine,
or a cloud instance, which can impose its own limits on CPU usage and be
terminated "from orbit" if it fails to complete.
Temporary storage requirements
------------------------------
OCRmyPDF will use a large amount of temporary storage for its work,
proportional to the total number of pixels needed to rasterize the PDF.
The raster image of a 8.5×11" color page at 300 DPI takes 25 MB
uncompressed; OCRmyPDF saves its intermediates as PNG, but that still
means it requires about 9 MB per intermediate based on average
compression ratios. Multiple intermediates per page are also required,
depending on the command line given. A rule of thumb would be to allow
100 MB of temporary storage per page in a file meaning that a small
cloud servers or small VM partitions should be provisioned with plenty
of extra space, if say, a 500 page file might be sent.
To check temporary storage usage on actual files, run
``ocrmypdf -k ...`` which will preserve and print the path to temporary
storage when the job is done.
To change where temporary files are stored, change the ``TMPDIR``
environment variable for ocrmypdf's environment. (Python's
``tempfile.gettempdir()`` returns the root directory in which temporary
files will be stored.) For example, one could redirect ``TMPDIR`` to a
large RAM disk to avoid wear on HDD/SSD and potentially improve
performance. On Amazon Web Services, ``TMPDIR`` can be set to `empheral
storage <https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/InstanceStorage.html>`__.
Timeouts
--------
To prevent excessively long OCR jobs consider setting
``--tesseract-timeout`` and/or ``--skip-big`` arguments. ``--skip-big``
is particularly helpful if your PDFs include documents such as reports
on standard page sizes with large images attached - often large images
are not worth OCR'ing anyway.
Commercial alternatives
-----------------------
The author also provides professional services that include OCR and
building databases around PDFs, and is happy to provide consultation.
Abbyy Cloud OCR is a viable commercial alternative with a web services
API.
Password protection, digital signatures and certification
=========================================================
Password protected PDFs usually have two passwords, and owner and user
password. When the user password is set to empty, PDF readers will open
the file automatically and marked it as "(SECURED)". While not as
reliable as a digital signature, this indicates that whoever set the
password approved of the file at that time. When the user password is
set, the document cannot be viewed without the password.
Either way, OCRmyPDF does not remove passwords from PDFs and exits with
an error on encountering them.
``qpdf`` can remove passwords. If the owner and user password are set, a
password is required for ``qpdf``. If only the owner password is set, then the
password can be stripped, even if one does not have the owner password.
After OCR is applied, password protection is not permitted on PDF/A
documents but the file can be converted to regular PDF.
Many programs exist which are capable of inserting an image of someone's
signature. On its own, this offers no security guarantees. It is trivial
to remove the signature image and apply it to other files. This practice
offers no real security.
Important documents can be digitally signed and certified to attest to
their authorship. OCRmyPDF cannot do this. Open source tools such as
pdfbox (Java) have this capability as does Adobe Acrobat.
+22
View File
@@ -0,0 +1,22 @@
===========
Performance
===========
Some users have noticed that current versions of OCRmyPDF do not run as quickly
as some older versions (specifically 6.x and older). This is because OCRmyPDF
added image optimization as a postprocessing step, and it is enabled by default.
Speed
=====
If running OCRmyPDF quickly is your main goal, you can use settings such as:
* ``--optimize 0`` to disable file size optimization
* ``--output-type pdf`` to disable PDF/A generation
* ``--fast-web-view 0`` to disable fast web view optimization
* ``--skip-big`` to skip large images, if some pages have large images
You can also avoid:
* ``--force-ocr``
* Image preprocessing
+392
View File
@@ -0,0 +1,392 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.40.1 (20161225.0304)
-->
<!-- Title: Pipeline: Pages: 1 -->
<svg width="1484pt" height="1277pt"
viewBox="0.00 0.00 1484.00 1277.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 1273.1977)">
<title>Pipeline:</title>
<polygon fill="#ffffff" stroke="transparent" points="-4,4 -4,-1273.1977 1480,-1273.1977 1480,4 -4,4"/>
<g id="clust1" class="cluster">
<title>clustertasks</title>
<polygon fill="none" stroke="#000000" points="8,-8 8,-1261.1977 1468,-1261.1977 1468,-8 8,-8"/>
<text text-anchor="middle" x="738" y="-1233.1977" font-family="Times,serif" font-size="30.00" fill="#ff3232">Pipeline:</text>
</g>
<!-- t0 -->
<g id="node1" class="node">
<title>t0</title>
<polygon fill="#efa03b" stroke="#000000" points="1215.7053,-1215.1977 1006.2947,-1215.1977 1002.2947,-1211.1977 1002.2947,-1179.1977 1211.7053,-1179.1977 1215.7053,-1183.1977 1215.7053,-1215.1977"/>
<polyline fill="none" stroke="#000000" points="1211.7053,-1211.1977 1002.2947,-1211.1977 "/>
<polyline fill="none" stroke="#000000" points="1211.7053,-1211.1977 1211.7053,-1179.1977 "/>
<polyline fill="none" stroke="#000000" points="1211.7053,-1211.1977 1215.7053,-1215.1977 "/>
<text text-anchor="middle" x="1109" y="-1191.1977" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.triage</text>
</g>
<!-- t1 -->
<g id="node2" class="node">
<title>t1</title>
<polygon fill="#efa03b" stroke="#000000" points="1280.0433,-1157.1977 941.9567,-1157.1977 937.9567,-1153.1977 937.9567,-1121.1977 1276.0433,-1121.1977 1280.0433,-1125.1977 1280.0433,-1157.1977"/>
<polyline fill="none" stroke="#000000" points="1276.0433,-1153.1977 937.9567,-1153.1977 "/>
<polyline fill="none" stroke="#000000" points="1276.0433,-1153.1977 1276.0433,-1121.1977 "/>
<polyline fill="none" stroke="#000000" points="1276.0433,-1153.1977 1280.0433,-1157.1977 "/>
<text text-anchor="middle" x="1109" y="-1133.1977" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.repair_and_parse_pdf</text>
</g>
<!-- t0&#45;&gt;t1 -->
<g id="edge1" class="edge">
<title>t0&#45;&gt;t1</title>
<path fill="none" stroke="#0044a0" d="M1109,-1179.1641C1109,-1175.4895 1109,-1171.5395 1109,-1167.604"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1112.5001,-1167.4199 1109,-1157.4199 1105.5001,-1167.42 1112.5001,-1167.4199"/>
</g>
<!-- t2 -->
<g id="node3" class="node">
<title>t2</title>
<polygon fill="#efa03b" stroke="#000000" points="1030.0926,-1078.2732 785,-1095.1731 539.9074,-1078.2732 540.1362,-1050.9285 1029.8638,-1050.9285 1030.0926,-1078.2732"/>
<polygon fill="none" stroke="#000000" points="1034.1249,-1082.0021 785,-1099.1801 535.8751,-1082.0021 536.1685,-1046.931 1033.8315,-1046.931 1034.1249,-1082.0021"/>
<text text-anchor="middle" x="785" y="-1064.7153" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.marker_pages</text>
</g>
<!-- t1&#45;&gt;t2 -->
<g id="edge2" class="edge">
<title>t1&#45;&gt;t2</title>
<path fill="none" stroke="#0044a0" d="M1023.449,-1121.1152C984.5695,-1112.8974 938.0675,-1103.0685 896.4957,-1094.2816"/>
<polygon fill="#0044a0" stroke="#0044a0" points="897.1066,-1090.8335 886.5989,-1092.1898 895.6589,-1097.6822 897.1066,-1090.8335"/>
</g>
<!-- t16 -->
<g id="node17" class="node">
<title>t16</title>
<polygon fill="#00cc66" stroke="#000000" points="1215.5059,-328.1861 942.4941,-328.1861 938.4941,-324.1861 938.4941,-292.1861 1211.5059,-292.1861 1215.5059,-296.1861 1215.5059,-328.1861"/>
<polyline fill="none" stroke="#000000" points="1211.5059,-324.1861 938.4941,-324.1861 "/>
<polyline fill="none" stroke="#000000" points="1211.5059,-324.1861 1211.5059,-292.1861 "/>
<polyline fill="none" stroke="#000000" points="1211.5059,-324.1861 1215.5059,-328.1861 "/>
<text text-anchor="middle" x="1077" y="-304.1861" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.weave_layers</text>
</g>
<!-- t1&#45;&gt;t16 -->
<g id="edge24" class="edge">
<title>t1&#45;&gt;t16</title>
<path fill="none" stroke="#0044a0" d="M1097.2373,-1120.9576C1077.987,-1088.7184 1043.7317,-1019.7372 1063,-963.2656 1106.87,-834.6914 1482.8098,-573.3183 1409,-459.2656 1362.7595,-387.8137 1273.2613,-350.2799 1198.8305,-330.7528"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1199.4555,-327.3005 1188.9015,-328.2301 1197.7316,-334.0849 1199.4555,-327.3005"/>
</g>
<!-- t17 -->
<g id="node18" class="node">
<title>t17</title>
<polygon fill="#efa03b" stroke="#000000" points="1439.5156,-1009.7492 1076.4844,-1009.7492 1072.4844,-1005.7492 1072.4844,-973.7492 1435.5156,-973.7492 1439.5156,-977.7492 1439.5156,-1009.7492"/>
<polyline fill="none" stroke="#000000" points="1435.5156,-1005.7492 1072.4844,-1005.7492 "/>
<polyline fill="none" stroke="#000000" points="1435.5156,-1005.7492 1435.5156,-973.7492 "/>
<polyline fill="none" stroke="#000000" points="1435.5156,-1005.7492 1439.5156,-1009.7492 "/>
<text text-anchor="middle" x="1256" y="-985.7492" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.generate_postscript_stub</text>
</g>
<!-- t1&#45;&gt;t17 -->
<g id="edge25" class="edge">
<title>t1&#45;&gt;t17</title>
<path fill="none" stroke="#0044a0" d="M1127.0251,-1121.1176C1153.0342,-1095.0292 1201.2007,-1046.7157 1230.7429,-1017.0834"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1233.2458,-1019.5302 1237.8275,-1009.9772 1228.2885,-1014.5879 1233.2458,-1019.5302"/>
</g>
<!-- t18 -->
<g id="node19" class="node">
<title>t18</title>
<polygon fill="#efa03b" stroke="#000000" points="1070.189,-214.4288 1263,-187.1099 1455.811,-214.4288 1455.631,-258.6317 1070.369,-258.6317 1070.189,-214.4288"/>
<polygon fill="none" stroke="#000000" points="1066.1715,-210.9543 1263,-183.0662 1459.8285,-210.9543 1459.6181,-262.6353 1066.3819,-262.6353 1066.1715,-210.9543"/>
<text text-anchor="middle" x="1263" y="-220.6461" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.metadata_fixup</text>
</g>
<!-- t1&#45;&gt;t18 -->
<g id="edge28" class="edge">
<title>t1&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1241.1115,-1121.1192C1311.0076,-1105.2558 1393.5851,-1075.5895 1449,-1020.2329 1458.4922,-1010.7507 1459,-1005.1661 1459,-991.7492 1459,-991.7492 1459,-991.7492 1459,-393.7258 1459,-341.9727 1441.8377,-325.2216 1402,-292.1861 1390.9869,-283.0534 1378.4885,-274.871 1365.6104,-267.6415"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1367.1079,-264.4723 1356.6476,-262.7888 1363.775,-270.6279 1367.1079,-264.4723"/>
</g>
<!-- t3 -->
<g id="node4" class="node">
<title>t3</title>
<polygon fill="#efa03b" stroke="#000000" points="1015.5194,-999.3074 785,-1016.2081 554.4806,-999.3074 554.6958,-971.9616 1015.3042,-971.9616 1015.5194,-999.3074"/>
<polygon fill="none" stroke="#000000" points="1019.5491,-1003.0204 785,-1020.2165 550.4509,-1003.0204 550.7267,-967.9639 1019.2733,-967.9639 1019.5491,-1003.0204"/>
<text text-anchor="middle" x="785" y="-985.7492" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.ocr_or_skip</text>
</g>
<!-- t2&#45;&gt;t3 -->
<g id="edge3" class="edge">
<title>t2&#45;&gt;t3</title>
<path fill="none" stroke="#0044a0" d="M785,-1046.5938C785,-1041.5635 785,-1036.139 785,-1030.7273"/>
<polygon fill="#0044a0" stroke="#0044a0" points="788.5001,-1030.4427 785,-1020.4427 781.5001,-1030.4428 788.5001,-1030.4427"/>
</g>
<!-- t4 -->
<g id="node5" class="node">
<title>t4</title>
<polygon fill="#efa03b" stroke="#000000" points="889.3166,-941.2656 582.6834,-941.2656 578.6834,-937.2656 578.6834,-905.2656 885.3166,-905.2656 889.3166,-909.2656 889.3166,-941.2656"/>
<polyline fill="none" stroke="#000000" points="885.3166,-937.2656 578.6834,-937.2656 "/>
<polyline fill="none" stroke="#000000" points="885.3166,-937.2656 885.3166,-905.2656 "/>
<polyline fill="none" stroke="#000000" points="885.3166,-937.2656 889.3166,-941.2656 "/>
<text text-anchor="middle" x="734" y="-917.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.rasterize_preview</text>
</g>
<!-- t3&#45;&gt;t4 -->
<g id="edge4" class="edge">
<title>t3&#45;&gt;t4</title>
<path fill="none" stroke="#0044a0" d="M767.1627,-967.797C762.8396,-961.9919 758.1927,-955.7519 753.7858,-949.8343"/>
<polygon fill="#0044a0" stroke="#0044a0" points="756.3465,-947.4129 747.5666,-941.483 750.7323,-951.5939 756.3465,-947.4129"/>
</g>
<!-- t5 -->
<g id="node6" class="node">
<title>t5</title>
<polygon fill="#efa03b" stroke="#000000" points="1028.0824,-883.2656 769.9176,-883.2656 765.9176,-879.2656 765.9176,-847.2656 1024.0824,-847.2656 1028.0824,-851.2656 1028.0824,-883.2656"/>
<polyline fill="none" stroke="#000000" points="1024.0824,-879.2656 765.9176,-879.2656 "/>
<polyline fill="none" stroke="#000000" points="1024.0824,-879.2656 1024.0824,-847.2656 "/>
<polyline fill="none" stroke="#000000" points="1024.0824,-879.2656 1028.0824,-883.2656 "/>
<text text-anchor="middle" x="897" y="-859.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.orient_page</text>
</g>
<!-- t3&#45;&gt;t5 -->
<g id="edge6" class="edge">
<title>t3&#45;&gt;t5</title>
<path fill="none" stroke="#0044a0" d="M869.1177,-967.8063C880.553,-961.0264 890.81,-952.3527 898,-941.2656 907.042,-927.3226 906.9457,-908.7053 904.5139,-893.387"/>
<polygon fill="#0044a0" stroke="#0044a0" points="907.914,-892.5383 902.5408,-883.4071 901.0469,-893.8961 907.914,-892.5383"/>
</g>
<!-- t4&#45;&gt;t5 -->
<g id="edge5" class="edge">
<title>t4&#45;&gt;t5</title>
<path fill="none" stroke="#0044a0" d="M784.6807,-905.2319C801.0865,-899.3943 819.4462,-892.8614 836.4913,-886.7963"/>
<polygon fill="#0044a0" stroke="#0044a0" points="838.1026,-889.938 846.3506,-883.288 835.7559,-883.343 838.1026,-889.938"/>
</g>
<!-- t6 -->
<g id="node7" class="node">
<title>t6</title>
<polygon fill="#efa03b" stroke="#000000" points="1064.7325,-825.2656 689.2675,-825.2656 685.2675,-821.2656 685.2675,-789.2656 1060.7325,-789.2656 1064.7325,-793.2656 1064.7325,-825.2656"/>
<polyline fill="none" stroke="#000000" points="1060.7325,-821.2656 685.2675,-821.2656 "/>
<polyline fill="none" stroke="#000000" points="1060.7325,-821.2656 1060.7325,-789.2656 "/>
<polyline fill="none" stroke="#000000" points="1060.7325,-821.2656 1064.7325,-825.2656 "/>
<text text-anchor="middle" x="875" y="-801.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.rasterize_with_ghostscript</text>
</g>
<!-- t5&#45;&gt;t6 -->
<g id="edge7" class="edge">
<title>t5&#45;&gt;t6</title>
<path fill="none" stroke="#0044a0" d="M890.1597,-847.2319C888.6839,-843.3412 887.0909,-839.1417 885.5115,-834.9776"/>
<polygon fill="#0044a0" stroke="#0044a0" points="888.731,-833.5965 881.9119,-825.4878 882.186,-836.0791 888.731,-833.5965"/>
</g>
<!-- t13 -->
<g id="node16" class="node">
<title>t13</title>
<polygon fill="#00cc66" stroke="#000000" points="1077,-515.2656 754.4141,-487.2656 1077,-459.2656 1399.5859,-487.2656 1077,-515.2656"/>
<text text-anchor="middle" x="1077" y="-481.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.select_image_layer</text>
</g>
<!-- t5&#45;&gt;t13 -->
<g id="edge20" class="edge">
<title>t5&#45;&gt;t13</title>
<path fill="none" stroke="#0044a0" d="M1000.2704,-847.241C1031.2359,-840.6873 1060.5415,-832.9862 1074,-825.2656 1114.0954,-802.2644 1145,-795.4899 1145,-749.2656 1145,-749.2656 1145,-749.2656 1145,-633.2656 1145,-590.368 1154.8687,-575.2844 1135,-537.2656 1131.571,-530.7041 1126.8893,-524.6314 1121.6622,-519.1451"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1123.8989,-516.4405 1114.2626,-512.0367 1119.0494,-521.4885 1123.8989,-516.4405"/>
</g>
<!-- t7 -->
<g id="node8" class="node">
<title>t7</title>
<polygon fill="#efa03b" stroke="#000000" points="856.0801,-767.2656 431.9199,-767.2656 427.9199,-763.2656 427.9199,-731.2656 852.0801,-731.2656 856.0801,-735.2656 856.0801,-767.2656"/>
<polyline fill="none" stroke="#000000" points="852.0801,-763.2656 427.9199,-763.2656 "/>
<polyline fill="none" stroke="#000000" points="852.0801,-763.2656 852.0801,-731.2656 "/>
<polyline fill="none" stroke="#000000" points="852.0801,-763.2656 856.0801,-767.2656 "/>
<text text-anchor="middle" x="642" y="-743.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.preprocess_remove_background</text>
</g>
<!-- t6&#45;&gt;t7 -->
<g id="edge8" class="edge">
<title>t6&#45;&gt;t7</title>
<path fill="none" stroke="#0044a0" d="M802.5546,-789.2319C777.7884,-783.067 749.9071,-776.1266 724.4138,-769.7806"/>
<polygon fill="#0044a0" stroke="#0044a0" points="724.95,-766.3073 714.4006,-767.288 723.259,-773.1 724.95,-766.3073"/>
</g>
<!-- t12 -->
<g id="node15" class="node">
<title>t12</title>
<polygon fill="#efa03b" stroke="#000000" points="742,-593.2656 358.1874,-565.2656 742,-537.2656 1125.8126,-565.2656 742,-593.2656"/>
<text text-anchor="middle" x="742" y="-559.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.select_visible_page_image</text>
</g>
<!-- t6&#45;&gt;t12 -->
<g id="edge18" class="edge">
<title>t6&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M875.1443,-789.1129C874.6867,-773.2956 872.5831,-750.0189 865,-731.2656 844.2991,-680.0716 804.0126,-630.4165 775.1838,-598.9641"/>
<polygon fill="#0044a0" stroke="#0044a0" points="777.4937,-596.3085 768.1202,-591.3701 772.3682,-601.076 777.4937,-596.3085"/>
</g>
<!-- t8 -->
<g id="node9" class="node">
<title>t8</title>
<polygon fill="#efa03b" stroke="#000000" points="732.6759,-709.2656 413.3241,-709.2656 409.3241,-705.2656 409.3241,-673.2656 728.6759,-673.2656 732.6759,-677.2656 732.6759,-709.2656"/>
<polyline fill="none" stroke="#000000" points="728.6759,-705.2656 409.3241,-705.2656 "/>
<polyline fill="none" stroke="#000000" points="728.6759,-705.2656 728.6759,-673.2656 "/>
<polyline fill="none" stroke="#000000" points="728.6759,-705.2656 732.6759,-709.2656 "/>
<text text-anchor="middle" x="571" y="-685.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.preprocess_deskew</text>
</g>
<!-- t7&#45;&gt;t8 -->
<g id="edge9" class="edge">
<title>t7&#45;&gt;t8</title>
<path fill="none" stroke="#0044a0" d="M619.9243,-731.2319C613.9709,-726.3685 607.4267,-721.0226 601.115,-715.8666"/>
<polygon fill="#0044a0" stroke="#0044a0" points="603.2653,-713.1037 593.3065,-709.4878 598.8367,-718.5248 603.2653,-713.1037"/>
</g>
<!-- t7&#45;&gt;t12 -->
<g id="edge17" class="edge">
<title>t7&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M717.0346,-731.1646C726.8757,-725.6884 735.7103,-718.5557 742,-709.2656 763.0291,-678.205 759.4083,-634.0888 752.8082,-602.8898"/>
<polygon fill="#0044a0" stroke="#0044a0" points="756.1481,-601.7976 750.487,-592.842 749.3278,-603.3733 756.1481,-601.7976"/>
</g>
<!-- t9 -->
<g id="node10" class="node">
<title>t9</title>
<polygon fill="#efa03b" stroke="#000000" points="712.8828,-651.2656 411.1172,-651.2656 407.1172,-647.2656 407.1172,-615.2656 708.8828,-615.2656 712.8828,-619.2656 712.8828,-651.2656"/>
<polyline fill="none" stroke="#000000" points="708.8828,-647.2656 407.1172,-647.2656 "/>
<polyline fill="none" stroke="#000000" points="708.8828,-647.2656 708.8828,-615.2656 "/>
<polyline fill="none" stroke="#000000" points="708.8828,-647.2656 712.8828,-651.2656 "/>
<text text-anchor="middle" x="560" y="-627.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.preprocess_clean</text>
</g>
<!-- t8&#45;&gt;t9 -->
<g id="edge10" class="edge">
<title>t8&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M567.5798,-673.2319C566.8624,-669.4493 566.0897,-665.3747 565.3216,-661.3247"/>
<polygon fill="#0044a0" stroke="#0044a0" points="568.7581,-660.6605 563.4559,-651.4878 561.8807,-661.9649 568.7581,-660.6605"/>
</g>
<!-- t8&#45;&gt;t12 -->
<g id="edge16" class="edge">
<title>t8&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M670.0045,-673.2618C693.2528,-667.2338 713.6919,-659.8307 722,-651.2656 734.1971,-638.6912 739.4901,-620.2051 741.6123,-603.5914"/>
<polygon fill="#0044a0" stroke="#0044a0" points="745.1041,-603.8427 742.5574,-593.5585 738.1349,-603.1862 745.1041,-603.8427"/>
</g>
<!-- t10 -->
<g id="node11" class="node">
<title>t10</title>
<polygon fill="#efa03b" stroke="#000000" points="340.1094,-583.2656 35.8906,-583.2656 31.8906,-579.2656 31.8906,-547.2656 336.1094,-547.2656 340.1094,-551.2656 340.1094,-583.2656"/>
<polyline fill="none" stroke="#000000" points="336.1094,-579.2656 31.8906,-579.2656 "/>
<polyline fill="none" stroke="#000000" points="336.1094,-579.2656 336.1094,-547.2656 "/>
<polyline fill="none" stroke="#000000" points="336.1094,-579.2656 340.1094,-583.2656 "/>
<text text-anchor="middle" x="186" y="-559.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.select_ocr_image</text>
</g>
<!-- t9&#45;&gt;t10 -->
<g id="edge11" class="edge">
<title>t9&#45;&gt;t10</title>
<path fill="none" stroke="#0044a0" d="M460.7568,-615.2213C409.7279,-605.9434 347.4237,-594.6153 295.0537,-585.0935"/>
<polygon fill="#0044a0" stroke="#0044a0" points="295.5223,-581.6214 285.0575,-583.276 294.2701,-588.5085 295.5223,-581.6214"/>
</g>
<!-- t9&#45;&gt;t12 -->
<g id="edge15" class="edge">
<title>t9&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M608.2948,-615.2213C627.0226,-608.2242 648.8712,-600.0609 669.4134,-592.3859"/>
<polygon fill="#0044a0" stroke="#0044a0" points="670.9148,-595.5613 679.0573,-588.7826 668.4648,-589.004 670.9148,-595.5613"/>
</g>
<!-- t11 -->
<g id="node12" class="node">
<title>t11</title>
<polygon fill="#00cc66" stroke="#000000" points="334.0996,-505.2656 19.9004,-505.2656 15.9004,-501.2656 15.9004,-469.2656 330.0996,-469.2656 334.0996,-473.2656 334.0996,-505.2656"/>
<polyline fill="none" stroke="#000000" points="330.0996,-501.2656 15.9004,-501.2656 "/>
<polyline fill="none" stroke="#000000" points="330.0996,-501.2656 330.0996,-469.2656 "/>
<polyline fill="none" stroke="#000000" points="330.0996,-501.2656 334.0996,-505.2656 "/>
<text text-anchor="middle" x="175" y="-481.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.ocr_tesseract_hocr</text>
</g>
<!-- t10&#45;&gt;t11 -->
<g id="edge12" class="edge">
<title>t10&#45;&gt;t11</title>
<path fill="none" stroke="#0044a0" d="M183.4487,-547.1748C182.1254,-537.7914 180.4788,-526.1154 178.9917,-515.5701"/>
<polygon fill="#0044a0" stroke="#0044a0" points="182.4378,-514.9417 177.5755,-505.5285 175.5064,-515.9193 182.4378,-514.9417"/>
</g>
<!-- t15 -->
<g id="node14" class="node">
<title>t15</title>
<polygon fill="#ff69b4" stroke="#000000" points="736.2696,-505.2656 355.7304,-505.2656 351.7304,-501.2656 351.7304,-469.2656 732.2696,-469.2656 736.2696,-473.2656 736.2696,-505.2656"/>
<polyline fill="none" stroke="#000000" points="732.2696,-501.2656 351.7304,-501.2656 "/>
<polyline fill="none" stroke="#000000" points="732.2696,-501.2656 732.2696,-469.2656 "/>
<polyline fill="none" stroke="#000000" points="732.2696,-501.2656 736.2696,-505.2656 "/>
<text text-anchor="middle" x="544" y="-481.2656" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.ocr_tesseract_textonly_pdf</text>
</g>
<!-- t10&#45;&gt;t15 -->
<g id="edge14" class="edge">
<title>t10&#45;&gt;t15</title>
<path fill="none" stroke="#0044a0" d="M269.0322,-547.1748C323.4947,-535.3086 394.7854,-519.776 451.1284,-507.5002"/>
<polygon fill="#0044a0" stroke="#0044a0" points="452.0829,-510.8744 461.1086,-505.3257 450.5927,-504.0348 452.0829,-510.8744"/>
</g>
<!-- t14 -->
<g id="node13" class="node">
<title>t14</title>
<polygon fill="#00cc66" stroke="#000000" points="330.0433,-411.7258 21.9567,-411.7258 17.9567,-407.7258 17.9567,-375.7258 326.0433,-375.7258 330.0433,-379.7258 330.0433,-411.7258"/>
<polyline fill="none" stroke="#000000" points="326.0433,-407.7258 17.9567,-407.7258 "/>
<polyline fill="none" stroke="#000000" points="326.0433,-407.7258 326.0433,-375.7258 "/>
<polyline fill="none" stroke="#000000" points="326.0433,-407.7258 330.0433,-411.7258 "/>
<text text-anchor="middle" x="174" y="-387.7258" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.render_hocr_page</text>
</g>
<!-- t11&#45;&gt;t14 -->
<g id="edge13" class="edge">
<title>t11&#45;&gt;t14</title>
<path fill="none" stroke="#0044a0" d="M174.8072,-469.2302C174.6642,-455.8584 174.4666,-437.3702 174.3027,-422.0357"/>
<polygon fill="#0044a0" stroke="#0044a0" points="177.8024,-421.987 174.1956,-412.025 170.8028,-422.0619 177.8024,-421.987"/>
</g>
<!-- t19 -->
<g id="node22" class="node">
<title>t19</title>
<polygon fill="#efa03b" stroke="#000000" points="351.7205,-381.5085 545,-354.1897 738.2795,-381.5085 738.0991,-425.7112 351.9009,-425.7112 351.7205,-381.5085"/>
<polygon fill="none" stroke="#000000" points="347.7033,-378.0329 545,-350.1463 742.2967,-378.0329 742.0858,-429.7148 347.9142,-429.7148 347.7033,-378.0329"/>
<text text-anchor="middle" x="545" y="-387.7258" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.merge_sidecars</text>
</g>
<!-- t11&#45;&gt;t19 -->
<g id="edge32" class="edge">
<title>t11&#45;&gt;t19</title>
<path fill="none" stroke="#0044a0" d="M246.3397,-469.2302C287.966,-458.7066 342.1269,-445.0142 392.8772,-432.184"/>
<polygon fill="#0044a0" stroke="#0044a0" points="393.8058,-435.5594 402.6429,-429.7151 392.0901,-428.773 393.8058,-435.5594"/>
</g>
<!-- t14&#45;&gt;t16 -->
<g id="edge22" class="edge">
<title>t14&#45;&gt;t16</title>
<path fill="none" stroke="#0044a0" d="M230.6615,-375.7128C262.2207,-366.4581 302.4172,-355.9425 339,-350.1861 448.3957,-332.9725 745.7869,-320.7866 927.9215,-314.6743"/>
<polygon fill="#0044a0" stroke="#0044a0" points="928.248,-318.1655 938.1258,-314.3342 928.0148,-311.1693 928.248,-318.1655"/>
</g>
<!-- t15&#45;&gt;t16 -->
<g id="edge21" class="edge">
<title>t15&#45;&gt;t16</title>
<path fill="none" stroke="#0044a0" d="M630.0636,-469.2083C667.5329,-460.6467 711.76,-449.5912 751,-437.2656 850.4985,-406.0121 963.1653,-359.5222 1026.586,-332.2894"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1028.1566,-335.4238 1035.9553,-328.2523 1025.3866,-328.9951 1028.1566,-335.4238"/>
</g>
<!-- t15&#45;&gt;t19 -->
<g id="edge31" class="edge">
<title>t15&#45;&gt;t19</title>
<path fill="none" stroke="#0044a0" d="M544.1928,-469.2302C544.2822,-460.8687 544.393,-450.5066 544.5035,-440.166"/>
<polygon fill="#0044a0" stroke="#0044a0" points="548.006,-439.9443 544.6132,-429.9075 541.0064,-439.8694 548.006,-439.9443"/>
</g>
<!-- t12&#45;&gt;t13 -->
<g id="edge19" class="edge">
<title>t12&#45;&gt;t13</title>
<path fill="none" stroke="#0044a0" d="M833.9833,-543.8486C879.0975,-533.3444 933.4556,-520.6879 979.3116,-510.0109"/>
<polygon fill="#0044a0" stroke="#0044a0" points="980.3156,-513.3709 989.2614,-507.6943 978.7282,-506.5532 980.3156,-513.3709"/>
</g>
<!-- t13&#45;&gt;t16 -->
<g id="edge23" class="edge">
<title>t13&#45;&gt;t16</title>
<path fill="none" stroke="#0044a0" d="M1077,-459.2068C1077,-426.2624 1077,-371.9187 1077,-338.7552"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1080.5001,-338.3818 1077,-328.3819 1073.5001,-338.3819 1080.5001,-338.3818"/>
</g>
<!-- t16&#45;&gt;t18 -->
<g id="edge27" class="edge">
<title>t16&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1117.3592,-292.0592C1133.8695,-284.6437 1153.6803,-275.7459 1173.2953,-266.9361"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1174.8979,-270.0531 1182.586,-262.7632 1172.0299,-263.6676 1174.8979,-270.0531"/>
</g>
<!-- t17&#45;&gt;t18 -->
<g id="edge26" class="edge">
<title>t17&#45;&gt;t18</title>
<path fill="none" stroke="#0044a0" d="M1326.2292,-973.7283C1377.8362,-955.768 1439,-922.4247 1439,-865.2656 1439,-865.2656 1439,-865.2656 1439,-393.7258 1439,-338.7092 1393.2056,-296.3464 1348.3356,-268.0433"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1350.0403,-264.9831 1339.6842,-262.7465 1346.3852,-270.9531 1350.0403,-264.9831"/>
</g>
<!-- t20 -->
<g id="node20" class="node">
<title>t20</title>
<polygon fill="#efa03b" stroke="#000000" points="1400.3184,-161.1062 1129.6816,-161.1062 1125.6816,-157.1062 1125.6816,-125.1062 1396.3184,-125.1062 1400.3184,-129.1062 1400.3184,-161.1062"/>
<polyline fill="none" stroke="#000000" points="1396.3184,-157.1062 1125.6816,-157.1062 "/>
<polyline fill="none" stroke="#000000" points="1396.3184,-157.1062 1396.3184,-125.1062 "/>
<polyline fill="none" stroke="#000000" points="1396.3184,-157.1062 1400.3184,-161.1062 "/>
<text text-anchor="middle" x="1263" y="-137.1062" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.optimize_pdf</text>
</g>
<!-- t18&#45;&gt;t20 -->
<g id="edge29" class="edge">
<title>t18&#45;&gt;t20</title>
<path fill="none" stroke="#0044a0" d="M1263,-182.9188C1263,-179.129 1263,-175.3607 1263,-171.739"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1266.5001,-171.4394 1263,-161.4394 1259.5001,-171.4394 1266.5001,-171.4394"/>
</g>
<!-- t21 -->
<g id="node21" class="node">
<title>t21</title>
<polygon fill="#efa03b" stroke="#000000" points="1096.0652,-47.332 1263,-20.0049 1429.9348,-47.332 1429.779,-91.5483 1096.221,-91.5483 1096.0652,-47.332"/>
<polygon fill="none" stroke="#000000" points="1092.0538,-43.9306 1263,-15.9468 1433.9462,-43.9306 1433.7643,-95.5532 1092.2357,-95.5532 1092.0538,-43.9306"/>
<text text-anchor="middle" x="1263" y="-53.5531" font-family="Times,serif" font-size="20.00" fill="#000000">ocrmypdf.pipeline.copy_final</text>
</g>
<!-- t20&#45;&gt;t21 -->
<g id="edge30" class="edge">
<title>t20&#45;&gt;t21</title>
<path fill="none" stroke="#0044a0" d="M1263,-124.9764C1263,-119.37 1263,-112.9163 1263,-106.2938"/>
<polygon fill="#0044a0" stroke="#0044a0" points="1266.5001,-105.8456 1263,-95.8457 1259.5001,-105.8457 1266.5001,-105.8456"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 25 KiB

+165
View File
@@ -0,0 +1,165 @@
=======
Plugins
=======
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL
NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and
"OPTIONAL" in this document are to be interpreted as described in
RFC 2119.
You can use plugins to customize the behavior of OCRmyPDF at certain points of
interest.
Currently, it is possible to:
- add new command line arguments
- override the decision for whether or not to perform OCR on a particular file
- modify the image is about to be sent for OCR
- modify the page image before it is converted to PDF
- replace the Tesseract OCR with another OCR engine that has similar behavior
- replace Ghostscript with another PDF to image converter (rasterizer) or
PDF/A generator
OCRmyPDF plugins are based on the Python ``pluggy`` package and conform to its
conventions. Note that: plugins installed with as setuptools entrypoints are
not checked currently, because OCRmyPDF assumes you may not want to enable
plugins for all files.
Script plugins
==============
Script plugins may be called from the command line, by specifying the name of a file.
Script plugins may be convenient for informal or "one-off" plugins, when a certain
batch of files needs a special processing step for example.
.. code-block:: bash
ocrmypdf --plugin ocrmypdf_example_plugin.py input.pdf output.pdf
Multiple plugins may be installed by issuing the ``--plugin`` argument multiple times.
Packaged plugins
================
Installed plugins may be installed into the same virtual environment as OCRmyPDF
is installed into. They may be invoked using Python standard module naming.
If you are intending to distribute a plugin, please package it.
.. code-block:: bash
ocrmypdf --plugin ocrmypdf_fancypants.pockets.contents input.pdf output.pdf
OCRmyPDF does not automatically import plugins, because the assumption is that
plugins affect different files differently and you may not want them activated
all the time. The command line or ``ocrmypdf.ocr(plugin='...')`` must call
for them.
Third parties that wish to distribute packages for ocrmypdf should package them
as packaged plugins, and these modules should begin with the name ``ocrmypdf_``
similar to ``pytest`` packages such as ``pytest-cov`` (the package) and
``pytest_cov`` (the module).
.. note::
We strongly recommend plugin authors name their plugins with the prefix
``ocrmypdf-`` (for the package name on PyPI) and ``ocrmypdf_`` (for the
module), just like pytest plugins.
Plugin requirements
===================
OCRmyPDF generally uses multiple worker processes. When a new worker is started,
Python will import all plugins again, including all plugins that were imported earlier.
This means that the global state of a plugin in one worker will not be shared with
other workers. As such, plugin hook implementations should be stateless, relying
only on their inputs. Hook implementations may use their input parameters to
to obtain a reference to shared state prepared by another hook implementation.
Plugins must expect that other instances of the plugin will be running
simultaneously.
The ``context`` object that is passed to many hooks can be used to share information
about a file being worked on. Plugins must write private, plugin-specific data to
a subfolder named ``{options.work_folder}/ocrmypdf-plugin-name``. Plugins MAY
read and write files in ``options.work_folder``, but should be aware that their
semantics are subject to change.
OCRmyPDF will delete ``options.work_folder`` when it has finished OCRing
a file, unless invoked with ``--keep-temporary-files``.
The documentation for some plugin hooks contain a detailed description of the
execution context in which they will be called.
Plugins should be prepared to work whether executed in worker threads or worker
processes. Generally, OCRmyPDF uses processes, but has a semi-hidden threaded
argument that simplifies debugging.
Plugin hooks
============
A plugin may provide the following hooks. Hooks must be decorated with
``ocrmypdf.hookimpl``, for example:
.. code-block:: python
from ocrmpydf import hookimpl
@hookimpl
def add_options(parser):
pass
The following is a complete list of hooks that are available, and when
they are called.
.. _firstresult:
**Note on firstresult hooks**
If multiple plugins install implementations for this hook, they will be called in
the reverse of the order in which they are installed (i.e., last plugin wins).
When each hook implementation is called in order, the first implementation that
returns a value other than ``None`` will "win" and prevent execution of all other
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
chaining operations.
Custom command line arguments
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.add_options
.. autofunction:: ocrmypdf.pluginspec.check_options
Applying special behavior before processing
-------------------------------------------
.. autofunction:: ocrmypdf.pluginspec.validate
PDF page to image
-----------------
.. autofunction:: ocrmypdf.pluginspec.rasterize_pdf_page
Modifying intermediate images
-----------------------------
.. autofunction:: ocrmypdf.pluginspec.filter_ocr_image
.. autofunction:: ocrmypdf.pluginspec.filter_page_image
OCR engine
----------
.. autofunction:: ocrmypdf.pluginspec.get_ocr_engine
.. autoclass:: ocrmypdf.pluginspec.OcrEngine
:members:
.. automethod:: __str__
.. autoclass:: ocrmypdf.pluginspec.OrientationConfidence
PDF/A production
----------------
.. autofunction:: ocrmypdf.pluginspec.generate_pdfa
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env python3
# Copyright 2016 findingorder: https://github.com/findingorder
#
# 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.
# This script must be edited to meet your needs.
import logging
import os
import sys
import ocrmypdf
# pylint: disable=logging-format-interpolation
# pylint: disable=logging-not-lazy
script_dir = os.path.dirname(os.path.realpath(__file__))
print(script_dir + '/batch.py: Start')
if len(sys.argv) > 1:
start_dir = sys.argv[1]
else:
start_dir = '.'
if len(sys.argv) > 2:
log_file = sys.argv[2]
else:
log_file = script_dir + '/ocr-tree.log'
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(message)s',
filename=log_file,
filemode='w',
)
ocrmypdf.configure_logging(ocrmypdf.Verbosity.default)
for dir_name, subdirs, file_list in os.walk(start_dir):
logging.info(dir_name + '\n')
os.chdir(dir_name)
for filename in file_list:
file_ext = os.path.splitext(filename)[1]
if file_ext == '.pdf':
full_path = dir_name + '/' + filename
print(full_path)
result = ocrmypdf.ocr(filename, filename, deskew=True)
if result == ocrmypdf.ExitCode.already_done_ocr:
print("Skipped document because it already contained text")
elif result == ocrmypdf.ExitCode.ok:
print("OCR complete")
logging.info(result)
+120
View File
@@ -0,0 +1,120 @@
# ocrmypdf completion -*- shell-script -*-
# Copyright 2019 Frank Pille
# Copyright 2020 Alex Willner
#
# 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.
set -o errexit
_ocrmypdf()
{
local cur prev cword words split
# Homebrew on Macs have version 1.3 of bash-completion which doesn't include - see #502
if declare -F _init_completions >/dev/null 2>&1; then
_init_completion -s || return
else
COMPREPLY=()
_get_comp_words_by_ref cur prev words cword
fi
if [[ $cur == -* ]]; then
COMPREPLY=( $( compgen -W '--language --image-dpi --output-type
--sidecar --version --jobs --quiet --verbose --title --author
--subject --keywords --rotate-pages --remove-background --deskew
--clean --clean-final --unpaper-args --oversample --remove-vectors
--threshold --force-ocr --skip-text --redo-ocr
--skip-big --jpeg-quality --png-quality --jbig2-lossy
--max-image-mpixels --tesseract-config --tesseract-pagesegmode
--help --tesseract-oem --pdf-renderer --tesseract-timeout
--rotate-pages-threshold --pdfa-image-compression --user-words
--user-patterns --keep-temporary-files --output-type
--no-progress-bar --pages --fast-web-view' \
-- "$cur" ) )
return
else
_filedir
return
fi
case $prev in
--version|-h|--help)
return
;;
--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)
COMPREPLY=$( command tesseract --list-langs 2>/dev/null )
COMPREPLY=( $( compgen -W '${COMPREPLY[@]##*:}' -- "$cur" ) )
return
;;
--image-dpi|--oversample|--skip-big|--max-image-mpixels|\
--tesseract-timeout|--rotate-pages-threshold)
COMPREPLY=( $( compgen -P "$cur" -W '{0..9}' ) )
return
;;
-j|--jobs)
COMPREPLY=( $( compgen -W '{1..'$( _ncpus )'}' -- "$cur" ) )
return
;;
-v|--verbose)
COMPREPLY=( $( compgen -W '{0..2}' -- "$cur" ) ) # max level ?
return
;;
--tesseract-pagesegmode)
COMPREPLY=( $( compgen -W '{1..13}' -- "$cur" ) )
return
;;
--sidecar|--title|--author|--subject|--keywords|--unpaper-args|--pages|--fast-web-view)
# argument required but no completions available
return
;;
esac
$split && return
} &&
complete -F _ocrmypdf ocrmypdf
set +o errexit
# ex: filetype=sh
+138
View File
@@ -0,0 +1,138 @@
# Copyright 2020 James R. Barlow
#
# 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.
complete -c ocrmypdf -x -n '__fish_is_first_arg' -l version
complete -c ocrmypdf -x -n '__fish_is_first_arg' -s h -s "?" -l help
complete -c ocrmypdf -r -l sidecar -d "write OCR to text file"
complete -c ocrmypdf -x -s q -l quiet
complete -c ocrmypdf -s r -l rotate-pages -d "rotate pages to correct orientation"
complete -c ocrmypdf -s d -l deskew -d "fix small horizontal alignment skew"
complete -c ocrmypdf -s c -l clean -d "clean document images before OCR"
complete -c ocrmypdf -s i -l clean-final -d "clean document images and keep result"
complete -c ocrmypdf -l remove-vectors -d "don't send vector objects to OCR"
complete -c ocrmypdf -l threshold -d "threshold images before OCR"
complete -c ocrmypdf -s f -l force-ocr -d "OCR documents that already have printable text"
complete -c ocrmypdf -s s -l skip-ocr -d "skip OCR on pages that text, otherwise try OCR"
complete -c ocrmypdf -l redo-ocr -d "redo OCR on any pages that seem to have OCR already"
complete -c ocrmypdf -s k -l keep-temporary-files -d "keep temporary files (debug)"
function __fish_ocrmypdf_languages
set langs (tesseract --list-langs ^/dev/null)
set arr (string split '\n' $langs)
for lang in $arr[2..-1]
echo $lang
end
end
complete -c ocrmypdf -x -s l -l language -a '(__fish_ocrmypdf_languages)' -d "language"
complete -c ocrmypdf -x -l image-dpi -d "assume this DPI if input image DPI is unknown"
function __fish_ocrmypdf_output_type
echo -e "pdfa\t"(_ "output a PDF/A (default)")
echo -e "pdf\t"(_ "output a standard PDF")
echo -e "pdfa-1\t"(_ "output a PDF/A-1b")
echo -e "pdfa-2\t"(_ "output a PDF/A-2b")
echo -e "pdfa-3\t"(_ "output a PDF/A-3b")
end
complete -c ocrmypdf -x -l output-type -a '(__fish_ocrmypdf_output_type)' -d "select PDF output options"
function __fish_ocrmypdf_pdf_renderer
echo -e "auto\t"(_ "auto select PDF renderer")
echo -e "hocr\t"(_ "use hocr renderer")
echo -e "sandwich\t"(_ "use sandwich renderer")
end
complete -c ocrmypdf -x -l pdf-renderer -a '(__fish_ocrmypdf_pdf_renderer)' -d "select PDF renderer options"
function __fish_ocrmypdf_optimize
echo -e "0\t"(_ "do not optimize")
echo -e "1\t"(_ "do safe, lossless optimizations (default)")
echo -e "2\t"(_ "do some lossy optimizations")
echo -e "3\t"(_ "do aggressive lossy optimizations (including lossy JBIG2)")
end
complete -c ocrmypdf -x -s O -l optimize -a '(__fish_ocrmypdf_optimize)' -d "select optimization level"
function __fish_ocrmypdf_verbose
echo -e "0\t"(_ "standard output messages")
echo -e "1\t"(_ "troubleshooting output messages")
echo -e "2\t"(_ "debugging output messages")
end
complete -c ocrmypdf -x -s v -l verbose -a '(__fish_ocrmypdf_verbose)' -d "set verbosity level"
complete -c ocrmypdf -x -l no-progress-bar -d "disable the progress bar"
function __fish_ocrmypdf_pdfa_compression
echo -e "auto\t"(_ "let Ghostscript decide how to compress images")
echo -e "jpeg\t"(_ "convert color and grayscale images to JPEG")
echo -e "lossless\t"(_ "convert color and grayscale images to lossless (PNG)")
end
complete -c ocrmypdf -x -l pdfa-image-compression -a '(__fish_ocrmypdf_pdfa_compression)' -d "set PDF/A image compression options"
complete -c ocrmypdf -x -s j -l jobs -d "how many worker processes to use"
complete -c ocrmypdf -x -l title -d "set metadata"
complete -c ocrmypdf -x -l author -d "set metadata"
complete -c ocrmypdf -x -l subject -d "set metadata"
complete -c ocrmypdf -x -l keywords -d "set metadata"
complete -c ocrmypdf -x -l oversample -d "oversample images to this DPI"
complete -c ocrmypdf -x -l skip-big -d "skip OCR on pages larger than this many MPixels"
complete -c ocrmypdf -x -l jpeg-quality -d "JPEG quality [0..100]"
complete -c ocrmypdf -x -l png-quality -d "PNG quality [0..100]"
complete -c ocrmypdf -x -l jbig2-lossy -d "enable lossy JBIG2 (see docs)"
complete -c ocrmypdf -x -l max-image-mpixels -d "image decompression bomb threshold"
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"
function __fish_ocrmypdf_tesseract_pagesegmode
echo -e "0\t"(_ "orientation and script detection (OSD) only")
echo -e "1\t"(_ "automatic page segmentation with OSD")
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 "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 "6\t"(_ "assume a single uniform block of text")
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 "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 "11\t"(_ "sparse text - find as much text as possible in no particular order")
echo -e "12\t"(_ "sparse text with OSD")
echo -e "13\t"(_ "raw line - treat the image as a single text line")
end
complete -c ocrmypdf -x -l tesseract-pagesegmode -a '(__fish_ocrmypdf_tesseract_pagesegmode)' -d "set tesseract --psm"
function __fish_ocrmypdf_tesseract_oem
echo -e "0\t"(_ "legacy engine only")
echo -e "1\t"(_ "neural nets LSTM engine only")
echo -e "2\t"(_ "legacy + LSTM engines")
echo -e "3\t"(_ "default, based on what is available")
end
complete -c ocrmypdf -x -l tesseract-oem -a '(__fish_ocrmypdf_tesseract_oem)' -d "set tesseract --oem"
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 -r -l user-words -d "specify location of user words file"
complete -c ocrmypdf -r -l user-patterns -d "specify location of user patterns file"
complete -c ocrmypdf -x -l fast-web-view -d "if file size if above this amount in MB, linearize PDF"
complete -c ocrmypdf -x -a "(__fish_complete_suffix .pdf)"
+15
View File
@@ -0,0 +1,15 @@
---
version: "3.3"
services:
ocrmypdf:
restart: always
container_name: ocrmypdf
image: jbarlow83/ocrmypdf
volumes:
- "/media/scan:/input"
- "/mnt/scan:/output"
environment:
- OCR_OUTPUT_DIRECTORY_YEAR_MONT=0
user: "<SET TO YOUR USER ID>:<SET TO YOUR GROUP ID>"
entrypoint: python3
command: watcher.py
+58
View File
@@ -0,0 +1,58 @@
# © 2020 James R Barlow: https://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.
import logging
from PIL import Image
from ocrmypdf import hookimpl
log = logging.getLogger(__name__)
@hookimpl
def add_options(parser):
parser.add_argument('--grayscale-ocr', action='store_true')
@hookimpl
def prepare(options):
pass
@hookimpl
def validate(pdfinfo, options):
pass
@hookimpl
def filter_ocr_image(page, image):
if page.options.grayscale_ocr:
log.info("graying")
return image.convert('L')
return image
@hookimpl
def filter_page_image(page, image_filename):
output = image_filename.with_suffix('.jpg')
with Image.open(image_filename) as im:
im.save(output)
return output
+90
View File
@@ -0,0 +1,90 @@
#!/bin/env python3
# Copyright 2017 github.com/Enantiomerie
#
# 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.
# This script must be edited to meet your needs.
import logging
import os
import shutil
import subprocess
import sys
import time
# pylint: disable=logging-format-interpolation
# pylint: disable=logging-not-lazy
script_dir = os.path.dirname(os.path.realpath(__file__))
timestamp = time.strftime("%Y-%m-%d-%H%M_")
log_file = script_dir + '/' + timestamp + 'ocrmypdf.log'
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s %(message)s',
filename=log_file,
filemode='w',
)
if len(sys.argv) > 1:
start_dir = sys.argv[1]
else:
start_dir = '.'
for dir_name, subdirs, file_list in os.walk(start_dir):
logging.info(dir_name)
os.chdir(dir_name)
for filename in file_list:
file_stem, file_ext = os.path.splitext(filename)
if file_ext != '.pdf':
continue
full_path = os.path.join(dir_name, filename)
timestamp_ocr = time.strftime("%Y-%m-%d-%H%M_OCR_")
filename_ocr = timestamp_ocr + file_stem + '.pdf'
# create string for pdf processing
# the script is processed as root user via chron
cmd = [
'docker',
'run',
'--rm',
'-i',
'jbarlow83/ocrmypdf',
'--deskew',
'-',
'-',
]
logging.info(cmd)
full_path_ocr = os.path.join(dir_name, filename_ocr)
with open(filename, 'rb') as input_file, open(
full_path_ocr, 'wb'
) as output_file:
proc = subprocess.run(
cmd,
stdin=input_file,
stdout=output_file,
stderr=subprocess.PIPE,
check=False,
)
logging.info(proc.stderr.read())
os.chmod(full_path_ocr, 0o664)
os.chmod(full_path, 0o664)
full_path_ocr_archive = sys.argv[2]
full_path_archive = sys.argv[2] + '/no_ocr'
shutil.move(full_path_ocr, full_path_ocr_archive)
shutil.move(full_path, full_path_archive)
logging.info('Finished.\n')
+161
View File
@@ -0,0 +1,161 @@
# Copyright (C) 2019 Ian Alexander: https://github.com/ianalexander
# Copyright (C) 2020 James R Barlow: https://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.
import json
import logging
import os
import sys
import time
from datetime import datetime
from pathlib import Path
import pikepdf
from watchdog.events import PatternMatchingEventHandler
from watchdog.observers import Observer
from watchdog.observers.polling import PollingObserver
import ocrmypdf
# pylint: disable=logging-format-interpolation
INPUT_DIRECTORY = os.getenv('OCR_INPUT_DIRECTORY', '/input')
OUTPUT_DIRECTORY = os.getenv('OCR_OUTPUT_DIRECTORY', '/output')
OUTPUT_DIRECTORY_YEAR_MONTH = bool(os.getenv('OCR_OUTPUT_DIRECTORY_YEAR_MONTH', ''))
ON_SUCCESS_DELETE = bool(os.getenv('OCR_ON_SUCCESS_DELETE', ''))
DESKEW = bool(os.getenv('OCR_DESKEW', ''))
OCR_JSON_SETTINGS = json.loads(os.getenv('OCR_JSON_SETTINGS', '{}'))
POLL_NEW_FILE_SECONDS = int(os.getenv('OCR_POLL_NEW_FILE_SECONDS', '1'))
USE_POLLING = bool(os.getenv('OCR_USE_POLLING', ''))
LOGLEVEL = os.getenv('OCR_LOGLEVEL', 'INFO').upper()
PATTERNS = ['*.pdf']
log = logging.getLogger('ocrmypdf-watcher')
def get_output_dir(root, basename):
if OUTPUT_DIRECTORY_YEAR_MONTH:
today = datetime.today()
output_directory_year_month = (
Path(root) / str(today.year) / f'{today.month:02d}'
)
if not output_directory_year_month.exists():
output_directory_year_month.mkdir(parents=True, exist_ok=True)
output_path = Path(output_directory_year_month) / basename
else:
output_path = Path(OUTPUT_DIRECTORY) / basename
return output_path
def wait_for_file_ready(file_path):
# This loop waits to make sure that the file is completely loaded on
# disk before attempting to read. Docker sometimes will publish the
# watchdog event before the file is actually fully on disk, causing
# pikepdf to fail.
retries = 5
while retries:
try:
pdf = pikepdf.open(file_path)
except (FileNotFoundError, pikepdf.PdfError) as e:
log.info(f"File {file_path} is not ready yet")
log.debug("Exception was", exc_info=e)
time.sleep(POLL_NEW_FILE_SECONDS)
retries -= 1
else:
pdf.close()
return True
return False
def execute_ocrmypdf(file_path):
file_path = Path(file_path)
output_path = get_output_dir(OUTPUT_DIRECTORY, file_path.name)
log.info("-" * 20)
log.info(f'New file: {file_path}. Waiting until fully loaded...')
if not wait_for_file_ready(file_path):
log.info(f"Gave up waiting for {file_path} to become ready")
return
log.info(f'Attempting to OCRmyPDF to: {output_path}')
exit_code = ocrmypdf.ocr(
input_file=file_path,
output_file=output_path,
deskew=DESKEW,
**OCR_JSON_SETTINGS,
)
if exit_code == 0 and ON_SUCCESS_DELETE:
log.info(f'OCR is done. Deleting: {file_path}')
file_path.unlink()
else:
log.info('OCR is done')
class HandleObserverEvent(PatternMatchingEventHandler):
def on_any_event(self, event):
if event.event_type in ['created']:
execute_ocrmypdf(event.src_path)
def main():
ocrmypdf.configure_logging(
verbosity=ocrmypdf.Verbosity.default, manage_root_logger=True
)
log.setLevel(LOGLEVEL)
log.info(
f"Starting OCRmyPDF watcher with config:\n"
f"Input Directory: {INPUT_DIRECTORY}\n"
f"Output Directory: {OUTPUT_DIRECTORY}\n"
f"Output Directory Year & Month: {OUTPUT_DIRECTORY_YEAR_MONTH}"
)
log.debug(
f"INPUT_DIRECTORY: {INPUT_DIRECTORY}\n"
f"OUTPUT_DIRECTORY: {OUTPUT_DIRECTORY}\n"
f"OUTPUT_DIRECTORY_YEAR_MONTH: {OUTPUT_DIRECTORY_YEAR_MONTH}\n"
f"ON_SUCCESS_DELETE: {ON_SUCCESS_DELETE}\n"
f"DESKEW: {DESKEW}\n"
f"ARGS: {OCR_JSON_SETTINGS}\n"
f"POLL_NEW_FILE_SECONDS: {POLL_NEW_FILE_SECONDS}\n"
f"USE_POLLING: {USE_POLLING}\n"
f"LOGLEVEL: {LOGLEVEL}\n"
)
if 'input_file' in OCR_JSON_SETTINGS or 'output_file' in OCR_JSON_SETTINGS:
log.error('OCR_JSON_SETTINGS should not specify input file or output file')
sys.exit(1)
handler = HandleObserverEvent(patterns=PATTERNS)
if USE_POLLING:
observer = PollingObserver()
else:
observer = Observer()
observer.schedule(handler, INPUT_DIRECTORY, recursive=True)
observer.start()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
observer.stop()
observer.join()
if __name__ == "__main__":
main()
+126
View File
@@ -0,0 +1,126 @@
# webservice.py wrapper for OCRmyPDF
# Copyright (C) 2019 James R. Barlow: github.com/jbarlow83
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
"""This is a simple web service/HTTP wrapper for OCRmyPDF
This may be more convenient than the command line tool for some Docker users.
Note that OCRmyPDF uses Ghostscript, which is licensed under AGPLv3+. While
OCRmyPDF is under GPLv3, this file is distributed under the Affero GPLv3+ license,
to emphasize that SaaS deployments should make sure they comply with
Ghostscript's license as well as OCRmyPDF's.
"""
import os
import shlex
from subprocess import PIPE, run
from tempfile import TemporaryDirectory
from flask import (
Flask,
Response,
abort,
flash,
redirect,
request,
send_from_directory,
url_for,
)
from werkzeug.utils import secure_filename
app = Flask(__name__)
app.secret_key = "secret"
app.config['MAX_CONTENT_LENGTH'] = 50_000_000
app.config.from_envvar("OCRMYPDF_WEBSERVICE_SETTINGS", silent=True)
ALLOWED_EXTENSIONS = set(["pdf"])
def allowed_file(filename):
return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
def do_ocrmypdf(file):
uploaddir = TemporaryDirectory(prefix="ocrmypdf-upload")
downloaddir = TemporaryDirectory(prefix="ocrmypdf-download")
filename = secure_filename(file.filename)
up_file = os.path.join(uploaddir.name, filename)
file.save(up_file)
down_file = os.path.join(downloaddir.name, filename)
cmd_args = [arg for arg in shlex.split(request.form["params"])]
if "--sidecar" in cmd_args:
return Response("--sidecar not supported", 501, mimetype='text/plain')
ocrmypdf_args = ["ocrmypdf", *cmd_args, up_file, down_file]
proc = run(ocrmypdf_args, stdout=PIPE, stderr=PIPE, encoding="utf-8")
if proc.returncode != 0:
stderr = proc.stderr
return Response(stderr, 400, mimetype='text/plain')
return send_from_directory(downloaddir.name, filename)
@app.route("/", methods=["GET", "POST"])
def upload_file():
if request.method == "POST":
if "file" not in request.files:
return Response("No file in POST", 400, mimetype='text/plain')
file = request.files["file"]
if file.filename == "":
return Response("Empty filename", 400, mimetype='text/plain')
if not allowed_file(file.filename):
return Response("Invalid filename", 400, mimetype='text/plain')
if file and allowed_file(file.filename):
return do_ocrmypdf(file)
return Response("Some other problem", 400, mimetype='text/plain')
return """
<!doctype html>
<title>OCRmyPDF webservice</title>
<h1>Upload a PDF (debug UI)</h1>
<form method=post enctype=multipart/form-data>
<label for="args">Command line parameters</label>
<input type=textbox name=params>
<label for="file">File to upload</label>
<input type=file name=file>
<input type=submit value=Upload>
</form>
<h4>Notice</h2>
<div style="font-size: 70%; max-width: 34em;">
<p>This is a webservice wrapper for OCRmyPDF.</p>
<p>Copyright 2019 James R. Barlow</p>
<p>This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
</p>
<p>This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
</p>
<p>
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see &lt;http://www.gnu.org/licenses/&gt;.
</p>
</div>
"""
if __name__ == "__main__":
app.run(host='0.0.0.0', port=5000)
-12
View File
@@ -1,12 +0,0 @@
from enum import IntEnum
class ExitCode(IntEnum):
ok = 0
bad_args = 1
input_file = 2
missing_dependency = 3
invalid_output_pdfa = 4
file_access_error = 5
already_done_ocr = 6
other_error = 15
-54
View File
@@ -1,54 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from tempfile import NamedTemporaryFile
from subprocess import Popen, PIPE, check_call
from shutil import copy
def rasterize_pdf(input_file, output_file, xres, yres, raster_device, log):
with NamedTemporaryFile(delete=True) as tmp:
args_gs = [
'gs',
'-dQUIET',
'-dBATCH',
'-dNOPAUSE',
'-sDEVICE=%s' % raster_device,
'-o', tmp.name,
'-r{0}x{1}'.format(str(xres), str(yres)),
input_file
]
p = Popen(args_gs, close_fds=True, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
stdout, stderr = p.communicate()
if stdout:
log.debug(stdout)
if stderr:
log.error(stderr)
if p.returncode == 0:
copy(tmp.name, output_file)
else:
log.error('Ghostscript rendering failed')
def generate_pdfa(pdf_pages, output_file, threads=1):
with NamedTemporaryFile(delete=True) as gs_pdf:
args_gs = [
"gs",
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
'-dNumRenderingThreads=' + str(threads),
"-sDEVICE=pdfwrite",
"-sColorConversionStrategy=/RGB",
"-sProcessColorModel=DeviceRGB",
"-dPDFA",
"-sPDFACompatibilityPolicy=2",
"-sOutputICCProfile=srgb.icc",
"-sOutputFile=" + gs_pdf.name,
]
args_gs.extend(pdf_pages)
check_call(args_gs)
copy(gs_pdf.name, output_file)
-230
View File
@@ -1,230 +0,0 @@
#!/usr/local/bin/python3
##############################################################################
# Copyright (c) 2013-14: fritz-hh from Github
# (https://github.com/fritz-hh)
#
# Copyright (c) 2010: Jonathan Brinley from Github
# (https://github.com/jbrinley/HocrConverter)
# Initial version by Jonathan Brinley, jonathanbrinley@gmail.com
##############################################################################
from reportlab.pdfgen.canvas import Canvas
from reportlab.lib.units import inch
from xml.etree import ElementTree
from PIL import Image
from collections import namedtuple
import re
import argparse
Rect = namedtuple('Rect', ['x1', 'y1', 'x2', 'y2'])
class HocrTransformError(Exception):
pass
class HocrTransform():
"""
A class for converting documents from the hOCR format.
For details of the hOCR format, see:
http://docs.google.com/View?docid=dfxcv4vc_67g844kf
"""
def __init__(self, hocrFileName, dpi):
self.dpi = dpi
self.boxPattern = re.compile(r'bbox((\s+\d+){4})')
self.hocr = ElementTree.parse(hocrFileName)
# if the hOCR file has a namespace, ElementTree requires its use to
# find elements
matches = re.match(r'({.*})html', self.hocr.getroot().tag)
self.xmlns = ''
if matches:
self.xmlns = matches.group(1)
# get dimension in pt (not pixel!!!!) of the OCRed image
self.width, self.height = None, None
for div in self.hocr.findall(
".//%sdiv[@class='ocr_page']" % (self.xmlns)):
coords = self.element_coordinates(div)
pt_coords = self.pt_from_pixel(coords)
self.width = pt_coords.x2 - pt_coords.x1
self.height = pt_coords.y2 - pt_coords.y1
# there shouldn't be more than one, and if there is, we don't want
# it
break
if self.width is None or self.height is None:
raise HocrTransformError("hocr file is missing page dimensions")
def __str__(self):
"""
Return the textual content of the HTML body
"""
if self.hocr is None:
return ''
body = self.hocr.find(".//%sbody" % (self.xmlns))
if body:
return self._get_element_text(body)
else:
return ''
def _get_element_text(self, element):
"""
Return the textual content of the element and its children
"""
text = ''
if element.text is not None:
text += element.text
for child in element.getchildren():
text += self._get_element_text(child)
if element.tail is not None:
text += element.tail
return text
def element_coordinates(self, element):
"""
Returns a tuple containing the coordinates of the bounding box around
an element
"""
out = (0, 0, 0, 0)
if 'title' in element.attrib:
matches = self.boxPattern.search(element.attrib['title'])
if matches:
coords = matches.group(1).split()
out = Rect._make(int(coords[n]) for n in range(4))
return out
def pt_from_pixel(self, pxl):
"""
Returns the quantity in PDF units (pt) given quantity in pixels
"""
return Rect._make(
(c / self.dpi * inch) for c in pxl)
def replace_unsupported_chars(self, s):
"""
Given an input string, returns the corresponding string that:
- is available in the helvetica facetype
- does not contain any ligature (to allow easy search in the PDF file)
"""
# The 'u' before the character to replace indicates that it is a
# unicode character
s = s.replace(u"", "fl")
s = s.replace(u"", "fi")
return s
def to_pdf(self, outFileName, imageFileName=None, showBoundingboxes=False,
fontname="Helvetica", invisibleText=False):
"""
Creates a PDF file with an image superimposed on top of the text.
Text is positioned according to the bounding box of the lines in
the hOCR file.
The image need not be identical to the image used to create the hOCR
file.
It can have a lower resolution, different color mode, etc.
"""
# create the PDF file
# page size in points (1/72 in.)
pdf = Canvas(
outFileName, pagesize=(self.width, self.height), pageCompression=1)
# draw bounding box for each paragraph
# light blue for bounding box of paragraph
pdf.setStrokeColorRGB(0, 1, 1)
# light blue for bounding box of paragraph
pdf.setFillColorRGB(0, 1, 1)
pdf.setLineWidth(0) # no line for bounding box
for elem in self.hocr.findall(
".//%sp[@class='%s']" % (self.xmlns, "ocr_par")):
elemtxt = self._get_element_text(elem).rstrip()
if len(elemtxt) == 0:
continue
pxl_coords = self.element_coordinates(elem)
pt = self.pt_from_pixel(pxl_coords)
# draw the bbox border
if showBoundingboxes:
pdf.rect(
pt.x1, self.height - pt.y2, pt.x2 - pt.x1, pt.y2 - pt.y1,
fill=1)
# check if element with class 'ocrx_word' are available
# otherwise use 'ocr_line' as fallback
elemclass = "ocr_line"
if self.hocr.find(
".//%sspan[@class='ocrx_word']" % (self.xmlns)) is not None:
elemclass = "ocrx_word"
# itterate all text elements
# light green for bounding box of word/line
pdf.setStrokeColorRGB(1, 0, 0)
pdf.setLineWidth(0.5) # bounding box line width
pdf.setDash(6, 3) # bounding box is dashed
pdf.setFillColorRGB(0, 0, 0) # text in black
for elem in self.hocr.findall(
".//%sspan[@class='%s']" % (self.xmlns, elemclass)):
elemtxt = self._get_element_text(elem).rstrip()
elemtxt = self.replace_unsupported_chars(elemtxt)
if len(elemtxt) == 0:
continue
pxl_coords = self.element_coordinates(elem)
pt = self.pt_from_pixel(pxl_coords)
# draw the bbox border
if showBoundingboxes:
pdf.rect(
pt.x1, self.height - pt.y2, pt.x2 - pt.x1, pt.y2 - pt.y1,
fill=0)
text = pdf.beginText()
fontsize = pt.y2 - pt.y1
text.setFont(fontname, fontsize)
if invisibleText:
text.setTextRenderMode(3) # Invisible (indicates OCR text)
# set cursor to bottom left corner of bbox (adjust for dpi)
text.setTextOrigin(pt.x1, self.height - pt.y2)
# scale the width of the text to fill the width of the bbox
text.setHorizScale(
100 * (pt.x2 - pt.x1) / pdf.stringWidth(
elemtxt, fontname, fontsize))
# write the text to the page
text.textLine(elemtxt)
pdf.drawText(text)
# put the image on the page, scaled to fill the page
if imageFileName is not None:
pdf.drawImage(imageFileName, 0, 0,
width=self.width, height=self.height)
# finish up the page and save it
pdf.showPage()
pdf.save()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Convert hocr file to PDF')
parser.add_argument('-b', '--boundingboxes', action="store_true",
default=False, help='Show bounding boxes borders')
parser.add_argument('-r', '--resolution', type=int,
default=300,
help='Resolution of the image that was OCRed')
parser.add_argument('-i', '--image', default=None,
help='Path to the image to be placed above the text')
parser.add_argument('hocrfile', help='Path to the hocr file to be parsed')
parser.add_argument(
'outputfile', help='Path to the PDF file to be generated')
args = parser.parse_args()
hocr = HocrTransform(args.hocrfile, args.resolution)
hocr.to_pdf(args.outputfile, args.image, args.boundingboxes)
-331
View File
@@ -1,331 +0,0 @@
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# © 2013-15: jbarlow83 from Github (https://github.com/jbarlow83)
#
#
# Use Leptonica to detect find and remove page skew. Leptonica uses the method
# of differential square sums, which its author claim is faster and more robust
# than the Hough transform used by ImageMagick.
from __future__ import print_function, absolute_import, division
import argparse
import ctypes as C
import sys
import os
import logging
from tempfile import TemporaryFile
logger = logging.getLogger(__name__)
def stderr(*objs):
"""Python 2/3 compatible print to stderr.
"""
print("leptonica.py:", *objs, file=sys.stderr)
from ctypes.util import find_library
lept_lib = find_library('lept')
if not lept_lib:
stderr("Could not find the Leptonica library")
sys.exit(3)
try:
lept = C.cdll.LoadLibrary(lept_lib)
except Exception:
stderr("Could not load the Leptonica library from %s", lept_lib)
sys.exit(3)
class _PIXCOLORMAP(C.Structure):
"""struct PixColormap from Leptonica src/pix.h
"""
_fields_ = [
("array", C.c_void_p),
("depth", C.c_int32),
("nalloc", C.c_int32),
("n", C.c_int32)
]
class _PIX(C.Structure):
"""struct Pix from Leptonica src/pix.h
"""
_fields_ = [
("w", C.c_uint32),
("h", C.c_uint32),
("d", C.c_uint32),
("wpl", C.c_uint32),
("refcount", C.c_uint32),
("xres", C.c_int32),
("yres", C.c_int32),
("informat", C.c_int32),
("text", C.POINTER(C.c_char)),
("colormap", C.POINTER(_PIXCOLORMAP)),
("data", C.POINTER(C.c_uint32))
]
PIX = C.POINTER(_PIX)
lept.pixRead.argtypes = [C.c_char_p]
lept.pixRead.restype = PIX
lept.pixScale.argtypes = [PIX, C.c_float, C.c_float]
lept.pixScale.restype = PIX
lept.pixDeskew.argtypes = [PIX, C.c_int32]
lept.pixDeskew.restype = PIX
lept.pixFindSkew.argtypes = [PIX, C.POINTER(C.c_float), C.POINTER(C.c_float)]
lept.pixFindSkew.restype = C.c_int32
lept.pixWriteImpliedFormat.argtypes = [C.c_char_p, PIX, C.c_int32, C.c_int32]
lept.pixWriteImpliedFormat.restype = C.c_int32
lept.pixDestroy.argtypes = [C.POINTER(PIX)]
lept.pixDestroy.restype = None
lept.getLeptonicaVersion.argtypes = []
lept.getLeptonicaVersion.restype = C.c_char_p
class LeptonicaErrorTrap(object):
"""Context manager to trap errors reported by Leptonica.
Leptonica's error return codes are unreliable to the point of being
almost useless. It does, however, write errors to stderr provided that is
not disabled at its compile time. Fortunately this is done using error
macros so it is very self-consistent.
This context manager redirects stderr to a temporary file which is then
read and parsed for error messages. As a side benefit, debug messages
from Leptonica are also suppressed.
"""
def __enter__(self):
self.tmpfile = TemporaryFile()
# Save the old stderr, and redirect stderr to temporary file
self.old_stderr_fileno = os.dup(sys.stderr.fileno())
os.dup2(self.tmpfile.fileno(), sys.stderr.fileno())
return
def __exit__(self, exc_type, exc_value, traceback):
# Restore old stderr
os.dup2(self.old_stderr_fileno, sys.stderr.fileno())
# Get data from tmpfile (in with block to ensure it is closed)
with self.tmpfile as tmpfile:
tmpfile.seek(0) # Cursor will be at end, so move back to beginning
leptonica_output = tmpfile.read().decode(errors='replace')
# If there are Python errors, let them bubble up
if exc_type:
logger.warning(leptonica_output)
return False
# If there are Leptonica errors, wrap them in Python excpetions
if 'Error' in leptonica_output:
if 'image file not found' in leptonica_output:
raise FileNotFoundError()
if 'pixWrite: stream not opened' in leptonica_output:
raise LeptonicaIOError()
raise LeptonicaError(leptonica_output)
return False
class LeptonicaError(Exception):
pass
class LeptonicaIOError(LeptonicaError):
pass
def pixRead(filename):
"""Load an image file into a PIX object.
Leptonica can load TIFF, PNM (PBM, PGM, PPM), PNG, and JPEG. If loading
fails then the object will wrap a C null pointer.
"""
with LeptonicaErrorTrap():
return lept.pixRead(filename.encode(sys.getfilesystemencoding()))
def pixScale(pix, scalex, scaley):
"""Returns the pix object rescaled according to the proportions given."""
with LeptonicaErrorTrap():
return lept.pixScale(pix, scalex, scaley)
def pixDeskew(pix, reduction_factor=0):
"""Returns the deskewed pix object.
A clone of the original is returned when the algorithm cannot find a skew
angle with sufficient confidence.
reduction_factor -- amount to downsample (0 for default) when searching
for skew angle
"""
with LeptonicaErrorTrap():
return lept.pixDeskew(pix, reduction_factor)
def pixFindSkew(pix):
"""Returns a tuple (deskew angle in degrees, confidence value).
Returns (None, None) if no angle is available.
"""
with LeptonicaErrorTrap():
angle = C.c_float(0.0)
confidence = C.c_float(0.0)
result = lept.pixFindSkew(pix, C.byref(angle), C.byref(confidence))
if result == 0:
return (angle.value, confidence.value)
else:
return (None, None)
def pixWriteImpliedFormat(filename, pix, jpeg_quality=0, jpeg_progressive=0):
"""Write pix to the filename, with the extension indicating format.
jpeg_quality -- quality (iff JPEG; 1 - 100, 0 for default)
jpeg_progressive -- (iff JPEG; 0 for baseline seq., 1 for progressive)
"""
fileroot, extension = os.path.splitext(filename)
fix_pnm = False
if extension.lower() in ('.pbm', '.pgm', '.ppm'):
# Leptonica does not process handle these extensions correctly, but
# does handle .pnm correctly. Add another .pnm suffix.
filename += '.pnm'
fix_pnm = True
with LeptonicaErrorTrap():
lept.pixWriteImpliedFormat(
filename.encode(sys.getfilesystemencoding()),
pix, jpeg_quality, jpeg_progressive)
if fix_pnm:
from shutil import move
move(filename, filename[:-4]) # Remove .pnm suffix
def pixDestroy(pix):
"""Destroy the pix object.
Function signature is pixDestroy(struct Pix **), hence C.byref() to pass
the address of the pointer.
"""
with LeptonicaErrorTrap():
lept.pixDestroy(C.byref(pix))
def getLeptonicaVersion():
"""Get Leptonica version string.
Caveat: Leptonica expects the caller to free this memory. We don't,
since that would involve binding to libc to access libc.free(),
a pointless effort to reclaim 100 bytes of memory.
"""
return lept.getLeptonicaVersion().decode()
def deskew(infile, outfile, dpi):
try:
pix_source = pixRead(infile)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open file: %s" % infile)
if dpi < 150:
reduction_factor = 1 # Don't downsample too much if DPI is already low
else:
reduction_factor = 0 # Use default
pix_deskewed = pixDeskew(pix_source, reduction_factor)
try:
pixWriteImpliedFormat(outfile, pix_deskewed)
except LeptonicaIOError:
raise LeptonicaIOError("Failed to open destination file: %s" % outfile)
pixDestroy(pix_source)
pixDestroy(pix_deskewed)
if __name__ == '__main__':
parser = argparse.ArgumentParser(
description="Python wrapper to access Leptonica")
subparsers = parser.add_subparsers(title='commands',
description='supported operations')
parser_deskew = subparsers.add_parser('deskew')
parser_deskew.add_argument('-r', '--dpi', dest='dpi', action='store',
type=int, default=300, help='input resolution')
parser_deskew.add_argument('infile', help='image to deskew')
parser_deskew.add_argument('outfile', help='deskewed output image')
parser_deskew.set_defaults(func=deskew)
args = parser.parse_args()
if getLeptonicaVersion() != u'leptonica-1.69':
print("Unexpected leptonica version: %s" % getLeptonicaVersion())
args.func(args)
def _test_output(mode, extension, im_format):
from PIL import Image
from tempfile import NamedTemporaryFile
with NamedTemporaryFile(prefix='test-lept-pnm', suffix=extension, delete=True) as tmpfile:
im = Image.new(mode=mode, size=(100, 100))
im.save(tmpfile)
pix = pixRead(tmpfile.name)
pixWriteImpliedFormat(tmpfile.name, pix)
pixDestroy(pix)
im_roundtrip = Image.open(tmpfile.name)
assert im_roundtrip.mode == im.mode, "leptonica mode differs"
assert im_roundtrip.format == im_format, \
"{0}: leptonica produced a {1}".format(
extension,
im_roundtrip.format)
def test_pnm_output():
params = [['1', '.pbm', 'PPM'], ['L', '.pgm', 'PPM'],
['RGB', '.ppm', 'PPM']]
for param in params:
_test_output(*param)
def test_skew_angle():
from PIL import Image, ImageDraw
from tempfile import NamedTemporaryFile
im = Image.new(mode='1', size=(1000, 1000), color=1)
draw = ImageDraw.Draw(im)
for n in range(20):
draw.line([(50, 25 + 50*n), (950, 25 + 50*n)], width=1)
del draw
test_angles = [0.1 * ang for ang in range(1, 10)] + \
[float(ang) for ang in range(1, 7)]
test_angles += [-ang for ang in test_angles]
test_angles = sorted(test_angles)
for rotate_angle in test_angles:
rotated_im = im.rotate(rotate_angle)
with NamedTemporaryFile(prefix='lept-skew', suffix='.png', delete=True) as tmpfile:
rotated_im.save(tmpfile)
pix = pixRead(tmpfile.name)
angle, confidence = pixFindSkew(pix)
pixDestroy(pix)
print('{0} {1} {2}'.format(rotate_angle, angle, confidence), file=sys.stderr)
-912
View File
@@ -1,912 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from contextlib import suppress
from tempfile import NamedTemporaryFile, mkdtemp
import sys
import os
import re
import shutil
import warnings
import multiprocessing
import atexit
import textwrap
import PyPDF2 as pypdf
from PIL import Image
from subprocess import Popen, check_call, PIPE, CalledProcessError, \
TimeoutExpired, check_output, STDOUT
try:
from subprocess import DEVNULL
except ImportError:
DEVNULL = open(os.devnull, 'wb')
from ruffus import transform, suffix, merge, active_if, regex, jobs_limit, \
formatter, follows, split, collate, check_if_uptodate
import ruffus.ruffus_exceptions as ruffus_exceptions
import ruffus.cmdline as cmdline
from .hocrtransform import HocrTransform
from .pageinfo import pdf_get_all_pageinfo
from .pdfa import generate_pdfa_def
from . import ghostscript
from . import tesseract
from . import ExitCode
warnings.simplefilter('ignore', pypdf.utils.PdfReadWarning)
BASEDIR = os.path.dirname(os.path.realpath(__file__))
JHOVE_PATH = os.path.realpath(os.path.join(BASEDIR, 'jhove'))
JHOVE_JAR = os.path.join(JHOVE_PATH, 'bin', 'JhoveApp.jar')
JHOVE_CFG = os.path.join(JHOVE_PATH, 'conf', 'jhove.conf')
# -------------
# External dependencies
MINIMUM_TESS_VERSION = '3.02.02'
def complain(message):
print(*textwrap.wrap(message), file=sys.stderr)
if tesseract.version() < MINIMUM_TESS_VERSION:
complain(
"Please install tesseract {0} or newer "
"(currently installed version is {1})".format(
MINIMUM_TESS_VERSION, tesseract.version()))
sys.exit(ExitCode.missing_dependency)
try:
import PIL.features
check_codec = PIL.features.check_codec
except (ImportError, AttributeError):
def check_codec(codec_name):
if codec_name == 'jpg':
return 'jpeg_encoder' in dir(Image.core)
elif codec_name == 'zlib':
return 'zip_encoder' in dir(Image.core)
raise NotImplementedError(codec_name)
def check_pil_encoder(codec_name, friendly_name):
try:
if check_codec(codec_name):
return
except Exception:
pass
complain(
"ERROR: Your version of the Python imaging library (Pillow) was "
"compiled without support for " + friendly_name + " encoding/decoding."
"\n"
"You will need to uninstall Pillow and reinstall it with PNG and JPEG "
"support (libjpeg and zlib)."
"\n"
"See installation instructions for your platform here:\n"
" https://pillow.readthedocs.org/installation.html"
)
sys.exit(ExitCode.missing_dependency)
check_pil_encoder('jpg', 'JPEG')
check_pil_encoder('zlib', 'PNG')
# -------------
# Parser
parser = cmdline.get_argparse(
prog="ocrmypdf",
description="Generate searchable PDF file from an image-only PDF file.",
version='3.0',
fromfile_prefix_chars='@',
ignored_args=[
'touch_files_only', 'recreate_database', 'checksum_file_name',
'key_legend_in_graph', 'draw_graph_horizontally', 'flowchart_format',
'forced_tasks', 'target_tasks'])
parser.add_argument(
'input_file',
help="PDF file containing the images to be OCRed")
parser.add_argument(
'output_file',
help="output searchable PDF file")
parser.add_argument(
'-l', '--language', action='append',
help="languages of the file to be OCRed")
metadata = parser.add_argument_group(
"Metadata options",
"Set output PDF/A metadata (default: use input document's title)")
metadata.add_argument(
'--title', type=str,
help="set document title (place multiple words in quotes)")
metadata.add_argument(
'--author', type=str,
help="set document author")
metadata.add_argument(
'--subject', type=str,
help="set document")
metadata.add_argument(
'--keywords', type=str,
help="set document keywords")
preprocessing = parser.add_argument_group(
"Preprocessing options",
"Improve OCR quality and final image")
preprocessing.add_argument(
'-d', '--deskew', action='store_true',
help="deskew each page before performing OCR")
preprocessing.add_argument(
'-c', '--clean', action='store_true',
help="clean pages from scanning artifacts before performing OCR")
preprocessing.add_argument(
'-i', '--clean-final', action='store_true',
help="incorporate the cleaned image in the final PDF file")
preprocessing.add_argument(
'--oversample', metavar='DPI', type=int, default=0,
help="oversample images to at least the specified DPI, to improve OCR "
"results slightly")
parser.add_argument(
'-f', '--force-ocr', action='store_true',
help="rasterize any fonts or vector images on each page and apply OCR")
parser.add_argument(
'-s', '--skip-text', action='store_true',
help="skip OCR on any pages that already contain text, but include the"
" page in final output")
parser.add_argument(
'--skip-big', type=float, metavar='MPixels',
help="skip OCR on pages larger than the specified amount of megapixels, "
"but include skipped pages in final output")
# parser.add_argument(
# '--exact-image', action='store_true',
# help="Use original page from PDF without re-rendering")
advanced = parser.add_argument_group(
"Advanced",
"Advanced options for power users")
advanced.add_argument(
'--tesseract-config', default=[], type=list, action='append',
help="additional Tesseract configuration files")
advanced.add_argument(
'--pdf-renderer', choices=['tesseract', 'hocr'], default='hocr',
help='choose OCR PDF renderer')
advanced.add_argument(
'--tesseract-timeout', default=180.0, type=float,
help='give up on OCR after the timeout, but copy the preprocessed page '
'into the final output')
debugging = parser.add_argument_group(
"Debugging",
"Arguments to help with troubleshooting and debugging")
debugging.add_argument(
'-k', '--keep-temporary-files', action='store_true',
help="keep temporary files (helpful for debugging)")
debugging.add_argument(
'-g', '--debug-rendering', action='store_true',
help="render each page twice with debug information on second page")
options = parser.parse_args()
# ----------
# Languages
if not options.language:
options.language = ['eng'] # Enforce English hegemony
# Support v2.x "eng+deu" language syntax
if '+' in options.language[0]:
options.language = options.language[0].split('+')
if not set(options.language).issubset(tesseract.languages()):
complain(
"The installed version of tesseract does not have language "
"data for the following requested languages: ")
for lang in (set(options.language) - tesseract.languages()):
complain(lang)
sys.exit(ExitCode.bad_args)
# ----------
# Arguments
if any((options.deskew, options.clean, options.clean_final)):
try:
from . import unpaper
except ImportError:
complain(
"Install the 'unpaper' program to use --deskew or --clean.")
sys.exit(ExitCode.bad_args)
else:
unpaper = None
if options.debug_rendering and options.pdf_renderer == 'tesseract':
complain(
"Ignoring --debug-rendering because it is not supported with"
"--pdf-renderer=tesseract.")
if options.force_ocr and options.skip_text:
complain(
"Error: --force-ocr and --skip-text are mutually incompatible.")
sys.exit(ExitCode.bad_args)
if options.clean and not options.clean_final \
and options.pdf_renderer == 'tesseract':
complain(
"Tesseract PDF renderer cannot render --clean pages without "
"also performing --clean-final, so --clean-final is assumed.")
# ----------
# Logging
_logger, _logger_mutex = cmdline.setup_logging(__name__, options.log_file,
options.verbose)
class WrappedLogger:
def __init__(self, my_logger, my_mutex):
self.logger = my_logger
self.mutex = my_mutex
def log(self, *args, **kwargs):
with self.mutex:
self.logger.log(*args, **kwargs)
def debug(self, *args, **kwargs):
with self.mutex:
self.logger.debug(*args, **kwargs)
def info(self, *args, **kwargs):
with self.mutex:
self.logger.info(*args, **kwargs)
def warning(self, *args, **kwargs):
with self.mutex:
self.logger.warning(*args, **kwargs)
def error(self, *args, **kwargs):
with self.mutex:
self.logger.error(*args, **kwargs)
def critical(self, *args, **kwargs):
with self.mutex:
self.logger.critical(*args, **kwargs)
_log = WrappedLogger(_logger, _logger_mutex)
def re_symlink(input_file, soft_link_name, log=_log):
"""
Helper function: relinks soft symbolic link if necessary
"""
# Guard against soft linking to oneself
if input_file == soft_link_name:
log.debug("Warning: No symbolic link made. You are using " +
"the original data directory as the working directory.")
return
# Soft link already exists: delete for relink?
if os.path.lexists(soft_link_name):
# do not delete or overwrite real (non-soft link) file
if not os.path.islink(soft_link_name):
raise Exception("%s exists and is not a link" % soft_link_name)
try:
os.unlink(soft_link_name)
except:
log.debug("Can't unlink %s" % (soft_link_name))
if not os.path.exists(input_file):
raise Exception("trying to create a broken symlink to %s" % input_file)
log.debug("os.symlink(%s, %s)" % (input_file, soft_link_name))
# Create symbolic link using absolute path
os.symlink(
os.path.abspath(input_file),
soft_link_name
)
# -------------
# The Pipeline
manager = multiprocessing.Manager()
_pdfinfo = manager.list()
_pdfinfo_lock = manager.Lock()
work_folder = mkdtemp(prefix="com.github.ocrmypdf.")
@atexit.register
def cleanup_working_files(*args):
if options.keep_temporary_files:
print("Temporary working files saved at:")
print(work_folder)
else:
with suppress(FileNotFoundError):
shutil.rmtree(work_folder)
@transform(
input=options.input_file,
filter=suffix('.pdf'),
output='.repaired.pdf',
output_dir=work_folder,
extras=[_log, _pdfinfo, _pdfinfo_lock])
def repair_pdf(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
args_qpdf = [
'qpdf', input_file, output_file
]
try:
out = check_output(args_qpdf, stderr=STDOUT, universal_newlines=True)
except CalledProcessError as e:
if e.returncode == 2:
print("{0}: not a valid PDF, and could not repair it.".format(
options.input_file))
print("Details:")
print(e.output)
else:
print(e.output)
sys.exit(ExitCode.input_file)
log.debug(out)
with pdfinfo_lock:
pdfinfo.extend(pdf_get_all_pageinfo(output_file))
log.info(pdfinfo)
def get_pageinfo(input_file, pdfinfo, pdfinfo_lock):
pageno = int(os.path.basename(input_file)[0:6]) - 1
with pdfinfo_lock:
pageinfo = pdfinfo[pageno].copy()
return pageinfo
def is_ocr_required(pageinfo, log):
page = pageinfo['pageno'] + 1
ocr_required = True
if not pageinfo['images']:
# If the page has no images, then it contains vector content or text
# or both. It seems quite unlikely that one would find meaningful text
# from rasterizing vector content. So skip the page.
log.info(
"Page {0} has no images - skipping OCR".format(page)
)
ocr_required = False
elif pageinfo['has_text']:
s = "Page {0} already has text! {1}"
if not options.force_ocr and not options.skip_text:
log.error(s.format(page,
"aborting (use --force-ocr to force OCR)"))
sys.exit(ExitCode.already_done_ocr)
elif options.force_ocr:
log.info(s.format(page,
"rasterizing text and running OCR anyway"))
ocr_required = True
elif options.skip_text:
log.info(s.format(page,
"skipping all processing on this page"))
ocr_required = False
if ocr_required and options.skip_big:
pixel_count = pageinfo['width_pixels'] * pageinfo['height_pixels']
if pixel_count > (options.skip_big * 1000000):
ocr_required = False
log.info(
"Page {0} is very large; skipping due to -b".format(page))
return ocr_required
@split(
repair_pdf,
os.path.join(work_folder, '*.page.pdf'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def split_pages(
input_file,
output_files,
log,
pdfinfo,
pdfinfo_lock):
for oo in output_files:
with suppress(FileNotFoundError):
os.unlink(oo)
pages = check_output(['qpdf', '--show-npages', input_file],
universal_newlines=True, close_fds=True)
for n in range(int(pages)):
args_qpdf = [
'qpdf', input_file,
'--pages', input_file, '{0}'.format(n + 1), '--',
os.path.join(work_folder, '{0:06d}.page.pdf'.format(n + 1))
]
check_call(args_qpdf)
from glob import glob
for filename in glob(os.path.join(work_folder, '*.page.pdf')):
pageinfo = get_pageinfo(filename, pdfinfo, pdfinfo_lock)
alt_suffix = '.ocr.page.pdf' if is_ocr_required(pageinfo, log) \
else '.skip.page.pdf'
re_symlink(
filename,
os.path.join(
work_folder,
os.path.basename(filename)[0:6] + alt_suffix))
@transform(
input=split_pages,
filter=suffix('.ocr.page.pdf'),
output='.page.png',
output_dir=work_folder,
extras=[_log, _pdfinfo, _pdfinfo_lock])
def rasterize_with_ghostscript(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
pageinfo = get_pageinfo(input_file, pdfinfo, pdfinfo_lock)
device = 'png16m' # 24-bit
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'] == 'index'
for image in pageinfo['images']):
device = 'png256'
elif all(image['bpc'] > 1 and image['color'] == 'gray'
for image in pageinfo['images']):
device = 'pnggray'
log.debug("Rendering {0} with {1}".format(
os.path.basename(input_file), device))
xres = max(pageinfo['xres'], options.oversample or 0)
yres = max(pageinfo['yres'], options.oversample or 0)
ghostscript.rasterize_pdf(input_file, output_file, xres, yres, device, log)
@transform(
input=rasterize_with_ghostscript,
filter=suffix(".page.png"),
output=".pp-deskew.png",
extras=[_log, _pdfinfo, _pdfinfo_lock])
def preprocess_deskew(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
if not options.deskew:
re_symlink(input_file, output_file, log)
return
pageinfo = get_pageinfo(input_file, pdfinfo, pdfinfo_lock)
dpi = int(pageinfo['xres'])
unpaper.deskew(input_file, output_file, dpi, log)
@transform(
input=preprocess_deskew,
filter=suffix(".pp-deskew.png"),
output=".pp-clean.png",
extras=[_log, _pdfinfo, _pdfinfo_lock])
def preprocess_clean(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
if not options.clean:
re_symlink(input_file, output_file, log)
return
pageinfo = get_pageinfo(input_file, pdfinfo, pdfinfo_lock)
dpi = int(pageinfo['xres'])
unpaper.clean(input_file, output_file, dpi, log)
@active_if(options.pdf_renderer == 'hocr')
@transform(
input=preprocess_clean,
filter=suffix(".pp-clean.png"),
output=".hocr",
extras=[_log, _pdfinfo, _pdfinfo_lock])
def ocr_tesseract_hocr(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
pageinfo = get_pageinfo(input_file, pdfinfo, pdfinfo_lock)
badxml = os.path.splitext(output_file)[0] + '.badxml'
args_tesseract = [
'tesseract',
'-l', '+'.join(options.language),
input_file,
badxml,
'hocr'
] + options.tesseract_config
p = Popen(args_tesseract, close_fds=True, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
try:
stdout, stderr = p.communicate(timeout=options.tesseract_timeout)
except TimeoutExpired:
p.kill()
stdout, stderr = p.communicate()
# Generate a HOCR file with no recognized text if tesseract times out
# Temporary workaround to hocrTransform not being able to function if
# it does not have a valid hOCR file.
with open(output_file, 'w', encoding="utf-8") as f:
f.write(tesseract.HOCR_TEMPLATE.format(
pageinfo['width_pixels'],
pageinfo['height_pixels']))
else:
if stdout:
log.info(stdout)
if stderr:
log.error(stderr)
if p.returncode != 0:
raise CalledProcessError(p.returncode, args_tesseract)
if os.path.exists(badxml + '.html'):
# Tesseract 3.02 appends suffix ".html" on its own (.badxml.html)
shutil.move(badxml + '.html', badxml)
elif os.path.exists(badxml + '.hocr'):
# Tesseract 3.03 appends suffix ".hocr" on its own (.badxml.hocr)
shutil.move(badxml + '.hocr', badxml)
# Tesseract 3.03 inserts source filename into hocr file without
# escaping it, creating invalid XML and breaking the parser.
# As a workaround, rewrite the hocr file, replacing the filename
# with a space. Don't know if Tesseract 3.02 does the same.
regex_nested_single_quotes = re.compile(
r"""title='image "([^"]*)";""")
with open(badxml, mode='r', encoding='utf-8') as f_in, \
open(output_file, mode='w', encoding='utf-8') as f_out:
for line in f_in:
line = regex_nested_single_quotes.sub(
r"""title='image " ";""", line)
f_out.write(line)
@active_if(options.pdf_renderer == 'hocr')
@collate(
input=[rasterize_with_ghostscript, preprocess_deskew, preprocess_clean],
filter=regex(r".*/(\d{6})(?:\.page|\.pp-deskew|\.pp-clean)\.png"),
output=os.path.join(work_folder, r'\1.image'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def select_image_for_pdf(
infiles,
output_file,
log,
pdfinfo,
pdfinfo_lock):
if options.clean_final:
image_suffix = '.pp-clean.png'
elif options.deskew:
image_suffix = '.pp-deskew.png'
else:
image_suffix = '.page.png'
image = next(ii for ii in infiles if ii.endswith(image_suffix))
pageinfo = get_pageinfo(image, pdfinfo, pdfinfo_lock)
if all(image['enc'] == 'jpeg' for image in pageinfo['images']):
# If all images were JPEGs originally, produce a JPEG as output
Image.open(image).save(output_file, format='JPEG')
else:
re_symlink(image, output_file)
@active_if(options.pdf_renderer == 'hocr')
@collate(
input=[select_image_for_pdf, ocr_tesseract_hocr],
filter=regex(r".*/(\d{6})(?:\.image|\.hocr)"),
output=os.path.join(work_folder, r'\1.rendered.pdf'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def render_hocr_page(
infiles,
output_file,
log,
pdfinfo,
pdfinfo_lock):
hocr = next(ii for ii in infiles if ii.endswith('.hocr'))
image = next(ii for ii in infiles if ii.endswith('.image'))
pageinfo = get_pageinfo(image, pdfinfo, pdfinfo_lock)
dpi = round(max(pageinfo['xres'], pageinfo['yres'], options.oversample))
hocrtransform = HocrTransform(hocr, dpi)
hocrtransform.to_pdf(output_file, imageFileName=image,
showBoundingboxes=False, invisibleText=True)
@active_if(options.pdf_renderer == 'hocr')
@active_if(options.debug_rendering)
@collate(
input=[select_image_for_pdf, ocr_tesseract_hocr],
filter=regex(r".*/(\d{6})(?:\.image|\.hocr)"),
output=os.path.join(work_folder, r'\1.debug.pdf'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def render_hocr_debug_page(
infiles,
output_file,
log,
pdfinfo,
pdfinfo_lock):
hocr = next(ii for ii in infiles if ii.endswith('.hocr'))
image = next(ii for ii in infiles if ii.endswith('.image'))
pageinfo = get_pageinfo(image, pdfinfo, pdfinfo_lock)
dpi = round(max(pageinfo['xres'], pageinfo['yres'], options.oversample))
hocrtransform = HocrTransform(hocr, dpi)
hocrtransform.to_pdf(output_file, imageFileName=None,
showBoundingboxes=True, invisibleText=False)
@active_if(options.pdf_renderer == 'tesseract')
@collate(
input=[preprocess_clean, split_pages],
filter=regex(r".*/(\d{6})(?:\.pp-clean\.png|\.page\.pdf)"),
output=os.path.join(work_folder, r'\1.rendered.pdf'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def tesseract_ocr_and_render_pdf(
input_files,
output_file,
log,
pdfinfo,
pdfinfo_lock):
input_image = next((ii for ii in input_files if ii.endswith('.png')), '')
input_pdf = next((ii for ii in input_files if ii.endswith('.pdf')))
if not input_image:
# Skipping this page
re_symlink(input_pdf, output_file)
return
args_tesseract = [
'tesseract',
'-l', '+'.join(options.language),
input_image,
os.path.splitext(output_file)[0], # Tesseract appends suffix
'pdf'
] + options.tesseract_config
p = Popen(args_tesseract, close_fds=True, stdout=PIPE, stderr=PIPE,
universal_newlines=True)
try:
stdout, stderr = p.communicate(timeout=options.tesseract_timeout)
if stdout:
log.info(stdout)
if stderr:
log.error(stderr)
except TimeoutExpired:
p.kill()
log.info("Tesseract - page timed out")
re_symlink(input_pdf, output_file)
@transform(
input=repair_pdf,
filter=suffix('.repaired.pdf'),
output='.pdfa_def.ps',
output_dir=work_folder,
extras=[_log])
def generate_postscript_stub(
input_file,
output_file,
log):
pdf = pypdf.PdfFileReader(input_file)
def from_document_info(key):
# pdf.documentInfo.get() DOES NOT behave as expected for a dict-like
# object, so call with precautions. TypeError may occur if the PDF
# is missing the optional document info section.
try:
s = pdf.documentInfo[key]
return str(s)
except (KeyError, TypeError):
return ''
pdfmark = {
'title': from_document_info('/Title'),
'author': from_document_info('/Author'),
'keywords': from_document_info('/Keywords'),
'subject': from_document_info('/Subject'),
}
if options.title:
pdfmark['title'] = options.title
if options.author:
pdfmark['author'] = options.author
if options.keywords:
pdfmark['keywords'] = options.keywords
if options.subject:
pdfmark['subject'] = options.subject
generate_pdfa_def(output_file, pdfmark)
@transform(
input=split_pages,
filter=suffix('.skip.page.pdf'),
output='.done.pdf',
output_dir=work_folder,
extras=[_log])
def skip_page(
input_file,
output_file,
log):
re_symlink(input_file, output_file, log)
@merge(
input=[render_hocr_page, render_hocr_debug_page, skip_page,
tesseract_ocr_and_render_pdf, generate_postscript_stub],
output=os.path.join(work_folder, 'merged.pdf'),
extras=[_log, _pdfinfo, _pdfinfo_lock])
def merge_pages(
input_files,
output_file,
log,
pdfinfo,
pdfinfo_lock):
def input_file_order(s):
'''Sort order: All rendered pages followed
by their debug page, if any, followed by Postscript stub.
Ghostscript documentation has the Postscript stub at the
beginning, but it works at the end and also gets document info
right that way.'''
if s.endswith('.ps'):
return 99999999
key = int(os.path.basename(s)[0:6]) * 10
if 'debug' in os.path.basename(s):
key += 1
return key
pdf_pages = sorted(input_files, key=input_file_order)
log.info(pdf_pages)
ghostscript.generate_pdfa(pdf_pages, output_file, options.jobs or 1)
@transform(
input=merge_pages,
filter=formatter(),
output=options.output_file,
extras=[_log, _pdfinfo, _pdfinfo_lock])
def copy_final(
input_file,
output_file,
log,
pdfinfo,
pdfinfo_lock):
shutil.copy(input_file, output_file)
def validate_pdfa(
input_file,
log):
args_qpdf = [
'qpdf',
'--check',
input_file
]
try:
check_output(args_qpdf, stderr=STDOUT, universal_newlines=True)
except CalledProcessError as e:
if e.returncode == 2:
print("{0}: not a valid PDF, and could not repair it.".format(
options.input_file))
print("Details:")
print(e.output)
elif e.returncode == 3:
log.info("qpdf --check returned warnings:")
log.info(e.output)
else:
print(e.output)
return False
return True
# @active_if(ocr_required and options.exact_image)
# @merge([render_hocr_blank_page, extract_single_page],
# os.path.join(work_folder, "%04i.merged.pdf") % pageno)
# def merge_hocr_with_original_page(infiles, output_file):
# with open(infiles[0], 'rb') as hocr_input, \
# open(infiles[1], 'rb') as page_input, \
# open(output_file, 'wb') as output:
# hocr_reader = pypdf.PdfFileReader(hocr_input)
# page_reader = pypdf.PdfFileReader(page_input)
# writer = pypdf.PdfFileWriter()
# the_page = hocr_reader.getPage(0)
# the_page.mergePage(page_reader.getPage(0))
# writer.addPage(the_page)
# writer.write(output)
def available_cpu_count():
try:
return multiprocessing.cpu_count()
except NotImplementedError:
pass
try:
import psutil
return psutil.cpu_count()
except (ImportError, AttributeError):
pass
complain(
"Could not get CPU count. Assuming one (1) CPU."
"Use -j N to set manually.")
return 1
def run_pipeline():
if not options.jobs or options.jobs == 1:
options.jobs = available_cpu_count()
try:
cmdline.run(options)
except ruffus_exceptions.RethrownJobError as e:
if options.verbose:
print(e)
# Yuck. Hunt through the ruffus exception to find out what the
# return code is supposed to be.
for exc in e.args:
task_name, job_name, exc_name, exc_value, exc_stack = exc
if exc_name == 'builtins.SystemExit':
return eval(
exc_value,
{'ExitCode': ExitCode}, {'exc_value': exc_value})
return ExitCode.other_error
if not validate_pdfa(options.output_file, _log):
_log.warning('Output file: The generated PDF/A file is INVALID')
return ExitCode.invalid_output_pdfa
return ExitCode.ok
if __name__ == '__main__':
sys.exit(run_pipeline())
-145
View File
@@ -1,145 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from subprocess import Popen, PIPE
from decimal import Decimal, getcontext
import re
import sys
import PyPDF2 as pypdf
FRIENDLY_COLORSPACE = {
'/DeviceGray': 'gray',
'/CalGray': 'gray',
'/DeviceRGB': 'rgb',
'/CalRGB': 'rgb',
'/DeviceCMYK': 'cmyk',
'/Lab': 'lab',
'/ICCBased': 'icc',
'/Indexed': 'index',
'/Separation': 'sep',
'/DeviceN': 'devn',
'/Pattern': '-'
}
FRIENDLY_ENCODING = {
'/CCITTFaxDecode': 'ccitt',
'/DCTDecode': 'jpeg',
'/JPXDecode': 'jpx',
'/JBIG2Decode': 'jbig2',
}
FRIENDLY_COMP = {
'gray': 1,
'rgb': 3,
'cmyk': 4,
'lab': 3,
'index': 1
}
def _page_has_inline_images(page):
# PDF always uses \r\n for separator regardless of platform
# Really basic heuristic that might trigger the odd false positive
# This is only finds the first image and is not quite spec compliant
try:
contents = page.getContents()
data = contents.getData()
except AttributeError:
# If we can't access the contents or data (empty page?) then there
# are no inline images
return False
begin_image, image_data, end_image = False, False, False
for data in re.split(b'\s+', data):
if data == b'BI':
begin_image = True
elif data == b'ID':
image_data = True
elif data == b'EI':
end_image = True
if all((begin_image, image_data, end_image)):
return True
return False
def _find_page_images(page, pageinfo):
try:
page['/Resources']['/XObject']
except KeyError:
return
# Look for XObject (out of line images)
for xobj in page['/Resources']['/XObject']:
# PyPDF2 returns the keys as an iterator
pdfimage = page['/Resources']['/XObject'][xobj]
if pdfimage['/Subtype'] != '/Image':
continue
if '/ImageMask' in pdfimage:
if pdfimage['/ImageMask']:
continue
image = {}
image['width'] = pdfimage['/Width']
image['height'] = pdfimage['/Height']
image['bpc'] = pdfimage['/BitsPerComponent']
if '/Filter' in pdfimage:
filter_ = pdfimage['/Filter']
if isinstance(filter_, pypdf.generic.ArrayObject):
filter_ = filter_[0]
image['enc'] = FRIENDLY_ENCODING.get(filter_, 'image')
else:
image['enc'] = 'image'
if '/ColorSpace' in pdfimage:
cs = pdfimage['/ColorSpace']
if isinstance(cs, pypdf.generic.ArrayObject):
cs = cs[0]
image['color'] = FRIENDLY_COLORSPACE.get(cs, '-')
else:
image['color'] = 'jpx' if image['enc'] == 'jpx' else '?'
image['comp'] = FRIENDLY_COMP.get(image['color'], '?')
image['dpi_w'] = image['width'] / pageinfo['width_inches']
image['dpi_h'] = image['height'] / pageinfo['height_inches']
image['dpi'] = (image['dpi_w'] * image['dpi_h']) ** Decimal(0.5)
yield image
def _pdf_get_pageinfo(infile, page: int):
pageinfo = {}
pageinfo['pageno'] = page
pageinfo['images'] = []
pdf = pypdf.PdfFileReader(infile)
page = pdf.pages[page - 1]
text = page.extractText()
pageinfo['has_text'] = (text.strip() != '')
width_pt = page['/MediaBox'][2] - page['/MediaBox'][0]
height_pt = page['/MediaBox'][3] - page['/MediaBox'][1]
pageinfo['width_inches'] = width_pt / Decimal(72.0)
pageinfo['height_inches'] = height_pt / Decimal(72.0)
pageinfo['images'] = [im for im in _find_page_images(page, pageinfo)]
# Look for inline images
if _page_has_inline_images(page):
raise NotImplementedError(
"Warning: input PDF contains inline images - not supported")
if pageinfo['images']:
xres = max(image['dpi_w'] for image in pageinfo['images'])
yres = max(image['dpi_h'] for image in pageinfo['images'])
pageinfo['xres'], pageinfo['yres'] = xres, yres
pageinfo['width_pixels'] = \
int(round(xres * pageinfo['width_inches']))
pageinfo['height_pixels'] = \
int(round(yres * pageinfo['height_inches']))
return pageinfo
def pdf_get_all_pageinfo(infile):
pdf = pypdf.PdfFileReader(infile)
getcontext().prec = 6
return [_pdf_get_pageinfo(infile, n) for n in range(pdf.numPages)]
-132
View File
@@ -1,132 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
#
# Generate a PDFA_def.ps file for Ghostscript >= 9.14
from __future__ import print_function, absolute_import, division
from string import Template
from subprocess import Popen, PIPE
import os
import codecs
# This is a template written in PostScript which is needed to create PDF/A
# files, from the Ghostscript documentation. Lines beginning with % are
# comments. Python substitution variables have a '$' prefix.
pdfa_def_template = u"""%!
% This is a sample prefix file for creating a PDF/A document.
% Feel free to modify entries marked with "Customize".
% This assumes an ICC profile to reside in the file (ISO Coated sb.icc),
% unless the user modifies the corresponding line below.
% Define entries in the document Info dictionary :
/ICCProfile ($icc_profile)
def
[ /Title <$title>
/Author <$author>
/Subject <$subject>
/Keywords <$keywords>
/DOCINFO pdfmark
% Define an ICC profile :
[/_objdef {icc_PDFA} /type /stream /OBJ pdfmark
[{icc_PDFA}
<<
/N currentpagedevice /ProcessColorModel known {
currentpagedevice /ProcessColorModel get dup /DeviceGray eq
{pop 1} {
/DeviceRGB eq
{3}{4} ifelse
} ifelse
} {
(ERROR, unable to determine ProcessColorModel) == flush
} ifelse
>> /PUT pdfmark
[{icc_PDFA} ICCProfile (r) file /PUT pdfmark
% Define the output intent dictionary :
[/_objdef {OutputIntent_PDFA} /type /dict /OBJ pdfmark
[{OutputIntent_PDFA} <<
/Type /OutputIntent % Must be so (the standard requires).
/S /GTS_PDFA1 % Must be so (the standard requires).
/DestOutputProfile {icc_PDFA} % Must be so (see above).
/OutputConditionIdentifier ($icc_identifier)
>> /PUT pdfmark
[{Catalog} <</OutputIntents [ {OutputIntent_PDFA} ]>> /PUT pdfmark
"""
def encode_text_string(s: str) -> str:
'''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
be UTF-16 (big endian) with byte order marks. A non-hexademical
representation is doable but this is preferable since it allows the output
Postscript file to be completely ASCII and no escaping of Postscript
characters is necessary.
'''
if s == '':
return ''
utf16_bytes = s.encode('utf-16be')
ascii_hex_bytes = codecs.encode(b'\xfe\xff' + utf16_bytes, 'hex')
ascii_hex_str = ascii_hex_bytes.decode('ascii').lower()
return ascii_hex_str
def _get_pdfa_def(icc_profile, icc_identifier, pdfmark):
pdfmark_utf16 = {k: encode_text_string(v) for k, v in pdfmark.items()}
t = Template(pdfa_def_template)
result = t.substitute(icc_profile=icc_profile,
icc_identifier=icc_identifier,
title=pdfmark_utf16.get('title', ''),
author=pdfmark_utf16.get('author', ''),
subject=pdfmark_utf16.get('subject', ''),
keywords=pdfmark_utf16.get('keywords', ''))
return result
def _get_postscript_icc_path():
"Parse Ghostscript's help message to find where iccprofiles are stored"
p_gs = Popen(['gs', '--help'], close_fds=True, universal_newlines=True,
stdout=PIPE, stderr=PIPE)
out, _ = p_gs.communicate()
lines = out.splitlines()
def search_paths(lines):
seeking = True
for line in lines:
if seeking:
if line.startswith('Search path'):
seeking = False
continue
else:
if line.strip().startswith('/'):
yield from (
path.strip() for path in line.split(':')
if path.strip() != '')
for root in search_paths(lines):
path = os.path.realpath(os.path.join(root, '../iccprofiles'))
if os.path.exists(path):
return path
raise FileNotFoundError("Could not find Ghostscript's iccprofiles")
def generate_pdfa_def(target_filename, pdfmark, icc='sRGB'):
if icc == 'sRGB':
icc_profile = os.path.join(_get_postscript_icc_path(), 'srgb.icc')
else:
raise NotImplementedError("Only supporting sRGB")
ps = _get_pdfa_def(icc_profile, icc, pdfmark)
# Since PostScript might not handle UTF-8 (it's hard to get a clear
# answer), insist on ascii
with open(target_filename, 'w', encoding='ascii') as f:
f.write(ps)
-69
View File
@@ -1,69 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from subprocess import STDOUT, CalledProcessError, check_output
import sys
import os
import re
from functools import lru_cache
from . import ExitCode
@lru_cache(maxsize=1)
def version():
args_tess = [
'tesseract',
'--version'
]
try:
versions = check_output(
args_tess, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError:
print("Could not find Tesseract executable on system PATH.")
sys.exit(ExitCode.missing_dependency)
tesseract_version = re.match(r'tesseract\s(.+)', versions).group(1)
return tesseract_version
@lru_cache(maxsize=1)
def languages():
args_tess = [
'tesseract',
'--list-langs'
]
try:
langs = check_output(
args_tess, close_fds=True, universal_newlines=True,
stderr=STDOUT)
except CalledProcessError as e:
print("Tesseract failed to report available languages.")
print("Output from Tesseract:")
print("-" * 40)
print(e.output)
sys.exit(ExitCode.missing_dependency)
return set(lang.strip() for lang in langs.splitlines()[1:])
HOCR_TEMPLATE = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name='ocr-system' content='tesseract 3.02.02' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "x.tif"; bbox 0 0 {0} {1}; ppageno 0'>
<div class='ocr_carea' id='block_1_1' title="bbox 0 1 {0} {1}">
<p class='ocr_par' dir='ltr' id='par_1' title="bbox 0 1 {0} {1}">
<span class='ocr_line' id='line_1' title="bbox 0 1 {0} {1}"><span class='ocrx_word' id='word_1' title="bbox 0 1 {0} {1}"> </span>
</span>
</p>
</div>
</div>
</body>
</html>'''
-109
View File
@@ -1,109 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
from ocrmypdf import pageinfo
from reportlab.pdfgen.canvas import Canvas
from PIL import Image
from tempfile import NamedTemporaryFile
from contextlib import suppress
import os
import shutil
import pytest
import img2pdf
from pkg_resources import Requirement, resource_filename
req = Requirement.parse('ocrmypdf')
TEST_OUTPUT = os.environ.get(
'OCRMYPDF_TEST_OUTPUT',
default=os.path.join(os.path.dirname(__file__), 'output'))
def setup_module():
with suppress(FileNotFoundError):
shutil.rmtree(TEST_OUTPUT)
with suppress(FileExistsError):
os.mkdir(TEST_OUTPUT)
def test_single_page_text():
filename = os.path.join(TEST_OUTPUT, 'text.pdf')
pdf = Canvas(filename, pagesize=(8*72, 6*72))
text = pdf.beginText()
text.setFont('Helvetica', 12)
text.setTextOrigin(1*72, 3*72)
text.textLine("Methink'st thou art a general offence and every"
" man should beat thee.")
pdf.drawText(text)
pdf.showPage()
pdf.save()
pdfinfo = pageinfo.pdf_get_all_pageinfo(filename)
assert len(pdfinfo) == 1
page = pdfinfo[0]
assert page['has_text']
assert len(page['images']) == 0
def test_single_page_image():
filename = os.path.join(TEST_OUTPUT, 'image-mono.pdf')
with NamedTemporaryFile() as im_tmp:
im = Image.new('1', (8, 8), 0)
for n in range(8):
im.putpixel((n, n), 1)
im.save(im_tmp.name, format='PNG')
pdf_bytes = img2pdf.convert([im_tmp.name], dpi=8)
with open(filename, 'wb') as pdf:
pdf.write(pdf_bytes)
pdfinfo = pageinfo.pdf_get_all_pageinfo(filename)
assert len(pdfinfo) == 1
page = pdfinfo[0]
assert not page['has_text']
assert len(page['images']) == 1
pdfimage = page['images'][0]
assert pdfimage['width'] == 8
assert pdfimage['color'] == 'gray'
# While unexpected, this is correct
# PDF spec says /FlateDecode image must have /BitsPerComponent 8
# So mono images get upgraded to 8-bit
assert pdfimage['bpc'] == 8
# DPI in a 1"x1" is the image width
assert pdfimage['dpi_w'] == 8
assert pdfimage['dpi_h'] == 8
def test_single_page_inline_image():
filename = os.path.join(TEST_OUTPUT, 'image-mono-inline.pdf')
pdf = Canvas(filename, pagesize=(8*72, 6*72))
with NamedTemporaryFile() as im_tmp:
im = Image.new('1', (8, 8), 0)
for n in range(8):
im.putpixel((n, n), 1)
im.save(im_tmp.name, format='PNG')
# Draw image in a 72x72 pt or 1"x1" area
pdf.drawInlineImage(im_tmp.name, 0, 0, width=72, height=72)
pdf.showPage()
pdf.save()
with pytest.raises(NotImplementedError):
pageinfo.pdf_get_all_pageinfo(filename)
def test_jpeg():
filename = resource_filename(req, 'tests/resources/c02-22.pdf')
pdfinfo = pageinfo.pdf_get_all_pageinfo(filename)
pdfimage = pdfinfo[0]['images'][0]
assert pdfimage['enc'] == 'jpeg'
-102
View File
@@ -1,102 +0,0 @@
#!/usr/bin/env python3
# © 2015 James R. Barlow: github.com/jbarlow83
# unpaper documentation:
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
from subprocess import Popen, PIPE
from tempfile import NamedTemporaryFile
import sys
import os
from functools import lru_cache
from . import ExitCode
@lru_cache(maxsize=1)
def version():
args_unpaper = [
'unpaper',
'--version'
]
p_unpaper = Popen(args_unpaper, close_fds=True, universal_newlines=True,
stdout=PIPE, stderr=PIPE)
version, _ = p_unpaper.communicate(timeout=5)
return version.strip()
try:
from PIL import Image
except ImportError:
print("Could not find Python3 imaging library", file=sys.stderr)
raise
def run(input_file, output_file, dpi, log, mode_args):
args_unpaper = [
'unpaper',
'-v',
'--dpi', str(dpi)
] + mode_args
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
im = Image.open(input_file)
if im.mode not in SUFFIXES.keys():
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 IOError:
log.error(
"Could not convert image with type " + im.mode)
sys.exit(ExitCode.missing_dependency)
try:
suffix = SUFFIXES[im.mode]
except KeyError:
log.error(
"Failed to convert image to a supported format.")
sys.exit(ExitCode.missing_dependency)
with NamedTemporaryFile(suffix=suffix) as input_pnm, \
NamedTemporaryFile(suffix=suffix, mode="r+b") as output_pnm:
im.save(input_pnm, format='PPM')
im.close()
os.unlink(output_pnm.name)
args_unpaper.extend([input_pnm.name, output_pnm.name])
p_unpaper = Popen(
args_unpaper, close_fds=True,
universal_newlines=True, stdout=PIPE, stderr=PIPE
)
out, err = p_unpaper.communicate()
log.debug(out)
log.debug(err)
Image.open(output_pnm.name).save(output_file)
def deskew(input_file, output_file, dpi, log):
run(input_file, output_file, dpi, log, [
'--mask-scan-size', '100', # don't blank out narrow columns
'--no-border-align', # don't align visible content to borders
'--no-mask-center', # don't center visible content within page
'--no-grayfilter', # don't remove light gray areas
'--no-blackfilter', # don't remove solid black areas
'--no-noisefilter', # don't remove salt and pepper noise
'--no-blurfilter' # don't remove blurry objects/debris
])
def clean(input_file, output_file, dpi, log):
run(input_file, output_file, dpi, log, [
'--mask-scan-size', '100', # don't blank out narrow columns
'--no-border-align', # don't align visible content to borders
'--no-mask-center', # don't center visible content within page
'--no-grayfilter', # don't remove light gray areas
'--no-blackfilter', # don't remove solid black areas
'--no-deskew', # don't deskew
])
-235
View File
@@ -1,235 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 2.38.0 (20140413.2041)
-->
<!-- Title: Pipeline: Pages: 1 -->
<svg width="728pt" height="651pt"
viewBox="0.00 0.00 728.00 650.53" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 646.53)">
<title>Pipeline:</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-646.53 724,-646.53 724,4 -4,4"/>
<g id="clust1" class="cluster"><title>clustertasks</title>
<polygon fill="none" stroke="black" points="8,-8 8,-634.53 712,-634.53 712,-8 8,-8"/>
<text text-anchor="middle" x="360" y="-606.53" font-family="Times,serif" font-size="30.00" fill="#ff3232">Pipeline:</text>
</g>
<!-- t0 -->
<g id="node1" class="node"><title>t0</title>
<polygon fill="#efa03b" stroke="#006000" points="481.791,-588.53 386.209,-588.53 382.209,-584.53 382.209,-552.53 477.791,-552.53 481.791,-556.53 481.791,-588.53"/>
<polyline fill="none" stroke="#006000" points="477.791,-584.53 382.209,-584.53 "/>
<polyline fill="none" stroke="#006000" points="477.791,-584.53 477.791,-552.53 "/>
<polyline fill="none" stroke="#006000" points="477.791,-584.53 481.791,-588.53 "/>
<text text-anchor="middle" x="432" y="-564.53" font-family="Times,serif" font-size="20.00" fill="#006000">repair_pdf</text>
</g>
<!-- t1 -->
<g id="node2" class="node"><title>t1</title>
<polygon fill="#efa03b" stroke="black" points="466.782,-509.564 374,-526.497 281.218,-509.564 281.304,-482.165 466.696,-482.165 466.782,-509.564"/>
<polygon fill="none" stroke="black" points="470.799,-512.902 374,-530.569 277.201,-512.902 277.311,-478.159 470.689,-478.159 470.799,-512.902"/>
<text text-anchor="middle" x="374" y="-495.991" font-family="Times,serif" font-size="20.00">split_pages</text>
</g>
<!-- t0&#45;&gt;t1 -->
<g id="edge1" class="edge"><title>t0&#45;&gt;t1</title>
<path fill="none" stroke="gray" d="M417.064,-552.394C412.296,-546.925 406.86,-540.689 401.493,-534.532"/>
<polygon fill="gray" stroke="gray" points="403.996,-532.077 394.787,-526.838 398.719,-536.676 403.996,-532.077"/>
</g>
<!-- t10 -->
<g id="node12" class="node"><title>t10</title>
<polygon fill="#efa03b" stroke="#006000" points="704.338,-451.452 493.662,-451.452 489.662,-447.452 489.662,-415.452 700.338,-415.452 704.338,-419.452 704.338,-451.452"/>
<polyline fill="none" stroke="#006000" points="700.338,-447.452 489.662,-447.452 "/>
<polyline fill="none" stroke="#006000" points="700.338,-447.452 700.338,-415.452 "/>
<polyline fill="none" stroke="#006000" points="700.338,-447.452 704.338,-451.452 "/>
<text text-anchor="middle" x="597" y="-427.452" font-family="Times,serif" font-size="20.00" fill="#006000">generate_postscript_stub</text>
</g>
<!-- t0&#45;&gt;t10 -->
<g id="edge16" class="edge"><title>t0&#45;&gt;t10</title>
<path fill="none" stroke="gray" d="M453.048,-552.468C461.441,-545.654 471.185,-537.73 480,-530.53 510.227,-505.84 544.753,-477.467 568.421,-457.99"/>
<polygon fill="gray" stroke="gray" points="570.818,-460.55 576.315,-451.493 566.369,-455.146 570.818,-460.55"/>
</g>
<!-- t2 -->
<g id="node3" class="node"><title>t2</title>
<polygon fill="#efa03b" stroke="black" points="451.555,-451.452 228.445,-451.452 224.445,-447.452 224.445,-415.452 447.555,-415.452 451.555,-419.452 451.555,-451.452"/>
<polyline fill="none" stroke="black" points="447.555,-447.452 224.445,-447.452 "/>
<polyline fill="none" stroke="black" points="447.555,-447.452 447.555,-415.452 "/>
<polyline fill="none" stroke="black" points="447.555,-447.452 451.555,-451.452 "/>
<text text-anchor="middle" x="338" y="-427.452" font-family="Times,serif" font-size="20.00">rasterize_with_ghostscript</text>
</g>
<!-- t1&#45;&gt;t2 -->
<g id="edge2" class="edge"><title>t1&#45;&gt;t2</title>
<path fill="none" stroke="#0044a0" d="M361.611,-478.092C358.514,-472.369 355.171,-466.19 352.003,-460.333"/>
<polygon fill="#0044a0" stroke="#0044a0" points="355.064,-458.636 347.227,-451.506 348.907,-461.967 355.064,-458.636"/>
</g>
<!-- t11 -->
<g id="node10" class="node"><title>t11</title>
<polygon fill="#efa03b" stroke="black" points="644.594,-393.452 551.406,-393.452 547.406,-389.452 547.406,-357.452 640.594,-357.452 644.594,-361.452 644.594,-393.452"/>
<polyline fill="none" stroke="black" points="640.594,-389.452 547.406,-389.452 "/>
<polyline fill="none" stroke="black" points="640.594,-389.452 640.594,-357.452 "/>
<polyline fill="none" stroke="black" points="640.594,-389.452 644.594,-393.452 "/>
<text text-anchor="middle" x="596" y="-369.452" font-family="Times,serif" font-size="20.00">skip_page</text>
</g>
<!-- t1&#45;&gt;t11 -->
<g id="edge13" class="edge"><title>t1&#45;&gt;t11</title>
<path fill="none" stroke="#0044a0" d="M425.139,-478.007C437.834,-470.731 450.731,-461.83 461,-451.452 473.874,-438.442 466.946,-427.178 481,-415.452 490.212,-407.766 513.965,-399.205 537.508,-392.067"/>
<polygon fill="#0044a0" stroke="#0044a0" points="538.592,-395.396 547.189,-389.203 536.607,-388.683 538.592,-395.396"/>
</g>
<!-- t9 -->
<g id="node11" class="node"><title>t9</title>
<polygon fill="#efa03b" stroke="black" points="272.496,-277.452 19.5039,-277.452 15.5039,-273.452 15.5039,-241.452 268.496,-241.452 272.496,-245.452 272.496,-277.452"/>
<polyline fill="none" stroke="black" points="268.496,-273.452 15.5039,-273.452 "/>
<polyline fill="none" stroke="black" points="268.496,-273.452 268.496,-241.452 "/>
<polyline fill="none" stroke="black" points="268.496,-273.452 272.496,-277.452 "/>
<text text-anchor="middle" x="144" y="-253.452" font-family="Times,serif" font-size="20.00">tesseract_ocr_and_render_pdf</text>
</g>
<!-- t1&#45;&gt;t9 -->
<g id="edge15" class="edge"><title>t1&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M277.341,-486.044C254.708,-478.702 232.21,-467.759 215,-451.452 168.207,-407.114 152.056,-329.127 146.633,-287.855"/>
<polygon fill="#0044a0" stroke="#0044a0" points="150.084,-287.235 145.422,-277.721 143.133,-288.065 150.084,-287.235"/>
</g>
<!-- t3 -->
<g id="node4" class="node"><title>t3</title>
<polygon fill="#efa03b" stroke="black" points="458.999,-393.452 291.001,-393.452 287.001,-389.452 287.001,-357.452 454.999,-357.452 458.999,-361.452 458.999,-393.452"/>
<polyline fill="none" stroke="black" points="454.999,-389.452 287.001,-389.452 "/>
<polyline fill="none" stroke="black" points="454.999,-389.452 454.999,-357.452 "/>
<polyline fill="none" stroke="black" points="454.999,-389.452 458.999,-393.452 "/>
<text text-anchor="middle" x="373" y="-369.452" font-family="Times,serif" font-size="20.00">preprocess_deskew</text>
</g>
<!-- t2&#45;&gt;t3 -->
<g id="edge3" class="edge"><title>t2&#45;&gt;t3</title>
<path fill="none" stroke="#0044a0" d="M348.691,-415.346C351.337,-411.112 354.23,-406.485 357.066,-401.946"/>
<polygon fill="#0044a0" stroke="#0044a0" points="360.042,-403.788 362.374,-393.453 354.106,-400.078 360.042,-403.788"/>
</g>
<!-- t6 -->
<g id="node6" class="node"><title>t6</title>
<polygon fill="#efa03b" stroke="black" points="664.375,-277.452 477.625,-277.452 473.625,-273.452 473.625,-241.452 660.375,-241.452 664.375,-245.452 664.375,-277.452"/>
<polyline fill="none" stroke="black" points="660.375,-273.452 473.625,-273.452 "/>
<polyline fill="none" stroke="black" points="660.375,-273.452 660.375,-241.452 "/>
<polyline fill="none" stroke="black" points="660.375,-273.452 664.375,-277.452 "/>
<text text-anchor="middle" x="569" y="-253.452" font-family="Times,serif" font-size="20.00">select_image_for_pdf</text>
</g>
<!-- t2&#45;&gt;t6 -->
<g id="edge7" class="edge"><title>t2&#45;&gt;t6</title>
<path fill="none" stroke="#0044a0" d="M423.032,-415.374C438.812,-409.925 454.543,-402.785 468,-393.452 508.201,-365.571 539.287,-316.662 555.808,-286.558"/>
<polygon fill="#0044a0" stroke="#0044a0" points="559.024,-287.965 560.653,-277.496 552.851,-284.664 559.024,-287.965"/>
</g>
<!-- t4 -->
<g id="node5" class="node"><title>t4</title>
<polygon fill="#efa03b" stroke="black" points="449.705,-335.452 300.295,-335.452 296.295,-331.452 296.295,-299.452 445.705,-299.452 449.705,-303.452 449.705,-335.452"/>
<polyline fill="none" stroke="black" points="445.705,-331.452 296.295,-331.452 "/>
<polyline fill="none" stroke="black" points="445.705,-331.452 445.705,-299.452 "/>
<polyline fill="none" stroke="black" points="445.705,-331.452 449.705,-335.452 "/>
<text text-anchor="middle" x="373" y="-311.452" font-family="Times,serif" font-size="20.00">preprocess_clean</text>
</g>
<!-- t3&#45;&gt;t4 -->
<g id="edge4" class="edge"><title>t3&#45;&gt;t4</title>
<path fill="none" stroke="#0044a0" d="M373,-357.346C373,-353.655 373,-349.665 373,-345.695"/>
<polygon fill="#0044a0" stroke="#0044a0" points="376.5,-345.453 373,-335.453 369.5,-345.453 376.5,-345.453"/>
</g>
<!-- t3&#45;&gt;t6 -->
<g id="edge6" class="edge"><title>t3&#45;&gt;t6</title>
<path fill="none" stroke="#0044a0" d="M414.399,-357.412C428.785,-351.039 444.852,-343.405 459,-335.452 466.909,-331.007 506.49,-303.805 535.921,-283.431"/>
<polygon fill="#0044a0" stroke="#0044a0" points="538.073,-286.198 544.299,-277.625 534.086,-280.444 538.073,-286.198"/>
</g>
<!-- t4&#45;&gt;t6 -->
<g id="edge5" class="edge"><title>t4&#45;&gt;t6</title>
<path fill="none" stroke="#0044a0" d="M432.605,-299.422C453.701,-293.395 477.617,-286.561 499.465,-280.319"/>
<polygon fill="#0044a0" stroke="#0044a0" points="500.687,-283.61 509.341,-277.497 498.764,-276.879 500.687,-283.61"/>
</g>
<!-- t5 -->
<g id="node7" class="node"><title>t5</title>
<polygon fill="#efa03b" stroke="black" points="455.922,-277.452 294.078,-277.452 290.078,-273.452 290.078,-241.452 451.922,-241.452 455.922,-245.452 455.922,-277.452"/>
<polyline fill="none" stroke="black" points="451.922,-273.452 290.078,-273.452 "/>
<polyline fill="none" stroke="black" points="451.922,-273.452 451.922,-241.452 "/>
<polyline fill="none" stroke="black" points="451.922,-273.452 455.922,-277.452 "/>
<text text-anchor="middle" x="373" y="-253.452" font-family="Times,serif" font-size="20.00">ocr_tesseract_hocr</text>
</g>
<!-- t4&#45;&gt;t5 -->
<g id="edge8" class="edge"><title>t4&#45;&gt;t5</title>
<path fill="none" stroke="#0044a0" d="M373,-299.346C373,-295.655 373,-291.665 373,-287.695"/>
<polygon fill="#0044a0" stroke="#0044a0" points="376.5,-287.453 373,-277.453 369.5,-287.453 376.5,-287.453"/>
</g>
<!-- t4&#45;&gt;t9 -->
<g id="edge14" class="edge"><title>t4&#45;&gt;t9</title>
<path fill="none" stroke="#0044a0" d="M303.36,-299.422C278.157,-293.259 249.509,-286.253 223.521,-279.898"/>
<polygon fill="#0044a0" stroke="#0044a0" points="224.249,-276.473 213.704,-277.497 222.586,-283.273 224.249,-276.473"/>
</g>
<!-- t7 -->
<g id="node8" class="node"><title>t7</title>
<polygon fill="#efa03b" stroke="black" points="426.366,-219.452 269.634,-219.452 265.634,-215.452 265.634,-183.452 422.366,-183.452 426.366,-187.452 426.366,-219.452"/>
<polyline fill="none" stroke="black" points="422.366,-215.452 265.634,-215.452 "/>
<polyline fill="none" stroke="black" points="422.366,-215.452 422.366,-183.452 "/>
<polyline fill="none" stroke="black" points="422.366,-215.452 426.366,-219.452 "/>
<text text-anchor="middle" x="346" y="-195.452" font-family="Times,serif" font-size="20.00">render_hocr_page</text>
</g>
<!-- t6&#45;&gt;t7 -->
<g id="edge9" class="edge"><title>t6&#45;&gt;t7</title>
<path fill="none" stroke="#0044a0" d="M501.184,-241.422C476.75,-235.286 448.99,-228.315 423.772,-221.982"/>
<polygon fill="#0044a0" stroke="#0044a0" points="424.429,-218.538 413.877,-219.497 422.724,-225.328 424.429,-218.538"/>
</g>
<!-- t8 -->
<g id="node9" class="node"><title>t8</title>
<polygon fill="#efa03b" stroke="black" points="663.742,-219.452 448.258,-219.452 444.258,-215.452 444.258,-183.452 659.742,-183.452 663.742,-187.452 663.742,-219.452"/>
<polyline fill="none" stroke="black" points="659.742,-215.452 444.258,-215.452 "/>
<polyline fill="none" stroke="black" points="659.742,-215.452 659.742,-183.452 "/>
<polyline fill="none" stroke="black" points="659.742,-215.452 663.742,-219.452 "/>
<text text-anchor="middle" x="554" y="-195.452" font-family="Times,serif" font-size="20.00">render_hocr_debug_page</text>
</g>
<!-- t6&#45;&gt;t8 -->
<g id="edge11" class="edge"><title>t6&#45;&gt;t8</title>
<path fill="none" stroke="#0044a0" d="M564.418,-241.346C563.4,-237.546 562.298,-233.43 561.203,-229.345"/>
<polygon fill="#0044a0" stroke="#0044a0" points="564.522,-228.207 558.554,-219.453 557.761,-230.018 564.522,-228.207"/>
</g>
<!-- t5&#45;&gt;t7 -->
<g id="edge10" class="edge"><title>t5&#45;&gt;t7</title>
<path fill="none" stroke="#0044a0" d="M364.752,-241.346C362.816,-237.329 360.708,-232.958 358.629,-228.645"/>
<polygon fill="#0044a0" stroke="#0044a0" points="361.693,-226.941 354.197,-219.453 355.387,-229.981 361.693,-226.941"/>
</g>
<!-- t5&#45;&gt;t8 -->
<g id="edge12" class="edge"><title>t5&#45;&gt;t8</title>
<path fill="none" stroke="#0044a0" d="M428.289,-241.346C447.587,-235.375 469.416,-228.622 489.404,-222.437"/>
<polygon fill="#0044a0" stroke="#0044a0" points="490.53,-225.753 499.049,-219.453 488.461,-219.065 490.53,-225.753"/>
</g>
<!-- t12 -->
<g id="node13" class="node"><title>t12</title>
<polygon fill="#efa03b" stroke="black" points="473.845,-105.456 554,-78.0208 634.155,-105.456 634.08,-149.848 473.92,-149.848 473.845,-105.456"/>
<polygon fill="none" stroke="black" points="469.836,-102.581 554,-73.7729 638.164,-102.581 638.078,-153.869 469.922,-153.869 469.836,-102.581"/>
<text text-anchor="middle" x="554" y="-111.726" font-family="Times,serif" font-size="20.00">merge_pages</text>
</g>
<!-- t7&#45;&gt;t12 -->
<g id="edge20" class="edge"><title>t7&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M389.35,-183.419C409.937,-175.33 435.398,-165.326 460.092,-155.624"/>
<polygon fill="#0044a0" stroke="#0044a0" points="461.479,-158.839 469.507,-151.924 458.919,-152.324 461.479,-158.839"/>
</g>
<!-- t8&#45;&gt;t12 -->
<g id="edge19" class="edge"><title>t8&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M554,-183.12C554,-177.585 554,-171.177 554,-164.592"/>
<polygon fill="#0044a0" stroke="#0044a0" points="557.5,-164.201 554,-154.201 550.5,-164.201 557.5,-164.201"/>
</g>
<!-- t11&#45;&gt;t12 -->
<g id="edge17" class="edge"><title>t11&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M615.939,-357.13C634.767,-339.333 661.745,-309.733 673,-277.452 686.754,-238.003 694.347,-219.364 673,-183.452 666.345,-172.256 656.999,-162.875 646.44,-155.047"/>
<polygon fill="#0044a0" stroke="#0044a0" points="648.378,-152.132 638.152,-149.36 644.417,-157.904 648.378,-152.132"/>
</g>
<!-- t9&#45;&gt;t12 -->
<g id="edge18" class="edge"><title>t9&#45;&gt;t12</title>
<path fill="none" stroke="#0044a0" d="M164.711,-241.235C186.394,-224.08 222.088,-198.224 257,-183.452 321.895,-155.994 399.893,-139.571 459.67,-130.141"/>
<polygon fill="#0044a0" stroke="#0044a0" points="460.504,-133.554 469.855,-128.574 459.439,-126.635 460.504,-133.554"/>
</g>
<!-- t10&#45;&gt;t12 -->
<g id="edge22" class="edge"><title>t10&#45;&gt;t12</title>
<path fill="none" stroke="gray" d="M629.379,-415.337C660.552,-396.255 703,-362.233 703,-318.452 703,-318.452 703,-318.452 703,-258.452 703,-224.066 705.581,-210.223 684,-183.452 673.976,-171.017 660.952,-160.773 647.026,-152.396"/>
<polygon fill="gray" stroke="gray" points="648.682,-149.312 638.258,-147.419 645.227,-155.4 648.682,-149.312"/>
</g>
<!-- t13 -->
<g id="node14" class="node"><title>t13</title>
<polygon fill="#efa03b" stroke="black" points="616.338,-52 495.662,-52 491.662,-48 491.662,-16 612.338,-16 616.338,-20 616.338,-52"/>
<polyline fill="none" stroke="black" points="612.338,-48 491.662,-48 "/>
<polyline fill="none" stroke="black" points="612.338,-48 612.338,-16 "/>
<polyline fill="none" stroke="black" points="612.338,-48 616.338,-52 "/>
<text text-anchor="middle" x="554" y="-28" font-family="Times,serif" font-size="20.00">validate_pdfa</text>
</g>
<!-- t12&#45;&gt;t13 -->
<g id="edge21" class="edge"><title>t12&#45;&gt;t13</title>
<path fill="none" stroke="#0044a0" d="M554,-73.9482C554,-69.9654 554,-66.007 554,-62.2247"/>
<polygon fill="#0044a0" stroke="#0044a0" points="557.5,-62.1573 554,-52.1573 550.5,-62.1574 557.5,-62.1573"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 16 KiB

+33
View File
@@ -0,0 +1,33 @@
[build-system]
requires = [
"setuptools >= 30.3.0",
"wheel",
"cffi",
"setuptools_scm",
"setuptools_scm_git_archive"
]
build-backend = "setuptools.build_meta"
[tool.black]
line-length = 88
target-version = ["py36", "py37", "py38"]
skip-string-normalization = true
include = '\.pyi?$'
exclude = '''
/(
\.eggs
| \.git
| \.hg
| \.mypy_cache
| \.tox
| \.venv
| _build
| buck-out
| build
| dist
| docs
| misc
| \.egg-info
| src/ocrmypdf/lib/_leptonica.py
)/
'''
-4
View File
@@ -1,4 +0,0 @@
ruffus>=2.6.3
Pillow>=2.4.0
reportlab>=3.1.44
PyPDF2>=1.25.1
+12
View File
@@ -0,0 +1,12 @@
# 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
cffi == 1.14.0
coloredlogs == 14.0 # technically optional
img2pdf == 0.3.6
pdfminer.six == 20200517
pikepdf == 1.16.1
pluggy == 0.13.1
Pillow == 7.1.2
reportlab == 3.5.42
tqdm == 4.46.1
+7
View File
@@ -0,0 +1,7 @@
pytest >= 5.0.0
pytest-helpers-namespace >= 2019.1.8
pytest-xdist >= 1.31.0
pytest-cov >= 2.10.0
python-xmp-toolkit == 2.0.1 # requires apt-get install libexempi3
# or brew install exempi
#PyMuPDF == 1.13.4 # optional
+1
View File
@@ -0,0 +1 @@
watchdog == 0.10.2
+1
View File
@@ -0,0 +1 @@
Flask >= 1, < 2
+28 -1
View File
@@ -1,2 +1,29 @@
[bdist_wheel]
python-tag = py34
python-tag = py35
[aliases]
test=pytest
[check-manifest]
ignore =
.github
[tool:pytest]
norecursedirs = lib .pc .git output cache resources
testpaths = tests
filterwarnings =
ignore:.*XMLParser.*:DeprecationWarning
markers =
slow
[isort]
multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
use_parentheses=True
line_length=88
known_first_party = ocrmypdf
known_third_party = PIL,_cffi_backend,cffi,flask,img2pdf,pdfminer,pikepdf,pkg_resources,pluggy,pytest,reportlab,setuptools,sphinx_rtd_theme,tqdm,watchdog,werkzeug
[metadata]
license_file = LICENSE
+60 -181
View File
@@ -1,214 +1,93 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# © 2015 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/.
from __future__ import print_function, unicode_literals
from setuptools import setup
from subprocess import Popen, STDOUT, check_output, CalledProcessError
from string import Template
from collections.abc import Mapping
import re
import sys
from setuptools import find_packages, setup
missing_program = '''
The program '{program}' could not be executed or was not found on your
system PATH.
'''
unknown_version = '''
OCRmyPDF requires '{program}' {need_version} or higher. Your system has
'{program}' but we cannot tell what version is installed. Contact the
package maintainer.
'''
old_version = '''
OCRmyPDF requires '{program}' {need_version} or higher. Your system appears
to have {found_version}. Please update this program.
'''
okay_its_optional = '''
This program is OPTIONAL, so installation of OCRmyPDF can proceed, but
some functionality may be missing.
'''
not_okay_its_required = '''
This program is REQUIRED for OCRmyPDF to work. Installation will abort.
'''
osx_install_advice = '''
If you have homebrew installed, try these command to install the missing
packages:
brew update
brew upgrade
brew install {package}
'''
linux_install_advice = '''
On systems with the aptitude package manager (Debian, Ubuntu), try these
commands:
sudo apt-get update
sudo apt-get install {package}
On RPM-based systems (Red Hat, Fedora), search for instructions on
installing the RPM for {program}.
'''
def get_platform():
if sys.platform.startswith('freebsd'):
return 'freebsd'
elif sys.platform.startswith('linux'):
return 'linux'
return sys.platform
def _error_trailer(program, package, optional, **kwargs):
if optional:
print(okay_its_optional.format(**locals()), file=sys.stderr)
else:
print(not_okay_its_required.format(**locals()), file=sys.stderr)
if isinstance(package, Mapping):
package = package[get_platform()]
if get_platform() == 'darwin':
print(osx_install_advice.format(**locals()), file=sys.stderr)
elif get_platform() == 'linux':
print(linux_install_advice.format(**locals()), file=sys.stderr)
def error_missing_program(
program,
package,
optional
):
print(missing_program.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def error_unknown_version(
program,
package,
optional,
need_version
):
print(unknown_version.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def error_old_version(
program,
package,
optional,
need_version,
found_version
):
print(old_version.format(**locals()), file=sys.stderr)
_error_trailer(**locals())
def check_external_program(
program,
need_version,
package,
version_check_args=['--version'],
version_scrape_regex=re.compile(r'(\d+\.\d+(?:\.\d+)?)'),
optional=False):
print('Checking for {program} >= {need_version}...'.format(
program=program, need_version=need_version))
try:
result = check_output(
[program] + version_check_args,
universal_newlines=True, stderr=STDOUT)
except (CalledProcessError, FileNotFoundError):
error_missing_program(program, package, optional)
if not optional:
sys.exit(1)
print('Continuing install without {program}'.format(program=program))
return
try:
found_version = version_scrape_regex.search(result).group(1)
except AttributeError:
error_unknown_version(program, package, optional, need_version)
sys.exit(1)
if found_version < need_version:
error_old_version(program, package, optional, need_version,
found_version)
print('Found {program} {found_version}'.format(
program=program, found_version=found_version))
command = next((arg for arg in sys.argv[1:] if not arg.startswith('-')), '')
if command.startswith('install') or \
command in ['check', 'test', 'nosetests', 'easy_install', 'egg_info']:
check_external_program(
program='tesseract',
need_version='3.02.02',
package={'darwin': 'tesseract', 'linux': 'tesseract-ocr'}
)
check_external_program(
program='gs',
need_version='9.14',
package='ghostscript'
)
check_external_program(
program='unpaper',
need_version='6.1',
package='unpaper',
optional=True
)
check_external_program(
program='qpdf',
need_version='5.0.0',
package='qpdf',
version_check_args=['--version']
)
if sys.version_info < (3, 6):
print("Python 3.6 or newer is required", file=sys.stderr)
sys.exit(1)
if 'upload' in sys.argv[1:]:
print('Use twine to upload the package - setup.py upload is insecure')
sys.exit(1)
install_requires = open('requirements.txt').read().splitlines()
tests_require = open('test_requirements.txt').read().splitlines()
tests_require = open('requirements/test.txt', encoding='utf-8').read().splitlines()
def readme():
with open('README.md', encoding='utf-8') as f:
return f.read()
setup(
name='ocrmypdf',
version='3.0', # also update: release notes, main.py
description='OCRmyPDF adds an OCR text layer to scanned PDF files, allowing them to be searched',
long_description=readme(),
long_description_content_type='text/markdown',
url='https://github.com/jbarlow83/OCRmyPDF',
author='James R. Barlow',
author_email='jim@purplerock.ca',
license='Public Domain',
packages=['ocrmypdf'],
author_email='james@purplerock.ca',
packages=find_packages('src', exclude=["tests", "tests.*"]),
package_dir={'': 'src'},
keywords=['PDF', 'OCR', 'optical character recognition', 'PDF/A', 'scanning'],
classifiers=[
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Intended Audience :: End Users/Desktop",
"Intended Audience :: Science/Research",
"Intended Audience :: System Administrators",
"License :: Public Domain",
"License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)",
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows :: Windows 10",
"Operating System :: POSIX",
"Operating System :: POSIX :: BSD",
"Operating System :: POSIX :: Linux",
"Topic :: Scientific/Engineering :: Image Recognition",
"Topic :: Text Processing :: Indexing",
"Topic :: Text Processing :: Linguistic",
],
install_requires=install_requires,
],
python_requires=' >= 3.6',
setup_requires=[ # can be removed whenever we can drop pip 9 support
'cffi >= 1.9.1', # to build the leptonica module
'pytest-runner', # to enable python setup.py test
'setuptools_scm', # so that version will work
'setuptools_scm_git_archive', # enable version from github tarballs
],
use_scm_version={'version_scheme': 'post-release'},
cffi_modules=['src/ocrmypdf/lib/compile_leptonica.py:ffibuilder'],
install_requires=[
'cffi >= 1.9.1', # must be a setup and install requirement
'coloredlogs >= 14.0', # strictly optional
'img2pdf >= 0.3.0, < 0.4', # pure Python, so track HEAD closely
'pdfminer.six >= 20191110, <= 20200726',
'pikepdf >= 1.14.0, < 2',
'Pillow >= 7.0.0',
'pluggy >= 0.13.0',
'reportlab >= 3.3.0', # oldest released version with sane image handling
'tqdm >= 4',
],
tests_require=tests_require,
entry_points={
'console_scripts': [
'ocrmypdf = ocrmypdf.main:run_pipeline'
],
},
entry_points={'console_scripts': ['ocrmypdf = ocrmypdf.__main__:run']},
package_data={'ocrmypdf': ['data/sRGB.icc']},
include_package_data=True,
zip_safe=False)
zip_safe=False,
project_urls={
'Documentation': 'https://ocrmypdf.readthedocs.io/',
'Source': 'https://github.com/jbarlow83/ocrmypdf',
'Tracker': 'https://github.com/jbarlow83/ocrmypdf/issues',
},
)
+30
View File
@@ -0,0 +1,30 @@
# © 2017 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/.
from pluggy import HookimplMarker as _HookimplMarker
from ocrmypdf import helpers, hocrtransform, leptonica, pdfa, pdfinfo
from ocrmypdf._version import PROGRAM_NAME, __version__
from ocrmypdf.api import Verbosity, configure_logging, ocr
from ocrmypdf.exceptions import (
BadArgsError,
DpiError,
EncryptedPdfError,
ExitCode,
ExitCodeException,
InputFileError,
MissingDependencyError,
OutputFileAccessError,
PdfMergeFailedError,
PriorOcrFoundError,
SubprocessOutputError,
TesseractConfigError,
UnsupportedImageFormatError,
)
from ocrmypdf.pluginspec import OcrEngine, OrientationConfidence
hookimpl = _HookimplMarker('ocrmypdf')
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
# © 2015-19 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 logging
import os
import signal
import sys
from multiprocessing import set_start_method
from ocrmypdf import __version__
from ocrmypdf._plugin_manager import get_parser_options_plugins
from ocrmypdf._sync import run_pipeline
from ocrmypdf._validation import check_closed_streams, check_options
from ocrmypdf.api import Verbosity, configure_logging
from ocrmypdf.exceptions import (
BadArgsError,
ExitCode,
InputFileError,
MissingDependencyError,
)
log = logging.getLogger('ocrmypdf')
def sigbus(*args):
raise InputFileError("Lost access to the input file")
def run(args=None):
_parser, options, plugin_manager = get_parser_options_plugins(args=args)
if not check_closed_streams(options):
return ExitCode.bad_args
if hasattr(os, 'nice'):
os.nice(5)
verbosity = options.verbose
if not os.isatty(sys.stderr.fileno()):
options.progress_bar = False
if options.quiet:
verbosity = Verbosity.quiet
options.progress_bar = False
configure_logging(
verbosity, progress_bar_friendly=options.progress_bar, manage_root_logger=True
)
log.debug('ocrmypdf %s', __version__)
try:
check_options(options, plugin_manager)
except ValueError as e:
log.error(e)
return ExitCode.bad_args
except BadArgsError as e:
log.error(e)
return e.exit_code
except MissingDependencyError as e:
log.error(e)
return ExitCode.missing_dependency
if hasattr(signal, 'SIGBUS'):
signal.signal(signal.SIGBUS, sigbus)
result = run_pipeline(options=options, plugin_manager=plugin_manager)
return result
if __name__ == '__main__':
if sys.platform == 'darwin' and sys.version_info < (3, 8):
set_start_method('spawn') # see python bpo-33725
sys.exit(run())
+138
View File
@@ -0,0 +1,138 @@
# © 2020 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 logging
import logging.handlers
import multiprocessing
import os
import signal
import sys
import threading
from multiprocessing import Pool as ProcessPool
from multiprocessing.dummy import Pool as ThreadPool
from typing import Callable, Iterable, Optional
from tqdm import tqdm
from ocrmypdf.exceptions import InputFileError
def log_listener(queue):
"""Listen to the worker processes and forward the messages to logging
For simplicity this is a thread rather than a process. Only one process
should actually write to sys.stderr or whatever we're using, so if this is
made into a process the main application needs to be directed to it.
See https://docs.python.org/3/howto/logging-cookbook.html#logging-to-a-single-file-from-multiple-processes
"""
while True:
try:
record = queue.get()
if record is None:
break
logger = logging.getLogger(record.name)
logger.handle(record)
except Exception: # pylint: disable=broad-except
import traceback # pylint: disable=import-outside-toplevel
print("Logging problem", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
def process_sigbus(*args):
raise InputFileError("A worker process lost access to an input file")
def process_init(queue, user_init):
"""Initialize a process pool worker"""
# Ignore SIGINT (our parent process will kill us gracefully)
signal.signal(signal.SIGINT, signal.SIG_IGN)
# Install SIGBUS handler (so our parent process can abort somewhat gracefully)
if hasattr(signal, 'SIGBUS'):
signal.signal(signal.SIGBUS, process_sigbus)
# Reconfigure the root logger for this process to send all messages to a queue
h = logging.handlers.QueueHandler(queue)
root = logging.getLogger()
root.handlers = []
root.addHandler(h)
if user_init:
user_init()
def thread_init(_queue, user_init):
# As a thread, block SIGBUS so the main thread deals with it...
if hasattr(signal, 'SIGBUS'):
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGBUS})
if user_init:
user_init()
def exec_progress_pool(
*,
use_threads: bool,
max_workers: int,
tqdm_kwargs: dict,
task_initializer: Optional[Callable] = None,
task: Optional[Callable] = None,
task_arguments: Optional[Iterable] = None,
task_finished: Optional[Callable] = None,
):
log_queue: multiprocessing.Queue = multiprocessing.Queue(-1)
listener = threading.Thread(target=log_listener, args=(log_queue,))
if use_threads:
pool_class = ThreadPool
initializer = thread_init
else:
pool_class = ProcessPool
initializer = process_init
listener.start()
with tqdm(**tqdm_kwargs) as pbar:
pool = pool_class(
processes=max_workers,
initializer=initializer,
initargs=(log_queue, task_initializer),
)
try:
results = pool.imap_unordered(task, task_arguments)
while True:
try:
result = results.next()
if task_finished:
task_finished(result, pbar)
else:
pbar.update()
except StopIteration:
break
except KeyboardInterrupt:
# Terminate pool so we exit instantly
pool.terminate()
# Don't try listener.join() here, will deadlock
raise
except Exception:
if not os.environ.get("PYTEST_CURRENT_TEST", ""):
# Unless inside pytest, exit immediately because no one wants
# to wait for child processes to finalize results that will be
# thrown away. Inside pytest, we want child processes to exit
# cleanly so that they output an error messages or coverage data
# we need from them.
pool.terminate()
raise
finally:
# Terminate log listener
log_queue.put_nowait(None)
pool.close()
pool.join()
listener.join()
+8
View File
@@ -0,0 +1,8 @@
# © 2020 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/.
"""Manage third party executables"""
+238
View File
@@ -0,0 +1,238 @@
# © 2017 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/.
"""Interface to Ghostscript executable"""
import logging
import os
import re
from io import BytesIO
from os import fspath
from pathlib import Path
from shutil import which
from subprocess import PIPE, CalledProcessError
from typing import Optional, cast
from PIL import Image
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
from ocrmypdf.helpers import Resolution
from ocrmypdf.subprocess import get_version, run
log = logging.getLogger(__name__)
_gswin = None
if os.name == 'nt':
_gswin = which('gswin64c')
if not _gswin:
_gswin = which('gswin32c')
if not _gswin:
raise MissingDependencyError(
"""
---------------------------------------------------------------------
This error normally occurs when ocrmypdf can't Ghostscript. Please
ensure Ghostscript is installed and its location is added to the
system PATH environment variable.
For details see:
https://ocrmypdf.readthedocs.io/en/latest/installation.html
---------------------------------------------------------------------
"""
)
_gswin = Path(_gswin).stem
GS = _gswin if _gswin else 'gs'
del _gswin
def version():
return get_version(GS)
def jpeg_passthrough_available() -> bool:
"""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.
"""
return version() >= '9.24'
def _gs_error_reported(stream) -> bool:
return True if re.search(r'error', stream, flags=re.IGNORECASE) else False
def rasterize_pdf(
input_file: os.PathLike,
output_file: os.PathLike,
*,
raster_device: str,
raster_dpi: Resolution,
pageno: int = 1,
page_dpi: Resolution = None,
rotation: int = None,
filter_vector: bool = False,
):
"""Rasterize one page of a PDF at resolution raster_dpi in canvas units."""
raster_dpi = raster_dpi.round(6)
if not page_dpi:
page_dpi = raster_dpi
args_gs = (
[
GS,
'-dQUIET',
'-dSAFER',
'-dBATCH',
'-dNOPAUSE',
f'-sDEVICE={raster_device}',
f'-dFirstPage={pageno}',
f'-dLastPage={pageno}',
f'-r{raster_dpi.x:f}x{raster_dpi.y:f}',
]
+ (['-dFILTERVECTOR'] if filter_vector else [])
+ [
'-o',
'-',
'-sstdout=%stderr',
'-dAutoRotatePages=/None', # Probably has no effect on raster
'-f',
fspath(input_file),
]
)
try:
p = run(args_gs, stdout=PIPE, stderr=PIPE, check=True)
except CalledProcessError as e:
log.error(e.stderr.decode(errors='replace'))
raise SubprocessOutputError('Ghostscript rasterizing failed')
else:
stderr = p.stderr.decode(errors='replace')
if _gs_error_reported(stderr):
log.error(stderr)
elif stderr:
log.debug(stderr)
with Image.open(BytesIO(p.stdout)) as im:
if rotation is not None:
log.debug("Rotating output by %i", rotation)
# rotation is a clockwise angle and Image.ROTATE_* is
# counterclockwise so this cancels out the rotation
if rotation == 90:
im = im.transpose(Image.ROTATE_90)
elif rotation == 180:
im = im.transpose(Image.ROTATE_180)
elif rotation == 270:
im = im.transpose(Image.ROTATE_270)
if rotation % 180 == 90:
page_dpi = page_dpi.flip_axis()
im.save(fspath(output_file), dpi=page_dpi)
def generate_pdfa(
pdf_pages,
output_file: os.PathLike,
compression: str,
pdf_version: str = '1.5',
pdfa_part: str = '2',
):
compression_args = []
if compression == 'jpeg':
compression_args = [
"-dAutoFilterColorImages=false",
"-dColorImageFilter=/DCTEncode",
"-dAutoFilterGrayImages=false",
"-dGrayImageFilter=/DCTEncode",
]
elif compression == 'lossless':
compression_args = [
"-dAutoFilterColorImages=false",
"-dColorImageFilter=/FlateEncode",
"-dAutoFilterGrayImages=false",
"-dGrayImageFilter=/FlateEncode",
]
else:
compression_args = [
"-dAutoFilterColorImages=true",
"-dAutoFilterGrayImages=true",
]
# 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':
# 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
compression_args.append('-dPassThroughJPEGImages=false')
# nb no need to specify ProcessColorModel when ColorConversionStrategy
# is set; see:
# https://bugs.ghostscript.com/show_bug.cgi?id=699392
args_gs = (
[
GS,
"-dQUIET",
"-dBATCH",
"-dNOPAUSE",
"-dSAFER",
"-dCompatibilityLevel=" + str(pdf_version),
"-sDEVICE=pdfwrite",
"-dAutoRotatePages=/None",
"-sColorConversionStrategy=" + strategy,
]
+ compression_args
+ [
"-dJPEGQ=95",
"-dPDFA=" + pdfa_part,
"-dPDFACompatibilityPolicy=1",
"-o",
"-",
"-sstdout=%stderr",
]
)
args_gs.extend(fspath(s) for s in pdf_pages) # Stringify Path objs
try:
with Path(output_file).open('wb') as output:
p = run(args_gs, stdout=output, stderr=PIPE, check=True)
except CalledProcessError as e:
# Ghostscript does not change return code when it fails to create
# PDF/A - check PDF/A status elsewhere
log.error(e.stderr.decode(errors='replace'))
raise SubprocessOutputError('Ghostscript PDF/A rendering failed')
else:
stderr = p.stderr.decode('utf-8', errors='replace')
if _gs_error_reported(stderr):
last_part = None
repcount = 0
for part in stderr.split('****'):
if part != last_part:
if repcount > 1:
log.error(f"(previous error message repeated {repcount} times)")
repcount = 0
log.error(part)
else:
repcount += 1
last_part = part
elif 'overprint mode not set' in stderr:
# Unless someone is going to print PDF/A documents on a
# magical sRGB printer I can't see the removal of overprinting
# being a problem....
log.debug(
"Ghostscript had to remove PDF 'overprinting' from the "
"input file to complete PDF/A conversion. "
)
+49
View File
@@ -0,0 +1,49 @@
# © 2018 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/.
"""Interface to jbig2 executable"""
from subprocess import PIPE
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.subprocess import get_version, run
def version():
return get_version('jbig2', regex=r'jbig2enc (\d+(\.\d+)*).*')
def available():
try:
version()
except MissingDependencyError:
return False
return True
def convert_group(*, cwd, infiles, out_prefix):
args = [
'jbig2',
'-b',
out_prefix,
'-s', # symbol mode (lossy)
# '-r', # refinement mode (lossless symbol mode, currently disabled in
# jbig2)
'-p',
]
args.extend(infiles)
proc = run(args, cwd=cwd, stdout=PIPE, stderr=PIPE)
proc.check_returncode()
return proc
def convert_single(*, cwd, infile, outfile):
args = ['jbig2', '-p', infile]
with open(outfile, 'wb') as fstdout:
proc = run(args, cwd=cwd, stdout=fstdout, stderr=PIPE)
proc.check_returncode()
return proc
+61
View File
@@ -0,0 +1,61 @@
# © 2018 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/.
"""Interface to pngquant executable"""
from os import fspath
from tempfile import NamedTemporaryFile
from PIL import Image
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.subprocess import get_version, run
def version():
return get_version('pngquant', regex=r'(\d+(\.\d+)*).*')
def available():
try:
version()
except MissingDependencyError:
return False
return True
def quantize(input_file, output_file, quality_min, quality_max):
input_file = fspath(input_file)
output_file = fspath(output_file)
if input_file.endswith('.jpg'):
with Image.open(input_file) as im, NamedTemporaryFile(suffix='.png') as tmp:
im.save(tmp)
args = [
'pngquant',
'--force',
'--skip-if-larger',
'--output',
output_file,
'--quality',
f'{quality_min}-{quality_max}',
'--',
tmp.name,
]
run(args)
else:
args = [
'pngquant',
'--force',
'--skip-if-larger',
'--output',
output_file,
'--quality',
f'{quality_min}-{quality_max}',
'--',
input_file,
]
run(args)
+335
View File
@@ -0,0 +1,335 @@
# © 2017 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/.
"""Interface to Tesseract executable"""
import logging
import os
import shutil
from collections import namedtuple
from os import fspath
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError, TimeoutExpired
from typing import List
from PIL import Image
from ocrmypdf.exceptions import (
MissingDependencyError,
SubprocessOutputError,
TesseractConfigError,
)
from ocrmypdf.subprocess import get_version, run
log = logging.getLogger(__name__)
OrientationConfidence = namedtuple('OrientationConfidence', ('angle', 'confidence'))
HOCR_TEMPLATE = """<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
<meta name='ocr-system' content='tesseract 4.0.0' />
<meta name='ocr-capabilities' content='ocr_page ocr_carea ocr_par ocr_line ocrx_word ocrp_wconf'/>
</head>
<body>
<div class='ocr_page' id='page_1' title='image "_blank.png"; bbox 0 0 {0} {1}; ppageno 0'>
</div>
</body>
</html>
"""
class TesseractLoggerAdapter(logging.LoggerAdapter):
def process(self, msg, kwargs):
kwargs['extra'] = self.extra
return '[tesseract] %s' % (msg), kwargs
def version():
return get_version('tesseract', regex=r'tesseract\s(.+)')
def has_textonly_pdf(langs=None):
"""Does Tesseract have textonly_pdf capability?
Available in v4.00.00alpha since January 2017. Best to
parse the parameter list.
"""
args_tess = tess_base_args(langs, engine_mode=None) + ['--print-parameters', 'pdf']
params = ''
try:
proc = run(args_tess, check=True, stdout=PIPE, stderr=STDOUT)
params = proc.stdout
except CalledProcessError as e:
raise MissingDependencyError(
"Could not --print-parameters from tesseract. This can happen if the "
"TESSDATA_PREFIX environment is not set to a valid tessdata folder. "
) from e
if b'textonly_pdf' in params:
return True
return False
def has_user_words():
"""Does Tesseract have --user-words capability?
Not available in 4.0, but available in 4.1. Also available in 3.x, but
we no longer support 3.x.
"""
return version() >= '4.1'
def get_languages():
def lang_error(output):
msg = (
"Tesseract failed to report available languages.\n"
"Output from Tesseract:\n"
"-----------\n"
)
msg += output
return msg
args_tess = ['tesseract', '--list-langs']
try:
proc = run(
args_tess, universal_newlines=True, stdout=PIPE, stderr=STDOUT, check=True
)
output = proc.stdout
except CalledProcessError as e:
raise MissingDependencyError(lang_error(e.output)) from e
for line in output.splitlines():
if line.startswith('Error'):
raise MissingDependencyError(lang_error(output))
_header, *rest = output.splitlines()
return set(lang.strip() for lang in rest)
def tess_base_args(langs: List[str], engine_mode: int) -> List[str]:
args = ['tesseract']
if langs:
args.extend(['-l', '+'.join(langs)])
if engine_mode is not None:
args.extend(['--oem', str(engine_mode)])
return args
def get_orientation(input_file: Path, engine_mode: int, timeout: float):
args_tesseract = tess_base_args(['osd'], engine_mode) + [
'--psm',
'0',
fspath(input_file),
'stdout',
]
try:
p = run(args_tesseract, stdout=PIPE, stderr=STDOUT, timeout=timeout, check=True)
stdout = p.stdout
except TimeoutExpired:
return OrientationConfidence(angle=0, confidence=0.0)
except CalledProcessError as e:
tesseract_log_output(e.stdout)
tesseract_log_output(e.stderr)
if (
b'Too few characters. Skipping this page' in e.output
or b'Image too large' in e.output
):
return OrientationConfidence(0, 0)
raise SubprocessOutputError() from e
else:
osd = {}
for line in stdout.decode().splitlines():
line = line.strip()
parts = line.split(':', maxsplit=2)
if len(parts) == 2:
osd[parts[0].strip()] = parts[1].strip()
angle = int(osd.get('Orientation in degrees', 0))
oc = OrientationConfidence(
angle=angle, confidence=float(osd.get('Orientation confidence', 0))
)
return oc
def tesseract_log_output(stream):
tlog = TesseractLoggerAdapter(
log, extra=log.extra if hasattr(log, 'extra') else None
)
if not stream:
return
try:
text = stream.decode()
except UnicodeDecodeError:
text = stream.decode('utf-8', 'ignore')
lines = text.splitlines()
for line in lines:
if line.startswith("Tesseract Open Source"):
continue
elif line.startswith("Warning in pixReadMem"):
continue
elif 'diacritics' in line:
tlog.warning("lots of diacritics - possibly poor OCR")
elif line.startswith('OSD: Weak margin'):
tlog.warning("unsure about page orientation")
elif 'Error in pixScanForForeground' in line:
pass # Appears to be spurious/problem with nonwhite borders
elif 'Error in boxClipToRectangle' in line:
pass # Always appears with pixScanForForeground message
elif 'parameter not found: ' in line.lower():
tlog.error(line.strip())
problem = line.split('found: ')[1]
raise TesseractConfigError(problem)
elif 'error' in line.lower() or 'exception' in line.lower():
tlog.error(line.strip())
elif 'warning' in line.lower():
tlog.warning(line.strip())
elif 'read_params_file' in line.lower():
tlog.error(line.strip())
else:
tlog.info(line.strip())
def page_timedout(timeout):
if timeout == 0:
return
log.warning("[tesseract] took too long to OCR - skipping")
def _generate_null_hocr(output_hocr, output_text, image):
"""Produce a .hocr file that reports no text detected on a page that is
the same size as the input image."""
with Image.open(image) as im:
w, h = im.size
output_hocr.write_text(HOCR_TEMPLATE.format(w, h), encoding='utf-8')
output_text.write_text('[skipped page]', encoding='utf-8')
def generate_hocr(
input_file: Path,
output_hocr: Path,
output_text: Path,
languages: List[str],
engine_mode: int,
tessconfig: List[str],
timeout: float,
pagesegmode: int,
user_words,
user_patterns,
):
prefix = output_hocr.with_suffix('')
args_tesseract = tess_base_args(languages, engine_mode)
if pagesegmode is not None:
args_tesseract.extend(['--psm', str(pagesegmode)])
if user_words:
args_tesseract.extend(['--user-words', user_words])
if user_patterns:
args_tesseract.extend(['--user-patterns', user_patterns])
# Reminder: test suite tesseract test plugins will break after any changes
# to the number of order parameters here
args_tesseract.extend([input_file, prefix, 'hocr', 'txt'] + tessconfig)
try:
p = run(args_tesseract, stdout=PIPE, stderr=STDOUT, timeout=timeout, check=True)
stdout = p.stdout
except TimeoutExpired:
# Generate a HOCR file with no recognized text if tesseract times out
# Temporary workaround to hocrTransform not being able to function if
# it does not have a valid hOCR file.
page_timedout(timeout)
_generate_null_hocr(output_hocr, output_text, input_file)
except CalledProcessError as e:
tesseract_log_output(e.output)
if b'Image too large' in e.output:
_generate_null_hocr(output_hocr, output_text, input_file)
return
raise SubprocessOutputError() from e
else:
tesseract_log_output(stdout)
# The sidecar text file will get the suffix .txt; rename it to
# whatever caller wants it named
if prefix.with_suffix('.txt').exists():
shutil.move(prefix.with_suffix('.txt'), output_text)
def use_skip_page(output_pdf, output_text):
output_text.write_text('[skipped page]', encoding='utf-8')
# A 0 byte file to the output to indicate a skip
output_pdf.write_bytes(b'')
def generate_pdf(
*,
input_file: Path,
output_pdf: Path,
output_text: Path,
languages: List[str],
engine_mode: int,
tessconfig: List[str],
timeout: float,
pagesegmode: int,
user_words,
user_patterns,
):
"""Use Tesseract to render a PDF.
input_file -- image to analyze
output_pdf -- file to generate
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)
if pagesegmode is not None:
args_tesseract.extend(['--psm', str(pagesegmode)])
args_tesseract.extend(['-c', 'textonly_pdf=1'])
if user_words:
args_tesseract.extend(['--user-words', user_words])
if user_patterns:
args_tesseract.extend(['--user-patterns', user_patterns])
prefix = os.path.splitext(output_pdf)[0] # Tesseract appends suffixes
# Reminder: test suite tesseract test plugins might break after any changes
# to the number of order parameters here
args_tesseract.extend([input_file, prefix, 'pdf', 'txt'] + tessconfig)
try:
p = run(args_tesseract, stdout=PIPE, stderr=STDOUT, timeout=timeout, check=True)
stdout = p.stdout
if os.path.exists(prefix + '.txt'):
shutil.move(prefix + '.txt', output_text)
except TimeoutExpired:
page_timedout(timeout)
use_skip_page(output_pdf, output_text)
except CalledProcessError as e:
tesseract_log_output(e.output)
if b'Image too large' in e.output:
use_skip_page(output_pdf, output_text)
return
raise SubprocessOutputError() from e
else:
tesseract_log_output(stdout)
+129
View File
@@ -0,0 +1,129 @@
# © 2015 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/.
# unpaper documentation:
# https://github.com/Flameeyes/unpaper/blob/master/doc/basic-concepts.md
"""Interface to unpaper executable"""
import logging
import os
import shlex
from pathlib import Path
from subprocess import PIPE, STDOUT, CalledProcessError
from tempfile import TemporaryDirectory
from typing import Tuple
from PIL import Image
from ocrmypdf.exceptions import MissingDependencyError, SubprocessOutputError
from ocrmypdf.subprocess import get_version
from ocrmypdf.subprocess import run as external_run
log = logging.getLogger(__name__)
def version():
return get_version('unpaper')
def _setup_unpaper_io(tmpdir: Path, input_file: Path) -> Tuple[Path, Path]:
SUFFIXES = {'1': '.pbm', 'L': '.pgm', 'RGB': '.ppm'}
with Image.open(input_file) as im:
im_modified = False
if im.mode not in SUFFIXES:
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 IOError as e:
raise MissingDependencyError(
"Could not convert image with type " + im.mode
) from e
else:
im_modified = True
try:
suffix = SUFFIXES[im.mode]
except KeyError:
raise MissingDependencyError(
"Failed to convert image to a supported format."
) from e
if im_modified or input_file.suffix != '.png':
input_png = tmpdir / 'input.png'
im.save(input_png, format='PNG', compress_level=1)
else:
# No changes, PNG input, just use the file we already have
input_png = input_file
output_pnm = tmpdir / f'output{suffix}'
return input_png, output_pnm
def run(input_file, output_file, dpi, mode_args):
args_unpaper = ['unpaper', '-v', '--dpi', str(dpi)] + mode_args
with TemporaryDirectory() as tmpdir:
input_png, output_pnm = _setup_unpaper_io(Path(tmpdir), input_file)
# To prevent any shenanigans from accepting arbitrary parameters in
# --unpaper-args, we:
# 1) run with cwd set to a tmpdir with only unpaper's files
# 2) forbid the use of '/' in arguments, to prevent changing paths
# 3) append absolute paths for the input and output file
# This should ensure that a user cannot clobber some other file with
# their unpaper arguments (whether intentionally or otherwise)
args_unpaper.extend([os.fspath(input_png), os.fspath(output_pnm)])
try:
proc = external_run(
args_unpaper,
check=True,
close_fds=True,
universal_newlines=True,
stderr=STDOUT, # unpaper writes logging output to stdout and stderr
cwd=tmpdir, # and cannot send file output to stdout
stdout=PIPE,
)
except CalledProcessError as e:
log.debug(e.stderr)
raise e from e
else:
log.debug(proc.stderr)
try:
with Image.open(output_pnm) as imout:
imout.save(output_file, dpi=(dpi, dpi))
except (FileNotFoundError, OSError):
raise SubprocessOutputError(
"unpaper: failed to produce the expected output file. "
+ " Called with: "
+ str(args_unpaper)
) from None
def validate_custom_args(args: str):
unpaper_args = shlex.split(args)
if any('/' in arg for arg in unpaper_args):
raise ValueError('No filenames allowed in --unpaper-args')
return unpaper_args
def clean(input_file, output_file, dpi, unpaper_args=None):
default_args = [
'--layout',
'none',
'--mask-scan-size',
'100', # don't blank out narrow columns
'--no-border-align', # don't align visible content to borders
'--no-mask-center', # don't center visible content within page
'--no-grayfilter', # don't remove light gray areas
'--no-blackfilter', # don't remove solid black areas
'--no-deskew', # don't deskew
]
if not unpaper_args:
unpaper_args = default_args
run(input_file, output_file, dpi, unpaper_args)
+291
View File
@@ -0,0 +1,291 @@
# © 2018 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 logging
from contextlib import suppress
from pathlib import Path
from typing import Optional
import pikepdf
log = logging.getLogger(__name__)
MAX_REPLACE_PAGES = 100
def _update_page_resources(*, page, font, font_key, procset):
"""Update this page's fonts with a reference to the Glyphless font"""
if '/Resources' not in page:
page['/Resources'] = pikepdf.Dictionary({})
resources = page['/Resources']
try:
fonts = resources['/Font']
except KeyError:
fonts = pikepdf.Dictionary({})
if font_key is not None and font_key not in fonts:
fonts[font_key] = font
resources['/Font'] = fonts
# Reassign /ProcSet to one that just lists everything - ProcSet is
# obsolete and doesn't matter but recommended for old viewer support
resources['/ProcSet'] = procset
def strip_invisible_text(pdf, page):
stream = []
in_text_obj = False
render_mode = 0
text_objects = []
page.page_contents_coalesce()
for operands, operator in pikepdf.parse_content_stream(page, ''):
if not in_text_obj:
if operator == pikepdf.Operator('BT'):
in_text_obj = True
render_mode = 0
text_objects.append((operands, operator))
else:
stream.append((operands, operator))
else:
if operator == pikepdf.Operator('Tr'):
render_mode = operands[0]
text_objects.append((operands, operator))
if operator == pikepdf.Operator('ET'):
in_text_obj = False
if render_mode != 3:
stream.extend(text_objects)
text_objects.clear()
def convert(op):
try:
return op.unparse()
except AttributeError:
return str(op).encode('ascii')
lines = []
for operands, operator in stream:
if operator == pikepdf.Operator('INLINE IMAGE'):
iim = operands[0]
line = iim.unparse()
else:
line = b' '.join(convert(op) for op in operands) + b' ' + operator.unparse()
lines.append(line)
content_stream = b'\n'.join(lines)
page.Contents = pikepdf.Stream(pdf, content_stream)
class OcrGrafter:
def __init__(self, context):
self.context = context
self.path_base = context.origin
self.pdf_base = pikepdf.open(self.path_base)
self.font, self.font_key = None, None
self.pdfinfo = context.pdfinfo
self.output_file = context.get_path('graft_layers.pdf')
self.procset = self.pdf_base.make_indirect(
pikepdf.Object.parse(b'[ /PDF /Text /ImageB /ImageC /ImageI ]')
)
self.emplacements = 1
self.interim_count = 0
def graft_page(
self,
*,
pageno: int,
image: Optional[Path],
textpdf: Optional[Path],
autorotate_correction: int,
):
if textpdf and not self.font:
self.font, self.font_key = self._find_font(textpdf)
emplaced_page = False
content_rotation = self.pdfinfo[pageno].rotation
path_image = Path(image).resolve() if image else None
if path_image is not None and path_image != self.path_base:
# We are updating the old page with a rasterized PDF of the new
# page (without changing objgen, to preserve references)
log.debug("Emplacement update")
with pikepdf.open(image) as pdf_image:
self.emplacements += 1
foreign_image_page = pdf_image.pages[0]
self.pdf_base.pages.append(foreign_image_page)
local_image_page = self.pdf_base.pages[-1]
self.pdf_base.pages[pageno].emplace(local_image_page)
del self.pdf_base.pages[-1]
emplaced_page = True
if emplaced_page:
content_rotation = autorotate_correction
text_rotation = autorotate_correction
text_misaligned = (text_rotation - content_rotation) % 360
log.debug(
f"Rotations for page {pageno}: [text, auto, misalign, content] = "
f"{text_rotation}, {autorotate_correction}, "
f"{text_misaligned}, {content_rotation}"
)
if textpdf and self.font:
# Graft the text layer onto this page, whether new or old
strip_old = self.context.options.redo_ocr
self._graft_text_layer(
page_num=pageno + 1,
textpdf=textpdf,
font=self.font,
font_key=self.font_key,
rotation=text_misaligned,
procset=self.procset,
strip_old_text=strip_old,
)
# Correct the rotation if applicable
self.pdf_base.pages[pageno].Rotate = (
content_rotation - autorotate_correction
) % 360
if self.emplacements % MAX_REPLACE_PAGES == 0:
self.save_and_reload()
def save_and_reload(self):
"""Save and reload the Pdf.
This will keep a lid on our memory usage for very large files. Attach
the font to page 1 even if page 1 doesn't use it, so we have a way to get it
back.
"""
page0 = self.pdf_base.pages[0]
_update_page_resources(
page=page0, font=self.font, font_key=self.font_key, procset=self.procset
)
# We cannot read and write the same file, that will corrupt it
# but we don't to keep more copies than we need to. Delete intermediates.
# {interim_count} is the opened file we were updateing
# {interim_count - 1} can be deleted
# {interim_count + 1} is the new file will produce and open
old_file = self.output_file.with_suffix(f'.working{self.interim_count - 1}.pdf')
if not self.context.options.keep_temporary_files:
with suppress(FileNotFoundError):
old_file.unlink()
next_file = self.output_file.with_suffix(
f'.working{self.interim_count + 1}.pdf'
)
self.pdf_base.save(next_file)
self.pdf_base.close()
self.pdf_base = pikepdf.open(next_file)
self.procset = self.pdf_base.pages[0].Resources.ProcSet
self.font, self.font_key = None, None # Ensure we reacquire this information
self.interim_count += 1
def finalize(self):
self.pdf_base.save(self.output_file)
self.pdf_base.close()
return self.output_file
def _find_font(self, text):
"""Copy a font from the filename text into pdf_base"""
font, font_key = None, None
possible_font_names = ('/f-0-0', '/F1')
try:
with pikepdf.open(text) as pdf_text:
try:
pdf_text_fonts = pdf_text.pages[0].Resources.get('/Font', {})
except (AttributeError, IndexError, KeyError):
return None, None
for f in possible_font_names:
pdf_text_font = pdf_text_fonts.get(f, None)
if pdf_text_font is not None:
font_key = f
break
if pdf_text_font:
font = self.pdf_base.copy_foreign(pdf_text_font)
return font, font_key
except (FileNotFoundError, pikepdf.PdfError):
# PdfError occurs if a 0-length file is written e.g. due to OCR timeout
return None, None
def _graft_text_layer(
self,
*,
page_num: int,
textpdf: Path,
font: pikepdf.Object,
font_key: pikepdf.Object,
procset: pikepdf.Object,
rotation: int,
strip_old_text: bool,
):
"""Insert the text layer from text page 0 on to pdf_base at page_num"""
log.debug("Grafting")
if Path(textpdf).stat().st_size == 0:
return
# This is a pointer indicating a specific page in the base file
with pikepdf.open(textpdf) as pdf_text:
pdf_text_contents = pdf_text.pages[0].Contents.read_bytes()
base_page = self.pdf_base.pages.p(page_num)
# The text page always will be oriented up by this stage but the original
# content may have a rotation applied. Wrap the text stream with a rotation
# so it will be oriented the same way as the rest of the page content.
# (Previous versions OCRmyPDF rotated the content layer to match the text.)
mediabox = [float(pdf_text.pages[0].MediaBox[v]) for v in range(4)]
wt, ht = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1]
mediabox = [float(base_page.MediaBox[v]) for v in range(4)]
wp, hp = mediabox[2] - mediabox[0], mediabox[3] - mediabox[1]
translate = pikepdf.PdfMatrix().translated(-wt / 2, -ht / 2)
untranslate = pikepdf.PdfMatrix().translated(wp / 2, hp / 2)
corner = pikepdf.PdfMatrix().translated(mediabox[0], mediabox[1])
# -rotation because the input is a clockwise angle and this formula
# uses CCW
rotation = -rotation % 360
rotate = pikepdf.PdfMatrix().rotated(rotation)
# Because of rounding of DPI, we might get a text layer that is not
# identically sized to the target page. Scale to adjust. Normally this
# is within 0.998.
if rotation in (90, 270):
wt, ht = ht, wt
scale_x = wp / wt
scale_y = hp / ht
# log.debug('%r', scale_x, scale_y)
scale = pikepdf.PdfMatrix().scaled(scale_x, scale_y)
# Translate the text so it is centered at (0, 0), rotate it there, adjust
# for a size different between initial and text PDF, then untranslate, and
# finally move the lower left corner to match the mediabox
ctm = translate @ rotate @ scale @ untranslate @ corner
pdf_text_contents = (
b'q %s cm\n' % ctm.encode() + pdf_text_contents + b'\nQ\n'
)
new_text_layer = pikepdf.Stream(self.pdf_base, pdf_text_contents)
if strip_old_text:
strip_invisible_text(self.pdf_base, base_page)
base_page.page_contents_add(new_text_layer, prepend=True)
_update_page_resources(
page=base_page, font=font, font_key=font_key, procset=procset
)
+79
View File
@@ -0,0 +1,79 @@
# © 2018 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 shutil
import sys
from argparse import Namespace
from copy import copy
from io import IOBase
from pathlib import Path
from typing import Iterator
from ocrmypdf.pdfinfo import PdfInfo
class PdfContext:
"""Holds our context for a particular run of the pipeline"""
def __init__(
self,
options: Namespace,
work_folder: Path,
origin: Path,
pdfinfo: PdfInfo,
plugin_manager,
):
self.options = options
self.work_folder = work_folder
self.origin = origin
self.pdfinfo = pdfinfo
self.plugin_manager = plugin_manager
def get_path(self, name: str) -> Path:
return self.work_folder / name
def get_page_contexts(self) -> Iterator['PageContext']:
npages = len(self.pdfinfo)
for n in range(npages):
yield PageContext(self, n)
class PageContext:
"""Holds our context for a page
Must be pickable, so stores only intrinsic/simple data elements or those
capable of their serializing themselves via __getstate__.
"""
def __init__(self, pdf_context: PdfContext, pageno):
self.work_folder = pdf_context.work_folder
self.origin = pdf_context.origin
self.options = pdf_context.options
self.pageno = pageno
self.pageinfo = pdf_context.pdfinfo[pageno]
self.plugin_manager = pdf_context.plugin_manager
def get_path(self, name: str) -> Path:
return self.work_folder / ("%06d_%s" % (self.pageno + 1, name))
def __getstate__(self):
state = self.__dict__.copy()
state['options'] = copy(self.options)
if not isinstance(state['options'].input_file, (str, bytes, os.PathLike)):
state['options'].input_file = 'stream'
if not isinstance(state['options'].output_file, (str, bytes, os.PathLike)):
state['options'].output_file = 'stream'
return state
def cleanup_working_files(work_folder: Path, options: Namespace):
if options.keep_temporary_files:
print(f"Temporary working files retained at:\n{work_folder}", file=sys.stderr)
else:
shutil.rmtree(work_folder, ignore_errors=True)
+50
View File
@@ -0,0 +1,50 @@
# © 2020 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 logging
import sys
from contextlib import suppress
from tqdm import tqdm
class PageNumberFilter(logging.Filter):
def filter(self, record):
pageno = getattr(record, 'pageno', None)
if isinstance(pageno, int):
record.pageno = f'{pageno:5d} '
elif pageno is None:
record.pageno = ''
return True
class TqdmConsole:
"""Wrapper to log messages in a way that is compatible with tqdm progress bar
This routes log messages through tqdm so that it can print them above the
progress bar, and then refresh the progress bar, rather than overwriting
it which looks messy.
For some reason Python 3.6 prints extra empty messages from time to time,
so we suppress those.
"""
def __init__(self, file):
self.file = file
self.py36 = sys.version_info[0:2] == (3, 6)
def write(self, msg):
# When no progress bar is active, tqdm.write() routes to print()
if self.py36:
if msg.strip() != '':
tqdm.write(msg.rstrip(), end='\n', file=self.file)
else:
tqdm.write(msg.rstrip(), end='\n', file=self.file)
def flush(self):
with suppress(AttributeError):
self.file.flush()
+828
View File
@@ -0,0 +1,828 @@
# © 2016 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 logging
import os
import re
import sys
from contextlib import suppress
from datetime import datetime, timezone
from pathlib import Path
from shutil import copyfileobj
from typing import BinaryIO, Dict, Iterable, Optional, Union, cast
import img2pdf
import pikepdf
from pikepdf.models.metadata import encode_pdf_date
from PIL import Image, ImageColor, ImageDraw
from ocrmypdf import leptonica
from ocrmypdf._exec import unpaper
from ocrmypdf._jobcontext import PageContext, PdfContext
from ocrmypdf._version import PROGRAM_NAME
from ocrmypdf._version import __version__ as VERSION
from ocrmypdf.exceptions import (
DpiError,
EncryptedPdfError,
InputFileError,
PriorOcrFoundError,
UnsupportedImageFormatError,
)
from ocrmypdf.helpers import Resolution, safe_symlink
from ocrmypdf.hocrtransform import HocrTransform
from ocrmypdf.optimize import optimize
from ocrmypdf.pdfa import generate_pdfa_ps
from ocrmypdf.pdfinfo import Colorspace, Encoding, PdfInfo
log = logging.getLogger(__name__)
VECTOR_PAGE_DPI = 400
def triage_image_file(input_file, output_file, options):
log.info("Input file is not a PDF, checking if it is an image...")
try:
im = Image.open(input_file)
except EnvironmentError as e:
# Recover the original filename
log.error(str(e).replace(str(input_file), str(options.input_file)))
raise UnsupportedImageFormatError() from e
with im:
log.info("Input file is an image")
if 'dpi' in im.info:
if im.info['dpi'] <= (96, 96) and not options.image_dpi:
log.info("Image size: (%d, %d)", *im.size)
log.info("Image resolution: (%d, %d)", *im.info['dpi'])
log.error(
"Input file is an image, but the resolution (DPI) is "
"not credible. Estimate the resolution at which the "
"image was scanned and specify it using --image-dpi."
)
raise DpiError()
elif not options.image_dpi:
log.info("Image size: (%d, %d)", *im.size)
log.error(
"Input file is an image, but has no resolution (DPI) "
"in its metadata. Estimate the resolution at which "
"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")
elif im.mode == 'CMYK':
log.error("Input CMYK image has no ICC profile, not usable")
raise UnsupportedImageFormatError()
try:
log.info("Image seems valid. Try converting to PDF...")
layout_fun = img2pdf.default_layout_fun
if options.image_dpi:
layout_fun = img2pdf.get_fixed_dpi_layout_fun(
Resolution(options.image_dpi, options.image_dpi)
)
with open(output_file, 'wb') as outf:
img2pdf.convert(
os.fspath(input_file),
layout_fun=layout_fun,
with_pdfrw=False,
outputstream=outf,
)
log.info("Successfully converted to PDF, processing...")
except img2pdf.ImageOpenError as e:
log.error(e)
raise UnsupportedImageFormatError() from e
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.
"""
with open(input_file, 'rb') as f:
signature = f.read(search_window)
m = re.search(br'%PDF-(\d\.\d)', signature)
if m:
return m.group(1)
return ''
def triage(original_filename, input_file, output_file, options):
try:
if _pdf_guess_version(input_file):
if options.image_dpi:
log.warning(
"Argument --image-dpi is being ignored because the "
"input file is a PDF, not an image."
)
# Origin file is a pdf create a symlink with pdf extension
safe_symlink(input_file, output_file)
return output_file
except EnvironmentError as e:
log.debug(f"Temporary file was at: {input_file}")
msg = str(e).replace(str(input_file), original_filename)
raise InputFileError(msg) from e
triage_image_file(input_file, output_file, options)
return output_file
def get_pdfinfo(
input_file,
detailed_analysis=False,
progbar=False,
max_workers=None,
check_pages=None,
):
try:
return PdfInfo(
input_file,
detailed_analysis=detailed_analysis,
progbar=progbar,
max_workers=max_workers,
check_pages=check_pages,
)
except pikepdf.PasswordError:
raise EncryptedPdfError()
except pikepdf.PdfError:
raise InputFileError()
def validate_pdfinfo_options(context: PdfContext):
pdfinfo = context.pdfinfo
options = context.options
if pdfinfo.needs_rendering:
log.error(
"This PDF contains dynamic XFA forms created by Adobe LiveCycle "
"Designer and can only be read by Adobe Acrobat or Adobe Reader."
)
raise InputFileError()
if pdfinfo.has_userunit and options.output_type.startswith('pdfa'):
log.error(
"This input file uses a PDF feature that is not supported "
"by Ghostscript, so you cannot use --output-type=pdfa for this "
"file. (Specifically, it uses the PDF-1.6 /UserUnit feature to "
"support very large or small page sizes, and Ghostscript cannot "
"output these files.) Use --output-type=pdf instead."
)
raise InputFileError()
if pdfinfo.has_acroform:
if options.redo_ocr:
log.error(
"This PDF has a user fillable form. --redo-ocr is not "
"currently possible on such files."
)
raise InputFileError()
else:
log.warning(
"This PDF has a fillable form. "
"Chances are it is a pure digital "
"document that does not need OCR."
)
if not options.force_ocr:
log.info(
"Use the option --force-ocr to produce an image of the "
"form and all filled form fields. The output PDF will be "
"'flattened' and will no longer be fillable."
)
context.plugin_manager.hook.validate(pdfinfo=pdfinfo, options=options)
def get_page_dpi(pageinfo, options):
"Get the DPI when nonsquare DPI is tolerable"
xres = max(
pageinfo.dpi.x or VECTOR_PAGE_DPI,
options.oversample or 0.0,
VECTOR_PAGE_DPI if pageinfo.has_vector else 0.0,
)
yres = max(
pageinfo.dpi.y or VECTOR_PAGE_DPI,
options.oversample or 0,
VECTOR_PAGE_DPI if pageinfo.has_vector else 0.0,
)
return Resolution(float(xres), float(yres))
def get_page_square_dpi(pageinfo, options) -> Resolution:
"Get the DPI when we require xres == yres, scaled to physical units"
xres = pageinfo.dpi.x or 0.0
yres = pageinfo.dpi.y or 0.0
userunit = float(pageinfo.userunit) or 1.0
units = float(
max(
(xres * userunit) or VECTOR_PAGE_DPI,
(yres * userunit) or VECTOR_PAGE_DPI,
VECTOR_PAGE_DPI if pageinfo.has_vector else 0.0,
options.oversample or 0.0,
)
)
return Resolution(units, units)
def get_canvas_square_dpi(pageinfo, options) -> Resolution:
"""Get the DPI when we require xres == yres, in Postscript units"""
units = float(
max(
(pageinfo.dpi.x) or VECTOR_PAGE_DPI,
(pageinfo.dpi.y) or VECTOR_PAGE_DPI,
VECTOR_PAGE_DPI if pageinfo.has_vector else 0.0,
options.oversample or 0.0,
)
)
return Resolution(units, units)
def is_ocr_required(page_context: PageContext):
pageinfo = page_context.pageinfo
options = page_context.options
ocr_required = True
if options.pages and pageinfo.pageno not in options.pages:
log.debug(f"skipped {pageinfo.pageno} as requested by --pages {options.pages}")
ocr_required = False
elif pageinfo.has_text:
if not options.force_ocr and not (options.skip_text or options.redo_ocr):
raise PriorOcrFoundError(
"page already has text! - aborting (use --force-ocr to force OCR; "
" see also help for the arguments --skip-text and --redo-ocr"
)
elif options.force_ocr:
log.info("page already has text! - rasterizing text and running OCR anyway")
ocr_required = True
elif options.redo_ocr:
if pageinfo.has_corrupt_text:
log.warning(
"some text on this page cannot be mapped to characters: "
"consider using --force-ocr instead"
)
else:
log.info("redoing OCR")
ocr_required = True
elif options.skip_text:
log.info("skipping all processing on this page")
ocr_required = False
elif not pageinfo.images and not options.lossless_reconstruction:
# 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
# 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.
if options.force_ocr and options.oversample:
# The user really wants to reprocess this file
log.info(
"page has no images - "
f"rasterizing at {options.oversample} DPI because "
"--force-ocr --oversample was specified"
)
elif options.force_ocr:
# Warn the user they might not want to do this
log.warning(
"page has no images - "
"all vector content will be "
f"rasterized at {VECTOR_PAGE_DPI} DPI, losing some resolution and likely "
"increasing file size. Use --oversample to adjust the "
"DPI."
)
else:
log.info(
"page has no images - "
"skipping all processing on this page to avoid losing detail. "
"Use --force-ocr if you wish to perform OCR on pages that "
"have vector content."
)
ocr_required = False
if ocr_required and options.skip_big and pageinfo.images:
pixel_count = pageinfo.width_pixels * pageinfo.height_pixels
if pixel_count > (options.skip_big * 1_000_000):
ocr_required = False
log.warning(
"page too big, skipping OCR "
f"({(pixel_count / 1_000_000):.1f} MPixels > {options.skip_big:.1f} MPixels --skip-big)"
)
return ocr_required
def rasterize_preview(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('rasterize_preview.jpg')
canvas_dpi = get_canvas_square_dpi(page_context.pageinfo, page_context.options)
page_dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
page_context.plugin_manager.hook.rasterize_pdf_page(
input_file=input_file,
output_file=output_file,
raster_device='jpeggray',
raster_dpi=canvas_dpi,
page_dpi=page_dpi,
pageno=page_context.pageinfo.pageno + 1,
)
return output_file
def describe_rotation(page_context: PageContext, orient_conf, correction: int):
"""
Describe the page rotation we are going to perform.
"""
direction = {0: '', 90: '', 180: '', 270: ''}
turns = {0: ' ', 90: '', 180: '', 270: ''}
existing_rotation = page_context.pageinfo.rotation
action = ''
if orient_conf.confidence >= page_context.options.rotate_pages_threshold:
if correction != 0:
action = 'will rotate ' + turns[correction]
else:
action = 'rotation appears correct'
else:
if correction != 0:
action = 'confidence too low to rotate'
else:
action = 'no change'
facing = ''
if existing_rotation != 0:
facing = f"with existing rotation {direction.get(existing_rotation, '?')}, "
facing += f"page is facing {direction.get(orient_conf.angle, '?')}"
return f"{facing}, confidence {orient_conf.confidence:.2f} - {action}"
def get_orientation_correction(preview: Path, page_context: PageContext):
"""Work out orientation correct for each page.
We ask Ghostscript to draw a preview page, which will rasterize with the
current /Rotate applied, and then ask OCR which way the page is
oriented. If the value of /Rotate is correct (e.g., a user already
manually fixed rotation), then OCR will say the page is pointing
up and the correction is zero. Otherwise, the orientation found by
OCR represents the clockwise rotation, or the counterclockwise
correction to rotation.
When we draw the real page for OCR, we rotate it by the CCW correction,
which points it (hopefully) upright. _graft.py takes care of the orienting
the image and text layers.
"""
orient_conf = page_context.plugin_manager.hook.get_ocr_engine().get_orientation(
preview, page_context.options
)
correction = orient_conf.angle % 360
log.info(describe_rotation(page_context, orient_conf, correction))
if (
orient_conf.confidence >= page_context.options.rotate_pages_threshold
and correction != 0
):
return correction
return 0
def rasterize(
input_file: Path,
page_context: PageContext,
correction: int = 0,
output_tag: str = '',
remove_vectors=None,
):
colorspaces = ['pngmono', 'pnggray', 'png256', 'png16m']
device_idx = 0
if remove_vectors is None:
remove_vectors = page_context.options.remove_vectors
output_file = page_context.get_path(f'rasterize{output_tag}.png')
pageinfo = page_context.pageinfo
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')
if pageinfo.has_vector:
device_idx = at_least('png16m')
device = colorspaces[device_idx]
log.debug(f"Rasterize with {device}")
# Produce the page image with square resolution or else deskew and OCR
# will not work properly.
canvas_dpi = get_canvas_square_dpi(pageinfo, page_context.options)
page_dpi = get_page_square_dpi(pageinfo, page_context.options)
page_context.plugin_manager.hook.rasterize_pdf_page(
input_file=input_file,
output_file=output_file,
raster_device=device,
raster_dpi=canvas_dpi,
page_dpi=page_dpi,
pageno=pageinfo.pageno + 1,
rotation=correction,
filter_vector=remove_vectors,
)
return output_file
def preprocess_remove_background(input_file: Path, page_context: PageContext):
if any(image.bpc > 1 for image in page_context.pageinfo.images):
output_file = page_context.get_path('pp_rm_bg.png')
leptonica.remove_background(input_file, output_file)
return output_file
else:
log.info("background removal skipped on mono page")
return input_file
def preprocess_deskew(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('pp_deskew.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
leptonica.deskew(input_file, output_file, dpi.x)
return output_file
def preprocess_clean(input_file: Path, page_context: PageContext):
output_file = page_context.get_path('pp_clean.png')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
unpaper.clean(input_file, output_file, dpi.x, page_context.options.unpaper_args)
return output_file
def create_ocr_image(image: Path, page_context: PageContext):
"""Create the image we send for OCR. May not be the same as the display
image depending on preprocessing. This image will never be shown to the
user."""
output_file = page_context.get_path('ocr.png')
options = page_context.options
with Image.open(image) as im:
white = ImageColor.getcolor('#ffffff', im.mode)
# pink = ImageColor.getcolor('#ff0080', im.mode)
draw = ImageDraw.ImageDraw(im)
log.debug('resolution %r', im.info['dpi'])
if not options.force_ocr:
# Do not mask text areas when forcing OCR, because we need to OCR
# all text areas
mask = None # Exclude both visible and invisible text from OCR
if options.redo_ocr:
mask = True # Mask visible text, but not invisible text
for textarea in page_context.pageinfo.get_textareas(
visible=mask, corrupt=None
):
# Calculate resolution based on the image size and page dimensions
# without regard whatever resolution is in pageinfo (may differ or
# be None)
bbox = [float(v) for v in textarea]
xyscale = tuple(float(coord) / 72.0 for coord in im.info['dpi'])
pixcoords = [
bbox[0] * xyscale[0],
im.height - bbox[3] * xyscale[1],
bbox[2] * xyscale[0],
im.height - bbox[1] * xyscale[1],
]
pixcoords = [int(round(c)) for c in pixcoords]
log.debug('blanking %r', pixcoords)
draw.rectangle(pixcoords, fill=white)
# draw.rectangle(pixcoords, outline=pink)
if options.threshold:
pix = leptonica.Pix.frompil(im)
pix = pix.masked_threshold_on_background_norm()
im_pix = pix.topil()
im_pix.info['dpi'] = im.info['dpi']
im = im_pix
del draw
filter_im = page_context.plugin_manager.hook.filter_ocr_image(
page=page_context, image=im
)
if filter_im is not None:
im = filter_im
# Pillow requires integer DPI
dpi = tuple(round(coord) for coord in im.info['dpi'])
im.save(output_file, dpi=dpi)
return output_file
def ocr_engine_hocr(input_file: Path, page_context: PageContext):
hocr_out = page_context.get_path('ocr_hocr.hocr')
hocr_text_out = page_context.get_path('ocr_hocr.txt')
options = page_context.options
ocr_engine = page_context.plugin_manager.hook.get_ocr_engine()
ocr_engine.generate_hocr(
input_file=input_file,
output_hocr=hocr_out,
output_text=hocr_text_out,
options=options,
)
return (hocr_out, hocr_text_out)
def should_visible_page_image_use_jpg(pageinfo):
# If all images were JPEGs originally, produce a JPEG as output
return pageinfo.images and all(im.enc == Encoding.jpeg for im in pageinfo.images)
def create_visible_page_jpg(image: Path, page_context: PageContext) -> Path:
output_file = page_context.get_path('visible.jpg')
with Image.open(image) as im:
# At this point the image should be a .png, but deskew, unpaper
# might have removed the DPI information. In this case, fall back to
# square DPI used to rasterize. When the preview image was
# rasterized, it was also converted to square resolution, which is
# what we want to give to the OCR engine, so keep it square.
if 'dpi' in im.info:
dpi = Resolution(*im.info['dpi'])
else:
# Fallback to page-implied DPI
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
# Pillow requires integer DPI
im.save(output_file, format='JPEG', dpi=dpi.to_int())
return output_file
def create_pdf_page_from_image(image: Path, page_context: PageContext):
# We rasterize a square DPI version of each page because most image
# processing tools don't support rectangular DPI. Use the square DPI as it
# accurately describes the image. It would be possible to resample the image
# at this stage back to non-square DPI to more closely resemble the input,
# except that the hocr renderer does not understand non-square DPI. The
# sandwich renderer would be fine.
output_file = page_context.get_path('visible.pdf')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
layout_fun = img2pdf.get_fixed_dpi_layout_fun(dpi)
# This create a single page PDF
with open(image, 'rb') as imfile, open(output_file, 'wb') as pdf:
log.debug('convert')
img2pdf.convert(
imfile, with_pdfrw=False, layout_fun=layout_fun, outputstream=pdf
)
log.debug('convert done')
return output_file
def render_hocr_page(hocr: Path, page_context: PageContext):
output_file = page_context.get_path('ocr_hocr.pdf')
dpi = get_page_square_dpi(page_context.pageinfo, page_context.options)
hocrtransform = HocrTransform(hocr, dpi.x) # square
hocrtransform.to_pdf(
output_file,
image_filename=None,
show_bounding_boxes=False,
invisible_text=True,
interword_spaces=True,
)
return output_file
def ocr_engine_textonly_pdf(input_image: Path, page_context: PageContext):
output_pdf = page_context.get_path('ocr_tess.pdf')
output_text = page_context.get_path('ocr_tess.txt')
options = page_context.options
ocr_engine = page_context.plugin_manager.hook.get_ocr_engine()
ocr_engine.generate_pdf(
input_file=input_image,
output_pdf=output_pdf,
output_text=output_text,
options=options,
)
return (output_pdf, output_text)
def get_docinfo(base_pdf: pikepdf.Pdf, context: PdfContext) -> Dict[str, str]:
options = context.options
def from_document_info(key):
try:
s = base_pdf.docinfo[key]
return str(s)
except (KeyError, TypeError):
return ''
pdfmark = {
k: from_document_info(k)
for k in ('/Title', '/Author', '/Keywords', '/Subject', '/CreationDate')
}
if options is not None:
if options.title:
pdfmark['/Title'] = options.title
if options.author:
pdfmark['/Author'] = options.author
if options.keywords:
pdfmark['/Keywords'] = options.keywords
if options.subject:
pdfmark['/Subject'] = options.subject
creator_tag = context.plugin_manager.hook.get_ocr_engine().creator_tag(options)
pdfmark['/Creator'] = f'{PROGRAM_NAME} {VERSION} / {creator_tag}'
pdfmark['/Producer'] = f'pikepdf {pikepdf.__version__}'
if 'OCRMYPDF_CREATOR' in os.environ:
pdfmark['/Creator'] = os.environ['OCRMYPDF_CREATOR']
if 'OCRMYPDF_PRODUCER' in os.environ:
pdfmark['/Producer'] = os.environ['OCRMYPDF_PRODUCER']
pdfmark['/ModDate'] = encode_pdf_date(datetime.now(timezone.utc))
return pdfmark
def generate_postscript_stub(context: PdfContext):
output_file = context.get_path('pdfa.ps')
generate_pdfa_ps(output_file)
return output_file
def convert_to_pdfa(input_pdf: Path, input_ps_stub: Path, context: PdfContext):
options = context.options
input_pdfinfo = context.pdfinfo
fix_docinfo_file = context.get_path('fix_docinfo.pdf')
output_file = context.get_path('pdfa.pdf')
# If the DocumentInfo record contains NUL characters, Ghostscript will
# produce XMP metadata which contains invalid XML entities (&#0;).
# NULs in DocumentInfo seem to be common since older Acrobats included them.
# pikepdf can deal with this, but we make the world a better place by
# stamping them out as soon as possible.
modified = False
with pikepdf.open(input_pdf) as pdf_file:
try:
len(pdf_file.docinfo)
except TypeError:
log.error(
"File contains a malformed DocumentInfo block - continuing anyway"
)
else:
if pdf_file.docinfo:
for k, v in pdf_file.docinfo.items():
if b'\x00' in bytes(v):
pdf_file.docinfo[k] = bytes(v).replace(b'\x00', b'')
modified = True
if modified:
pdf_file.save(fix_docinfo_file)
else:
safe_symlink(input_pdf, fix_docinfo_file)
context.plugin_manager.hook.generate_pdfa(
pdf_version=input_pdfinfo.min_version,
pdf_pages=[fix_docinfo_file],
pdfmark=input_ps_stub,
output_file=output_file,
compression=options.pdfa_image_compression,
pdfa_part=options.output_type[-1], # is pdfa-1, pdfa-2, or pdfa-3
)
return output_file
def should_linearize(working_file: Path, context: PdfContext):
filesize = os.stat(working_file).st_size
if filesize > (context.options.fast_web_view * 1_000_000):
return True
return False
def metadata_fixup(working_file: Path, context: PdfContext):
output_file = context.get_path('metafix.pdf')
options = context.options
def report_on_metadata(missing):
if not missing:
return
if options.output_type.startswith('pdfa'):
log.warning(
"Some input metadata could not be copied because it is not "
"permitted in PDF/A. You may wish to examine the output "
"PDF's XMP metadata."
)
log.debug("The following metadata fields were not copied: %r", missing)
else:
log.error(
"Some input metadata could not be copied."
"You may wish to examine the output PDF's XMP metadata."
)
log.info("The following metadata fields were not copied: %r", missing)
with pikepdf.open(context.origin) as original, pikepdf.open(working_file) as pdf:
docinfo = get_docinfo(original, context)
with pdf.open_metadata() as meta:
meta.load_from_docinfo(docinfo, delete_missing=False, raise_failure=False)
# If xmp:CreateDate is missing, set it to the modify date to
# match Ghostscript, for consistency
if 'xmp:CreateDate' not in meta:
meta['xmp:CreateDate'] = meta.get('xmp:ModifyDate', '')
# Ghostscript likes to set title to Untitled if omitted from input.
# Reverse this, because PDF/A TechNote 0003:Metadata in PDF/A-1
# and the XMP Spec do not make this recommendation.
if meta.get('dc:title') == 'Untitled':
with original.open_metadata() as original_meta:
if 'dc:title' not in original_meta:
del meta['dc:title']
meta_original = original.open_metadata()
missing = set(meta_original.keys()) - set(meta.keys())
report_on_metadata(missing)
pdf.save(
output_file,
compress_streams=True,
preserve_pdfa=True,
object_stream_mode=pikepdf.ObjectStreamMode.generate,
linearize=( # Don't linearize if optimize() will be linearizing too
should_linearize(working_file, context)
if options.optimize == 0
else False
),
)
return output_file
def optimize_pdf(input_file: Path, context: PdfContext):
output_file = context.get_path('optimize.pdf')
save_settings = dict(
compress_streams=True,
preserve_pdfa=True,
object_stream_mode=pikepdf.ObjectStreamMode.generate,
linearize=should_linearize(input_file, context),
)
optimize(input_file, output_file, context, save_settings)
return output_file
def merge_sidecars(txt_files: Iterable[Optional[Path]], context: PdfContext):
output_file = context.get_path('sidecar.txt')
with open(output_file, 'w', encoding="utf-8") as stream:
for page_num, txt_file in enumerate(txt_files):
if page_num != 0:
stream.write('\f') # Form feed between pages
if txt_file:
with open(txt_file, 'r', encoding="utf-8") as in_:
txt = in_.read()
# Some OCR engines (e.g. Tesseract v4 alpha) add form feeds
# between pages, and some do not. For consistency, we ignore
# any added by the OCR engine and them on our own.
if txt.endswith('\f'):
stream.write(txt[:-1])
else:
stream.write(txt)
else:
stream.write(f'[OCR skipped on page {(page_num + 1)}]')
return output_file
def copy_final(input_file, output_file, _context: PdfContext):
log.debug('%s -> %s', input_file, output_file)
with open(input_file, 'rb') as input_stream:
if output_file == '-':
copyfileobj(input_stream, sys.stdout.buffer)
sys.stdout.flush()
elif hasattr(output_file, 'writable'):
output_stream = output_file
copyfileobj(input_stream, output_stream)
with suppress(AttributeError):
output_stream.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
# get the appropriate umask, ownership, etc.
with open(output_file, 'wb') as output_stream:
copyfileobj(input_stream, output_stream)
+103
View File
@@ -0,0 +1,103 @@
# © 2020 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 argparse
import importlib
import importlib.util
import sys
from functools import partial
from pathlib import Path
from typing import Callable, List, Tuple, Union
import pluggy
from ocrmypdf import pluginspec
from ocrmypdf.cli import get_parser, plugins_only_parser
class OcrmypdfPluginManager(pluggy.PluginManager):
"""pluggy.PluginManager that can fork.
Capable of reconstructing itself in child workers.
Arguments:
setup_func: callback that initializes the plugin manager with all
standard plugins
"""
def __init__(
self, *args, setup_func: Callable[[pluggy.PluginManager], None], **kwargs
):
self._init_args = args
self._setup_func = setup_func
self._init_kwargs = kwargs
super().__init__(*args, **kwargs)
setup_func(self)
def __getstate__(self):
state = dict(
_init_args=self._init_args,
_setup_func=self._setup_func,
_init_kwargs=self._init_kwargs,
)
return state
def __setstate__(self, state):
self.__init__(
*state['_init_args'],
setup_func=state['_setup_func'],
**state['_init_kwargs'],
)
def _setup_plugins(
pm: pluggy.PluginManager, plugins: List[Union[str, Path]], builtins: bool = True
):
pm.add_hookspecs(pluginspec)
all_plugins: List[Union[str, Path]] = []
if builtins:
all_plugins.extend(
[
'ocrmypdf.builtin_plugins.ghostscript',
'ocrmypdf.builtin_plugins.tesseract_ocr',
]
)
all_plugins.extend(plugins)
for name in all_plugins:
if isinstance(name, Path) or name.endswith('.py'):
# Import by filename
module_name = Path(name).stem
spec = importlib.util.spec_from_file_location(module_name, name)
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)
else:
# Import by dotted module name
module = importlib.import_module(name)
pm.register(module)
def get_plugin_manager(plugins: List[str], builtins=True):
pm = OcrmypdfPluginManager(
project_name='ocrmypdf',
setup_func=partial(_setup_plugins, plugins=plugins, builtins=builtins),
)
return pm
def get_parser_options_plugins(
args,
) -> Tuple[argparse.ArgumentParser, argparse.Namespace, pluggy.PluginManager]:
pre_options, _unused = plugins_only_parser.parse_known_args(args=args)
plugin_manager = get_plugin_manager(pre_options.plugins)
parser = get_parser()
plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member
options = parser.parse_args(args=args)
return parser, options, plugin_manager
+409
View File
@@ -0,0 +1,409 @@
# © 2016 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 logging
import logging.handlers
import os
import sys
import threading
from functools import partial
from pathlib import Path
from tempfile import mkdtemp
from typing import List, NamedTuple, Optional, Tuple
import pikepdf
import PIL
from ocrmypdf._concurrent import exec_progress_pool
from ocrmypdf._graft import OcrGrafter
from ocrmypdf._jobcontext import PageContext, PdfContext, cleanup_working_files
from ocrmypdf._logging import PageNumberFilter
from ocrmypdf._pipeline import (
convert_to_pdfa,
copy_final,
create_ocr_image,
create_pdf_page_from_image,
create_visible_page_jpg,
generate_postscript_stub,
get_orientation_correction,
get_pdfinfo,
is_ocr_required,
merge_sidecars,
metadata_fixup,
ocr_engine_hocr,
ocr_engine_textonly_pdf,
optimize_pdf,
preprocess_clean,
preprocess_deskew,
preprocess_remove_background,
rasterize,
rasterize_preview,
render_hocr_page,
should_visible_page_image_use_jpg,
triage,
validate_pdfinfo_options,
)
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf._validation import (
check_requested_output_file,
create_input_file,
report_output_file_size,
)
from ocrmypdf.exceptions import ExitCode, ExitCodeException
from ocrmypdf.helpers import (
available_cpu_count,
check_pdf,
pikepdf_enable_mmap,
samefile,
)
from ocrmypdf.pdfa import file_claims_pdfa
log = logging.getLogger(__name__)
class PageResult(NamedTuple):
pageno: int
pdf_page_from_image: Optional[Path]
ocr: Optional[Path]
text: Optional[Path]
orientation_correction: int
tls = threading.local()
tls.pageno = None
old_factory = logging.getLogRecordFactory()
def record_factory(*args, **kwargs):
record = old_factory(*args, **kwargs)
if hasattr(tls, 'pageno'):
record.pageno = tls.pageno
return record
logging.setLogRecordFactory(record_factory)
def preprocess(
page_context: PageContext,
image: Path,
remove_background: bool,
deskew: bool,
clean: bool,
) -> Path:
if remove_background:
image = preprocess_remove_background(image, page_context)
if deskew:
image = preprocess_deskew(image, page_context)
if clean:
image = preprocess_clean(image, page_context)
return image
def make_intermediate_images(
page_context: PageContext, orientation_correction: int
) -> Tuple[Path, Optional[Path]]:
options = page_context.options
ocr_image = preprocess_out = None
rasterize_out = rasterize(
page_context.origin,
page_context,
correction=orientation_correction,
remove_vectors=False,
)
if not any([options.clean, options.clean_final, options.remove_vectors]):
ocr_image = preprocess_out = preprocess(
page_context,
rasterize_out,
options.remove_background,
options.deskew,
clean=False,
)
else:
if not options.lossless_reconstruction:
preprocess_out = preprocess(
page_context,
rasterize_out,
options.remove_background,
options.deskew,
clean=options.clean_final,
)
if options.remove_vectors:
rasterize_ocr_out = rasterize(
page_context.origin,
page_context,
correction=orientation_correction,
remove_vectors=True,
output_tag='_ocr',
)
else:
rasterize_ocr_out = rasterize_out
if (
preprocess_out
and rasterize_ocr_out == rasterize_out
and options.clean == options.clean_final
):
# Optimization: image for OCR is identical to presentation image
ocr_image = preprocess_out
else:
ocr_image = preprocess(
page_context,
rasterize_ocr_out,
options.remove_background,
options.deskew,
clean=options.clean,
)
return ocr_image, preprocess_out
def exec_page_sync(page_context: PageContext):
options = page_context.options
tls.pageno = page_context.pageno + 1
if not is_ocr_required(page_context):
return PageResult(
pageno=page_context.pageno,
pdf_page_from_image=None,
ocr=None,
text=None,
orientation_correction=0,
)
orientation_correction = 0
if options.rotate_pages:
# Rasterize
rasterize_preview_out = rasterize_preview(page_context.origin, page_context)
orientation_correction = get_orientation_correction(
rasterize_preview_out, page_context
)
ocr_image, preprocess_out = make_intermediate_images(
page_context, orientation_correction
)
ocr_image_out = create_ocr_image(ocr_image, page_context)
pdf_page_from_image_out = None
if not options.lossless_reconstruction:
assert preprocess_out
visible_image_out = preprocess_out
if should_visible_page_image_use_jpg(page_context.pageinfo):
visible_image_out = create_visible_page_jpg(visible_image_out, page_context)
filtered_image = page_context.plugin_manager.hook.filter_page_image(
page=page_context, image_filename=visible_image_out
)
if filtered_image:
visible_image_out = filtered_image
pdf_page_from_image_out = create_pdf_page_from_image(
visible_image_out, page_context
)
if options.pdf_renderer == 'hocr':
(hocr_out, text_out) = ocr_engine_hocr(ocr_image_out, page_context)
ocr_out = render_hocr_page(hocr_out, page_context)
if options.pdf_renderer == 'sandwich':
(ocr_out, text_out) = ocr_engine_textonly_pdf(ocr_image_out, page_context)
return PageResult(
pageno=page_context.pageno,
pdf_page_from_image=pdf_page_from_image_out,
ocr=ocr_out,
text=text_out,
orientation_correction=orientation_correction,
)
def post_process(pdf_file, context: PdfContext):
pdf_out = pdf_file
if context.options.output_type.startswith('pdfa'):
ps_stub_out = generate_postscript_stub(context)
pdf_out = convert_to_pdfa(pdf_out, ps_stub_out, context)
pdf_out = metadata_fixup(pdf_out, context)
return optimize_pdf(pdf_out, context)
def worker_init(max_pixels: int):
# 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
# threaded, but harmless to set again.
PIL.Image.MAX_IMAGE_PIXELS = max_pixels
pikepdf_enable_mmap()
def exec_concurrent(context: PdfContext):
"""Execute the pipeline concurrently"""
# Run exec_page_sync on every page context
max_workers = min(len(context.pdfinfo), context.options.jobs)
if max_workers > 1:
log.info("Start processing %d pages concurrently", max_workers)
sidecars: List[Optional[Path]] = [None] * len(context.pdfinfo)
ocrgraft = OcrGrafter(context)
def update_page(result: PageResult, pbar):
sidecars[result.pageno] = result.text
pbar.update()
ocrgraft.graft_page(
pageno=result.pageno,
image=result.pdf_page_from_image,
textpdf=result.ocr,
autorotate_correction=result.orientation_correction,
)
pbar.update()
exec_progress_pool(
use_threads=context.options.use_threads,
max_workers=max_workers,
tqdm_kwargs=dict(
total=(2 * len(context.pdfinfo)),
desc='OCR',
unit='page',
unit_scale=0.5,
disable=not context.options.progress_bar,
),
task_initializer=partial(worker_init, PIL.Image.MAX_IMAGE_PIXELS),
task=exec_page_sync,
task_arguments=context.get_page_contexts(),
task_finished=update_page,
)
# Output sidecar text
if context.options.sidecar:
text = merge_sidecars(sidecars, context)
# Copy text file to destination
copy_final(text, context.options.sidecar, context)
# Merge layers to one single pdf
pdf = ocrgraft.finalize()
# PDF/A and metadata
pdf = post_process(pdf, context)
# Copy PDF file to destination
copy_final(pdf, context.options.output_file, context)
class NeverRaise(Exception):
"""An exception that is never raised"""
pass # pylint: disable=unnecessary-pass
def configure_debug_logging(log_filename, prefix=''):
log_file_handler = logging.FileHandler(log_filename, delay=True)
log_file_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'[%(asctime)s] - %(name)s - %(levelname)7s -%(pageno)s %(message)s'
)
log_file_handler.setFormatter(formatter)
log_file_handler.addFilter(PageNumberFilter())
logging.getLogger(prefix).addHandler(log_file_handler)
return log_file_handler
def run_pipeline(options, *, plugin_manager, api=False):
# 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.)
if not options.jobs:
options.jobs = available_cpu_count()
if not plugin_manager:
plugin_manager = get_plugin_manager(options.plugins)
work_folder = Path(mkdtemp(prefix="com.github.ocrmypdf."))
debug_log_handler = None
if (options.keep_temporary_files or options.verbose >= 1) and not os.environ.get(
'PYTEST_CURRENT_TEST', ''
):
debug_log_handler = configure_debug_logging(Path(work_folder) / "debug.log")
pikepdf_enable_mmap()
try:
check_requested_output_file(options)
start_input_file, original_filename = create_input_file(options, work_folder)
# Triage image or pdf
origin_pdf = triage(
original_filename, start_input_file, work_folder / 'origin.pdf', options
)
# Gather pdfinfo and create context
pdfinfo = get_pdfinfo(
origin_pdf,
detailed_analysis=options.redo_ocr,
progbar=options.progress_bar,
max_workers=options.jobs if not options.use_threads else 1, # To help debug
check_pages=options.pages,
)
context = PdfContext(options, work_folder, origin_pdf, pdfinfo, plugin_manager)
# Validate options are okay for this pdf
validate_pdfinfo_options(context)
# Execute the pipeline
exec_concurrent(context)
if options.output_file == '-':
log.info("Output sent to stdout")
elif (
hasattr(options.output_file, 'writable') and options.output_file.writable()
):
log.info("Output written to stream")
elif samefile(options.output_file, os.devnull):
pass # Say nothing when sending to dev null
else:
if options.output_type.startswith('pdfa'):
pdfa_info = file_claims_pdfa(options.output_file)
if pdfa_info['pass']:
log.info(
"Output file is a %s (as expected)", pdfa_info['conformance']
)
else:
log.warning(
"Output file is okay but is not PDF/A (seems to be %s)",
pdfa_info['conformance'],
)
return ExitCode.pdfa_conversion_failed
if not check_pdf(options.output_file):
log.warning('Output file: The generated PDF is INVALID')
return ExitCode.invalid_output_pdf
report_output_file_size(options, start_input_file, options.output_file)
except (KeyboardInterrupt if not api else NeverRaise) as e:
if options.verbose >= 1:
log.exception("KeyboardInterrupt")
else:
log.error("KeyboardInterrupt")
return ExitCode.ctrl_c
except (ExitCodeException if not api else NeverRaise) as e:
if str(e):
log.error("%s: %s", type(e).__name__, str(e))
else:
log.error(type(e).__name__)
return e.exit_code
except (Exception if not api else NeverRaise) as e: # pylint: disable=broad-except
log.exception("An exception occurred while executing the pipeline")
return ExitCode.other_error
finally:
if debug_log_handler:
try:
debug_log_handler.close()
log.removeHandler(debug_log_handler)
except EnvironmentError as e:
print(e, file=sys.stderr)
cleanup_working_files(work_folder, options)
return ExitCode.ok
+118
View File
@@ -0,0 +1,118 @@
# Copyright (c) 2014, Armin Ronacher
#
# Copyright (c) 2017, James R Barlow
#
# Some rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are
# met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * The names of the contributors may not be used to endorse or
# promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import codecs
import os
import sys
def verify_python3_env(): # pragma: no cover
"""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
except Exception:
fs_enc = 'ascii'
if fs_enc != 'ascii':
return
extra = ''
if os.name == 'posix':
import subprocess
rv = subprocess.run(
['locale', '-a'], stdout=subprocess.PIPE, stderr=subprocess.PIPE
).stdout
good_locales = set()
has_c_utf8 = False
# Make sure we're operating on text here.
if isinstance(rv, bytes):
rv = rv.decode('ascii', 'replace')
for line in rv.splitlines():
locale = line.strip()
if locale.lower().endswith(('.utf-8', '.utf8')):
good_locales.add(locale)
if locale.lower() in ('c.utf8', 'c.utf-8'):
has_c_utf8 = True
extra += '\n\n'
if not good_locales:
extra += (
'Additional information: on this system no suitable UTF-8\n'
'locales were discovered. This most likely requires resolving\n'
'by reconfiguring the locale system.'
)
elif has_c_utf8:
extra += (
'This system supports the C.UTF-8 locale which is recommended.\n'
'You might be able to resolve your issue by exporting the\n'
'following environment variables:\n\n'
' export LC_ALL=C.UTF-8\n'
' export LANG=C.UTF-8'
)
else:
extra += (
'This system lists a couple of UTF-8 supporting locales that\n'
'you can pick from. The following suitable locales were\n'
'discovered: %s'
) % ', '.join(sorted(good_locales))
bad_locale = None
for locale in os.environ.get('LC_ALL'), os.environ.get('LANG'):
if locale and locale.lower().endswith(('.utf-8', '.utf8')):
bad_locale = locale
if locale is not None:
break
if bad_locale is not None:
extra += (
'\nocrmypdf discovered that you exported a UTF-8 locale\n'
'but the locale system could not pick up from it because\n'
'it does not exist. The exported locale is "%s" but it\n'
'is not supported'
) % bad_locale
raise RuntimeError(
'ocrmypdf will abort further execution because Python 3 '
'was configured to use ASCII as encoding for the '
'environment.' + extra
)
+421
View File
@@ -0,0 +1,421 @@
#!/usr/bin/env python3
# © 2015-17 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 locale
import logging
import os
import sys
import unicodedata
from pathlib import Path
from shutil import copyfileobj
from typing import Tuple
import pikepdf
import PIL
from ocrmypdf._exec import jbig2enc, pngquant, unpaper
from ocrmypdf._unicodefun import verify_python3_env
from ocrmypdf.exceptions import (
BadArgsError,
InputFileError,
MissingDependencyError,
OutputFileAccessError,
)
from ocrmypdf.helpers import (
is_file_writable,
is_iterable_notstr,
monotonic,
safe_symlink,
)
from ocrmypdf.subprocess import check_external_program
# -------------
# External dependencies
HOCR_OK_LANGS = frozenset(['eng', 'deu', 'spa', 'ita', 'por'])
DEFAULT_LANGUAGE = 'eng' # Enforce English hegemony
log = logging.getLogger(__name__)
# --------
# Critical environment tests
verify_python3_env()
def check_platform():
if os.name == 'nt' and sys.maxsize <= 2 ** 32: # pragma: no cover
# 32-bit interpreter on Windows
log.error(
"You are running OCRmyPDF in a 32-bit (x86) Python interpreter."
"Please use a 64-bit (x86-64) version of Python."
)
def check_options_languages(options, ocr_engine_languages):
if not options.languages:
options.languages = {DEFAULT_LANGUAGE}
system_lang = locale.getlocale()[0]
if system_lang and not system_lang.startswith('en'):
log.debug("No language specified; assuming --language %s", DEFAULT_LANGUAGE)
if not ocr_engine_languages:
return
if not options.languages.issubset(ocr_engine_languages):
msg = (
f"OCR engine does not have language data for the following "
"requested languages: \n"
)
for lang in options.languages - ocr_engine_languages:
msg += lang + '\n'
raise MissingDependencyError(msg)
def check_options_output(options):
is_latin = options.languages.issubset(HOCR_OK_LANGS)
if options.pdf_renderer == 'hocr' and not is_latin:
msg = (
"The 'hocr' PDF renderer is known to cause problems with one "
"or more of the languages in your document. Use "
"--pdf-renderer auto (the default) to avoid this issue."
)
log.warning(msg)
lossless_reconstruction = False
if not any(
(
options.deskew,
options.clean_final,
options.force_ocr,
options.remove_background,
)
):
lossless_reconstruction = True
options.lossless_reconstruction = lossless_reconstruction
if not options.lossless_reconstruction and options.redo_ocr:
raise BadArgsError(
"--redo-ocr is not currently compatible with --deskew, "
"--clean-final, and --remove-background"
)
def check_options_sidecar(options):
if options.sidecar == '\0':
if options.output_file == '-':
raise BadArgsError(
"--sidecar filename must be specified when output file is stdout."
)
options.sidecar = options.output_file + '.txt'
def check_options_preprocessing(options):
if options.clean_final:
options.clean = True
if options.unpaper_args and not options.clean:
raise BadArgsError("--clean is required for --unpaper-args")
if options.clean:
check_external_program(
program='unpaper',
package='unpaper',
version_checker=unpaper.version,
need_version='6.1',
required_for=['--clean, --clean-final'],
)
try:
if options.unpaper_args:
options.unpaper_args = unpaper.validate_custom_args(
options.unpaper_args
)
except Exception as e:
raise BadArgsError(str(e))
def _pages_from_ranges(ranges):
if is_iterable_notstr(ranges):
return set(ranges)
pages = []
page_groups = ranges.replace(' ', '').split(',')
for g in page_groups:
if not g:
continue
try:
start, end = g.split('-')
except ValueError:
pages.append(int(g) - 1)
else:
try:
pages.extend(range(int(start) - 1, int(end)))
except ValueError:
raise BadArgsError("invalid page range")
if not monotonic(pages):
log.warning(
"List of pages to process contains duplicate pages, or pages that are "
"out of order"
)
if any(page < 0 for page in pages):
raise BadArgsError("pages refers to a page number less than 1")
log.debug("OCRing only these pages: %s", pages)
return set(pages)
def check_options_ocr_behavior(options):
exclusive_options = sum(
[
(1 if opt else 0)
for opt in (options.force_ocr, options.skip_text, options.redo_ocr)
]
)
if exclusive_options >= 2:
raise BadArgsError("Choose only one of --force-ocr, --skip-text, --redo-ocr.")
if options.pages and options.sidecar:
raise BadArgsError("--pages and --sidecar are mutually exclusive")
if options.pages:
options.pages = _pages_from_ranges(options.pages)
def check_options_optimizing(options):
if options.optimize >= 2:
check_external_program(
program='pngquant',
package='pngquant',
version_checker=pngquant.version,
need_version='2.0.1',
required_for='--optimize {2,3}',
)
if options.optimize >= 2:
# Although we use JBIG2 for optimize=1, don't nag about it unless the
# user is asking for more optimization
check_external_program(
program='jbig2',
package='jbig2enc',
version_checker=jbig2enc.version,
need_version='0.28',
required_for='--optimize {2,3} | --jbig2-lossy',
recommended=True if not options.jbig2_lossy else False,
)
if options.optimize == 0 and any(
[options.jbig2_lossy, options.png_quality, options.jpeg_quality]
):
log.warning(
"The arguments --jbig2-lossy, --png-quality, and --jpeg-quality "
"will be ignored because --optimize=0."
)
def check_options_advanced(options):
if options.pdfa_image_compression != 'auto' and options.output_type.startswith(
'pdfa'
):
log.warning(
"--pdfa-image-compression argument has no effect when "
"--output-type is not 'pdfa', 'pdfa-1', or 'pdfa-2'"
)
def check_options_metadata(options):
docinfo = [options.title, options.author, options.keywords, options.subject]
for s in (m for m in docinfo if m):
for c in s:
if unicodedata.category(c) == 'Co' or ord(c) >= 0x10000:
raise ValueError(
"One of the metadata strings contains "
"an unsupported Unicode character: '{}' (U+{})".format(
c, hex(ord(c))[2:].upper()
)
)
def check_options_pillow(options):
PIL.Image.MAX_IMAGE_PIXELS = int(options.max_image_mpixels * 1_000_000)
if PIL.Image.MAX_IMAGE_PIXELS == 0:
PIL.Image.MAX_IMAGE_PIXELS = None
def _check_options(options, plugin_manager, ocr_engine_languages):
check_platform()
check_options_languages(options, ocr_engine_languages)
check_options_metadata(options)
check_options_output(options)
check_options_sidecar(options)
check_options_preprocessing(options)
check_options_ocr_behavior(options)
check_options_optimizing(options)
check_options_advanced(options)
check_options_pillow(options)
plugin_manager.hook.check_options(options=options)
def check_options(options, plugin_manager):
ocr_engine_languages = plugin_manager.hook.get_ocr_engine().languages(options)
_check_options(options, plugin_manager, ocr_engine_languages)
def check_closed_streams(options): # pragma: no cover
"""Work around Python issue with multiprocessing forking on closed streams
https://bugs.python.org/issue28326
Attempting to a fork/exec a new Python process when any of std{in,out,err}
are closed or not flushable for some reason may raise an exception.
Fix this by opening devnull if the handle seems to be closed. Do this
globally to avoid tracking places all places that fork.
Seems to be specific to multiprocessing.Process not all Python process
forkers.
The error actually occurs when the stream object is not flushable,
but replacing an open stream object that is not flushable with
/dev/null is a bad idea since it will create a silent failure. Replacing
a closed handle with /dev/null seems safe.
"""
if sys.version_info[0:3] >= (3, 6, 4):
return True # Issued fixed in Python 3.6.4+
if sys.stderr is None:
sys.stderr = open(os.devnull, 'w')
if sys.stdin is None:
if options.input_file == '-':
log.error("Trying to read from stdin but stdin seems closed")
return False
sys.stdin = open(os.devnull, 'r')
if sys.stdout is None:
if options.output_file == '-':
# Can't replace stdout if the user is piping
# If this case can even happen, it must be some kind of weird
# stream.
log.error(
"Output was set to stdout '-' but the stream attached to "
"stdout does not support the flush() system call. This "
"will fail."
)
return False
sys.stdout = open(os.devnull, 'w')
return True
def log_page_orientations(pdfinfo):
direction = {0: 'n', 90: 'e', 180: 's', 270: 'w'}
orientations = []
for n, page in enumerate(pdfinfo):
angle = page.rotation or 0
if angle != 0:
orientations.append('{0}{1}'.format(n + 1, direction.get(angle, '')))
if orientations:
log.info('Page orientations detected: %s', ' '.join(orientations))
def create_input_file(options, work_folder: Path) -> Tuple[Path, str]:
if options.input_file == '-':
# stdin
log.info('reading file from standard input')
target = work_folder / 'stdin'
with open(target, 'wb') as stream_buffer:
copyfileobj(sys.stdin.buffer, stream_buffer)
return target, "stdin"
elif hasattr(options.input_file, 'readable'):
if not options.input_file.readable():
raise InputFileError("Input file stream is not readable")
log.info('reading file from input stream')
target = work_folder / 'stream'
with open(target, 'wb') as stream_buffer:
copyfileobj(options.input_file, stream_buffer)
return target, "stream"
else:
try:
target = work_folder / 'origin'
safe_symlink(options.input_file, target)
return target, os.fspath(options.input_file)
except FileNotFoundError:
raise InputFileError(f"File not found - {options.input_file}")
def check_requested_output_file(options):
if options.output_file == '-':
if sys.stdout.isatty():
raise BadArgsError(
"Output was set to stdout '-' but it looks like stdout "
"is connected to a terminal. Please redirect stdout to a "
"file."
)
elif hasattr(options.output_file, 'writable'):
if not options.output_file.writable():
raise OutputFileAccessError("Output stream is not writable")
elif not is_file_writable(options.output_file):
raise OutputFileAccessError(
f"Output file location ({options.output_file}) is not a writable file."
)
def report_output_file_size(options, input_file, output_file):
try:
output_size = Path(output_file).stat().st_size
input_size = Path(input_file).stat().st_size
except FileNotFoundError:
return # Outputting to stream or something
with pikepdf.open(output_file) as p:
# Overhead constants obtained by estimating amount of data added by OCR
# PDF/A conversion, and possible XMP metadata addition, with compression
FILE_OVERHEAD = 4000
OCR_PER_PAGE_OVERHEAD = 3000
reasonable_overhead = FILE_OVERHEAD + OCR_PER_PAGE_OVERHEAD * len(p.pages)
ratio = output_size / input_size
reasonable_ratio = output_size / (input_size + reasonable_overhead)
if reasonable_ratio < 1.35 or input_size < 25000:
return # Seems fine
reasons = []
image_preproc = {
'deskew',
'clean_final',
'remove_background',
'oversample',
'force_ocr',
}
for arg in image_preproc:
if getattr(options, arg, False):
reasons.append(
f"The argument --{arg.replace('_', '-')} was issued, causing transcoding."
)
if options.optimize == 0:
reasons.append("Optimization was disabled.")
else:
image_optimizers = {
'jbig2': jbig2enc.available(),
'pngquant': pngquant.available(),
}
for name, available in image_optimizers.items():
if not available:
reasons.append(
f"The optional dependency '{name}' was not found, so some image "
f"optimizations could not be attempted."
)
if options.output_type.startswith('pdfa'):
reasons.append("PDF/A conversion was enabled. (Try `--output-type pdf`.)")
if options.plugins:
reasons.append("Plugins were used.")
if reasons:
explanation = "Possible reasons for this include:\n" + '\n'.join(reasons) + "\n"
else:
explanation = "No reason for this increase is known. Please report this issue."
log.warning(
f"The output file size is {ratio:.2f}× larger than the input file.\n"
f"{explanation}"
)
+13
View File
@@ -0,0 +1,13 @@
# © 2017 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 pkg_resources
PROGRAM_NAME = 'ocrmypdf'
# Official PEP 396
__version__ = pkg_resources.get_distribution('ocrmypdf').version
+297
View File
@@ -0,0 +1,297 @@
# © 2019 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 logging
import os
import sys
from enum import IntEnum
from pathlib import Path
from typing import BinaryIO, Iterable, Union
from ocrmypdf._logging import PageNumberFilter, TqdmConsole
from ocrmypdf._plugin_manager import get_plugin_manager
from ocrmypdf._sync import run_pipeline
from ocrmypdf._validation import check_options
from ocrmypdf.cli import ArgumentParser, get_parser
from ocrmypdf.helpers import is_iterable_notstr
try:
import coloredlogs
except ModuleNotFoundError:
coloredlogs = None
PathOrIO = Union[BinaryIO, os.PathLike, str, bytes]
class Verbosity(IntEnum):
"""Verbosity level for configure_logging."""
quiet = -1 #: Suppress most messages
default = 0 #: Default level of logging
debug = 1 #: Output ocrmypdf debug messages
debug_all = 2 #: More detailed debugging from ocrmypdf and dependent modules
def configure_logging(
verbosity: Verbosity,
progress_bar_friendly: bool = True,
manage_root_logger: bool = False,
):
"""Set up logging.
Library users may wish to use this function if they want their log output to be
similar to ocrmypdf command line interface. If not used, the external application
should configure logging on its own.
ocrmypdf will perform all of its logging under the ``"ocrmypdf"`` logging namespace.
In addition, ocrmypdf imports pdfminer, which logs under ``"pdfminer"``. A library
user may wish to configure both; note that pdfminer is extremely chatty at the log
level ``logging.INFO``.
Library users may perform additional configuration afterwards.
Args:
verbosity (Verbosity): Verbosity level.
progress_bar_friendly (bool): Install the TqdmConsole log handler, which is
compatible with the tqdm progress bar; without this log messages will
overwrite the progress bar
manage_root_logger (bool): Configure the process's root logger, to ensure
all log output is sent through
Returns:
The toplevel logger for ocrmypdf (or the root logger, if we are managing it).
"""
prefix = '' if manage_root_logger else 'ocrmypdf'
log = logging.getLogger(prefix)
log.setLevel(logging.DEBUG)
if progress_bar_friendly:
console = logging.StreamHandler(stream=TqdmConsole(sys.stderr))
else:
console = logging.StreamHandler(stream=sys.stderr)
if verbosity < 0:
console.setLevel(logging.ERROR)
elif verbosity >= 1:
console.setLevel(logging.DEBUG)
else:
console.setLevel(logging.INFO)
console.addFilter(PageNumberFilter())
if verbosity >= 2:
fmt = '%(levelname)7s %(name)s -%(pageno)s %(message)s'
else:
fmt = '%(pageno)s%(message)s'
use_colors = progress_bar_friendly
if not coloredlogs:
use_colors = False
if use_colors:
if os.name == 'nt':
use_colors = coloredlogs.enable_ansi_support()
if use_colors:
use_colors = coloredlogs.terminal_supports_colors()
if use_colors:
formatter = coloredlogs.ColoredFormatter(fmt=fmt)
else:
formatter = logging.Formatter(fmt=fmt)
console.setFormatter(formatter)
log.addHandler(console)
if verbosity <= 1:
pdfminer_log = logging.getLogger('pdfminer')
pdfminer_log.setLevel(logging.ERROR)
pil_log = logging.getLogger('PIL')
pil_log.setLevel(logging.INFO)
if manage_root_logger:
logging.captureWarnings(True)
return log
def create_options(
*, input_file: PathOrIO, output_file: PathOrIO, parser: ArgumentParser, **kwargs
):
cmdline = []
deferred = []
for arg, val in kwargs.items():
if val is None:
continue
# These arguments with special handling for which we bypass
# argparse
if arg in {'progress_bar', 'plugins'}:
deferred.append((arg, val))
continue
cmd_style_arg = arg.replace('_', '-')
# Booleans are special: add only if True, omit for False
if isinstance(val, bool):
if val:
cmdline.append(f"--{cmd_style_arg}")
continue
if is_iterable_notstr(val):
for elem in val:
cmdline.append(f"--{cmd_style_arg}")
cmdline.append(elem)
continue
# We have a parameter
cmdline.append(f"--{cmd_style_arg}")
if isinstance(val, (int, float)):
cmdline.append(str(val))
elif isinstance(val, str):
cmdline.append(val)
elif isinstance(val, Path):
cmdline.append(str(val))
else:
raise TypeError(f"{arg}: {val} ({type(val)})")
try:
cmdline.append(os.fspath(input_file))
except TypeError:
cmdline.append('stream://input_file')
try:
cmdline.append(os.fspath(output_file))
except TypeError:
cmdline.append('stream://output_file')
parser._api_mode = True
options = parser.parse_args(cmdline)
for keyword, val in deferred:
setattr(options, keyword, val)
if options.input_file == 'stream://input_file':
options.input_file = input_file
if options.output_file == 'stream://output_file':
options.output_file = output_file
return options
def ocr( # pylint: disable=unused-argument
input_file: PathOrIO,
output_file: PathOrIO,
*,
language: Iterable[str] = None,
image_dpi: int = None,
output_type=None,
sidecar: os.PathLike = None,
jobs: int = None,
use_threads: bool = None,
title: str = None,
author: str = None,
subject: str = None,
keywords: str = None,
rotate_pages: bool = None,
remove_background: bool = None,
deskew: bool = None,
clean: bool = None,
clean_final: bool = None,
unpaper_args: str = None,
oversample: int = None,
remove_vectors: bool = None,
threshold: bool = None,
force_ocr: bool = None,
skip_text: bool = None,
redo_ocr: bool = None,
skip_big: float = None,
optimize: int = None,
jpg_quality: int = None,
png_quality: int = None,
jbig2_lossy: bool = None,
jbig2_page_group_size: int = None,
pages: str = None,
max_image_mpixels: float = None,
tesseract_config: Iterable[str] = None,
tesseract_pagesegmode: int = None,
tesseract_oem: int = None,
pdf_renderer=None,
tesseract_timeout: float = None,
rotate_pages_threshold: float = None,
pdfa_image_compression=None,
user_words: os.PathLike = None,
user_patterns: os.PathLike = None,
fast_web_view: float = None,
plugins: Iterable[str] = None,
keep_temporary_files: bool = None,
progress_bar: bool = None,
**kwargs,
):
"""Run OCRmyPDF on one PDF or image.
For most arguments, see documentation for the equivalent command line parameter.
A few specific arguments are discussed here:
Args:
use_threads: Use worker threads instead of processes. This reduces
performance but may make debugging easier since it is easier to set
breakpoints.
input_file: If a :class:`pathlib.Path`, ``str`` or ``bytes``, this is
interpreted as file system path to the input file. If the object
appears to be a readable stream (with methods such as ``.read()``
and ``.seek()``), the object will be read in its entirety and saved to
a temporary file. If ``input_file`` is ``"-"``, standard input will be
read.
output_file: If a :class:`pathlib.Path`, ``str`` or ``bytes``, this is
interpreted as file system path to the output file. If the object
appears to be a writable stream (with methods such as ``.read()`` and
``.seek()``), the output will be written to this stream. If
``output_file`` is ``"-"``, the output will be written to ``sys.stdout``
(provided that standard output does not seem to be a terminal device).
When a stream is used as output, whether via a writable object or
``"-"``, some final validation steps are not performed (we do not read
back the stream after it is written).
Raises:
ocrmypdf.PdfMergeFailedError: If the input PDF is malformed, preventing merging
with the OCR layer.
ocrmypdf.MissingDependencyError: If a required dependency program is missing or
was not found on PATH.
ocrmypdf.UnsupportedImageFormatError: If the input file type was an image that
could not be read, or some other file type that is not a PDF.
ocrmypdf.DpiError: If the input file is an image, but the resolution of the
image is not credible (allowing it to proceed would cause poor OCR).
ocrmypdf.OutputFileAccessError: If an attempt to write to the intended output
file failed.
ocrmypdf.PriorOcrFoundError: If the input PDF seems to have OCR or digital
text already, and settings did not tell us to proceed.
ocrmypdf.InputFileError: Any other problem with the input file.
ocrmypdf.SubprocessOutputError: Any error related to executing a subprocess.
ocrmypdf.EncryptedPdfERror: If the input PDF is encrypted (password protected).
OCRmyPDF does not remove passwords.
ocrmypdf.TesseractConfigError: If Tesseract reported its configuration was not
valid.
Returns:
:class:`ocrmypdf.ExitCode`
"""
if not plugins:
plugins = []
else:
plugins = list(plugins)
parser = get_parser()
_plugin_manager = get_plugin_manager(plugins)
_plugin_manager.hook.add_options(parser=parser) # pylint: disable=no-member
create_options_kwargs = {
k: v for k, v in locals().items() if not k.startswith('_') and k != 'kwargs'
}
create_options_kwargs.update(kwargs)
options = create_options(**create_options_kwargs)
check_options(options, _plugin_manager)
return run_pipeline(options=options, plugin_manager=_plugin_manager, api=True)
+5
View File
@@ -0,0 +1,5 @@
# © 2020 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/.
@@ -0,0 +1,92 @@
# © 2020 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 logging
from ocrmypdf import hookimpl
from ocrmypdf._exec import ghostscript
from ocrmypdf._validation import HOCR_OK_LANGS
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.subprocess import check_external_program
log = logging.getLogger(__name__)
@hookimpl
def check_options(options):
gs_version = ghostscript.version()
check_external_program(
program='gs',
package='ghostscript',
version_checker=gs_version,
need_version='9.15', # limited by Travis CI / Ubuntu 14.04 backports
)
if gs_version in ('9.24', '9.51'):
raise MissingDependencyError(
f"Ghostscript {gs_version} contains serious regressions and is not "
"supported. Please upgrade to a newer version, or downgrade to the "
"previous version."
)
# We have these constraints to check for.
# 1. Ghostscript < 9.20 mangles multibyte Unicode
# 2. hocr doesn't work on non-Latin languages (so don't select it)
is_latin = options.languages.issubset(HOCR_OK_LANGS)
if gs_version < '9.20' and options.output_type != 'pdf' and not is_latin:
# https://bugs.ghostscript.com/show_bug.cgi?id=696874
# Ghostscript < 9.20 fails to encode multibyte characters properly
msg = (
"The installed version of Ghostscript does not work correctly "
"with the OCR languages you specified. Use --output-type pdf or "
"upgrade to Ghostscript 9.20 or later to avoid this issue."
)
msg += f"Found Ghostscript {gs_version}"
log.warning(msg)
if options.output_type == 'pdfa':
options.output_type = 'pdfa-2'
if options.output_type == 'pdfa-3' and ghostscript.version() < '9.19':
raise MissingDependencyError(
"--output-type pdfa-3 requires Ghostscript 9.19 or later"
)
@hookimpl
def rasterize_pdf_page(
input_file,
output_file,
raster_device,
raster_dpi,
pageno,
page_dpi=None,
rotation=None,
filter_vector=False,
):
ghostscript.rasterize_pdf(
input_file,
output_file,
raster_device=raster_device,
raster_dpi=raster_dpi,
pageno=pageno,
page_dpi=page_dpi,
rotation=rotation,
filter_vector=filter_vector,
)
return output_file
@hookimpl
def generate_pdfa(pdf_pages, pdfmark, output_file, compression, pdf_version, pdfa_part):
ghostscript.generate_pdfa(
pdf_pages=[*pdf_pages, pdfmark],
output_file=output_file,
compression=compression,
pdf_version=pdf_version,
pdfa_part=pdfa_part,
)
return output_file
@@ -0,0 +1,187 @@
# © 2020 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 logging
import os
from ocrmypdf import hookimpl
from ocrmypdf._exec import tesseract
from ocrmypdf.cli import numeric
from ocrmypdf.exceptions import MissingDependencyError
from ocrmypdf.helpers import clamp
from ocrmypdf.pluginspec import OcrEngine
from ocrmypdf.subprocess import check_external_program
log = logging.getLogger(__name__)
@hookimpl
def add_options(parser):
tess = parser.add_argument_group("Tesseract", "Advanced control of Tesseract OCR")
tess.add_argument(
'--tesseract-config',
action='append',
metavar='CFG',
default=[],
help="Additional Tesseract configuration files -- see documentation",
)
tess.add_argument(
'--tesseract-pagesegmode',
action='store',
type=int,
metavar='PSM',
choices=range(0, 14),
help="Set Tesseract page segmentation mode (see tesseract --help)",
)
tess.add_argument(
'--tesseract-oem',
action='store',
type=int,
metavar='MODE',
choices=range(0, 4),
help=(
"Set Tesseract 4.0 OCR engine mode: "
"0 - original Tesseract only; "
"1 - neural nets LSTM only; "
"2 - Tesseract + LSTM; "
"3 - default."
),
)
tess.add_argument(
'--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',
)
tess.add_argument(
'--user-words',
metavar='FILE',
help="Specify the location of the Tesseract user words file. This is a "
"list of words Tesseract should consider while performing OCR in "
"addition to its standard language dictionaries. This can improve "
"OCR quality especially for specialized and technical documents.",
)
tess.add_argument(
'--user-patterns',
metavar='FILE',
help="Specify the location of the Tesseract user patterns file.",
)
@hookimpl
def check_options(options):
check_external_program(
program='tesseract',
package={'linux': 'tesseract-ocr'},
version_checker=tesseract.version,
need_version='4.0.0', # using backport for Travis CI
)
# Decide on what renderer to use
if options.pdf_renderer == 'auto':
options.pdf_renderer = 'sandwich'
if options.pdf_renderer == 'sandwich' and not tesseract.has_textonly_pdf(
set(options.languages)
):
raise MissingDependencyError(
"You are using an alpha version of Tesseract 4.0 that does not support "
"the textonly_pdf parameter. We don't support versions this old."
)
if not tesseract.has_user_words() and (options.user_words or options.user_patterns):
log.warning(
"Tesseract 4.0 ignores --user-words and --user-patterns, so these "
"arguments have no effect."
)
if options.tesseract_pagesegmode in (0, 2):
log.warning(
"The --tesseract-pagesegmode argument you select will disable OCR. "
"This may cause processing to fail."
)
@hookimpl
def validate(pdfinfo, options):
# Tesseract 4.x can be multithreaded, and we also run multiple workers. We want
# to manage how many threads it uses to avoid creating total threads than cores.
# Performance testing shows we're better off
# parallelizing ocrmypdf and forcing Tesseract to be single threaded, which we
# get by setting the envvar OMP_THREAD_LIMIT to 1. But if the page count of the
# input file is small, then we allow Tesseract to use threads, subject to the
# constraint: (ocrmypdf workers) * (tesseract threads) <= max_workers.
# As of Tesseract 4.1, 3 threads is the most effective on a 4 core/8 thread system.
if not os.environ.get('OMP_THREAD_LIMIT', '').isnumeric():
tess_threads = clamp(options.jobs // len(pdfinfo), 1, 3)
os.environ['OMP_THREAD_LIMIT'] = str(tess_threads)
else:
tess_threads = int(os.environ['OMP_THREAD_LIMIT'])
if tess_threads > 1:
log.info("Using Tesseract OpenMP thread limit %d", tess_threads)
class TesseractOcrEngine(OcrEngine):
@staticmethod
def version():
return tesseract.version()
@staticmethod
def creator_tag(options):
tag = '-PDF' if options.pdf_renderer == 'sandwich' else ''
return f"Tesseract OCR{tag} {TesseractOcrEngine.version()}"
def __str__(self):
return f"Tesseract OCR {TesseractOcrEngine.version()}"
@staticmethod
def languages(options):
return tesseract.get_languages()
@staticmethod
def get_orientation(input_file, options):
return tesseract.get_orientation(
input_file,
engine_mode=options.tesseract_oem,
timeout=options.tesseract_timeout,
)
@staticmethod
def generate_hocr(input_file, output_hocr, output_text, options):
tesseract.generate_hocr(
input_file=input_file,
output_hocr=output_hocr,
output_text=output_text,
languages=options.languages,
engine_mode=options.tesseract_oem,
tessconfig=options.tesseract_config,
timeout=options.tesseract_timeout,
pagesegmode=options.tesseract_pagesegmode,
user_words=options.user_words,
user_patterns=options.user_patterns,
)
@staticmethod
def generate_pdf(input_file, output_pdf, output_text, options):
tesseract.generate_pdf(
input_file=input_file,
output_pdf=output_pdf,
output_text=output_text,
languages=options.languages,
engine_mode=options.tesseract_oem,
tessconfig=options.tesseract_config,
timeout=options.tesseract_timeout,
pagesegmode=options.tesseract_pagesegmode,
user_words=options.user_words,
user_patterns=options.user_patterns,
)
@hookimpl
def get_ocr_engine():
return TesseractOcrEngine()
+482
View File
@@ -0,0 +1,482 @@
# © 2015-19 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 argparse
from ocrmypdf._version import PROGRAM_NAME as _PROGRAM_NAME
from ocrmypdf._version import __version__ as _VERSION
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
_numeric.__name__ = basetype.__name__
return _numeric
class ArgumentParser(argparse.ArgumentParser):
"""Override parser's default behavior of calling sys.exit()
https://stackoverflow.com/questions/5943249/python-argparse-and-controlling-overriding-the-exit-status-code
"""
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._api_mode = False
def error(self, message):
if not self._api_mode:
super().error(message)
return
raise ValueError(message)
class LanguageSetAction(argparse.Action):
def __init__(self, option_strings, dest, default=None, **kwargs):
if default is None:
default = set()
super().__init__(option_strings, dest, default=default, **kwargs)
def __call__(self, parser, namespace, values, option_string=None):
dest = getattr(namespace, self.dest)
if '+' in values:
dest.update(lang for lang in values.split('+'))
else:
dest.add(values)
def get_parser():
parser = ArgumentParser(
prog=_PROGRAM_NAME,
allow_abbrev=True,
fromfile_prefix_chars='@',
formatter_class=argparse.RawDescriptionHelpFormatter,
description="""\
Generates a searchable PDF or PDF/A from a regular PDF.
OCRmyPDF rasterizes each page of the input PDF, optionally corrects page
rotation and performs image processing, runs the Tesseract OCR engine on the
image, and then creates a PDF from the OCR information.
""",
epilog="""\
OCRmyPDF attempts to keep the output file at about the same size. If a file
contains losslessly compressed images, and images in the output file will be
losslessly compressed as well.
PDF is a page description file that attempts to preserve a layout exactly.
A PDF can contain vector objects (such as text or lines) and raster objects
(images). A page might have multiple images. OCRmyPDF is prepared to deal
with the wide variety of PDFs that exist in the wild.
When a PDF page contains text, OCRmyPDF assumes that the page has already
been OCRed or is a "born digital" page that should not be OCRed. The default
behavior is to exit in this case without producing a file. You can use the
option --skip-text to ignore pages with text, or --force-ocr to rasterize
all objects on the page and produce an image-only PDF as output.
ocrmypdf --skip-text file_with_some_text_pages.pdf output.pdf
ocrmypdf --force-ocr word_document.pdf output.pdf
If you are concerned about long-term archiving of PDFs, use the default option
--output-type pdfa which converts the PDF to a standardized PDF/A-2b. This
removes some features from the PDF such as Javascript or forms. If you want to
minimize the number of changes made to your PDF, use --output-type pdf.
If OCRmyPDF is given an image file as input, it will attempt to convert the
image to a PDF before processing. For more control over the conversion of
images to PDF, use the Python package img2pdf or other image to PDF software.
For example, this command uses img2pdf to convert all .png files beginning
with the 'page' prefix to a PDF, fitting each image on A4-sized paper, and
sending the result to OCRmyPDF through a pipe.
img2pdf --pagesize A4 page*.png | ocrmypdf - myfile.pdf
Online documentation is located at:
https://ocrmypdf.readthedocs.io/en/latest/introduction.html
""",
)
parser.add_argument(
'input_file',
metavar="input_pdf_or_image",
help="PDF file containing the images to be OCRed (or '-' to read from "
"standard input)",
)
parser.add_argument(
'output_file',
metavar="output_pdf",
help="Output searchable PDF file (or '-' to write to standard output). "
"Existing files will be ovewritten. If same as input file, the "
"input file will be updated only if processing is successful.",
)
parser.add_argument(
'-l',
'--language',
dest='languages',
action=LanguageSetAction,
help="Language(s) of the file to be OCRed (see tesseract --list-langs for "
"all language packs installed in your system). Use -l eng+deu for "
"multiple languages.",
)
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'],
default='pdfa',
help="Choose output type. 'pdfa' creates a PDF/A-2b compliant file for "
"long term archiving (default, recommended) but may not suitable "
"for users who want their file altered as little as possible. 'pdfa' "
"also has problems with full Unicode text. 'pdf' attempts to "
"preserve file contents as much as possible. 'pdf-a1' creates a "
"PDF/A1-b file. 'pdf-a2' is equivalent to 'pdfa'. 'pdf-a3' creates a "
"PDF/A3-b file.",
)
# Use null string '\0' as sentinel to indicate the user supplied no argument,
# since that is the only invalid character for filepaths on all platforms
# bool('\0') is True in Python
parser.add_argument(
'--sidecar',
nargs='?',
const='\0',
default=None,
metavar='FILE',
help="Generate sidecar text files that contain the same text recognized "
"by Tesseract. This may be useful for building a OCR text database. "
"If FILE is omitted, the sidecar file be named {output_file}.txt "
"If FILE is set to '-', the sidecar is written to stdout (a "
"convenient way to preview OCR quality). The output file and sidecar "
"may not both use stdout at the same time.",
)
parser.add_argument(
'--version',
action='version',
version=_VERSION,
help="Print program version and exit",
)
jobcontrol = parser.add_argument_group("Job control options")
jobcontrol.add_argument(
'-j',
'--jobs',
metavar='N',
type=numeric(int, 0, 256),
help="Use up to N CPU cores simultaneously (default: use all).",
)
jobcontrol.add_argument(
'-q', '--quiet', action='store_true', help="Suppress INFO messages"
)
jobcontrol.add_argument(
'-v',
'--verbose',
type=numeric(int, 0, 2),
default=0,
const=1,
nargs='?',
help="Print more verbose messages for each additional verbose level. Use "
"`-v 1` typically for much more detailed logging. Higher numbers "
"are probably only useful in debugging.",
)
jobcontrol.add_argument(
'--no-progress-bar',
action='store_false',
dest='progress_bar',
help=argparse.SUPPRESS,
)
jobcontrol.add_argument(
'--use-threads', action='store_true', help=argparse.SUPPRESS
)
metadata = parser.add_argument_group(
"Metadata options",
"Set output PDF/A metadata (default: copy input document's metadata)",
)
metadata.add_argument(
'--title', type=str, help="Set document title (place multiple words in quotes)"
)
metadata.add_argument('--author', type=str, help="Set document author")
metadata.add_argument(
'--subject', type=str, help="Set document subject description"
)
metadata.add_argument('--keywords', type=str, help="Set document keywords")
preprocessing = parser.add_argument_group(
"Image preprocessing options",
"Options to improve the quality of the final PDF and OCR",
)
preprocessing.add_argument(
'-r',
'--rotate-pages',
action='store_true',
help="Automatically rotate pages based on detected text orientation",
)
preprocessing.add_argument(
'--remove-background',
action='store_true',
help="Attempt to remove background from gray or color pages, setting it "
"to white ",
)
preprocessing.add_argument(
'-d',
'--deskew',
action='store_true',
help="Deskew each page before performing OCR",
)
preprocessing.add_argument(
'-c',
'--clean',
action='store_true',
help="Clean pages from scanning artifacts before performing OCR, and send "
"the cleaned page to OCR, but do not include the cleaned page in "
"the output",
)
preprocessing.add_argument(
'-i',
'--clean-final',
action='store_true',
help="Clean page as above, and incorporate the cleaned image in the final "
"PDF. Might remove desired content.",
)
preprocessing.add_argument(
'--unpaper-args',
type=str,
default=None,
help="A quoted string of arguments to pass to unpaper. Requires --clean. "
"Example: --unpaper-args '--layout double'.",
)
preprocessing.add_argument(
'--oversample',
metavar='DPI',
type=numeric(int, 0, 5000),
default=0,
help="Oversample images to at least the specified DPI, to improve OCR "
"results slightly",
)
preprocessing.add_argument(
'--remove-vectors',
action='store_true',
help="EXPERIMENTAL. Mask out any vector objects in the PDF so that they "
"will not be included in OCR. This can eliminate false characters.",
)
preprocessing.add_argument(
'--threshold',
action='store_true',
help=(
"EXPERIMENTAL. Threshold image to 1bpp before sending it to Tesseract "
"for OCR. Can improve OCR quality compared to Tesseract's thresholder."
),
)
ocrsettings = parser.add_argument_group("OCR options", "Control how OCR is applied")
ocrsettings.add_argument(
'-f',
'--force-ocr',
action='store_true',
help="Rasterize any text or vector objects on each page, apply OCR, and "
"save the rastered output (this rewrites the PDF)",
)
ocrsettings.add_argument(
'-s',
'--skip-text',
action='store_true',
help="Skip OCR on any pages that already contain text, but include the "
"page in final output; useful for PDFs that contain a mix of "
"images, text pages, and/or previously OCRed pages",
)
ocrsettings.add_argument(
'--redo-ocr',
action='store_true',
help="Attempt to detect and remove the hidden OCR layer from files that "
"were previously OCRed with OCRmyPDF or another program. Apply OCR "
"to text found in raster images. Existing visible text objects will "
"not be changed. If there is no existing OCR, OCR will be added.",
)
ocrsettings.add_argument(
'--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",
)
optimizing = parser.add_argument_group(
"Optimization options", "Control how the PDF is optimized after OCR"
)
optimizing.add_argument(
'-O',
'--optimize',
type=int,
choices=range(0, 4),
default=1,
help=(
"Control how PDF is optimized after processing:"
"0 - do not optimize; "
"1 - do safe, lossless optimizations (default); "
"2 - do some lossy optimizations; "
"3 - do aggressive lossy optimizations (including lossy JBIG2)"
),
)
optimizing.add_argument(
'--jpeg-quality',
type=numeric(int, 0, 100),
default=0,
metavar='Q',
help=(
"Adjust JPEG quality level for JPEG optimization. "
"100 is best quality and largest output size; "
"1 is lowest quality and smallest output; "
"0 uses the default."
),
)
optimizing.add_argument(
'--jpg-quality',
type=numeric(int, 0, 100),
default=0,
metavar='Q',
dest='jpeg_quality',
help=argparse.SUPPRESS, # Alias for --jpeg-quality
)
optimizing.add_argument(
'--png-quality',
type=numeric(int, 0, 100),
default=0,
metavar='Q',
help=(
"Adjust PNG quality level to use when quantizing PNGs. "
"Values have same meaning as with --jpeg-quality"
),
)
optimizing.add_argument(
'--jbig2-lossy',
action='store_true',
help=(
"Enable JBIG2 lossy mode (better compression, not suitable for some "
"use cases - see documentation)."
),
)
optimizing.add_argument(
'--jbig2-page-group-size',
type=numeric(int, 1, 10000),
default=0,
metavar='N',
# Adjust number of pages to consider at once for JBIG2 compression
help=argparse.SUPPRESS,
)
advanced = parser.add_argument_group(
"Advanced", "Advanced options to control OCRmyPDF"
)
advanced.add_argument(
'--pages',
type=str,
help=(
"Limit OCR to the specified pages (ranges or comma separated), "
"skipping others"
),
)
advanced.add_argument(
'--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,
)
advanced.add_argument(
'--pdf-renderer',
choices=['auto', 'hocr', 'sandwich'],
default='auto',
help="Choose OCR PDF renderer - the default option is to let OCRmyPDF "
"choose. See documentation for discussion.",
)
advanced.add_argument(
'--rotate-pages-threshold',
default=14.0,
type=numeric(float, 0, 1000),
metavar='CONFIDENCE',
help="Only rotate pages when confidence is above this value (arbitrary "
"units reported by tesseract)",
)
advanced.add_argument(
'--pdfa-image-compression',
choices=['auto', 'jpeg', 'lossless'],
default='auto',
help="Specify how to compress images in the output PDF/A. 'auto' lets "
"OCRmyPDF decide. 'jpeg' changes all grayscale and color images to "
"JPEG compression. 'lossless' uses PNG-style lossless compression "
"for all images. Monochrome images are always compressed using a "
"lossless codec. Compression settings "
"are applied to all pages, including those for which OCR was "
"skipped. Not supported for --output-type=pdf ; that setting "
"preserves the original compression of all images.",
)
advanced.add_argument(
'--fast-web-view',
type=numeric(float, 0),
default=1.0,
metavar="MEGABYTES",
help="If the size of file is more than this threshold (in MB), then "
"linearize the PDF for fast web viewing. This allows the PDF to be "
"displayed before it is fully downloaded in web browsers, but increases "
"the space required slightly. By default we skip this for small files "
"which do not benefit. If the threshold is 0 it will be apply to all files. "
"Set the threshold very high to disable.",
)
advanced.add_argument(
'--plugin',
dest='plugins',
action='append',
default=[],
help="Name of plugin to import. Argument may be issued multiple times to "
"import multiple plugins. Plugins may be specified as module names in "
"Python syntax, provided they are installed in the same Python (virtual) "
"environment as ocrmypdf; or you may give the path to the Python file that "
"contains the plugin. Plugins must conform to the specification in the "
"OCRmyPDF documentation.",
)
debugging = parser.add_argument_group(
"Debugging", "Arguments to help with troubleshooting and debugging"
)
debugging.add_argument(
'-k',
'--keep-temporary-files',
action='store_true',
help="Keep temporary files (helpful for debugging)",
)
return parser
plugins_only_parser = ArgumentParser(
prog=_PROGRAM_NAME, fromfile_prefix_chars='@', add_help=False, allow_abbrev=False
)
plugins_only_parser.add_argument(
'--plugin',
dest='plugins',
action='append',
default=[],
help="Name of plugin to import.",
)
Binary file not shown.
+104
View File
@@ -0,0 +1,104 @@
# © 2016 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/.
from enum import IntEnum
from textwrap import dedent
class ExitCode(IntEnum):
ok = 0
bad_args = 1
input_file = 2
missing_dependency = 3
invalid_output_pdf = 4
file_access_error = 5
already_done_ocr = 6
child_process_error = 7
encrypted_pdf = 8
invalid_config = 9
pdfa_conversion_failed = 10
other_error = 15
ctrl_c = 130
class ExitCodeException(Exception):
exit_code = ExitCode.other_error
message = ""
def __str__(self):
super_msg = super().__str__() # Don't do str(super())
if self.message:
return self.message.format(super_msg)
return super_msg
class BadArgsError(ExitCodeException):
exit_code = ExitCode.bad_args
class PdfMergeFailedError(ExitCodeException):
exit_code = ExitCode.input_file
message = dedent(
'''\
Failed to merge PDF image layer with OCR layer
Usually this happens because the input PDF file is malformed and
ocrmypdf cannot automatically correct the problem on its own.
Try using
ocrmypdf --pdf-renderer sandwich [..other args..]
'''
)
class MissingDependencyError(ExitCodeException):
exit_code = ExitCode.missing_dependency
class UnsupportedImageFormatError(ExitCodeException):
exit_code = ExitCode.input_file
class DpiError(ExitCodeException):
exit_code = ExitCode.input_file
class OutputFileAccessError(ExitCodeException):
exit_code = ExitCode.file_access_error
class PriorOcrFoundError(ExitCodeException):
exit_code = ExitCode.already_done_ocr
class InputFileError(ExitCodeException):
exit_code = ExitCode.input_file
class SubprocessOutputError(ExitCodeException):
exit_code = ExitCode.child_process_error
class EncryptedPdfError(ExitCodeException):
exit_code = ExitCode.encrypted_pdf
message = dedent(
'''\
Input PDF is encrypted. The encryption must be removed to
perform OCR.
For information about this PDF's security use
qpdf --show-encryption infilename
You can remove the encryption using
qpdf --decrypt [--password=[password]] infilename
'''
)
class TesseractConfigError(ExitCodeException):
exit_code = ExitCode.invalid_config
message = "Error occurred while parsing a Tesseract configuration file"
+237
View File
@@ -0,0 +1,237 @@
# © 2016 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 logging
import multiprocessing
import os
import shutil
import warnings
from collections import namedtuple
from collections.abc import Iterable
from contextlib import suppress
from functools import wraps
from io import StringIO
from math import isclose
from pathlib import Path
from typing import Any, Sequence, TypeVar
import pikepdf
log = logging.getLogger(__name__)
class Resolution(namedtuple('Resolution', ('x', 'y'))):
"""The number of pixels per inch in each 2D direction."""
__slots__ = ()
def round(self, ndigits: int):
return Resolution(round(self.x, ndigits), round(self.y, ndigits))
def to_int(self):
return Resolution(int(round(self.x)), int(round(self.y)))
@property
def is_square(self) -> bool:
return isclose(self.x, self.y, rel_tol=1e-3)
def take_max(self, vals, yvals=None):
if yvals is not None:
return Resolution(max(self.x, *vals), max(self.y, *yvals))
max_x, max_y = self.x, self.y
for x, y in vals:
max_x = max(x, max_x)
max_y = max(y, max_y)
return Resolution(max_x, max_y)
def flip_axis(self):
return Resolution(self.y, self.x)
def __str__(self):
return f"{self.x:f}x{self.y:f}"
def __repr__(self):
return f"Resolution({self.x}x{self.y} dpi)"
def safe_symlink(input_file: os.PathLike, soft_link_name: os.PathLike):
"""
Helper function: relinks soft symbolic link if necessary
"""
input_file = os.fspath(input_file)
soft_link_name = os.fspath(soft_link_name)
# Guard against soft linking to oneself
if input_file == soft_link_name:
log.warning(
"No symbolic link made. You are using "
"the original data directory as the working directory."
)
return
# Soft link already exists: delete for relink?
if os.path.lexists(soft_link_name):
# do not delete or overwrite real (non-soft link) file
if not os.path.islink(soft_link_name):
raise FileExistsError(f"{soft_link_name} exists and is not a link")
try:
os.unlink(soft_link_name)
except OSError:
log.debug("Can't unlink %s", soft_link_name)
if not os.path.exists(input_file):
raise FileNotFoundError(f"trying to create a broken symlink to {input_file}")
if os.name == 'nt':
# Don't actually use symlinks on Windows due to permission issues
shutil.copyfile(input_file, soft_link_name)
return
log.debug("os.symlink(%s, %s)", input_file, soft_link_name)
# Create symbolic link using absolute path
os.symlink(os.path.abspath(input_file), soft_link_name)
def samefile(f1: os.PathLike, f2: os.PathLike):
if os.name == 'nt':
return f1 == f2
else:
return os.path.samefile(f1, f2)
def is_iterable_notstr(thing: Any) -> bool:
"""Is this is an iterable type, other than a string?"""
return isinstance(thing, Iterable) and not isinstance(thing, str)
def monotonic(L: Sequence) -> bool:
"""Does list increase monotonically?"""
return all(b > a for a, b in zip(L, L[1:]))
def page_number(input_file: os.PathLike) -> int:
"""Get one-based page number implied by filename (000002.pdf -> 2)"""
return int(os.path.basename(os.fspath(input_file))[0:6])
def available_cpu_count() -> int:
"""Returns number of CPUs in the system."""
try:
return multiprocessing.cpu_count()
except NotImplementedError:
pass
warnings.warn(
"Could not get CPU count. Assuming one (1) CPU." "Use -j N to set manually."
)
return 1
def is_file_writable(test_file: os.PathLike) -> bool:
"""Intentionally racy test if target is writable.
We intend to write to the output file if and only if we succeed and
can replace it atomically. Before doing the OCR work, make sure
the location is writable.
"""
try:
p = Path(test_file)
if p.is_symlink():
p = p.resolve(strict=False)
# p.is_file() throws an exception in some cases
if p.exists() and p.is_file():
return os.access(
os.fspath(p),
os.W_OK,
effective_ids=(os.access in os.supports_effective_ids),
)
else:
try:
fp = p.open('wb')
except OSError:
return False
else:
fp.close()
with suppress(OSError):
p.unlink()
return True
except (EnvironmentError, RuntimeError) as e:
log.debug(e)
log.error(str(e))
return False
def check_pdf(input_file: Path) -> bool:
"""Check if a PDF complies with the PDF specification.
Checks for proper formatting and proper linearization.
"""
pdf = None
try:
pdf = pikepdf.open(input_file)
except pikepdf.PdfError as e:
log.error(e)
return False
else:
messages = pdf.check()
for msg in messages:
if 'error' in msg.lower():
log.error(msg)
else:
log.warning(msg)
sio = StringIO()
linearize = None
try:
pdf.check_linearization(sio)
except RuntimeError:
pass
else:
linearize = sio.getvalue()
if linearize:
log.warning(linearize)
if not messages and not linearize:
return True
return False
finally:
if pdf:
pdf.close()
T = TypeVar('T')
def clamp(n: T, smallest: T, largest: T) -> T:
"""Clamps the value of n to between smallest and largest."""
return max(smallest, min(n, largest))
def pikepdf_enable_mmap():
try:
if pikepdf._qpdf.set_access_default_mmap(True):
log.debug("pikepdf mmap enabled")
except AttributeError:
log.debug("pikepdf mmap not available")
def deprecated(func):
"""Warn that function is deprecated"""
@wraps(func)
def new_func(*args, **kwargs):
warnings.simplefilter('always', DeprecationWarning) # turn off filter
warnings.warn(
"Call to deprecated function {}.".format(func.__name__),
category=DeprecationWarning,
stacklevel=2,
)
warnings.simplefilter('default', DeprecationWarning) # reset filter
return func(*args, **kwargs)
return new_func

Some files were not shown because too many files have changed in this diff Show More