自動化無しに生活無し

WEBとかAIとかLinux関係をひたすら書く備忘録系ブログ

Djangoでアップロードされた.aiと.psファイルのサムネイルを自動生成させる【PhotoShop,Illustrator】

thumbnail

ファイルをアップロードした後、ファイル名だけ表示されている状態では、それが何なのかパット見でよくわからない。

ファイルの内容がよくわからない

だからこそ、事前にサムネイルを用意させる。こんなふうに

サムネイル表示でファイルの内容がわかる

だが、サムネイル画像のアップロードまでユーザーに押し付けるのは、気軽なファイル共有を前提としたウェブアプリのコンセプトが台無しになる。そこで、アップロードしたファイルのサムネイルをサーバーサイドに自動生成してもらう。

本記事ではファイルアップロード後のサムネイルの自動生成、とりわけ、PhotoShopの.psファイル、及びIllustratorの.aiファイルのサムネイルを自動生成する方法を解説する。

ソースコードはDjangoで画像及びファイルをアップロードする方法から流用する。

事前準備

まず、.psファイル及び.aiファイルを解析するため、Pythonのライブラリをインストールさせる

pip install Pillow
pip install psd-tools

.psファイルのサムネイル作成はpsd-toolsというライブラリを使用する。一方で、.aiファイルのサムネイル作成には画像編集でお馴染みのPillowだけでOK。

それぞれ、ファイルを読み込み、画像を生成するスクリプトの例をここに記す。

#Illustrator

from PIL import Image

image   = Image.open("example.ai")
image.save("example.png")



#PhotoShop

from psd_tools import PSDImage

image   = PSDImage.open('example.psd')
image.composite().save('example.png')

いずれも数行でサムネイル生成ができるが、フォトショップの場合はレイヤーが多いと生成に時間がかかってしまう。

リクエストのタイムアウトを考慮すれば、この処理はバッチ処理に回すべきだと思うが、本記事ではviews.pyに記す。(いずれバッチ処理を書く)

サムネイルを自動生成する処理

冒頭でも述べたとおり、ソースコードはDjangoで画像及びファイルをアップロードする方法から流用する。

まずmodels.py。サムネイルを格納するフィールドを作る。さらにクラス名、カラム名いずれも雑だったので一部修正した。

class Document(models.Model):

    class Meta:
        db_table    = "document"

    file        = models.FileField(verbose_name="ファイル",upload_to="file/document/")
    mime        = models.TextField(verbose_name="MIMEタイプ")
    thumbnail   = models.ImageField(verbose_name="サムネイル",upload_to="file/thumbnail/",null=True)

mimepython-magicで抜き取ったMIMEタイプを格納する。これでフォトショップとイラストレーターの判定ができるようにする。

thumbnailnullTrueにしておくのがポイント。レコード作成時にはサムネイルは空になるので、nullを許可しないとエラーが出る。サムネイル生成に失敗した時の対策にもなる。

続いて、views.py。ユーザーからのPOSTリクエストを受け取った後に保存、その後にサムネイルを自動生成している。

from django.shortcuts import render,redirect

from django.views import View
from django.contrib.auth.mixins import LoginRequiredMixin


from .models import Photo,Document
from .forms import PhotoForm,DocumentForm

from django.conf import settings

import magic

ALLOWED_MIME    = [ "image/vnd.adobe.photoshop","application/postscript" ]

"""
省略
"""

class DocumentView(View):

    """
    省略
    """

    def post(self, request, *args, **kwargs):

        #fileが指定されていない場合、後述の直接参照でインデックスエラーを防ぐためアーリーリターン
        if "file" not in request.FILES:
            return redirect("upload:document")

        mime_type   = magic.from_buffer(request.FILES["file"].read(1024) , mime=True)
        print(mime_type)

        #mime属性の保存(後のバッチ処理に繋げる)
        copied          = request.POST.copy()
        copied["mime"]  = mime_type

        form        = DocumentForm(copied,request.FILES)

        if not form.is_valid():
            print("バリデーションNG")
            return redirect("upload:document")

        if mime_type not in ALLOWED_MIME:
            print("このファイルは許可されていません。")
            return redirect("upload:document")


        print("バリデーションOK")
        result  = form.save() #TIPS:←返り値がモデルクラスのオブジェクトになるので、id属性を参照すれば良い。
        print(result.id)
        
        #======ここから先、サムネイル作成処理==========

        #処理結果のIDを元に、サムネイルの保存を行い、thumbnailに保存したパスを指定する
        document        = Document.objects.filter(id=result.id).first()

        #upload_to、settings内にあるMEDIA_ROOTを読み取り、そこに画像ファイルを保存。
        from django.conf import settings
        path            = Document.thumbnail.field.upload_to # ←予めmediaディレクトリにthumbnailディレクトリを作る
        thumbnail_path  = path + str(document.id) + ".png"
        full_path       = settings.MEDIA_ROOT + "/" + thumbnail_path 

        #フォトショップの場合
        if document.mime == "image/vnd.adobe.photoshop":
            from psd_tools import PSDImage
            image   = PSDImage.open(settings.MEDIA_ROOT + "/" + str(document.file))
            image.composite().save(full_path)

        #イラストレーターの場合
        elif document.mime == "application/postscript":
            from PIL import Image
            image   = Image.open(settings.MEDIA_ROOT + "/" + str(document.file))
            image.save(full_path)
        else:
            return redirect("upload:document")

        document.thumbnail   = thumbnail_path
        document.save()

        return redirect("upload:document")

document    = DocumentView.as_view()

form.save()するまではこれまでのファイルアップロードと同様である。ただ、注意する点はform.save()した後、返り値としてモデルオブジェクトが得られるので(result)、それを使い.id属性でDBにレコード作成したデータのidを抜き取る。

生成したサムネイルの保存先のパスと、URLから見たパスは全く異なるので、それぞれ生成する。その時、thumbnailで定義したフィールドオプションのupload_toは下記に倣って抜き取る

Document.file.field.upload_to

参照:Djangoでviews.pyからmodels.pyのフィールドオプションを参照する【verbose_name,upload_to】

他にもsettings.pyMEDIA_ROOTを読み取る。MEDIA_ROOTは画像の実体を保存するパスだ。

パスを定義した後は、事前準備の項で解説したフォトショップ、イラストレーターの画像生成処理を元にコードを書く。

#フォトショップの場合
if document.mime == "image/vnd.adobe.photoshop":
    from psd_tools import PSDImage
    image   = PSDImage.open(settings.MEDIA_ROOT + "/" + str(document.file))
    image.composite().save(full_path)

#イラストレーターの場合
elif document.mime == "application/postscript":
    from PIL import Image
    image   = Image.open(settings.MEDIA_ROOT + "/" + str(document.file))
    image.save(full_path)
else:
    return redirect("upload:document")

最後に、URLから見たパスをthumbnailに指定、保存する。upload_toに書かれた内容と、ファイル名が指定されている。

document.thumbnail   = thumbnail_path
document.save()

これで、生成したサムネイルがウェブアプリ側から見れる。

サムネイル表示でファイルの内容がわかる

結論

これだけでユーザビリティは大幅に向上するだろう。閲覧、投稿いずれも行いやすくなったのだから。

ただ、レイヤー数の多いフォトショップファイル、大容量のファイルのサムネイルを自動生成する時、処理時間が極端に長くなる(5秒から10秒ぐらい)。これではリクエストがタイムアウトになってしまう。レスポンスが遅いから、アップロードした後で閲覧することも難しいだろう。

この問題はバッチ処理に書き換えることで対処できる。サムネイルがnullになっているデータを炙り出し、バッチ処理がサムネイルを生成する。それでリクエストとサムネイル自動生成の処理を分離できる。一時的にサムネイルがnullになって表示されるが、その時はDTLでNO-IMAGE等マークがついたデフォルトのサムネイルを指定すれば良いだろう。

それから、サムネイルの画像サイズも調整するべきだと思う。現状では画像サイズが大きいので、大量に表示させようとすると遅延がひどいことになる。画像加工用ライブラリのPillowを使ってどうにかするなど、対策が必要と思われる。

ソースコード

https://github.com/seiya0723/auto_thumbnail_create

カスタムユーザーモデルも含まれている。

スポンサーリンク

シェアボタン

Twitter LINEで送る Facebook はてなブログ