使用 Amazon S3 Object Lambda 在擷取影像時動態加上浮水印影像
教學
概觀
透過 Amazon S3 Object Lambda,您可將自己的程式碼新增至 S3 GET、HEAD 和 LIST 請求,以便在資料傳回應用程式時對其做出修改。您可以使用自訂程式碼修改 S3 GET 請求傳回的資料,以轉換資料格式 (例如,XML 轉換為 JSON)、動態調整影像大小、修訂機密資料以及更多動作。您也可以使用 S3 Object Lambda 修改 S3 LIST 請求的輸出,以建立儲存貯體中物件的自訂檢視,以及用於修改物件名稱和大小等物件中繼資料的 S3 HEAD 請求。
本教學的目的是向您展示如何開始使用 Amazon S3 Object Lambda。許多組織在 Amazon S3 中存放可由不同應用程式存取的影像,每個影像都有獨特的資料格式要求。在某些情況下,影像可能需要修改才能包含浮水印,視存取影像的使用者而定 (例如,付費訂閱用戶可以檢視沒有浮水印的影像,而非付費使用者則會收到有浮水印的影像)。
在本教學中,我們將使用 S3 Object Lambda 在從 Amazon S3 擷取影像時將浮水印加入影像。S3 Object Lambda 可用來在從 Amazon S3 擷取資料時修改資料,而無需變更現有物件或維護資料的多個衍生副本。透過呈現相同資料的多個檢視並且不需要儲存衍生副本,您可以節省儲存成本。
您會完成的目標
在本教學中,您將:
- 建立 Amazon S3 儲存貯體
- 建立 S3 存取點
- 建立 AWS Lambda 函數以修改影像
- 建立 S3 Object Lambda 存取點
先決條件
您需要 AWS 帳戶才能完成本教學。存取此支援頁面,瞭解有關如何建立和啟用新 AWS 帳戶的詳細資訊。
您可以為本教學建立 IAM 使用者,也可以為現有的 IAM 使用者新增許可。若要完成本教學,您的 IAM 使用者必須包含下列許可,才能存取相關 AWS 資源並執行特定動作:
- s3:CreateBucket
- s3:PutObject
- s3:GetObject
- s3:ListBucket
- s3:CreateAccessPoint
- s3:CreateAccessPointForObjectLambda
- s3-object-lambda:WriteGetObjectResponse
- lambda:CreateFunction
- lambda:InvokeFunction
- iam:AttachRolePolicy
- iam:CreateRole
- iam:PutRolePolicy
若要清理您在本教學中建立的資源,您將需要下列 IAM 許可:
- s3:DeleteBucket
- s3:DeleteAccessPoint
- s3:DeleteAccessPointForObjectLambda
- lambda:DeleteFunction
- iam:DeleteRole
AWS 經驗
完成時間
20 分鐘
完成成本
低於 1 美元 (Amazon S3 定價頁面)
要求
AWS 帳戶*
**過去 24 小時內建立的帳戶可能尚未具有本教學所需資源的存取權。
使用的服務
上次更新日期
2023 年 2 月 1 日
先決條件
您需要 AWS 帳戶才能完成本教學。存取此支援頁面,瞭解有關如何建立和啟用新 AWS 帳戶的詳細資訊。
您可以為本教學建立 IAM 使用者,也可以為現有的 IAM 使用者新增許可。若要完成本教學,您的 IAM 使用者必須包含下列許可,才能存取相關 AWS 資源並執行特定動作:
-
所需許可
- s3:CreateBucket
- s3:PutObject
- s3:GetObject
- s3:ListBucket
- s3:CreateAccessPoint
- s3:CreateAccessPointForObjectLambda
- s3-object-lambda:WriteGetObjectResponse
- lambda:CreateFunction
- lambda:InvokeFunction
- iam:AttachRolePolicy
- iam:CreateRole
- iam:PutRolePolicy
-
所需許可
若要清理您在本教學中建立的資源,您將需要下列 IAM 許可:
- s3:DeleteBucket
- s3:DeleteAccessPoint
- s3:DeleteAccessPointForObjectLambda
- lambda:DeleteFunction
- iam:DeleteRole
實作
步驟 1:建立 Amazon S3 儲存貯體
1.1 – 登入 Amazon S3 主控台
- 登入 AWS 管理主控台,然後開啟 Amazon S3 主控台。
1.2 – 建立 S3 儲存貯體
- 從左側導覽窗格的 Amazon S3 功能表中選取儲存貯體,然後選擇建立儲存貯體按鈕。
1.3
- 在儲存貯體名稱欄位中,輸入儲存貯體的全域唯一描述性名稱。選取您想在其中建立儲存貯體的 AWS 區域。我們將在稍後的教學中建立另一個必須位於相同 AWS 區域的資源。
- 至於剩餘的選項,使用預設選項即可。導覽至頁面底部,然後選擇建立儲存貯體。
步驟 2:上傳物件
2.1 – 上傳物件
- 從可用儲存貯體清單中,選取您剛剛建立的儲存貯體名稱。
2.2
- 接下來,請確定已選取物件分頁。然後在物件區段中,選擇上傳按鈕。
2.3 – 新增檔案
- 選擇新增檔案按鈕,然後從檔案瀏覽器中選取您要上傳的影像。
- 如果您想要的話,您可以上傳這張範例影像。
2.4 – 上傳
- 向下導覽頁面,然後選擇上傳按鈕。
2.5
- 上傳完成且顯示成功後,請選擇關閉按鈕。
步驟 3:建立 S3 存取點
建立將用於支援 S3 Object Lambda 存取點的 Amazon S3 存取點,我們將在稍後的教學中建立這個存取點。
3.2
- 在屬性區段中,輸入所需的存取點名稱,接著選取瀏覽 S3按鈕來選擇您在步驟 1 中輸入的儲存貯體名稱。接下來,將網路原點設為網際網路。
3.3
- 保留所有其他預設值。瀏覽至頁面底部,然後選擇建立存取點按鈕。
3.4
- 當您導覽至左側導覽窗格中的存取點時,S3 存取點現在會出現在清單中。
步驟 4:建立 Lambda 函數
- 接下來,建立 Lambda 函數,該函數會在透過 S3 Object Lambda 存取點提出 S3 GET 請求時調用。
- 我們會使用 AWS 管理主控台中的 AWS CloudShell 來建置和測試 S3 Object Lambda。如果您符合以下要求,則可以使用自己的電腦或 AWS Cloud9 執行個體來建置解決方案:
- 最新版的 AWS Command Line Interface (CLI)
- 用來建立 AWS Lambda 函數/層和 IAM 角色的憑證
- Python 3.9
- 壓縮工具程式
- jq 工具程式
4.1 — 啟動 CloudShell 終端
選取 AWS 管理主控台右上方功能表中的 CloudShell 圖示。
如果出現 CloudShell 介紹視窗,敬請閱讀內容並選擇關閉。
瀏覽器會開啟一個具有 CloudShell 終端的分頁 (類似下列螢幕截圖):
4.2 — 準備部署 Lambda 函數
- 在 CloudShell 中執行下列程式碼以準備環境,並使用 Pillow 模組部署 Lambda 層。將下列程式碼複製並貼上 CloudShell 中,以安裝所需的相依項並部署 Lambda 函數。
# Install the required libraries to build new python
sudo yum install gcc openssl-devel bzip2-devel libffi-devel -y
# Install Pyenv
curl https://pyenv.run | bash
echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile
echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile
echo 'eval "$(pyenv init -)"' >> ~/.bash_profile
source ~/.bash_profile
# Install Python version 3.9
pyenv install 3.9.13
pyenv global 3.9.13
# Build the pillow Lambda layer
mkdir python
cd python
pip install pillow -t .
cd ..
zip -r9 pillow.zip python/
aws lambda publish-layer-version \
--layer-name Pillow \
--description "Python Image Library" \
--license-info "HPND" \
--zip-file fileb://pillow.zip \
--compatible-runtimes python3.9
附註:複製和貼上程式碼時,CloudShell 會打開一個警告視窗,確認您要貼上多行程式碼。選取貼上。
此步驟可能需要 10 到 15 分鐘才能完成。
4.3 – 建置 Lambda 函數
- 下載 TrueType 字型,此字型將由 Lambda 函數用來將浮水印新增至影像。將以下命令複製並貼上 CloudShell 中。
wget https://m.media-amazon.com/images/G/01/mobile-apps/dex/alexa/branding/Amazon_Typefaces_Complete_Font_Set_Mar2020.zip
- 擷取 TrueType 字型,此字型將會用來在影像中寫入浮水印文字。
unzip -oj Amazon_Typefaces_Complete_Font_Set_Mar2020.zip "Amazon_Typefaces_Complete_Font_Set_Mar2020/Ember/AmazonEmber_Rg.ttf"
- 建立將用來處理 S3 Object Lambda 請求的 Lambda 程式碼。
cat << EOF > lambda.py
import boto3
import json
import os
import logging
from io import BytesIO
from PIL import Image, ImageDraw, ImageFont
from urllib import request
from urllib.parse import urlparse, parse_qs, unquote
from urllib.error import HTTPError
from typing import Optional
logger = logging.getLogger('S3-img-processing')
logger.addHandler(logging.StreamHandler())
logger.setLevel(getattr(logging, os.getenv('LOG_LEVEL', 'INFO')))
FILE_EXT = {
'JPEG': ['.jpg', '.jpeg'],
'PNG': ['.png'],
'TIFF': ['.tif']
}
OPACITY = 64 # 0 = transparent and 255 = full solid
def get_img_encoding(file_ext: str) -> Optional[str]:
result = None
for key, value in FILE_EXT.items():
if file_ext in value:
result = key
break
return result
def add_watermark(img: Image, text: str) -> Image:
font = ImageFont.truetype("AmazonEmber_Rg.ttf", 82)
txt = Image.new('RGBA', img.size, (255, 255, 255, 0))
if img.mode != 'RGBA':
image = img.convert('RGBA')
else:
image = img
d = ImageDraw.Draw(txt)
# Positioning Text
width, height = image.size
text_width, text_height = d.textsize(text, font)
x = width / 2 - text_width / 2
y = height / 2 - text_height / 2
# Applying Text
d.text((x, y), text, fill=(255, 255, 255, OPACITY), font=font)
# Combining Original Image with Text and Saving
watermarked = Image.alpha_composite(image, txt)
return watermarked
def handler(event, context) -> dict:
logger.debug(json.dumps(event))
object_context = event["getObjectContext"]
# Get the presigned URL to fetch the requested original object
# from S3
s3_url = object_context["inputS3Url"]
# Extract the route and request token from the input context
request_route = object_context["outputRoute"]
request_token = object_context["outputToken"]
parsed_url = urlparse(event['userRequest']['url'])
object_key = parsed_url.path
logger.info(f'Object to retrieve: {object_key}')
parsed_qs = parse_qs(parsed_url.query)
for k, v in parsed_qs.items():
parsed_qs[k][0] = unquote(v[0])
filename = os.path.splitext(os.path.basename(object_key))
# Get the original S3 object using the presigned URL
req = request.Request(s3_url)
try:
response = request.urlopen(req)
except HTTPError as e:
logger.info(f'Error downloading the object. Error code: {e.code}')
logger.exception(e.read())
return {'status_code': e.code}
if encoding := get_img_encoding(filename[1].lower()):
logger.info(f'Compatible Image format found! Processing image: {"".join(filename)}')
img = Image.open(response)
logger.debug(f'Image format: {img.format}')
logger.debug(f'Image mode: {img.mode}')
logger.debug(f'Image Width: {img.width}')
logger.debug(f'Image Height: {img.height}')
img_result = add_watermark(img, parsed_qs.get('X-Amz-watermark', ['Watermark'])[0])
img_bytes = BytesIO()
if img.mode != 'RGBA':
# Watermark added an Alpha channel that is not compatible with JPEG. We need to convert to RGB to save
img_result = img_result.convert('RGB')
img_result.save(img_bytes, format='JPEG')
else:
# Will use the original image format (PNG, GIF, TIFF, etc.)
img_result.save(img_bytes, encoding)
img_bytes.seek(0)
transformed_object = img_bytes.read()
else:
logger.info(f'File format not compatible. Bypass file: {"".join(filename)}')
transformed_object = response.read()
# Write object back to S3 Object Lambda
s3 = boto3.client('s3')
# The WriteGetObjectResponse API sends the transformed data
if os.getenv('AWS_EXECUTION_ENV'):
s3.write_get_object_response(
Body=transformed_object,
RequestRoute=request_route,
RequestToken=request_token)
else:
# Running in a local environment. Saving the file locally
with open(f'myImage{filename[1]}', 'wb') as f:
logger.debug(f'Writing file: myImage{filename[1]} to the local filesystem')
f.write(transformed_object)
# Exit the Lambda function: return the status code
return {'status_code': 200}
EOF
- 建立包含 Python 程式碼和 TrueType 字型檔案的 Lambda 壓縮檔案。
zip -r9 lambda.zip lambda.py AmazonEmber_Rg.ttf
- 建立附加至 Lambda 函數的 IAM 角色。
aws iam create-role --role-name ol-lambda-images --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{"Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}'
- 將預先定義的 IAM 政策附加到先前建立的 IAM 角色。此政策包含執行 Lambda 函數所需的最低許可。
aws iam attach-role-policy --role-name ol-lambda-images --policy-arn arn:aws:iam::aws:policy/service-role/AmazonS3ObjectLambdaExecutionRolePolicy
export OL_LAMBDA_ROLE=$(aws iam get-role --role-name ol-lambda-images | jq -r .Role.Arn)
export LAMBDA_LAYER=$(aws lambda list-layers --query 'Layers[?contains(LayerName, `Pillow`) == `true`].LatestMatchingVersion.LayerVersionArn' | jq -r .[])
- 建立並上傳 Lambda 函數。
aws lambda create-function --function-name ol_image_processing \
--zip-file fileb://lambda.zip --handler lambda.handler --runtime python3.9 \
--role $OL_LAMBDA_ROLE \
--layers $LAMBDA_LAYER \
--memory-size 1024
步驟 5:建立 S3 Object Lambda 存取點
5.1 — 建立 S3 Object Lambda 存取點
- 導覽至 S3 主控台,然後在左側導覽窗格中選擇 Object Lambda 存取點。選擇建立 Object Lambda 存取點按鈕。
在一般區段中,對於 Object Lambda 存取點名稱,輸入 ol-amazon-s3-images-guide。
確保 S3 Object Lambda 存取點的 AWS 區域與您在步驟 1.3 中建立 S3 儲存貯體時指定的 AWS 區域相符。
對於支援的存取點,使用瀏覽 S3 按鈕指定您在步驟 3.2 中建立的 S3 存取點的 Amazon Resource Name (ARN)。
向下導覽以檢視轉換組態。在 S3 API 清單中,選取 GetObject 選項。
在 Lambda 函數下,指定 ol_image_processing。
接下來,導覽至頁面底部,然後選擇建立 Object Lambda 存取點。
步驟 6:從 S3 Object Lambda 存取點下載影像
6.1 — 開啟 S3 Object Lambda 存取點
- 在 S3 主控台左側導覽窗格中選擇 Object Lambda 存取點以返回 S3 Object Lambda 存取點清單,然後選取您在步驟 5.1 中建立的 S3 Object Lambda 存取點。在此範例中,我們選擇 S3 Object Lambda 存取點做為 ol-amazon-s3-images-guide。
選取您在步驟 2.4 中上傳的影像,然後選擇開啟按鈕。
6.2 — 從 AWS CLI 下載轉換後的影像
- 您也可以使用 AWS CLI 下載影像。若要這麼做,您需要 S3 Object Lambda 存取點的 Amazon Resource Name (ARN)。在 S3 主控台中,導覽至 Object Lambda 存取點頁面,選取 S3 Object Lambda 存取點的名稱,接著選取屬性分頁,然後選擇 Amazon Resource Name (ARN) 下方的複製圖示。
6.3 — 從 CloudShell 執行 AWS CLI 命令
在 CloudShell 瀏覽器分頁中,輸入下列內容:
aws s3api get-object --bucket <paste the ARN copied above here> --key <image filename here> <filename to write here>
6.4 — 將影像下載到本機電腦
在 CloudShell 中,選擇右上角的動作,然後選取下載檔案。
從 S3 Object Lambda 存取點下載影像時,輸入您在步驟 6.3 中定義的檔案名稱,然後選擇下載。
現在,您可以從本機電腦打開影像。
附註:影像檢視程式可能依電腦和作業系統而異。如果您不確定要使用哪個應用程式來開啟影像,請洽詢您的管理員。
步驟 7:清除資源
7.1 — 刪除 S3 Object Lambda 存取點
- 導覽至 S3 主控台,然後在左側導覽窗格中選擇 Object Lambda 存取點。
- 在 Object Lambda 存取點頁面上,選擇您在步驟 5.1 中建立的 S3 Object Lambda 存取點左側的選項按鈕。
選擇刪除。
在出現的文字欄位中,輸入 S3 Object Lambda 存取點的名稱,確認您要刪除 S3 Object Lambda 存取點,然後選擇刪除。
7.2 — 刪除 S3 存取點
- 在 S3 主控台的左側導覽窗格中,選擇存取點。
- 導覽至您在步驟 3.1 中建立的 S3 存取點,然後選擇 S3 存取點名稱旁邊的選項按鈕。
- 選擇刪除。
在出現的文字欄位中輸入存取點的名稱,以確認您要刪除存取點,然後選擇刪除。
7.3 – 刪除測試物件
- 導覽至 S3 主控台,然後選取左側導覽窗格中的儲存貯體功能表選項。首先,您需要從測試儲存貯體中刪除測試物件。選取您在本教學中使用的儲存貯體名稱。
- 選取測試物件名稱左側的核取方塊,然後選擇刪除按鈕。
- 在刪除物件頁面上,確認您是否已選取要刪除的物件,並在永久刪除物件確認方塊中輸入刪除。然後,選擇刪除物件按鈕以繼續。
7.4 — 刪除 S3 儲存貯體
- 接下來,在左側導覽窗格的 S3 主控台功能表中選擇儲存貯體。選取為本教學建立的來源儲存貯體左側的選項按鈕,然後選擇刪除按鈕。
檢閱警告訊息。如果要繼續刪除此儲存貯體,請在刪除儲存貯體確認方塊中輸入儲存貯體的名稱,然後選擇刪除儲存貯體。
7.5 – 刪除 Lambda 函數
- 在 AWS Lambda 主控台中,選擇左側導覽窗格的函數。
- 選取您在步驟 4.3 中建立之函數名稱左側的核取方塊。
- 選擇動作,然後選擇刪除。在刪除函數對話方塊中,選擇「刪除」。
結語
恭喜您! 您學會了如何使用 Amazon S3 Object Lambda 在擷取影像時動態新增浮水印至影像,並將處理過的影像傳送回請求的用戶端。您可以針對您的使用案例自訂 Lambda 函數,以修改 S3 GET、HEAD 和 LIST 請求所傳回的資料,包含使用呼叫者特定詳細資料的自訂浮水印、遮罩敏感資料、篩選特定資料列、使用其他資料庫的資訊增強資料、轉換資料格式等等。