如何在Python中为PDF文件加水印?实现代码示例

2021年11月17日03:50:14 发表评论 1,454 次浏览

本文带你了解如何使用 Python 中的 PyPDF4 和 reportlab 库在 PDF 文件中添加和删除水印,包括详细的Python为PDF文件加水印示例代码。

便携式文档格式 (PDF)标准化为 ISO 32000,是 Adob​​e 于 1993 年开发的一种文件格式,用于以独立于应用程序软件、硬件和操作系统的方式呈现文档,包括文本格式和图像。

如何在Python中为PDF文件加水印?基于PostScript 语言,每个 PDF 文件都封装了一个固定版式平面文档的完整描述,包括文本、字体、矢量图形、光栅图像和其他显示所需的信息。

对 PDF 的熟悉导致其作为数字存档领域的解决方案被快速而广泛地采用。由于 PDF 比其他文件格式更通用,因此几乎可以从任何操作系统或设备轻松查看它们显示的信息。

Python如何为PDF文件加水印?在本教程中,你将学习如何在 Python 中使用PyPDF4reportlab为 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,如果输出文件与输入文件的路径相似或未指定输出文件,则将创建一个临时文件,以防选定的modeHDD(硬盘驱动器)。

接下来,让我们添加从包含多个 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:将要执行的动作,要么是watermarkunwatermarkPDF文件,默认是水印。
  • 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文件加水印?实现代码示例

现在让我们删除添加的水印:

$ 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 元数据
木子山

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: