├── logo.ico ├── README.md ├── requirement.txt ├── main.ui ├── ui_main.py └── main.py /logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RotaNova/DocxToPDFWithWatermark/HEAD/logo.ico -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 批量处理小工具 2 | ## 初衷 3 | 为家里的老一辈制作的简单小工具,帮助他们一键将word文档批量转换成PDF,并添加定制水印。 4 | 5 | *备注:没有开额外的处理线程,仅简单的制作的一个Qt应用,方便小白们一键处理文件,也供Pyside6入门学习。如果后期有需求反响,会考虑完善代码。 6 | 7 | ### 系统运行要求 8 | ①Window系统 9 | ②Microsoft Word 10 | 11 | ## 环境配置 12 | pip install -r requirement 13 | -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | altgraph==0.17.2 2 | colorama==0.4.4 3 | docx2pdf==0.1.8 4 | future==0.18.2 5 | install==1.3.5 6 | Jinja2==3.1.2 7 | MarkupSafe==2.1.1 8 | pefile==2021.9.3 9 | pyinstaller==5.0.1 10 | pyinstaller-hooks-contrib==2022.4 11 | PyPDF4==1.27.0 12 | PySide6==6.3.0 13 | PySide6-Addons==6.3.0 14 | PySide6-Essentials==6.3.0 15 | pywin32==304 16 | pywin32-ctypes==0.2.0 17 | qt-material==2.11 18 | shiboken6==6.3.0 19 | tqdm==4.64.0 20 | -------------------------------------------------------------------------------- /main.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 799 10 | 600 11 | 12 | 13 | 14 | MainWindow 15 | 16 | 17 | 18 | 19 | 20 | 10 21 | 20 22 | 781 23 | 531 24 | 25 | 26 | 27 | 28 | QLayout::SetDefaultConstraint 29 | 30 | 31 | 32 | 33 | 34 | 24 35 | 36 | 37 | 38 | false 39 | 40 | 41 | Qt::LeftToRight 42 | 43 | 44 | false 45 | 46 | 47 | Word文件批量转PDF, 批量添加水印 48 | 49 | 50 | false 51 | 52 | 53 | Qt::AlignCenter 54 | 55 | 56 | 57 | 58 | 59 | 60 | 说明:转换完的PDF文件保存在软件执行的当前路径的PDF目录下,添加了水印的PDF文件保存在相同路径的PDFMarked目录下。 61 | 62 | 63 | Qt::AlignCenter 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 选取文件目录 73 | 74 | 75 | 76 | 77 | 78 | 79 | Qt::Horizontal 80 | 81 | 82 | 83 | 40 84 | 20 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 请选取要进行批量PDF转换的Word文档的目录 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 选取水印文件 104 | 105 | 106 | 107 | 108 | 109 | 110 | Qt::Horizontal 111 | 112 | 113 | 114 | 40 115 | 20 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 请选取要添加的水印模板文件,格式为PDF 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | true 133 | 134 | 135 | 136 | 137 | 138 | 139 | false 140 | 141 | 142 | 开始转换 143 | 144 | 145 | 146 | 147 | 148 | 149 | 软件版本:v0.1 日期:2022年5月9日 版权@QILIN YOU 150 | 151 | 152 | Qt::AlignCenter 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 0 163 | 0 164 | 799 165 | 22 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /ui_main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | ################################################################################ 4 | ## Form generated from reading UI file 'mainKZzsXq.ui' 5 | ## 6 | ## Created by: Qt User Interface Compiler version 6.3.0 7 | ## 8 | ## WARNING! All changes made in this file will be lost when recompiling UI file! 9 | ################################################################################ 10 | 11 | from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale, 12 | QMetaObject, QObject, QPoint, QRect, 13 | QSize, QTime, QUrl, Qt) 14 | from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor, 15 | QFont, QFontDatabase, QGradient, QIcon, 16 | QImage, QKeySequence, QLinearGradient, QPainter, 17 | QPalette, QPixmap, QRadialGradient, QTransform) 18 | from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLayout, 19 | QMainWindow, QMenuBar, QPlainTextEdit, QPushButton, 20 | QSizePolicy, QSpacerItem, QStatusBar, QVBoxLayout, 21 | QWidget) 22 | 23 | class Ui_MainWindow(object): 24 | def setupUi(self, MainWindow): 25 | if not MainWindow.objectName(): 26 | MainWindow.setObjectName(u"MainWindow") 27 | MainWindow.resize(799, 600) 28 | self.centralwidget = QWidget(MainWindow) 29 | self.centralwidget.setObjectName(u"centralwidget") 30 | self.verticalLayoutWidget = QWidget(self.centralwidget) 31 | self.verticalLayoutWidget.setObjectName(u"verticalLayoutWidget") 32 | self.verticalLayoutWidget.setGeometry(QRect(10, 20, 781, 531)) 33 | self.layout = QVBoxLayout(self.verticalLayoutWidget) 34 | self.layout.setObjectName(u"layout") 35 | self.layout.setSizeConstraint(QLayout.SetDefaultConstraint) 36 | self.layout.setContentsMargins(0, 0, 0, 0) 37 | self.Title = QLabel(self.verticalLayoutWidget) 38 | self.Title.setObjectName(u"Title") 39 | font = QFont() 40 | font.setPointSize(24) 41 | self.Title.setFont(font) 42 | self.Title.setAcceptDrops(False) 43 | self.Title.setLayoutDirection(Qt.LeftToRight) 44 | self.Title.setAutoFillBackground(False) 45 | self.Title.setScaledContents(False) 46 | self.Title.setAlignment(Qt.AlignCenter) 47 | 48 | self.layout.addWidget(self.Title) 49 | 50 | self.Discription = QLabel(self.verticalLayoutWidget) 51 | self.Discription.setObjectName(u"Discription") 52 | self.Discription.setAlignment(Qt.AlignCenter) 53 | 54 | self.layout.addWidget(self.Discription) 55 | 56 | self.horizontalLayout = QHBoxLayout() 57 | self.horizontalLayout.setObjectName(u"horizontalLayout") 58 | self.pickWorkPath = QPushButton(self.verticalLayoutWidget) 59 | self.pickWorkPath.setObjectName(u"pickWorkPath") 60 | 61 | self.horizontalLayout.addWidget(self.pickWorkPath) 62 | 63 | self.horizontalSpacer = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 64 | 65 | self.horizontalLayout.addItem(self.horizontalSpacer) 66 | 67 | self.workPath = QLabel(self.verticalLayoutWidget) 68 | self.workPath.setObjectName(u"workPath") 69 | 70 | self.horizontalLayout.addWidget(self.workPath) 71 | 72 | 73 | self.layout.addLayout(self.horizontalLayout) 74 | 75 | self.horizontalLayout_2 = QHBoxLayout() 76 | self.horizontalLayout_2.setObjectName(u"horizontalLayout_2") 77 | self.chooseWaterMarkFile = QPushButton(self.verticalLayoutWidget) 78 | self.chooseWaterMarkFile.setObjectName(u"chooseWaterMarkFile") 79 | 80 | self.horizontalLayout_2.addWidget(self.chooseWaterMarkFile) 81 | 82 | self.horizontalSpacer_2 = QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum) 83 | 84 | self.horizontalLayout_2.addItem(self.horizontalSpacer_2) 85 | 86 | self.watermarkFile = QLabel(self.verticalLayoutWidget) 87 | self.watermarkFile.setObjectName(u"watermarkFile") 88 | 89 | self.horizontalLayout_2.addWidget(self.watermarkFile) 90 | 91 | 92 | self.layout.addLayout(self.horizontalLayout_2) 93 | 94 | self.results = QPlainTextEdit(self.verticalLayoutWidget) 95 | self.results.setObjectName(u"results") 96 | self.results.setReadOnly(True) 97 | 98 | self.layout.addWidget(self.results) 99 | 100 | self.startConvert = QPushButton(self.verticalLayoutWidget) 101 | self.startConvert.setObjectName(u"startConvert") 102 | self.startConvert.setEnabled(False) 103 | 104 | self.layout.addWidget(self.startConvert) 105 | 106 | self.authorLabel = QLabel(self.verticalLayoutWidget) 107 | self.authorLabel.setObjectName(u"authorLabel") 108 | self.authorLabel.setAlignment(Qt.AlignCenter) 109 | 110 | self.layout.addWidget(self.authorLabel) 111 | 112 | MainWindow.setCentralWidget(self.centralwidget) 113 | self.menubar = QMenuBar(MainWindow) 114 | self.menubar.setObjectName(u"menubar") 115 | self.menubar.setGeometry(QRect(0, 0, 799, 22)) 116 | MainWindow.setMenuBar(self.menubar) 117 | self.statusbar = QStatusBar(MainWindow) 118 | self.statusbar.setObjectName(u"statusbar") 119 | MainWindow.setStatusBar(self.statusbar) 120 | 121 | self.retranslateUi(MainWindow) 122 | 123 | QMetaObject.connectSlotsByName(MainWindow) 124 | # setupUi 125 | 126 | def retranslateUi(self, MainWindow): 127 | MainWindow.setWindowTitle(QCoreApplication.translate("MainWindow", u"MainWindow", None)) 128 | self.Title.setText(QCoreApplication.translate("MainWindow", u"Word\u6587\u4ef6\u6279\u91cf\u8f6cPDF\uff0c \u6279\u91cf\u6dfb\u52a0\u6c34\u5370", None)) 129 | self.Discription.setText(QCoreApplication.translate("MainWindow", u"\u8bf4\u660e\uff1a\u8f6c\u6362\u5b8c\u7684PDF\u6587\u4ef6\u4fdd\u5b58\u5728\u8f6f\u4ef6\u6267\u884c\u7684\u5f53\u524d\u8def\u5f84\u7684PDF\u76ee\u5f55\u4e0b\uff0c\u6dfb\u52a0\u4e86\u6c34\u5370\u7684PDF\u6587\u4ef6\u4fdd\u5b58\u5728\u76f8\u540c\u8def\u5f84\u7684PDFMarked\u76ee\u5f55\u4e0b\u3002", None)) 130 | self.pickWorkPath.setText(QCoreApplication.translate("MainWindow", u"\u9009\u53d6\u6587\u4ef6\u76ee\u5f55", None)) 131 | self.workPath.setText(QCoreApplication.translate("MainWindow", u"\u8bf7\u9009\u53d6\u8981\u8fdb\u884c\u6279\u91cfPDF\u8f6c\u6362\u7684Word\u6587\u6863\u7684\u76ee\u5f55", None)) 132 | self.chooseWaterMarkFile.setText(QCoreApplication.translate("MainWindow", u"\u9009\u53d6\u6c34\u5370\u6587\u4ef6", None)) 133 | self.watermarkFile.setText(QCoreApplication.translate("MainWindow", u"\u8bf7\u9009\u53d6\u8981\u6dfb\u52a0\u7684\u6c34\u5370\u6a21\u677f\u6587\u4ef6\uff0c\u683c\u5f0f\u4e3aPDF", None)) 134 | self.startConvert.setText(QCoreApplication.translate("MainWindow", u"\u5f00\u59cb\u8f6c\u6362", None)) 135 | self.authorLabel.setText(QCoreApplication.translate("MainWindow", u"\u8f6f\u4ef6\u7248\u672c:v0.3 \u65e5\u671f\uff1a2022\u5e745\u67089\u65e5 \u7248\u6743@QILIN YOU", None)) 136 | # retranslateUi 137 | 138 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # import os 4 | from pathlib import Path 5 | import sys 6 | import time 7 | 8 | from docx2pdf import convert 9 | import PyPDF4 10 | from PyPDF4 import PdfFileWriter, PdfFileReader 11 | import logging 12 | 13 | from PySide6.QtGui import * 14 | from PySide6.QtWidgets import * 15 | import ui_main 16 | from qt_material import apply_stylesheet 17 | 18 | 19 | class MainWindow(QMainWindow): 20 | def __init__(self): 21 | QMainWindow.__init__(self) 22 | self.ui = ui_main.Ui_MainWindow() 23 | self.ui.setupUi(self) 24 | self.ui.centralwidget.setLayout(self.ui.layout) 25 | # self.cwd = Path.cwd() 26 | self.setWindowTitle("批量PDF转换加水印工具") 27 | self.show() 28 | self.workPath = "" 29 | self.watermarkPath = "" 30 | self.ui.startConvert.clicked.connect(self.start_convert) 31 | self.ui.startConvert.setEnabled(False) 32 | self.ui.pickWorkPath.clicked.connect(self.choose_work_path) 33 | self.ui.chooseWaterMarkFile.clicked.connect(self.choose_watermark_file) 34 | 35 | def mylog(self, msg): 36 | logging.info(msg) 37 | self.ui.results.appendPlainText(msg) 38 | QApplication.processEvents() 39 | 40 | def logError(self, msg): 41 | logging.error(msg) 42 | self.ui.results.appendPlainText(msg) 43 | QApplication.processEvents() 44 | app.exit() 45 | 46 | def add_watermark(self, input_pdf, output_pdf, watermark): 47 | """ 48 | 49 | :param input_pdf: 50 | :param output_pdf: 51 | :param watermark: 52 | :return: 53 | """ 54 | # reads the watermark pdf file through 55 | # PdfFileReader 56 | QApplication.processEvents() 57 | try: 58 | watermark_instance = PdfFileReader(watermark) 59 | except Exception as e: 60 | self.logError(e) 61 | # fetches the respective page of 62 | # watermark(1st page) 63 | if watermark_instance.getNumPages() > 1: 64 | button = QMessageBox.critical(self, "警告", 65 | "您选取的水印文件好像有问题,请检查水印文件!", 66 | buttons = QMessageBox.Ignore | QMessageBox.NoToAll, 67 | defaultButton=QMessageBox.Ignore) 68 | if button == QMessageBox.NoToAll: 69 | self.logError("退出程序!") 70 | watermark_page = watermark_instance.getPage(0) 71 | 72 | # reads the input pdf file 73 | pdf_reader = PdfFileReader(input_pdf) 74 | 75 | # It creates a pdf writer object for the 76 | # output file 77 | pdf_writer = PdfFileWriter() 78 | 79 | # iterates through the original pdf to 80 | # merge watermarks 81 | for page in range(pdf_reader.getNumPages()): 82 | page = pdf_reader.getPage(page) 83 | # will overlay the watermark_page on top 84 | # of the current page. 85 | page.mergePage(watermark_page) 86 | # add that newly merged page to the 87 | # pdf_writer object. 88 | pdf_writer.addPage(page) 89 | 90 | with open(output_pdf, 'wb') as out: 91 | # writes to the respective output_pdf provided 92 | try: 93 | pdf_writer.write(out) 94 | except Exception as e: 95 | self.logError(e) 96 | 97 | 98 | def word2pdf(self, word_name, pdf_name=None): 99 | """ 100 | 101 | :param word_name: 102 | :param pdf_name: 103 | :return: 104 | """ 105 | QApplication.processEvents() 106 | if pdf_name: 107 | try: 108 | convert(word_name, pdf_name) 109 | except Exception as e: 110 | self.logError(e) 111 | else: 112 | try: 113 | convert(word_name) 114 | except Exception as e: 115 | self.logError(e) 116 | 117 | 118 | def start_convert(self): 119 | self.ui.results.clear() 120 | base_path = Path.cwd() 121 | target_path = Path(self.workPath) 122 | pdf_path = base_path.joinpath('PDF') 123 | pdf_mark_path = base_path.joinpath("PDFMarked") 124 | if not Path.exists(pdf_path): 125 | Path.mkdir(pdf_path) 126 | if not Path.exists(pdf_mark_path): 127 | Path.mkdir(pdf_mark_path) 128 | self.ui.startConvert.setEnabled(False) 129 | self.ui.startConvert.setText('开始处理文件,清耐心等待') 130 | QApplication.processEvents() 131 | 132 | files = [] 133 | tfiles = Path(target_path).glob("*") 134 | for file in tfiles: 135 | files.append(file.name) 136 | files_num = 0 137 | for file in files: 138 | if file.split('.')[-1] != 'doc' and file.split('.')[-1] != 'docx': 139 | continue 140 | else: 141 | files_num += 1 142 | if files_num > 0: 143 | self.mylog('开始转换PDF...') 144 | count = 0 145 | for file in files: 146 | file_path = target_path.joinpath(file) 147 | if file.split('.')[-1] != 'doc' and file.split('.')[-1] != 'docx': 148 | continue 149 | file_name = file.split('.')[0] 150 | pdf_file_path = pdf_path.joinpath(file_name+'.pdf') 151 | # print(file_path, pdf_file_path) 152 | if Path.exists(pdf_file_path): 153 | self.mylog("《{}》已存在,如需重新转换请删除PDF目录下的该文件".format(file_name)) 154 | else: 155 | self.mylog("正在转换《{}》文件为pdf".format(file_name)) 156 | count += 1 157 | self.word2pdf(word_name=str(file_path), pdf_name=str(pdf_file_path)) 158 | self.mylog("《{}》转换成功!".format(file_name)) 159 | self.mylog("转换完成!总计处理文件{}个!".format(count)) 160 | else: 161 | button = QMessageBox.question(self, "提示", "似乎没有需要转换的文件", 162 | QMessageBox.Yes) 163 | 164 | files = [] 165 | tfiles = Path(pdf_path).glob("*") 166 | for file in tfiles: 167 | files.append(file.name) 168 | files_num = 0 169 | for file in files: 170 | if file.split('.')[-1] != 'pdf': 171 | continue 172 | else: 173 | files_num += 1 174 | if files_num > 0: 175 | self.mylog("开始添加水印...") 176 | markfile_path = self.watermarkPath 177 | count = 0 178 | for file in files: 179 | if file.split('.')[-1] != 'pdf': 180 | continue 181 | input_path = pdf_path.joinpath(file) 182 | output_path = pdf_mark_path.joinpath(file) 183 | if Path.exists(output_path): 184 | self.mylog("《{}》已存在,如需重新添加请删除PDFMarked目录下的该文件".format(file)) 185 | else: 186 | self.mylog("为《{}》添加水印".format(file)) 187 | count += 1 188 | self.add_watermark(str(input_path), str(output_path), markfile_path) 189 | self.mylog("《{}》添加成功!".format(file)) 190 | self.mylog("添加完成!总计处理文件{}个!".format(count)) 191 | else: 192 | button = QMessageBox.question(self, "提示", "似乎没有需要添加水印的文件", 193 | QMessageBox.Yes) 194 | self.ui.startConvert.setEnabled(True) 195 | self.ui.startConvert.setText("继续转换") 196 | 197 | def choose_work_path(self): 198 | self.workPath = QFileDialog.getExistingDirectory() 199 | self.ui.workPath.setText(self.workPath) 200 | logging.info(self.workPath) 201 | if self.workPath and self.watermarkPath: 202 | self.ui.startConvert.setEnabled(True) 203 | 204 | def choose_watermark_file(self): 205 | self.watermarkPath = QFileDialog.getOpenFileName(filter="pdf(*.pdf)")[0] 206 | self.ui.watermarkFile.setText(self.watermarkPath) 207 | logging.info(self.watermarkPath) 208 | if self.workPath and self.watermarkPath: 209 | self.ui.startConvert.setEnabled(True) 210 | 211 | 212 | 213 | if __name__ == "__main__": 214 | logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='info.log') 215 | logging.basicConfig(level=logging.ERROR, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', filename='error.log') 216 | app = QApplication([]) 217 | app.setWindowIcon(QIcon("logo.ico")) 218 | window = MainWindow() 219 | apply_stylesheet(app, theme='dark_teal.xml') 220 | sys.exit(app.exec()) --------------------------------------------------------------------------------