<Python/pikepdf>送るメニューからPDFファイル達を一括で暗号化する方法

[PR]
目次

こんな感じ。

まず、下のサイトからPythonをダウンロードしてインストールする。

コマンドプロンプトを開いて以下のコマンドを実行しPDFファイルを操作するのに使うpikepdfというライブラリをインストールする。

pip install pikepdf

以下のコードをコピペして適当なファイル名(例えば「EncryptPdfContent.py」とか)で適当なフォルダに保存する。このときファイルのエンコードはBOM付きのUTF-8にしておく。

#
# コマンドライン引数で指定されたPDFファイル達を暗号化する。
#
# 準備:
# 1. pipコマンドをアップデートする。
#    C:\> python -m pip install --upgrade pip
# 2. パッケージpikepdfをインストールする。
#    C:\> pip install pikepdf
#

#
# モジュールをインポートする。
#
import os
import sys
import shutil
import ctypes
import pikepdf
import tkinter as tk
import tkinter.messagebox as msgbox

#
# 定数を宣言する。
#
CAPTION = 'PDFファイルを暗号化'
ICON = "C:/Program Files/MyScripts/icons/~~encryptpdf.ico"

#
# ダイアログを表示して暗号化に使用するパスワードを取得する。
#
def get_password():

    # ダイアログの位置とサイズを定義する。
    screen_width = root.winfo_screenwidth(); screen_height = root.winfo_screenheight()
    window_width = 545; window_height = 202
    root.geometry("%dx%d+%d+%d" % (window_width, window_height, (screen_width - window_width) / 2, (screen_height - window_height) / 2))
    # ダイアログのリサイズを禁止する。
    root.resizable(width=False, height=False)
    # エスケープキー押下時の処理を登録する。
    root.bind("<Escape>", lambda event: cancel_button_handler(None))
    # 閉じるマークボタン押下時用のハンドラを登録する。
    root.wm_protocol('WM_DELETE_WINDOW', lambda: cancel_button_handler(None))

    # ガイドメッセージ用のラベルを作成する。
    tk.Label(root, text='1文字以上のパスワードを入力してください。').place(x=20, y=15)

    # ラベルの位置とサイズを定義する。
    label_width = 10
    label_left = 20
    label_top = 63

    # パスワード入力用のエディットボックス用のラベルを作成する。
    tk.Label(root, text='パスワード:').place(x=label_left, y=label_top)
    tk.Label(root, text='パスワード(確認):').place(x=label_left, y=label_top+38)

    # エディットボックスの位置とサイズを定義する。
    editbox_width = 36
    editbox_left = 150
    editbox_top = 61

    # パスワード入力用のエディットボックスを作成する。
    editboxPassword1 = tk.Entry(root, width=editbox_width, show='*')
    editboxPassword1.place(x=editbox_left, y=editbox_top)
    editboxPassword2 = tk.Entry(root, width=editbox_width, show='*')
    editboxPassword2.place(x=editbox_left, y=editbox_top+38)

    # エディットボックス内のリターンキー押下時の処理を登録する。
    editboxPassword1.bind("<Return>", \
        lambda event: editboxPassword2.focus_set() if len(editboxPassword1.get()) > 0 else ok_button_handler(event))
    editboxPassword2.bind("<Return>", lambda event: ok_button_handler(event))
    # エディットボックス内のエスケープキー押下時の処理を登録する。
    editboxPassword1.bind("<Escape>", lambda event: cancel_button_handler(None))
    editboxPassword2.bind("<Escape>", lambda event: cancel_button_handler(None))

    # OKボタン押下時用のハンドラを定義する。
    def ok_button_handler(event):
        # エディットボックスからパスワードを取り出す。
        password = editboxPassword1.get()
        confirm = editboxPassword2.get()
        # 空欄なら。。。
        if len(password) == 0:
            msgbox.showwarning(CAPTION, f'1文字以上のパスワードを入力してください。')
            editboxPassword1.focus_set()
            return "break"
        # 一致しないなら。。。
        if password != confirm:
            msgbox.showwarning(CAPTION, f'パスワードが一致しません。\rもう一度入力してください。')
            editboxPassword2.focus_set()
            return "break"
        # ここまで来たらエラーなしと判断し呼び出し元へ返る。
        root.quit()

    # キャンセルボタン押下時用のハンドラを定義する。
    def cancel_button_handler(event):
        res = msgbox.askokcancel(CAPTION, f'キャンセルされました。\r処理を中断します。', default=msgbox.CANCEL)
        if res:
            sys.exit(255)
        return "break"

    # ボタンの位置とサイズを定義する。
    button_width = 10
    button_left = 281
    button_top = 147

    # OKボタンを作成する。
    buttonOK = tk.Button(root, text='OK', width=button_width)
    buttonOK.place(x=button_left, y=button_top)
    buttonOK.bind("<Button-1>", ok_button_handler)
    buttonOK.bind("<Return>", lambda event: ok_button_handler(None))
    buttonOK.bind("<Escape>", lambda event: cancel_button_handler(None))

    # キャンセルボタンを作成する。
    buttonCancel = tk.Button(root, text='キャンセル', width=button_width)
    buttonCancel.place(x=button_left+125, y=button_top)
    buttonCancel.bind("<Button-1>", cancel_button_handler)
    buttonCancel.bind("<Return>", lambda event: cancel_button_handler(None))
    buttonCancel.bind("<Escape>", lambda event: cancel_button_handler(None))

    # ダイアログを表示する。
    root.deiconify()
    # ダイアログを前面に移動してアクティブにする。
    root.lift()
    root.focus_force()
    # パスワード入力用のエディットボックスにフォーカスをセットする。
    editboxPassword1.focus_set()
    # メッセージループを回す。
    root.mainloop()

    # パスワードを返す。
    return editboxPassword1.get()

#
# メインルーチン。
#
if __name__ == '__main__':

    # 例外処理。。。
    try:

        # 高DPI対策を行う。
        ctypes.windll.shcore.SetProcessDpiAwareness(True)

        # メインウインドウを作成する。
        root = tk.Tk()
        root.withdraw()
        # ダイアログのキャプションとアイコンを設定する。
        root.title(CAPTION)
        if os.path.isfile(ICON):
            root.iconbitmap(ICON)

        # コマンドライン引数を確認する。
        if len(sys.argv) == 1:
            msgbox.showerror(CAPTION, f'ファイルが選択されていません。\rファイルを1件以上選択して実行してください。\r処理を中断します。')
            sys.exit(255)

        # 選択されたPDFファイルについて。。。
        targets = set([])
        for file in sys.argv[1:]:
            # 指定されたファイルが存在しなければプログラムを終了する。
            if not os.path.isfile(file):
                msgbox.showerror(CAPTION, f'指定されたファイルが存在しないかフォルダが選択されました。\r処理を中断します。\r\r{file}')
                sys.exit(255)
            # 指定されたファイルの拡張子が".pdf"でなければそのファイルをスキップする。
            ext = os.path.splitext(file)[1]
            if ext.lower() != '.pdf':
                msgbox.showwarning(CAPTION, f'指定されたファイルの拡張子が".pdf"でありません。\rこのファイルをスキップします。\r\r{file}')
                continue
            # PDFファイルを確認する。
            with pikepdf.open(file) as org:
                # PDFファイルが既に暗号化されていたら処理をスキップする。
                if org.is_encrypted:
                    msgbox.showwarning(CAPTION, f'指定されたPDFファイルは既に暗号化されています。\rこのファイルをスキップします。\r\r{file}')
                    continue
            # PDFファイルを処理対象に加える。
            targets.add(file)

        # スキップ対象とならなかったPDFファイルの数が0件の場合はプログラムを終了する。
        if len(targets) == 0:
            msgbox.showwarning(CAPTION, f'処理対象となるPDFファイルの数が0件です。\r処理を中断します。')
            sys.exit(255)
        skipped = len(sys.argv[1:]) - len(targets)

        # ダイアログを表示して暗号化に使用するパスワードを取得する。
        password = get_password()

        # 選択されたPDFファイルについて。。。
        for pdf in targets:
            # PDFファイルをバックアップする。
            bak = pdf + '.bak'
            if os.path.isfile(bak):
                res = msgbox.askquestion(CAPTION, f'PDFファイルのバックアップができません。\rバックアップファイルと同じ名前のファイルが存在しています。\rファイルを上書きしますか。\r\r{bak}', default=msgbox.NO)
                if res  == 'yes':
                    shutil.copy2(pdf, bak)
            else:
                shutil.copy2(pdf, bak)
            # PDFファイルを暗号化する。
            with pikepdf.open(pdf, allow_overwriting_input=True) as org:
                org.save(pdf, encryption=pikepdf.Encryption(user=password))

        # 終了メッセージを表示する。
        msgbox.showinfo(CAPTION, f'すべての処理が完了しました。\r\r暗号化したファイル数: %d件\rスキップしたファイル数: %d件' % (len(targets), skipped))

        # 終了する。
        root.withdraw()
        sys.exit(0)

    # すべての例外をキャッチする。
    except Exception as ex:

        # もしTkinterが使えれば。。。
        msgbox.showerror(CAPTION, f'例外が発生しました。\rコンソール上で起動してスタックトレースを確認して下さい。\r\r{ex}')
        # 例外を表示する。
        print(f'\n{ex}\n')
        # スタックトレースを表示する。
        from traceback import TracebackException
        from traceback import StackSummary
        tb = TracebackException.from_exception(ex)
        summary = StackSummary.from_list(tb.stack)
        print(''.join(summary.format()))

以下のようなリンク先を持つショートカットを作る。下のは、上のPythonのコードを「%ProgramFiles%\MyScripts\EncryptPdfContent.py」にコピペした場合。ショートカットは適当なファイル名(例えば「PDFファイルを暗号化」とか)に変更する。

python.exe "%ProgramFiles%\MyScripts\EncryptPdfContent.py"

作ったショートカットを以下のフォルダにコピーする。

%USERPROFILE%\AppData\Roaming\Microsoft\Windows\SendTo

PDFファイル達を選択して右クリックし表示された送るメニューから上で作ったショートカットをクリックする。パスワードを入力するダイアログが表示されるので適当に入力するとPDFファイル達が暗号化される。

以上

コメント

コメントする

コメントは日本語で入力してください(スパム対策)。

CAPTCHA

目次