Skip to content

Commit 26f09d8

Browse files
committed
Added properties to video encode:
* cropTo (fraction) - applied before scaling * padTo (fraction) - applied after scaling * optional (boolean) - skip output when no input matches inputLabel Added property to all outputs: * enabled (boolean) - skip output if true. Can be used with SpEL-expressions in profile
2 parents 58230f8 + f229c72 commit 26f09d8

File tree

18 files changed

+215
-7
lines changed

18 files changed

+215
-7
lines changed

checks.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jacocoTestCoverageVerification {
1414
'*QueueService.getQueue*',
1515
'*QueueService.migrateQueues()',
1616
'*.ShutdownHandler.*',
17-
'*FfmpegExecutor.runFfmpeg$lambda$7(java.lang.Process)',
17+
'*FfmpegExecutor.runFfmpeg$lambda$?(java.lang.Process)',
1818
]
1919
limit {
2020
counter = 'LINE'

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ data class AudioEncode(
2929
override val optional: Boolean = false,
3030
val format: String = "mp4",
3131
val inputLabel: String = DEFAULT_AUDIO_LABEL,
32+
override val enabled: Boolean = true,
3233
) : AudioEncoder() {
3334

3435
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
3536
val outputName = "${job.baseName}$suffix.$format"
37+
if (!enabled) {
38+
return logOrThrow("$outputName is disabled. Skipping...")
39+
}
3640
val audioIn = job.inputs.audioInput(inputLabel)
3741
?: return logOrThrow("Can not generate $outputName! No audio input with label '$inputLabel'.")
3842
val analyzed = audioIn.analyzedAudio

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ private val log = KotlinLogging.logger { }
1212
abstract class AudioEncoder : OutputProducer {
1313

1414
abstract val optional: Boolean
15+
abstract val enabled: Boolean
1516

1617
fun logOrThrow(message: String): Output? {
17-
if (optional) {
18+
if (optional || !enabled) {
1819
log.info { message }
1920
return null
2021
} else {

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package se.svt.oss.encore.model.profile
66

77
import se.svt.oss.encore.model.input.DEFAULT_VIDEO_LABEL
8+
import se.svt.oss.mediaanalyzer.file.FractionString
89

910
data class GenericVideoEncode(
1011
override val width: Int?,
@@ -18,4 +19,8 @@ data class GenericVideoEncode(
1819
override val format: String,
1920
override val codec: String,
2021
override val inputLabel: String = DEFAULT_VIDEO_LABEL,
22+
override val optional: Boolean = false,
23+
override val cropTo: FractionString? = null,
24+
override val padTo: FractionString? = null,
25+
override val enabled: Boolean = true,
2126
) : VideoEncode

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,15 @@ data class SimpleAudioEncode(
1919
val suffix: String = "_$codec",
2020
val params: LinkedHashMap<String, String> = linkedMapOf(),
2121
override val optional: Boolean = false,
22+
override val enabled: Boolean = false,
2223
val format: String = "mp4",
2324
val inputLabel: String = DEFAULT_AUDIO_LABEL,
2425
) : AudioEncoder() {
2526
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
2627
val outputName = "${job.baseName}$suffix.$format"
28+
if (!enabled) {
29+
return logOrThrow("$outputName is disabled. Skipping...")
30+
}
2731
job.inputs.analyzedAudio(inputLabel)
2832
?: return logOrThrow("Can not generate $outputName! No audio input with label '$inputLabel'.")
2933
val outParams = linkedMapOf<String, Any>()

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
@@ -25,11 +25,15 @@ data class ThumbnailEncode(
2525
val suffixZeroPad: Int = 2,
2626
val inputLabel: String = DEFAULT_VIDEO_LABEL,
2727
val optional: Boolean = false,
28+
val enabled: Boolean = true,
2829
val intervalSeconds: Double? = null,
2930
val decodeOutput: Int? = null,
3031
) : OutputProducer {
3132

3233
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
34+
if (!enabled) {
35+
return logOrThrow("Thumbnail with suffix $suffix is disabled. Skipping...")
36+
}
3337
if (job.segmentLength != null) {
3438
return logOrThrow("Thumbnail is not supported in segmented encode!")
3539
}
@@ -105,7 +109,7 @@ data class ThumbnailEncode(
105109
"select=${times.joinToString("+") { "(isnan(prev_pts)+lt(prev_pts*TB\\,$it))*gte(pts*TB\\,$it)" }}"
106110

107111
private fun logOrThrow(message: String): Output? {
108-
if (optional) {
112+
if (optional || !enabled) {
109113
log.info { message }
110114
return null
111115
} else {

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
@@ -26,13 +26,17 @@ data class ThumbnailMapEncode(
2626
val rows: Int = 20,
2727
val quality: Int = 5,
2828
val optional: Boolean = true,
29+
val enabled: Boolean = true,
2930
val suffix: String = "_${cols}x${rows}_${tileWidth}x${tileHeight}_thumbnail_map",
3031
val format: String = "jpg",
3132
val inputLabel: String = DEFAULT_VIDEO_LABEL,
3233
val decodeOutput: Int? = null,
3334
) : OutputProducer {
3435

3536
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
37+
if (!enabled) {
38+
return logOrThrow("Thumbnail map with suffix $suffix is disabled. Skipping...")
39+
}
3640
if (job.segmentLength != null) {
3741
return logOrThrow("Thumbnail map is not supported in segmented encode!")
3842
}
@@ -111,7 +115,7 @@ data class ThumbnailMapEncode(
111115
}
112116

113117
private fun logOrThrow(message: String): Output? {
114-
if (optional) {
118+
if (optional || !enabled) {
115119
log.info { message }
116120
return null
117121
} else {

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

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
package se.svt.oss.encore.model.profile
66

7+
import io.github.oshai.kotlinlogging.KotlinLogging
78
import org.apache.commons.math3.fraction.Fraction
89
import se.svt.oss.encore.config.EncodingProperties
910
import se.svt.oss.encore.model.EncoreJob
@@ -12,9 +13,14 @@ import se.svt.oss.encore.model.input.videoInput
1213
import se.svt.oss.encore.model.mediafile.toParams
1314
import se.svt.oss.encore.model.output.Output
1415
import se.svt.oss.encore.model.output.VideoStreamEncode
16+
import se.svt.oss.mediaanalyzer.file.FractionString
17+
import se.svt.oss.mediaanalyzer.file.stringValue
18+
import se.svt.oss.mediaanalyzer.file.toFraction
1519
import se.svt.oss.mediaanalyzer.file.toFractionOrNull
1620
import kotlin.math.absoluteValue
1721

22+
private val log = KotlinLogging.logger { }
23+
1824
interface VideoEncode : OutputProducer {
1925
val width: Int?
2026
val height: Int?
@@ -27,12 +33,20 @@ interface VideoEncode : OutputProducer {
2733
val format: String
2834
val codec: String
2935
val inputLabel: String
36+
val optional: Boolean
37+
val enabled: Boolean
38+
val cropTo: FractionString?
39+
val padTo: FractionString?
3040

3141
override fun getOutput(job: EncoreJob, encodingProperties: EncodingProperties): Output? {
42+
if (!enabled) {
43+
log.info { "Encode $suffix is not enabled. Skipping." }
44+
return null
45+
}
3246
val audioEncodesToUse = audioEncodes.ifEmpty { listOfNotNull(audioEncode) }
3347
val audio = audioEncodesToUse.flatMap { it.getOutput(job, encodingProperties)?.audioStreams.orEmpty() }
3448
val videoInput = job.inputs.videoInput(inputLabel)
35-
?: throw RuntimeException("No valid video input with label $inputLabel!")
49+
?: return logOrThrow("No valid video input with label $inputLabel!")
3650
return Output(
3751
id = "$suffix.$format",
3852
video = VideoStreamEncode(
@@ -68,11 +82,14 @@ interface VideoEncode : OutputProducer {
6882
videoInput: VideoIn,
6983
): String? {
7084
val videoFilters = mutableListOf<String>()
85+
cropTo?.toFraction()?.let {
86+
videoFilters.add("crop=min(iw\\,ih*${it.stringValue()}):min(ih\\,iw/(${it.stringValue()}))")
87+
}
7188
var scaleToWidth = width
7289
var scaleToHeight = height
7390
val videoStream = videoInput.analyzedVideo.highestBitrateVideoStream
7491
val isRotated90 = videoStream.rotation?.rem(180)?.absoluteValue == 90
75-
val outputDar = (videoInput.padTo ?: videoInput.cropTo)?.toFractionOrNull()
92+
val outputDar = (padTo ?: cropTo ?: videoInput.padTo ?: videoInput.cropTo)?.toFractionOrNull()
7693
?: (videoStream.displayAspectRatio?.toFractionOrNull() ?: Fraction(videoStream.width, videoStream.height))
7794
.let { if (isRotated90) it.reciprocal() else it }
7895
val outputIsPortrait = outputDar < Fraction.ONE
@@ -88,10 +105,22 @@ interface VideoEncode : OutputProducer {
88105
} else if (scaleToWidth != null || scaleToHeight != null) {
89106
videoFilters.add("scale=${scaleToWidth ?: -2}:${scaleToHeight ?: -2}")
90107
}
108+
padTo?.toFraction()?.let {
109+
videoFilters.add("pad=aspect=${it.stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2")
110+
}
91111
filters?.let { videoFilters.addAll(it) }
92112
if (debugOverlay) {
93113
videoFilters.add("drawtext=text=$suffix:fontcolor=white:fontsize=50:box=1:[email protected]:boxborderw=5:x=10:y=10")
94114
}
95115
return if (videoFilters.isEmpty()) null else videoFilters.joinToString(",")
96116
}
117+
118+
fun logOrThrow(message: String): Output? {
119+
if (optional) {
120+
log.info { message }
121+
return null
122+
} else {
123+
throw RuntimeException(message)
124+
}
125+
}
97126
}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package se.svt.oss.encore.model.profile
66

77
import com.fasterxml.jackson.annotation.JsonProperty
88
import se.svt.oss.encore.model.input.DEFAULT_VIDEO_LABEL
9+
import se.svt.oss.mediaanalyzer.file.FractionString
910

1011
data class X264Encode(
1112
override val width: Int?,
@@ -21,6 +22,10 @@ data class X264Encode(
2122
override val suffix: String,
2223
override val format: String = "mp4",
2324
override val inputLabel: String = DEFAULT_VIDEO_LABEL,
25+
override val optional: Boolean = false,
26+
override val cropTo: FractionString? = null,
27+
override val padTo: FractionString? = null,
28+
override val enabled: Boolean = true,
2429
) : X26XEncode() {
2530
override val codecParamName: String
2631
get() = "x264-params"

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package se.svt.oss.encore.model.profile
66

77
import com.fasterxml.jackson.annotation.JsonProperty
88
import se.svt.oss.encore.model.input.DEFAULT_VIDEO_LABEL
9+
import se.svt.oss.mediaanalyzer.file.FractionString
910

1011
data class X265Encode(
1112
override val width: Int?,
@@ -21,6 +22,10 @@ data class X265Encode(
2122
override val suffix: String,
2223
override val format: String = "mp4",
2324
override val inputLabel: String = DEFAULT_VIDEO_LABEL,
25+
override val optional: Boolean = false,
26+
override val cropTo: FractionString? = null,
27+
override val padTo: FractionString? = null,
28+
override val enabled: Boolean = true,
2429
) : X26XEncode() {
2530
override val codecParamName: String
2631
get() = "x265-params"

0 commit comments

Comments
 (0)