Process assets using media handlers and workflows processing-assets-using-media-handlers-and-workflows
Adobe Experience Manager Assets comes with a set of default workflows and media handlers to process assets. A workflow defines the tasks to be executed on the assets, then delegates the specific tasks to the media handlers, for example, thumbnail generation or metadata extraction.
A workflow can be configured to automatically execute when an asset of a particular MIME type is uploaded. The processing steps are defined in terms of a series of Assets media handlers. Experience Manager provides some built-in handlers, and extra ones can be either custom developed or defined by delegating the process to a command-line tool.
Media handlers are services in Assets that perform specific actions on assets. For example, when an MP3 audio file is uploaded into Experience Manager, a workflow triggers an MP3 handler that extracts the metadata and generates a thumbnail. Media handlers are used with workflows. Most common MIME types are supported within Experience Manager. Specific tasks can be performed on assets by either extending or creating workflows, extending or creating media handlers, or disabling and enabling media handlers.
Default media handlers default-media-handlers
The following media handlers are available within Assets and handle the most common MIME types:
- application/pdf
- application/illustrator
Important - An uploaded MP3 file is processed using a third-party library. The library calculates a non-accurate approximate length if the MP3 has variable bitrate (VBR).
- application/java-archive
- application/zip
- image/gif
- image/png
- application/photoshop
- image/jpeg
- image/tiff
- image/x-ms-bmp
- image/bmp
- application/vnd.openxmlformats-officedocument.wordprocessingml.document
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
- application/vnd.openxmlformats-officedocument.presentationml.presentation
All the handlers perform the following tasks:
- extracting all available metadata from the asset.
- creating a thumbnail image of an asset.
To view the active media handlers:
- In your browser, navigate to
https://localhost:4502/system/console/components
. - Click
com.day.cq.dam.core.impl.store.AssetStoreImpl
. - A list with all the active media handlers is displayed. For example:
Use media handlers in workflows to perform tasks on assets using-media-handlers-in-workflows-to-perform-tasks-on-assets
Media handlers are services that are used with workflows.
Experience Manager has some default workflows to process assets. To view them, open the Workflow console and click the Models tab: the workflow titles that start with Assets are the assets-specific ones.
Existing workflows can be extended and new ones can be created to process assets according to specific requirements.
The following example shows how to enhance the AEM Assets Synchronization workflow so that sub-assets are generated for all assets except PDF documents.
Disable or enable a media handler disabling-enabling-a-media-handler
The media handlers can be disabled or enabled through the Apache Felix Web Management Console. When the media handler is disabled, its tasks are not performed on the assets.
To enable/disable a media handler:
- In your browser, navigate to
https://<host>:<port>/system/console/components
. - Click Disable next to the name of the media handler. For example:
com.day.cq.dam.handler.standard.mp3.Mp3Handler
. - Refresh the page: an icon is displayed beside the media handler indicating it is disabled.
- To enable the media handler, click Enable next to the name of the media handler.
Create a media handler creating-a-new-media-handler
To support a new media type or to execute specific tasks on an asset, it is necessary to create a media handler. This section describes how to proceed.
Important classes and interfaces important-classes-and-interfaces
The best way to start an implementation is to inherit from a provided abstract implementation that takes care of most things and provides reasonable default behavior: the com.day.cq.dam.core.AbstractAssetHandler
class.
This class already provides an abstract service descriptor. So if you inherited from this class and use the maven-sling-plugin, make sure that you set the inherit flag to true
.
Implement the following methods:
extractMetadata()
: extracts all available metadata.getThumbnailImage()
: creates a thumbnail image out of the passed asset.getMimeTypes()
: returns the asset MIME types.
Here is an example template:
package my.own.stuff; /** * @scr.component inherit="true" * @scr.service */ public class MyMediaHandler extends com.day.cq.dam.core.AbstractAssetHandler { // implement the relevant parts }
The interface and classes include:
-
com.day.cq.dam.api.handler.AssetHandler
interface: This interface describes the service which adds support for specific MIME types. Adding a MIME type requires to implement this interface. The interface contains methods for importing and exporting the specific documents, for creating thumbnails and extracting metadata. -
com.day.cq.dam.core.AbstractAssetHandler
class: This class serves as basis for all other asset handler implementations and provides common used functionality. -
com.day.cq.dam.core.AbstractSubAssetHandler
class:- This class serves as basis for all other asset handler implementations and provides commonly used functionality plus commonly used functionality for subasset extraction.
- The best way to start an implementation is to inherit from a provided abstract implementation that takes care of most things and provides reasonable default behavior: the com.day.cq.dam.core.AbstractAssetHandler Class.
- This class already provides an abstract service descriptor. So if you inherited from this class and use the maven-sling-plugin, make sure that you set the inherit flag to true.
The following methods must be implemented:
extractMetadata()
: this method extracts all available metadata.getThumbnailImage()
: this method creates a thumbnail image out of the passed asset.getMimeTypes()
: this method returns the asset MIME types.
Here is an example template:
package my.own.stuff; /** * @scr.component inherit=“true” * @scr.service */ public class MyMediaHandler extends com.day.cq.dam.core.AbstractAssetHandler
The interface and classes include:
com.day.cq.dam.api.handler.AssetHandler
interface: This interface describes the service which adds support for specific MIME types. Adding a MIME type requires to implement this interface. The interface contains methods for importing and exporting the specific documents, for creating thumbnails and extracting metadata.com.day.cq.dam.core.AbstractAssetHandler
class: This class serves as basis for all other asset handler implementations and provides common used functionality.com.day.cq.dam.core.AbstractSubAssetHandler
class: This class serves as basis for all other asset handler implementations and provides common used functionality plus common used functionality for subasset extraction.
Example: create a specific text handler example-create-a-specific-text-handler
In this section, you create a specific Text Handler that generates thumbnails with a watermark.
Proceed as follows:
Refer to Development Tools to install and set up Eclipse with a Maven plugin and for setting up the dependencies that are needed for the Maven project.
After you perform the following procedure, when you upload a TXT file into Experience Manager, the file’s metadata are extracted and two thumbnails with a watermark are generated.
-
In Eclipse, create
myBundle
Maven project:-
In the Menu bar, click File > New > Other.
-
In the dialog box, expand the Maven folder, select the Maven project, then click Next.
-
Check the Create a simple project box and the Use default Workspace location box, then click Next.
-
Define a Maven project:
- Group Id:
com.day.cq5.myhandler
. - Artifact Id: myBundle.
- Name: My Experience Manager bundle.
- Description: This is my Experience Manager bundle.
- Group Id:
-
Click Finish.
-
-
Set the Java™ compiler to version 1.5:
-
Right-click the
myBundle
project, select Properties. -
Select Java™ Compiler and set following properties to 1.5:
- Compiler compliance level
- Generated .class files compatibility
- Source compatibility
-
Click OK. In the dialog window, click Yes.
-
-
Replace the code in the
pom.xml
file with the following code:code language-xml <project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <!-- ====================================================================== --> <!-- P A R E N T P R O J E C T D E S C R I P T I O N --> <!-- ====================================================================== --> <parent> <groupId>com.day.cq.dam</groupId> <artifactId>dam</artifactId> <version>5.2.14</version> <relativePath>../parent</relativePath> </parent> <!-- ====================================================================== --> <!-- P R O J E C T D E S C R I P T I O N --> <!-- ====================================================================== --> <groupId>com.day.cq5.myhandler</groupId> <artifactId>myBundle</artifactId> <name>My CQ5 bundle</name> <version>0.0.1-SNAPSHOT</version> <description>This is my CQ5 bundle</description> <packaging>bundle</packaging> <!-- ====================================================================== --> <!-- B U I L D D E F I N I T I O N --> <!-- ====================================================================== --> <build> <plugins> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-scr-plugin</artifactId> </plugin> <plugin> <groupId>org.apache.sling</groupId> <artifactId>maven-sling-plugin</artifactId> <configuration> <slingUrlSuffix>/libs/dam/install/</slingUrlSuffix> </configuration> </plugin> <plugin> <groupId>org.apache.felix</groupId> <artifactId>maven-bundle-plugin</artifactId> <extensions>true</extensions> <configuration> <instructions> <Bundle-Category>cq5</Bundle-Category> <Export-Package> com.day.cq5.myhandler </Export-Package> </instructions> </configuration> </plugin> </plugins> </build> <!-- ====================================================================== --> <!-- D E P E N D E N C I E S --> <!-- ====================================================================== --> <dependencies> <dependency> <groupId>com.day.cq.dam</groupId> <artifactId>cq-dam-api</artifactId> <version>5.2.10</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.day.cq.dam</groupId> <artifactId>cq-dam-core</artifactId> <version>5.2.10</version> <scope>provided</scope> </dependency> <dependency> <groupId>com.day.cq</groupId> <artifactId>cq-commons</artifactId> </dependency> <dependency> <groupId>javax.jcr</groupId> <artifactId>jcr</artifactId> </dependency> <dependency> <groupId>org.apache.felix</groupId> <artifactId>org.osgi.compendium</artifactId> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> </dependency> <dependency> <groupId>commons-lang</groupId> <artifactId>commons-lang</artifactId> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> </dependency> <dependency> <groupId>com.day.commons</groupId> <artifactId>day-commons-gfx</artifactId> </dependency> <dependency> <groupId>com.day.commons</groupId> <artifactId>day-commons-text</artifactId> </dependency> <dependency> <groupId>com.day.cq.workflow</groupId> <artifactId>cq-workflow-api</artifactId> </dependency> <dependency> <groupId>com.day.cq.wcm</groupId> <artifactId>cq-wcm-foundation</artifactId> <version>5.2.22</version> </dependency> </dependencies>
-
Create the package
com.day.cq5.myhandler
that contains the Java™ classes undermyBundle/src/main/java
:- Under myBundle, right-click
src/main/java
, select New, then Package. - Name it
com.day.cq5.myhandler
and click Finish.
- Under myBundle, right-click
-
Create the Java™ class
MyHandler
:- In Eclipse, under
myBundle/src/main/java
, right-click thecom.day.cq5.myhandler
package. Select New, then Class. - In the dialog window, name the Java™ class
MyHandler
and click Finish. Eclipse creates and opens the fileMyHandler.java
. - In
MyHandler.java
, replace the existing code with the following and then save the changes:
code language-java package com.day.cq5.myhandler; import java.awt.Color; import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import javax.jcr.Node; import javax.jcr.RepositoryException; import javax.jcr.Session; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.day.cq.dam.api.metadata.ExtractedMetadata; import com.day.cq.dam.core.AbstractAssetHandler; import com.day.image.Font; import com.day.image.Layer; import com.day.cq.wcm.foundation.ImageHelper; /** * The <code>MyHandler</code> can extract text files * @scr.component inherit="true" immediate="true" metatype="false" * @scr.service * **/ public class MyHandler extends AbstractAssetHandler { /** * Logger instance for this class. */ private static final Logger log = LoggerFactory.getLogger(MyHandler.class); /** * Music icon margin */ private static final int MARGIN = 10; /** * @see com.day.cq.dam.api.handler.AssetHandler#getMimeTypes() */ public String[] getMimeTypes() { return new String[] {"text/plain"}; } public ExtractedMetadata extractMetadata(Node asset) { ExtractedMetadata extractedMetadata = new ExtractedMetadata(); InputStream data = getInputStream(asset); try { // read text data InputStreamReader reader = new InputStreamReader(data); char[] buffer = new char[4096]; String text = ""; while (reader.read(buffer) != -1) { text += new String(buffer); } reader.close(); long wordCount = this.wordCount(text); extractedMetadata.setProperty("text", text); extractedMetadata.setMetaDataProperty("Word Count",wordCount); setMimetype(extractedMetadata, asset); } catch (Throwable t) { log.error("handling error: " + t.toString(), t); } finally { IOUtils.closeQuietly(data); } return extractedMetadata; } // ----------------------< helpers >---------------------------------------- protected BufferedImage getThumbnailImage(Node node) { ExtractedMetadata metadata = extractMetadata(node); final String text = (String) metadata.getProperty("text"); // create text layer final Layer layer = new Layer(500, 600, Color.WHITE); layer.setPaint(Color.black); Font font = new Font("Arial", 12); String displayText = this.getDisplayText(text, 600, 12); if(displayText!=null && displayText.length() > 0) { // commons-gfx Font class would throw IllegalArgumentException on empty or null text layer.drawText(10, 10, 500, 600, displayText, font, Font.ALIGN_LEFT, 0, 0); } // create watermark and merge with text layer Layer watermarkLayer; try { final Session session = node.getSession(); watermarkLayer = ImageHelper.createLayer(session, "/content/dam/we-retail/en/products/apparel/gloves/Gloves.jpg"); watermarkLayer.setX(MARGIN); watermarkLayer.setY(MARGIN); layer.merge(watermarkLayer); } catch (RepositoryException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } layer.crop(new Rectangle(510, 600)); return layer.getImage(); } // ---------------< private >----------------------------------------------- /** * This method cuts lines if the text file is too long.. * * @param text * * text to check * * @param height * * text box height (px) * * @param fontheight * * font height (px) * * @return the text which will fit into the box */ private String getDisplayText(String text, int height, int fontheight) { String trimmedText = text.trim(); int numOfLines = height / fontheight; String lines[] = trimmedText.split("\n"); if (lines.length <= numOfLines) { return trimmedText; } else { String cuttetText = ""; for (int i = 0; i < numOfLines; i++) { cuttetText += lines[i] + "\n"; } return cuttetText; } } /** * * This method counts the number of words in a string * * @param text the String whose words would like to be counted * * @return the number of words in the string * */ private long wordCount(String text) { // We need to keep track of the last character, if we have two whitespaces in a row we do not want to double count. // The starting of the document is always a whitespace. boolean prevWhiteSpace = true; boolean currentWhiteSpace = true; char c; long numwords = 0; int j = text.length(); int i = 0; while (i < j) { c = text.charAt(i++); if (c == 0) { break; } currentWhiteSpace = Character.isWhitespace(c); if (currentWhiteSpace && !prevWhiteSpace) { numwords++; } prevWhiteSpace = currentWhiteSpace; } // If we do not end with a whitespace then we need to add one extra word. if (!currentWhiteSpace) { numwords++; } return numwords; } }
- In Eclipse, under
-
Compile the Java™ class and create the bundle:
- Right-click the
myBundle
project, select Run As, then Maven Install. - The bundle
myBundle-0.0.1-SNAPSHOT.jar
(containing the compiled class) is created undermyBundle/target
.
- Right-click the
-
In CRX explorer, create a node under
/apps/myApp
. Name =install
, Type =nt:folder
. -
Copy the bundle
myBundle-0.0.1-SNAPSHOT.jar
and store it under/apps/myApp/install
(for example, with WebDAV). The new text handler is now active in Experience Manager. -
In your browser, open the Apache Felix Web Management Console. Select the Components tab and disable the default text handler
com.day.cq.dam.core.impl.handler.TextHandler
.
Command-Line based media handler command-line-based-media-handler
Experience Manager enables you to run any command-line tool within a workflow to convert assets (such as ImageMagick) and to add the new rendition to the asset. Only install the command-line tool on the disk hosting the Experience Manager server, and to add and configure a process step to the workflow. The invoked process, called CommandLineProcess
, also filters according to specific MIME types and to create multiple thumbnails based on the new rendition.
The following conversions can be automatically run and stored within Assets:
- EPS and AI transformation using ImageMagick and Ghostscript.
- FLV video transcoding using FFmpeg.
- MP3 encoding using LAME.
- Audio processing using SOX.
The CommandLineProcess
process performs the following operations in the listed order:
- Filters the file according to specific MIME types, if specified.
- Creates a temporary directory on the disk hosting the Experience Manager server.
- Streams the original file to the temporary directory.
- Executes the command defined by the arguments of the step. The command is being executed within the temporary directory with the permissions of the user running Experience Manager.
- Streams the result back into the rendition folder of the Experience Manager server.
- Deletes the temporary directory.
- Creates thumbnails based on those renditions, if specified. The number and the dimensions of the thumbnails are defined by the arguments of the step.
An example using ImageMagick an-example-using-imagemagick
The following example shows you how to set up the command-line process step so that every time an asset with the miMIME e-type GIF or TIFF is added to /content/dam
on the Experience Manager server, a flipped image of the original is created. Three more thumbnails 140x100, 48x48, and 10x250 are also created.
To do this, use ImageMagick. ImageMagick is a free, command-line software used to create, edit, and compose bitmap images.
Install ImageMagick on the disk hosting the Experience Manager server:
-
Install ImageMagick: See ImageMagick documentation.
-
Set up the tool so that one the command line, you can run
convert
. -
To see if the tool is installed properly, run the following command
convert -h
on the command line.It displays a help screen with all the possible options of the convert tool.
note note NOTE In some versions of Windows, the convert command may fail to run because it conflicts with the native convert utility that is part of Windows installation. In this case, mention the complete path for the ImageMagick software used to convert image files to thumbnails. For example, "C:\Program Files\ImageMagick-6.8.9-Q16\convert.exe" -define jpeg:size=319x319 ${filename} -thumbnail 319x319 cq5dam.thumbnail.319.319.png
. -
To see if the tool runs properly, add a JPG image to the working directory and run the command convert
<image-name>.jpg -flip <image-name>-flipped.jpg
on the command line. A flipped image is added to the directory. Then, add the command-line process step to the DAM Update Asset workflow. -
Go to the Workflow console.
-
In the Models tab, edit the DAM Update Asset model.
-
Change the Arguments of the Web enabled rendition step to:
mime:image/gif,mime:image/tiff,tn:140:100,tn:48:48,tn:10:250,cmd:convert ${directory}/${filename} -flip ${directory}/${basename}.flipped.jpg
. -
Save the workflow.
To test the modified workflow, add an asset to /content/dam
.
- In the file system, get a TIFF image of your choice. Rename it to
myImage.tiff
and copy it to/content/dam
, for example, by using WebDAV. - Go to the CQ5 DAM console, for example,
https://localhost:4502/libs/wcm/core/content/damadmin.html
. - Open the asset myImage.tiff and verify that the flipped image and the three thumbnails have been created.
Configure the CommandLineProcess process step configuring-the-commandlineprocess-process-step
This section describes how to set the Process Arguments of the CommandLineProcess.
Separate the values of the Process Arguments using comma and do not start it with a whitespace.
Several MIME types can be defined.
Several thumbnails can be defined.
The following variables can be used to create the command:
${filename}
: name of the input file, for example, original.jpg${file}
: full path name of the input file, for example, /tmp/cqdam0816.tmp/original.jpg
${directory}
: directory of the input file, for example, /tmp/cqdam0816.tmp
${basename}
: name of the input file without its extension, for example, original${extension}
: extension of the input file, for example, JPG.For example, if ImageMagick is installed on the disk hosting the Experience Manager server and if you create a process step using CommandLineProcess as Implementation and the following values as Process Arguments:
mime:image/gif,mime:image/tiff,tn:140:100,tn:48:48,tn:10:250,cmd:convert ${directory}/${filename} -flip ${directory}/${basename}.flipped.jpg
Then, when the workflow runs, the step only applies to assets that have image/gif
or mime:image/tiff
as mime-types
. It creates a flipped image of the original, converts it into JPG, and creates three thumbnails with the dimensions 140x100, 48x48, and 10x250.
Use the following Process Arguments to create the three standard thumbnails using ImageMagick:
mime:image/tiff,mime:image/png,mime:image/bmp,mime:image/gif,mime:image/jpeg,cmd:convert ${filename} -define jpeg:size=319x319 -thumbnail "319x319>" -background transparent -gravity center -extent 319x319 -write png:cq5dam.thumbnail.319.319.png -thumbnail "140x100>" -background transparent -gravity center -extent 140x100 -write cq5dam.thumbnail.140.100.png -thumbnail "48x48>" -background transparent -gravity center -extent 48x48 cq5dam.thumbnail.48.48.png
Use the following Process Arguments to create the web-enabled rendition using ImageMagick:
mime:image/tiff,mime:image/png,mime:image/bmp,mime:image/gif,mime:image/jpeg,cmd:convert ${filename} -define jpeg:size=1280x1280 -thumbnail "1280x1280>" cq5dam.web.1280.1280.jpeg
dam:Asset
) or descendants of an asset.