スポンサーリンク

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

こんな感じ。

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

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

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 = r"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)
        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))

    # すべての例外をキャッチする。
    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のバージョンが3.12でPythonのコードを「%ProgramFiles%\MyScripts\EncryptPdfContent.py」にコピペした場合。ショートカットは適当なファイル名(例えば「PDFファイルを暗号化」とか)に変更する。

%USERPROFILE%\AppData\Local\Programs\Python\Python312\python.exe "%ProgramFiles%\MyScripts\EncryptPdfContent.py"

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

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

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

以上


この記事を書いた人


 こんにちは、その日暮らしです/地方国立大理系院卒→大手大企業就職→ソフト開発二十年超→メンタル壊して退職→ちょっと回復→資格取得頑張る(日商簿記3級と応用情報は合格でデスペはギブアップ)→コロナ禍で再就職無理→離婚orz→実家へ出戻ってこどおじ化(笑)→WordPressの勉強のためブログに挑戦/そんな訳でブログの更新頻度は低いですが日々いろんなことを試して得た知識を投稿していこうと思っています/以上