开发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参数)和​(1b)​处理配置文件中定义的任何参数(rendition.instructions参数)。

  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() { ... }

打开工作index.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或其他依赖于操作系统的库等应用程序的npm模块。 最好限制仅使用JavaScript npm模块。

  1. 打开Asset compute项目根目录中的命令行(可在VS代码中通过​Terminal > New Terminal​完成)并执行命令:

    $ 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上的Worker index.js

最终index.js可在Github上获取,网址为:

疑难解答

在此页面上