本文带你了解如何使用 Python 中的 PyPDF4 和 reportlab 库在 PDF 文件中添加和删除水印,包括详细的Python为PDF文件加水印示例代码。
便携式文档格式 (PDF)标准化为 ISO 32000,是 Adobe 于 1993 年开发的一种文件格式,用于以独立于应用程序软件、硬件和操作系统的方式呈现文档,包括文本格式和图像。
如何在Python中为PDF文件加水印?基于PostScript 语言,每个 PDF 文件都封装了一个固定版式平面文档的完整描述,包括文本、字体、矢量图形、光栅图像和其他显示所需的信息。
对 PDF 的熟悉导致其作为数字存档领域的解决方案被快速而广泛地采用。由于 PDF 比其他文件格式更通用,因此几乎可以从任何操作系统或设备轻松查看它们显示的信息。
Python如何为PDF文件加水印?在本教程中,你将学习如何在 Python 中使用PyPDF4和reportlab为 PDF 文件或包含 PDF 文件集合的文件夹添加水印。
首先,让我们安装必要的库:
$ pip install PyPDF4==1.27.0 reportlab==3.5.59
从代码开始,让我们导入库并定义一些我们需要的配置:
from PyPDF4 import PdfFileReader, PdfFileWriter
from PyPDF4.pdf import ContentStream
from PyPDF4.generic import TextStringObject, NameObject
from PyPDF4.utils import b_
import os
import argparse
from io import BytesIO
from typing import Tuple
# Import the reportlab library
from reportlab.pdfgen import canvas
# The size of the page supposedly A4
from reportlab.lib.pagesizes import A4
# The color of the watermark
from reportlab.lib import colors
PAGESIZE = A4
FONTNAME = 'Helvetica-Bold'
FONTSIZE = 40
# using colors module
# COLOR = colors.lightgrey
# or simply RGB
# COLOR = (190, 190, 190)
COLOR = colors.red
# The position attributes of the watermark
X = 250
Y = 10
# The rotation angle in order to display the watermark diagonally if needed
ROTATION_ANGLE = 45
接下来,定义我们的第一个效用函数:
def get_info(input_file: str):
"""
Extracting the file info
"""
# If PDF is encrypted the file metadata cannot be extracted
with open(input_file, 'rb') as pdf_file:
pdf_reader = PdfFileReader(pdf_file, strict=False)
output = {
"File": input_file, "Encrypted": ("True" if pdf_reader.isEncrypted else "False")
}
if not pdf_reader.isEncrypted:
info = pdf_reader.getDocumentInfo()
num_pages = pdf_reader.getNumPages()
output["Author"] = info.author
output["Creator"] = info.creator
output["Producer"] = info.producer
output["Subject"] = info.subject
output["Title"] = info.title
output["Number of pages"] = num_pages
# To Display collected metadata
print("## File Information ##################################################")
print("\n".join("{}:{}".format(i, j) for i, j in output.items()))
print("######################################################################")
return True, output
Python如何为PDF文件加水印?该get_info()
函数收集输入PDF文件的元数据,可以提取以下属性:作者、创建者、制作人、主题、标题和页数。
值得注意的是,你无法为加密的 PDF 文件提取这些属性。
def get_output_file(input_file: str, output_file: str):
"""
Check whether a temporary output file is needed or not
"""
input_path = os.path.dirname(input_file)
input_filename = os.path.basename(input_file)
# If output file is empty -> generate a temporary output file
# If output file is equal to input_file -> generate a temporary output file
if not output_file or input_file == output_file:
tmp_file = os.path.join(input_path, 'tmp_' + input_filename)
return True, tmp_file
return False, output_file
当没有指定输出文件时,或者输入和输出文件的路径相等时,上述函数返回临时输出文件的路径。
def create_watermark(wm_text: str):
"""
Creates a watermark template.
"""
if wm_text:
# Generate the output to a memory buffer
output_buffer = BytesIO()
# Default Page Size = A4
c = canvas.Canvas(output_buffer, pagesize=PAGESIZE)
# you can also add image instead of text
# c.drawImage("logo.png", X, Y, 160, 160)
# Set the size and type of the font
c.setFont(FONTNAME, FONTSIZE)
# Set the color
if isinstance(COLOR, tuple):
color = (c/255 for c in COLOR)
c.setFillColorRGB(*color)
else:
c.setFillColor(COLOR)
# Rotate according to the configured parameter
c.rotate(ROTATION_ANGLE)
# Position according to the configured parameter
c.drawString(X, Y, wm_text)
c.save()
return True, output_buffer
return False, None
该函数执行以下操作:
- 创建水印文件并将其存储在内存中。
- 使用reportlab在我们创建的画布上应用之前定义的参数。
如何在Python中为PDF文件加水印?请注意drawString()
,你可以使用drawImage()
绘制图像来代替使用该方法来编写文本,就像上面函数中的注释一样。
def save_watermark(wm_buffer, output_file):
"""
Saves the generated watermark template to disk
"""
with open(output_file, mode='wb') as f:
f.write(wm_buffer.getbuffer())
f.close()
return True
save_watermark()
将生成的水印模板保存到物理文件中,以防你需要对其进行可视化。
现在让我们编写负责为给定 PDF 文件添加水印的函数:
def watermark_pdf(input_file: str, wm_text: str, pages: Tuple = None):
"""
Adds watermark to a pdf file.
"""
result, wm_buffer = create_watermark(wm_text)
if result:
wm_reader = PdfFileReader(wm_buffer)
pdf_reader = PdfFileReader(open(input_file, 'rb'), strict=False)
pdf_writer = PdfFileWriter()
try:
for page in range(pdf_reader.getNumPages()):
# If required to watermark specific pages not all the document pages
if pages:
if str(page) not in pages:
continue
page = pdf_reader.getPage(page)
page.mergePage(wm_reader.getPage(0))
pdf_writer.addPage(page)
except Exception as e:
print("Exception = ", e)
return False, None, None
return True, pdf_reader, pdf_writer
Python为PDF文件加水印示例解释:此功能旨在将输入的 PDF 文件与生成的水印合并。它接受以下参数:
input_file
:PDF 文件的水印路径。wm_text
:要设置为水印的文本。pages
:要加水印的页面。
它执行以下操作:
- 创建水印并将其存储在内存缓冲区中。
- 遍历输入文件的所有页面,并将每个选定页面与先前生成的水印合并。水印就像页面顶部的叠加层。
- 将结果页面添加到
pdf_writer
对象。
def unwatermark_pdf(input_file: str, wm_text: str, pages: Tuple = None):
"""
Removes watermark from the pdf file.
"""
pdf_reader = PdfFileReader(open(input_file, 'rb'), strict=False)
pdf_writer = PdfFileWriter()
for page in range(pdf_reader.getNumPages()):
# If required for specific pages
if pages:
if str(page) not in pages:
continue
page = pdf_reader.getPage(page)
# Get the page content
content_object = page["/Contents"].getObject()
content = ContentStream(content_object, pdf_reader)
# Loop through all the elements page elements
for operands, operator in content.operations:
# Checks the TJ operator and replaces the corresponding string operand (Watermark text) with ''
if operator == b_("Tj"):
text = operands[0]
if isinstance(text, str) and text.startswith(wm_text):
operands[0] = TextStringObject('')
page.__setitem__(NameObject('/Contents'), content)
pdf_writer.addPage(page)
return True, pdf_reader, pdf_writer
此功能的目的是从 PDF 文件中去除水印文本。它接受以下参数:
input_file
:PDF 文件的水印路径。wm_text
:要设置为水印的文本。pages
:要加水印的页面。
它执行以下操作:
- 遍历输入文件的页面并获取每个页面的内容。
- 使用抓取的内容,它找到运算符
TJ
并替换此运算符后面的字符串(水印文本)。 - 将合并后的结果页面添加到
pdf_writer
对象。
def watermark_unwatermark_file(**kwargs):
input_file = kwargs.get('input_file')
wm_text = kwargs.get('wm_text')
# watermark -> Watermark
# unwatermark -> Unwatermark
action = kwargs.get('action')
# HDD -> Temporary files are saved on the Hard Disk Drive and then deleted
# RAM -> Temporary files are saved in memory and then deleted.
mode = kwargs.get('mode')
pages = kwargs.get('pages')
temporary, output_file = get_output_file(
input_file, kwargs.get('output_file'))
if action == "watermark":
result, pdf_reader, pdf_writer = watermark_pdf(
input_file=input_file, wm_text=wm_text, pages=pages)
elif action == "unwatermark":
result, pdf_reader, pdf_writer = unwatermark_pdf(
input_file=input_file, wm_text=wm_text, pages=pages)
# Completed successfully
if result:
# Generate to memory
if mode == "RAM":
output_buffer = BytesIO()
pdf_writer.write(output_buffer)
pdf_reader.stream.close()
# No need to create a temporary file in RAM Mode
if temporary:
output_file = input_file
with open(output_file, mode='wb') as f:
f.write(output_buffer.getbuffer())
f.close()
elif mode == "HDD":
# Generate to a new file on the hard disk
with open(output_file, 'wb') as pdf_output_file:
pdf_writer.write(pdf_output_file)
pdf_output_file.close()
pdf_reader.stream.close()
if temporary:
if os.path.isfile(input_file):
os.replace(output_file, input_file)
output_file = input_file
上面的函数接受几个参数:
input_file
:PDF 文件的水印路径。wm_text
:要设置为水印的文本。action
: 执行是否加水印或取消水印文件的操作。mode
: 临时文件的位置是内存还是硬盘。pages
:要加水印的页面。
watermark_unwatermark_file()
函数调用先前定义的函数watermark_pdf()
或unwatermark_pdf()
取决于所选的action
.
Python如何为PDF文件加水印?基于选定的mode
,如果输出文件与输入文件的路径相似或未指定输出文件,则将创建一个临时文件,以防选定的mode
是HDD
(硬盘驱动器)。
接下来,让我们添加从包含多个 PDF 文件的文件夹中添加或删除水印的功能:
def watermark_unwatermark_folder(**kwargs):
"""
Watermarks all PDF Files within a specified path
Unwatermarks all PDF Files within a specified path
"""
input_folder = kwargs.get('input_folder')
wm_text = kwargs.get('wm_text')
# Run in recursive mode
recursive = kwargs.get('recursive')
# watermark -> Watermark
# unwatermark -> Unwatermark
action = kwargs.get('action')
# HDD -> Temporary files are saved on the Hard Disk Drive and then deleted
# RAM -> Temporary files are saved in memory and then deleted.
mode = kwargs.get('mode')
pages = kwargs.get('pages')
# Loop though the files within the input folder.
for foldername, dirs, filenames in os.walk(input_folder):
for filename in filenames:
# Check if pdf file
if not filename.endswith('.pdf'):
continue
# PDF File found
inp_pdf_file = os.path.join(foldername, filename)
print("Processing file:", inp_pdf_file)
watermark_unwatermark_file(input_file=inp_pdf_file, output_file=None,
wm_text=wm_text, action=action, mode=mode, pages=pages)
if not recursive:
break
如何在Python中为PDF文件加水印?该函数根据recursive
参数的值是否递归地遍历指定文件夹的文件,并逐个处理这些文件。
接下来,让我们创建一个实用函数来检查路径是文件路径还是目录路径:
def is_valid_path(path):
"""
Validates the path inputted and checks whether it is a file path or a folder path
"""
if not path:
raise ValueError(f"Invalid Path")
if os.path.isfile(path):
return path
elif os.path.isdir(path):
return path
else:
raise ValueError(f"Invalid Path {path}")
Python为PDF文件加水印示例 - 现在我们拥有了本教程所需的所有函数,让我们制作最后一个用于解析命令行参数的函数:
def parse_args():
"""
Get user command line parameters
"""
parser = argparse.ArgumentParser(description="Available Options")
parser.add_argument('-i', '--input_path', dest='input_path', type=is_valid_path,
required=True, help="Enter the path of the file or the folder to process")
parser.add_argument('-a', '--action', dest='action', choices=[
'watermark', 'unwatermark'], type=str, default='watermark',
help="Choose whether to watermark or to unwatermark")
parser.add_argument('-m', '--mode', dest='mode', choices=['RAM', 'HDD'], type=str,
default='RAM', help="Choose whether to process on the hard disk drive or in memory")
parser.add_argument('-w', '--watermark_text', dest='watermark_text',
type=str, required=True, help="Enter a valid watermark text")
parser.add_argument('-p', '--pages', dest='pages', type=tuple,
help="Enter the pages to consider e.g.: [2,4]")
path = parser.parse_known_args()[0].input_path
if os.path.isfile(path):
parser.add_argument('-o', '--output_file', dest='output_file',
type=str, help="Enter a valid output file")
if os.path.isdir(path):
parser.add_argument('-r', '--recursive', dest='recursive', default=False, type=lambda x: (
str(x).lower() in ['true', '1', 'yes']), help="Process Recursively or Non-Recursively")
# To Porse The Command Line Arguments
args = vars(parser.parse_args())
# To Display The Command Line Arguments
print("## Command Arguments #################################################")
print("\n".join("{}:{}".format(i, j) for i, j in args.items()))
print("######################################################################")
return args
下面是定义的参数:
input_path
: 一个必需参数,用于输入要处理的文件或文件夹的路径,该参数与is_valid_path()
之前定义的函数相关联。action
:将要执行的动作,要么是watermark
或unwatermark
PDF文件,默认是水印。mode
: 指定生成的临时文件的目的地,是内存还是硬盘。watermark_text
:要设置为水印的字符串。pages
:要加水印的页面(例如第一页是[0]
,第二页和第四页是[1, 3]
等)。如果未指定,则为所有页面。output_file
: 输出文件的路径。recursive
: 是否递归处理文件夹。
现在我们拥有了一切,让我们编写主要代码以根据传递的参数执行:
if __name__ == '__main__':
# Parsing command line arguments entered by user
args = parse_args()
# If File Path
if os.path.isfile(args['input_path']):
# Extracting File Info
get_info(input_file=args['input_path'])
# Encrypting or Decrypting a File
watermark_unwatermark_file(
input_file=args['input_path'], wm_text=args['watermark_text'], action=args[
'action'], mode=args['mode'], output_file=args['output_file'], pages=args['pages']
)
# If Folder Path
elif os.path.isdir(args['input_path']):
# Encrypting or Decrypting a Folder
watermark_unwatermark_folder(
input_folder=args['input_path'], wm_text=args['watermark_text'],
action=args['action'], mode=args['mode'], recursive=args['recursive'], pages=args['pages']
)
相关: 如何在 Python 中从 PDF 中提取图像。
现在让我们测试我们的程序,如果你打开一个终端窗口并输入:
$ python pdf_watermarker.py --help
它将显示定义的参数及其各自的描述:
usage: pdf_watermarker.py [-h] -i INPUT_PATH [-a {watermark,unwatermark}] [-m {RAM,HDD}] -w WATERMARK_TEXT [-p PAGES]
Available Options
optional arguments:
-h, --help show this help message and exit
-i INPUT_PATH, --input_path INPUT_PATH
Enter the path of the file or the folder to process
-a {watermark,unwatermark}, --action {watermark,unwatermark}
Choose whether to watermark or to unwatermark
-m {RAM,HDD}, --mode {RAM,HDD}
Choose whether to process on the hard disk drive or in memory
-w WATERMARK_TEXT, --watermark_text WATERMARK_TEXT
Enter a valid watermark text
-p PAGES, --pages PAGES
Enter the pages to consider e.g.: [2,4]
Python如何为PDF文件加水印?现在让我们以给我的简历加水印为例:
$ python pdf_watermarker.py -a watermark -i "CV Bassem Marji_Eng.pdf" -w "CONFIDENTIAL" -o CV_watermark.pdf
将产生以下输出:
## Command Arguments #################################################
input_path:CV Bassem Marji_Eng.pdf
action:watermark
mode:RAM
watermark_text:CONFIDENTIAL
pages:None
output_file:CV_watermark.pdf
######################################################################
## File Information ##################################################
File:CV Bassem Marji_Eng.pdf
Encrypted:False
Author:TMS User
Creator:Microsoft® Word 2013
Producer:Microsoft® Word 2013
Subject:None
Title:None
Number of pages:1
######################################################################
Python为PDF文件加水印示例如下,这就是输出CV_watermark.pdf
文件的样子:
$ python pdf_watermarker.py -a unwatermark -i "CV_watermark.pdf" -w "CONFIDENTIAL" -o CV.pdf
这次水印将被删除并保存到一个新的 PDF 文件中CV.pdf
。
如何在Python中为PDF文件加水印?你还可以分别为模式和页面设置-m
和-p
。你还可以为位于特定路径下的 PDF 文件列表添加水印:
$ python pdf_watermarker.py -i "C:\Scripts\Test" -a "watermark" -w "CONFIDENTIAL" -r False
或者从一堆 PDF 文件中去除水印:
$ python pdf_watermarker.py -i "C:\Scripts\Test" -a "unwatermark" -w "CONFIDENTIAL" -m HDD -p[0] -r False
结论
Python如何为PDF文件加水印?在本教程中,你学习了如何使用 Python 中的 reportlab 和 PyPDF4 库在 PDF 文件中添加和删除水印。我希望这篇文章能帮助你理解这个很酷的功能。
以下是一些其他与 PDF 相关的教程:
- 如何在 Python 中将 PDF 转换为 Docx
- 如何在 Python 中合并 PDF 文件
- 如何在 Python 中提取 PDF 元数据