Files
LibreMediaConverter/app/src/test/java/org/libremediaconverter/ffmpeg/FFmpegConcatCommandTest.kt
T
JMR-devandClaude Opus 5 496f1c7e73 Apply ktlintFormat
Tool output only, no hand edits, so this is safe to read with whitespace
diffing off. It is its own commit for exactly that reason: a whole-repo
reformat folded into the commit that configured the formatter would have
made both unreviewable.

What it did, mostly: trailing commas on wrapped argument lists, signatures
collapsed onto one line where they fit inside 120 columns, import order, and
four genuinely unused imports removed. Unit tests pass unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:10:44 -05:00

117 lines
4.0 KiB
Kotlin

package org.libremediaconverter.ffmpeg
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test
import org.libremediaconverter.model.ConcatStrategy
import org.libremediaconverter.model.OutputFormat
import java.io.File
class FFmpegConcatCommandTest {
private val listFile = File("/cache/list.txt")
private val output = File("/cache/joined.mp4")
private val inputs = listOf("/cache/a.mp4", "/cache/b.mp4")
@Test
fun `list file quotes each entry for the concat demuxer`() {
val text = FFmpegConcatCommand.listFileContents(inputs)
assertEquals("file '/cache/a.mp4'\nfile '/cache/b.mp4'\n", text)
}
@Test
fun `apostrophes in filenames are escaped`() {
// An unescaped apostrophe silently truncates the entry, so the file would be
// skipped rather than the command failing.
val text = FFmpegConcatCommand.listFileContents(listOf("/cache/jason's clip.mp4"))
assertTrue("apostrophe not escaped: $text", text.contains("""jason'\''s clip.mp4"""))
}
@Test
fun `stream copy uses the concat demuxer and copies codecs`() {
val args = FFmpegConcatCommand.build(
ConcatStrategy.STREAM_COPY,
inputs,
listFile,
output,
OutputFormat.MP4_H264,
)
assertTrue(args.contains("concat"))
assertEquals("copy", args[args.indexOf("-c") + 1])
assertEquals(listFile.absolutePath, args[args.indexOf("-i") + 1])
assertFalse("stream copy must not re-encode", args.contains("libx264"))
}
@Test
fun `stream copy allows absolute paths in the list file`() {
val args = FFmpegConcatCommand.build(
ConcatStrategy.STREAM_COPY,
inputs,
listFile,
output,
OutputFormat.MP4_H264,
)
// Without -safe 0 the demuxer rejects the absolute paths we generate.
assertEquals("0", args[args.indexOf("-safe") + 1])
}
@Test
fun `re-encode passes every input separately and builds a filter graph`() {
val args = FFmpegConcatCommand.build(
ConcatStrategy.REENCODE,
inputs,
listFile,
output,
OutputFormat.MP4_H264,
)
assertEquals(2, args.count { it == "-i" })
val filter = args[args.indexOf("-filter_complex") + 1]
assertTrue("missing concat filter: $filter", filter.contains("concat=n=2:v=1:a=1"))
assertTrue("inputs must be normalised before joining", filter.contains("scale="))
assertTrue("frame rates must be normalised", filter.contains("fps=30"))
assertTrue(args.contains("libx264"))
}
@Test
fun `re-encode maps the filter outputs rather than raw streams`() {
val args = FFmpegConcatCommand.build(
ConcatStrategy.REENCODE,
inputs,
listFile,
output,
OutputFormat.MP4_H264,
)
assertTrue(args.contains("[v]"))
assertTrue(args.contains("[a]"))
}
@Test
fun `mp4 output gets faststart on both strategies`() {
listOf(ConcatStrategy.STREAM_COPY, ConcatStrategy.REENCODE).forEach { strategy ->
val args = FFmpegConcatCommand.build(strategy, inputs, listFile, output, OutputFormat.MP4_H264)
assertTrue("$strategy missing faststart", args.contains("+faststart"))
}
}
@Test
fun `mkv output does not get faststart`() {
val args = FFmpegConcatCommand.build(
ConcatStrategy.STREAM_COPY,
inputs,
listFile,
File("/cache/j.mkv"),
OutputFormat.MKV_H264,
)
assertFalse(args.contains("+faststart"))
}
@Test
fun `output path is always last`() {
listOf(ConcatStrategy.STREAM_COPY, ConcatStrategy.REENCODE).forEach { strategy ->
val args = FFmpegConcatCommand.build(strategy, inputs, listFile, output, OutputFormat.MP4_H264)
assertEquals(output.absolutePath, args.last())
}
}
}