Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions checks.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ jacocoTestCoverageVerification {
'*QueueService.migrateQueues()',
'*.ShutdownHandler.*',
'*FfmpegExecutor.runFfmpeg$lambda$7(java.lang.Process)',
'*FilterSettings.*',
'*.FfmpegExecutor.getProgress(java.lang.Double, java.lang.String)'
]
limit {
counter = 'LINE'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,11 @@ data class AudioEncode(
val inputLabel: String = DEFAULT_AUDIO_LABEL,
) : AudioEncoder() {

override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
override fun getOutput(
job: EncoreJob,
encodingProperties: EncodingProperties,
filterSettings: FilterSettings,
): Output? {
val outputName = "${job.baseName}$suffix.$format"
val audioIn = job.inputs.audioInput(inputLabel)
?: return logOrThrow("Can not generate $outputName! No audio input with label '$inputLabel'.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ import se.svt.oss.encore.model.output.Output
JsonSubTypes.Type(value = ThumbnailMapEncode::class, name = "ThumbnailMapEncode"),
)
interface OutputProducer {
fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output?
fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties, filterSettings: FilterSettings): Output?
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,26 @@ data class Profile(
val encodes: List<OutputProducer>,
val scaling: String? = "bicubic",
val deinterlaceFilter: String = "yadif",
val filterSettings: FilterSettings = FilterSettings(),
val joinSegmentParams: LinkedHashMap<String, Any?> = linkedMapOf(),
)

data class FilterSettings(
/**
* The splitFilter property will be treated differently depending on if the values contains a '=' or not.
* If no '=' is included, the value is treated as the name of the filter to use and something like
* 'SPLITFILTERVALUE=N[ou1][out2]...' will be added to the filtergraph, where N is the number of
* relevant outputs in the profile.
* If an '=' is included, the value is assumed to already include the size parameters and something like
* 'SPLITFILTERVALUE[ou1][out2]...' will be added to the filtergraph. Care must be taken to ensure that the
* size parameters match the number of relevant outputs in the profile.
* This latter form of specifying the split filter can be useful for
* certain custom split filters that allow extra parameters, ie ni_quadra_split filter for netinit quadra
* cards which allows access to scaled output from the decoder.
*/
val splitFilter: String = "split",
val scaleFilter: String = "scale",
val scaleFilterParams: LinkedHashMap<String, String> = linkedMapOf(),
val cropFilter: String = "crop",
val padFilter: String = "pad",
)
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ data class SimpleAudioEncode(
val format: String = "mp4",
val inputLabel: String = DEFAULT_AUDIO_LABEL,
) : AudioEncoder() {
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
override fun getOutput(
job: EncoreJob,
encodingProperties: EncodingProperties,
filterSettings: FilterSettings,
): Output? {
val outputName = "${job.baseName}$suffix.$format"
job.inputs.analyzedAudio(inputLabel)
?: return logOrThrow("Can not generate $outputName! No audio input with label '$inputLabel'.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ data class ThumbnailEncode(
val decodeOutput: Int? = null,
) : OutputProducer {

override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
override fun getOutput(
job: EncoreJob,
encodingProperties: EncodingProperties,
filterSettings: FilterSettings,
): Output? {
if (job.segmentLength != null) {
return logOrThrow("Thumbnail is not supported in segmented encode!")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,11 @@ data class ThumbnailMapEncode(
val decodeOutput: Int? = null,
) : OutputProducer {

override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
override fun getOutput(
job: EncoreJob,
encodingProperties: EncodingProperties,
filterSettings: FilterSettings,
): Output? {
if (job.segmentLength != null) {
return logOrThrow("Thumbnail map is not supported in segmented encode!")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ interface VideoEncode : OutputProducer {
val codec: String
val inputLabel: String

override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties, filterSettings: FilterSettings): Output? {
val audioEncodesToUse = audioEncodes.ifEmpty { listOfNotNull(audioEncode) }
val audio = audioEncodesToUse.flatMap { it.getOutput(job, encodingProperties)?.audioStreams.orEmpty() }
val audio = audioEncodesToUse.flatMap { it.getOutput(job, encodingProperties, filterSettings)?.audioStreams.orEmpty() }
val videoInput = job.inputs.videoInput(inputLabel)
?: throw RuntimeException("No valid video input with label $inputLabel!")
return Output(
Expand All @@ -40,7 +40,7 @@ interface VideoEncode : OutputProducer {
firstPassParams = firstPassParams().toParams(),
inputLabels = listOf(inputLabel),
twoPass = twoPass,
filter = videoFilter(job.debugOverlay, encodingProperties, videoInput),
filter = videoFilter(job.debugOverlay, encodingProperties, videoInput, filterSettings),
),
audioStreams = audio,
output = "${job.baseName}$suffix.$format",
Expand All @@ -66,6 +66,7 @@ interface VideoEncode : OutputProducer {
debugOverlay: Boolean,
encodingProperties: EncodingProperties,
videoInput: VideoIn,
filterSettings: FilterSettings,
): String? {
val videoFilters = mutableListOf<String>()
var scaleToWidth = width
Expand All @@ -83,10 +84,28 @@ interface VideoEncode : OutputProducer {
scaleToHeight = width
}
if (scaleToWidth != null && scaleToHeight != null) {
videoFilters.add("scale=$scaleToWidth:$scaleToHeight:force_original_aspect_ratio=decrease:force_divisible_by=2")
val scaleParams = listOf(
"$scaleToWidth",
"$scaleToHeight",
) + (
linkedMapOf<String, String>(
"force_original_aspect_ratio" to "decrease",
"force_divisible_by" to "2",
) + filterSettings.scaleFilterParams
)
.map { "${it.key}=${it.value}" }
videoFilters.add(
"${filterSettings.scaleFilter}=${scaleParams.joinToString(":") }",
)
videoFilters.add("setsar=1/1")
} else if (scaleToWidth != null || scaleToHeight != null) {
videoFilters.add("scale=${scaleToWidth ?: -2}:${scaleToHeight ?: -2}")
val filterParams = listOf(
scaleToWidth?.toString() ?: "-2",
scaleToHeight?.toString() ?: "-2",
) + filterSettings.scaleFilterParams.map { "${it.key}=${it.value}" }
videoFilters.add(
"${filterSettings.scaleFilter}=${filterParams.joinToString(":") }",
)
}
filters?.let { videoFilters.addAll(it) }
if (debugOverlay) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ class CommandBuilder(
log.debug { "No video outputs for video input ${input.videoLabel}" }
return@mapIndexedNotNull null
}
val split = "split=${splits.size}${splits.joinToString("")}"
val split = splitFilter(splits)
val analyzed = input.analyzedVideo
val globalVideoFilters = globalVideoFilters(input, analyzed)
val filters = (globalVideoFilters + split).joinToString(",")
Expand All @@ -163,6 +163,17 @@ class CommandBuilder(
return videoSplits + streamFilters
}

private fun splitFilter(splits: List<String>): String {
val splitFilter = profile.filterSettings.splitFilter

if (splitFilter.find { it == '=' } != null) {
// here we assume the size of the split is already included in the
// custom split filter.
return "${splitFilter}${splits.joinToString("")}"
}
return "$splitFilter=${splits.size}${splits.joinToString("")}"
}

private fun VideoStreamEncode?.usesInput(input: VideoIn) =
this?.inputLabels?.contains(input.videoLabel) == true

Expand All @@ -189,6 +200,7 @@ class CommandBuilder(

private fun globalVideoFilters(input: VideoIn, videoFile: VideoFile): List<String> {
val filters = mutableListOf<String>()
val filterSettings = profile.filterSettings
val videoStream = videoFile.highestBitrateVideoStream
if (videoStream.isInterlaced) {
log.debug { "Video input ${input.videoLabel} is interlaced. Applying deinterlace filter." }
Expand All @@ -203,16 +215,16 @@ class CommandBuilder(
?: videoStream.displayAspectRatio?.toFractionOrNull()
?: defaultAspectRatio
filters.add("setdar=${dar.stringValue()}")
filters.add("scale=iw*sar:ih")
filters.add("${filterSettings.scaleFilter}=iw*sar:ih")
} else if (videoStream.sampleAspectRatio?.toFractionOrNull() == null) {
filters.add("setsar=1/1")
}

input.cropTo?.toFraction()?.let {
filters.add("crop=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
filters.add("${filterSettings.cropFilter}=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
}
input.padTo?.toFraction()?.let {
filters.add("pad=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
filters.add("${filterSettings.padFilter}=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
}
return filters + input.videoFilters
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class FfmpegExecutor(
it.getOutput(
encoreJob,
encoreProperties.encoding,
profile.filterSettings,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class AudioEncodeTest {
@Test
fun `no audio streams throws exception`() {
assertThatThrownBy {
audioEncode.getOutput(job(), EncodingProperties())
audioEncode.getOutput(job(), EncodingProperties(), FilterSettings())
}.isInstanceOf(RuntimeException::class.java)
.hasMessageContaining("No audio streams in input")
}
Expand All @@ -42,6 +42,7 @@ class AudioEncodeTest {
audioEncode.getOutput(
job,
EncodingProperties(audioMixPresets = mapOf("default" to AudioMixPreset(fallbackToAuto = false))),
FilterSettings(),
)
}.isInstanceOf(RuntimeException::class.java)
.hasMessage("Audio layout of audio input 'main' is not supported!")
Expand All @@ -52,6 +53,7 @@ class AudioEncodeTest {
val output = audioEncode.getOutput(
job(getAudioStream(6)),
EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_aac_stereo.mp4")
Expand Down Expand Up @@ -88,6 +90,7 @@ class AudioEncodeTest {
),
),
),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_aac_stereo.mp4")
Expand Down Expand Up @@ -130,6 +133,7 @@ class AudioEncodeTest {
),
),
),
FilterSettings(),
)
assertThat(output).isNull()
}
Expand All @@ -150,6 +154,7 @@ class AudioEncodeTest {
),
),
),
FilterSettings(),
)
}.isInstanceOf(RuntimeException::class.java)
.hasMessageContaining("No audio mix preset for 'de': 5.1 -> stereo")
Expand All @@ -158,15 +163,15 @@ class AudioEncodeTest {
@Test
fun `unmapped input optional returns null`() {
val audioEncodeLocal = audioEncode.copy(inputLabel = "other", optional = true)
val output = audioEncodeLocal.getOutput(job(getAudioStream(6)), EncodingProperties())
val output = audioEncodeLocal.getOutput(job(getAudioStream(6)), EncodingProperties(), FilterSettings())
assertThat(output).isNull()
}

@Test
fun `unmapped input not optional throws`() {
val audioEncodeLocal = audioEncode.copy(inputLabel = "other")
assertThatThrownBy {
audioEncodeLocal.getOutput(job(getAudioStream(6)), EncodingProperties())
audioEncodeLocal.getOutput(job(getAudioStream(6)), EncodingProperties(), FilterSettings())
}.isInstanceOf(RuntimeException::class.java)
.hasMessage("Can not generate test_aac_stereo.mp4! No audio input with label 'other'.")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ class ThumbnailEncodeTest {
val output = encode.getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_thumb%02d.jpg")
Expand All @@ -48,6 +49,7 @@ class ThumbnailEncodeTest {
thumbnailTime = 5.0,
),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_thumb%02d.jpg")
Expand All @@ -71,6 +73,7 @@ class ThumbnailEncodeTest {
val output = selectorEncode.getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)

assertThat(output)
Expand All @@ -94,6 +97,7 @@ class ThumbnailEncodeTest {
duration = 4.0,
),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_thumb%02d.jpg")
Expand Down Expand Up @@ -124,6 +128,7 @@ class ThumbnailEncodeTest {
),
),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasOutput("test_thumb%02d.jpg")
Expand All @@ -143,6 +148,7 @@ class ThumbnailEncodeTest {
val output = encode.copy(inputLabel = "other", optional = true).getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output).isNull()
}
Expand All @@ -153,6 +159,7 @@ class ThumbnailEncodeTest {
encode.copy(inputLabel = "other", optional = false).getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
}.isInstanceOf(RuntimeException::class.java)
.hasMessageContaining("No video input with label other!")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class ThumbnailMapEncodeTest {

@Test
fun `correct output`() {
val output = encode.getOutput(defaultEncoreJob(), EncodingProperties())
val output = encode.getOutput(defaultEncoreJob(), EncodingProperties(), FilterSettings())
assertThat(output)
.hasNoAudioStreams()
.hasId("_12x20_160x90_thumbnail_map.jpg")
Expand All @@ -44,6 +44,7 @@ class ThumbnailMapEncodeTest {
defaultEncoreJob()
.copy(seekTo = 1.0, duration = 5.0),
EncodingProperties(),
FilterSettings(),
)
assertThat(output)
.hasNoAudioStreams()
Expand All @@ -63,6 +64,7 @@ class ThumbnailMapEncodeTest {
val output = encode.copy(inputLabel = "other", optional = true).getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
assertThat(output).isNull()
}
Expand All @@ -73,6 +75,7 @@ class ThumbnailMapEncodeTest {
encode.copy(inputLabel = "other", optional = false).getOutput(
job = defaultEncoreJob(),
encodingProperties = EncodingProperties(),
FilterSettings(),
)
}.isInstanceOf(RuntimeException::class.java)
.hasMessageContaining("No input with label other!")
Expand Down
Loading