開發Asset compute工作人員

asset compute背景工作是Asset compute專案的核心,因為提供可執行或協調資產所執行工作以建立新轉譯的自訂功能。

asset compute專案會自動產生一個簡單背景工作,將資產的原始二進位檔複製到指定的轉譯中,不需進行任何轉換。 在本教學課程中,我們將修改此背景工作,以製作更有趣的轉譯,以說明Asset compute背景工作的強大功能。

我們將建立一個Asset compute背景工作,產生新的水準影像轉譯,在資產轉譯的左側和右側覆蓋空白空間,但資產版本模糊。 最終轉譯的寬度、高度和模糊會參數化。

asset compute工作器調用的邏輯流

asset compute背景工作者會在renditionCallback(...)函式中實作Asset computeSDK背景工作API合約,其概念如下:

  • 輸入: AEM資產的原始二進位和處理設定檔參數
  • 輸出: 要新增一或多個轉譯至AEM資產

asset compute工作邏輯流

  1. AEM製作服務會叫用Asset compute背景工作,提供資產的​(1a)​原始二進位檔(source參數),以及處理設定檔(rendition.instructions參數)中定義的​(1b)​任何參數。

  2. asset computeSDK可協調自訂Asset compute中繼資料背景工作的renditionCallback(...)函式,並根據資產的原始二進位​(1a)​和任何參數​(1b)​產生新的二進位轉譯。

    • 在本教學課程中,會建立「正在處理中」的轉譯,這表示工作程式會製作轉譯,但也可以將來源二進位檔傳送至其他Web服務API,以產生轉譯。
  3. asset compute工作器會將新格式副本的二進位資料保存到rendition.path

  4. 寫入rendition.path的二進位資料會透過Asset computeSDK傳輸至AEM製作服務,並以​(4a)​文字轉譯和​(4b)​保存至資產的中繼資料節點的形式公開。

上圖闡述了Asset compute開發人員所關心的問題,以及Asset compute工作人員調用的邏輯流程。 有些人會了解Asset compute執行的內部詳細資訊,但只有公開Asset computeSDK API合約才能依賴。

工人解剖

所有Asset compute工人都遵循相同的基本結構和投入/產出合同。

'use strict';

// Any npm module imports used by the worker
const { worker, SourceCorruptError } = require('@adobe/asset-compute-sdk');
const fs = require('fs').promises;

/**
Exports the worker implemented by a custom rendition callback function, which parametrizes the input/output contract for the worker.
 + `source` represents the asset's original binary used as the input for the worker.
 + `rendition` represents the worker's output, which is the creation of a new asset rendition.
 + `params` are optional parameters, which map to additional key/value pairs, including a sub `auth` object that contains Adobe I/O access credentials.
**/
exports.main = worker(async (source, rendition, params) => {
    // Perform any necessary source (input) checks
    const stats = await fs.stat(source.path);
    if (stats.size === 0) {
        // Throw appropriate errors whenever an erring condition is met
        throw new SourceCorruptError('source file is empty');
    }

    // Access any custom parameters provided via the Processing Profile configuration
    let param1 = rendition.instructions.exampleParam;

    /** 
    Perform all work needed to transform the source into the rendition.
    
    The source data can be accessed:
        + In the worker via a file available at `source.path`
        + Or via a presigned GET URL at `source.url`
    **/
    if (success) {
        // A successful worker must write some data back to `renditions.path`. 
        // This example performs a trivial 1:1 copy of the source binary to the rendition
        await fs.copyFile(source.path, rendition.path);
    } else {
        // Upon failure an Asset Compute Error (exported by @adobe/asset-compute-commons) should be thrown.
        throw new GenericError("An error occurred!", "example-worker");
    }
});

/**
Optionally create helper classes or functions the worker's rendition callback function invokes to help organize code.

Code shared across workers, or to complex to be managed in a single file, can be broken out across supporting JavaScript files in the project and imported normally into the worker. 
**/
function customHelperFunctions() { ... }

開啟工作索引.js

自動產生的index.js

  1. 確認Asset compute專案在VS程式碼中開啟
  2. 導覽至/actions/worker資料夾
  3. 開啟index.js檔案

這是我們在本教學課程中將修改的背景JavaScript檔案。

安裝和匯入支援的npm模組

以Node.js為基礎的Asset compute專案,可從健全的npm模組生態系統中獲益。 若要運用npm模組,我們必須先將它們安裝至我們的Asset compute專案。

在此背景工作中,我們利用jimp直接在Node.js程式碼中建立和操控轉譯影像。

警告

並非所有npm模組都支援Asset compute操作資產。 不支援依賴應用程式(如ImageMagick或其他OS相關庫)存在的npm模組。 最好限制為僅限JavaScriptnpm模組的使用。

  1. 開啟Asset compute專案根目錄中的命令列(可透過​終端機>新終端機​在VS程式碼中完成)並執行命令:

    $ npm install jimp
    
  2. jimp模組匯入工作程式碼中,以便透過Jimp JavaScript物件使用。
    更新工作人員index.js頂部的require指令,以從jimp模組導入Jimp對象:

    'use strict';
    
    const Jimp = require('jimp');
    const { worker, SourceCorruptError } = require('@adobe/asset-compute-sdk');
    const fs = require('fs').promises;
    
    exports.main = worker(async (source, rendition, params) => {
        // Check handle a corrupt input source
        const stats = await fs.stat(source.path);
        if (stats.size === 0) {
            throw new SourceCorruptError('source file is empty');
        }
    
        // Do work here
    });
    

讀取參數

asset compute背景工作可讀取參數,這些參數可透過AEM中定義為Cloud Service製作服務的處理設定檔傳入。 參數會透過rendition.instructions物件傳遞至背景工作。

可通過訪問工作代碼中的rendition.instructions.<parameterName>來讀取這些內容。

在此處,我們將閱讀可設定轉譯的SIZEBRIGHTNESSCONTRAST,如果未透過處理設定檔提供任何值,則提供預設值。 請注意,從AEM以Cloud Service處理設定檔叫用renditions.instructions時,會以字串形式傳入,因此請確定它們已轉換為背景代碼中正確的資料類型。

'use strict';

const Jimp = require('jimp');
const { worker, SourceCorruptError } = require('@adobe/asset-compute-sdk');
const fs = require('fs').promises;

exports.main = worker(async (source, rendition, params) => {
    const stats = await fs.stat(source.path);
    if (stats.size === 0) {
        throw new SourceCorruptError('source file is empty');
    }

    // Read in parameters and set defaults if parameters are provided
    // Processing Profiles pass in instructions as Strings, so make sure to parse to correct data types
    const SIZE = parseInt(rendition.instructions.size) || 800; 
    const CONTRAST = parseFloat(rendition.instructions.contrast) || 0;
    const BRIGHTNESS = parseFloat(rendition.instructions.brightness) || 0;

    // Do work here
}

擲回錯誤

asset compute工作人員可能會遇到導致錯誤的情況。 AdobeAsset computeSDK提供一套預先定義的錯誤,在發生此類情況時可擲回。 如果未應用特定錯誤類型,則可以使用GenericError,也可以定義特定自定義ClientErrors

開始處理轉譯之前,請檢查以確保此工作器的上下文中所有參數都有效且受支援:

  • 確保SIZECONTRASTBRIGHTNESS的轉譯指令參數有效。 若未設定,則擲回自訂錯誤RenditionInstructionsError
    • 在此檔案底部定義了擴展ClientError的自定義RenditionInstructionsError類。 當寫入工作器的測試時,使用特定的自定義錯誤非常有用。
'use strict';

const Jimp = require('jimp');
// Import the Asset Compute SDK provided `ClientError` 
const { worker, SourceCorruptError, ClientError } = require('@adobe/asset-compute-sdk');
const fs = require('fs').promises;

exports.main = worker(async (source, rendition, params) => {
    const stats = await fs.stat(source.path);
    if (stats.size === 0) {
        throw new SourceCorruptError('source file is empty');
    }

    // Read in parameters and set defaults if parameters are provided
    const SIZE = parseInt(rendition.instructions.size) || 800; 
    const CONTRAST = parseFloat(rendition.instructions.contrast) || 0;
    const BRIGHTNESS = parseFloat(rendition.instructions.brightness) || 0;

    if (SIZE <= 10 || SIZE >= 10000) {
        // Ensure size is within allowable bounds
        throw new RenditionInstructionsError("'size' must be between 10 and 1,0000");
    } else if (CONTRAST <= -1 || CONTRAST >= 1) {
        // Ensure contrast is valid value
        throw new RenditionInstructionsError("'contrast' must between -1 and 1");
    } else if (BRIGHTNESS <= -1 || BRIGHTNESS >= 1) {
        // Ensure contrast is valid value
        throw new RenditionInstructionsError("'brightness' must between -1 and 1");
    }

    // Do work here
}

// Create a new ClientError to handle invalid rendition.instructions values
class RenditionInstructionsError extends ClientError {
    constructor(message) {
        // Provide a:
        // + message: describing the nature of this erring condition
        // + name: the name of the error; usually same as class name
        // + reason: a short, searchable, unique error token that identifies this error
        super(message, "RenditionInstructionsError", "rendition_instructions_error");

        // Capture the strack trace
        Error.captureStackTrace(this, RenditionInstructionsError);
    }
}

建立轉譯

在讀取、淨化和驗證參數後,會寫入程式碼以產生轉譯。 產生轉譯的虛擬碼如下:

  1. 以透過size參數指定的平方尺寸建立新的renditionImage畫布。
  2. 從來源資產的二進位檔建立image物件
  3. 使用​Jimp​資料庫來轉換影像:
    • 將原始影像裁切到正方形
    • 從「方形」影像的中心切開圓
    • 縮放以符合SIZE參數值所定義的尺寸
    • 根據CONTRAST參數值調整對比度
    • 根據BRIGHTNESS參數值調整亮度
  4. 將轉換後的image置於具有透明背景的renditionImage的中心
  5. 將作品renditionImage寫入rendition.path,以便儲存回AEM作為資產轉譯。

此程式碼採用Jimp API來執行這些影像轉換。

asset compute工作人員必須同步完成其工作,並且必須在工作人員的renditionCallback完成之前將rendition.path完全寫回。 這要求使用await運算子同步執行非同步函式呼叫。 如果您不熟悉JavaScript非同步函式,以及如何讓這些函式以同步方式執行,請熟悉JavaScript的await運算子

已完成的工作程式index.js應如下所示:

'use strict';

const Jimp = require('jimp');
const { worker, SourceCorruptError, ClientError } = require('@adobe/asset-compute-sdk');
const fs = require('fs').promises;

exports.main = worker(async (source, rendition, params) => {
    const stats = await fs.stat(source.path);
    if (stats.size === 0) {
        throw new SourceCorruptError('source file is empty');
    }

    // Read/parse and validate parameters
    const SIZE = parseInt(rendition.instructions.size) || 800; 
    const CONTRAST = parseFloat(rendition.instructions.contrast) || 0;
    const BRIGHTNESS = parseFloat(rendition.instructions.brightness) || 0;

    if (SIZE <= 10 || SIZE >= 10000) {
        throw new RenditionInstructionsError("'size' must be between 10 and 1,0000");
    } else if (CONTRAST <= -1 || CONTRAST >= 1) {
        throw new RenditionInstructionsError("'contrast' must between -1 and 1");
    } else if (BRIGHTNESS <= -1 || BRIGHTNESS >= 1) {
        throw new RenditionInstructionsError("'brightness' must between -1 and 1");
    }

    // Create target rendition image 
    let renditionImage =  new Jimp(SIZE, SIZE, 0x0);

    // Read and perform transformations on the source binary image
    let image = await Jimp.read(source.path);

    // Crop a circle from the source asset, and then apply contrast and brightness
    image.crop(
            image.bitmap.width < image.bitmap.height ? 0 : (image.bitmap.width - image.bitmap.height) / 2,
            image.bitmap.width < image.bitmap.height ? (image.bitmap.height - image.bitmap.width) / 2 : 0,
            image.bitmap.width < image.bitmap.height ? image.bitmap.width : image.bitmap.height,
            image.bitmap.width < image.bitmap.height ? image.bitmap.width : image.bitmap.height
        )   
        .circle()
        .scaleToFit(SIZE, SIZE)
        .contrast(CONTRAST)
        .brightness(BRIGHTNESS);

    // Place the transformed image onto the transparent renditionImage to save as PNG
    renditionImage.composite(image, 0, 0)

    // Write the final transformed image to the asset's rendition
    await renditionImage.writeAsync(rendition.path);
});

// Custom error used for renditions.instructions parameter checking
class RenditionInstructionsError extends ClientError {
    constructor(message) {
        super(message, "RenditionInstructionsError", "rendition_instructions_error");
        Error.captureStackTrace(this, RenditionInstructionsError);
    }
}

運行工作

現在工作程式碼已完成,且先前已在manifest.yml中註冊並設定,您可以使用本機Asset compute開發工具執行該程式碼,以查看結果。

  1. 從Asset compute專案的根目錄

  2. 執行 aio app run

  3. 等待Asset compute開發工具在新視窗中開啟

  4. 在​選擇檔案……​下拉式清單,選取要處理的範例影像

    • 選取要作為源資產二進位檔的範例影像檔案
    • 如果尚未存在,請點選左側的​(+),然後上傳範例影像檔案,然後重新整理開發工具瀏覽器視窗
  5. "name": "rendition.png"更新為此工作器,以生成透明PNG。

    • 請注意,此「name」參數僅用於開發工具,不應依賴。
    {
        "renditions": [
            {
                "worker": "...",
                "name": "rendition.png"
            }
        ]
    }
    
  6. 點選​執行​並等待轉譯產生

  7. 轉譯​區段會預覽產生的轉譯。 點選轉譯預覽以下載完整轉譯

    預設PNG轉譯

使用參數運行工作器

透過處理設定檔設定傳入的參數,可在「Asset compute開發工具」中模擬,方法是在轉譯參數JSON上提供索引鍵/值配對。

警告

在本機開發期間,當從AEM以Cloud Service處理設定檔的形式傳入為字串時,可以使用各種資料類型來傳入值,因此請務必視需要剖析正確的資料類型。
例如,Jimp的crop(width, height)函式要求其參數為int的。如果未將parseInt(rendition.instructions.size)剖析為int,則對jimp.crop(SIZE, SIZE)的呼叫將會失敗,因為參數將是不相容的「String」類型。

我們的程式碼接受下列項目的參數:

  • size 會將轉譯的大小(高度和寬度定義為整數)
  • contrast 定義對比度調整,必須介於–1和1之間,作為浮點
  • brightness 定義亮度調整,必須介於–1和1之間,作為浮點

通過以下方式在工作器index.js中讀取:

  • const SIZE = parseInt(rendition.instructions.size) || 800
  • const CONTRAST = parseFloat(rendition.instructions.contrast) || 0
  • const BRIGHTNESS = parseFloat(rendition.instructions.brightness) || 0
  1. 更新轉譯參數以自訂大小、對比度和亮度。

    {
        "renditions": [
            {
                "worker": "...",
                "name": "rendition.png",
                "size": "450",
                "contrast": "0.30",
                "brightness": "0.15"
            }
        ]
    }
    
  2. 再次點選​執行

  3. 點選轉譯預覽以下載並檢閱產生的轉譯。 請注意其尺寸,以及與預設轉譯相比對比度和亮度的變更方式。

    參數化PNG轉譯

  4. 將其他影像上載到​源檔案​下拉清單中,然後嘗試使用不同參數運行工作器!

在Github上工作index.js

最終index.js可在Github上取得,網址為:

疑難排解

本頁內容