test(media): 覆盖转码保持原始宽高比
This commit is contained in:
1 parent
40a884cc85
commit
217ff24f1c
3 files changed
+189
-1
No files matched your search
+131
-1
@@ -74,9 +74,58 @@ class FfmpegTranscodeRunnerSmokeTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_landscape_aspect_and_write_actual_dimensions() throws Exception {
|
||||
String ffmpeg = binaryPath("ffmpeg");
|
||||
String ffprobe = binaryPath("ffprobe");
|
||||
assumeTrue(ffmpeg != null && ffprobe != null,
|
||||
"本机没有 ffmpeg/ffprobe,跳过真实转码 smoke");
|
||||
|
||||
TranscodeRunner.TranscodeResult result = transcodeOneRung(
|
||||
ffmpeg, ffprobe, 1024, 576,
|
||||
new TranscodeRunner.LadderRung("360p", 640, 360, 400, 64));
|
||||
|
||||
assertRenditionDimensions(result, 0, 640, 360);
|
||||
assertNoUpscaleOrDistortion(result, 1024, 576, 640, 360);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_keep_portrait_orientation_and_even_dimensions() throws Exception {
|
||||
String ffmpeg = binaryPath("ffmpeg");
|
||||
String ffprobe = binaryPath("ffprobe");
|
||||
assumeTrue(ffmpeg != null && ffprobe != null,
|
||||
"本机没有 ffmpeg/ffprobe,跳过真实转码 smoke");
|
||||
|
||||
TranscodeRunner.TranscodeResult result = transcodeOneRung(
|
||||
ffmpeg, ffprobe, 720, 1280,
|
||||
new TranscodeRunner.LadderRung("360p", 640, 360, 400, 64));
|
||||
|
||||
assertRenditionDimensions(result, 0, 360, 640);
|
||||
assertNoUpscaleOrDistortion(result, 720, 1280, 360, 640);
|
||||
}
|
||||
|
||||
@Test
|
||||
void should_not_upscale_when_source_short_edge_is_below_rung() throws Exception {
|
||||
String ffmpeg = binaryPath("ffmpeg");
|
||||
String ffprobe = binaryPath("ffprobe");
|
||||
assumeTrue(ffmpeg != null && ffprobe != null,
|
||||
"本机没有 ffmpeg/ffprobe,跳过真实转码 smoke");
|
||||
|
||||
TranscodeRunner.TranscodeResult result = transcodeOneRung(
|
||||
ffmpeg, ffprobe, 640, 360,
|
||||
new TranscodeRunner.LadderRung("480p", 854, 480, 800, 64));
|
||||
|
||||
assertRenditionDimensions(result, 0, 640, 360);
|
||||
assertNoUpscaleOrDistortion(result, 640, 360, 640, 360);
|
||||
}
|
||||
|
||||
private void generateSample(String ffmpeg, Path sample) throws Exception {
|
||||
generateSample(ffmpeg, sample, 640, 360);
|
||||
}
|
||||
|
||||
private void generateSample(String ffmpeg, Path sample, int width, int height) throws Exception {
|
||||
Process process = new ProcessBuilder(ffmpeg, "-y",
|
||||
"-f", "lavfi", "-i", "testsrc=size=640x360:rate=15",
|
||||
"-f", "lavfi", "-i", "testsrc=size=" + width + "x" + height + ":rate=15",
|
||||
"-f", "lavfi", "-i", "sine=frequency=440:duration=2",
|
||||
"-shortest", "-t", "2",
|
||||
"-c:v", "libx264", "-pix_fmt", "yuv420p", "-c:a", "aac",
|
||||
@@ -87,6 +136,84 @@ class FfmpegTranscodeRunnerSmokeTest {
|
||||
assertThat(process.exitValue()).as("生成测试片失败").isZero();
|
||||
}
|
||||
|
||||
private TranscodeRunner.TranscodeResult transcodeOneRung(
|
||||
String ffmpeg, String ffprobe, int width, int height,
|
||||
TranscodeRunner.LadderRung rung) throws Exception {
|
||||
Path work = Files.createTempDirectory("fruitshorts-aspect-smoke");
|
||||
try {
|
||||
Path sample = work.resolve("sample-" + width + "x" + height + ".mp4");
|
||||
generateSample(ffmpeg, sample, width, height);
|
||||
Path output = work.resolve("out");
|
||||
TranscodeRunner.TranscodeResult result = new FfmpegTranscodeRunner().run(
|
||||
new TranscodeRunner.TranscodeRequest(
|
||||
2L, sample, output, "transcoded/aspect",
|
||||
List.of(rung), ffmpeg, ffprobe, 2, 1, 120,
|
||||
new TranscodeRunner.SpriteOptions(false, 1, 10, 160)));
|
||||
|
||||
Path playlist = output.resolve(rung.quality()).resolve("index.m3u8");
|
||||
Dimensions probed = probeDimensions(ffprobe, playlist);
|
||||
assertThat(result.renditions()).hasSize(1);
|
||||
assertThat(result.renditions().get(0).width()).isEqualTo(probed.width());
|
||||
assertThat(result.renditions().get(0).height()).isEqualTo(probed.height());
|
||||
assertThat(probeSampleAspectRatio(ffprobe, playlist)).isEqualTo("1:1");
|
||||
assertThat(readLines(output.resolve("master.m3u8")))
|
||||
.anyMatch(line -> line.contains("RESOLUTION="
|
||||
+ probed.width() + "x" + probed.height()));
|
||||
return result;
|
||||
} finally {
|
||||
deleteRecursively(work);
|
||||
}
|
||||
}
|
||||
|
||||
private void assertRenditionDimensions(
|
||||
TranscodeRunner.TranscodeResult result, int index, int width, int height) {
|
||||
TranscodeRunner.ProducedRendition rendition = result.renditions().get(index);
|
||||
assertThat(rendition.width()).isEqualTo(width);
|
||||
assertThat(rendition.height()).isEqualTo(height);
|
||||
}
|
||||
|
||||
private void assertNoUpscaleOrDistortion(
|
||||
TranscodeRunner.TranscodeResult result,
|
||||
int sourceWidth, int sourceHeight, int width, int height) {
|
||||
TranscodeRunner.ProducedRendition rendition = result.renditions().get(0);
|
||||
assertThat(rendition.width() % 2).isZero();
|
||||
assertThat(rendition.height() % 2).isZero();
|
||||
assertThat(rendition.width()).isLessThanOrEqualTo(sourceWidth);
|
||||
assertThat(rendition.height()).isLessThanOrEqualTo(sourceHeight);
|
||||
double sourceRatio = sourceWidth / (double) sourceHeight;
|
||||
double outputRatio = rendition.width() / (double) rendition.height();
|
||||
assertThat(Math.abs(sourceRatio - outputRatio)).isLessThan(0.01);
|
||||
assertThat(rendition.width()).isEqualTo(width);
|
||||
assertThat(rendition.height()).isEqualTo(height);
|
||||
}
|
||||
|
||||
private Dimensions probeDimensions(String ffprobe, Path playlist) throws Exception {
|
||||
String output = runAndRead(List.of(
|
||||
ffprobe, "-v", "error", "-select_streams", "v:0",
|
||||
"-show_entries", "stream=width,height",
|
||||
"-of", "csv=s=x:p=0", playlist.toString()));
|
||||
String[] parts = output.lines().findFirst().orElse("").split("x");
|
||||
assertThat(parts).hasSize(2);
|
||||
return new Dimensions(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]));
|
||||
}
|
||||
|
||||
private String probeSampleAspectRatio(String ffprobe, Path playlist) throws Exception {
|
||||
String output = runAndRead(List.of(
|
||||
ffprobe, "-v", "error", "-select_streams", "v:0",
|
||||
"-show_entries", "stream=sample_aspect_ratio",
|
||||
"-of", "default=noprint_wrappers=1:nokey=1",
|
||||
playlist.toString()));
|
||||
return output.lines().findFirst().orElse("").trim();
|
||||
}
|
||||
|
||||
private String runAndRead(List<String> command) throws Exception {
|
||||
Process process = new ProcessBuilder(command).redirectErrorStream(true).start();
|
||||
String output = new String(process.getInputStream().readAllBytes()).trim();
|
||||
assertThat(process.waitFor(30, TimeUnit.SECONDS)).as("ffprobe 超时").isTrue();
|
||||
assertThat(process.exitValue()).as("ffprobe 失败:%s", output).isZero();
|
||||
return output;
|
||||
}
|
||||
|
||||
/** 在 PATH 里找可执行文件;找不到返回 null(由调用方 assume)。 */
|
||||
private String binaryPath(String name) {
|
||||
try {
|
||||
@@ -122,4 +249,7 @@ class FfmpegTranscodeRunnerSmokeTest {
|
||||
// 同上
|
||||
}
|
||||
}
|
||||
|
||||
private record Dimensions(int width, int height) {
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user