Custom Preview Generation with the Lambda Synchronizer
Besides the preview types OMN generates out of the box, you can configure your own custom preview types — for example a square thumbnail for a shop feed, or a watermarked rendition for a partner portal. Which custom previews an asset gets is decided by a script that OMN evaluates for every checked-in file.
With the Lambda synchronizer that script is JavaScript stored in an S3 bucket, and it no longer renders anything itself: it only declares which previews to produce and with which tool parameters. The rendering is done by the preview Lambdas.
|
The on-prem synchronizer still uses the mechanism described in Custom Preview Configuration: a local shell script that renders the preview file and prints the resulting OMN file type on stdout. That mechanism is unchanged. The Lambda synchronizer uses the JavaScript contract described here instead. A single OMN installation can hold both kinds of configuration side by side — see Migrating from a shell custom preview for how to convert an existing shell script. |
Prerequisites
Custom preview scripts require the Lambda synchronizer to be active and a bucket for the script objects to be configured. Both are part of the global OMN configuration:
synchronizer.mode=lambda
preview.lambda.custom.script.bucket=config-<account>
| Property | Purpose |
|---|---|
|
Selects the synchronizer implementation. |
|
The bucket the custom preview scripts are loaded from. A standard OMN deployment points it at the configuration bucket provisioned for the respective AWS account, whose name is derived from the account. |
So in a standard deployment you only have to upload your scripts into that bucket and reference them as described under Registering a custom preview type.
|
Unlike the queue URLs and the source and preview bucket names,
|
Registering a custom preview type
A custom preview type is a configuration record consisting of a name, a pointer to the script and the script type. Maintain the records in the configuration client under Custom Preview configuration, where each row has three fields:
| Field | Required | Description |
|---|---|---|
Preview name |
yes |
The custom preview type name, for example |
Command line / Path |
yes |
The pointer to the script. For is lambda the path of the script object relative to the root of the configuration bucket, for example |
Script type |
yes |
is lambda — the script is fetched from the configuration bucket and run by the Lambda synchronizer. is shell — the script is a local script run by the on-prem synchronizer. Existing rows were migrated to is shell. |
|
For is lambda, Command line / Path is always a path relative to the root of the
bucket named by Inside the configuration bucket the objects are organised under an account-specific path, so the value normally starts with that prefix: Bucket (preview.lambda.custom.script.bucket) config-<account> Command line / Path <account-prefix>/omn-web/scripts/rotated-image.js Resulting object s3://config-<account>/<account-prefix>/omn-web/scripts/rotated-image.js Ask your project’s AWS administrator for the prefix that applies to your account. |
The same records can be read and written through the configuration API, where the three
fields are called identifier, command and scriptType (LAMBDA / SHELL):
GET /api/config/v1/system/preview-configuration/custom PUT /api/config/v1/system/preview-configuration/custom
PUT replaces the complete list, so always send every custom preview type you want to
keep — types missing from the payload are deleted. A record sent without scriptType
defaults to SHELL.
[
{
"identifier" : "RotatedImage",
"command" : "<account-prefix>/omn-web/scripts/rotated-image.js",
"scriptType" : "LAMBDA"
}
]
The records are stored in the table SUI_EXTENDED_PREVIEW_TYPE, whose SCRIPT_TYPE
column was added in OMN 7.0 with the default value SHELL.
|
The table is shared with the on-prem synchronizer, and Script type decides which of the two runs a row: the on-prem synchronizer processes the is shell rows and skips the is lambda ones, the Lambda synchronizer does the opposite. The two execution paths are complements, so every configured row runs exactly once — on whichever synchronizer its type names. A system being migrated can therefore hold both kinds of row side by side. What does have to stay unique is the Preview name: two rows sharing it would write to the same retrieval name and overwrite each other’s preview. |
The script contract
The script is evaluated in the OMN WebApp once per configured custom preview type and per checked-in file. It receives all inputs in a single binding and returns its decision by assigning a global variable.
|
The script must assign its result to a global variable named |
Script input
All inputs are exposed as one binding named context. It is a Java map, so read it with
context.get('<key>') — dot access such as context.fileType is not available.
| Key | Type | Description |
|---|---|---|
|
number |
Database identity of the asset being checked in. |
|
string |
S3 bucket the source file is stored in. |
|
string |
S3 bucket the generated previews are written to. |
|
string |
Bucket-relative S3 object key of the source file. |
|
string |
Storage GUID pre-generated for this custom preview type. |
|
string |
Bucket-relative S3 object key the preview will be written to, without file extension — the extension is appended from the descriptor’s |
|
string |
The custom preview type being evaluated, i.e. the |
|
string |
OMN file type name of the source file, trimmed, e.g. |
|
number |
Page count of the source document. |
|
object |
The source file descriptor, for branching on more than the file type. |
|
On the check-in path |
Two further bindings are in scope: logger, an SLF4J logger writing to the per-script
category described under Applying changes, and entryFacade / api, the accessors
OMN provides to all its JavaScript scripts.
Script output
Assign to result either one descriptor object or an array of descriptor objects — one
per custom preview to generate. Each descriptor is dispatched as its own Lambda
invocation.
| Field | Required | Description |
|---|---|---|
|
yes |
Retrieval name of the generated preview and base name of the stored file. An entry without a non-blank |
|
yes |
OMN file type of the generated preview, e.g. |
|
no |
The tool arguments, passed verbatim to the tool of the Lambda the source is routed to — see Standard preview Lambdas and their tools. Omitting the field lets OMN compute the standard arguments for that tool; an empty array means "explicitly no arguments"; a non-empty array is used verbatim and then |
|
no |
Which standard preview type’s arguments to inherit when |
|
no |
|
When name equals context.get('identifier'), the pre-generated guid and
targetPath from the input are reused, so the stored preview ends up exactly where the
script was told. A descriptor with a different name gets a freshly generated GUID and
its own target path.
|
The following script generates one 80×80 JPEG thumbnail for every checked-in file:
result = [
{
name: context.get('identifier'),
fileType: "JPEG",
cliArgs: ["-resize", "80x80>", "-quality", "80"]
}
];
Returning an empty array is how a script opts out for a given file. The following script produces the preview for raster images only:
var RASTER = ["JPEG", "TIFF", "PNGf", "8BPS"];
var fileType = context.get('fileType');
if (RASTER.indexOf(fileType) < 0) {
logger.info("skipping custom preview for fileType {}", fileType);
result = [];
} else {
result = {
name: context.get('identifier'),
fileType: "JPEG",
cliArgs: ["-resize", "1200x1200>", "-quality", "85"]
};
}
Standard preview Lambdas and their tools
A standard descriptor is routed to one of the standard preview Lambdas. The script does not
choose: OMN derives it from the source file type. That also decides which tool the cliArgs
are passed to, so the arguments have to match it.
| Lambda | Source | cliArgs are arguments for |
|---|---|---|
|
Raster images, camera raw, Photoshop |
ImageMagick |
|
ImageMagick |
|
|
Office documents, text, HTML, SVG, EPS |
ImageMagick |
|
Video |
ffmpeg, producing WebM. |
|
Audio |
ffmpeg, producing MP3. |
|
InDesign, QuarkXPress |
Nothing — this Lambda renders with PDFBox and ignores |
Which file types belong to which Lambda is not hard-coded: it follows the preview plugin
registry, where the first entry whose supported file types contain the source type wins. A file
type no entry claims is routed to image, which rarely yields a usable preview — check the log
in that case.
The route also decides which tuning fields of Optional tuning fields have an effect:
iccProfileMode, sourceFileModifier and sourcePreArgs only for image, renderDpi for
pdf, office and prepress. Setting one of them on a descriptor routed elsewhere is not an
error, it simply has no effect.
Optional tuning fields
Each descriptor may additionally override the job settings OMN derived from the source
file. The convention is uniform: omit a field, or set it to null, to inherit the derived
value; set it to override that value for this custom preview only.
These are separate job fields rather than tool arguments, so — unlike toolParams and
basePreviewType — they keep applying even when the descriptor supplies cliArgs verbatim.
| Field | Type | Description |
|---|---|---|
|
number |
Render resolution in DPI for PDF and Office sources. |
|
string |
ICC profile chain to apply: |
|
string |
ImageMagick read modifier appended to the source path, e.g. |
|
array of string |
Tool arguments placed before the source path. An empty array means "explicitly none", omitting the field inherits. |
|
number |
Upper bound on the pages expanded from a multi-page source. |
|
number |
Maximum source width and height in pixels. A larger source is not processed. |
|
boolean |
Render only the first page of a page-based source — |
|
object |
Overrides merged into the parameter map OMN uses to compute |
The toolParams keys are the standard preview tool parameters of the matching processor type
— the same names previewPluginRegistry.xml uses, such as IMCoarseQuality or
Html5VideoCodec. The following script produces a WebM rendition, inheriting the standard
multimedia arguments but overriding two transcoding parameters:
result = {
name: context.get('identifier'),
fileType: "WEBM",
basePreviewType: "ORIGINAL_MULTIMEDIA",
toolParams: {
Html5VideoCodec: "libvpx",
Html5Crf: 30
}
};
Project-specific Lambdas
When a preview cannot be expressed as arguments to the standard tools, set lambda to
custom and name the Lambda that should generate it:
result = {
name: context.get('identifier'),
fileType: "JPEG",
lambda: "custom",
handler: "demo",
params: "text=OMN 7.0 demo;width=600;height=600"
};
| Field | Required | Description |
|---|---|---|
|
yes |
The logical handler key of the target Lambda. A |
|
no |
An arbitrary parameter string passed through to that Lambda verbatim. OMN never parses it; its format is defined by the Lambda itself. |
The key=value;key=value form above is not a convention OMN imposes — it is simply what the
demo handler happens to parse. Your own handler may expect JSON, a single value, or nothing
at all; whatever it defines, the script has to match it.
The script never names a deployed function directly. The orchestrator resolves the
handler key through the SSM parameter /omn/preview/custom-handlers, which holds a JSON
object mapping keys to function names:
{
"demo" : "omn-preview-custom-demo",
"watermark" : "omn-preview-custom-watermark"
}
The registry is cached by the orchestrator and refreshed every 60 seconds, so a new key takes effect within a minute without redeploying anything.
|
Using How to build, package, deploy and register such a function — including the job and result contracts it has to implement, and why the shared execution role usually needs no change — is described in Project-specific Custom Preview Lambda. |
Applying changes
-
Upload the changed script into the configuration bucket under the object key entered as the record’s Command line / Path. No restart is needed.
-
OMN caches the script bodies and verifies them against the S3 object’s ETag once per check-in batch, so a new version is picked up at the start of the next batch. A single check-in outside a batch — a REST upload, for example — still uses the previously loaded version.
-
If the bucket is temporarily unreachable, the last successfully loaded script body is used and an error is logged.
-
Changing a record through the configuration API takes effect the same way; only the script body is cached, not the record itself.
Every custom preview type logs to its own category, named after the type:
com.meylemueller.isy.sync.lambda.CustomPreviewScript.<identifier>
Each run writes the received context tagged [input] and the resulting descriptors tagged
[output]; [skip] and [error] mark a script that produced nothing. Anything the script
logs itself through logger appears in the same category. Set the parent category
com.meylemueller.isy.sync.lambda.CustomPreviewScript to DEBUG to trace all types at once.
Custom previews are supplementary: they are added to the previews an asset already has, and a
failure never breaks or delays the check-in. Every failure is logged and swallowed — an empty
script, an unusable result, a malformed descriptor, an exception, or a run exceeding the time
limit set by the JVM system property omn.customPreview.script.timeoutMs (30000 ms by
default). A single bad descriptor in an array does not discard the others.
When a custom preview does not appear, start at the [output] line of its own category.
If it lists the descriptor, the problem is downstream — look at LambdaPreviewDispatcher,
which reports descriptors it cannot dispatch. If it lists none, look at
CustomPreviewScriptRunner and RhinoValues, which report an unusable result and values
that could not be converted.
|
|
The time limit releases the check-in thread, but it cannot abort the script itself: a script caught in an endless loop keeps occupying a thread until the process is restarted. The script also runs unsandboxed inside the WebApp JVM and can reach Java classes and OMN beans. Treat it as trusted server code: keep it short, free of side effects, and limited to deciding which previews to generate. |
Migrating from a shell custom preview
A shell custom preview received its inputs as command-line placeholders, rendered the
file into a temporary directory and printed the resulting OMN file type on stdout. The
JavaScript equivalent receives the same inputs from context and returns a descriptor
instead of a file. The inputs map as follows:
| Shell placeholder | JavaScript equivalent |
|---|---|
{sourceFilePath} |
|
{fileType} |
|
{tempDirPath} |
|
{pageCount} |
|
{previewType} |
|
The file type printed on stdout |
The descriptor’s |
The sample shell script that rotates a file by 90°:
#!/bin/bash
convert "$1" -rotate 90 "$3/${5}.${2,,}"
echo $2
The same custom preview as a Lambda script — the rotation moves from an executed command
into the declared cliArgs, and the file type moves from stdout into fileType:
result = {
name: context.get('identifier'),
fileType: context.get('fileType'),
cliArgs: ["-rotate", "90"]
};
To migrate:
-
Rewrite each shell script as a JavaScript script that assigns
result, using the mapping above. -
Upload each script to the bucket configured as
preview.lambda.custom.script.bucket. -
For each custom preview type, replace Command line / Path with the script’s path relative to the root of the configuration bucket and set Script type to is lambda.
-
Leave or remove the old is shell row as you like — the Lambda synchronizer skips it either way. Keep it only while the on-prem synchronizer still runs against the same configuration, and give it a Preview name different from the Lambda row so the two do not overwrite each other’s preview.
-
Check in a test asset and verify the
[input]and[output]lines in the categorycom.meylemueller.isy.sync.lambda.CustomPreviewScript.<identifier>.
|
A Lambda custom preview script must not render anything. Everything the shell script did
with ImageMagick, Ghostscript or ffmpeg has to be expressed as |