重い PDF を Pythonista で軽量・分割した話


この記事は Pythonista Advent Calendar 2017 の9日目の記事です。

Pythonista でライフハックするための、小ネタを紹介します。

概要

iPhone と Pythonista があれば、いつでもどこでもプログラミングが出来ます。
とは言えフリック入力で Python Syntaxを打ち続けるのは、エディタ支援があっても流石につらいので、

  1. Bluetoothキーボード
  2. バンカーリング

という最小限のギアを組み合わせて、ライフハックしています。

📱 実際にやってみると?

iPhone でプログラミングしてみて感じることは、

  1. 検索する時にiPhoneを手に持つと、Bluetoothペアリング状態でソフトウェアキーボードが隠れてつらい
  2. 画面サイズに限界のあるiPhoneで、ドキュメントを見ながらアプリを行ったり来たりするのはつらい

でした。iPad ProとSplitViewを使えば上記の問題を解決できますが、iPad Proは外に持ち出しません...

いろいろ試行錯誤して、眼を通したいページやドキュメントを PDF にして紙の資料にすれば、
解決できるのでは?と考えました。

📃 紙の資料にプリントする

iPhoneからPDFをプリントするアプリはいくつかありますが、
私は自宅にプリンターを所持していないので、コンビニでプリント出来るアプリをよく使っています。

ただ、そのアプリに送信出来るPDFのファイルサイズが6MBまでという制約があり、対処に困りました。
そのためだけにPDF分割のアプリをダウンロードしたり、Macでリサイズしてもう一度iPhoneに戻すのは、
あまりスマートではありません。

...

前置きが長くなりました。
この問題に対処するためにPythonistaを使って、PDFのファイルサイズを6MBまでに抑えて分割します。

🐍 Python で解決

# -*- coding: utf-8 -*-

import os
import appex

from PIL import Image
from PyPDF2 import PdfFileWriter, PdfFileReader

div = 6

def imageWriter(page0):
    xObject = page0['/Resources']['/XObject'].getObject()
    for obj in xObject:
        if xObject[obj]['/Subtype'] == '/Image':
            size = (xObject[obj]['/Width'], xObject[obj]['/Height'])
            data = xObject[obj].getData()
            if xObject[obj]['/ColorSpace'] == '/DeviceRGB':
                mode = "RGB"
            else:
                mode = "P"
            if xObject[obj]['/Filter'] == '/FlateDecode':
                img = Image.frombytes(mode, size, data)
                img.save(obj[1:] + ".png")
            elif xObject[obj]['/Filter'] == '/DCTDecode':
                with open(obj[1:] + ".jpg", "wb") as img:
                    img.write(data)
            elif xObject[obj]['/Filter'] == '/JPXDecode':
                with open(obj[1:] + ".jp2", "wb") as img:
                    img.write(data)

def main():
    src = appex.get_file_path() 
    src = PdfFileReader(src, 'rb')
    items = [range(i, min(i + div, src.numPages)) for i in range(0, src.numPages, div)]

    if not os.path.exists('pdf'): 
        os.mkdir('pdf')

    for i, lst in enumerate(items):
        dst = PdfFileWriter()
        for j in lst:
            dst.addPage(src.getPage(j))
            with open('pdf/%03d.pdf' % i, 'wb') as out:
                dst.removeLinks()
                dst.write(out)

if __name__ == '__main__':
    main()

PyPDF2 内で余計なメタデータを削除・ページ数を分割することで、1ファイルあたりのサイズを6MBまで
軽量化しています。Pythonista Documents 内の pdf フォルダに書き出されます。

PILPyPDF2 はPythonista built-in モジュールなので、別途 pip インストールする
必要がありません。appex モジュールはPythonista用モジュールなので、他の環境では
os.path モジュールなどに読み替える必要があります。

まとめ

ポケットに収まるサイズ感でモバイルハック出来るのがPythonista最大のメリットなので、
工夫次第でなんでも出来そうです。カフェの小さなテーブルでも邪魔にならないサイズ感です☕

日常の小さな問題を解決する手段としての、Pythonの小ネタの紹介でした。