[Python] 纯文本查看 复制代码
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
img2singlepdf.py - Convert each image to its own PDF (same basename).
Examples:
python img2singlepdf.py imgs/*.jpg
python img2singlepdf.py folder_with_images --outdir export_pdfs --pagesize A4 --margin 36
python img2singlepdf.py *.png --fit contain --autorotate
"""
import argparse
import os
import re
import sys
import glob
from typing import List, Tuple
from PIL import Image, ImageOps
PRESETS = {
"A4": (595, 842), # 210x297 mm at 72 dpi
"LETTER": (612, 792), # 8.5x11 in at 72 dpi
"LEGAL": (612, 1008),
"A3": (842, 1191),
"A5": (420, 595),
}
def parse_pagesize(s: str) -> Tuple[int, int]:
if not s:
return PRESETS["A4"]
key = s.strip().upper()
if key in PRESETS:
return PRESETS[key]
m = re.match(r"^\s*(\d+)\s*[xX]\s*(\d+)\s*$", s)
if m:
return int(m.group(1)), int(m.group(2))
raise argparse.ArgumentTypeError(f"Invalid pagesize: {s}. Use A4, Letter, or WxH like 1240x1754.")
def collect_images(paths: List[str]) -> List[str]:
imgs = []
for p in paths:
if os.path.isdir(p):
for root, _, files in os.walk(p):
for f in files:
if re.search(r"\.(jpe?g|png|webp|tiff?|bmp)$", f, re.I):
imgs.append(os.path.join(root, f))
else:
expanded = glob.glob(p)
if expanded:
imgs.extend([e for e in expanded if os.path.isfile(e)])
elif os.path.isfile(p):
imgs.append(p)
# 去重并保持顺序
seen, uniq = set(), []
for x in imgs:
ab = os.path.abspath(x)
if ab not in seen:
seen.add(ab)
uniq.append(ab)
return uniq
def to_rgb_safely(im: Image.Image) -> Image.Image:
if im.mode in ("RGBA", "LA"):
bg = Image.new("RGB", im.size, (255, 255, 255))
bg.paste(im, mask=im.split()[-1])
return bg
if im.mode != "RGB":
return im.convert("RGB")
return im
def autorotate_page_size(img_w: int, img_h: int, page_w: int, page_h: int):
if (img_w >= img_h) != (page_w >= page_h):
return page_h, page_w
return page_w, page_h
def resize_with_fit(img: Image.Image, target_w: int, target_h: int, fit: str) -> Image.Image:
fit = fit.lower()
if fit == "none":
return img
if fit == "stretch":
return img.resize((target_w, target_h), Image.LANCZOS)
if fit == "contain":
return ImageOps.contain(img, (target_w, target_h), Image.LANCZOS)
if fit == "cover":
# scale to cover then center-crop
sw, sh = img.size
ratio = max(target_w / sw, target_h / sh)
new_size = (max(1, int(sw * ratio)), max(1, int(sh * ratio)))
img = img.resize(new_size, Image.LANCZOS)
left = (img.width - target_w) // 2
top = (img.height - target_h) // 2
return img.crop((left, top, left + target_w, top + target_h))
raise ValueError(f"Unknown fit: {fit}")
def make_pdf_for_image(img_path: str, outdir: str, pagesize: Tuple[int,int], margin: int, fit: str, autorotate: bool, dpi: int):
base = os.path.splitext(os.path.basename(img_path))[0]
out_pdf = os.path.join(outdir, base + ".pdf")
with Image.open(img_path) as im:
im = to_rgb_safely(im)
points_w, points_h = pagesize
if autorotate:
points_w, points_h = autorotate_page_size(im.width, im.height, points_w, points_h)
pixel_w = int(points_w * dpi / 72 + 0.5)
pixel_h = int(points_h * dpi / 72 + 0.5)
pixel_margin = int(margin * dpi / 72 + 0.5)
cw, ch = max(1, pixel_w - 2 * pixel_margin), max(1, pixel_h - 2 * pixel_margin)
page = Image.new("RGB", (pixel_w, pixel_h), (255, 255, 255))
work = resize_with_fit(im, cw, ch, fit)
# 居中粘贴
x = pixel_margin + (cw - work.width) // 2
y = pixel_margin + (ch - work.height) // 2
page.paste(work, (x, y))
# 保存为单页 PDF,Title 使用原文件名
page.save(out_pdf, "PDF", resolution=float(dpi), save_all=False, title=base)
return out_pdf
def main():
ap = argparse.ArgumentParser(description="Convert each image to its own PDF with same basename.")
ap.add_argument("inputs", nargs="+", help="Image files, folders, or globs.")
ap.add_argument("--outdir", default=".", help="Output directory for PDFs. Default: current directory")
ap.add_argument("--pagesize", default="A4", type=parse_pagesize,
help="Page size (A4/Letter/Legal/A3/A5 or WxH like 1240x1754). Default: A4")
ap.add_argument("--margin", type=int, default=36, help="Margin in points (72pt = 1 inch). Default: 36")
ap.add_argument("--fit", choices=["contain", "cover", "stretch", "none"], default="contain",
help="How image fits the page content area. Default: contain")
ap.add_argument("--autorotate", action="store_true", help="Auto-rotate page orientation to match image.")
ap.add_argument("--dpi", type=int, default=300, help="Output resolution in DPI (dots per inch). Default: 300")
args = ap.parse_args()
imgs = collect_images(args.inputs)
if not imgs:
print("No images found.", file=sys.stderr)
sys.exit(1)
os.makedirs(args.outdir, exist_ok=True)
page_w, page_h = args.pagesize
margin = max(0, int(args.margin))
ok, fail = 0, 0
for p in imgs:
try:
out_pdf = make_pdf_for_image(
p, args.outdir, (page_w, page_h), margin, args.fit, args.autorotate, args.dpi
)
print(f"OK -> {out_pdf}")
ok += 1
except Exception as e:
print(f"FAIL -> {p}: {e}", file=sys.stderr)
fail += 1
print(f"Done. Success: {ok}, Failed: {fail}")
if __name__ == "__main__":
main()
运行命令
[Python] 纯文本查看 复制代码python tu.py imgs/*.jpg
获取当前目录的imgs文件夹所有jpg格式的图片转换成PDF到当前目录
指定dpi
[Python] 纯文本查看 复制代码python tu.py imgs/*.jpg --dpi 600
指定 转换的dpi 为600 默认300
