Today, I will take you through an example of a Video export workflow. The goal is to start the workflow on a Video document, display a form to choose the target and profile of the export, then start said export. The workflow should end automatically when the export is done.

What's a video profile and target you may ask? Well, a target will describe the format of the video. For example an AVI file using the h264 codec for the video and the mp3 codec for the audio, or, an MKV file using Theora for the video and Flac for the audio. The profile of the video will be its size and frame rate. For instance, I want an HD video with 30 frame per second. Profiles and targets will be stored in custom directories. We can start here.

In Nuxeo Studio, when you want to add a directory you are limited by three types: Simple Vocabulary, Child Vocabulary, Hierarchical Vocabulary. But, we need more information than just an Id and a Label. For the profiles, we need a height and the fps and for the target we need the audio codec, the video codec, the extension and the mime-type. Here are the two examples I am starting with:

id, label, height, fps, obsolete
"hd1080p60fps","HD 1080P 60 fps","1080","60","0"
"hd1080p30fps","HD 1080P 30 fps","1080","30","0"
"svcdPal","SVCD PAL","576","25","0"

id, label, acodec, vcodec, extension, mimetype, obsolete
"webm","WebM (vpx)","libvorbis","libvpx","webm","video/webm","0"
"oggF","OGG (theora/flac)","libflac","libtheora","ogv","video/theora","0"
"oggV","OGG (theora/vorbis)","libvorbis","libtheora","ogv","video/theora","0"

As this cannot be made with Studio, I have used the Nuxeo IDE to start a new plugin project. To declare my new directories I need a new schema contribution for each of them and the appropriate directory contribution.

The schema definition is pretty simple:

<component name="org.nuxeo.video.converter.schemas">
<extension point="schema" target="org.nuxeo.ecm.core.schema.TypeService">
<schema name="videoProfile" src="schemas/video_profile.xsd"/>
<schema name="videoTarget" src="schemas/video_target.xsd"/>
</extension>
</component>

The XSD schema itself is more interesting. What I did is basically copy the traditional vocabulary schema and add the field I needed. I did this to keep the columns used by the widgets defined in Nuxeo Studio.

<?xml version="1.0"?>
<xs:schema targetNamespace="http://www.nuxeo.org/ecm/schemas/videoTarget/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/videoTarget/"&gt;
<xs:include schemaLocation="base.xsd" />

&lt;xs:element name="id" type="xs:string"/&gt;
&lt;xs:element name="label" type="xs:string"/&gt;
&lt;xs:element name="acodec" type="xs:string"/&gt;
&lt;xs:element name="vcodec" type="xs:string"/&gt;
&lt;xs:element name="extension" type="xs:string"/&gt;
&lt;xs:element name="mimetype" type="xs:string"/&gt;
&lt;xs:element name="obsolete" type="xs:integer" default="0"/&gt;
&lt;xs:element name="ordering" type="xs:integer" default="10000000"/&gt;

</xs:schema>


 

<?xml version="1.0"?>
<xs:schema targetNamespace="http://www.nuxeo.org/ecm/schemas/videoProfile/"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:nxs="http://www.nuxeo.org/ecm/schemas/videoProfile/"&gt;
<xs:include schemaLocation="base.xsd" />

&lt;xs:element name="id" type="xs:string"/&gt;
&lt;xs:element name="label" type="xs:string"/&gt;
&lt;xs:element name="height" type="xs:integer"/&gt;
&lt;xs:element name="fps" type="xs:double"/&gt;
&lt;xs:element name="obsolete" type="xs:integer" default="0"/&gt;
&lt;xs:element name="ordering" type="xs:integer" default="10000000"/&gt;

</xs:schema>


Once the schemas are contributed, I add the directories. It's pretty straightforward again. Just use the explorer to look at the SQL directories extension point.

<component name="org.nuxeo.video.converter.directories">
<extension point="directories" target="org.nuxeo.ecm.directory.sql.SQLDirectoryFactory">
<directory name="videoProfile">
<schema>videoProfile</schema>
<dataSource>java:/nxsqldirectory</dataSource>
<cacheTimeout>3600</cacheTimeout>
<cacheMaxSize>1000</cacheMaxSize>
<table>videoProfile</table>
<idField>id</idField>
<autoincrementIdField>false</autoincrementIdField>
<dataFile>directories/video_profile.csv</dataFile>
<createTablePolicy>on_missing_columns</createTablePolicy>
</directory>

&lt;directory name="videoTarget"&gt;
  &lt;schema&gt;videoTarget&lt;/schema&gt;
  &lt;dataSource&gt;java:/nxsqldirectory&lt;/dataSource&gt;
  &lt;cacheTimeout&gt;3600&lt;/cacheTimeout&gt;
  &lt;cacheMaxSize&gt;1000&lt;/cacheMaxSize&gt;
  &lt;table&gt;videoTarget&lt;/table&gt;
  &lt;idField&gt;id&lt;/idField&gt;
  &lt;autoincrementIdField&gt;false&lt;/autoincrementIdField&gt;
  &lt;dataFile&gt;directories/video_target.csv&lt;/dataFile&gt;
  &lt;createTablePolicy&gt;on_missing_columns&lt;/createTablePolicy&gt;
&lt;/directory&gt;

</extension>
</component>


And that's all you need to declare your custom directories.

The next question is how to use this in Nuxeo Studio. Usually, when you declare something out of Studio, you use the registries to make them available. But the thing is, there is no vocabulary/directory registry. So how are we suppose to use our newly contributed directories? There is a trick.

I've started a very simple workflow with a start, task and end node. Right now it does nothing except show a form to select the profile and target for the video. I have not added anything else. To set this up, you first add two String variables to the node (profile and target).

Then, when designing the form, drag and drop your two new variables and choose vocabulary as widget type. Select any vocabulary, it does not matter which one because we're going to change it next. Add a Custom Property at the end of the widget definition. It must be named directoryName and the value must be the name of the directory (videoProfile in the example).

Workflow Node Variables
Vocabulary Widget Parameters

Once you do that, just save the widget and reopen it. You will see that the directoryName property has replaced the vocabulary chosen at the beginning and that the custom property has disappeared, which means it works. Just try this workflow. Don't forget to set the assignee variable to Context["workflowInitiator"]. This way you'll be able to see the form and choose the profile and target.

The next step is to create an operation that will handle the conversion. And that is the topic for my next blog post :)