18

I have a few ebooks scanned from originals. They're formatted so that a single PDF page contains two actual pages: one on the left, and one on the right.

I want to programmatically split each PDF page into two, so the left 50% of PDF page 1 becomes page 1 and its right becomes page 2, and so on for all the pages.

Does anyone know of a command line utility or script that could help with this?


Output from pdfimages -list -f 1 -l 1 file.pdf:

page   num  type   width height color comp bpc  enc interp  object ID x-ppi y-ppi size ratio
--------------------------------------------------------------------------------------------
   1     0 image    1921  1561  rgb     3   8  jpeg   no       643  0   200   200  200K 2.3%
   1     1 stencil     1     1  -       1   1  image  no   [inline]   0.692     2    -    - 
   1     2 stencil     1     1  -       1   1  image  no   [inline]   0.722 0.650    -    - 
   1     3 stencil     1     1  -       1   1  image  no   [inline]       3     3    -    - 

Second PDF:

page   num  type   width height color comp bpc  enc interp  object ID x-ppi y-ppi size ratio
--------------------------------------------------------------------------------------------
   1     0 image     456   625  gray    1   8  jpx    yes      251  0    72    72 11.7K 4.2%
2
  • 1
    In the past I have successfully used a command line tool "mutool" to extract / slice-up PDF files. It's part of the mupdf package (see: mupdf.com). I don't know if it will work for you in this case however, as far as I understand the above output from pdfimages, your pdf file(s) seem to consist mostly of (or are a wrapper around) a single large JPEG image ...
    – Robb W.
    Nov 26, 2017 at 1:10
  • Does this answer your question? Split pages in pdf
    – user202729
    Nov 17 at 8:10

9 Answers 9

19
+100

This should work it needs pdftk tool ( and ghostscript ).

A simple case:

Step One: Split into individual pages

 pdftk clpdf.pdf burst

this produces files pg_0001.pdf, pg_0002.pdf, ... pg_NNNN.pdf, one for each page. It also produces doc_data.txt which contains page dimensions.

Step Two: Create left and right half pages

  pw=`cat doc_data.txt  | grep PageMediaDimensions | head -1 | awk '{print $2}'`
  ph=`cat doc_data.txt  | grep PageMediaDimensions | head -1 | awk '{print $3}'`
  w2=$(( pw / 2 ))
  w2px=$(( w2*10 ))
  hpx=$((  ph*10 ))
  for f in  pg_[0-9]*.pdf ; do
   lf=left_$f
   rf=right_$f
   gs -o ${lf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [0 0]>> setpagedevice" -f ${f}
   gs -o ${rf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [-${w2} 0]>> setpagedevice" -f ${f}
  done

Step Three: Merge left and right in order to produce newfile.pdf containing single page .pdf.

  ls -1 [lr]*_[0-9]*pdf | sort -n -k3 -t_ > fl
  pdftk `cat fl`  cat output newfile.pdf 

A more general case:

  1. The example above assumes all pages are same size. The doc_data.txt file contains size for each split page. If the command

    grep PageMediaDimensions <doc_data.txt | sort | uniq | wc -l

    does not return 1 then the pages have different dimensions and some extra logic is needed in Step Two.

  2. If the split is not exactly 50:50 then a better formula than w2=$(( pw / 2 )), used in the example above, is needed.

This second example shows how to handle this more general case.

Step One: split with pdftk as before

Step Two: Now create three files that contain the width and height of each pages and a default for the fraction of the split the left page will use.

  grep PageMediaDimensions <doc_data.txt | awk '{print $2}'    >   pws.txt
  grep PageMediaDimensions <doc_data.txt | awk '{print $3}'    > phs.txt
  grep PageMediaDimensions <doc_data.txt | awk '{print "0.5"}' > lfrac.txt

the file lfrac.txt can be hand edited if information is available for where to split different pages.

Step Three: Now create left and right split pages, using the different pages sizes and (if edited) different fractional locations for the split.

#!/bin/bash
exec 3<pws.txt
exec 4<phs.txt
exec 5<lfrac.txt

for f in  pg_[0-9]*.pdf ; do
 read <&3 pwloc
 read <&4 phloc
 read <&5 lfr
 wl=`echo "($lfr)"'*'"$pwloc" | bc -l`;wl=`printf "%0.f" $wl`
 wr=$(( pwloc - wl ))
 lf=left_$f
 rf=right_$f
 hpx=$((  phloc*10 ))
 w2px=$(( wl*10 ))
 gs -o ${lf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [0 0]>> setpagedevice" -f ${f}
 w2px=$(( wr*10 ))
 gs -o ${rf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [-${wl} 0]>> setpagedevice" -f ${f}
done

Step Four: This is the same merge step as in the previous, simpler, example.

  ls -1 [lr]*_[0-9]*pdf | sort -n -k3 -t_ > fl
  pdftk `cat fl`  cat output newfile.pdf 
4
  • Wow this is impressive, Chris, thank you so much! I noticed on Fedora, pdftk isn't available, so I used tools from poppler-utils: pdfseparate clpdf.pdf pg_%04d.pdf and pdfunite $(cat fl) newfile.pdf
    – hnasarat
    Apr 11, 2020 at 4:29
  • I just realized that this will double the PDF filesize. It seems each page is duplicated, just with half the data invisible! I wonder how to fix…
    – hnasarat
    Apr 11, 2020 at 4:50
  • Thanks for a very comprehensive answer! I believe the height and width are confused so the definitions of pw and ph should be switched around so pw is defined using awk '{print $3}' and ph uses awk '{print $2}'. I have tested this – however, I am using pfdtk-java which is a reimplementation of pdftk in Java so I would not edit the answer right away.
    – mgd
    Dec 5, 2022 at 14:23
  • This answer does not work for me when the page size is not an integer, so I modified it (and also package it into a script) and post it in another answer below.
    – user202729
    Nov 10 at 12:04
6

You can widen your choice of tools by converting the pdf to PostScript as follows, then using pstops. I've assumed we start from an A4 portrait page showing two pages as they might have been scanned from an open book, with the spine going horizontally through the middle, like this:

original

Obviously, you can change the values in the solution below to fit your precise case.

You can convert this pdf to PostScript with pdf2ps (which is part of the ghostscript package). Then tool pstops from package psutils, can be used to rotate the page right (clockwise) around the bottom left corner, rescale it and move the result up so that only the bottom half covers a whole page:

one page

A second page can be created from the same original page by a similar rotation, scale, and translation. The result can be converted back to pdf. A single command can draw each page onto 2 new pages:

pdf2ps myfile.pdf out.ps
pstops -p a4 '[email protected](1cm,29cm),[email protected](-16cm,29cm)' out.ps new.ps
ps2pdf new.ps new.pdf

The syntax is explained in the man page. Here we have R for rotate right, @1.2 to scale, (x,y) to move the result. The comma (,) produces 2 pages from each original page.

Note that this will double the size of the resulting pdf, since each page is fully drawn twice, even though you only see half of it each time.

6

You specifically ask for a solution for the command line - probably because you do not want to sit and select every single page.

But if it is acceptable to open every book, then pdfarranger will work.

It will not double the size of the PDF-file.

Before splitting After splitting

3

You want Libpoppler, or more precisely the pdfimages tool therein. It is free software, will extract the images from the PDF. If the PDF contains scanned images, they are not always oriented correctly, off by a few degrees. If the page contains two images, one for each scanned page, it becomes easy ... if not, you will have to cut them manually (dirty) or try ImageMagick to split them.

http://poppler.freedesktop.org/

http://en.wikipedia.org/wiki/Pdfimages

Taken from stackoverflow.

2

You might want to look at imagemagick:

$ convert -resize 1000x1000 /links/www/Salix/pdf/index.pdf a.jpg
$ convert -crop 500x1000+0+0  a.jpg b.jpg
$ convert -crop 500x1000+500+0 a.jpg c.jpg
$ convert c.jpg c.pdf
$ convert b.jpg b.pdf
1

I suggest PDFjam and its daughter script PDFjoin. First crop the left side and save as left.pdf and then crop the right side and save as right.pdf and then merge individual pages of the two files. You can read the examples on the page for inspiration. If that’s too easy for you, you could go the Ghostscript route, but then you’ll have to do some pixel math, external page counting and a final concat etc.

Don’t go the jpg route - you’ll get crazy file bloat and artifact generation.

1

You can do it with sejda-console 3:

sejda-console splitbyevery -n 2 -f input.pdf --outputPrefix '[BASENAME]_[FILENUMBER###]' --output .

[BASENAME]_[FILENUMBER###] means it will create

input_001.pdf
input_002.pdf
...
1

Solution

Taken from https://stackoverflow.com/a/30617745/5267751 with a small number of modifications. Only works for A4 → A5 paper.

Save the script to a file, then call it as instructed.

#!/bin/bash
set -o errexit
if [[ $# != 3 ]]; then echo "Usage: $0 <input.pdf> <output.pdf> <rotation degrees (0, 90, 180, 270)>"; exit 1; fi

tmpdir="$(mktemp -d)";   trap 'rm -rf "$tmpdir"' EXIT;  olddir="$(pwd)"
cp "$1" "$tmpdir"/input.pdf;  cd "$tmpdir"

qpdf --rotate="$3" input.pdf rotated.pdf
pdfcrop --bbox "0 0 421 595"   --clip --papersize "a5" rotated.pdf left.pdf
pdfcrop --bbox "421 0 842 595" --clip --papersize "a5" rotated.pdf right.pdf
pdftk A=left.pdf B=right.pdf shuffle A B output output.pdf

cd "$olddir"
mv "$tmpdir/out.pdf" "$2"

Alternative solution

Modifying the existing answer, using pdftk and gs.

This handles non-integer page size (use python to calculate the values), and can rotate the page beforehand.

Save the script to a file, then call it as instructed.

#!/bin/bash
set -o errexit
if [[ $# != 3 ]]; then echo "Usage: $0 <input.pdf> <output.pdf> <rotation degrees (0, 90, 180, 270)>"; exit 1; fi

tmpdir="$(mktemp -d)"
trap 'rm -rf "$tmpdir"' EXIT
olddir="$(pwd)"
qpdf --rotate="$3" "$1" "$tmpdir/tmp.pdf"
cd "$tmpdir"

pdftk tmp.pdf burst
grep PageMediaDimensions <doc_data.txt | awk '{print $2}'    > pws.txt
grep PageMediaDimensions <doc_data.txt | awk '{print $3}'    > phs.txt
grep PageMediaDimensions <doc_data.txt | awk '{print "0.5"}' > lfrac.txt
exec 3<pws.txt
exec 4<phs.txt
exec 5<lfrac.txt
for f in  pg_[0-9]*.pdf ; do
    read <&3 pwloc
    read <&4 phloc
    read <&5 lfr
    wl=$(python -c "print($lfr*$pwloc)")
    wr=$(python -c "print($pwloc-$wl)")
    lf=left_$f
    rf=right_$f
    hpx=$(python -c "print(round($phloc*10))")
    w2px=$(python -c "print(round($wl*10))")
    gs -o ${lf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [0 0]>> setpagedevice" -f ${f}
    w2px=$(python -c "print(round($wr*10))")
    gs -o ${rf} -sDEVICE=pdfwrite -g${w2px}x${hpx} -c "<</PageOffset [-${wl} 0]>> setpagedevice" -f ${f}
done
pdftk $(ls -1 [lr]*_[0-9]*pdf | sort -n -k3 -t_) cat output out.pdf

cd "$olddir"
mv "$tmpdir/out.pdf" "$2"
0

PDFcreator has a com plugin you may be able to communicate to. But you need the "plus" edition, which is $10 a year.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .