用python做PDF本地化压缩,新增多进程
用python做PDF壓縮
雖然現在有很多成熟的工具了,但是就是想自己搗鼓一下
在網上找了一圈,發現實現方法有兩種,一種是需要聯網上傳(TinyPNG的API)壓縮的,一種是本地用python算法
這里采用的是本地,基本的思路是
1、提取PDF內容,保存成圖片
2、壓縮圖片
3、圖片合成PDF
4、新增加入多進程和隊列的方式,加快壓縮
聯網上傳的我覺得直接用i love pdf這個網頁,挺好用的,就不知道安不安全。。。
Compress PDF online. Same PDF quality less file size (ilovepdf.com)
這里參考了兩篇博客的代碼
(2條消息) Python實現PDF文件壓縮_xinxinbupp的博客-CSDN博客
(2條消息) Python-從PDF中提取圖片、壓縮PDF_xinRCNN的博客-CSDN博客
但是感覺壓縮出來的圖片不是很理想,就想找一個圖片壓縮算法替換上去
在網上找到一個python的圖片壓縮算法,說是**“可能是最接近微信朋友圈的圖片壓縮算法”**
GitHub - Freefighter/Luban-Py: Python version of Luban(魯班)—Image compression with efficiency very close to WeChat Moments/可能是最接近微信朋友圈的圖片壓縮算法
依賴安裝
先安裝庫 fitz,再安裝庫pymupdf,地址:https://github.com/pymupdf/PyMuPDF/
pip install fitzpip install PyMuPDF pip install easygui # 用來彈出文件選擇框的,thinker的話會彈出兩個窗口怪怪的縫合修改
CV大法用上
# -*- coding:utf-8 -*- # author: peng # file: mypdf.py # time: 2021/9/8 17:47 # desc:壓縮PDF,對純圖片的PDF效果效果較好,有文字內容的可能會比較模糊,推薦高質量的壓縮import fitz from PIL import Image import os from shutil import copyfile, rmtree from math import ceilfrom time import strftime, localtime, time import easygui as gfrom functools import wraps # 時間計數裝飾器 def runtime(func):@wraps(func)def wrapper(*args, **kwargs):print(strftime("%Y-%m-%d %H:%M:%S", localtime()))start = time()func(*args, **kwargs)end = time()print(func.__name__, args[-1], args[-2], " spend time ", end - start, " sec")return wrapperclass Luban(object):def __init__(self, quality, ignoreBy=102400):self.ignoreBy = ignoreByself.quality = qualitydef setPath(self, path):self.path = pathdef setTargetDir(self, foldername="target"):self.dir, self.filename = os.path.split(self.path)self.targetDir = os.path.join(self.dir, foldername)if not os.path.exists(self.targetDir):os.makedirs(self.targetDir)self.targetPath = os.path.join(self.targetDir, "c_" + self.filename)def load(self):self.img = Image.open(self.path)if self.img.mode == "RGB":self.type = "JPEG"elif self.img.mode == "RGBA":self.type = "PNG"else: # 其他的圖片就轉成JPEGself.img = self.img.convert("RGB")self.type = "JPEG"def computeScale(self):# 計算縮小的倍數srcWidth, srcHeight = self.img.sizesrcWidth = srcWidth + 1 if srcWidth % 2 == 1 else srcWidthsrcHeight = srcHeight + 1 if srcHeight % 2 == 1 else srcHeightlongSide = max(srcWidth, srcHeight)shortSide = min(srcWidth, srcHeight)scale = shortSide / longSideif (scale <= 1 and scale > 0.5625):if (longSide < 1664):return 1elif (longSide < 4990):return 2elif (longSide > 4990 and longSide < 10240):return 4else:return max(1, longSide // 1280)elif (scale <= 0.5625 and scale > 0.5):return max(1, longSide // 1280)else:return ceil(longSide / (1280.0 / scale))def compress(self):self.setTargetDir()# 先調整大小,再調整品質if os.path.getsize(self.path) <= self.ignoreBy:copyfile(self.path, self.targetPath)else:self.load()scale = self.computeScale()srcWidth, srcHeight = self.img.sizecache = self.img.resize((srcWidth // scale, srcHeight // scale),Image.ANTIALIAS)cache.save(self.targetPath, self.type, quality=self.quality)# 提取成圖片 def covert2pic(doc, totaling, zooms=None):''':param totaling: pdf的頁數:param zooms: 值越大,分辨率越高,文件越清晰,列表內兩個浮點數,每個尺寸的縮放系數,默認為分辨率的2倍:return:'''if zooms is None:zooms = [2.0, 2.0]if os.path.exists('.pdf'): # 臨時文件,需為空rmtree('.pdf')os.mkdir('.pdf')print(f"pdf頁數為 {totaling} \n創建臨時文件夾.....")for pg in range(totaling):page = doc[pg]print(f"\r{page}", end="")trans = fitz.Matrix(*zooms).preRotate(0) # 0為旋轉角度pm = page.getPixmap(matrix=trans, alpha=False)lurl = '.pdf/%s.jpg' % str(pg + 1)pm.writePNG(lurl) #保存doc.close()# 圖片合成pdf def pic2pdf(obj, ratio, totaling):doc = fitz.open()compressor = Luban(quality=ratio)for pg in range(totaling):path = '.pdf/%s.jpg' % str(pg + 1)compressor.setPath(path)compressor.compress()print(f"\r 插入圖片 {pg + 1}/{totaling} 中......", end="")img = '.pdf/target/c_%s.jpg' % str(pg + 1)imgdoc = fitz.open(img) # 打開圖片pdfbytes = imgdoc.convertToPDF() # 使用圖片創建單頁的 PDFos.remove(img)imgpdf = fitz.open("pdf", pdfbytes)doc.insertPDF(imgpdf) # 將當前頁插入文檔if os.path.exists(obj): # 若pdf文件存在先刪除os.remove(obj)doc.save(obj) # 保存pdf文件doc.close()@runtime def pdfz(doc, obj, ratio, totaling):covert2pic(doc, totaling)pic2pdf(obj, ratio, totaling)def pic_quality():print("輸入壓縮等級1~3:")comp_level = input("壓縮等級(1=高畫質50%,2=中畫質70%,3=低畫質80%):(輸入數字并按回車鍵)")# 用字典模擬Switch分支,注意輸入的值是str類型ratio = {'1': 40, '2': 20, '3': 10}# 字典中沒有則默認 低畫質壓縮return ratio.get(comp_level, 10)if __name__ == "__main__":print("請選擇需要壓縮的PDF文件")while True:'''打開選擇文件夾對話框'''filepath = g.fileopenbox(title=u"選擇PDF", filetypes=['*.pdf'])if filepath == None:input("還未選擇文件,輸入任意鍵繼續.......")continueelse:filedir, filename = os.path.split(filepath)print(u'已選中文件【%s】' % (filename))if filename.endswith(".pdf") == False:input("選擇的文件類型不對,輸入任意鍵繼續.......")continueratio = pic_quality()obj = "new_" + filenamedoc = fitz.open(filepath)totaling = doc.pageCountpdfz(doc, obj, ratio, totaling)rmtree('.pdf')oldsize = os.stat(filepath).st_sizenewsize = os.stat(obj).st_sizeprint('壓縮結果 %.2f M >>>> %.2f M'%(oldsize/(1024 * 1024),newsize/(1024 * 1024)))input(f"壓縮已完成,文件保存在改程序目錄下{filedir},如需繼續壓縮請按任意鍵")效果
壓縮出來的結果:
當然,不是所有的pdf壓縮都會變小。。。本身pdf文件小的,處理出來后可能會變大,原因應該是圖片提取保存的時候圖片文件變大,所有壓縮進去的時候也會變大。
新增多進程
在使用過多線程時,發現速度沒什么提升,因為這個程序CPU和IO都有占用,大家可以測試在多線程和多進程下哪個速度快就采用哪個
Python中單線程、多線程和多進程的效率對比實驗
Python并發編程之多進程
別的博客中說到:“需要注意的是隊列中Queue.Queue是線程安全的,但并不是進程安全,所以多進程一般使用線程、進程安全的multiprocessing.Queue(),而使用這個Queue如果數據量太大會導致進程莫名卡住(絕壁大坑來的),需要不斷地消費。”
這里對代碼的修改部分有幾個小地方,提取圖片的參數變為pdf路徑(因為doc參數在進程調用時會出錯),隊列
轉pdf內部加入判斷隊列為空和取操作,這樣就簡單實現了生產者-消費者模式
最終多進程會比單進程節約大約30%的時間(節約了處理圖片和生成pdf的時間,就是函數pic2pdf)
缺點
-
使用的不是GUI界面,沒那么美觀,感覺也沒必要吧哈哈哈
-
提取文件的時候比較慢,想著多線程但是不會,可能要對文件分塊,還是算了
-
用pyinstaller(本人在conda創建的虛擬環境下python2.6打包出來小一點)打包出來,文件大小差不多30M,而且打包之后運行就沒那么流暢了,而且有個坑點
執行過程在cmd黑窗口中打印信息時,有時,一不小心鼠標點到了黑窗口里,程序就會暫停,要回車才能繼續,網上的說法是
“或許是cmd啟用了快速編輯模式導致的問題。在快速編輯模式,鼠標點擊cmd窗口時,可以直接選擇窗口里的文本,如果此時cmd中運行的進程需要在cmd窗口中輸出信息,這個進程就會被暫停,直到按下回車。”
解決方法:Python 解決cmd窗口鼠標點擊后掛起不執行問題(禁止快速編輯模式)_淺醉櫻花雨的專欄-CSDN博客
加入代碼:但是沒用。。。輸入的時候會輸入不了,暫時不加了
可以看下邊的方法,只對cmd設置而已
Windows下CMD(命令提示符)腳本運行過程中被阻塞_VisionLRJ的博客-CSDN博客_cmd阻塞
總結
以上是生活随笔為你收集整理的用python做PDF本地化压缩,新增多进程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: html的em标签引发的一系列思考
- 下一篇: 喝酒神器摇骰子小程序源码分享以及搭建效果