見出し画像

Python(Pillow、piked)とPostscrit(Ghostscript)で画像とPDFを圧縮

Python初心者です。
半年くらいPythonを活用しています。

今回も既存のアプリでやれば早いのでしょうが
大量の重いJPGとPDFを共有する必要があり、
これもしかして軽くしても業務に支障がないのでは
と思い
Pythonでチャレンジすることにしました。

ではPython(Pillow、piked)とPostscrit(Ghostscript)
画像とPDFを圧縮していきます。



まずPDF圧縮


いつものようにgoogle driveをマウント

from google.colab import drive
drive.mount('/content/drive')


まずPDFをpikedで圧縮

!pip install pikepdf

pukepdfとは

pikepdfは、PDFファイルを扱うためのPythonライブラリです。PDFファイルの圧縮や、ページの操作などができます。主な役割は以下の通りです。

  • PDFファイルの読み込みと保存: PDFファイルをPythonプログラム内で読み込み、保存することができます。

  • PDFの圧縮: PDFファイル内の画像やフォント、メタデータを最適化し、ファイルサイズを削減します。圧縮後もPDFのレイアウトや品質を保ちつつ、ファイル容量を減らすことができます。

  • PDFの操作: ページの追加、削除、抽出、結合、分割など、PDFファイル内のページ操作を行うことができます。


そしてprint_file_size()関数でビフォーアフターをプリント

import os
import pikepdf

pdf_input_dir = '/content/drive/MyDrive/Project_folder/tw'
pdf_output_dir = '/content/drive/MyDrive/OTJ/Project_folder/compressed_pdfs'

if not os.path.exists(pdf_output_dir):
    os.makedirs(pdf_output_dir)

# ファイルサイズをプリントする関数
def print_file_size(file_path, label):
    size = os.path.getsize(file_path)
    print(f"{label}: {os.path.basename(file_path)} - {size / 1024:.2f} KB")

# PDF圧縮の関数(optimize_versionを削除)
def compress_pdf(input_pdf_path, output_pdf_path):
    pdf = pikepdf.open(input_pdf_path)
    # 最適化なしでPDFをそのまま保存
    pdf.save(output_pdf_path)

# フォルダ内のすべてのPDFを圧縮
for filename in os.listdir(pdf_input_dir):
    if filename.endswith(".pdf"):
        input_path = os.path.join(pdf_input_dir, filename)
        output_path = os.path.join(pdf_output_dir, filename)
        
        # 圧縮前のファイルサイズを表示
        print_file_size(input_path, "圧縮前")
        
        # PDFの圧縮
        compress_pdf(input_path, output_path)
        
        # 圧縮後のファイルサイズを表示
        print_file_size(output_path, "圧縮後")
        print("---")

結果

圧縮前: XX_before.pdf - 6214.02 KB
圧縮後: XX_after.pdf - 6216.83 KB
圧縮前: YY_before.pdf - 4470.90 KB
圧縮後:YY_after.pdf - 4473.85 KB

あれ?!むしろ増えている

pikepdfによる圧縮では、PDF内のメタデータや一部の非表示コンテンツの最適化を行うため、PDFのファイルサイズが大幅に削減されない場合があります。また、pikepdfは、画像圧縮やフォントの埋め込みなど、積極的なファイルの軽量化を行うわけではありません。

なるほど


Ghostscriptを使ったPDF圧縮

GhostScriptとは

Ghostscriptは、PostScriptおよびPDFファイルの読み込み・操作・出力を行うために使われます。

PDFファイルの圧縮や、PDFのレンダリング(表示・印刷)、PDFファイルのページを分割したり、複数のPDFファイルを1つに結合したり、PostScriptファイルからPDFを生成することなどが可能です。

ファイル内の画像の再サンプリング、フォントの最適化、不要なメタデータやオブジェクトの削除を行い、PDFの軽量化を実現します。

PDFやPostScriptファイルを印刷するためのレンダリングエンジンとしても使用されます。これにより、PDFファイルを直接プリンタに送信することができます。

PostScriptファイルからPDFを作成したり、逆にPDFをPostScriptファイルに変換したりすることができます。また、他の形式(PNGやJPEGなど)の画像ファイルとして出力することも可能です。

圧縮のレベルの設定

  • /screen: 低解像度で、ファイルサイズを大幅に削減します。

  • /ebook: 中程度の圧縮で、電子書籍向け。

  • /printer: 高解像度での印刷用に最適化します。

  • /prepress: 商業印刷向けで、最高品質。

今回は/screenでやってみる

インストール

!apt-get install ghostscript


import os
import subprocess

pdf_input_dir = '/content/drive/MyDrive/Project_folder/tw'
pdf_output_dir = '/content/drive/MyDrive/OTJ/Project_folder/compressed_pdfs'

if not os.path.exists(pdf_output_dir):
    os.makedirs(pdf_output_dir)

def print_file_size(file_path, label):
    size = os.path.getsize(file_path)
    print(f"{label}: {os.path.basename(file_path)} - {size / 1024:.2f} KB")

# Ghostscriptを使ってPDFを圧縮する関数
def compress_pdf(input_pdf_path, output_pdf_path):
    gs_command = [
        'gs', '-sDEVICE=pdfwrite', '-dCompatibilityLevel=1.4',
        '-dPDFSETTINGS=/screen',  # 画質を調整 (screen、ebook、printerなど)
        '-dNOPAUSE', '-dQUIET', '-dBATCH',
        f'-sOutputFile={output_pdf_path}', input_pdf_path
    ]
    subprocess.run(gs_command, check=True)

# フォルダ内のすべてのPDFを圧縮
for filename in os.listdir(pdf_input_dir):
    if filename.endswith(".pdf"):
        input_path = os.path.join(pdf_input_dir, filename)
        output_path = os.path.join(pdf_output_dir, filename)
        
        # 圧縮前のファイルサイズを表示
        print_file_size(input_path, "圧縮前")
        
        # Ghostscriptを使ってPDFの圧縮
        compress_pdf(input_path, output_path)
        
        # 圧縮後のファイルサイズを表示
        print_file_size(output_path, "圧縮後")
        print("---")

結果

圧縮前: XX_before.pdf - 6214.02 KB
圧縮後: XX_after.pdf - 266.49 KB
圧縮前: YY_before.pdf - 4470.90 KB
圧縮後:YY_after.pdf - 264.64 KB

かなり軽くなりましたがPDF内の画像がギザギザ
これでは業務に支障があるので

'-dPDFSETTINGS=/screen', # 画質を調整 (screen、ebook、printerなど)
これを-dPDFSETTINGS=/printer でやってみる

圧縮後: XX_after.pdf -2078.33 KB
圧縮後:YY_after.pdf - 1911.38 KB

OK、OK



JPGを圧縮


JPGをpillowで圧縮

!pip install pillow

Pillowとは

Pillowは、画像を処理・操作するためのライブラリです。具体的な役割としては、以下のようなことができます。

  • 画像の読み込み: JPEG、PNG、GIF、BMPなど様々な形式の画像ファイルをPythonで読み込むことができます。

  • 画像の保存: 画像を別の形式に変換したり、サイズや圧縮率を指定して保存することが可能です。

  • 画像の操作: 画像のリサイズ、トリミング、回転、フィルタ処理(ぼかし、シャープなど)、透過処理などの基本的な画像操作が簡単に行えます。

  • 画像の圧縮: JPEGファイルの品質を調整して圧縮することができます。例えば、画質をできるだけ落とさずにファイルサイズを減らすためのJPEG圧縮も可能です。

import os
from PIL import Image

input_dir = '/content/drive/MyDrive/Project_folder/tw/aaa'
output_dir = '/content/drive/MyDrive/compressed_images'

if not os.path.exists(output_dir):
    os.makedirs(output_dir)

def compress_image(image_path, output_path, quality=85):
    img = Image.open(image_path)
    img.save(output_path, 'JPEG', quality=quality)

def print_file_size(file_path, label):
    size = os.path.getsize(file_path)
    print(f"{label}: {os.path.basename(file_path)} - {size / 1024:.2f} KB")

for filename in os.listdir(input_dir):
    if filename.endswith(".jpg") or filename.endswith(".jpeg"):
        input_path = os.path.join(input_dir, filename)
        output_path = os.path.join(output_dir, filename)
        
        # 圧縮前のファイルサイズ
        print_file_size(input_path, "圧縮前")
        
        # 画像の圧縮
        compress_image(input_path, output_path)
        
        # 圧縮後のファイルサイズ
        print_file_size(output_path, "圧縮後")
        print("---")

結果

圧縮前: XX_before.jpg - 2688.64
圧縮後: XX_after.jpg - 2145.15 KB
圧縮前: YY_before.jpg - 2956.11 K
圧縮後:YY_after.jpg - 2348.97 KB

85%にダウン

実際の画像は公開できません。すみません。

def compress_image(image_path, output_path, quality=85):
現在の設定(85)は比較的高品質ですので、これを下げることで圧縮率を高める

quality=70でやってみる

70%にダウン

画像サイズを落とすほかの選択肢

画像のリサイズ(解像度を下げる)

もし画像の解像度が高い場合、リサイズすることでファイルサイズを大幅に減らすことができます。例えば、幅や高さを半分にすると、ファイルサイズも大幅に小さくなります。

def compress_and_resize_image(image_path, output_path, quality=85, resize_factor=0.5):
    img = Image.open(image_path)
    img = img.resize(
        (int(img.width * resize_factor), int(img.height * resize_factor)),
        Image.ANTIALIAS
    )
    img.save(output_path, 'JPEG', quality=quality)

resize_factorを 0.5 にすることで、解像度が半分になります。これによって、視覚的な違いがそれほどない場合でもファイルサイズを大幅に削減できます。

Progressive JPEGを使用

Pillowを使ってプログレッシブJPEGを保存すると、最初の読み込みで画像の簡易バージョンを表示し、詳細が後から表示されるため、Web表示において効果的です。ファイルサイズも減ることがあります。

def compress_image_with_progressive(image_path, output_path, quality=85):
    img = Image.open(image_path)
    img.save(output_path, 'JPEG', quality=quality, progressive=True)


WebP形式へ変更

JPEGよりもWebP形式に変換することで、同じ画質を保ちながらファイルサイズをさらに削減できます。PillowはWebP形式をサポートしています。
Web用途で効果的。

def convert_to_webp(image_path, output_path, quality=85):
    img = Image.open(image_path)
    img.save(output_path, 'WebP', quality=quality)


カラーの減少

画像がフルカラーではなくても良い場合、カラーパレットを256色に減らすことで、ファイルサイズを削減できます。

def reduce_colors(image_path, output_path, quality=85):
    img = Image.open(image_path).convert('P', palette=Image.ADAPTIVE, colors=256)
    img.save(output_path, 'JPEG', quality=quality)


まとめ

業務で重いPDFや画像データのやり取りが増えてくる
弊社でもNASなどを使っている。
アップロードやダウンロードでかなりストレスを感じることがある。

実際に解像度が悪くても業務に支障のない場合は
今回のように一括変換ができると早い。
今回は試行錯誤したけど次回からはサクッとできるでしょう!

実際にjpgは50くらいあるので一括変換は非常に便利に感じました。

また以下の4点も勉強になりました。

画像のサイズを下げる選択肢

  1. 画像のリサイズ(解像度を下げる)

  2. Progressive JPEGを使用

  3. WebP形式へ変更

  4. カラーの減少



この記事が気に入ったらサポートをしてみませんか?