Refactor reporting of optimization failures

This commit is contained in:
James R. Barlow
2022-06-13 01:30:15 -07:00
parent 13d11e76e5
commit 17a5b8b43c
7 changed files with 64 additions and 41 deletions
+2 -2
View File
@@ -833,7 +833,7 @@ def metadata_fixup(working_file: Path, context: PdfContext):
def optimize_pdf(input_file: Path, context: PdfContext, executor: Executor):
output_file = context.get_path('optimize.pdf')
output_pdf = context.plugin_manager.hook.optimize_pdf(
output_pdf, messages = context.plugin_manager.hook.optimize_pdf(
input_pdf=input_file,
output_pdf=output_file,
context=context,
@@ -848,7 +848,7 @@ def optimize_pdf(input_file: Path, context: PdfContext, executor: Executor):
savings = 1 - output_size / input_size
log.info(f"Optimize ratio: {ratio:.2f} savings: {(savings):.1%}")
return output_pdf
return output_pdf, messages
def enumerate_compress_ranges(iterable):
+12 -6
View File
@@ -18,7 +18,7 @@ from concurrent.futures.thread import BrokenThreadPool
from functools import partial
from pathlib import Path
from tempfile import mkdtemp
from typing import List, NamedTuple, Optional, Tuple, cast
from typing import List, NamedTuple, Optional, Sequence, Tuple, cast
import PIL
@@ -230,7 +230,9 @@ def exec_page_sync(page_context: PageContext) -> PageResult:
)
def post_process(pdf_file: Path, context: PdfContext, executor: Executor) -> Path:
def post_process(
pdf_file: Path, context: PdfContext, executor: Executor
) -> Tuple[Path, Sequence[str]]:
pdf_out = pdf_file
if context.options.output_type.startswith('pdfa'):
ps_stub_out = generate_postscript_stub(context)
@@ -248,7 +250,7 @@ def worker_init(max_pixels: int) -> None:
pikepdf_enable_mmap()
def exec_concurrent(context: PdfContext, executor: Executor) -> None:
def exec_concurrent(context: PdfContext, executor: Executor) -> Sequence[str]:
"""Execute the pipeline concurrently"""
# Run exec_page_sync on every page context
@@ -300,13 +302,15 @@ def exec_concurrent(context: PdfContext, executor: Executor) -> None:
# Merge layers to one single pdf
pdf = ocrgraft.finalize()
messages: List[str] = []
if options.output_type != 'none':
# PDF/A and metadata
log.info("Postprocessing...")
pdf = post_process(pdf, context, executor)
pdf, messages = post_process(pdf, context, executor)
# Copy PDF file to destination
copy_final(pdf, options.output_file, context)
return messages
def configure_debug_logging(
@@ -386,7 +390,7 @@ def run_pipeline(
validate_pdfinfo_options(context)
# Execute the pipeline
exec_concurrent(context, executor)
optimize_messages = exec_concurrent(context, executor)
if options.output_file == '-':
log.info("Output sent to stdout")
@@ -412,7 +416,9 @@ def run_pipeline(
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)
report_output_file_size(
options, start_input_file, options.output_file, optimize_messages
)
except (KeyboardInterrupt if not api else NeverRaise):
if options.verbose >= 1:
+12 -16
View File
@@ -14,12 +14,12 @@ import sys
import unicodedata
from pathlib import Path
from shutil import copyfileobj
from typing import List, Set, Tuple
from typing import List, Optional, Sequence, Set, Tuple
import pikepdf
import PIL
from ocrmypdf._exec import jbig2enc, pngquant, unpaper
from ocrmypdf._exec import unpaper
from ocrmypdf.exceptions import (
BadArgsError,
InputFileError,
@@ -294,8 +294,15 @@ def check_requested_output_file(options):
def report_output_file_size(
options, input_file, output_file, file_overhead=4000, page_overhead=3000
options,
input_file: Path,
output_file: Path,
optimize_messages: Optional[Sequence[str]] = None,
file_overhead: int = 4000,
page_overhead: int = 3000,
):
if optimize_messages is None:
optimize_messages = []
try:
output_size = Path(output_file).stat().st_size
input_size = Path(input_file).stat().st_size
@@ -324,19 +331,8 @@ def report_output_file_size(
f"The argument --{arg.replace('_', '-')} was issued, causing transcoding."
)
if hasattr(options, 'optimize') and 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."
)
reasons.extend(optimize_messages)
if options.output_type.startswith('pdfa'):
reasons.append("PDF/A conversion was enabled. (Try `--output-type pdf`.)")
if options.plugins:
+18 -3
View File
@@ -10,6 +10,7 @@
import argparse
import logging
from pathlib import Path
from typing import Sequence, Tuple
from ocrmypdf import PdfContext, hookimpl
from ocrmypdf._concurrent import Executor
@@ -130,13 +131,27 @@ def optimize_pdf(
context: PdfContext,
executor: Executor,
linearize: bool,
) -> Path:
) -> Tuple[Path, Sequence[str]]:
save_settings = dict(
linearize=linearize,
**get_pdf_save_settings(context.options.output_type),
)
optimize(input_pdf, output_pdf, context, save_settings, executor)
return output_pdf
result_path = optimize(input_pdf, output_pdf, context, save_settings, executor)
messages = []
if context.options.optimize == 0:
messages.append("Optimization was disabled.")
else:
image_optimizers = {
'jbig2': jbig2enc.available(),
'pngquant': pngquant.available(),
}
for name, available in image_optimizers.items():
if not available:
messages.append(
f"The optional dependency '{name}' was not found, so some image "
f"optimizations could not be attempted."
)
return result_path, messages
@hookimpl
+1 -1
View File
@@ -629,7 +629,7 @@ def optimize(
options = context.options
if options.optimize == 0:
safe_symlink(input_file, output_file)
return
return output_file
if options.jpeg_quality == 0:
options.jpeg_quality = DEFAULT_JPEG_QUALITY if options.optimize < 3 else 40
+14 -2
View File
@@ -10,7 +10,15 @@ from abc import ABC, abstractmethod
from argparse import ArgumentParser, Namespace
from logging import Handler
from pathlib import Path
from typing import TYPE_CHECKING, AbstractSet, List, NamedTuple, Optional
from typing import (
TYPE_CHECKING,
AbstractSet,
List,
NamedTuple,
Optional,
Sequence,
Tuple,
)
import pluggy
@@ -467,7 +475,7 @@ def optimize_pdf(
context: PdfContext,
executor: Executor,
linearize: bool,
) -> Path:
) -> Tuple[Path, Sequence[str]]:
"""Optimize a PDF after image, OCR and metadata processing.
If the input_pdf is a PDF/A, the plugin should modify input_pdf in a way
@@ -490,6 +498,10 @@ def optimize_pdf(
Path: If optimization is successful, the hook should return ``output_file``.
If optimization does not produce a smaller file, the hook should return
``input_file``.
Sequence[str]: Any comments that the plugin wishes to report to the user,
especially reasons it was not able to further optimize the file. For
example, the plugin could report that a required third party was not
installed, so a specific optimization was not attempted.
Note:
This is a :ref:`firstresult hook<firstresult>`.
+5 -11
View File
@@ -145,22 +145,16 @@ def test_report_file_size(tmp_path, caplog):
pdf.Root.Dummy2 = waste_of_space + waste_of_space
pdf.save(out)
with patch('ocrmypdf._validation.jbig2enc.available', return_value=True), patch(
'ocrmypdf._validation.pngquant.available', return_value=True
):
vd.report_output_file_size(opts, in_, out)
assert 'No reason' in caplog.text
vd.report_output_file_size(opts, in_, out, ['The optional dependency...'])
assert 'optional dependency' in caplog.text
caplog.clear()
with patch('ocrmypdf._validation.jbig2enc.available', return_value=False), patch(
'ocrmypdf._validation.pngquant.available', return_value=True
):
vd.report_output_file_size(opts, in_, out)
assert 'optional dependency' in caplog.text
vd.report_output_file_size(opts, in_, out, [])
assert 'No reason' in caplog.text
caplog.clear()
opts = make_opts(in_, out, optimize=0, output_type='pdf')
vd.report_output_file_size(opts, in_, out)
vd.report_output_file_size(opts, in_, out, ["Optimization was disabled."])
assert 'disabled' in caplog.text
caplog.clear()