connect
search

Transcoding Profiles for Extracting Media

When extracting clips with the Clipping Media feature, you must use an NTP profile. For each option, Individual Files, Single File and Single file without selected clips, you must use a previously configured NTP profile.

Important

The NTP profile must be configured properly and its output paths must match the paths from the Extract media modal. Placeholder files are created at the specified paths when starting the extraction, and the NTP profile is expected to output there.

Individual Files

LiquidJS context

Each clip is extracted from a file, in this case the same file. For the purpose of this example, we shall call it file1. The resulting clip files will have as a parent file file1. As such, we will address these clips as file1.extractionClips.

Each clip has the path where it's extracted. In the NTP profile template, each input has a property .extractionClips. It's an array and it has the following structure:

Array<{
name: string; // The name of the extracted clip. It will be the filename without extension.
location: string; // Location of the extracted clip. Extracted from the path used in the extraction modal.
filename: string; // Filename of the extracted clip. Extracted same as above.
markers: Array<{ // The markers used when extracting a clip.
frame: {
in: number; // The start frame of the clip.
out: number; // The end frame of the clip.
duration: number; // Duration in frames. Calculated as: out - in + 1.
}
timecode: {
in: string; // The start timecode of the clip.
out: string; // The end timecode of the clip.
duration: string; // Duration in timecode.
}
}>;
}>

1. Using a Hybrik profile

Multiple transcode tasks

First, in order to have a single Hybrik job that extracts the clips, we must create a transcode_task for each clip.

transcode_task for each clip.
{% capture transcode_extractions %}
{% for ec in file.extractionClips %}
{
"kind":"transcode",
"uid":"transcode_marker_{# ec.name #}_{# forloop.index #}",
...
},
{% endfor %}
{% endcapture %}
{# transcode_extractions | slice: 0, -1 #}

The transcode tasks are captured in the transcode_extractions variable.

{% capture transcode_extractions %}

We loop through file.extractionClips and create a transcode_task.

{% for ec in file.extractionClips %}

Each transcode task must have an unique id. We can use the current clip name, {# ec.name #}, and the current index in the loop, {# forloop.index #}.

"uid":"transcode_marker_{# ec.name #}_{# forloop.index #}",

We render the transcode tasks without the trailing comma, to avoid any JSON validation errors.

{# transcode_extractions | slice: 0, -1 #}

Output location

As previously stated, it is important that we get the output location right.

"payload":{
"location":{
"storage_provider":"s3",
"path":"{# ec.location #}/"
},
"targets":[
{
"file_pattern": "{# ec.filename #}",
...
}
]
}

For each transcode task we use the clip location and filename for the output. The ec variable is previously defined in the loop and represents the current clip.

Trim

We use asset timecode syntax in this profile.

"trim":{
"inpoint_asset_tc":"{# ec.markers[0].timecode.in #}",
"outpoint_asset_tc":"{# ec.markers[0].timecode.out #}",
"source_timecode_selector":"highest"
}

Timecode track

We want our resulted clip to follow the timecode of our source file. This means you won't see it playing from 00:00:00:00, but from the start timecode it was clipped from. This configuration is optional.

"timecode": [
{
"source": "start_value",
"start_value": "{# ec.markers[0].timecode.in #}"
}
],

Element connections

At the end of the Hybrik job JSON, we must connect the transcode tasks to the source. This is done similar to how we've done when defining the tasks.

{
"success": [
{% capture transcode_connections %}
{% for ec in file.extractionClips %}
{
"element":"transcode_marker_{# ec.name #}_{# forloop.index #}"
},
{% endfor %}
{% endcapture %}
{# transcode_connections | slice: 0, -1 #}
]
}

The following job JSON is the resulting one that was sent to Hybrik:

Hybrik job JSON
{
"task_tags": [
"dev",
"dev"
],
"definitions": {
"profile_name": "NTP_with_markers",
"credentials_key": "oz-dev-credentials",
"source_path": "s3://tf-s3-ownzones-arrakis/testarrakis/uploads/",
"source_name": "scene_3a_2002.mov"
},
"name": "Extraction Clips - H264- MP4 - Trim by timecode 5105230a-39f7-41b2-89d2-e36d5e74b053",
"payload": {
"elements": [
{
"uid": "sources",
"kind": "source",
"payload": {
"kind": "asset_complex",
"payload": {
"asset_versions": [
{
"version_uid": "asset1",
"asset_components": [
{
"component_uid": "asset1",
"kind": "name",
"name": "{{source_name}}",
"location": {
"storage_provider": "s3",
"path": "{{source_path}}"
}
}
]
}
]
}
}
},
{
"kind": "transcode",
"uid": "transcode_marker_clip_m1_1",
"payload": {
"location": {
"storage_provider": "s3",
"path": "s3://tf-s3-ownzones-arrakis/testarrakis/uploads/"
},
"targets": [
{
"file_pattern": "clip_m1.mp4",
"existing_files": "replace",
"container": {
"kind": "mp4"
},
"timecode": [
{
"source": "start_value",
"start_value": "00:00:00:00"
}
],
"video": {
"codec": "h264",
"profile": "high",
"level": "4.2",
"width": "1920",
"height": "1080",
"bitrate_kb": "50000",
"bitrate_mode": "vbr",
"frame_rate": "23.98",
"dar": "1.778",
"par": "1",
"interlace_mode": "progressive",
"color_matrix": "bt709",
"color_trc": "bt709",
"color_primaries": "bt709"
},
"audio": [
{
"codec": "ac3",
"channels": 2,
"sample_rate": 48000,
"bitrate_kb": 348,
"sample_size": 16,
"bitrate_mode": "cbr"
}
],
"trim": {
"inpoint_asset_tc": "00:00:00:00",
"outpoint_asset_tc": "00:00:04:04",
"source_timecode_selector": "highest"
}
}
]
}
},
{
"kind": "transcode",
"uid": "transcode_marker_clip_m2_2",
"payload": {
"location": {
"storage_provider": "s3",
"path": "s3://tf-s3-ownzones-arrakis/testarrakis/uploads/"
},
"targets": [
{
"file_pattern": "clip_m2.mp4",
"existing_files": "replace",
"container": {
"kind": "mp4"
},
"timecode": [
{
"source": "start_value",
"start_value": "00:00:04:05"
}
],
"video": {
"codec": "h264",
"profile": "high",
"level": "4.2",
"width": "1920",
"height": "1080",
"bitrate_kb": "50000",
"bitrate_mode": "vbr",
"frame_rate": "23.98",
"dar": "1.778",
"par": "1",
"interlace_mode": "progressive",
"color_matrix": "bt709",
"color_trc": "bt709",
"color_primaries": "bt709"
},
"audio": [
{
"codec": "ac3",
"channels": 2,
"sample_rate": 48000,
"bitrate_kb": 348,
"sample_size": 16,
"bitrate_mode": "cbr"
}
],
"trim": {
"inpoint_asset_tc": "00:00:04:05",
"outpoint_asset_tc": "00:00:05:00",
"source_timecode_selector": "highest"
}
}
]
}
}
],
"connections": [
{
"from": [
{
"element": "sources"
}
],
"to": {
"success": [
{
"element": "transcode_marker_clip_m1_1"
},
{
"element": "transcode_marker_clip_m2_2"
}
]
}
}
]
},
"subscription_key": "arrakis"
}

Hybrik Trim

According to the Hybrik documentation, trimming can be done in various ways. We will cover the following:

  1. frame number

This syntax trims using the frame in/out points.

"trim":{
"inpoint_frame":{# ec.markers[0].frame.in #},
"outpoint_frame":{# ec.markers[0].frame.out #}
}
AdvantagesDisadvantages
No quirks as timecode (zero/non-zero based).Less readable in resulted Hybrik JSON file.
Easy to adjust using plus/minus operations, as it is a number.
  1. timecode

This syntax trims using timecode in/out points. It is always zero-based, ignoring any embedded timecode.

"trim":{
"inpoint_tc":"{# ec.markers[0].timecode.in #}",
"outpoint_tc":"{# ec.markers[0].timecode.out #}",
}
AdvantagesDisadvantages
Readable in resulted Hybrik JSON file.Fails for files with custom start timecode.
Can’t be adjusted.
  1. asset timecode

This syntax trims using timecode in/out points. It checks the embedded timecode, by specifying source_timecode_selector.

"trim":{
"inpoint_asset_tc":"{# ec.markers[0].timecode.in #}",
"outpoint_asset_tc":"{# ec.markers[0].timecode.out #}",
"source_timecode_selector":"highest"
}
AdvantagesDisadvantages
Readable in resulted Hybrik JSON file.Uses proper source_timecode_selector value .
Works for files with custom start timecode.Can’t be adjusted.

2. Using a Media Convert profile

In Media Convert, to set the specific output path you need the following:

  • full path without extension.
  • extension.

One way of achieving this, with the information we have is below:

"Outputs": [
{
"Preset": "MP4_SD_300Kbps",
"Extension": "{# file.extractionClips[0].filename | split: '.' | last #}"
}
],
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "{# file.extractionClips[0].location | append: "/" | append: file.extractionClips[0].filename | split: '.' | first #}"
}
}

Extract the extension from the filename, by splitting by dot.

"Extension": "{# file.extractionClips[0].filename | split: '.' | last #}"

Create the full path.

"Destination": "{# file.extractionClips[0].location | append: "/" | append: file.extractionClips[0].filename | split: '.' | first #}"

Note

This logic won't work if there are multiple dots.

Media Convert Trim

According to the Media Convert documentation, trimming can be done using timecode.

"InputClippings": [
{
"StartTimecode": "{# file.extractionClips[0].markers[0].timecode.in #}",
"EndTimecode": "{# file.extractionClips[0].markers[0].timecode.out #}"
}
]

Depending on the file, you might have to switch TimecodeConfig to be either EMBEDDED or ZEROBASED. You can use {# file.startTimecode #} for dynamically switching. In this profile, we have it ZEROBASED hardcoded.

"TimecodeConfig": {
"Source": "ZEROBASED"
},

Single Files and Single file without selected clips

Using Hybrik Sources - asset_complex

To stitch multiple clips into one file we will be using the asses_complex concept. As we will have only one output, there will be one asset_version element per marker with only one asset_component element inside specifying the source file location and name, and also the trim property.

* asset_complex
* asset_version[0] # first marker
* asset_component[0]
* asset_version[1] # second marker
* asset_component[0]
* asset_version[2] # third marker
* asset_component[0]
...
...

To achieve this structure we will be using:

{% capture asset_versions %}
{% for marker in file.extractionClips[0].markers %}
{
"asset_components": [
{
"kind": "name",
"name": "{{source_name}}",
"location": {
"storage_provider": "s3",
"path": "{{source_path}}"
},
"trim":{
"inpoint_frame":{# marker.frame.in #},
"outpoint_frame":{# marker.frame.out | plus: 1 #}
}
}
]
},
{% endfor %}
{% endcapture %}
{# asset_versions | slice: 0, -1 #}

The asset_version elements are captured in the asset_versions variable.

{% capture asset_versions %}

We loop through file.extractionClips[0].markers and create an asset_version element for each of the markers.

{% for marker in file.extractionClips[0].markers %}

We render the asset_versions without the trailing comma, to avoid any JSON validation errors.

{# asset_versions | slice: 0, -1 #}

Important
  • We must use the file.extractionClips variable somewhere outside the capture block, otherwise the templating engine will not populate the context with it because of some limitations:

{% assign _e = file.extractionClips %}

  • For trim.outpoint_frame we need to add 1 to our value as Hybrik slices the last frame. E.g:

{ "inpoint_frame":0, "outpoint_frame": 4 }

This will result in the following frame sequence: 0-1-2-3

The transcode task

As we will have only one output file, there will only be one transcode task:

{
"kind":"transcode",
"uid":"transcode_clip",
"payload":{
"location":{
"storage_provider":"s3",
"path":"{{destination_path}}"
},
"targets":[
{
"file_pattern": "{{output_basename}}",
"existing_files":"replace",
"container":{
"kind":"mov"
},
"video":{
...
},
"audio":[
{
...
}
]
}
]
}
}

Element connections

At the end of the Hybrik job JSON, we must connect the transcode task to the source.

"connections": [
{
"from": [
{
"element": "sources"
}
],
"to": {
"success": [
{
"element":"transcode_clip"
}
]
}
}
]

Using a Media Convert profile

The following important aspects must be considered when creating a Media Convert template for concatenation:

  • The output location.
  • The inputs.

To set the specific output path you will need the following:

  • Full path without extension.
  • Extension.

One way of achieving this, with the information we have is below:

Output location

"Outputs": [
{
"Preset": "MP4_SD_300Kbps",
"Extension": "{# file.extractionClips[0].filename | split: '.' | last #}"
}
],
"OutputGroupSettings": {
"Type": "FILE_GROUP_SETTINGS",
"FileGroupSettings": {
"Destination": "{# file.extractionClips[0].location | append: "/" | append: file.extractionClips[0].filename | split: '.' | first #}"
}
}

Extract the extension from the filename, by splitting by dot.

"Extension": "{# file.extractionClips[0].filename | split: '.' | last #}"

Create the full path. Note that this logic won't work for multiple dots present.

"Destination": "{# file.extractionClips[0].location | append: "/" | append: file.extractionClips[0

Inputs

In order to achieve stitching we should use the following Inputs structure:

* Inputs
* Inputs[0] # first marker
* InputClippings[0]
* Inputs[1] # second marker
* InputClippings[0]
* Inputs[2] # third marker
* InputClippings[0]
...
...
Important

Do not try to use the following structure:

* Inputs
* Inputs[0]
* InputClippings[0] # first marker
* InputClippings[1] # second marker
* InputClippings[2] # third marker
...
...

This will work in some scenarios, but MC will return an error if markers[0].in > markers[1].in. The error returned will be something like this:

[1040]Clipping region [2] of input ID [1] start timecode is equal or earlier than the previous region start timecode.

First, in order to achieve this structure we create an Input for each clip.

{% capture inputs %}
{% for marker in file.extractionClips[0].markers %}
{
...
"FileInput": "{# file.locator.dirname #}/{# file.locator.basename #}{# file.locator.extname #}",
"InputClippings": [
{
"StartTimecode": "{# marker.timecode.in #}",
"EndTimecode": "{# marker.timecode.out #}"
}
]
},
{% endfor %}
{% endcapture %}
{# inputs | slice: 0, -1 #}

The inputs are captured in the inputs variable.

We loop through file.extractionClips[0].markers and create an Input.

{% for marker in file.extractionClips[0].markers %}

We render the Inputs without the trailing comma, to avoid JSON validation error.

{# transcode_extractions | slice: 0, -1 #}

Important

We must use the file.extractionClips , file.locator.dirname, file.locator.basename, file.locator.extname variables somewhere outside the capture block, otherwise the templating engine will not populate the context with them because of some limitations:

{% assign _e = file.extractionClips %}
{% assign _ldn = file.locator.dirname %}
{% assign _lbn = file.locator.basename %}
{% assign _lextn = file.locator.extname %}
Configuring Native Transcoder ProfilesConfiguring a MediaWarp Profile