Asset Compute ワーカーの開発

Asset Compute ワーカーは、Asset Compute プロジェクトの中核となり、アセットで実行された作業を実行(編成)して新しいレンディションを作成するカスタム機能を提供します。

Asset Compute プロジェクトは、アセットの元のバイナリを名前付きのレンディションに変換せずにコピーする、単純なワーカーを自動生成します。このチュートリアルでは、このワーカーを変更して、より興味深いレンディションを作成し、Asset Compute ワーカーの力を説明します。

新しい水平方向の画像レンディションを生成するアセットコンピューティングワーカーを作成します。アセットレンディションの左右の空きスペースを、アセットのぼかしたバージョンで埋めます。最終的なレンディションの幅、高さ、ぼかしがパラメーター化されます。

Asset Compute ワーカー呼び出しの論理フロー

Asset Compute ワーカーは、Asset Compute SDK ワーカー API コントラクトを renditionCallback(...) 関数で実装します。この関数は、概念的には次のようなものになります。

  • 入力: AEM アセットのオリジナルバイナリパラメーターと処理プロファイルパラメーター
  • 出力: AEM Asset に追加する 1 つ以上のレンディション

Asset Compute ワーカーの論理フロー

  1. AEM オーサーサービスが Asset Compute ワーカーを呼び出し、アセットの​ (1a) ​オリジナルバイナリ(source パラメーター)と、(1b) ​処理プロファイルで定義されているパラメーター(rendition.instructions パラメーター)を渡します。

  2. Asset Compute SDK がカスタム Asset Compute メタデータワーカーの renditionCallback(...) 関数の実行を調整し、アセットのバイナリ​ (1a) ​および任意のパラメーター​ (1b) ​に基づいて、新しいバイナリレンディションを生成します。

    • このチュートリアルでは、レンディションが「処理中」に作成されます。つまり、ワーカーはレンディションを構成しますが、ソースバイナリを他の web サービス API に送信して、レンディションを生成することもできます。
  3. Asset Compute ワーカーが新しいレンディションのバイナリデータを rendition.path に保存します。

  4. rendition.path に書き込まれた XMP(XML)データが、Asset Compute SDK を通じて AEM オーサーサービスに転送され、そこで​ (4a) ​テキストレンディションとして公開され、(4b) ​アセットのメタデータノードに保存されます。

上の図は、Asset Compute 開発者に関する懸念事項と、Asset Compute ワーカー呼び出しへの論理的な流れを示しています。興味深い事に 内部での Asset Compute 実行の詳細が使用できますが、依存できるのはパブリック Asset Compute SDK 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() { ... }

ワーカー index.js を開く

自動生成された index.js

  1. VS Code で Asset Compute プロジェクトが開いていることを確認します。
  2. /actions/worker フォルダーに移動します。
  3. index.js ファイルを開きます。

これは、このチュートリアルで変更するワーカー JavaScript ファイルです。

サポートする npm モジュールのインストールと読み込み

Node.js ベースの Asset Compute プロジェクトは、堅牢な npm モジュールエコシステムです。npm モジュールを活用するには、まずモジュールを Asset Compute プロジェクトにインストールする必要があります。

このワーカーでは、jimp を利用して、Node.js コード内で直接レンディションイメージを作成および操作します。

WARNING
アセット操作用の npm モジュールの一部が、Asset Computeでサポートされているわけではありません。ImageMagick などのアプリケーションの存在に依存する npm モジュールや、その他の OS 依存ライブラリはサポートされていません。 JavaScript のみの npm モジュールの使用に制限することをお勧めします。
  1. Asset Compute プロジェクトのルートでコマンドラインを開きます(VS Code では、ターミナル/新しいターミナル)をクリックし、次のコマンドを実行します。

    code language-none
    $ npm install jimp
    
  2. jimp モジュールをワーカーコードに読み込んで、Jimp JavaScript オブジェクトによって使用できるようにします。
    ワーカーの index.js の上部にある require ディレクティブを更新して、jimp モジュールから Jimp オブジェクトを読み込みます。

    code language-javascript
    '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 as a Cloud Service オーサーサービスで定義された処理プロファイル経由で渡すことができるパラメーターを読み取ることができます。パラメーターは、rendition.instructions オブジェクトを介してワーカーに渡されます。

これらは、ワーカーコードの rendition.instructions.<parameterName> にアクセスすることで読み取ることができます。

ここでは、設定可能なレンディションの SIZEBRIGHTNESS、および CONTRAST を読み取り、処理プロファイル経由で何も指定されていない場合はデフォルト値を提供します。renditions.instructions は、AEM as a Cloud Service の処理プロファイルから呼び出されたときに文字列として渡されるので、ワーカーコード内で正しいデータ型に変換されるようにしてください。

'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
}

エラーをスロー errors

Asset Compute ワーカーで、エラーとなる状況が発生する場合があります。このような状況が発生した場合に、Adobe Asset Compute SDK が提供する事前定義済みのエラーのスイートをスローすることができます。特定のエラータイプが適用されない場合、GenericError を使用するか、特定のカスタム ClientErrors を定義できます。

レンディションの処理を開始する前に、このワーカーのコンテキストで、次のすべてのパラメーターが有効でサポートされていることを確認します。

  • SIZECONTRAST および BRIGHTNESS のレンディション命令パラメーターが有効であることを確認します。有効でない場合は、カスタムエラー 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. 構成された renditionImagerendition.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」パラメーターは開発ツールにのみ使用されるものなので、このパラメーターに依存しないでください。
    code language-json
    {
        "renditions": [
            {
                "worker": "...",
                "name": "rendition.png"
            }
        ]
    }
    
  6. 実行」をタップして、レンディションが生成されるのを待ちます。

  7. この「レンディション」セクションには、生成されたレンディションのプレビューが表示されます。レンディションのプレビューをタップして、レンディション全体をダウンロード

    デフォルトの PNG レンディション

パラメータを指定してワーカーを実行

処理プロファイル設定を通じて渡されるパラメーターは、レンディションパラメーター JSON でキーと値のペアとして指定することで、Asset Compute 開発ツールでシミュレートできます。

WARNING
ローカル開発時に、様々なデータ型を使用して値を渡すことができます。AEM as a Cloud Service 処理プロファイルから文字列として渡す場合は、必要に応じて正しいデータ型が解析されるようにします。
例えば、Jimp の crop(width, height) 関数は、int 型のパラメーターが必要です。parseInt(rendition.instructions.size) が int に解析されない場合、パラメーターが「文字列」型と互換性がないので、jimp.crop(SIZE, SIZE) の呼び出しは失敗します。

アドビのコードは次のパラメーターを受け入れます。

  • 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. レンディションパラメーターを更新して、サイズ、コントラストおよび明るさをカスタマイズします。

    code language-json
    {
        "renditions": [
            {
                "worker": "...",
                "name": "rendition.png",
                "size": "450",
                "contrast": "0.30",
                "brightness": "0.15"
            }
        ]
    }
    
  2. 実行」をもう一度タップします。

  3. レンディションのプレビューをタップし、生成されたレンディションをダウンロードして確認します。その寸法と、デフォルトのレンディションと比べてコントラストや明るさがどのように変更されたかに注目してください。

    パラメーター化された PNG レンディション

  4. 他の画像を​ ソースファイル ​ドロップダウンにアップロードし、別のパラメーターを指定してワーカーを実行してみてください。

Github のワーカー index.js

最終的な index.js は、GitHub で次の場所から入手できます。

トラブルシューティング

recommendation-more-help
4859a77c-7971-4ac9-8f5c-4260823c6f69