Skip to content

Commit dc646e1

Browse files
committed
feat: support for specifiying custom filters for split,scale,crop,pad
When doing hardware encoding, it can be desirable to use hardware filters for split, scaling, cropping and padding. This commits add support for specifying replacements for the standard filters in a profile. It is required that the filter specified supports the parameters used by encore. Signed-off-by: Gustav Grusell <gustav.grusell@eyevinn.se>
1 parent 26f09d8 commit dc646e1

File tree

15 files changed

+212
-26
lines changed

15 files changed

+212
-26
lines changed

checks.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ jacocoTestCoverageVerification {
1515
'*QueueService.migrateQueues()',
1616
'*.ShutdownHandler.*',
1717
'*FfmpegExecutor.runFfmpeg$lambda$?(java.lang.Process)',
18+
'*FilterSettings.*',
1819
]
1920
limit {
2021
counter = 'LINE'

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/AudioEncode.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ data class AudioEncode(
3232
override val enabled: Boolean = true,
3333
) : AudioEncoder() {
3434

35-
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
35+
override fun getOutput(
36+
job: EncoreJob,
37+
encodingProperties: EncodingProperties,
38+
filterSettings: FilterSettings,
39+
): Output? {
3640
val outputName = "${job.baseName}$suffix.$format"
3741
if (!enabled) {
3842
return logOrThrow("$outputName is disabled. Skipping...")

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/OutputProducer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,5 @@ import se.svt.oss.encore.model.output.Output
2121
JsonSubTypes.Type(value = ThumbnailMapEncode::class, name = "ThumbnailMapEncode"),
2222
)
2323
interface OutputProducer {
24-
fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output?
24+
fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties, filterSettings: FilterSettings): Output?
2525
}

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/Profile.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,26 @@ data class Profile(
1010
val encodes: List<OutputProducer>,
1111
val scaling: String? = "bicubic",
1212
val deinterlaceFilter: String = "yadif",
13+
val filterSettings: FilterSettings = FilterSettings(),
1314
val joinSegmentParams: LinkedHashMap<String, Any?> = linkedMapOf(),
1415
)
16+
17+
data class FilterSettings(
18+
/**
19+
* The splitFilter property will be treated differently depending on if the values contains a '=' or not.
20+
* If no '=' is included, the value is treated as the name of the filter to use and something like
21+
* 'SPLITFILTERVALUE=N[ou1][out2]...' will be added to the filtergraph, where N is the number of
22+
* relevant outputs in the profile.
23+
* If an '=' is included, the value is assumed to already include the size parameters and something like
24+
* 'SPLITFILTERVALUE[ou1][out2]...' will be added to the filtergraph. Care must be taken to ensure that the
25+
* size parameters match the number of relevant outputs in the profile.
26+
* This latter form of specifying the split filter can be useful for
27+
* certain custom split filters that allow extra parameters, ie ni_quadra_split filter for netinit quadra
28+
* cards which allows access to scaled output from the decoder.
29+
*/
30+
val splitFilter: String = "split",
31+
val scaleFilter: String = "scale",
32+
val scaleFilterParams: LinkedHashMap<String, String> = linkedMapOf(),
33+
val cropFilter: String = "crop",
34+
val padFilter: String = "pad",
35+
)

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/SimpleAudioEncode.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ data class SimpleAudioEncode(
2323
val format: String = "mp4",
2424
val inputLabel: String = DEFAULT_AUDIO_LABEL,
2525
) : AudioEncoder() {
26-
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
26+
override fun getOutput(
27+
job: EncoreJob,
28+
encodingProperties: EncodingProperties,
29+
filterSettings: FilterSettings,
30+
): Output? {
2731
val outputName = "${job.baseName}$suffix.$format"
2832
if (!enabled) {
2933
return logOrThrow("$outputName is disabled. Skipping...")

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/ThumbnailEncode.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,11 @@ data class ThumbnailEncode(
3030
val decodeOutput: Int? = null,
3131
) : OutputProducer {
3232

33-
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
33+
override fun getOutput(
34+
job: EncoreJob,
35+
encodingProperties: EncodingProperties,
36+
filterSettings: FilterSettings,
37+
): Output? {
3438
if (!enabled) {
3539
return logOrThrow("Thumbnail with suffix $suffix is disabled. Skipping...")
3640
}

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/ThumbnailMapEncode.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ data class ThumbnailMapEncode(
3333
val decodeOutput: Int? = null,
3434
) : OutputProducer {
3535

36-
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
36+
override fun getOutput(
37+
job: EncoreJob,
38+
encodingProperties: EncodingProperties,
39+
filterSettings: FilterSettings,
40+
): Output? {
3741
if (!enabled) {
3842
return logOrThrow("Thumbnail map with suffix $suffix is disabled. Skipping...")
3943
}

encore-common/src/main/kotlin/se/svt/oss/encore/model/profile/VideoEncode.kt

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ interface VideoEncode : OutputProducer {
3838
val cropTo: FractionString?
3939
val padTo: FractionString?
4040

41-
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
41+
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties, filterSettings: FilterSettings): Output? {
4242
if (!enabled) {
4343
log.info { "Encode $suffix is not enabled. Skipping." }
4444
return null
4545
}
4646
val audioEncodesToUse = audioEncodes.ifEmpty { listOfNotNull(audioEncode) }
47-
val audio = audioEncodesToUse.flatMap { it.getOutput(job, encodingProperties)?.audioStreams.orEmpty() }
47+
val audio = audioEncodesToUse.flatMap { it.getOutput(job, encodingProperties, filterSettings)?.audioStreams.orEmpty() }
4848
val videoInput = job.inputs.videoInput(inputLabel)
4949
?: return logOrThrow("No valid video input with label $inputLabel!")
5050
return Output(
@@ -54,7 +54,7 @@ interface VideoEncode : OutputProducer {
5454
firstPassParams = firstPassParams().toParams(),
5555
inputLabels = listOf(inputLabel),
5656
twoPass = twoPass,
57-
filter = videoFilter(job.debugOverlay, encodingProperties, videoInput),
57+
filter = videoFilter(job.debugOverlay, encodingProperties, videoInput, filterSettings),
5858
),
5959
audioStreams = audio,
6060
output = "${job.baseName}$suffix.$format",
@@ -80,10 +80,11 @@ interface VideoEncode : OutputProducer {
8080
debugOverlay: Boolean,
8181
encodingProperties: EncodingProperties,
8282
videoInput: VideoIn,
83+
filterSettings: FilterSettings,
8384
): String? {
8485
val videoFilters = mutableListOf<String>()
8586
cropTo?.toFraction()?.let {
86-
videoFilters.add("crop=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
87+
videoFilters.add("${filterSettings.cropFilter}=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
8788
}
8889
var scaleToWidth = width
8990
var scaleToHeight = height
@@ -100,13 +101,31 @@ interface VideoEncode : OutputProducer {
100101
scaleToHeight = width
101102
}
102103
if (scaleToWidth != null && scaleToHeight != null) {
103-
videoFilters.add("scale=$scaleToWidth:$scaleToHeight:force_original_aspect_ratio=decrease:force_divisible_by=2")
104+
val scaleParams = listOf(
105+
"$scaleToWidth",
106+
"$scaleToHeight",
107+
) + (
108+
linkedMapOf<String, String>(
109+
"force_original_aspect_ratio" to "decrease",
110+
"force_divisible_by" to "2",
111+
) + filterSettings.scaleFilterParams
112+
)
113+
.map { "${it.key}=${it.value}" }
114+
videoFilters.add(
115+
"${filterSettings.scaleFilter}=${scaleParams.joinToString(":") }",
116+
)
104117
videoFilters.add("setsar=1/1")
105118
} else if (scaleToWidth != null || scaleToHeight != null) {
106-
videoFilters.add("scale=${scaleToWidth ?: -2}:${scaleToHeight ?: -2}")
119+
val filterParams = listOf(
120+
scaleToWidth?.toString() ?: "-2",
121+
scaleToHeight?.toString() ?: "-2",
122+
) + filterSettings.scaleFilterParams.map { "${it.key}=${it.value}" }
123+
videoFilters.add(
124+
"${filterSettings.scaleFilter}=${filterParams.joinToString(":") }",
125+
)
107126
}
108127
padTo?.toFraction()?.let {
109-
videoFilters.add("pad=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
128+
videoFilters.add("${filterSettings.padFilter}=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
110129
}
111130
filters?.let { videoFilters.addAll(it) }
112131
if (debugOverlay) {

encore-common/src/main/kotlin/se/svt/oss/encore/process/CommandBuilder.kt

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,8 @@ class CommandBuilder(
145145
log.debug { "No video outputs for video input ${input.videoLabel}" }
146146
return@mapIndexedNotNull null
147147
}
148-
val split = "split=${splits.size}${splits.joinToString("")}"
148+
// val split = "split=${splits.size}${splits.joinToString("")}"
149+
val split = splitFilter(splits)
149150
val analyzed = input.analyzedVideo
150151
val globalVideoFilters = globalVideoFilters(input, analyzed)
151152
val filters = (globalVideoFilters + split).joinToString(",")
@@ -163,6 +164,17 @@ class CommandBuilder(
163164
return videoSplits + streamFilters
164165
}
165166

167+
private fun splitFilter(splits: List<String>): String {
168+
val splitFilter = profile.filterSettings.splitFilter
169+
170+
if (splitFilter.find { it == '=' } != null) {
171+
// here we assume the size of the split is already included in the
172+
// custom split filter.
173+
return "${splitFilter}${splits.joinToString("")}"
174+
}
175+
return "$splitFilter=${splits.size}${splits.joinToString("")}"
176+
}
177+
166178
private fun VideoStreamEncode?.usesInput(input: VideoIn) =
167179
this?.inputLabels?.contains(input.videoLabel) == true
168180

@@ -189,6 +201,7 @@ class CommandBuilder(
189201

190202
private fun globalVideoFilters(input: VideoIn, videoFile: VideoFile): List<String> {
191203
val filters = mutableListOf<String>()
204+
val filterSettings = profile.filterSettings
192205
val videoStream = videoFile.highestBitrateVideoStream
193206
if (videoStream.isInterlaced) {
194207
log.debug { "Video input ${input.videoLabel} is interlaced. Applying deinterlace filter." }
@@ -203,16 +216,16 @@ class CommandBuilder(
203216
?: videoStream.displayAspectRatio?.toFractionOrNull()
204217
?: defaultAspectRatio
205218
filters.add("setdar=${dar.stringValue()}")
206-
filters.add("scale=iw*sar:ih")
219+
filters.add("${filterSettings.scaleFilter}=iw*sar:ih")
207220
} else if (videoStream.sampleAspectRatio?.toFractionOrNull() == null) {
208221
filters.add("setsar=1/1")
209222
}
210223

211224
input.cropTo?.toFraction()?.let {
212-
filters.add("crop=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
225+
filters.add("${filterSettings.cropFilter}=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
213226
}
214227
input.padTo?.toFraction()?.let {
215-
filters.add("pad=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
228+
filters.add("${filterSettings.padFilter}=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
216229
}
217230
return filters + input.videoFilters
218231
}

encore-common/src/main/kotlin/se/svt/oss/encore/service/FfmpegExecutor.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class FfmpegExecutor(
4949
it.getOutput(
5050
encoreJob,
5151
encoreProperties.encoding,
52+
profile.filterSettings,
5253
)
5354
}
5455
check(outputs.isNotEmpty()) {

0 commit comments

Comments
 (0)