Conditional Processing
Not all transcoding workflows are as straightforward as “convert format A to format B”. There are many scenarios where you want to do something like “if source has 2 channels of audio, do A, and if source has 6 channels of audio do B”. Hybrik offers several different types of conditional processing that allow you to handle a wide variety of scenarios. These types of conditional operations fall into three categories:
- Conditionally generating targets or target tracks depending on the presence of specific source tracks. This uses the
include_if_source_has
object. - Conditionally generating targets or executing filters depending on certain conditions. This uses the
include_conditions
object. - And finally, modifying output parameters based on characteristics of the. This uses ternary operators of the form:
<condition> ? <result if true> : <result if false>
Conditional Track Generation: include_if_source_has
Some media scenarios require specific output tracks to be created only when the source contains a certain input. In these cases, you can use the include_if_source_has
property in the job JSON.
Let’s suppose that you have input files coming in that either have 1 or 2 tracks of stereo audio. The second track could be the alternate language track, but not all of the input files have the alternate track. You would want to create the second output track conditionally based upon the presence of a second input track. Here’s what that would look like. Remember that the audio tracks are a zero-based array, so the first audio track is audio[0]
:
"targets": [
{
"file_pattern": "{source_basename}_converted{default_extension}",
"existing_files": "replace",
"container": {
"kind": "mp4"
},
"video": {
"height": 1080,
"codec": "h264",
"bitrate_kb": 5000,
"max_bitrate_kb": 6000
},
"audio": [
{
"codec": "aac_lc",
"bitrate_kb": 128,
"channels": 2
},
{
"include_if_source_has": [
"audio[1]"
],
"codec": "aac_lc",
"bitrate_kb": 128,
"channels": 2
}
]
}
]
Note that include_if_source_has
is an array, and therefore you can specify multiple conditions that must be met for the output to be generated. In the example below, we want to create a single stereo track from the mono channels in the 7th and 8th input tracks only if both of those tracks exist in the source.
{
"include_if_source_has": [
"audio[6]",
"audio[7]"
],
"codec": "aac_lc",
"bitrate_kb": 128,
"channels": 2,
"source": [
{
"track": 6
},
{
"track": 7
}
]
}
You can test a source for the presence and number of the following types of input tracks: audio, subtitles, and timecode. Use the include_if_source_has
element to conditionally create these types of outputs:
- Any
target
in atranscode
task (which may contain multiple tracks) audio
tracks in a transcodetarget
subtitle
tracks in a transcodetarget
timecode
tracks in a transcodetarget
Conditional Track and Filter Creation: include_conditions
The next type of conditional execution allows you to test for more general properties than whether a track exists. The include_conditions
element lets you test for general properties of the source when deciding which targets or tracks to create. In addition, it also allows the conditional execution of video or audio filters. The conditional operators can reference many different types of source properties, including dimensions, duration, tracks, channels, etc. It can also reference the results of an analyzer step executed previously in the workflow.
NOTE: It is possible to use conditions to filter out all targets from a transcode
task which will cause the task to fail
In the following example, if our input video is less than 1000 pixels high, we want to create only one output target at 576 pixels high. If the input video is greater than 1000 pixels high, we want to create two output targets – one at 720 and one at 1080.
"targets": [
{
"file_pattern": "{source_basename}_576.mp4",
"include_conditions": [
"source.video.height < 1000"
],
"existing_files": "replace",
"container": "{{container}}",
"video": {
"codec": "h264",
"bitrate_mode": "vbr",
"bitrate_kb": 2000,
"max_bitrate_kb": 2400,
"height": 576,
"width": 1024
},
"audio": [
{
"channels": 2,
"codec": "aac_lc",
"bitrate_kb": 96
}
]
},
{
"file_pattern": "{source_basename}_720.mp4",
"include_conditions": [
"source.video.height >= 1000"
],
"existing_files": "replace",
"container": "{{container}}",
"video": {
"codec": "h264",
"bitrate_mode": "vbr",
"bitrate_kb": 3200,
"max_bitrate_kb": 3640,
"height": 720,
"width": 1280
},
"audio": [
{
"channels": 2,
"codec": "aac_lc",
"bitrate_kb": 96
}
]
},
{
"file_pattern": "{source_basename}_1080.mp4",
"include_conditions": [
"source.video.height >= 1000"
],
"existing_files": "replace",
"container": "{{container}}",
"video": {
"codec": "h264",
"bitrate_mode": "vbr",
"bitrate_kb": 4000,
"max_bitrate_kb": 4800,
"height": 1080,
"width": 1920
},
"audio": [
{
"channels": 2,
"codec": "aac_lc",
"bitrate_kb": 96
}
]
}
]
The include_conditions
array can also be used to conditionally apply a filter. In the example below, we want to apply a timecode-printing filter to the output, but only if the duration of the file is longer than 5 minutes. The JSON would look like this:
"targets": [
{
"file_pattern": "{source_basename}_converted.mp4",
"existing_files": "replace",
"container": {
"kind": "mp4"
},
"video": {
"codec": "h264",
"width": 720,
"height": 480,
"bitrate_kb": 6000,
"profile": "baseline",
"filters": [
{
"include_conditions": [
"source.duration_sec >= 600"
],
"kind": "print_timecode",
"payload": {
"y": 100,
"x": 100,
"font": "sans",
"font_color": "white",
"background_color": "black",
"font_size": 32,
"timecode_kind": "timecode_auto",
"timecode_source": "media"
}
}
]
}
}
]
Overrides and Ternary Operators
The third and last type of conditional processing that Hybrik offers uses something called ternary operators. A ternary operator consists of a condition, a desired result if the condition evaluates true, and a result if the condition evaluates false. The basic structure looks like this: <condition> ? <result if true> : <result if false>
.
Suppose that we wanted to set the output bitrate based on the bitrate of the source. For example, if our input is less than 3Mbps, we want the output to be 3Mbps, but if the input is greater than 3Mbps, we want the output to be 8Mbps. This would look like:
"overrides": {
"bitrate_kb": "source.video.bitrate_kb >= 5000 ? 5000 : 3000",
}
This expression can be read as “is the source.video.bitrate_kb
greater than or equal to 5000? If yes, then the video bitrate is set to 5000. If not (and the bitrate is below 5000), the video bitrate is set to 3000.”
When submitting a JSON job to Hybrik, it will verify that any value specified for bitrate_kb
is an integer, and seeing a bitrate_kb
value such as source.video.bitrate_kb >= 3000 ? 8000 : 3000
would generate data validation errors like err": "Invalid operation"
and "message": "should be <= 1000001"
. We therefore need to use the special object called “overrides”. This will cause expressions in the overrides
object to override corresponding items in the enclosing object:
"targets": [
{
"file_pattern": "{source_basename}_converted{default_extension}",
"existing_files": "replace",
"container": {
"kind": "mp4"
},
"video": {
"height": 1080,
"codec": "h264",
"bitrate_mode": "vbr",
"overrides": {
"bitrate_kb": "source.video.bitrate_kb >= 5000 ? 5000 : 3000",
"max_bitrate_kb": "bitrate_kb * 1.20"
}
},
"audio": [
{
"codec": "aac_lc",
"bitrate_kb": 128,
"channels": 2
}
]
}
]
The example sets the output bitrate depending on what the input bitrate is. It also sets the maximum output bitrate to be 20% higher than the base bitrate.
You can’t use ternary operators directly as settings, they must be used in the special overrides
object as shown above. If a parameter is specified in the video
object itself AND in the overrides
object, the overrides
value will take precedent.
Target Overrides based on Analyze Task Results
If an analyze task is run prior to a transcode task, the resulting values from the analyze task can be accessed and used in a target override.
The example below has an analyze task for detecting areas of silence in the source. This is followed by a transcode task in which an override exists to set trim values. In this case, the override will apply:
- a trim inpoint for the target at one second before the source’s first section of silence ends
"trim.inpoint_sec": "deep_properties.audio[0].silence.leading_sec - 1"
- a trim outpoint after one second of silence has passed in the final section of silence in the source.
"trim.outpoint_sec": "deep_properties.audio[0].silence.events.last()['begin'] + 1"
The resulting output file will have 1-second “handles” of silence before and after audio content.
{
"uid": "analyze_task",
"kind": "analyze",
"payload": {
"options": {
"response_version": 2
},
"general_properties": {
"enabled": true
},
"deep_properties": {
"audio": {
"silence": {
"enabled": true,
"is_optional": true,
"noise_db": -60,
"duration_sec": 3
}
}
}
}
},
{
"uid": "transcode_task",
"kind": "transcode",
"task": {
"retry_method": "fail"
},
"payload": {
"location": {
"storage_provider": "s3",
"path": "{{destination_path}}"
},
"targets": [
{
"overrides": {
"trim.inpoint_sec": "deep_properties.audio[0].silence.leading_sec - 1",
"trim.outpoint_sec": "deep_properties.audio[0].silence.events.last()['begin'] + 1"
},
"container": {
"kind": "mp4" ...
Example Conditions
The include_conditions
and ternary operators use conditions that evaluate as true
or false
. There are many different conditions that can be evaluated. Conditions can use math expressions from the math.js library (https://mathjs.org).
Combining Multiple Conditions and Making Comparisons
You can use logical operators like ||
and &&
(OR and AND) to combine multiple conditions.
You can use the following to make comparisons:
==
(equals)!=
(does not equal)>
(greater than)<
(less than)>=
(greater than or equal to)<=
(less than or equal to)ft_compare(A, B)
(compare to floating point values)equalText(A, B)
(compare two string values)
See examples below.
Video Conditions
NOTE: Depending on container type and codec type, some options may not be available
source.video.bitrate_kb == 30000
- Is the source video’s bitrate exactly 30 mbps
(abs(source.video.bitrate_kb - 30000)<3000)
- Is the source video’s bitrate within 3000k of 30000k
source.video.bitrate_kb >= 20000 && source.video.bitrate_kb <= 30000
- Is the source video’s bitrate between 20 mbps and 30mbps
source.video.width == 720
- Is the source video’s with 720 pixels
source.video.height == 512
- Is the source video’s height exactly 512 pixels
ft_compare(source.video.frame_rate, 29.97) == 0
- This is how you compare frame rates. This will round floating point values. If the frame rates match,
ft_compare
returns0
so this expression istrue
if the source frame rate is29.97
- This is how you compare frame rates. This will round floating point values. If the frame rates match,
equalText(source.container.kind, 'mxf')
- Is the container of kind
mxf
.equalText
is the way to compare 2 string values
- Is the container of kind
equalText(source.video.codec, 'mpeg2')
- Is the video codec
mpeg2
- Is the video codec
equalText(source.video.chroma_format, 'yuv422p')
- Is the pixel format
yuv422p
- Is the pixel format
exists(source.video.bitrate_mode) && equalText(source.video.bitrate_mode, 'cbr')
- Is the source video constant bitrate
general_properties.container.duration_sec >= 30
- Is the source video 30 seconds or greater in duration
equalText(source.video.interlace_mode, 'progressive')
- Is the source video progressive scan
Audio Conditions
NOTE: We often talk about audio tracks as “track 1” for the first track. In programming, we start with a 0-index so the first track is the 0th track
exists(source.audio)
- Does the source contain audio
source.audio.length == 2
- (where
length
represents the number of audio tracks present)
- (where
exists(source.audio) && source.audio.length == 2
- Does the source’s audio have 2 tracks
- NOTE: It is important to first check if the source has audio, otherwise this check will error on video-only sources
source.audio[0].channels == 2
- Does the source’s first audio track have 2 channels
source.audio[0].sample_size == 16
- Is the source’s first audio track 16 bit
source.audio[0].sample_rate == 48000
- Is the source’s first audio sample rate 48k
equalText(source.audio[0].codec, 'pcm')
- Is the first audio track’s codec
pcm
. The[0]
indicates the first audio track
- Is the first audio track’s codec
Captions
NOTE: These are based on metadata in the file. It is possible that captions may exist even if the checks fail
source.subtitle.length > 0
- are text tracks present?
equalText(source.subtitle[0].format, 'EIA-608')
- the
[0]
indicates the first text track)
- the
Timecode
source.timecode.length > 0
- Does this source contain 1 or more timecodes.
length
represents the number of timecode tracks present
- Does this source contain 1 or more timecodes.
equalText(source.timecode[0].start_value, '01:00:00;00')
- Does the source have timecode starting at 1 hour (dropframe). The
[0]
indicates the first timecode track - NOTE: the semicolon seconds/frames separator matters. If the timecode is ndf, the character will be a colon
- Does the source have timecode starting at 1 hour (dropframe). The
Quality Control
The same conditions can be used in a qc
task. See our Tutorial on Analysis and QC for further examples.
Examples
In the following example, based on the include_conditions
example above, will behave differently depending on the source. On a 720p source, only 2 outputs will be created. With a 1080p or larger source, 3 outputs will be created. Additionally, the second track of audio is conditionally added to the outputs based on the source. The same logic could be applied discrete audio and video output as well.