AEM Cloud Service - Firefly Headless App with Asset Compute worker for creating High Res Renditions


In this post we'll create a Headless Firefly App and add a Asset Compute Worker for generating High Res JPEG renditions using the Photoshop I/O Apihttps://adobedocs.github.io/photoshop-api-docs/

Project Firefly & Asset Compute (still in beta) is a framework for building Micro Services Apps that run in Experience Cloud Runtime, you can learn more about it here - https://github.com/AdobeDocs/project-firefly/blob/master/overview/what_is.md

Thank you unknown coders for the code snippets...





Create Adobe I/O Project

1) Assuming your org was provisioned for Adobe Experience Cloud (plus Creative Cloud) products and you are a System Administrator in Adobe Developer Consolehttps://console.adobe.io, login using your adobe id and create a new project...



2) Add a new workspace for your dev firefly app...



3) In your IO workspace add the Asset Compute Service. In this step, you'll generate a private/public key pair for adding in the app configuration later....



4) Add the I/O Events and I/O Management API Services....



5) Add the Photoshop API Service (assuming your org was provisioned for using Creative Cloud Services). This is required for submitting a Create Rendition job from your micro services worker...



6) With all the necessary services added, here is how your project should look...




Create FireFly App

7) Install latest NodeJS (> v10) https://nodejs.org/en/download/ check the version  node -v

8) Install AIO command line aio-cli from https://github.com/adobe/aio-cli#usage 

                         npm install -g @adobe/aio-cli 

check version is > 3.7.0 

                         npm show @adobe/aio-cli version

9) Create your app eaem-ps-high-res-rend-worker

                         aio app init --asset-compute eaem-ps-high-res-rend-worker

10) Follow the prompts and provide app specific configuration...



11) Create the integration file ps-asset-compute-integration.yaml, add the private/public key, org specific values.... 



12) Provide the integration file path as value for parameter ASSET_COMPUTE_INTEGRATION_FILE_PATH in the app eaem-ps-high-res-rend-worker/.env file



13) Add your worker specific logic in eaem-ps-high-res-rend-worker\actions\eaem-ps-high-res-rend-worker\index.js for creating an Azure SAS (Shared Access Signature) token, generate presigned url for uploading the asset to shared storage accessible to PS API for storing the generated renditions....

'use strict';

const { worker, SourceCorruptError, GenericError } = require('@adobe/asset-compute-sdk');
const { downloadFile } = require("@adobe/httptransfer");
const fetch = require("@adobe/node-fetch-retry");
const fs = require('fs').promises;
const util = require("util");
const sleep = util.promisify(setTimeout);
const AzureStorage = require('azure-storage');

class EAEMPhotoshopService {
    constructor(auth) {
        this.auth = auth;
    }

    async createRendition(sourceURL, renditionStoreUrl){
        const URL = "https://image.adobe.io/pie/psdService/renditionCreate";

        const body = JSON.stringify({
            inputs: [{
                storage: "azure",
                href: sourceURL
            }],
            outputs: [{
                storage: "azure",
                href: renditionStoreUrl,
                type: "image/jpeg",
                "overwrite": true,
                "quality": 7 // generate a high quality rendition
            }]
        });

        const response = await fetch(URL, {
            method: "post",
            body,
            headers: this._headers()
        });

        if (response.ok) {
            const respJSON = await response.json();

            console.log("EAEM Job Status Url: " , respJSON._links.self.href);

            return respJSON._links.self.href;
        }else{
            console.log("EAEM error posting rendition request: " , response);
        }

        return null;
    }

    async checkStatus(statusUrl) {
        const response = await fetch(statusUrl, {
            headers: this._headers()
        });

        if (response.ok) {
            return (await response.json());
        } else {
            console.log("EAEM: Error checking status", response);
        }

        return null;
    }

    _getAzureReadWritePresignedUrl(instructions) {
        const blobService = AzureStorage.createBlobService(instructions["AZURE_STORAGE_ACCOUNT"],
                                    instructions["AZURE_STORAGE_KEY"]);

        const sharedAccessPolicy = {
            AccessPolicy: {
                Permissions: 'racwdl',
                Start: new Date(),
                Expiry: new Date(new Date().getTime() + 60 * 60000) // expire in 1 hour
            }
        };

        const psRenditionPath = "photoshop/" + instructions.userData.path;

        var token = blobService.generateSharedAccessSignature(instructions["AZURE_STORAGE_CONTAINER_NAME"], psRenditionPath,
                                    sharedAccessPolicy);

        return blobService.getUrl(instructions["AZURE_STORAGE_CONTAINER_NAME"], psRenditionPath, token);
    }

    _headers() {
        return {
            "Authorization": `Bearer ${this.auth.accessToken}`,
            "x-api-key": this.auth.clientId,
            "Content-Type": "application/json"
        };
    }
}

exports.main = worker(async (source, rendition, params, other) => {
    const stats = await fs.stat(source.path);

    if (stats.size === 0) {
        throw new SourceCorruptError('EAEM: Source file is empty');
    }

    console.log("EAEM Rendition Instructions for the app...", rendition.instructions);

    const SERVICE = new EAEMPhotoshopService(params.auth);
    const psRendUrl = SERVICE._getAzureReadWritePresignedUrl(rendition.instructions);
    const statusUrl = await SERVICE.createRendition(source.url, psRendUrl);

    if(!statusUrl){
        throw new GenericError("EAEM: Error submitting rendition request");
    }

    let retries = rendition.instructions["REND_GEN_STATUS_RETRIES"] || "5";

    retries = parseInt(retries);

    while (true) {
        if(retries-- <= 0){
            console.log("EAEM: Exhausted retries for rendition generation check, quitting now...");
            break;
        }

        const result = await SERVICE.checkStatus(statusUrl);

        if(!result || result.outputs[0].errors){
            console.log("EAEM: Rendition generation failed...", result.outputs[0].errors);
            throw new GenericError("EAEM: Error generating rendition");
        }else if(result.outputs[0].status == "succeeded" ){
            console.log("EAEM: Rendition generation successful, available at : " + rendition.target);
            break;
        }

        await sleep(1000);
    }

    await downloadFile(psRendUrl, rendition.path);
});

14) Install Docker Desktop https://www.docker.com/get-started. No further configuration required, you need docker running to run the worker tests....

15) If you are on windows, open command prompt, followed by the Git Bash shell (the app needs some linux specific commands for execution)



14) Execute the default unit tests aio app test



15) Run the app aio app run. This will start a local dev server and open the a browser window http://localhost:9000/ to test your worker... the worker input is provided as a JSON

{
    "renditions": [
        {
            "worker": "https://126827-eaemhighresrendition-sreekassetcomputeps.adobeioruntime.net/api/v1/web/EAEMHighResRendition-0.0.1/eaem-ps-high-res-rend-worker",
            "name": "rendition.jpg",
			"REND_GEN_STATUS_RETRIES": 15,
            "AZURE_STORAGE_ACCOUNT": "ACCOUNT",
            "AZURE_STORAGE_KEY": "KEY",
            "AZURE_STORAGE_CONTAINER_NAME" : "sreek"
        }
    ]
}



16)  When the dev server is running, to update app logic, use aio app deploy

17) Folders in Azure Cloud Storage used by the app for intermediate storage....





No comments:

Post a Comment