Insert last page between every page (1-A-2-A-3-A)

Post Reply
User avatar
magnussandstrom
Advanced member
Posts: 563
Joined: Thu Jul 30, 2020 6:34 pm
Location: Sweden
Contact:

Insert last page between every page (1-A-2-A-3-A)

Post by magnussandstrom »

What is the best way to build a flow that inserts the last page between every page, regardless of how many pages the incoming PDF contains?

We often receive business cards as PDF files where the last page contains the back of the card, while the other pages contain the fronts with different people's contact details.
jan_suhr
Advanced member
Posts: 703
Joined: Fri Nov 04, 2011 1:12 pm
Location: Nyköping, Sweden

Re: Insert last page between every page (1-A-2-A-3-A)

Post by jan_suhr »

Jan Suhr
Color Consult AB
Sweden
=============
Check out my apps
User avatar
magnussandstrom
Advanced member
Posts: 563
Joined: Thu Jul 30, 2020 6:34 pm
Location: Sweden
Contact:

Re: Insert last page between every page (1-A-2-A-3-A)

Post by magnussandstrom »

I can solve it, but it’s not pretty. I’m wondering how others have solved it, I guess that it’s quite common.
User avatar
magnussandstrom
Advanced member
Posts: 563
Joined: Thu Jul 30, 2020 6:34 pm
Location: Sweden
Contact:

Re: Insert last page between every page (1-A-2-A-3-A)

Post by magnussandstrom »

I ended upp building a small app using Python.

Code: Select all

import argparse
import os
import shutil
import sys
import tempfile
from pathlib import Path

import fitz  # PyMuPDF


EXIT_SUCCESS = 0
EXIT_USAGE_ERROR = 1
EXIT_INPUT_ERROR = 2
EXIT_PROCESSING_ERROR = 3


def info(message: str) -> None:
    """Write normal status messages to stdout."""
    print(message)


def error(message: str) -> None:
    """Write error messages to stderr."""
    print(message, file=sys.stderr)


def validate_input_path(path: Path) -> None:
    if not path.exists():
        raise FileNotFoundError(f"Input file does not exist: {path}")
    if not path.is_file():
        raise ValueError(f"Input is not a file: {path}")
    if path.suffix.lower() != ".pdf":
        raise ValueError(f"Input file must be a PDF: {path}")


def interleave_last_page(input_pdf: Path, output_pdf: Path) -> None:
    validate_input_path(input_pdf)
    output_pdf.parent.mkdir(parents=True, exist_ok=True)

    temp_path = None

    try:
        with fitz.open(str(input_pdf)) as src:

            if src.page_count < 3:
                raise ValueError("Input PDF must contain at least 3 pages.")

            last_page_index = src.page_count - 1
            expected_output_pages = (src.page_count - 1) * 2

            with fitz.open() as out:

                metadata = src.metadata or {}
                if metadata:
                    try:
                        out.set_metadata(metadata)
                    except Exception:
                        pass

                for i in range(last_page_index):
                    out.insert_pdf(src, from_page=i, to_page=i)
                    out.insert_pdf(src, from_page=last_page_index, to_page=last_page_index)

                if out.page_count != expected_output_pages:
                    raise RuntimeError(
                        f"Internal error: output has {out.page_count} pages "
                        f"(expected {expected_output_pages})."
                    )

                fd, temp_name = tempfile.mkstemp(
                    prefix=output_pdf.stem + "_",
                    suffix=".tmp.pdf",
                    dir=str(output_pdf.parent),
                )
                os.close(fd)
                temp_path = Path(temp_name)

                out.save(str(temp_path))

        shutil.move(str(temp_path), str(output_pdf))
        temp_path = None

        # SUCCESS LOG → STDOUT
        info(f"INPUT={input_pdf}")
        info(f"INPUT_PAGES={last_page_index + 1}")
        info(f"OUTPUT={output_pdf}")
        info(f"OUTPUT_PAGES={expected_output_pages}")
        info("STATUS=OK")

    finally:
        if temp_path is not None and temp_path.exists():
            try:
                temp_path.unlink()
            except Exception:
                pass


def parse_args(argv=None):
    parser = argparse.ArgumentParser(
        description="Insert the last page between all previous pages in a PDF."
    )
    parser.add_argument("input_pdf", help="Path to input PDF")
    parser.add_argument("output_pdf", help="Path to output PDF")
    return parser.parse_args(argv)


def main(argv=None) -> int:
    try:
        args = parse_args(argv)

        input_pdf = Path(args.input_pdf)
        output_pdf = Path(args.output_pdf)

        interleave_last_page(input_pdf, output_pdf)

        return EXIT_SUCCESS

    except SystemExit:
        raise

    except (FileNotFoundError, ValueError) as e:
        error(f"ERROR={e}")
        return EXIT_INPUT_ERROR

    except Exception as e:
        error(f"ERROR={e}")
        return EXIT_PROCESSING_ERROR


if __name__ == "__main__":
    sys.exit(main())
Post Reply