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.
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.
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.}}>;}>
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 #}
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.
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"}
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 #}"}],
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:
{"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"}
According to the Hybrik documentation, trimming can be done in various ways. We will cover the following:
This syntax trims using the frame in/out points.
"trim":{"inpoint_frame":{# ec.markers[0].frame.in #},"outpoint_frame":{# ec.markers[0].frame.out #}}
Advantages | Disadvantages |
---|---|
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. |
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 #}",}
Advantages | Disadvantages |
---|---|
Readable in resulted Hybrik JSON file. | Fails for files with custom start timecode. |
Can’t be adjusted. |
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"}
Advantages | Disadvantages |
---|---|
Readable in resulted Hybrik JSON file. | Uses proper source_timecode_selector value . |
Works for files with custom start timecode. | Can’t be adjusted. |
In Media Convert, to set the specific output path you need the following:
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 #}"
This logic won't work if there are multiple dots.
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"},
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 #}
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 %}
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
transcode
taskAs 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":[{...}]}]}}
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"}]}}]
The following important aspects must be considered when creating a Media Convert template for concatenation:
To set the specific output path you will need the following:
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. Note that this logic won't work for multiple dots present.
"Destination": "{# file.extractionClips[0].location | append: "/" | append: file.extractionClips[0
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]......
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 #}
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 %}