Squarebox

CatDV JavaScript Support

You can use JavaScript as a simple and powerful mechanism to extend and customise the functionality of CatDV in a number of ways:

A calculated user-defined field can be based on evaluating a JavaScript expression. (A calculated expression can also be used in many other situations in both CatDV and the worker where text is specified, for example to burn in text when exporting a movie, to calculate the path in a worker processing step, when performing a bulk edit command, etc.)

A UI script is executed within the desktop client and web client and can respond to fields being edited, buttons being clicked, or clips being loaded in order to customise the appearance of fields or values shown in a picklist, to show or hide details panel tabs based on the value of certain clip fields, to perform validation, and so on.

An ‘Execute JavaScript’ processing step in the Pegasus Worker can be used as an alternative to executing a batch or shell script through the command line or using a tool like Node.js.

Similarly, you can include an ‘Execute JavaScript’ processing step in a Pegasus custom action.

A worker plugin provides broadly similar functionality to executing a JavaScript processing step but encapsulates the script, plus a user interface to configure it and any additional resources or libraries, as a single deployable extension that appears as a named processing step such as "Upload to YouTube", similar to other built in processing steps

There is a lot of commonality and overlap between the API provided in each of these situations but there are also some differences, highlighted later in this document.

Please note that JavaScript support is not an end user feature and is provided for the use of the Square Box professional services team or by approved resellers and systems integrators who have completed an advanced CatDV U training course. The features described below are subject to change without notice. Technical support on JavaScript will normally constitute chargeable work.

Also, please note that the use of JavaScript worker plugins, other than in development mode, will normally require a deployment license. Please enquire before relying on the use of JavaScript in your worker actions!

Object model

To allow the JavaScript script to interact with the rest of CatDV the host environment provides a simple object model.

· In most cases you will have a clip available, ie. the current selected clip which the command applies to. This is normally passed in to the current function as a variable called ‘clip’ and has a number of fields and methods available, described below.

· In some cases the command is applied to a collection of clips and you are passed an array ‘clips’

· Other objects represent clip event markers, a catalog, or a sequence

· The global variable ‘CatDV’ provides access to a number of utility functions

Accessing clip fields

Once you have a reference to a clip you can read clip fields in various ways:

clip.get("NM1") – using a legacy attribute id

clip.get("@FNumber") – access a media metadata field using a legacy attribute id

clip.get("clip[my.user.1]") – using canonical name of a user field (same as REST API)

clip.name – directly access a clip field as if it was a member using its canonical name (same as REST API)

clip.fields["my.user.1"] – directly access a user-defined clip field by its id, but now you’re accessing a javascript associative array clip.fields by looking up a string so you need to quote the id.

clip.media.fields["FNumber"] – access a media metadata field via an associative array so you need to quote the id

clip.media.audioFormat – access a built in media field

clip.catalog.fields["my.catalog.field"] – access a custom catalog field

clip.contents() – if the clip is a metaclip or summary clip this will give you the constituent clips

clip.markers – access to the markers on a clip, where each marker has fields 'name', 'in', 'out', 'description' and 'category', eg. clip.markers[0].name

clip.poster – access to the jpeg bytes of a poster thumbnail as a byte array. When setting the poster the image data can be provided: as a byte array, as a filename (to load the thumbnail from an image file), or as a timecode value (to generate a thumbnail from the movie at that frame).

Note that in the past it was possible to access user-defined clips using a shorter notation like clip[‘myField’] or even clip.myField if the name didn’t contain special characters but this was an implementation side-effect and wasn’t recommended, and is no longer supported unless specifically enabled with legacy.identifiers=true. Instead you should update your scripts to use clip.fields[‘myField’] or clip.get(‘clip[myField]’). You should also write ${clip[myField]} instead of ${myField} in variable expressions.

As well as reading clip fields you can modify them, eg. clip.set("NM1", "New name") or clip.name = "New name".

Note that multi value fields (multi checkbox, multi linked hierarchy, etc.) store the value as a newline separated list of values, so if you want the values as an array use clip.fields['my.multi.field'].split('\n'). Note also that a hierarchy field stores the full value like "France/Paris" even though just "Paris" is shown in the user interface.

To look up the name of a field you can use tool tip text in the details panel, refer to the REST API documentation, or consult the Professional Services team.

Variable expressions and calculated fields

In the desktop client and in the Worker Node there are lots of situations where a textual parameter needs to be specified, for example when transcoding a movie with burnt-in text, or when specifying a file or directory in a scripted worker action or Pegasus custom action, and so on.

For some time CatDV and the worker have allowed variable expressions such as “Title: ${NM1}” or “$p{s/.mov$//}” to be used in these situations, where the expression is evaluated based on fields from the current clip, or variables like $p and $i that have been set based on the current file.

Variable expressions are also used when defining the value of a user-defined calculated field.

When accessing clip fields in a variable expression, originally only old-style attribute ids like ${NM1}, ${U3}, ${MF}, ${@FNumber} were supported, but this has since been extended to also support more mnemonic attribute ids similar to those use in the server REST API, like ${clip.name}, ${custom.clip.field.id} for user-defined fields, and so on. To see the attribute id for a field look at the tool tip over fields in the details panel, in the field chooser when customising views, and in Preferences > Field Definitions > Built-In Fields.

As well as substituting the value of a clip field or path variable, with an optional regular expression to modify the value, it is possible to use a javascript expression instead. To do this, the expression must start with “javascript:” (or “js:” as a more compact short cut) and return a value, eg. “javascript:2+3” would evaluate to “5”. When evaluating a javascript expression the $() function can be used to access variables like $p, eg. js:$(‘$p’), and also clip fields using old style attribute ids.

Taken together his means there are quite a few different ways to access clip fields in a variable expression:

· ${NM1}, ${U1}, ${@MXF UMID} – using old attribute id

· ${clip.name}, ${my.field.id}, ${media[MXF UMID]}, ${importSource[UnixFileMode]}, ${catalog[my.project.id]} – new attribute id (though strictly speaking there is no attribute in the desktop client for custom catalog fields, which means they can’t directly be shown in a view definition or details panel layout, but can be accessed via a variable expression or calculated field)

· js:clip.name, js:clip.fields['my.field.id'], js:clip.media.fields['MXF UMID'], js:clip.catalog.fields['my.project.id'] – using clip object

· js:$('NM1'}, js:$('clip.name'), js:$('my.field.id'), js:$('media[MXF UMID]') – using the $() function

· js:clip.get(‘NM1’) – using get() function

Most of these should work equally in the desktop client, worker, or web interface, though the internal implementations are different between the desktop/worker and web interface, so it’s worth testing on both platforms. Which to use will largely depend on the complexity of the expression and personal preferences.

If you want to access a basic variable expression or regular expression from javascript from within a worker script or Pegasus custom action then you can use expand(expr), or the underlying CatDV.expandVariables(expr, params, clip) function if you want to provide parameters like $g in a map or apply it to a different clip.

UI script

The UI script is available when logged on to the Enterprise server. It is stored on the server in the settings table so that it is loaded when the user logs on. The current implementation uses a single script across all production groups. The script needs to be installed by the Professional Services team.

The UI script has handlers that respond to various events, such as creating a field in the details panel when the user interface is loaded, responding to changes to a clip, etc.

· catdv_onCreateField(group, fieldId) – return an object to override a field definition (see below)

· catdv_onLoad(clip) – called when a clip is selected, for example to show or hide details panel tabs or change pick list values depending on the value of a field

· catdv_onUpdate(clip, fieldId, interim) – called after a clip is edited, with similar capabilities to the onLoad handler. In the web client, this is called for every amendment (potentially every keypress), with interim=true; test for interim=false if you only want to process updates when a field loses focus. (In the desktop client, interim is always false.)

· catdv_onImport(clips) – called after clips are imported into the catalog, allowing other fields to be populated automatically (but note that this function isn't guaranteed always to be called, for example if the user isn't logged on at the time, if a clip is created by the worker, etc.)

· catdv_onValidateClip(clip) – called when a clip is about to be saved or another clip is being loaded into the details panel. This function can return an error message that is displayed if the clip isn't valid (for example, to say that some fields are missing or contain inconsistent values) but this is only advisory and isn't guaranteed to prevent the changes from being saved

· catdv_onAction(type, fieldId, value) – unlike onUpdate, this is called before an action is performed, for example a select button is pressed, a value is chosen from a drop down pick list, or a toolbar button or menu command is invoked. This method can return true to silently intercept and reject a button click so it isn't performed, or a message that is displayed explaining why the action isn't valid. Return null or false to let the action proceed normally. The type argument is one of "MenuItem", "CheckBox", "ComboBox", "RadioButton", "TextField" or "Button".

· catdv_onMarkerSelected(clip, marker) – this function is called when the movie play head moves over an event marker (or a marker is selected from the drop down in the movie controller). The method is also called (with marker set to null) when the play head leaves the marker again. For range markers the specified range of the marker is used, for event markers the marker stays active for a second or two after the playhead moves over the marker.

· catdv_onCreateMarker(clip, marker) – this function is called when the user creates a new marker, just before the marker dialog is displayed, giving the UI script the chance to populate default values for the fields. If required the function can return an error message to prevent the marker being created.

· catdv_onNotification(notifications) – this function is called whenever one or more notifications are received from the server

· catdv_onConfirmDownload(clips, confirm) – this function is called from the web client when the user attempts to download a file and could display a copyright message or perform other validation before calling confirm() to permit the download to proceed, eg.

function catdv_onConfirmDownload(clips, confirm)
{
  CatDV.askOptionsAsync(“Confirm”, “This is copyrighted!”, ["Decline", "Accept"], function(answer)
  {
      if (answer=="Accept") confirm();
  });
}

You don't need to provide all the event handler functions, only ones you are interested in responding to. If a function throws an error for any reason it is automatically disabled until the script is reloaded.

Certain handlers relate to particular fields being edited, in which case the canonical field id (eg. "clip.name") is passed in to the method.

Uses of UI scripts

One common use of a UI script is to override the definition of a field, either based on the current production group (if the same field has different definitions in different groups for example), or if the same list of pick list values is shared between fields.

The onCreateField handler can return the following properties to change the field definition: label – the displayed field label, readOnly – set the field read only or not, twoColumns – set the field to be displayed in two details panel columns, multiline – set the field to be displayed over multiple rows, hideIfBlank – suppress the field unless it has a value, visible – show or hide the field, fieldID – inherit all the values from another field definition (including the label of that field), basedOn – inherit all the values from another field definition except for the field label.

Another common use of UI scripts is to show or hide certain tabs dynamically based on the current clip, for example to show different logging fields depending on the type of asset. This is done using the onLoad and onUpdate handlers. These can also be used to populate pick list values depending on the current clip to provide functionality similar to linked hierarchy fields, or to load pick lists from another source.

Other advanced capabilities include implementing action buttons, highlighting fields depending on the current marker, and more.

Worker plugin extensions

A worker extension can be installed by placing a .catdv Worker Plugin Bundle in the appropriate extensions directory:

Mac OS X: /Library/Application Support/Square Box/Extensions

Windows: %ProgramData%\Square Box\Extensions

Linux: /usr/local/catdvWorker/extensions

Each extension function is defined by a .jx (or .jxe) JavaScript file. This file should set various fields on the global 'plugin' object that is passed in when the script is loaded to describe the plugin:

plugin.name = "My plugin";
plugin.description = "This is a test plugin";
plugin.version = “1.0.1”;
plugin.batchMode = false;
plugin.section = “File actions”;
plugin.licenseKey = “My Plugin”;
plugin.fields = [ … ];
plugin.onLoad = function(params) { return fields; }
plugin.onValidate = function(params) {
  if (!params[‘xyz]’) return “Missing xyz”;
  else return null;
}
plugin.method = function(clip, clips, params, variables) { … }

The 'batchMode' flag should be set to true if the extension can be used in bulk query, quick folder scan, and batch file watch actions, in which case the method is invoked just once to process all the matching files or clips in one go. (Normally the processing step is applied to each clip or file individually in turn and isn't available in bulk processing watch actions.) This will also enable the extension to be used in a scheduled task, in which case ‘clips’ will be null.

The plugin 'fields' parameter is a list of fields that will be displayed when configuring the processing step, for example:

plugin.fields = [ 
{ id: "method", label: "Method:", type: "radio", values: [ "Remove spaces", "Remove punctuation", "Remove both" ], default: 0 },
	{ id: "strict", label: "Strict:", type: "checkbox", value: "Avoid anything nasty", default: true },
	{ id: "maxlen", label: "Maximum length:", type: "number", default: 32 },
	{ id: "pattern", label: "Pattern:", type: "text" }
];

This would display four fields in the worker configuration editor, and the values entered can then be accessed as params['method'] etc. when the processing step is performed.

'id' is the identifier used to access the value of a parameter, 'label' is the label shown in the configuration editor, 'type' is one of "radio", "checkbox", "combo", "multiline", "password", "fieldChooser", "openDir", "openFile", "saveDir", "saveFile", or "text". 'values' specifies the values for a radio button or combo box field, and optionally for a checkbox field (a checkbox field can have a single value or multiple values). 'default' is an optional initial default value.

You can optionally specify an onLoad() function instead of a fixed fields array to dynamically define the fields to show when editing the processing step. You can also specify an onValidate() function that is called after editing the processing step and return a message if there is a validation error.

When the processing step is performed the method is called with both the main clip and a list of clips. In normal use with a watch folder or server query action there will just be a single clip (which could be a metaclip), but if you import a file with scene detection enabled, or import a Final Cut XML file or similar, then importing a single file could result in multiple clips. You would also use the 'clips' argument in bulk processing steps.

The 'params' argument gives access to parameters entered when the processing step is configured. Just as with other types of processing steps, any variables in the parameters would already have been expanded by the time the function is called, but you can also directly access any worker variable, for example variables['$g'].

If the function returns a string the message is logged in the task summary but other than that the return value has no effect. To indicate a fatal error which will cause the watch action task to fail you should throw an exception, eg. throw "Failed to copy file".

Approved plugins need to be encrypted and will need a license code to fully function but can be tested in development mode (which runs one task at a time) without needing a license. To allow several plugins to share the same license key you can specify plugin.licenseKey. You can also set this to “none” if the plugin doesn’t need to be licensed (though it still needs to be encrypted).

Worker extension bundles

During development create a subdirectory in the extensions directory and place .jx file or files in this (it’s possible for one plugin bundle to implement more than one extension function). You can also include other scripts and resources that are used by those extension(s) in that directory, eg.

myplugin/archive.jx
myplugin/restore.jx
myplugin/sharedLibrary.js
myplugin/image.png

In development mode you can directly run the extensions from these .jx files.

To deploy them the .jx files need to be encrypted (and the other files can be encrypted too if desired) This needs to be done by the CatDV Professional Services team:

myplugin/archive.jxe
myplugin/restore.jxe
myplugin/sharedLibrary.jse
myplugin/image.png.enc

Finally, package them up “jar cf myplugin.catdv myplugin” to create an installable plugin bundle. The ‘myplugin.catdv’ file can then be dropped onto a worker node editor and the user will be prompted to install it.

While developing plugins take care not to leave both the myplugin directory and myplugin.catdv file, or both .jx and .jxe files, alongside each other in the extensions directory otherwise you will get duplicate definitions when the extensions are loaded.

When an extension runs the CatDV.readFile() and CatDV.requires() functions will automatically read a file from the bundle, and decrypt is as required, eg.

var image = CatDV.readFile(“myplugin/image.png”);

The extensions .jxe (encrypted .jx extension), .jse (encrypted .js script) and .enc (other encrypted resources) are recognised for encrypted files.

For final distribution you would normally combine myplugin.catdv and a ReadMe.txt file as a ZIP archive myplugin1.0.1.zip whose name includes the version number, but not put a version number on the myplugin.catdv file.

Pegasus custom actions & Pegasus Worker scripts

If you use CatDV Pegasus you can include a Javascript processing step when defining a custom action, giving you full access to all the capabilities provided by the CatDV Javascript API:

function perform(clip, clips, variables)
{
}

Similarly, with Pegasus Worker, you can define a Javascript processing step. This can either directly accesses the variables ‘clip’, ‘clips’ and ‘variables’ or define a function with the same prototype as above, in which case that function will be called.

In both cases global functions defined on the global ‘CatDV’ object can be called, as well as a function called expand() which is provided as a convenience to access CatDV variable expressions, eg.

var path = expand(“$f{s,/[^/]+,,}”);

Global functions

The ‘CatDV’ object provides access to a number of utility functions and the current context in which the script is executing.

The following methods give basic information about the environment in which the script is running:

· CatDV.isDesktopClient() – returns true in the CatDV desktop client, false elsewhere

· CatDV.isWebClient() – returns true when running in the web interface

· CatDV.isWorkerNode() – returns true in worker scripts

· CatDV.getSoftwareVersion() – return current software eg. "CatDV Pegasus 12.1b6"

· CatDV.getUserName(), getUserRole() – return the current user name (and permissions role)

· CatDV.getProperty(name) – return additional information, for example 'serverVersion', 'groupName', 'currentTab', 'catalogName', 'ffmpegPath', and java system properties like 'user.dir' or 'os.name' are provided in the desktop client

· CatDV.getScriptFile() – return the path to the current script file if this is a worker extension

These methods give access to useful lists of data:

· getAllGroups() – returns an array of group names

· getMyGroups() – returns an array of group names that the user is a member of

· getAllUsers() – return a list of all user names on the system

· getPicklistValuesAsync(fieldId, callback) – return the picklist values for a field as an array, passing the data to a callback function once it is available

· getPicklistValues(fieldId) – return the picklist values immediately

These utility methods relate to JavaScript:

· CatDV.stringify(object) – equivalent to JSON.stringify() in case that isn't present (this implementation may behave slightly differently if passed a java rather than javascript object – need to check)

· CatDV.parse(json) – equivalent to JSON.parse() in case that isn't available (again, this might slightly different objects based on a java map rather than a javascript bridge object – need to check)

· CatDV.log(object, …) – log one or more messages to the error log for debugging etc.

· CatDV.requires(module) – similar to the Node.js ‘requires’ keyword, this will load a library from another file, returning whatever is placed in the global 'exports' variable. For example, in myScript.js you could define:

exports.doSomething = function() ...

Then to access this in main script:

var myModule = CatDV.requires('myScript.js');
myModule.doSomething();

If you write CatDV.requires("myModule") this will load my myModule/index.js, where myModule is a subdirectory of the CatDV worker extensions directory.

You can also write

exports = { doSomething: function() … ,  }

User interface functions

The following methods relate to UI scripts only (and to Pegasus custom actions), and provide interaction with the desktop client user interface (the details panel, movie panel, menu commands, etc.):

· CatDV.setTabVisible(tabName, visible) – show or hide a details panel tab (and select it)

· CatDV.setPicklistValues(fieldID, values[]) – set the pick list values for a drop down pick list

· CatDV.setFieldOptions(fieldID, options) – show or hide a field on a details panel tab or make it read only. The options object can contain the parameters 'tabName' (to narrow down which tab to apply to if the field occurs in more than one place), 'readOnly', and 'visible'.

· CatDV.getClip() – get the current clip (though it's normally passed in to handler functions)

· CatDV.getMovieTime() – return current play head timecode

· CatDV.setMovieTime(timecode) – set the play head position

· CatDV.getPlaybackRate() – get current movie playback rate

· CatDV.setPlaybackRate(rate) – set movie playback rate

· CatDV.performMenuCommand(name, [arg, callback]) – experimental feature to invoke a desktop client menu command by name. Very limited interaction with the user interface of the command is available. If the command prompts for a file, or prompts for a simple string, this can be provided by specifying the argument ‘arg’ as a string. The optional callback function is called when the command completes and is passed two arguments: any completion message which is displayed by the command, and any exception or error message reported by the command.

· CatDV.selectClip(clip, action) – select a particular clip in the main window and optionally perform a double click action on it (one of: ‘editSequence’, ‘mediaDialog’, ‘play’, ‘fullscreen’, ‘verbatim’, ‘html’, ‘movieInfo’, ‘details’, ‘launchApp’, or null). Return true if the clip was found in the main window.

· CatDV.openClip(clipId, title) – load a clip from server (given its unique database id) and open it in a new tab with the given title. If that clip is already open in the current catalog, and the title is null, the user is given the option to select that clip instead in the current tab instead.

Displaying messages and getting user input

There are several methods for displaying messages and getting input in UI scripts, though only the asynchronous versions are available in the web interface:

· CatDV.showMessage(msg) – display a simple message dialog

· CatDV.askOKCancel(msg) – display a simple confirmation dialog

· CatDV.askQuestion(prompt, defaultValue) – display a simple input dialog

· CatDV.askOptions(title, prompt,

· CatDV.askOKCancelAsync(title: string, msg: string, okCallback: () => void)) – display a simple confirmation dialog and call okCallback if OK is pressed

· CatDV.askQuestionAsync(title:string, prompt: string, defaultValue: string, callback: (answer) => void)) – display a simple input dialog and pass the answer to the callback

· CatDV.askOptionsAsync(title:string, prompt: string, options : string[], callback: (option) => void) – display a confirmation dialog with a list of button choices such as “Yes”, “No”, “Cancel”.

· CatDV.chooseFile(prompt:string, defaultFile:file-or-path, showDirChooser:bool, forSaving:bool) – display a file or directory chooser and return the selected file path or null if cancel was pressed.

File utilities

The following file handling functions are available:

· CatDV.listFiles(dir) – return an array of file names given a directory path, or null if the directory doesn't exist

· CatDV.fileExists(pathOrClip) – given either a file path as a string, or a clip with a media path, returns true if the media file exists

· CatDV.deleteFile(path) – returns true if the file given by the file path was successfully deleted

· CatDV.copyFile(pathOrClip, destination) – copy a file or files to a new destination directory and return true for success, false for failure.

· CatDV.moveFile(pathOrClip, destination) – move a file or files, or rename a file and return true for success, false for failure.

For copyFile and moveFile the source file can either be specified by a file path (as a string), or it can be a clip, in which case the media file for that clip is copied. In the case of a metaclip, all the files are copied, and relative paths are preserved. In the case of copying or moving a single file the destination path is taken to be the complete file path (so you can rename a file) unless it ends in a file separator slash or refers to an existing directory.

· CatDV.readFile(path) – read the entire contents of a file and return it as a byte array (see below)

· CatDV.writeFile(path, text) – create (or overwrite) a file with the given text or byte array

· CatDV.appendFile(path, text) – append the given text or byte array to an existing file (which is created if it doesn’t already exist)

Executing commands and making http requests

· CatDV.execute(cmd, args, …) – execute an external command via the command line, specified by a file path and a list of arguments, and return the stdout output of the command

· CatDV.executeWithStatus(cmd, args, …) – execute an external command and return an object with fields exitStatus, stdout, and stderr.

· CatDV.executeWithEnvironment(args, envp, cwd) – as above but with the option to set environment environment variables. Pass in args as an array, and environment as an array of strings of the form name=value.

· CatDV.execJava(className, args, …) – launch an external java process using the embedded worker JVM specifying the class name. Note that it isn’t currently possible to specify additional files on the class path, but if you pass the path to a jar file instead of a class name it is executed like ‘java -jar xxx.jar’.

· CatDV.httpRequest(request) – make an HTTP request and return a result object. The request object has 'protocol', 'method', 'hostname', 'port', 'path', 'headers' and 'postData' fields. You can also directly pass in a URL as a string. The result object has 'statusCode', 'error', 'data' and 'headers' fields. The returned ‘data’ field is a byte array.

· CatDV.encryptPassword(password) – encrypt a password using the public key that the CatDV server we are connected to is using, for when we make a REST API http request to that server. But see also callRESTAPI().

· CatDV.callRESTAPI(method, query, postData) – make a REST API call to the CatDV server we are connected to. This actually uses RMI as a short cut to get data provided by the REST API. If full access to headers etc. is required then make an http request instead. This request is synchronous and is only available in the worker. For example

CatDV.callRESTAPI("GET", "/info", null)

· CatDV.httpRequestAsync(request, callback) – similar to httpRequest but asynchronous, and call a callback function with the result.

· CatDV.callRESTAPIAsync(method, query, postData, callback) – similar to callRESTAPI but asynchronous, and call a callback function with the result.

Miscellaneous functions

· CatDV.getMediaStores() – get a list of media stores defined on the server (as an array of SMediaStore objects, with ‘name’ and ‘paths’ fields).

· CatDV.callProcessingStep(action, params, clips) – programmatically invoke another worker processing step, for example

  CatDV.callProcessingStep("Dump Variables",
    { path: "/tmp/dump.out" }, null );

CatDV.expandVariables(expr, params, clip) – expand a variable expression such as “${NM1}{s/x/y/}”

CatDV.put(key, value) & CatDV.get(key) – each time a JavaScript function is invoked in the worker or desktop client a new JavaScript engine is created, so functions that are defined or variables that are set will be lost from one invocation to the next. To get around this you can store values against the CatDV object itself, eg.

  CatDV.put(‘tempfile’, ‘/tmp/myfile.txt’);
  var data = CatDV.readFile(CatDV.tempfile);

· CatDV.sleep(time) – wait a fixed time in milliseconds. Return true for success or false if it was interrupted.

· CatDV.signSHA256RSA(input, privateKey) – sign a string using SHA256withRSA encoding (to support creating signed JSON Web Tokens)

· CatDV.base64UrlEncode(input) – encode a string or byte array using base64 url encoding

· CatDV.base64UrlDecode(text) – decode a base64 url encoded string and return the original contents as a byte array

· CatDV.setProgress(String msg, int val, int max) – report progress for long running tasks.

· CatDV.getDistinctValues(fieldId, catalogId, groupId) – return an array of strings with distinct values of a column that occur on the server. Set catalogId and groupId to -1 to return values across all catalogs and production groups. The field can be a numeric field id (as used by the worker command line or defined in the AttributeID class, eg. 103 for “Video format”) or the name/id of a field like “clip.name” or “U25”). See also performColumnQuery().

Timecode functions

These functions allow calculations to be performed with timecodes:

· CatDV.timecodeDiff(out, in) – subtract two timecode values and return the difference in seconds. You can also use this to convert a timecode value to seconds by passing null for the second parameter.

· CatDV.timecodeAdd(start, diff) – add a number of seconds (or a second timecode value) to a base timecode

· clip.getTimecode(timecode) – utility function method on the clip object to convert a string, eg. “0:00:05:00” or “00:00:10;00(5994)”, or a number of seconds since the start of the clip, to a timecode value. Unless a specific timecode format is specified the timecode will have the same time base as the clip.

· CatDV.getTimecode(timecode, format) – convert a string or number of seconds to a timecode value in the specified format. Format can be a clip to use the timecode format of that clip, a frame rate, or be left as null.

Markers

You can access the markers for a clip using the clip.markers field, which returns an array of marker objects. The marker object has built-in fields 'name', 'in', 'out', 'description' and 'category'. You can also access custom marker fields using ‘fields, eg.

clip.markers[1].fields[‘my.custom.field’] = …

The marker object is a copy of the marker data in the clip, so after making any changes to markers you have to call explicit methods to persist the changes to the clip:

· clip.deleteMarker(marker) – remove a marker from the clip

· clip.saveMarker(marker) – save changes back to the clip after modifying any fields of a marker, e.g.

  clip.markers[0].name = “Modified”;
  clip.saveMarker(markers[0]);  // or change isn’t saved

· clip.addMarker(markerFields) – define a new marker by passing in an object with 'timecode' or 'in' as a minimum, plus optional 'out', 'name', 'category' and 'description' fields. 


See also the catdv_onMarkerCreated() and catdv_onMarkerSelected() UI handlers.

Performing queries and creating new clips

These methods provide access to clips other than the current selection and allow creation of new clips and catalogs:

· CatDV.findClips(queryText) – perform a REST API-like query and return an array of clip objects (not simply JSON objects). You can also pass an XML query string instead if preferred.

· CatDV.countClips(queryText) – return a count of matching clips without fetching back all the clip data

· CatDV.performQuery(queryText) – perform a REST API-like query (or an XML query) and return a ‘catalog’ or ‘result set’ object (see below). For details of the query language see http://www.squarebox.com/server7-clip-query-filter-syntax/

· CatDV.performColumnQuery(field, queryText) – perform a query and return an array of strings (or other objects) for the chosen field only. If you don’t need all the clip fields then this is more efficient that returning a lot of metadata you don’t need. See also getDistinctValues().

· CatDV.importClip(filePath) – perform a basic import the file and always return a single clip representing the file. If the file can't be imported as a media file a generic clip is created instead.

· CatDV.importClips(filePath, options) – perform a full import the file and return a list of clips (e.g. if it is an XML or batch file or if scene detection is in use more than one clip may be returned). If it is part of a complex clip then a metaclip may be returned. For example

  var clip = CatDV.importClips("~/file.mts",
    { importer: "MPEG", "thumbnail.mode": 0 }) [0];

· CatDV.newCatalog() – create a new empty in-memory catalog

· CatDV.findCatalogs(queryText) – perform a catalog query and return an array of “light” SCatalog objects (with members ID, name, etc.)

· CatDV.getCatalogs() – return all the catalogs on the server (as an array of SCatalog objects)

· CatDV.getReadableCatalogs() – return all the accessible catalogs (as an array of SCatalog objects)

· CatDV.openCatalog(id) – open a catalog from the server based on its id and return a full catalog object

· CatDV.deleteCatalog(id) – delete a catalog from the server by its id

· CatDV.newSequence(name, start) – create a new empty sequence with the specified start timecode

· clip.duplicate() – create a copy of an existing clip that you can add to another catalog

Manipulating clip lists

These methods provide access to clip lists stored on the server:

· CatDV.getClipLists() – return a list of all the clip lists as “light” SFolder objects

· CatDV.findClipList(name) – return the id of a clip list given its name (or -1 if not found)

· CatDV.createClipList(name) – create a clip list (by default shared with everyone in the current group) and return its id

· CatDV.getClipList(id) – get a clip list by its id as an SFolder object

· CatDV.saveClipList(sfolder) – save changes to the name or ownership of a clip list

· CatDV.deleteClipList(id) – delete a clip list by id

· CatDV.openClipList(id) – open a clip list and the clips within it and return it as a catalog object on which you can call methods like add, remove, getClips, and publishChanges to edit the clip list.

Catalog object

The Catalog object gives access to the catalog or underlying clip list for the current window that contains clips.

There are different sorts of ‘catalog’, including a locally saved .cdv catalog file, a catalog that has been published to the server, an unsaved in-memory catalog containing temporary clips, and a result set after querying the server for matching clips. The contents of a clip list, which might contain clips from different catalogs on the server, are also presented in a ‘catalog’ object.

You obtain a catalog object in various ways:

· clip.getCatalogObject() – returns the catalog object or result set that this clip is part of

· CatDV.performQuery(), CatDV.openCatalog(), CatDV.newCatalog() return or create catalog objects, as does CatDV.openClipList().

The following methods are available on catalog objects, though some of these methods are only relevant to certain types of catalog. For example you can only call publishChanges() on a server catalog or query result set or clip list, not a local or temporary in-memory catalog.

· getName() – get the name of the catalog

· getComment() – get the notes field of the catalog

· setComment(String comment) – set the catalog notes

· isOnServer() – return true if this is a server catalog or server query results

· getSCatalog() – return the underlying server object if this is the result of opening a complete catalog from the server

· getFilePath() – return the path if this is a .cdv catalog file or null otherwise

· getClips() – return an array of all the clips in the catalog or query result set. Note that changes made to this array are not automatically persisted to the catalog, for that you need to call add() or remove().

· getNumClips() – return a count of clips in the catalog or result set

· getClip(index) – return a clip by its index in the clip list

· getField(id) – get the value of a custom catalog field

· setField(id, value) – set a custom catalog field

· add(clip/clips) – append a single clip or a list of clips to the catalog

· remove(clip/clips) – delete one or more clips from the catalog

· saveToFile(filePath) – save the catalog or query results to a file

· publishChanges() – publish changes to the catalog to the server (and return a string summarising the operation)

· publish(catalogName) – publish to server as a new catalog and return an SCatalog

Sequence object

The sequence object extends the clip object and allows a sequence to be edited. You can create a sequence with CatDV.newSequence() and test whether an existing clip is a sequence or not by calling getNumTracks(), see below.

· getNumTracks() – return the number of tracks the sequence has. If this is called on a clip which isn’t a sequence -1 is returned. A valid track index from 0 to getNumTracks()-1 must be passed to most of the functions below.

· getTrackType(track) – return the type of the track, 1=video, 2=audio, 3=both

· setTrackType(track, type) – change the type of the track

· addTrack(type) – add a new track of the specified type

· removeTrack(track) – remove the track with that index

· getNumClips(track) – get the number of items in the specified track

· getSourceClip(track, index) – get the source clip for an item in a track

· getSourceIn(track, index) – get the start timecode within the source clip that this sequence is refererencing

· getSourceOut(track, index) – get the end timecode within the source clip that this sequence is refererencing

· getIn(track, index) – get the start timecode of an item within the sequence

· getOut(track, index) – get the end timecode of an item within the sequence

· addClip(track, clip, srcIn, srcOut) – add a reference to part of a source clip at the end of the sequence track

· addClip(track, clip, srcIn, srcOut, destIn, destOut) – add a reference to a source clip at a particular point in the sequence. Take care not to overlap existing clips.

· removeClip(track, index, shiftUp) – remove an item from the sequence. Pass true or false depending on whether to shift subsequent clips up or not.

· adjustPosition(track, index, offset) – shift an item in the sequence. Take care not to overlap existing clips or the sequence will become corrupted!

Server objects

Some methods like CatDV.findCatalogs(), CatDV.getCatalogs(), CatDV.getReadableCatalogs(), CatDV.getClipLists(), CatDV.findClipList(), and CatDV.getMediaStores() return “lightweight” server data transfer objects like SCatalog, SFolder, SClip, SImportSource etc. You can also access server objects via the clip.sclip, clip.smedia, clip.scatalog, and clip.ssource fields.

These server objects all have an ‘ID’ field, which gives the server database id of that object, a toString() method, and other fields like ‘name’ etc. that depend on the specific object.

Server objects are just data structures that correspond to rows in the database and don’t have any methods. So while an SCatalog object gives access to basic details about a catalog on the server, it’s only once you open the catalog up that you get a full catalog object containing the clips, with methods like publishChanges() etc.

Confusingly, if you use the REST API then you will end up with yet another representation of clips, catalogs etc., namely as a JSON object. These are broadly similar to server objects, in that they don’t have methods, but store their data as a map rather than a structure.

Byte arrays

CatDV.readFile(), CatDV.httpRequest() and clip.poster all return binary data as a byte array, with the following fields and methods:

· length – return the number of bytes

· toString() – interpret the data as a UTF-8 string

· getByte(int pos) – read the byte at the given position

· getInt2(int pos) – read a 2-byte integer (big endian)

· getInt4(int pos) – read a 4 byte integer (big endian)

· slice(int start, int len) – return a subarray

The converse methods, eg. writing data to a file with CatDV.writeFile() or passing ‘postData’ to CatDV.httpRequest(), all accept either an existing byte array or data passed as a UTF8 string so there is usually no need to explicitly create a byte array. If necessary you can use CatDV.base64UrlDecode() to create one from a string.

You can mutate a byte array using:

· append(ByteArray other)

· setLength(int len)

· setByte(int pos, int byte)

Timestamps

clip.modifiedDate, clip.recordedDate, and clip.importSource.importedDate return a timestamp object, which has a number of useful methods including:

· toString() – format the date using default format

· toISOString() – return an ISO-style string

· toString(“YYYY-MM-DD”) – format times using the specified format string (see online documentation for java.util.SimpleDateFormat for details).

· toYMDHMString(), toYMDHMSString(), toShortString(), toHMSString() – other formats

· getComponents() – return an array of 6 integers with yr,mth,day,hr,min,sec

· toNow() – how many seconds ago that timestamp is

· getSeconds() – Unix time (number of seconds since Jan 1, 1970)

· addSeconds(), diffInSeconds(), diffInDays(), compareTo() – other date arithmetic methods

Media functions (scene detection, image extraction, and transcoding)

You can perform programmatic scene detection:

· CatDV.detectScenes(movie, threshold, advanced, minLength) – perform scene detection on a movie (specified by a clip or a filename) and return an array of times (seconds from the start of the movie). The ‘threshold’ is typically in the range 25 (more sensitive) to 150 (less sensitive). ‘advanced’ is true or false depending on which algorithm to use, while ‘minLength’ is the minimum gap in seconds between scene boundaries if the advanced algorithm is being used.

You can get a java image from a file and then scale and compress it to JPEG encoded bytes, or use it in a custom rendering function. You can also set the poster thumbnail for a clip by assigning clip.poster to an image or byte array (or calling clip.setPoster()).

· CatDV.getImage(src, time) – return a java image from the specified time within a movie. The source parameter can be a clip or a file name, and the time can be a timecode value or a number of seconds from the start of the clip or file. The source parameter can also be an image decoder, see below.

· CatDV.getImageDecoder(src) – if you are going to read many images from the same file it is more efficient to open up the movie just once and keep it open as an image decoder

· CatDV.dispose(imageDecoder) – once you have finished you should close the movie

· CatDV.encodeJPEG(image, maxWidth, maxHeight, orientation, quality) – encode an image as JPEG bytes and return a byte array object. Orientation is one of 0, 90, 180, 270 and quality is from 0 (highest compression) to 1 (highest quality).

This method enables a basic transcode to be performed:

· CatDV.transcode(clip, destFile, settings) – transcode a clip to a destination file path using xml transcode settings.

These functions support rendering out a movie based on JavaScript, for example with programmatically calculated transitions, custom title slates, etc.

· CatDV.transcode2(destFile, settings, src) – transcode a clip using the new segment based exporter. Settings are provided as a map.

· CatDV.transcode2(destFile, settings, videoSegments, audioSegments) – transcode a movie defined by a list of video and audio segments. These segments can be obtained using one of the functions below, or defined programmatically.

· CatDV.deconstructVideo(clip, showMarkers, markerCategory, includeDescriptions, markerDuration) – return a list of video segments to render out a clip (including metaclips and sequences) by creating a separate segment for each file, and also by looking at markers and creating a separate segment depending on the marker text being shown

· CatDV.deconstructAudio(clip) – return a list of audio segments to render out a clip or (sequence or metaclip)

The transcode() method uses xml exporter settings such as

<exportSettings format="ffmpeg" container="mp4" vcodec="libx264" acodec="aac" scaleMode="4" maxWidth="480" maxHeight="270" deinterlace="true" textPos="5" textColour="2" fontSize="18" visibleTimecode="true" bitcPos="2" bitcColour="7" bitcSize="30" quality="32" videoBitRate="0" videoFrameRate="0" audioBitRate="0" audioSampleRate="0" audioTrack="0" audioChans="0" options="-preset slow -movflags +faststart" even="true" smartStereo="true" />

The transcode2() method simplifies this by accepting a javascript object, eg.

{ container: ‘mp4’, vcodec: ‘libx264’, width: 1024, height: 720 }

Video and audio segments are specified as an array of objects, eg.

[ { text: ‘My title’, duration: 2.0}, { src: clip, in: clip.in2, out: clip.out2 }, { file: ‘input.avi’, start: 0.5, duration: 5.0 } ]

or

[ { duration: 2.0 }, { frequency: 1000, duration: 0.1 }, { file: ‘music.mp3’ } ]

Video segments can be defined by ‘src’ (a source clip), ‘file’ (a file path), ‘text’ (a title page), or be left blank (for a black filler). Other than for source clips the duration must be specified. Similarly for audio segments.

Video segments can also be defined completely programmatically by executing the script for each frame and using java AWT methods:

[ { duration: 5.0, script: function(g, i, numFrames, frameRate, width, height) {
  var image = CatDV.getImage(source, i/frameRate);
  g.drawImage(image, 0, 0, null);
  g.setColor(java.awt.Color.RED);
  g.drawString(“Time = “+i/frameRate, width/2, height/2);
}} ] 

Availability of features

Not all features are relevant in every situation where JavaScript is available. For example, some relate to the interactive user interface and are only relevant in the desktop and web, while some are provided in both synchronous and asynchronous versions (as JavaScript in web browsers is single threaded and enforces the use of callbacks rather than blocking calls).

As far as possible the desktop and web interface provide the same UI script capabilities as the same script is run in both contexts, though there are some minor differences for implementation reasons.

Function or capability

Desktop UI Script

Web UI Script

Desktop custom action

Worker script/ plugin

catdv_onCreateField()

Y

Y

-

-

catdv_onLoad()

Y

Y

-

-

catdv_onUpdate()

Y

Y

-

-

catdv_onValidate()

Y

N

-

-

catdv_onImport()

Y

-

-

-

catdv_onMarkerSelected()

Y

N

-

-

catdv_onCreateMarker()

Y

N

-

-

catdv_onAction()

Y

Y

-

-

catdv_onDownload()

-

Y

-

-

clip.get()

Y

Y

Y

Y

clip.set()

Y

Y

Y

Y

clip.poster

--

-

Y

Y

CatDV.getProperty()

Y

Y

Y

Y

CatDV.isDesktopClient()

Y

Y

Y

Y

CatDV.isWebClient()

Y

Y

Y

Y

CatDV.isWorkerNode()

Y

Y

Y

Y

CatDV.getSoftwareVersion()

Y

Y

Y

Y

CatDV.getUserName()

Y

Y

Y

Y

CatDV.getScriptFile()

-

-

-

Y

CatDV.setTabVisible()

Y

Y

Y

-

CatDV.setPicklistValues()

Y

Y

Y

-

CatDV.setFieldOptions()

Y

N

Y

-

CatDV.getClip()

Y

Y

Y

-

CatDV.showMessage()

Y

Y

Y

-

CatDV.askOKCancel()

Y

-

Y

-

CatDV.askQuestion()

Y

-

Y

-

CatDV.askOptions()

Y

-

Y

-

CatDV.askOKCancelAsync()

Y

Y

Y

-

CatDV.askQuestionAsync()

Y

Y

Y

-

CatDV.askOptionsAsync()

Y

Y

Y

-

CatDV.chooseFile()

Y

-

Y

-

CatDV.getMovieTime()

Y

N

Y

-

CatDV.setMovieTime()

Y

N

Y

-

CatDV.getPlaybackRate()

Y

N

Y

-

CatDV.setPlaybackRate()

Y

N

Y

-

CatDV.saveMarker()

Y

N

Y

-

CatDV.performMenuCommand()

--

-

Y

-

CatDV.getAllGroups()

Y

N

Y

Y

CatDV.getMyGroups()

Y

N

Y

Y

CatDV.getAllUsers()

Y

N

Y

Y

CatDV.getPicklistValuesAsync()

Y

Y

Y

-

CatDV.getPicklistValues()

Y

-

Y

Y

CatDV.listFiles()

--

-

Y

Y

CatDV.fileExists()

--

-

Y

Y

CatDV.deleteFile()

--

-

Y

Y

CatDV.copyFile()

--

-

Y

Y

CatDV.moveFile()

--

-

Y

Y

CatDV.readFile()

--

-

Y

Y

CatDV.writeFile()

--

-

Y

Y

CatDV.appendFile()

--

-

Y

Y

CatDV.stringify()

Y

-

Y

Y

CatDV.parse()

Y

-

Y

Y

CatDV.requires()

--

-

Y

Y

CatDV.log()

Y

Y

Y

Y

CatDV.execute()

--

-

Y

Y

CatDV.execJava()

-

-

N

Y

CatDV.transcode()

--

-

Y

Y

CatDV.transcode2()

--

-

Y

Y

CatDV.detectScenes()

--

-

Y

Y

CatDV.getImage()

--

-

Y

Y

CatDV.httpRequestAsync()

Y

-

Y

-

CatDV.httpRequest()

--

-

Y

Y

CatDV.setProgress()

--

-

Y

Y

CatDV.encryptPassword()

Y

-

Y

Y

CatDV.callRESTAPIAsync()

Y

Y

Y

-

CatDV.callRESTAPI()

--

-

Y

Y

CatDV.callProcessingStep()

-

-

-

Y

CatDV.findClips()

--

-

Y

Y

CatDV.performQuery()

--

-

Y

Y

CatDV.performColumnQuery()

--

-

Y

Y

CatDV.getDistinctColumns()

Y

-

Y

Y

CatDV.newCatalog()

--

-

Y

Y

CatDV.importClip()

--

-

Y

Y

CatDV.importClips()

--

-

Y

Y

CatDV.expandVariables()

Y

N

Y

Y

CatDV.get()/put()

Y

N

Y

Y

CatDV.getCatalogs()

Y

N

Y

Y

CatDV.getReadableCatalogs ()

Y

N

Y

Y

CatDV.getMediaStores()

Y

N

Y

Y

CatDV.getClipLists ()

Y

N

Y

Y

CatDV.findClipList ()

Y

N

Y

Y

CatDV.createClipList ()

Y

N

Y

Y

CatDV.getClipList()

Y

N

Y

Y

CatDV.saveClipList()

Y

N

Y

Y

CatDV.deleteClipList()

Y

N

Y

Y

CatDV.openClipList()

Y

N

Y

Y

CatDV.selectClip()

Y

-

Y

-

CatDV.openClip()

Y

N

Y

-

-- indicates the feature is provided on the client for use in custom actions only. Although it may be present it shouldn't be used in UI scripts and may give unpredictable results there (eg. causing the user interface to lock up).

- means the feature is not relevant in that context and is unlikely ever to be implemented.

N indicates the feature is not currently implemented but might be added in future.

Points to note

JavaScript within a web browser is single threaded and system calls are not allowed to block, so only asynchronous calls like callRestAPIAsync() that provide the results via a callback function can be used from there.

Within the desktop client you can make both synchronous and asynchronous calls but should avoid making any synchronous calls from UI scripts, both because they can cause the UI to lock up and because they will prevent the script running in the web interface.

Many of these methods can throw exceptions, eg. CatDV.execute() if the command doesn’t exist, catalog.publishChanges() if there’s a server error, etc.

If the JavaScript code doesn’t catch these exceptions, or throws an exception of its own, then they are handled as follows on the desktop client:

A UI script that throws an exception will display an error message then disable the offending handler (eg. catdv_onUpdate) to prevent runaway script problems. The handler is disabled until the script is next reloaded (eg. by logging off and on again).

A worker processing step or extension or a custom action processing step will cause that action to terminate with an error, just as it would for any other kind of failure.

System requirements

JavaScript support within the CatDV desktop application and worker node is provided by Java’s “Nashorn” JavaScript interpreter which requires Java 8 or later and implements ECMAScript 5.1.

JavaScript within the web interface is executed within the web browser, and so the exact version of JavaScript provided depends on the web browser being used.

To use the features as described in this document you need CatDV 12.1, Worker 7.0, and Server 7.2.0 (or later). The worker and desktop versions that a feature first appeared in are listed below.

Change history

23 Apr 2019 (8.0.1)

· Add execJava().

· Clarify that legacy user-defined clip field identifiers like clip[‘my.user.field’] no longer work and should be written as clip.fields[‘my.user.field’], and also that worker plugins shouldn’t include version number in the file name.

25 Feb 2019 (13.0b16)

· Add ‘interim’ parameter to catdv_onUpdate()

1 Feb 2019 (13.0b15, 8.0b7)

· Add getNumClips() and getClip() to catalog object.

· Return empty result set if performQuery() finds no clips rather than an error.

· Give clips and catalogs a more useful toString() representation.

26 Nov 2018 (13.0a13)

· Add getUserRole()

30 Oct 2018 (12.1.10, 13.0a11)

· Add duplicate() method to clip

22 Sep 2018 (13.0a7)

· Add catdv_onNotification() handler. Add arg and callback to performMenuCommand().

23 July 2018 (7.0.8, 12.1.8)

· Add getDistinctValues() and performColumnQuery()

· Add chooseFile()

· Change selectClip() to return true for success (if the clip is found) and add openClip()

23 June 2018 (7.0.6, 12.1.7)

· Add access to server objects like clip.sclip, clip.scatalog

· Add clip.ID to give you the server clip id as an integer (unlike clip.id which is a string like “12345.1” and includes the sequence number)

· Add appendFile() and setProgress().

· Restructure documentation into topics and add new or updated sections on markers, timestamp functions, the catalog object, server objects, byte arrays, etc.

24 May 2018 (7.0.4, 12.1.6)

· Add detectScenes() and catdv_onCreateMarker().

· Provide access to custom marker fields

17 May 2018 (7.0.3)

· Add support for versioning of worker plugins

13 April 2018 (7.0.1, 12.1.5)

· Add sleep(),signSHA256RSA(),base64UrlEncode(),base64UrlDecode() and encodeJPEG() methods. Fix httpRequest() to return server error response.

8 March 2018 (7.0rc3, 12.1.5)

· Add Timestamp.toString(“YYYY-MM-DD”)

7 February 2018 (7.0rc1)

· Add onLoad() and onValidate() to worker plugins

29 January 2018 (12.1.3, 7.3x)

· Implement askOKCancelAsync, askOptionAsync, askQuestionAsync

25 January 2018 (12.1.2, 7.0b21)

· Implement ${importSource[Xyz]}, ${catalog[Abc]}, etc.

· Add documentation on catdv_onDownload and on variable expressions

1 December 2017 (12.1.0, 7.0b19)

· Add getTimecode(timecode, format)

· Add programmatic rendering methods

· Worker plugin enhancements, including additional field types and the ability to specify a licenseKey category.

23 November 2017 (12.1rc4)

· Fix timecodeDiff() and getTimecode()

· Improved sequence editing

· Add selectClip()

· Add executeWithEnvironment()

12 October 2017 (7.0b18, 12.1rc2)

· Add getCatalogs(), getReadableCatalogs(), getMediaStores(), and countClips()

· Add methods to open, create and edit clip lists

· Change extension for Worker Extensions to .jx or .jxe and add support for encrypted packages

9 October 2017 (12.1rc1)

· Allow ‘CatDV’ to be used as a global map variable to store values and functions between invocations.

· Provide access to ‘CatDV’ object when evaluating a js: variable expression.

3 October 2017 (7.0a17)

· Change worker javascript processing step so it doesn’t pre-expand variable expressions like $x that occur within the text of the script inline. Instead, provide functions expand() and CatDV.expandVariables().

22 August 2017 (12.1b10)

· Change readFile(), httpRequest(), executeWithStatus() to return data as a byte array rather than a string

· Add methods to get and set clip poster thumbnails

26 July 2017 (12.1b9, 7.0a14)

· Change unadorned method names like getPicklistValues() etc. so they are synchronous by default, and add corresponding xxxAsync() methods when the version taking a callback is needed

July 2017 (12.1b8, 7.0a13)

· Use global CatDV object instead of context variable passed to the catdv_onXXX methods

· Remove CatDV.saveMarker() and add methods to clip instead

· Change callRESTAPI so that it works with objects not strings (so JSON.stringify and JSON.parse are called for you)

· Deprecate clip['my.user.1'], clip.media['FNumber'] and clip.catalog ['my.catalog.field'] and use 'fields' instead when referring to fields on any of these objects, eg. clip.fields['my.user.1']