DjangoでRAW画像をjpegに変換し、アップロード

# RAW画像をjpegに変換、さらにサムネイル作成

デジカメのraw画像を、formからストレージにアップすると同時にjpegに変換し、サムネイルを作成するまで
環境
+ Linux 5.14.0-362.8.1.el9_3.x86_64 
+ Python 3.9.18
+ Django 4.2.14

```
pip install rawpy
pip install opencv-python
```

``` settings.py

FILE_ROOT = os.path.join(BASE_DIR, 'files')
PHOTO_PATH = FILE_ROOT + '/photo'

```
``` forms.py
from django import forms

class MultipleFileUploadForm(forms.Form):

  files = MultipleFileField(label="ファイルを指定", help_text="一度に複数のファイルを指定し、アップロードできます")
  memo = forms.CharField(max_length=200, label="メモ", required=False, help_text="検索対象ですので、検索しやすいように自由に記載くださ")

  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    for field in self.fields.values():
      field.widget.attrs['class'] = 'form-control form-control-sm'
```

``` templates/upload.html
{% load django_bootstrap5 %}

<form action="{% url 'dashboard:upload_photo' %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row">
  <div class="mb-3 col-md-12">
    {% bootstrap_form form %}
  </div>
</div>
<button type="submit" class="btn btn-primary btn-sm mt-2 w-25" name="button">アップロード</button>
</form>

```

```views.py
import cv2, os, rawpy

from .forms import MultipleFileUploadForm
from .models import File
from accounts.models import CustomUser

from django.conf import settings
from django.contrib.auth.mixins import LoginRequiredMixin
from django.core.files.storage import FileSystemStorage
from django.shortcuts import redirect

class UploadImageView(LoginRequiredMixin, FormView):
  model = File
  form_class = MultipleFileUploadForm
  template_name = 'dashboard/upload_image.html'

  def post(self, request):
    files = request.FILES.getlist('files')

    if not os.path.exists(settings.PHOTO_PATH + DS + str(self.request.user.pk)):
      os.mkdir(settings.PHOTO_PATH + DS + str(self.request.user.pk))

    if not os.path.exists(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'raw'):
      os.mkdir(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'raw')

    if not os.path.exists(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'jpeg'):
      os.mkdir(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'jpeg')

    if not os.path.exists(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'thumbnail'):
      os.mkdir(settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'thumbnail')

    fss = FileSystemStorage(location=settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'raw')

    for file in files:
      ### 同一ファイルのアップ防止 ###
      file_exist = File.objects.filter(
        file_name=file.name,
      ).first()

      if not file_exist:
        fss.save(file.name, file)

        # raw画像→jpeg変換
        filename_no_extension = os.path.splitext(file.name)[0]
        raw_file_path = settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'raw' + DS + file.name
        jpeg_file_path = settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'jpeg' + DS + filename_no_extension + '.jpg'
        thumb_file_path = settings.PHOTO_PATH + DS + str(self.request.user.pk) + DS + 'thumbnail' + DS + filename_no_extension + '_thumb.jpg'

        raw = rawpy.imread(raw_file_path)
        decoded = raw.postprocess(bright=1.21)[:, :, [2, 1, 0]]
        cv2.imwrite(jpeg_file_path, decoded)
        # 画像を読み込み
        cv2_image = cv2.imread(jpeg_file_path)
        # 画像のサイズを取得
        h, w = cv2_image.shape[:2]
        rate = round(300 / w, 2)
        # 画像のサイズを指定：縦を300px横なりゆきにリサイズ
        resized_image = cv2.resize(cv2_image, None, fx=rate, fy=rate)
        cv2.imwrite(thumb_file_path, resized_image)

        file_obj = File(
          custom_user=CustomUser.objects.get(pk=self.request.user.pk),
          type='写真',
          file_name=file.name,
          file_size=file.size / 1024,
          memo=request.POST.get('memo'),
          description=self.request.user.username + '-' + self.request.user.email + '-'+ str(uuid.uuid4())
        )
        file_obj.save()

    return redirect('/dashboard/')
```

### 最後に
バリデーション、エラー時の対応をせねば..