From 790d3022f68acfc6b9b4247b0593e6f956621fdd Mon Sep 17 00:00:00 2001 From: "James R. Barlow" Date: Sun, 26 Sep 2021 01:07:34 -0700 Subject: [PATCH] Implement --output-type=none to skip producing the PDF and use only the sidecar Closes #787 --- misc/completion/ocrmypdf.fish | 1 + src/ocrmypdf/_sync.py | 15 ++++++++------- src/ocrmypdf/_validation.py | 18 ++++++++++++++---- src/ocrmypdf/cli.py | 5 +++-- tests/conftest.py | 5 +++++ tests/test_main.py | 26 ++++++++++++++++++++++++++ tests/test_validation.py | 6 ++++++ 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/misc/completion/ocrmypdf.fish b/misc/completion/ocrmypdf.fish index d085acdd..d4bb76d3 100644 --- a/misc/completion/ocrmypdf.fish +++ b/misc/completion/ocrmypdf.fish @@ -54,6 +54,7 @@ function __fish_ocrmypdf_output_type 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") + echo -e "none\t"(_ "do not produce an output PDF (for example, if you only care about --sidecar)") end complete -c ocrmypdf -x -l output-type -a '(__fish_ocrmypdf_output_type)' -d "select PDF output options" diff --git a/src/ocrmypdf/_sync.py b/src/ocrmypdf/_sync.py index b6611595..31693f9e 100644 --- a/src/ocrmypdf/_sync.py +++ b/src/ocrmypdf/_sync.py @@ -290,15 +290,16 @@ def exec_concurrent(context: PdfContext, executor: Executor): # Copy text file to destination copy_final(text, options.sidecar, context) - # Merge layers to one single pdf - pdf = ocrgraft.finalize() + if options.output_type != 'none': + # Merge layers to one single pdf + pdf = ocrgraft.finalize() - # PDF/A and metadata - log.info("Postprocessing...") - pdf = post_process(pdf, context, executor) + # PDF/A and metadata + log.info("Postprocessing...") + pdf = post_process(pdf, context, executor) - # Copy PDF file to destination - copy_final(pdf, options.output_file, context) + # Copy PDF file to destination + copy_final(pdf, options.output_file, context) def configure_debug_logging(log_filename: Path, prefix: str = ''): diff --git a/src/ocrmypdf/_validation.py b/src/ocrmypdf/_validation.py index 47d97680..15c45226 100644 --- a/src/ocrmypdf/_validation.py +++ b/src/ocrmypdf/_validation.py @@ -26,7 +26,7 @@ from ocrmypdf.exceptions import ( MissingDependencyError, OutputFileAccessError, ) -from ocrmypdf.helpers import is_file_writable, monotonic, safe_symlink +from ocrmypdf.helpers import is_file_writable, monotonic, safe_symlink, samefile from ocrmypdf.hocrtransform import HOCR_OK_LANGS from ocrmypdf.subprocess import check_external_program @@ -75,12 +75,18 @@ def check_options_output(options): is_latin = options.languages.issubset(HOCR_OK_LANGS) if options.pdf_renderer.startswith('hocr') and not is_latin: - msg = ( + log.warning( "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." + "`--pdf-renderer auto` (the default) to avoid this issue." + ) + + if options.output_type == 'none' and options.output_file != os.devnull: + raise BadArgsError( + "Since you specified `--pdf-renderer none`, the output file " + f"{options.output_file} cannot be produced. Set the output file to " + f"{os.devnull} to suppress this message." ) - log.warning(msg) lossless_reconstruction = False if not any( @@ -107,6 +113,10 @@ def check_options_sidecar(options): raise BadArgsError( "--sidecar filename must be specified when output file is stdout." ) + elif options.output_file == os.devnull: + raise BadArgsError( + "--sidecar filename must be specified when output file is /dev/null or NUL." + ) options.sidecar = options.output_file + '.txt' if options.sidecar == options.input_file or options.sidecar == options.output_file: raise BadArgsError( diff --git a/src/ocrmypdf/cli.py b/src/ocrmypdf/cli.py index eab0862f..87012321 100644 --- a/src/ocrmypdf/cli.py +++ b/src/ocrmypdf/cli.py @@ -147,7 +147,7 @@ Online documentation is located at: ) parser.add_argument( '--output-type', - choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3'], + choices=['pdfa', 'pdf', 'pdfa-1', 'pdfa-2', 'pdfa-3', 'none'], default='pdfa', help="Choose output type. 'pdfa' creates a PDF/A-2b compliant file for " "long term archiving (default, recommended) but may not suitable " @@ -155,7 +155,8 @@ Online documentation is located at: "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.", + "PDF/A3-b file. 'none' will produce no output, which may be helpful if " + "only the --sidecar is desired.", ) # Use null string '\0' as sentinel to indicate the user supplied no argument, diff --git a/tests/conftest.py b/tests/conftest.py index 9a059c83..f7874255 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -69,6 +69,11 @@ def outpdf(tmp_path): return tmp_path / 'out.pdf' +@pytest.fixture(scope="function") +def outtxt(tmp_path): + return tmp_path / 'out.txt' + + @pytest.fixture(scope="function") def no_outpdf(tmp_path): """This just documents the fact that a test is not expected to produce diff --git a/tests/test_main.py b/tests/test_main.py index 0bd69ae9..92e1c97f 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -881,3 +881,29 @@ def test_image_dpi_threshold(resources, outpdf): 'tests/plugins/tesseract_noop.py', ) assert outpdf.exists() + + +def test_outputtype_none_bad_setup(resources, outpdf): + p, _out, err = run_ocrmypdf( + resources / 'trivial.pdf', + outpdf, + '--output-type=none', + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + assert p.returncode == ExitCode.bad_args + assert 'Set the output file to' in err + + +def test_outputtype_none(resources, outtxt): + p, _out, err = run_ocrmypdf( + resources / 'trivial.pdf', + os.devnull, + '--output-type=none', + '--sidecar', + outtxt, + '--plugin', + 'tests/plugins/tesseract_noop.py', + ) + assert p.returncode == ExitCode.ok + assert outtxt.exists() diff --git a/tests/test_validation.py b/tests/test_validation.py index a027e17a..fdd5080d 100644 --- a/tests/test_validation.py +++ b/tests/test_validation.py @@ -6,6 +6,7 @@ import logging +import os from unittest.mock import patch import pikepdf @@ -298,3 +299,8 @@ def test_sidecar_equals_output(resources, no_outpdf): op = no_outpdf with pytest.raises(BadArgsError, match=r'--sidecar'): run_ocrmypdf_api(resources / 'trivial.pdf', op, '--sidecar', op) + + +def test_devnull_sidecar(resources): + with pytest.raises(BadArgsError, match=r'--sidecar.*NUL'): + run_ocrmypdf_api(resources / 'trivial.pdf', os.devnull, '--sidecar')