0
1

djangoで画像の容量を圧縮してパフォーマンスの向上を図ってみた話し

Posted at

サーバー上でたくさんの写真を使用すると読み込みに時間がかかって仕方がない...

ということで画像の容量を圧縮すればいいのではと、色々調べてみた。画像の拡張子を調べてみるとJPEGの圧縮率が高く写真の記録やインターネット上で大きな画像を表示したい時に多く使用されるとのこと。ではどうやってこの圧縮を行うのか。一枚一枚圧縮してからアップロードなどというしちめんどくさいことはできない。と思い調べてみるとDjangoのカスタム管理コマンドで画像を圧縮するスクリプトを書けばいいということに行き着く。

Djangoのカスタム管理コマンド

*chat GPTとの対話にて作成。。。
アプリ/management/commands/compress_images.pyとディレクトリを作って以下のスクリプトを作成。

class Command(BaseCommand):
    help = 'メディアディレクトリ内の画像を全て85%に圧縮し、photo1_compressed、photo2_compressed、photo3_compressed フィールドに上書き保存'

    def handle(self, *args, **kwargs):
        media_root = settings.MEDIA_ROOT
        for root, dirs, files in os.walk(media_root):
            for file in files:
                if file.lower().endswith(('.png', '.jpg', '.jpeg')):
                    file_path = os.path.join(root, file)
                    self.compress_images(file_path)

    def compress_images(self, file_path):
        if '_compressed' in os.path.basename(file_path):
            self.stdout.write(self.style.SUCCESS(f'Skipping already compressed file {file_path}'))
            return

        compressed_file_path = f"{os.path.splitext(file_path)[0]}_compressed{os.path.splitext(file_path)[1]}"
        
        try:
            with Image.open(file_path) as img:
                if img.mode in ("RGBA", "P"):
                    img = img.convert("RGB")
                img = ImageOps.exif_transpose(img)  # Exifデータに基づいて画像を回転
                img.save(compressed_file_path, optimize=True, quality=85)
            
            with open(compressed_file_path, 'rb') as f:
                django_file = File(f, name=os.path.basename(compressed_file_path))
                
                for <index> in <モデル名>.objects.all():
                    if <index>.photo1.name == os.path.basename(file_path):
                        <index>.photo1_compressed.delete(save=False)
                        <index>.photo1_compressed.save(django_file.name, django_file, save=True)
            
            self.stdout.write(self.style.SUCCESS(f'Compressed and saved as {compressed_file_path}'))
        
        except Exception as e:
            self.stdout.write(self.style.ERROR(f'Error processing {file_path}: {str(e)}'))

簡単に要約すると、メディアディレクトリ内の画像を見回ってrowデータを見つけたら圧縮をかけてファイル名に_compressedというプレフィックスを追加する。これによって圧縮済みのファイルを区別して、_compressedがついているファイルは圧縮をスキップする、という仕組み。そうじゃないとコマンドを実行するたびに85%圧縮、そのまた85%圧縮を繰り返すのでは?という危惧があり...

そのほかの整合性を揃える。

今回は
modelにphoto1,photo2,photo3といったフィールドを用意していたが、これとは別にphoto1_compressed,photo2_compressed,photo3_compressedといった別のフィールドを追加。(上記スクリプトにおいても圧縮したファイルはこちらのフィールドに追加するようにしている。)

ここでカスタムコマンドpython manage.py compress_images を実行して、

テンプレートも書き直すと

<img src="{{ object.photo1_compressed.url }}" alt="">

画像が表示され、デベロッパーツール上で確認しても_compressedのファイル名に変更されている。

念の為filezillaで写真の容量を確認すると多少の誤差はあるが85%カットになっている!
確かに心なしか表示が速くなった!

余談

今回は85%カットにしたがさらに数字を小さくすると高速化を図れるが画質が荒くなる。どこまでカットできるのだろうか。なんて悠長なことを書いているが実際にこの高速化ができるまで一週間以上かけて作った血と汗の結晶である。(エラーざんまい、デバックしまくり、途中で変なことしてデータベース消えかける、permisshion errorなど様々なことに悩まされた。)さらには上記のスクリプトを完全には理解できてい節も...これから徐々にできるようになろう😂これは自分のための備忘録になってくれるだろう。

0
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1