forked from k-space/inventory-app
152 lines
5.0 KiB
Python
152 lines
5.0 KiB
Python
|
|
import boto3
|
|
import const
|
|
from botocore.exceptions import ClientError
|
|
from bson.objectid import ObjectId
|
|
from flask import Blueprint, abort, redirect, request
|
|
from jpegtran import JPEGImage
|
|
from oidc import login_required, read_user
|
|
from pymongo import MongoClient
|
|
from werkzeug.utils import secure_filename
|
|
|
|
page_inventory_image = Blueprint("inventory_image", __name__)
|
|
db = MongoClient(const.MONGO_URI).get_default_database()
|
|
|
|
|
|
def is_image_ext(filename):
|
|
return '.' in filename and \
|
|
filename.rsplit('.', 1)[1].lower() in ["jpg", "jpeg"]
|
|
|
|
# AWS S3 credentials / env is automagically imported https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html#using-environment-variables
|
|
# AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, etc
|
|
def get_bucket(): #TODO
|
|
return boto3.client('s3',
|
|
endpoint_url=const.AWS_S3_ENDPOINT_URL,
|
|
config=boto3.session.Config(signature_version='s3v4'))
|
|
|
|
@page_inventory_image.route("/inventory/<item_id>/upload-photo", methods=["POST"])
|
|
@login_required
|
|
def upload_photo(item_id):
|
|
user = read_user()
|
|
item = db.inventory.find_one(filter = { "_id": ObjectId(item_id) }, projection = { "thumbs": 1 })
|
|
if not item:
|
|
return "Item not found", 404
|
|
if item.get("type") == "key" and "k-space:inventory:keys" not in user.get("groups", []):
|
|
return abort(403)
|
|
if "file" not in request.files:
|
|
return "No file part", 400
|
|
file = request.files["file"]
|
|
if not file.filename:
|
|
return "No selected file", 400
|
|
if file and is_image_ext(secure_filename(file.filename)):
|
|
try:
|
|
file.seek(0)
|
|
img = JPEGImage(blob=file.read())
|
|
except:
|
|
return "Not a valid JPEG", 400
|
|
if min(img.width, img.height) < 576:
|
|
return "Image must have smallest dimension of at least 576px", 400
|
|
bucket = get_bucket()
|
|
file.seek(0)
|
|
bucket.upload_fileobj(file, const.BUCKET_NAME, item_id)
|
|
|
|
db.inventory.update_one({ "_id": ObjectId(item_id) }, {"$set": {"has_photo": True}})
|
|
delete_thumbs(item)
|
|
return redirect("/m/inventory/%s/view" % item_id)
|
|
else:
|
|
return "File is not valid", 400
|
|
|
|
|
|
@page_inventory_image.app_template_filter('thumbnail')
|
|
def thumbnail_filter(item_id, dimension):
|
|
return get_scaled_photo(item_id, dimension)
|
|
|
|
@page_inventory_image.route("/m/photo/<item_id>/<dimension>")
|
|
def get_scaled_photo(item_id=None, dimension=0, retry=2):
|
|
dimension = int(dimension) #why
|
|
if retry <= 0:
|
|
return "", 500
|
|
if dimension not in [240, 480, 576, 600]:
|
|
return abort(404)
|
|
item = db.inventory.find_one(filter = { "_id": ObjectId(item_id) }, projection = { "thumbs": 1 })
|
|
if not item:
|
|
return abort(404)
|
|
if item.get("thumbs", {}).get(str(dimension)):
|
|
thumb = item["thumbs"][str(dimension)]
|
|
img_url = get_image_url(thumb["name"])
|
|
if not img_url:
|
|
delete_thumbs(item)
|
|
return get_scaled_photo(item_id, dimension, retry - 1)
|
|
else:
|
|
make_thumb(item, dimension)
|
|
return get_scaled_photo(item_id, dimension, retry - 1)
|
|
|
|
return img_url
|
|
|
|
def get_item(key):
|
|
try:
|
|
bucket = get_bucket()
|
|
r = bucket.get_object(Bucket=const.BUCKET_NAME, Key=key)
|
|
return r['Body'].read()
|
|
except:
|
|
return None
|
|
|
|
def make_thumb(item, dimension):
|
|
img = get_item(str(item["_id"]))
|
|
img = scale_image(img, dimension)
|
|
bucket = get_bucket()
|
|
thumb_name = "thumb_%s_%d" % (item["_id"], dimension)
|
|
bucket.put_object(Body=img.as_blob(), Bucket=const.BUCKET_NAME, Key=thumb_name)
|
|
db.inventory.update_one({
|
|
"_id": ObjectId(item["_id"]),
|
|
}, {
|
|
"$set": {
|
|
"thumbs.%d.name" % dimension : thumb_name,
|
|
},
|
|
})
|
|
return img
|
|
|
|
def scale_image(img, dimension):
|
|
img = JPEGImage(blob=img)
|
|
if img.exif_orientation:
|
|
img = img.exif_autotransform()
|
|
|
|
if img.width < img.height:
|
|
img = img.downscale(dimension, img.height * dimension // img.width, 100)
|
|
else:
|
|
img = img.downscale(img.width * dimension // img.height, dimension, 100)
|
|
|
|
ratio = 4 / 3
|
|
crop_height = min(img.width / ratio, img.height)
|
|
crop_width = crop_height * ratio
|
|
crop_x = int((img.width - crop_width) / 2) & ~7
|
|
crop_y = int((img.height - crop_height) / 2) & ~7
|
|
return img.crop(crop_x, crop_y, int(crop_width), int(crop_height))
|
|
|
|
def delete_thumbs(item):
|
|
bucket = get_bucket()
|
|
for _, thumb in item.get("thumbs", {}).items():
|
|
if thumb.get("name"):
|
|
bucket.delete_object(Bucket=const.BUCKET_NAME, Key=thumb["name"])
|
|
db.inventory.update_one({
|
|
"_id": ObjectId(item["_id"])
|
|
}, {
|
|
"$unset": {
|
|
"thumbs": ""
|
|
},
|
|
})
|
|
|
|
def get_image_url(item_id):
|
|
bucket=get_bucket()
|
|
try:
|
|
return bucket.generate_presigned_url(
|
|
ClientMethod='get_object',
|
|
Params={
|
|
'Bucket': const.BUCKET_NAME,
|
|
'Key': item_id
|
|
},
|
|
ExpiresIn=3600
|
|
)
|
|
except ClientError:
|
|
return None
|