写真追加画面処理を作る

写真追加に必要なもの

必要なもの一覧

写真は1枚づつ選択して追加します。写真は転送した後でPillowを使って縮小を試みますが、かならずできるとは限らないので失敗した場合は、大きいままになります。

写真追加画面

photoadd.html

  <!DOCTYPE html>
  <html lang="jp">
  <head>
      <meta charset="UTF-8">
      <title>PhotoAdd -- 写真追加</title>
      <meta name="viewport" content="width=device-width,initial-scale=1.0">
      <meta name="description" content="pythonとpythonanywhereのサンプルページです" >
      <meta name="keywords" content="プログラム,教育,python,pythonanywhere,bootstrap,flask" >
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
      <link rel="stylesheet" type="text/css"
      href="{{ url_for('static', filename='css/style.css')}}">
  </head>
  <body>
      <div class="container">
        <nav class="navbar w-100 navbar-expand-sm navbar-dark bg-dark" aria-label="Third navbar example">
          <div class="container-fluid">
            <a class="navbar-brand" href="#">Event Photos</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarsExample03" aria-controls="navbarsExample03" aria-expanded="false" aria-label="Toggle navigation">
              <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarsExample03">
              <ul class="navbar-nav me-auto mb-2 mb-sm-0">
                <li class="nav-item">
                  <a class="nav-link active" aria-current="page" href="/">Home</a>
                </li>
                <li class="nav-item dropdown">
                  <a class="nav-link dropdown-toggle" href="#" id="dropdown03" data-bs-toggle="dropdown" aria-expanded="false">操作</a>
                  <ul class="dropdown-menu" aria-labelledby="dropdown03">
                    <li><a class="dropdown-item" href="/event">イベント追加</a></li>
                    <li><a class="dropdown-item" href="/logout">ログアウト</a></li>
                  </ul>
                </li>
              </ul>
            </div>
          </div>
        </nav>
        <main>
          <div id="wrapper">
            <header class="d-flex justify-content-end">
              <p>ID: {{id}} </p>
            </header>
            <div class="py-5 text-center">
              <h2>写真追加</h2>
              <p class="lead">イベントに写真を追加します</p>
            </div>
            <div class="row">
              <div class="col-md-12 col-lg-12">
                <h4 class="mb-2">イベント情報</h4>
                <form action="/savephoto"  enctype="multipart/form-data" method="POST" >
                  <p class="text-danger">{{errmess}}</p>
                  <div class="row g-3">
                    <div class="col-12">
                      <label for="eventname" class="form-label">イベント名</label>
                      <input type="text"  value="{{edata[0].eventname}}" class="form-control" name="eventname" disabled>
                    </div>
                    <div class="col-12">
                      <label for="id" class="form-label">イベント代表</label>
                      <input type="text"  value="{{edata[0].ownername}}" class="form-control" name="id" disabled>
                    </div>
                    <div class="col-12">
                      <label for="rmemo" class="form-label">画像ファイル</label>
                      <input name="file" type="file" class="form-control" accept="image/*">
                    </div>
                    <div class="col-12">
                      <label for="comments" class="form-label">写真コメント</label>
                      <input type="text" value="" class="form-control" name="comments" >
                    </div>
                    <input type="hidden" value="{{id}}" name="fromid">
                    <input type="hidden" value="{{ekey}}" name="ekey">
                    <button class="w-100 btn btn-primary btn-lg" type="submit" name="regist" value="regist">登録</button>
                    <button class="w-100 btn btn-secondary btn-lg" type="submit" name="cancel" value="cancel">キャンセル</button>
                  </div>
                </form>
              </div>
            </div>
          </div>
        </main>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script>
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script>
    </body>
  </html>

ファイルのuploadは他のプログラムと大差ありません。

main 写真追加部分

main.py(抜粋)

  from PIL import Image       #install Pillow
  from PIL.ExifTags import TAGS
  
  def get_exif_of_image(file):
    """Get EXIF of an image if exists.

    指定した画像のEXIFデータを取り出す関数
    @return exif_table Exif データを格納した辞書
    """
    im = Image.open(file)

    # Exif データを取得
    # 存在しなければそのまま終了 空の辞書を返す
    try:
        exif = im._getexif()
    except:
        return {}

    # タグIDそのままでは人が読めないのでデコードして
    # テーブルに格納する
    exif_table = {}
    if exif is not None:
        for tag_id, value in exif.items():
            tag = TAGS.get(tag_id, tag_id)
            exif_table[tag] = str(value)    #データーで保存するとエラーが起きることがあるのでstr()

    #edit datetime
    if 'DateTime' in exif_table:
        string = exif_table['DateTime']
        exif_table['DateTime'] = string.replace(":","/",2)
    else:
        exif_table['DateTime']=""

    return exif_table

def zipfile(filename):

  # 保存したい画像フォーマットを指定
  # jpg, png, webpのいずれかを指定する
  # 圧縮したい画像がjpg画像の場合の保存フォーマット
  save_format_jpg = "jpg"

  # 圧縮したい画像がpng画像の場合の保存フォーマット
  save_format_png = "png"

  # 圧縮したい画像がwebp画像の場合の保存フォーマット
  save_format_webp = "webp"

  # 「jpg」と「webp」画像の圧縮率の指定
  # 「jpg」の場合は、0-95, 「webp」の場合は、0-100で指定
  compression_rate_jpg = 50
  compression_rate_webp = 50

  # 「png」画像の色数の指定
  # 1 - 256で指定
  compression_color_num = 256

  ##########プロセス処理部分##########

  # 元の画像ファイル名をファイル名と拡張子に分割する。
  file, ext = os.path.splitext(filename)
  # 画像フォーマットによる場合わけ
  if ext in ('.png','.PNG'):
      # 圧縮したい画像の読み込み。
      img = Image.open(filename)
      # 画像の色数を削減
      img = img.convert("P", palette=Image.ADAPTIVE,
                        colors=compression_color_num)
      # 画像を指定した色数で保存(pngの場合)
      try:
        img.save((file +  '.' + save_format_png), optimize=True)
      except:
        pass

  elif ext in ( '.jpg','.JPG') or ext in ('.webp','.WEBP'):
      # 圧縮したい画像の読み込み。
      img = Image.open(filename)

      # 保存する画像フォーマットを拡張子により、指定
      if ext == '.jpg':
          save_format = save_format_jpg
          compression_rate = compression_rate_jpg
      elif ext == '.webp':
          save_format = save_format_webp
          compression_rate = compression_rate_webp

      # 画像を指定した圧縮率で保存(jpgとwebpの場合)
      try:
        img.save((file + "." + save_format), optimize=True, quality=compression_rate)
      except:
        pass

  @app.route("/newphoto/<ekey>", methods = ["GET"])
  def newPhoto(ekey):
    # 新規の入り
    if "id" in session:
      if ekey:
        events = Event(con,cur)
        edata = events.getEvent(ekey)
        return render_template("photoadd.html", id=session["id"],ekey=ekey,edata=edata)
      else:
        return render_template("login.html")
    else:
      return render_template("login.html")
  
  @app.route("/savephoto", methods = ["POST"])
  def savePhoto():
    # 写真の保存
    if "id" in session:
      if request.form.get("regist"):
        comments = request.form.get("comments")
        filedata = request.files['file']
        ekey = request.form.get("ekey")
        f = filedata.filename           #実ファイル名
        tmpfilename = randStr(4)        #temporaryファイル名
        #return redirect(url_for("dirPhotos",ekey=ekey))
  
        filedata.save(os.path.join(script_directory + '/static/images/',  tmpfilename + f))
        exif = get_exif_of_image(os.path.join(script_directory + '/static/images/' , tmpfilename + f))       #exifで撮影日付を取得する
        #圧縮する
        zipfile(os.path.join(script_directory + '/static/images/' , tmpfilename + f))
  
        ekey = request.form.get("ekey")
        photo = PhotoBase(con,cur)
        photo.newPhoto(session["id"], ekey, tmpfilename+f, exif['DateTime'],comments)
        return redirect(url_for("dirPhotos",ekey=ekey))
      else:
        return redirect(url_for("top"))
    else:
      return render_template("login.html")
  
  @app.route("/photos/<ekey>", methods = ["GET"])
  def dirPhotos(ekey):
    # イベント一覧の入り
    if "id" in session:
      if ekey:
        events = Event(con,cur)
        edata = events.getEvent(ekey)
        pdata = events.dirEventPhotos(ekey)
        errmess = "" if pdata == None or len(pdata) <= 10 else "写真が満杯です"
      return render_template("photos.html", id=session["id"],edata=edata,pdata=pdata,errmess=errmess)
    else:
      return render_template("login.html")
  
  @app.route("/photo/<ekey>/<pkey>/<filename>", methods = ["GET"])
  def deletePhoto(ekey,pkey,filename):
    # イベント一覧の入り
    if "id" in session:
      if pkey:
        photos = PhotoBase(con,cur)
        photos.deletePhoto(pkey)
      return redirect(url_for("dirPhotos",ekey=ekey))
    else:
      return render_template("login.html")                          

exifデータを取得して撮影日付を取得しています。そのあと圧縮を試みます。

データベースのphotosにexifと一緒に登録しています。