1

I have a PDF document which I want to reduce in size. I've already tried PyPDF2 File Size reductions:

from PyPDF2 import PdfReader, PdfWriter

reader = PdfReader("example.pdf")
writer = PdfWriter()

for page in reader.pages:
    page.compress_content_streams()  # This is CPU intensive!
    writer.add_page(page)
writer.add_metadata(reader.metadata)

with open("out.pdf", "wb") as f:
    writer.write(f)

and I used

mutool clean -czggggfDi input.pdf output.pdf

but that didn't really change the size.

Using mutool extract input.pdf I can see that the 7.3 MB of the PDF are likely dominated by 4 images (img-0317.png: 6.4 MB and img-0201.jpg: 3.5 MB and img-0095.png: 1.3 MB and img-0133.png: 1.1 MB). Just storing those PNG images as JPG with gimp reduced the size from 6.4 MB to 1.4 MB.

How can I exchange the embedded images without changing anything else?

1 Answer 1

2

You can try Ghostscript (see here and, for optimizations, this German article) – or, for even more compression, pdfsizeopt (see here). The latter takes a lot longer, but gets better results on scanned text pages.

Ghostscript

Should be provided by your package management. On Debian/Ubuntu/Mint etc, you can install it using sudo apt install ghostscript. You then can use a shell script wrapper like the one I compiled going by the above sources:

#!/bin/bash
# Compress/Optimize PDF files
# see: https://askubuntu.com/a/256449/68291
# and: https://www.linux-community.de/ausgaben/linuxuser/2019/04/optimal-kombiniert/

[[ -z "$1" ]] && {
  echo
  echo "Compress/Optimize PDF files"
  echo "Syntax:"
  echo "  ${0} <InFile> [OutFile] [level]"
  echo ""
  echo "Levels:"
  echo "- screen:    72dpi (low res)"
  echo "- ebook:    150dpi (OK; default if not specified otherwise)"
  echo "- prepress: 300dpi (Acrobat Distiller 'Prepress Optimized')"
  echo "- printer:  300dpi (Acrobat Distiller 'Print Optimized')"
  echo "- default:  ???dpi (auto-select, usually results in bigger file)"
  exit 1
}

[[ ! -f "$1" ]] && {
  echo
  echo "Input File '${1}' not found, nothing to do."
  exit 5
}

outfile="$2"
[[ -z "$2" ]] && outfile=${1%\.pdf}_comp.pdf

[[ -f "$outfile" ]] && {
  echo
  echo "The specified output file '$outfile' does already exist, aborting."
  exit 3
}

case "$3" in
  screen)   level="-dPDFSETTINGS=/screen" ;;      #  72dpi
  prepress) level="-dPDFSETTINGS=/prepress" ;;    # 300dpi
  printer)  level="-dPDFSETTINGS=/printer" ;;     # 300dpi
  default)  level="-dPDFSETTINGS=/default" ;;     # auto-estimate
  *)        level="-dPDFSETTINGS=/ebook -dColorImageResolution=150 -dGrayImageResolution=150 -dMonoImageResolution=150 -dMonoImageDownsampleType=/Subsample" ;;       # 150dpi
esac;

gs -sDEVICE=pdfwrite \
-dColorImageDownsampleType=/Bicubic \
-dGrayImageDownsampleType=/Bicubic \
-dMonoImageDownsampleType=/Bicubic \
-dCompatibilityLevel=1.4 $level -dNOPAUSE -dQUIET -dBATCH -sOutputFile=$outfile $1

Called without parameters it tells you its syntax. With just the input PDF as parameter, it compresses images to 150dpi (ebook), writing output to a file having _comp (aka "compressed") appended to its basename.

PdfSizeOpt

This requires quite some binaries downloaded from different places (mostly Github). To make it easier, here's a script performing the "installation" steps – just run it in an empty directory:

wget https://github.com/pts/tif22pnm/releases/download/2014-01-09/png22pnm.xstatic
wget https://github.com/pts/sam2p/releases/download/v0.49.4/sam2p.xstatic
wget http://www.jonof.id.au/files/kenutils/pngout-20200115-linux.tar.gz
wget https://github.com/pts/pdfsizeopt-jbig2/releases/download/2017-01-24/jbig2.xstatic
wget https://raw.githubusercontent.com/pts/pdfsizeopt/master/pdfsizeopt.single

mv png22pnm.xstatic png22pnm
mv sam2p.xstatic sam2p
mv jbig2.xstatic jbig2
tar xf pngout-20200115-linux.tar.gz --strip-components=2 pngout-20200115-linux/amd64/pngout
chmod u+x pdfsizeopt.single png22pnm sam2p

Then you can use the following wrapper script to compress your PDF files (works very good on scanned text pages; not sure if it's really suited for graphical stuff):

#!/bin/bash
# see: https://askubuntu.com/a/1011292
# takes quite long but gets good results on scanned text pages, even leaving OCR intact

[[ -z "$1" ]] && {
  echo
  echo "Compress PDF consisting of scanned text pages."
  echo
  echo "Usage: $0 <infile>"
  echo
  echo "Run 'pdfsizeopt.single --help' for more details."
  echo
  exit
}

./pdfsizeopt.single --v=30 --do-fast-bilevel-images=yes $1 ${1%\.pdf}_opt.pdf

(if you want to call it from anywhere, adjust the path of ./pdfsizeopt.single to point to where the tools have been placed, and make an alias to the script)

I decided to include the --do-fast-bilevel-images=yes parameter as it speeds up processing a lot (on a test PDF: about 2min without the parameter, less than a second with the parameter), plus --v=30 to reduce output to a usable level. There are many more parameters you can play with to optimize results to your liking.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.