使用 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 資源並執行特定動作: 

實作

步驟 1:建立 Amazon S3 儲存貯體

1.1 – 登入 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.1 — 建立 S3 存取點

  • 導覽至 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 存取點

建立用於存取 S3 儲存貯體中存放之影像的 S3 Object Lambda 存取點。

5.1 — 建立 S3 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 存取點下載影像

建立 S3 物件 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 中上傳的影像,然後選擇開啟按鈕。

瀏覽器會開啟新的分頁,其中包含您的影像和浮水印。
 
從 S3 Object Lambda 存取點下載的所有相容影像現在都會包含浮水印文字。


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 請求所傳回的資料,包含使用呼叫者特定詳細資料的自訂浮水印、遮罩敏感資料、篩選特定資料列、使用其他資料庫的資訊增強資料、轉換資料格式等等。

本頁對您是否有幫助?

後續步驟