功能描述

老大最近一直需要将一个好几兆的pdf文件压缩到2mb甚至1mb以内。通常的做法可能会是打印pdf文件,然后用打印机设置生产很小的pdf文件,或者就是截图保存等等。但都有点太傻瓜了,这时候还是python大法好,网上摘取了一部分别人的代码,稍作修改,就变成了一个能用的小工具。在此感谢各位大佬的资源。目前差不多能用,功能如下:

  1. 从PDF中提取图片
  2. 将图片进行质量和尺寸大小的压缩
  3. 生成新的PDF文件

源代码

1.1 安装必要的库

先安装库 fitz,再安装库pymupdf,地址:https://github.com/pymupdf/PyMuPDF/

pip install fitz

pip install pymupdf
1.2 源代码

第一个pdf2pic从pdf中提取jpg文件的部分引用了别人的代码
以下两行doc.引用的注意了,不然会报错

lenXREF = doc.xref_length()
text = doc.xref_object(i)  # 定义对象字符串

另外加入了重新调整过大的照片尺寸,和保存照片的质量,这里有个变量comp_ratio

im = im.resize((1376, y_s), Image.ANTIALIAS)
im.save(pic_path_d, quality=comp_ratio)
import fitz
import re
import os
from PIL import Image
from tkinter import filedialog


def pdf2pic(path, pic_path, comp_ratio):
    checkXO = r"/Type(?= */XObject)"  # 使用正则表达式来查找图片
    checkIM = r"/Subtype(?= */Image)"
    doc = fitz.open(path)  # 打开pdf文件
    imgcount = 0  # 图片计数
    lenXREF = doc.xref_length()    # 获取对象数量长度

    # 打印PDF的信息
    print("文件名:{}, 页数: {}, 对象: {}".format(path, len(doc), lenXREF - 1))

    # 遍历每一个对象
    for i in range(1, lenXREF):
        text = doc.xref_object(i)  # 定义对象字符串
        isXObject = re.search(checkXO, text)  # 使用正则表达式查看是否是对象
        isImage = re.search(checkIM, text)  # 使用正则表达式查看是否是图片
        if not isXObject or not isImage:  # 如果不是对象也不是图片,则continue
            continue
        imgcount += 1
        pix = fitz.Pixmap(doc, i)  # 生成图像对象
        new_name = "pic{}.jpg".format(imgcount)  # 生成图片的名称
        print(new_name)
        if pix.n < 5:  # 如果pix.n<5,可以直接存为PNG
            pic_path_d = os.path.join(pic_path, new_name)
            pix.writeImage(os.path.join(pic_path, new_name))
            im = Image.open(pic_path_d)
            x, y = im.size
            if x > 1376:
                y_s = int(y * 1376 / x)
                im = im.resize((1376, y_s), Image.ANTIALIAS)

            im.save(pic_path_d, quality=comp_ratio)
        else:  # 否则先转换CMYK
            pix0 = fitz.Pixmap(fitz.csRGB, pix)
            pix0.writeImage(os.path.join(pic_path, new_name))
            pix0 = None

        pix = None  # 释放资源
        print("提取了{}张图片".format(imgcount))

    os.startfile(pic_path)

下面这个rea是用来将文件夹内的照片重新组合为pdf文件

def rea(path, pdf_name):
    file_list = os.listdir(path)
    pic_name = []
    im_list = []
    for x in file_list:
        if "jpg" in x or 'png' in x or 'jpeg' in x:
            pic_name.append(x)

    pic_name.sort()
    new_pic = []

    for x in pic_name:
        if "jpg" in x:
            new_pic.append(x)

    for x in pic_name:
        if "png" in x:
            new_pic.append(x)

    print("hec", new_pic)

    im1 = Image.open(os.path.join(path, new_pic[0]))
    new_pic.pop(0)
    for i in new_pic:
        img = Image.open(os.path.join(path, i))
        # im_list.append(Image.open(i))
        if img.mode == "RGBA":
            img = img.convert('RGB')
            im_list.append(img)
        else:
            im_list.append(img)
    im1.save(pdf_name, "PDF", resolution=100.0, save_all=True, append_images=im_list)
    print("输出文件名称:", pdf_name)


def pdf_out():
    print('功能完善中')

主程序中随意加了一些判断,如压缩等级1、2、3等。

if __name__ == '__main__':
    print("Hello world!请先输入压缩等级1~3,然后在弹出的对话框中选择需要压缩的文件")
    comp_level = input("压缩等级(1=高画质,2=中画质,3=低画质):(输入数字并按回车键)")
    ratio = 10
    if comp_level == "1":
        ratio = 20
    elif comp_level == "2":
        ratio = 10
    elif comp_level == "3":
        ratio = 5
    '''打开选择文件夹对话框'''
    filepath = filedialog.askopenfilename()  # 获得选择好的文件
    print('选择的PDF地址:', filepath)
    if os.path.exists("./pdf_output"):
        pass
    else:
        os.mkdir("./pdf_output")
    pic_path = str(os.getcwd()) + "\pdf_output"
    print('提取图片的输出地址:', pic_path )
    pdf2pic(filepath, pic_path, comp_ratio=ratio)

    pdf_name = 'Compressed.pdf'
    if ".pdf" in pdf_name:
        rea(pic_path, pdf_name=pdf_name)
    else:
        rea(pic_path, pdf_name="{}.pdf".format(pdf_name))

    print("压缩完成,请关闭窗口。若压缩等级不合适,请先删除图片和文件并重新打开程序。")