Skip to content

Commit 6998b83

Browse files
fhermanssonJohn Laurin (jola10)
authored andcommitted
add options for -xerror and other global params to EncodingProperties. Added environment variable ENCORE_TMPDIR to reliably configure temp directory.
1 parent 5c14a5e commit 6998b83

File tree

8 files changed

+49
-33
lines changed

8 files changed

+49
-33
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ data class EncodingProperties(
1212
val audioMixPresets: Map<String, AudioMixPreset> = mapOf("default" to AudioMixPreset()),
1313
@NestedConfigurationProperty
1414
val defaultChannelLayouts: Map<Int, ChannelLayout> = emptyMap(),
15-
val flipWidthHeightIfPortrait: Boolean = true
15+
val flipWidthHeightIfPortrait: Boolean = true,
16+
val exitOnError: Boolean = true,
17+
val globalParams: LinkedHashMap<String, Any?> = linkedMapOf(),
1618
)

encore-common/src/main/kotlin/se/svt/oss/encore/model/mediafile/Extensions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,4 +83,5 @@ fun AudioFile.selectAudioStream(index: Int?): AudioFile {
8383
fun Map<String, Any?>.toParams(): List<String> =
8484
flatMap { entry ->
8585
listOfNotNull("-${entry.key}", entry.value?.let { "$it" })
86+
.filterNot { it.isEmpty() }
8687
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ import se.svt.oss.encore.model.input.videoInput
1414
import se.svt.oss.encore.model.mediafile.toParams
1515
import se.svt.oss.encore.model.output.Output
1616
import se.svt.oss.encore.model.output.VideoStreamEncode
17+
import se.svt.oss.encore.process.createTempDir
1718
import se.svt.oss.mediaanalyzer.file.stringValue
18-
import kotlin.io.path.createTempDirectory
1919

2020
data class ThumbnailMapEncode(
2121
val tileWidth: Int = 160,
@@ -53,7 +53,7 @@ data class ThumbnailMapEncode(
5353
?.let { "gte(t\\,$it)*(isnan(prev_selected_t)+gt(floor((t-$it)/$interval)\\,floor((prev_selected_t-$it)/$interval)))" }
5454
?: "isnan(prev_selected_t)+gt(floor(t/$interval)\\,floor(prev_selected_t/$interval))"
5555

56-
val tempFolder = createTempDirectory(suffix).toFile()
56+
val tempFolder = createTempDir(suffix).toFile()
5757
tempFolder.deleteOnExit()
5858

5959
val pad = "aspect=${Fraction(tileWidth, tileHeight).stringValue()}:x=(ow-iw)/2:y=(oh-ih)/2"

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

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import se.svt.oss.encore.model.input.inputParams
1515
import se.svt.oss.encore.model.mediafile.AudioLayout
1616
import se.svt.oss.encore.model.mediafile.audioLayout
1717
import se.svt.oss.encore.model.mediafile.channelLayout
18+
import se.svt.oss.encore.model.mediafile.toParams
1819
import se.svt.oss.encore.model.output.AudioStreamEncode
1920
import se.svt.oss.encore.model.output.Output
2021
import se.svt.oss.encore.model.output.VideoStreamEncode
@@ -165,13 +166,15 @@ class CommandBuilder(
165166
val readDuration = encoreJob.duration?.let {
166167
it + (encoreJob.seekTo ?: 0.0)
167168
}
168-
return listOf(
169-
"ffmpeg",
170-
"-hide_banner",
171-
"-loglevel",
172-
"+level",
173-
"-y"
174-
) + inputs.inputParams(readDuration)
169+
return buildList {
170+
add("ffmpeg")
171+
if (encodingProperties.exitOnError) {
172+
add("-xerror")
173+
}
174+
addAll(encodingProperties.globalParams.toParams())
175+
addAll(listOf("-hide_banner", "-loglevel", "+level", "-y"))
176+
addAll(inputs.inputParams(readDuration))
177+
}
175178
}
176179

177180
private fun globalVideoFilters(input: VideoIn, videoFile: VideoFile): List<String> {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package se.svt.oss.encore.process
2+
3+
import java.nio.file.Path
4+
import kotlin.io.path.createTempDirectory
5+
6+
fun createTempDir(prefix: String): Path {
7+
val tmpdir = System.getenv("ENCORE_TMPDIR")
8+
?: System.getProperty("java.io.tmpdir")
9+
return createTempDirectory(tmpdir?.let { Path.of(it) }, prefix)
10+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ import se.svt.oss.encore.model.EncoreJob
1414
import se.svt.oss.encore.model.input.maxDuration
1515
import se.svt.oss.encore.model.mediafile.toParams
1616
import se.svt.oss.encore.process.CommandBuilder
17+
import se.svt.oss.encore.process.createTempDir
1718
import se.svt.oss.encore.service.profile.ProfileService
1819
import se.svt.oss.mediaanalyzer.MediaAnalyzer
1920
import se.svt.oss.mediaanalyzer.file.MediaFile
2021
import java.io.File
21-
import java.nio.file.Files
2222
import java.util.concurrent.TimeUnit
2323
import kotlin.math.min
2424
import kotlin.math.round
@@ -57,7 +57,7 @@ class FfmpegExecutor(
5757
val commands =
5858
CommandBuilder(encoreJob, profile, outputFolder, encoreProperties.encoding).buildCommands(outputs)
5959
log.info { "Start encoding ${encoreJob.baseName}..." }
60-
val workDir = Files.createTempDirectory("encore_").toFile()
60+
val workDir = createTempDir("encore_").toFile()
6161
val duration = encoreJob.duration ?: encoreJob.inputs.maxDuration()
6262
return try {
6363
File(outputFolder).mkdirs()

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

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,13 @@ package se.svt.oss.encore.service.localencode
66

77
import mu.KotlinLogging
88
import org.springframework.stereotype.Service
9+
import se.svt.oss.encore.config.EncoreProperties
10+
import se.svt.oss.encore.model.EncoreJob
11+
import se.svt.oss.encore.process.createTempDir
912
import se.svt.oss.mediaanalyzer.file.AudioFile
1013
import se.svt.oss.mediaanalyzer.file.ImageFile
1114
import se.svt.oss.mediaanalyzer.file.MediaFile
1215
import se.svt.oss.mediaanalyzer.file.VideoFile
13-
import se.svt.oss.encore.config.EncoreProperties
14-
import se.svt.oss.encore.model.EncoreJob
1516
import java.io.File
1617
import java.nio.file.Files
1718
import java.nio.file.Path
@@ -28,7 +29,7 @@ class LocalEncodeService(
2829
encoreJob: EncoreJob
2930
): String {
3031
return if (encoreProperties.localTemporaryEncode) {
31-
Files.createTempDirectory("job_${encoreJob.id}").toString()
32+
createTempDir("job_${encoreJob.id}").toString()
3233
} else {
3334
encoreJob.outputFolder
3435
}
@@ -58,12 +59,7 @@ class LocalEncodeService(
5859
}
5960

6061
private fun moveTempLocalFiles(destination: File, tempDirectory: String) {
61-
val filesBeforeMove = File(tempDirectory).listFiles()
62-
filesBeforeMove?.forEach { moveFile(it, destination) }
63-
val fileCountAfterMove = destination.list()?.size
64-
if (fileCountAfterMove != filesBeforeMove?.count()) {
65-
throw RuntimeException("File count after moving files from temp to output folder differs.")
66-
}
62+
File(tempDirectory).listFiles()?.forEach { moveFile(it, destination) }
6763
}
6864

6965
private fun moveFile(file: File, destination: File) {

encore-common/src/test/kotlin/se/svt/oss/encore/process/CommandBuilderTest.kt

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ internal class CommandBuilderTest {
2929
val profile: Profile = mockk()
3030
var videoFile = defaultVideoFile
3131
var encoreJob = defaultEncoreJob()
32-
val encodingProperties = mockk<EncodingProperties>()
32+
val encodingProperties = EncodingProperties()
3333

3434
private lateinit var commandBuilder: CommandBuilder
3535

@@ -38,7 +38,6 @@ internal class CommandBuilderTest {
3838
@BeforeEach
3939
internal fun setUp() {
4040
commandBuilder = CommandBuilder(encoreJob, profile, encoreJob.outputFolder, encodingProperties)
41-
every { encodingProperties.defaultChannelLayouts } returns emptyMap()
4241
every { profile.scaling } returns "scaling"
4342
every { profile.deinterlaceFilter } returns "yadif"
4443
}
@@ -59,7 +58,7 @@ internal class CommandBuilderTest {
5958
)
6059
val buildCommands = commandBuilder.buildCommands(listOf(output))
6160
val command = buildCommands.first().joinToString(" ")
62-
assertThat(command).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -i /input/test.mp4 -map 0:a -vn -c:a copy -metadata comment=Transcoded using Encore /output/path/out.mp4")
61+
assertThat(command).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -i /input/test.mp4 -map 0:a -vn -c:a copy -metadata comment=Transcoded using Encore /output/path/out.mp4")
6362
}
6463

6564
@Test
@@ -88,7 +87,7 @@ internal class CommandBuilderTest {
8887
)
8988
val buildCommands = commandBuilder.buildCommands(listOf(output))
9089
val command = buildCommands.first().joinToString(" ")
91-
assertThat(command).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:a]join=inputs=3:channel_layout=3.0:map=0.0-FL|1.0-FR|2.0-FC,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]aformat=channel_layouts=stereo[AUDIO-test-out-0] -map [AUDIO-test-out-0] -vn -c:a:0 aac -metadata comment=Transcoded using Encore /output/path/out.mp4")
90+
assertThat(command).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:a]join=inputs=3:channel_layout=3.0:map=0.0-FL|1.0-FR|2.0-FC,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]aformat=channel_layouts=stereo[AUDIO-test-out-0] -map [AUDIO-test-out-0] -vn -c:a:0 aac -metadata comment=Transcoded using Encore /output/path/out.mp4")
9291
}
9392

9493
@Test
@@ -98,7 +97,7 @@ internal class CommandBuilderTest {
9897
assertThat(buildCommands).hasSize(1)
9998

10099
val command = buildCommands.first().joinToString(" ")
101-
assertThat(command).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=Transcoded using Encore /output/path/out.mp4")
100+
assertThat(command).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=Transcoded using Encore /output/path/out.mp4")
102101
}
103102

104103
@Test
@@ -109,8 +108,8 @@ internal class CommandBuilderTest {
109108
val firstPass = buildCommands[0].joinToString(" ")
110109
val secondPass = buildCommands[1].joinToString(" ")
111110

112-
assertThat(firstPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -i ${defaultVideoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -an first pass -f mp4 /dev/null")
113-
assertThat(secondPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -i ${defaultVideoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=$metadataComment /output/path/out.mp4")
111+
assertThat(firstPass).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -i ${defaultVideoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -an first pass -f mp4 /dev/null")
112+
assertThat(secondPass).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -i ${defaultVideoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=$metadataComment /output/path/out.mp4")
114113
}
115114

116115
@Test
@@ -132,8 +131,8 @@ internal class CommandBuilderTest {
132131
val firstPass = buildCommands[0].joinToString(" ")
133132
val secondPass = buildCommands[1].joinToString(" ")
134133

135-
assertThat(firstPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -ss 47.11 -i ${videoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -an first pass -f mp4 /dev/null")
136-
assertThat(secondPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -ss 47.11 -i ${videoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=$metadataComment /output/path/out.mp4")
134+
assertThat(firstPass).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -ss 47.11 -i ${videoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -an first pass -f mp4 /dev/null")
135+
assertThat(secondPass).isEqualTo("ffmpeg -xerror -hide_banner -loglevel +level -y -ss 47.11 -i ${videoFile.file} -filter_complex sws_flags=scaling;[0:v]split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[0:a]join=inputs=8:channel_layout=7.1:map=0.0-FL|1.0-FR|2.0-FC|3.0-LFE|4.0-BL|5.0-BR|6.0-SL|7.0-SR,asplit=1[AUDIO-main-test-out-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0] -map [VIDEO-test-out] -map [AUDIO-test-out-0] video params audio params -metadata comment=$metadataComment /output/path/out.mp4")
137136
}
138137

139138
@Test
@@ -196,7 +195,12 @@ internal class CommandBuilderTest {
196195
inputs = inputs
197196
)
198197

199-
commandBuilder = CommandBuilder(encoreJob, profile, "/tmp/123", encodingProperties)
198+
commandBuilder = CommandBuilder(
199+
encoreJob,
200+
profile,
201+
"/tmp/123",
202+
encodingProperties.copy(exitOnError = false, globalParams = linkedMapOf("err_detect" to "explode"))
203+
)
200204

201205
val buildCommands = commandBuilder.buildCommands(listOf(output(true), audioOutput("other", "extra")))
202206

@@ -205,8 +209,8 @@ internal class CommandBuilderTest {
205209
val firstPass = buildCommands[0].joinToString(" ")
206210
val secondPass = buildCommands[1].joinToString(" ")
207211

208-
assertThat(firstPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -f mp4 -t 22.5 -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:v:1]yadif,setdar=16/9,scale=iw*sar:ih,crop=min(iw\\,ih*1/1):min(ih\\,iw/(1/1)),pad=aspect=16/9:x=(ow-iw)/2:y=(oh-ih)/2,video,filter,split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -ss 12.1 -an -t 10.4 first pass -f mp4 /dev/null")
209-
assertThat(secondPass).isEqualTo("ffmpeg -hide_banner -loglevel +level -y -f mp4 -t 22.5 -i /input/test.mp4 -ac 4 -t 22.5 -i /input/main-audio.mp4 -t 22.5 -i /input/other-audio.mp4 -filter_complex sws_flags=scaling;[0:v:1]yadif,setdar=16/9,scale=iw*sar:ih,crop=min(iw\\,ih*1/1):min(ih\\,iw/(1/1)),pad=aspect=16/9:x=(ow-iw)/2:y=(oh-ih)/2,video,filter,split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[1:a]join=inputs=4:channel_layout=4.0:map=0.0-FL|1.0-FR|2.0-FC|3.0-BC,audio-main,main-filter,asplit=1[AUDIO-main-test-out-0];[2:a:3]asplit=1[AUDIO-other-extra-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0];[AUDIO-other-extra-0]audio-filter-extra[AUDIO-extra-0] -map [VIDEO-test-out] -ss 12.1 -map [AUDIO-test-out-0] -ss 12.1 -t 10.4 video params audio params -metadata comment=Transcoded using Encore /tmp/123/out.mp4 -map [AUDIO-extra-0] -ss 12.1 -t 10.4 -vn audio extra -metadata comment=Transcoded using Encore /tmp/123/extra.mp4")
212+
assertThat(firstPass).isEqualTo("ffmpeg -err_detect explode -hide_banner -loglevel +level -y -f mp4 -t 22.5 -i /input/test.mp4 -filter_complex sws_flags=scaling;[0:v:1]yadif,setdar=16/9,scale=iw*sar:ih,crop=min(iw\\,ih*1/1):min(ih\\,iw/(1/1)),pad=aspect=16/9:x=(ow-iw)/2:y=(oh-ih)/2,video,filter,split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out] -map [VIDEO-test-out] -ss 12.1 -an -t 10.4 first pass -f mp4 /dev/null")
213+
assertThat(secondPass).isEqualTo("ffmpeg -err_detect explode -hide_banner -loglevel +level -y -f mp4 -t 22.5 -i /input/test.mp4 -ac 4 -t 22.5 -i /input/main-audio.mp4 -t 22.5 -i /input/other-audio.mp4 -filter_complex sws_flags=scaling;[0:v:1]yadif,setdar=16/9,scale=iw*sar:ih,crop=min(iw\\,ih*1/1):min(ih\\,iw/(1/1)),pad=aspect=16/9:x=(ow-iw)/2:y=(oh-ih)/2,video,filter,split=1[VIDEO-main-test-out];[VIDEO-main-test-out]video-filter[VIDEO-test-out];[1:a]join=inputs=4:channel_layout=4.0:map=0.0-FL|1.0-FR|2.0-FC|3.0-BC,audio-main,main-filter,asplit=1[AUDIO-main-test-out-0];[2:a:3]asplit=1[AUDIO-other-extra-0];[AUDIO-main-test-out-0]audio-filter[AUDIO-test-out-0];[AUDIO-other-extra-0]audio-filter-extra[AUDIO-extra-0] -map [VIDEO-test-out] -ss 12.1 -map [AUDIO-test-out-0] -ss 12.1 -t 10.4 video params audio params -metadata comment=Transcoded using Encore /tmp/123/out.mp4 -map [AUDIO-extra-0] -ss 12.1 -t 10.4 -vn audio extra -metadata comment=Transcoded using Encore /tmp/123/extra.mp4")
210214
}
211215

212216
private fun output(twoPass: Boolean): Output {

0 commit comments

Comments
 (0)