Skip to content

Commit f0fec12

Browse files
committed
feat: optional external audio mix presets
Signed-off-by: Fredrik Lundkvist <fredrik.lundkvist@eyevinn.se>
1 parent 92d62cc commit f0fec12

File tree

6 files changed

+125
-1
lines changed

6 files changed

+125
-1
lines changed

encore-common/src/main/kotlin/se/svt/oss/encore/config/EncodingProperties.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
package se.svt.oss.encore.config
66

77
import org.springframework.boot.context.properties.NestedConfigurationProperty
8+
import org.springframework.core.io.Resource
89
import se.svt.oss.encore.model.profile.ChannelLayout
910

1011
data class EncodingProperties(
12+
val audioMixPresetLocation: Resource? = null,
1113
@NestedConfigurationProperty
1214
val audioMixPresets: Map<String, AudioMixPreset> = mapOf("default" to AudioMixPreset()),
1315
@NestedConfigurationProperty

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import se.svt.oss.encore.model.input.maxDuration
1414
import se.svt.oss.encore.model.mediafile.toParams
1515
import se.svt.oss.encore.process.CommandBuilder
1616
import se.svt.oss.encore.process.createTempDir
17+
import se.svt.oss.encore.service.audiomix.AudiomixPresetService
1718
import se.svt.oss.encore.service.profile.ProfileService
1819
import se.svt.oss.mediaanalyzer.MediaAnalyzer
1920
import se.svt.oss.mediaanalyzer.file.MediaFile
@@ -28,6 +29,7 @@ private val log = KotlinLogging.logger { }
2829
class FfmpegExecutor(
2930
private val mediaAnalyzer: MediaAnalyzer,
3031
private val profileService: ProfileService,
32+
private val audioMixService: AudiomixPresetService,
3133
private val encoreProperties: EncoreProperties,
3234
) {
3335

@@ -45,10 +47,14 @@ class FfmpegExecutor(
4547
): List<MediaFile> {
4648
ShutdownHandler.checkShutdown()
4749
val profile = profileService.getProfile(encoreJob)
50+
val audioMixPresets = audioMixService.getAudioMixPresets()
51+
val encodingProperties = encoreProperties.encoding.copy(
52+
audioMixPresets = audioMixPresets,
53+
)
4854
val outputs = profile.encodes.mapNotNull {
4955
it.getOutput(
5056
encoreJob,
51-
encoreProperties.encoding,
57+
encodingProperties,
5258
)
5359
}
5460

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package se.svt.oss.encore.service.audiomix
2+
3+
import com.fasterxml.jackson.core.JsonProcessingException
4+
import com.fasterxml.jackson.databind.DeserializationFeature
5+
import com.fasterxml.jackson.databind.ObjectMapper
6+
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper
7+
import com.fasterxml.jackson.module.kotlin.readValue
8+
import io.github.oshai.kotlinlogging.KotlinLogging
9+
import org.springframework.aot.hint.annotation.RegisterReflectionForBinding
10+
import org.springframework.stereotype.Service
11+
import se.svt.oss.encore.config.AudioMixPreset
12+
import se.svt.oss.encore.config.EncoreProperties
13+
import java.io.File
14+
import java.util.Locale
15+
16+
private val log = KotlinLogging.logger { }
17+
18+
@Service
19+
@RegisterReflectionForBinding(AudioMixPreset::class)
20+
class AudiomixPresetService(
21+
private val objectMapper: ObjectMapper,
22+
private val encoreProperties: EncoreProperties,
23+
) {
24+
private val yamlMapper: YAMLMapper =
25+
YAMLMapper().findAndRegisterModules()
26+
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) as YAMLMapper
27+
28+
private fun mapper() =
29+
if (encoreProperties.encoding.audioMixPresetLocation?.filename?.let {
30+
File(it).extension.lowercase(Locale.getDefault()) in setOf("yml", "yaml")
31+
} == true
32+
) {
33+
yamlMapper
34+
} else {
35+
objectMapper
36+
}
37+
38+
fun getAudioMixPresets(): Map<String, AudioMixPreset> = try {
39+
log.debug { "Reading presets from ${encoreProperties.encoding.audioMixPresetLocation}" }
40+
encoreProperties.encoding.audioMixPresetLocation?.let { location ->
41+
mapper().readValue<Map<String, AudioMixPreset>>(location.inputStream)
42+
} ?: encoreProperties.encoding.audioMixPresets
43+
} catch (e: JsonProcessingException) {
44+
throw RuntimeException("Error parsing audio mix presets ${e.message}")
45+
}
46+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package se.svt.oss.encore.service.audiomix
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper
4+
import org.assertj.core.api.Assertions.assertThat
5+
import org.assertj.core.api.Assertions.assertThatThrownBy
6+
import org.junit.jupiter.api.BeforeEach
7+
import org.junit.jupiter.api.Test
8+
import org.springframework.core.io.ClassPathResource
9+
import se.svt.oss.encore.config.EncodingProperties
10+
import se.svt.oss.encore.config.EncoreProperties
11+
import java.io.IOException
12+
13+
class AudioMixPresetServiceTest {
14+
15+
private lateinit var mixService: AudiomixPresetService
16+
private val objectMapper = ObjectMapper().findAndRegisterModules()
17+
private val encoreProperties =
18+
EncoreProperties(encoding = EncodingProperties(ClassPathResource("audiomixpreset/audio-mix-presets.yml")))
19+
20+
@BeforeEach
21+
internal fun setUp() {
22+
mixService = AudiomixPresetService(
23+
objectMapper,
24+
encoreProperties,
25+
)
26+
}
27+
28+
@Test
29+
fun `successfully parses existing and valid presets`() {
30+
val presets = mixService.getAudioMixPresets()
31+
assertThat(presets).hasSize(2)
32+
assertThat(presets["default"]).isNotNull
33+
assertThat(presets["de"]).isNotNull
34+
}
35+
36+
@Test
37+
fun `nonexistent preset throws error`() {
38+
mixService = AudiomixPresetService(
39+
objectMapper,
40+
encoreProperties.copy(
41+
encoding = encoreProperties.encoding.copy(
42+
audioMixPresetLocation = ClassPathResource(
43+
"i-dont-exist",
44+
),
45+
),
46+
),
47+
)
48+
assertThatThrownBy {
49+
mixService.getAudioMixPresets()
50+
}
51+
.isInstanceOf(IOException::class.java)
52+
.hasMessageStartingWith("class path resource [i-dont-exist] cannot be opened because it does not exist")
53+
}
54+
}

encore-common/src/test/resources/application-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ encore-settings:
1919
poll-delay: 1s
2020
shared-work-dir: ${java.io.tmpdir}/encore-shared
2121
encoding:
22+
audio-mix-preset-location: classpath:audiomixpreset/audio-mix-presets.yml
2223
default-channel-layouts:
2324
3: "3.0"
2425
audio-mix-presets:
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
default:
2+
default-pan:
3+
stereo: FL=FL+0.707107*FC+0.707107*BL+0.707107*SL|FR=FR+0.707107*FC+0.707107*BR+0.707107*SR
4+
pan-mapping:
5+
mono:
6+
stereo: FL=0.707*FC|FR=0.707*FC
7+
de:
8+
fallback-to-auto: false
9+
default-pan:
10+
stereo: FL<FL+1.5*FC+0.707107*BL+0.707107*SL|FR<FR+1.5*FC+0.707107*BR+0.707107*SR
11+
pan-mapping:
12+
"[5.1]":
13+
"[5.1]": c0=c0|c1=c1|c2<1.5*c2|c3=c3|c4=c4|c5=c5
14+
"[5.1(side)]":
15+
"[5.1]": c0=c0|c1=c1|c2<1.5*c2|c3=c3|c4=c4|c5=c5

0 commit comments

Comments
 (0)