交互式菜单选择版: 运行脚本后,它会自动列出当前目录下的所有 PDF 文件并标上序号,如果只有 1 个就直接选中;如果有多个,你只需敲对应的数字编号即可,省去手打文件名的麻烦,且不会误处理无关文件。

交互选择版脚本:auto_unimpose.py

import copy
import glob
import os
import sys
from pypdf import PdfReader, PdfWriter


def select_pdf_file():
  """扫描当前目录下的 PDF,列出菜单供用户选择"""
  # 查找所有 pdf,排除之前生成的成果文件
  pdf_files = [
      f
      for f in glob.glob("*.pdf")
      if not f.endswith("_A4_顺序版.pdf") and not f.startswith("output_")
  ]

  if not pdf_files:
    print("[!] 错误:当前目录下没有找到任何待处理的 PDF 文件!")
    sys.exit(1)

  # 如果恰好只有 1 个文件,直接默认选中
  if len(pdf_files) == 1:
    print(f"[*] 发现唯一待处理文件: {pdf_files[0]}")
    return pdf_files[0]

  # 存在多个文件时,按修改时间降序排序并展示列表
  pdf_files.sort(key=lambda x: os.path.getmtime(x), reverse=True)
  print("=" * 45)
  print("请选择要处理的试卷 PDF 文件(输入序号):")
  for idx, fname in enumerate(pdf_files, 1):
    print(f"  [{idx}] {fname}")
  print("=" * 45)

  while True:
    choice = input(f"请输入数字序号 (1-{len(pdf_files)}),或按 'q' 退出: ").strip()
    if choice.lower() == "q":
      print("已取消操作。")
      sys.exit(0)

    if choice.isdigit():
      val = int(choice)
      if 1 <= val <= len(pdf_files):
        selected = pdf_files[val - 1]
        print(f"[*] 已选择: {selected}")
        return selected

    print("[!] 输入无效,请重新输入正确的数字。")


def run_unimpose():
  input_pdf_path = select_pdf_file()
  base_name = os.path.splitext(input_pdf_path)[0]
  output_pdf_path = f"{base_name}_A4_顺序版.pdf"

  reader = PdfReader(input_pdf_path)
  writer = PdfWriter()

  total_sheets = len(reader.pages)
  if total_sheets == 0:
    print("[!] 错误:该 PDF 没有任何页面!")
    return

  N = total_sheets * 2  # 自动推导总单页容量
  print(
      f"[*] 页面统计:共 {total_sheets} 个 B4 大面,切分后对应 {N} 页"
      " A4。"
  )

  pages_dict = {}

  for idx, page in enumerate(reader.pages):
    rect = page.mediabox
    width = float(rect.width)
    height = float(rect.height)
    mid_x = width / 2

    # 1. 裁剪左半页
    left_page = copy.copy(page)
    left_page.mediabox = copy.copy(rect)
    left_page.mediabox.upper_right = (mid_x, height)

    # 2. 裁剪右半页
    right_page = copy.copy(page)
    right_page.mediabox = copy.copy(rect)
    right_page.mediabox.lower_left = (mid_x, 0)

    # 3. 骑马钉解拼数学映射
    # idx 从 0 开始:
    # 偶数面 (正面): 左 = N - idx, 右 = 1 + idx
    # 奇数面 (反面): 左 = 1 + idx, 右 = N - idx
    if idx % 2 == 0:
      p_left = N - idx
      p_right = 1 + idx
    else:
      p_left = 1 + idx
      p_right = N - idx

    pages_dict[p_left] = left_page
    pages_dict[p_right] = right_page

  # 4. 按 1 ~ N 顺序写入新文件
  print("[*] 正在按页码重新装订排序...")
  for p_num in sorted(pages_dict.keys()):
    writer.add_page(pages_dict[p_num])

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

  print(f"[✓] 全部完成!生成文件已保存在同目录:\n    -> {output_pdf_path}")


if __name__ == "__main__":
  run_unimpose()

终端运行效果演示

如果有多个 PDF,运行 python auto_unimpose.py 时终端会弹出:

=============================================
请选择要处理的试卷 PDF 文件(输入序号):
  [1] 2024届模拟考试试卷_数学.pdf
  [2] 语文期末测试B4扫描.pdf
  [3] 英语强化试题.pdf
=============================================
请输入数字序号 (1-3),或按 'q' 退出: 1
[*] 已选择: 2024届模拟考试试卷_数学.pdf
[*] 页面统计:共 32 个 B4 大面,切分后对应 64 页 A4。
[*] 正在按页码重新装订排序...
[✓] 全部完成!生成文件已保存在同目录:
    -> 2024届模拟考试试卷_数学_A4_顺序版.pdf

如果目录下正好只有一个待处理的 PDF,它会自动直接执行,不再多问一步。