syntaxwebsite/app/routes/image.py

652 lines
27 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, make_response, jsonify, abort, Response
import hashlib
import gzip
import json
from PIL import Image
from app.models.groups import GroupIcon
from app.models.user_thumbnail import UserThumbnail
from app.models.asset_thumbnail import AssetThumbnail
from app.models.place_icon import PlaceIcon
from app.models.user import User
from app.models.asset import Asset
from app.routes.thumbnailer import TakeThumbnail
from app.util import s3helper
from app.extensions import redis_controller, csrf
from config import Config
from io import BytesIO
config = Config()
ImageRoute = Blueprint('image', __name__)
def HandleResolutionCheck(
WidthParametersName : list[ str ] = [ 'width', 'x' ],
HeightParametersName : list[ str ] = [ 'height', 'y' ],
AllowedWidths : list[ int ] = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
AllowedHeights : list[ int ] = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
MustBeSquare : bool = True,
CanRoundToNearest : bool = True
) -> ( int, int ):
"""
Handles resolution checking for images. Aborts the request if the resolution is invalid.
Must be called in a flask request context.
:param WidthParametersName: The names of valid parameters for width.
:param HeightParametersName: The names of valid parameters for height.
:param AllowedWidths: The allowed widths.
:param AllowedHeights: The allowed heights.
:param MustBeSquare: Whether or not the image must be square.
:param CanRoundToNearest: Whether or not the image can round to the nearest resolution.
:return: ( width, height )
"""
Width : int = None
Height : int = None
for WidthParameterName in WidthParametersName:
if WidthParameterName in request.args:
Width = request.args.get( key=WidthParameterName, default=None, type=int )
break
for HeightParameterName in HeightParametersName:
if HeightParameterName in request.args:
Height = request.args.get( key=HeightParameterName, default=None, type=int )
break
if Width is None or Height is None:
abort( 400 )
if ( Width not in AllowedWidths or Height not in AllowedHeights ) and not CanRoundToNearest:
abort( 400 )
if MustBeSquare and Width != Height:
abort( 400 )
if CanRoundToNearest:
if Width not in AllowedWidths:
Width = min( AllowedWidths, key=lambda x:abs(x-Width) )
if Height not in AllowedHeights:
Height = min( AllowedHeights, key=lambda x:abs(x-Height) )
return ( Width, Height )
def HandleImageResize(
ImageContentHash : str,
TargetWidth : int,
TargetHeight : int,
CroppedHash : str,
CacheControl : str = "max-age=120",
SkipCacheCroppedImage : bool = False,
ReturnAsJSON : bool = False
) -> Response:
"""
Handles image resizing
:param ImageContentHash: The content hash of the image.
:param TargetWidth: The target width.
:param TargetHeight: The target height.
:param CroppedHash: The hash of the cropped image.
:return: Flask Response
"""
if s3helper.DoesKeyExist(CroppedHash) and not SkipCacheCroppedImage:
if ReturnAsJSON:
return jsonify({
"Final": True,
"Url": f"{config.CDN_URL}/{CroppedHash}"
})
ImageResponse = make_response(redirect(f"{config.CDN_URL}/{CroppedHash}"))
ImageResponse.headers['Cache-Control'] = CacheControl
return ImageResponse
if not s3helper.DoesKeyExist(ImageContentHash):
if ReturnAsJSON:
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
return redirect("/static/img/placeholder.png")
ImageContent = BytesIO( s3helper.GetFileFromS3(ImageContentHash) )
ImageObj = Image.open(ImageContent)
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
VirtualFile = BytesIO()
ImageObj.save(VirtualFile, "PNG")
VirtualFile.seek(0)
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
if ReturnAsJSON:
return jsonify({
"Final": True,
"Url": f"{config.CDN_URL}/{CroppedHash}"
})
ImageResponse = make_response(redirect(f"{config.CDN_URL}/{CroppedHash}"))
ImageResponse.headers['Cache-Control'] = CacheControl
return ImageResponse
@ImageRoute.route("/avatar-thumbnail/image", methods=["GET"])
@ImageRoute.route('/Thumbs/Avatar.ashx', methods=['GET'])
@ImageRoute.route('/thumbs/avatar.ashx', methods=['GET'])
def avatar():
userId = request.args.get('userId', default = None, type = int)
username = request.args.get('username', default = None, type = str)
if (userId is None and username is None):
return redirect("/static/img/placeholder.png")
TargetX, TargetY = HandleResolutionCheck(
WidthParametersName = [ 'x', 'width' ],
HeightParametersName = [ 'y', 'height' ],
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
MustBeSquare = True,
CanRoundToNearest = True
)
if username is not None and userId is None:
UserObj : User = User.query.filter_by(username=username).first()
if UserObj is None:
return redirect("/static/img/placeholder.png")
userId = UserObj.id
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
if ThumbnailObj is None:
return redirect("/static/img/placeholder.png")
ContentHash = ThumbnailObj.full_contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetX,
TargetHeight = TargetY,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
@ImageRoute.route('/avatar-thumbnail/json', methods=['GET'])
def avatar_json():
userId = request.args.get('userId', None, type=int)
if userId is None:
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
TargetWidth, TargetHeight = HandleResolutionCheck(
WidthParametersName = [ 'width', 'x' ],
HeightParametersName = [ 'height', 'y' ],
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
MustBeSquare = True,
CanRoundToNearest = True
)
thumbnail : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
if thumbnail is None:
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
ContentHash = thumbnail.full_contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetWidth,
TargetHeight = TargetHeight,
CroppedHash = CroppedHash,
CacheControl = "max-age=120",
ReturnAsJSON = True
)
@ImageRoute.route('/headshot-thumbnail/image', methods=['GET'])
@ImageRoute.route('/Thumbs/Head.ashx', methods=['GET'])
def head():
userId = request.args.get('userId', default = None, type = int)
if userId is None:
return redirect("/static/img/placeholder.png")
TargetX, TargetY = HandleResolutionCheck(
WidthParametersName = [ 'x', 'width' ],
HeightParametersName = [ 'y', 'height' ],
AllowedWidths = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
AllowedHeights = [ 48, 180, 420, 60, 100, 150, 352, 200, 500 ],
MustBeSquare = True,
CanRoundToNearest = True
)
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
if ThumbnailObj is None:
return redirect("/static/img/placeholder.png")
ContentHash = ThumbnailObj.headshot_contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetX,
TargetHeight = TargetY,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
@ImageRoute.route('/asset-thumbnail/json', methods=['GET'])
def asset_json():
assetId = request.args.get( 'assetId', None, type=int )
if assetId is None:
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
TargetWidth, TargetHeight = HandleResolutionCheck(
WidthParametersName = [ 'width', 'x' ],
HeightParametersName = [ 'height', 'y' ],
AllowedWidths = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
AllowedHeights = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
MustBeSquare = False,
CanRoundToNearest = True
)
thumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=assetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
if thumbnailObj is None:
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
if thumbnailObj.moderation_status != 0 or thumbnailObj.asset.moderation_status != 0:
if thumbnailObj.moderation_status == 2 or thumbnailObj.asset.moderation_status == 2:
return jsonify({
"Final": True,
"Url": "/static/img/ContentDeleted.png"
})
return jsonify({
"Final": False,
"Url": "/static/img/placeholder.png"
})
ContentHash = thumbnailObj.content_hash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetWidth,
TargetHeight = TargetHeight,
CroppedHash = CroppedHash,
CacheControl = "max-age=120",
ReturnAsJSON = True
)
@ImageRoute.route('/asset-thumbnail/image', methods=['GET'])
@ImageRoute.route('/thumbs/asset.ashx', methods=['GET'])
@ImageRoute.route('/Thumbs/Asset.ashx', methods=['GET'])
def asset():
assetId = request.args.get('assetId', default = None, type = int) or request.args.get('assetid', default = None, type = int)
if assetId is None:
return redirect("/static/img/placeholder.png")
TargetX, TargetY = HandleResolutionCheck(
WidthParametersName = [ 'x', 'width' ],
HeightParametersName = [ 'y', 'height' ],
AllowedWidths = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,360,1280,720],
AllowedHeights = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720],
MustBeSquare = False,
CanRoundToNearest = True
)
ThumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=assetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
if ThumbnailObj is None:
return redirect("/static/img/placeholder.png")
if ThumbnailObj.moderation_status != 0 or ThumbnailObj.asset.moderation_status != 0:
if ThumbnailObj.moderation_status == 2 or ThumbnailObj.asset.moderation_status == 2:
return redirect("/static/img/ContentDeleted.png")
return redirect("/static/img/placeholder.png")
ContentHash = ThumbnailObj.content_hash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetX,
TargetHeight = TargetY,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
@ImageRoute.route('/Thumbs/GroupIcon.ashx', methods=['GET'])
def groupicon():
groupid = request.args.get( key='groupid', default=None, type=int )
if groupid is None:
return redirect("/static/img/placeholder.png")
TargetX, TargetY = HandleResolutionCheck(
WidthParametersName = [ 'x', 'width' ],
HeightParametersName = [ 'y', 'height' ],
AllowedWidths = [48,180,420,60,100,150,352],
AllowedHeights = [48,180,420,60,100,150,352],
MustBeSquare = True,
CanRoundToNearest = True
)
ThumbnailObj : GroupIcon = GroupIcon.query.filter_by(group_id=groupid).first()
if ThumbnailObj is None:
return redirect("/static/img/placeholder.png")
if ThumbnailObj.moderation_status != 0:
if ThumbnailObj.moderation_status == 2:
return redirect("/static/img/ContentDeleted.png")
return redirect("/static/img/placeholder.png")
ContentHash = ThumbnailObj.content_hash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetX,
TargetHeight = TargetY,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
@ImageRoute.route('/Thumbs/GameIcon.ashx', methods=['GET'])
@ImageRoute.route('/Thumbs/PlaceIcon.ashx', methods=['GET'])
def placeicon():
assetId = request.args.get('assetId', default = None, type = int) or request.args.get('assetid', default = None, type = int)
if assetId is None:
return redirect("/static/img/placeholder.png")
TargetX, TargetY = HandleResolutionCheck(
WidthParametersName = [ 'x', 'width' ],
HeightParametersName = [ 'y', 'height' ],
AllowedWidths = [48,180,420,60,100,150,352,324,576],
AllowedHeights = [48,180,420,60,100,150,352,324,576],
MustBeSquare = False,
CanRoundToNearest = True
)
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=assetId).first()
if PlaceIconObj is None:
return redirect("/static/img/placeholder.png")
if PlaceIconObj.moderation_status != 0 or PlaceIconObj.asset.moderation_status != 0:
if PlaceIconObj.moderation_status == 2 or PlaceIconObj.asset.moderation_status == 2:
return redirect("/static/img/ContentDeleted.png")
return redirect("/static/img/placeholder.png")
ContentHash = PlaceIconObj.contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetX}-{TargetY}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetX,
TargetHeight = TargetY,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
@ImageRoute.route('/Game/Tools/ThumbnailAsset.ashx', methods=['GET'])
def thumbnail_asset():
ExpectedFormat : str = request.args.get( key='fmt', default='png', type=str )
AssetId : int = request.args.get( key='aid', default=None, type=int )
if AssetId is None:
return redirect("/static/img/placeholder.png")
if ExpectedFormat.lower() != "png":
return redirect("/static/img/placeholder.png")
AssetObj : Asset = Asset.query.filter_by(id=AssetId).first()
if AssetObj is None:
return redirect("/static/img/placeholder.png")
TargetWidth, TargetHeight = HandleResolutionCheck(
WidthParametersName = [ 'wd', 'width' ],
HeightParametersName = [ 'ht', 'height' ],
AllowedWidths = [48,180,420,60,100,150,352,75],
AllowedHeights = [48,180,420,60,100,150,352,75],
MustBeSquare = True,
CanRoundToNearest = True
)
thumbnail : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=AssetId).order_by(AssetThumbnail.asset_version_id.desc()).first()
if thumbnail is None:
TakeThumbnail(AssetId)
return redirect("/static/img/placeholder.png")
if thumbnail.moderation_status != 0:
if thumbnail.moderation_status == 2 or thumbnail.asset.moderation_status == 2:
return redirect("/static/img/ContentDeleted.png")
return redirect("/static/img/placeholder.png")
ContentHash = thumbnail.content_hash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
return HandleImageResize(
ImageContentHash = ContentHash,
TargetWidth = TargetWidth,
TargetHeight = TargetHeight,
CroppedHash = CroppedHash,
CacheControl = "max-age=120"
)
import urllib.parse
@ImageRoute.route("/v1/batch", methods=["POST"])
@csrf.exempt
def BatchImageRequest():
if request.headers.get("Content-Encoding") == "gzip":
try:
data = gzip.decompress(request.data)
except Exception as e:
return jsonify({"success": False, "message": "Invalid gzip data"}), 400
try:
JSONData = json.loads(data)
except Exception as e:
return jsonify({"success": False, "message": "Invalid JSON data"}), 400
else:
JSONData = request.json
if JSONData is None:
return jsonify({"success": False, "message": "Missing JSON data"}), 400
# [{'requestId': 'type=GameIcon&id=1&w=128&h=128&filters=', 'targetId': 1, 'type': 'GameIcon', 'size': '128x128', 'isCircular': False}]
if len(JSONData) > 15:
return jsonify({"success": False, "message": "Too many requests"}), 400
if len(JSONData) == 0:
return jsonify({"data":[]}), 200
ProcessedRequests = []
for RequestObj in JSONData:
if "requestId" not in RequestObj or "targetId" not in RequestObj or "type" not in RequestObj or "size" not in RequestObj:
continue
if RequestObj["type"] not in [ "Avatar", "AvatarHeadShot", "GameIcon", "GameThumbnail", "Asset", "GroupIcon"]:
continue
if "x" not in RequestObj["size"]:
continue
SplittedSize = RequestObj["size"].split("x")
if len(SplittedSize) != 2:
continue
try:
TargetWidth = int(SplittedSize[0])
TargetHeight = int(SplittedSize[1])
except:
continue
AllowedSizes = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720]
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
RequestType = RequestObj["type"]
if RequestType == "Avatar":
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=RequestObj["targetId"]).first()
if ThumbnailObj is None:
continue
ContentHash = ThumbnailObj.full_contenthash
elif RequestType == "AvatarHeadShot":
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=RequestObj["targetId"]).first()
if ThumbnailObj is None:
continue
ContentHash = ThumbnailObj.headshot_contenthash
elif RequestType == "GameIcon":
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=RequestObj["targetId"]).first()
if PlaceIconObj is None:
continue
ContentHash = PlaceIconObj.contenthash
elif RequestType == "GameThumbnail" or RequestType == "Asset":
thumbnailObj : AssetThumbnail = AssetThumbnail.query.filter_by(asset_id=RequestObj["targetId"]).order_by(AssetThumbnail.asset_version_id.desc()).first()
if thumbnailObj is None:
continue
if thumbnailObj.moderation_status != 0:
continue
ContentHash = thumbnailObj.content_hash
elif RequestType == "GroupIcon":
ThumbnailObj : GroupIcon = GroupIcon.query.filter_by(group_id=RequestObj["targetId"]).first()
if ThumbnailObj is None:
continue
if ThumbnailObj.moderation_status != 0:
continue
ContentHash = ThumbnailObj.content_hash
else:
continue
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
if not s3helper.DoesKeyExist(CroppedHash):
if not s3helper.DoesKeyExist(ContentHash):
continue
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
ImageObj = Image.open(ImageContent)
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
VirtualFile = BytesIO()
ImageObj.save(VirtualFile, "PNG")
VirtualFile.seek(0)
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
ProcessedRequests.append({
"requestId": RequestObj["requestId"],
"targetId": RequestObj["targetId"],
"state": "Completed",
"imageUrl": f"{config.CDN_URL}/{CroppedHash}",
"version": None
})
return jsonify({
"data": ProcessedRequests
})
@ImageRoute.route("/v1/users/avatar-headshot", methods=["GET"])
def multi_avatar_headshot():
userIdsCSV = request.args.get('userIds', default = None, type = str)
if userIdsCSV is None:
return jsonify( { "errors": [ { "code": 4, "message": "The requested Ids are invalid, of an invalid type or missing." } ] } ), 400
userIds = userIdsCSV.split(",")
if len(userIds) > 100:
return jsonify( { "errors": [ { "code": 1, "message": "There are too many requested Ids." } ] } ), 400
requestedSize = request.args.get('size', default = "48x48", type = str)
if "x" not in requestedSize:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
SplittedSize = requestedSize.split("x")
if len(SplittedSize) != 2:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
try:
TargetWidth = int(SplittedSize[0])
TargetHeight = int(SplittedSize[1])
AllowedSizes = [48,180,420,60,100,150,352,396,480,512,576,700,768,640,36,1280,720]
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
except:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
ProcessedRequests = []
for userId in userIds:
try:
userId = int(userId)
except:
continue
ThumbnailObj : UserThumbnail = UserThumbnail.query.filter_by(userid=userId).first()
if ThumbnailObj is None:
continue
ContentHash = ThumbnailObj.headshot_contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
if not s3helper.DoesKeyExist(CroppedHash):
if not s3helper.DoesKeyExist(ContentHash):
continue
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
ImageObj = Image.open(ImageContent)
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
VirtualFile = BytesIO()
ImageObj.save(VirtualFile, "PNG")
VirtualFile.seek(0)
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
ProcessedRequests.append({
"targetId": userId,
"state": "Completed",
#"imageUrl": f"{config.CDN_URL}/{CroppedHash}", # 2020 Does not allow things like cdn.syntax.eco to be used directly in the "Texture" property so we have to redirect them to an allowed route on the www. domain
"imageUrl": f"{config.BaseURL}/headshot-thumbnail/image?userId={userId}&x={TargetWidth}&y={TargetHeight}",
"version": "1"
})
return jsonify({
"data": ProcessedRequests
}), 200
@ImageRoute.route("/v1/games/icons", methods=["GET"])
def get_game_icons():
universeIdsCSV = request.args.get('universeIds', default = None, type = str)
if universeIdsCSV is None:
return jsonify( { "errors": [ { "code": 4, "message": "The requested Ids are invalid, of an invalid type or missing." } ] } ), 400
universeIdsList = universeIdsCSV.split(",")
if len(universeIdsList) > 100:
return jsonify( { "errors": [ { "code": 1, "message": "There are too many requested Ids." } ] } ), 400
requestedSize = request.args.get('size', default = "50x50", type = str)
if "x" not in requestedSize:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
SplittedSize = requestedSize.split("x")
if len(SplittedSize) != 2:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
try:
TargetWidth = int(SplittedSize[0])
TargetHeight = int(SplittedSize[1])
AllowedSizes = [50, 128, 150, 256, 420, 512]
TargetWidth = min(AllowedSizes, key=lambda x:abs(x-TargetWidth))
TargetHeight = min(AllowedSizes, key=lambda x:abs(x-TargetHeight))
except:
return jsonify( { "errors": [ { "code": 3, "message": "The requested size is invalid. Please see documentation for valid thumbnail size parameter name and format." } ] } ), 400
ProcessedRequests = []
for universeId in universeIdsList:
try:
universeId = int(universeId)
except:
continue
PlaceIconObj : PlaceIcon = PlaceIcon.query.filter_by(placeid=universeId).first()
if PlaceIconObj is None:
continue
ContentHash = PlaceIconObj.contenthash
CroppedHash = hashlib.sha512(f"{ContentHash}-{TargetWidth}-{TargetHeight}-v3".encode('utf-8')).hexdigest()
if not s3helper.DoesKeyExist(CroppedHash):
if not s3helper.DoesKeyExist(ContentHash):
continue
ImageContent = BytesIO( s3helper.GetFileFromS3(ContentHash) )
ImageObj = Image.open(ImageContent)
ImageObj = ImageObj.resize((int(TargetWidth),int(TargetHeight))).convert('RGBA')
VirtualFile = BytesIO()
ImageObj.save(VirtualFile, "PNG")
VirtualFile.seek(0)
s3helper.UploadBytesToS3(VirtualFile.getvalue(), CroppedHash, contentType="image/png")
ProcessedRequests.append({
"targetId": universeId,
"state": "Completed",
#"imageUrl": f"{config.CDN_URL}/{CroppedHash}"
"imageUrl": f"{config.BaseURL}/Thumbs/GameIcon.ashx?assetId={str(universeId)}&x={str(TargetWidth)}&y={str(TargetHeight)}"
})
return jsonify({
"data": ProcessedRequests
}), 200