Skip to content

Commit

Permalink
added options disableImageSequenceDetection and ffprobeInputParams
Browse files Browse the repository at this point in the history
Signed-off-by: Finn Hermansson <[email protected]>
  • Loading branch information
fhermansson committed Dec 15, 2023
1 parent 01496f0 commit f0a170e
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 42 deletions.
24 changes: 16 additions & 8 deletions src/main/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String?> = linkedMapOf()
): MediaFile {
val mediaInfo = try {
mediaInfoAnalyzer.analyze(file)
mediaInfoAnalyzer.analyze(file, disableImageSequenceDetection)
} catch (e: Exception) {
log.warn(e) { "Error running mediainfo on $file!" }
null
Expand All @@ -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<String, String?>
): MediaFile {
val format = probeResult.format ?: throw IllegalStateException("No format detected in ffprobe result!")
val formatName = mediaInfo?.generalTrack?.format ?: format.format_name
Expand Down Expand Up @@ -93,15 +99,16 @@ class MediaAnalyzer
format = formatName,
overallBitrate = overallBitrate,
duration = duration,
videoStreams = videoStreams(probeResult, mediaInfo, probeInterlaced),
videoStreams = videoStreams(probeResult, mediaInfo, probeInterlaced, ffprobeInputParams),
audioStreams = audioStreams(probeResult, mediaInfo)
)
}

private fun videoStreams(
probeResult: ProbeResult,
mediaInfo: MediaInfo?,
probeInterlaced: Boolean
probeInterlaced: Boolean,
ffprobeInputParams: LinkedHashMap<String, String?>
): List<VideoStream> {
val videoStreams = probeResult.videoStreams
val mediaInfoStreams = mediaInfo?.videoTracks?.let {
Expand All @@ -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(
Expand Down
59 changes: 38 additions & 21 deletions src/main/kotlin/se/svt/oss/mediaanalyzer/ffprobe/FfprobeAnalyzer.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String?>): ProbeResult {
val command = buildList<String> {
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<ProbeResult>(
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"
Expand All @@ -31,19 +38,29 @@ class FfprobeAnalyzer
}

@JvmOverloads
fun isInterlaced(file: String, videoIndex: Int = 0): Boolean {
fun isInterlaced(file: String, videoIndex: Int = 0, ffprobeInputParams: LinkedHashMap<String, String?>): Boolean {
val command = buildList<String> {
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<ProbeResult>(
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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<MediaInfo>(
objectMapper,
"mediainfo",
"--Output=JSON",
file
*args.toTypedArray()
)
if (exitCode != 0) {
throw RuntimeException("mediainfo returned exit code: $exitCode")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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())
Expand All @@ -93,6 +92,7 @@ class MediaAnalyzerIntegrationTest {
.hasBitrate(128104)
.hasProfile("LC")
}

@Test
fun testAudioHeAac() {
val file = javaClass.getResource("/he-aac_test.mp4").file
Expand All @@ -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)
}
}
8 changes: 4 additions & 4 deletions src/test/kotlin/se/svt/oss/mediaanalyzer/MediaAnalyzerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -230,19 +230,19 @@ internal class MediaAnalyzerTest {
}

private fun mockFfprobe(jsonPath: String) {
every { anyConstructed<FfprobeAnalyzer>().analyze(file) } returns parse(jsonPath)
every { anyConstructed<FfprobeAnalyzer>().analyze(file, any()) } returns parse(jsonPath)
}

private fun mockFfprobeInterlaced(interlaced: Boolean) {
every { anyConstructed<FfprobeAnalyzer>().isInterlaced(any(), any()) } returns interlaced
every { anyConstructed<FfprobeAnalyzer>().isInterlaced(any(), any(), any()) } returns interlaced
}

private fun mockMediaInfoFails() {
every { anyConstructed<MediaInfoAnalyzer>().analyze(file) } throws RuntimeException("mediainfo failed!")
every { anyConstructed<MediaInfoAnalyzer>().analyze(file, any()) } throws RuntimeException("mediainfo failed!")
}

private fun mockMediaInfo(jsonPath: String) {
every { anyConstructed<MediaInfoAnalyzer>().analyze(file) } returns parse(jsonPath)
every { anyConstructed<MediaInfoAnalyzer>().analyze(file, any()) } returns parse(jsonPath)
}

private inline fun <reified T> parse(file: String): T = objectMapper.readValue<T>(javaClass.getResource(file))
Expand Down
Binary file added src/test/resources/rawaudio.tts
Binary file not shown.

0 comments on commit f0a170e

Please sign in to comment.