From f0a170ec3ad8e0002d94841df716bac9fe237bb0 Mon Sep 17 00:00:00 2001 From: Finn Hermansson Date: Fri, 15 Dec 2023 10:51:24 +0100 Subject: [PATCH] added options disableImageSequenceDetection and ffprobeInputParams Signed-off-by: Finn Hermansson --- .../se/svt/oss/mediaanalyzer/MediaAnalyzer.kt | 24 ++++--- .../mediaanalyzer/ffprobe/FfprobeAnalyzer.kt | 59 +++++++++++------- .../mediainfo/MediaInfoAnalyzer.kt | 14 +++-- .../MediaAnalyzerIntegrationTest.kt | 28 +++++++-- .../oss/mediaanalyzer/MediaAnalyzerTest.kt | 8 +-- src/test/resources/rawaudio.tts | Bin 0 -> 220500 bytes 6 files changed, 91 insertions(+), 42 deletions(-) create mode 100644 src/test/resources/rawaudio.tts diff --git a/src/main/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzer.kt b/src/main/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzer.kt index 4cb25f1..ad6837c 100644 --- a/src/main/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzer.kt +++ b/src/main/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzer.kt @@ -29,9 +29,14 @@ class MediaAnalyzer private val log = KotlinLogging.logger { } - fun analyze(file: String, probeInterlaced: Boolean = false): MediaFile { + fun analyze( + file: String, + probeInterlaced: Boolean = false, + disableImageSequenceDetection: Boolean = false, + ffprobeInputParams: LinkedHashMap = linkedMapOf() + ): MediaFile { val mediaInfo = try { - mediaInfoAnalyzer.analyze(file) + mediaInfoAnalyzer.analyze(file, disableImageSequenceDetection) } catch (e: Exception) { log.warn(e) { "Error running mediainfo on $file!" } null @@ -57,14 +62,15 @@ class MediaAnalyzer ) } - val probeResult = ffprobeAnalyzer.analyze(file) - return mergeResults(probeResult, mediaInfo, probeInterlaced) + val probeResult = ffprobeAnalyzer.analyze(file, ffprobeInputParams) + return mergeResults(probeResult, mediaInfo, probeInterlaced, ffprobeInputParams) } private fun mergeResults( probeResult: ProbeResult, mediaInfo: MediaInfo?, - probeInterlaced: Boolean + probeInterlaced: Boolean, + ffprobeInputParams: LinkedHashMap ): MediaFile { val format = probeResult.format ?: throw IllegalStateException("No format detected in ffprobe result!") val formatName = mediaInfo?.generalTrack?.format ?: format.format_name @@ -93,7 +99,7 @@ class MediaAnalyzer format = formatName, overallBitrate = overallBitrate, duration = duration, - videoStreams = videoStreams(probeResult, mediaInfo, probeInterlaced), + videoStreams = videoStreams(probeResult, mediaInfo, probeInterlaced, ffprobeInputParams), audioStreams = audioStreams(probeResult, mediaInfo) ) } @@ -101,7 +107,8 @@ class MediaAnalyzer private fun videoStreams( probeResult: ProbeResult, mediaInfo: MediaInfo?, - probeInterlaced: Boolean + probeInterlaced: Boolean, + ffprobeInputParams: LinkedHashMap ): List { val videoStreams = probeResult.videoStreams val mediaInfoStreams = mediaInfo?.videoTracks?.let { @@ -118,7 +125,8 @@ class MediaAnalyzer ?: (ffVideoStream.r_frame_rate.toFraction().toDouble() * duration).toInt() val interlaced = if (probeInterlaced) ffprobeAnalyzer.isInterlaced( probeResult.format!!.filename, - index + index, + ffprobeInputParams ) else videoTrack?.isInterlaced ?: false VideoStream( diff --git a/src/main/kotlin/se/svt/oss/mediaanalyzer/ffprobe/FfprobeAnalyzer.kt b/src/main/kotlin/se/svt/oss/mediaanalyzer/ffprobe/FfprobeAnalyzer.kt index d06bc45..ade7b4b 100644 --- a/src/main/kotlin/se/svt/oss/mediaanalyzer/ffprobe/FfprobeAnalyzer.kt +++ b/src/main/kotlin/se/svt/oss/mediaanalyzer/ffprobe/FfprobeAnalyzer.kt @@ -10,18 +10,25 @@ import se.svt.oss.mediaanalyzer.util.ProcessUtil class FfprobeAnalyzer @JvmOverloads constructor(private val objectMapper: ObjectMapper = ObjectMapper().findAndRegisterModules()) { - fun analyze(file: String): ProbeResult { + fun analyze(file: String, ffprobeInputParams: LinkedHashMap): ProbeResult { + val command = buildList { + add("ffprobe") + add("-v") + add("quiet") + add("-of") + add("json") + add("-show_streams") + add("-show_format") + add("-show_error") + ffprobeInputParams.forEach { (key, value) -> + add("-$key") + value?.let { add(it) } + } + add(file) + } val (exitCode, probeResult) = ProcessUtil.runAndParse( objectMapper, - "ffprobe", - "-v", - "quiet", - "-of", - "json", - "-show_streams", - "-show_format", - "-show_error", - file + *command.toTypedArray() ) if (exitCode != 0 || probeResult.error != null) { val message = probeResult.error?.string ?: "exitcode: $exitCode" @@ -31,19 +38,29 @@ class FfprobeAnalyzer } @JvmOverloads - fun isInterlaced(file: String, videoIndex: Int = 0): Boolean { + fun isInterlaced(file: String, videoIndex: Int = 0, ffprobeInputParams: LinkedHashMap): Boolean { + val command = buildList { + add("ffprobe") + add("-v") + add("quiet") + add("-of") + add("json") + add("-read_intervals") + add("%+5") + add("-select_streams") + add("v:$videoIndex") + add("-show_entries") + add("frame=interlaced_frame") + add("-show_error") + ffprobeInputParams.forEach { (key, value) -> + add("-$key") + value?.let { add(it) } + } + add(file) + } val (exitCode, probeResult) = ProcessUtil.runAndParse( objectMapper, - "ffprobe", - "-v", - "quiet", - "-of", - "json", - "-read_intervals", "%+5", - "-select_streams", "v:$videoIndex", - "-show_entries", "frame=interlaced_frame", - "-show_error", - file + *command.toTypedArray() ) if (exitCode != 0 || probeResult.error != null) { val message = probeResult.error?.string ?: "exitcode: $exitCode" diff --git a/src/main/kotlin/se/svt/oss/mediaanalyzer/mediainfo/MediaInfoAnalyzer.kt b/src/main/kotlin/se/svt/oss/mediaanalyzer/mediainfo/MediaInfoAnalyzer.kt index f4629ac..a8223ba 100644 --- a/src/main/kotlin/se/svt/oss/mediaanalyzer/mediainfo/MediaInfoAnalyzer.kt +++ b/src/main/kotlin/se/svt/oss/mediaanalyzer/mediainfo/MediaInfoAnalyzer.kt @@ -10,12 +10,18 @@ import se.svt.oss.mediaanalyzer.util.ProcessUtil class MediaInfoAnalyzer @JvmOverloads constructor(private val objectMapper: ObjectMapper = ObjectMapper().findAndRegisterModules()) { - fun analyze(file: String): MediaInfo { + fun analyze(file: String, disableImageSequenceDetection: Boolean): MediaInfo { + val args = buildList { + add("mediainfo") + add("--Output=JSON") + if (disableImageSequenceDetection) { + add("--File_TestContinuousFileNames=0") + } + add(file) + } val (exitCode, mediaInfo) = ProcessUtil.runAndParse( objectMapper, - "mediainfo", - "--Output=JSON", - file + *args.toTypedArray() ) if (exitCode != 0) { throw RuntimeException("mediainfo returned exit code: $exitCode") diff --git a/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerIntegrationTest.kt b/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerIntegrationTest.kt index 88aea26..b42e318 100644 --- a/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerIntegrationTest.kt +++ b/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerIntegrationTest.kt @@ -25,11 +25,10 @@ class MediaAnalyzerIntegrationTest { fun testVideoFile() { val file = javaClass.getResource("/test.mp4").file val videoFile = MediaAnalyzer().analyze(file, true) as VideoFile - println("videoFile is: $videoFile") assertThat(videoFile) .hasFormat("MPEG-4") - .hasDuration(10.016) - .hasOverallBitrate(2424115) + .hasDuration(10.01) + .hasOverallBitrate(2425568) .hasFileSize(3034992) assertThat(videoFile.file).endsWith("/test.mp4") @@ -80,8 +79,8 @@ class MediaAnalyzerIntegrationTest { assertThat(audioFile) .hasFormat("MPEG-4") - .hasOverallBitrate(132016) - .hasDuration(2.643) + .hasOverallBitrate(133124) + .hasDuration(2.621) assertThat(audioFile.audioStreams).hasSize(1) assertThat(audioFile.audioStreams.first()) @@ -93,6 +92,7 @@ class MediaAnalyzerIntegrationTest { .hasBitrate(128104) .hasProfile("LC") } + @Test fun testAudioHeAac() { val file = javaClass.getResource("/he-aac_test.mp4").file @@ -115,4 +115,22 @@ class MediaAnalyzerIntegrationTest { .hasBitrate(64183) .hasProfile("HE-AAC") } + + @Test + fun ffprobeInputParams() { + val file = javaClass.getResource("/rawaudio.tts").file + + val audioFile = MediaAnalyzer() + .analyze( + file, + ffprobeInputParams = linkedMapOf( + "f" to "s16le", + "ac" to "1", + "ar" to "22050", + ) + ) + assertThat(audioFile).isInstanceOf(AudioFile::class.java) + assertThat(audioFile as AudioFile) + .hasDuration(5.0) + } } diff --git a/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerTest.kt b/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerTest.kt index 0a0707b..3bc850b 100644 --- a/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerTest.kt +++ b/src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerTest.kt @@ -230,19 +230,19 @@ internal class MediaAnalyzerTest { } private fun mockFfprobe(jsonPath: String) { - every { anyConstructed().analyze(file) } returns parse(jsonPath) + every { anyConstructed().analyze(file, any()) } returns parse(jsonPath) } private fun mockFfprobeInterlaced(interlaced: Boolean) { - every { anyConstructed().isInterlaced(any(), any()) } returns interlaced + every { anyConstructed().isInterlaced(any(), any(), any()) } returns interlaced } private fun mockMediaInfoFails() { - every { anyConstructed().analyze(file) } throws RuntimeException("mediainfo failed!") + every { anyConstructed().analyze(file, any()) } throws RuntimeException("mediainfo failed!") } private fun mockMediaInfo(jsonPath: String) { - every { anyConstructed().analyze(file) } returns parse(jsonPath) + every { anyConstructed().analyze(file, any()) } returns parse(jsonPath) } private inline fun parse(file: String): T = objectMapper.readValue(javaClass.getResource(file)) diff --git a/src/test/resources/rawaudio.tts b/src/test/resources/rawaudio.tts new file mode 100644 index 0000000000000000000000000000000000000000..95049cde3f6cee2b0e90a27e86e6aa75e61846e9 GIT binary patch literal 220500 zcmeIu0Sy2E0K%a6Pi+o2h(KY$fB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 zV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM z7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b* z1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd z0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwA zz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEj zFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r z3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@ z0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VK zfB^#r3>YwAz<>b*1`HT5V8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5 wV8DO@0|pEjFkrxd0RsjM7%*VKfB^#r3>YwAz<>b*1`HT5V8DO@0|q_@23>&w0RR91 literal 0 HcmV?d00001