PDFTools
批量添加目录
,图像增强
,PDF拆分
,PDF合并
PDF 是一种便携,易查找的电子书,在学习工作中我们可能需要翻阅大量的参考书籍,实体书往往贵且笨重,如果需要经常翻阅,那么可以购置纸质书。但是大多数时候我们可能只是需要查阅书中的某一部分,如此电子书便具有不可比拟的优势,互联网上有者大量的电子书籍,且多数以pdf
格式流传,其中一部分为文字版,一部分为扫描版。这些文件一般为自制的盗版书籍,往往缺少目录,或者扫描不清晰。以致索引困难,观感极差。
市面上也有很多pdf编辑器
,如Adobe Acrobat PDF
,PDF Element
,福昕PDF编辑器
等,但他们大多价格昂贵,且没有批量添加目录的功能,虽有OCR增强的功能,但是比较耗时,且在年迈的PC上容易卡死。此工具没有使用OCR,仅对扫描页面的图片逐一增强来改善PDF清晰度。
pyqt5
,pymupdf
,python3.7
,qt designer
,QCandyUi
(可选)-i https://pypi.tuna.tsinghua.edu.cn/simple
,如pip3 install pymupdf -i https://pypi.tuna.tsinghua.edu.cn/simple
pip3 install pyqt5,pyqt5-tools
designer
可启动 QT DesignerAnanconda
,则pyqt
版本过高可能会导致Spyder
无法打开,此时请使用如下命令,更换版本,(不要频繁的使用conda update)pip3 uninstall pyqt5
pip3 install pyqt5==5.12.0
版本号如果有误,终端会列出可安装的版本,选择其一输入即可
IDE:PyCharm Edu ,Spyder ,VS Code + Python 插件
上述工具择其一即可,其中PyCharm 配置后可一体化开发 配置教程-博客园,配置教程-知乎
我没有配置,逐一进行
用途 | 工具/方法 |
---|---|
绘制用户界面 (UI) | QT Designer |
UI转Python代码 | pyuic5 |
编写Python 代码 | PyCharm / VS Code |
执行代码 | python3.7 (PyCharm / VS Code) |
打包为可执行文件 (可选) | pyinstaller |
1.主界面 (使用QCandyUi美化后的截图)
2.添加目录 (详见生成的 demo.txt)
3.文本增强
4.拆分文档
5.合并文档
使用QT Designer 绘制界面(如上图) ,选择 QDialog without Button
,拖拽控件,绘制后保存为.ui
文件,我分别保存为PDFTools.ui
add_UI.ui
enhance_UI.ui
merge_UI.ui
split_UI.ui
,绘制界面比较简单,但是控件命名
应当添加适当的前缀或后缀
加以区分。
使用pyuic5
命令将ui文件转换为python代码,切换到项目文件夹,输入
pyuic5 PDFTools.ui -o PDFTools.py
依次对界面代码进行转换,生成后的py文件无需手动更改,当再次生成时会完全覆盖。
编写调用窗口和信号处理代码。pyqt延续了qt的设计思想,只要处理好信号与槽(可理解为触发事件与处理方法关联),那么编写项目也会得心应手。在编写过程中可查看qt类手册,pyqt中的方法大多与QT C++同名,但是少了丑陋的指针
->
,使代码不那么扎眼。具体见代码解析
推荐使用ipython
对方法/类 进行测试
启动
> python main.py
如果需要可进行打包
pyinstaller --onefile --windowed --icon=PDF.ico main.py
代码组成
--------窗口信号处理----------
main.py
callAdd.py
callEnhance.py
callSplit.py
callMerge.py
--------窗口界面布局----------
PDFTools.py
add_UI.py
enhance_UI.py
split_UI.py
merge_UI.py
--------PDF处理函数----------
addFunctins.py
enhanceFunctions.py
splitFunctions.py
用到的QT控件信号, 通过 connect 可关联方法
控件 | 信号 |
---|---|
pushButton | clicked |
spinBox | valueChanged /editintFinished |
Slider | sliderMoved / valueChanged |
radioButton | toggled |
lineEdit | textChanged |
checkBox | stateChanged |
comboBox | currentIndexChanged |
用到的QT控件方法 ,大多数控件都有setText() 和text() 方法,不一一列举
控件 | 方法 |
---|---|
textEdit | setText() /text() |
lineEdit | setText() / text() |
checkBox | isChecked() |
tableWidget | setItem() |
spinBox | setReadOnly() |
lineEdit | setReadOnly |
pymupdf
中的方法
名称 | 描述 |
---|---|
open | 打开文件(pdf,图片) |
save | 保存 |
setToC | 设置目录 |
getPixmap | 获取本页的图片 |
insertPDF | 插入PDF |
close | 关闭 |
pageCount | 获取页码(属性) |
os
,PIL
中的方法
方法 | 描述 |
---|---|
open | 打开txt |
write | 写入txt |
close | 关闭文本 |
strip | 删除指定符号 |
split | 根据指定符号分割字符串 |
len | 计算长度,数量 |
range | 连续的数 |
replace | 替换指定字符串 |
os.getcwd() | 获取当前文件路径 |
os.path.exists() | 是否存在文件夹 |
os.makedirs() | 创建文件夹 |
os.remove() | 删除文件 |
Image.open | 打开图片 |
ImageEnhance.Contrast | 增强方法 |
main.py
展示了 创建类,关联信号与槽,创建窗口的一般途径
import ctypes
import sys
from PyQt5.QtWidgets import QApplication, QDialog
from callAdd import AddForm
from callEnhance import EnhanceForm
from callMerge import MergeForm
from callSplit import SplitForm
from PDFTools import Ui_Dialog # or import *
class MyForm(QDialog):
def __init__(self):
super().__init__()
self.ui=Ui_Dialog()
self.ui.setupUi(self)
self.ui.pushButton_add.clicked.connect(self.add_Window)
self.ui.pushButton_enhance.clicked.connect(self.enhance_Window)
self.ui.pushButton_split.clicked.connect(self.split_Window)
self.ui.pushButton_merge.clicked.connect(self.merge_Window)
self.show()
def add_Window(self):
self.w1=AddForm()
self.w1.show()
#self.hide()
def enhance_Window(self):
self.w2=EnhanceForm()
self.w2.show()
def split_Window(self):
self.w3=SplitForm()
self.w3.show()
def merge_Window(self):
self.w4=MergeForm()
self.w4.show()
if __name__=="__main__":
app=QApplication(sys.argv)
#set taskbar icon
myappid = 'mycompany.myproduct.subproduct.version' # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
w=MyForm()
w.show()
sys.exit(app.exec_())
主要功能代码部分
callAdd.py
工程中的四个callxxx.py
均独立启动使用
# 添加目录事件主要逻辑
def start(self):
if self.ui.lineEdit_PDF.text() != "" and self.ui.lineEdit_TXT.text() != "":
self.ui.textEdit_progress.setText("可以")
self.pdf=add2pdf(self.doc, self.txt, self.offsetNum)
if self.pdf != None:
newname=self.ui.lineEdit_PDF.text().replace(".pdf","-new.pdf")
self.pdf.save(newname)
self.ui.textEdit_progress.setText("目录添加成功")
else:
self.ui.textEdit_progress.setText("缺少文件")
addFunctions.py
def add2pdf(pdffile, txtfile, offset):
"""
添加目录到PDF,其中文件应为打开状态
:param: pdffile , textfile , offset
:return:pdffile
"""
lines = txtfile.readlines()
toc = []
for line in lines:
if line[0] == '#' or len(line.split()) == 0:
continue
level = get_level(line)
title = get_title(line)
page = get_page(line)
toc.append([level, title, page])
pdffile.setToC(toc)
return pdffile
callEnhance.py
#增强pdf 事件逻辑代码
def w2_start(self):
oldFileName = self.ui.lineEdit_w2_openPDF.text()
newFileName = oldFileName.replace(".pdf", "[enhanced].pdf")
if oldFileName != '':
doc = fitz.open(oldFileName)
enhanceDoc = fitz.open()
num = doc.pageCount
baseBar = 1.0 / num * 100
for i in range(num):
imgpdf = getEnhancedPdf(doc[i], i, self.saveFlag,
[self.colorValue, self.contrastValue, self.sharpnessValue,
self.brightnessValue], 2)
enhanceDoc.insertPDF(imgpdf)
self.ui.label_w2_tips.setText("正在处理第 %d 页" % i)
self.ui.progressBar_w2.setValue(i * baseBar)
enhanceDoc.save(newFileName)
doc.close()
enhanceDoc.close()
self.ui.progressBar_w2.setValue(100)
self.ui.label_w2_tips.setText("处理完毕")
enhanceFunctions.py
"""
===============================================================================
ImageEnhance.Color(image) 色彩平衡。 0-黑白,1-原图(可为小数,可 > 1)
ImageEnhance.Contrast(image) 对比度。 0-灰色图像,1-原图
ImageEnhance.Brightness(image) 亮度。 0.0-黑色图像,1-原图
ImageEnhacne.Sharpness(image) 锐化。0.0是模糊图像,1.0是原始图像,2.0是锐化图像
===============================================================================
上述类的增强方法都为 enhance(factor), 显示用show(),见下例子
===============================================================================
from PIL import Image,ImageEnhance
img=Image.open("name.png")
# 下面的 Contrast 可换为 Color, Brightness, Sharpness 之一, 其他不变
enhanceImg=ImageEnhance.Contrast(img)
newImage=enhanceImg.enhance(2)
newImage.show()
newImage.save("new.png")
===============================================================================
"""
import fitz
import os
from PIL import Image, ImageEnhance
# 设置缩放及旋转角度
def setZoom(zoom_xy): # 设置为 2
rotate = int(0) # 设置图片的旋转角度
zoom_x = zoom_xy # 设置图片相对于PDF文件在X轴上的缩放比例
zoom_y = zoom_xy # 设置图片相对于PDF文件在Y轴上的缩放比例
trans = fitz.Matrix(zoom_x, zoom_y).preRotate(rotate)
return trans
def getEnhancedPdf(docPage, index, saveFlag=False, factor=[1.0, 1.0, 1.0, 1.0], zoom_xy=2):
"""
获取图像,并增加对比度
:param : docPage: doc[i]
:param :factor : color, contrast, sharpness, brightness
:param :zoom_xy : enlargement factor
:return : a page of enhanced pdf
"""
pix = docPage.getPixmap(matrix=setZoom(zoom_xy), alpha=False)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
enhanceTemp1 = ImageEnhance.Color(img)
pilImg1 = enhanceTemp1.enhance(factor[0]) # PIL img
enhanceTemp2 = ImageEnhance.Contrast(pilImg1)
pilImg2 = enhanceTemp2.enhance(factor[1]) # PIL img
enhanceTemp3 = ImageEnhance.Sharpness(pilImg2)
pilImg3 = enhanceTemp3.enhance(factor[2]) # PIL img
enhanceTemp4 = ImageEnhance.Brightness(pilImg3)
pilImg4 = enhanceTemp4.enhance(factor[3]) # PIL img
# pilImg.show()
folder = os.getcwd() + '/split' # 创建文件夹
if not os.path.exists(folder):
os.makedirs(folder)
imgName = folder + "/splitImage(" + "%04d" % index + ").png"
pilImg4.save(imgName)
fitImg = fitz.open(imgName)
if saveFlag == False:
os.remove(imgName)
pdfBytes = fitImg.convertToPDF()
imgpdf = fitz.open("pdf", pdfBytes)
return imgpdf
callSplit.py
def w3_startSplit(self):
if self.splitMode==0 or self.pathStr=='':
self.tipStr="未添加pdf文件 或 未设置拆分模式 ! "
elif self.splitMode==1:
self.tipStr=split_pdf_same_page(self.pathStr,self.pageSame)
elif self.splitMode==2:
self.tipStr=split_pdf_custom_page(self.pathStr,self.pageStr)
else:
pass
self.ui.label_w3_tip.setText(self.tipStr)
splitFunctions.py
# -*- coding: utf-8 -*-
"""
Created on Fri Jul 19 09:18:18 2020
@author: cherish
"""
import fitz
def split_pdf_same_page(pathStr, page):
"""
将pdf 拆分为 多个pdf文件,每page页为一部分
:param: pathStr :pdf路径的字符串, page : int 分割区间
:return: true
"""
doc = fitz.open(pathStr)
# doc1 = fitz.open() # 空 pdf 文件
docPage = doc.pageCount
if page > docPage or page ==0 :
return "错误 : 页码超出范围,或 分割区间为0 !"
num = docPage//page # 可以拆成 page 页的部分
for i in range(num): # 0 到 num-1
doc1 = fitz.open() # 空 pdf 文件
doc1.insertPDF(doc, from_page=page*i, to_page=page*(i+1)-1, start_at=-1)
partName = '[part'+str(i)+'].pdf'
newName = pathStr.replace(".pdf", partName)
doc1.save(newName)
doc1.close()
surplus = docPage % page # 最后少于 page 的部分
if surplus != 0:
doc1 = fitz.open() # 空 pdf 文件
doc1.insertPDF(doc, from_page=page*num, to_page=docPage-1, start_at=-1)
partName = '[part'+str(num)+'].pdf'
newName = pathStr.replace(".pdf", partName)
doc1.save(newName)
doc1.close()
return "拆分成功"
def split_pdf_custom_page(pathStr, pageStr):
"""
将pdf拆分为自定义页码的多个pdf文件
:param: str ,pdf路径的字符串, pageStr: 自定义页码的字符串
:return: Error page string / True
"""
doc = fitz.open(pathStr)
docPage = doc.pageCount
pageStr = pageStr.replace(',', ',') # 如果是中文逗号
pageList = pageStr.split(",")
for page in pageList:
pse = page.split('-') # page start and end
ps = int(pse[0])
pe = int(pse[0]) if len(pse) == 1 else int(pse[1])
if ps > pe or pe > docPage:
return "请检查> "+ page + " 是否有误 !" # error: page part
doc1 = fitz.open() # 创建 空pdf文件
doc1.insertPDF(doc, from_page=ps-1, to_page=pe-1, start_at=-1)
partName = '[page'+str(ps)+'-' + str(pe)+'].pdf'
newName = pathStr.replace(".pdf", partName)
doc1.save(newName)
doc1.close()
return "拆分成功"
merge_UI.py
设置表格列宽,使用pyuic后会覆盖
self.tableWidget_w4_fileList.setColumnWidth(0, 450)
self.tableWidget_w4_fileList.setColumnWidth(1, 70)
callMerge.py
def w4_start(self):
if self.rowCount != 0:
doc = fitz.open(self.ui.tableWidget_w4_fileList.item(0, 0).text())
basebar = int(1.0 / self.rowCount * 100)
for i in range(1, self.rowCount):
doc1 = fitz.open(self.ui.tableWidget_w4_fileList.item(i, 0).text())
doc.insertPDF(doc1)
self.ui.progressBar_w4_bar.setValue(i * basebar)
self.ui.progressBar_w4_bar.setValue(100)
doc.save("merge.pdf")
doc.close()
demo.txt
该文件是存放目录的文件,要求和格式见下,在打开添加目录
窗口的同时会自动生成该文件
# =========================================================================
#
# 建议在如下网站搜集目录:
# 1.京东图书 https://book.jd.com/
# 2.豆瓣读书 https://book.douban.com/
# 3.当当图书 http://book.dangdang.com/
# 4.文泉书局 https://wqbook.wqxuetang.com/
#
# =========================================================================
# 标准格式1 如下:(空行不影响)
# 特征 :两部分构成: 标题 + 空格 + 页码
# 标题中含 '第' 和 '章' 的会识别为一级标题,其他为二级标题
# =========================================================================
第1章概述 1
什么是OpenCV 1
OpenCV怎么用 2
什么是计算机视觉 3
OpenCV的起源 6
OpenCV的结构 7
使用IPP来加速OpenCV 8
谁拥有OpenCV 9
下载和安装OpenCV 9
# ==========================================================================
# 标准格式2 如下
# 特征 :章序/节序 + 空格 + 标题 + 空格 + 页码 (空格用于区分各元素)
# 无 章节序 的 默认识别为 二级标题 ,若想设置为一级标题,请在前加 '@ ',
# 一般 需要区分的是 前言 目录 附录 参考文献 这些
# 节序中有一个点 表二级标题(如 6.1 ),两个点 表三级标题(如 1.2.3),以此类推
# ==========================================================================
第6章 支持向量机 121
6.1 间隔与支持向量 121
6.2 对偶问题 123
6.3 核函数 126
6.4 软间隔与正则化 129
6.5 支持向量回归 133
6.6 核方法 137
6.7 阅读材料 139
习题 141
休息一会儿 145
@ 参考文献 520
# ==========================================================================
- [ ] 自定义样式表
- [ ] 自动爬取目录
- [ ] img2pdf
第一次使用pyqt5
写一个完整的项目,用时大概六天。前期绘制界面一天。后期逐一完善各个功能四天,写文档,改bug一天。最大的感受就是Python语法友好,轮子很全。本次项目也是熟悉pyqt的过程。写完本应用基本掌握了常用的控件,信号,槽。整体而言,使用pyqt5编写一些小工具还是很方便的。至于执行效率,一般的小项目基本体现不出来。
PyQt5优点:相较于QT Creator,python代码比较优雅;相较于 C# Winform/WPF ,python拥有较多的库。可用样式表setStyleSheet
美化控件
缺点:QT Designer 可设置参数偏少,需要使用代码设置,控件不够美观,打包文件偏大。