fix(media): 转码错误信息截断并上报进度

This commit is contained in:
Zhou Weidong committed 2026-10-22 15:12:05 +08:00
1 parent f9bc6eee6d
commit 660afaf66d
18 files changed
+469 -34

No files matched your search

@@ -12,6 +12,7 @@ import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
@@ -40,13 +41,15 @@ class FfmpegTranscodeRunnerSmokeTest {
Path output = work.resolve("out");
TranscodeRunner runner = new FfmpegTranscodeRunner();
AtomicInteger maxProgress = new AtomicInteger();
// 只跑两档(360p/480p)保持秒级;四档阶梯的逻辑在 TranscodeWorkerTest 里断言
TranscodeRunner.TranscodeResult result = runner.run(new TranscodeRunner.TranscodeRequest(
1L, sample, output, "transcoded/smoke",
List.of(new TranscodeRunner.LadderRung("360p", 640, 360, 400, 64),
new TranscodeRunner.LadderRung("480p", 854, 480, 800, 64)),
ffmpeg, ffprobe, 2, 1, 120,
new TranscodeRunner.SpriteOptions(true, 1, 10, 160)));
new TranscodeRunner.SpriteOptions(true, 1, 10, 160),
value -> maxProgress.accumulateAndGet(value, Math::max)));
assertThat(Files.exists(output.resolve("master.m3u8"))).isTrue();
assertThat(Files.exists(output.resolve("poster.jpg"))).isTrue();
@@ -60,6 +63,7 @@ class FfmpegTranscodeRunnerSmokeTest {
assertThat(rendition.sizeBytes()).isGreaterThan(0L);
}
assertThat(result.durationSec()).isBetween(1, 5);
assertThat(maxProgress.get()).isGreaterThanOrEqualTo(90);
assertThat(result.thumbnail()).isNotNull();
assertThat(result.thumbnail().status()).isEqualTo("READY");
assertThat(result.thumbnail().thumbWidth()).isEqualTo(160);
@@ -21,7 +21,6 @@ import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -54,24 +53,22 @@ class TranscodeServiceTriggerTest {
}
@Test
void should_create_once_and_reuse_queued_task_when_retrying_successful_asset() {
void should_reset_completed_task_in_place_and_reuse_it_when_triggered_again() {
TranscodeTask success = task(10L, TranscodeService.STATUS_SUCCESS);
TranscodeTask queued = task(100L, TranscodeService.STATUS_QUEUED);
given(taskMapper.selectOne(any())).willReturn(success, null, queued);
doAnswer(invocation -> {
TranscodeTask inserted = invocation.getArgument(0);
inserted.setId(100L);
return 1;
}).when(taskMapper).insert(any(TranscodeTask.class));
queued.setId(10L);
given(taskMapper.selectOne(any())).willReturn(success, queued);
given(taskMapper.update(any(), any())).willReturn(1);
TranscodeTriggerRespVO first = transcodeService.trigger(ASSET_ID, "/files/video.mp4");
TranscodeTriggerRespVO second = transcodeService.trigger(ASSET_ID, "/files/video.mp4");
assertThat(first.getTaskId()).isEqualTo(100L);
assertThat(first.getTaskId()).isEqualTo(10L);
assertThat(first.getStatus()).isEqualTo(TranscodeService.STATUS_QUEUED);
assertThat(second.getTaskId()).isEqualTo(100L);
assertThat(second.getTaskId()).isEqualTo(10L);
assertThat(second.getStatus()).isEqualTo(TranscodeService.STATUS_QUEUED);
verify(taskMapper).insert(any(TranscodeTask.class));
verify(taskMapper, never()).insert(any(TranscodeTask.class));
verify(taskMapper).update(any(), any());
}
@Test
@@ -111,17 +108,39 @@ class TranscodeServiceTriggerTest {
TranscodeTask running = task(41L, TranscodeService.STATUS_RUNNING);
running.setRetryCount(2);
given(taskMapper.selectById(41L)).willReturn(running);
String longReason = "ffmpeg 输出异常:" + "x".repeat(5000);
String status = transcodeService.fail(
41L, "ffmpeg 退出码 1:Invalid data found");
String status = transcodeService.fail(41L, longReason);
assertThat(status).isEqualTo(TranscodeService.STATUS_FAILED);
assertThat(running.getStatus()).isEqualTo(TranscodeService.STATUS_FAILED);
assertThat(running.getRetryCount()).isEqualTo(3);
assertThat(running.getErrorMsg()).contains("Invalid data found");
assertThat(running.getErrorMsg()).contains("ffmpeg 输出异常");
assertThat(running.getErrorMsg()).contains("truncated");
assertThat(running.getErrorMsg()).contains("total=" + longReason.length());
assertThat(running.getErrorMsg().length()).isLessThanOrEqualTo(500);
verify(taskMapper).updateById(running);
}
@Test
void should_update_progress_only_for_running_task() {
given(taskMapper.update(any(), any())).willReturn(1);
transcodeService.updateProgress(51L, 46);
verify(taskMapper).update(any(), any());
}
@Test
void should_mark_stale_running_task_failed() {
given(taskMapper.update(any(), any())).willReturn(2);
int recovered = transcodeService.recoverStaleRunningTasks();
assertThat(recovered).isEqualTo(2);
verify(taskMapper).update(any(), any());
}
private TranscodeTask task(Long id, String status) {
TranscodeTask task = new TranscodeTask();
task.setId(id);
@@ -72,6 +72,7 @@ class TranscodeWorkerTest {
@Test
void should_register_renditions_and_mark_asset_ready_when_transcode_succeeds() {
given(transcodeService.claimNext()).willReturn(queuedTask());
runner.progress = 45;
assertThat(worker.processOne()).isTrue();
@@ -92,6 +93,7 @@ class TranscodeWorkerTest {
eq("/files/transcoded/88/poster.jpg"), eq(120));
verify(episodeCompletionPort).markEpisodeReady(
12L, "/files/transcoded/88/master.m3u8", 120);
verify(transcodeService).updateProgress(TASK_ID, 45);
verify(transcodeService, never()).fail(any(), anyString());
}
@@ -110,11 +112,12 @@ class TranscodeWorkerTest {
@Test
void should_keep_main_transcode_successful_when_thumbnail_generation_fails() {
given(transcodeService.claimNext()).willReturn(queuedTask());
String thumbnailError = "tile 滤镜失败:" + "x".repeat(5000);
runner.thumbnail = new TranscodeRunner.ProducedThumbnail(
"transcoded/88/thumbnails/thumbnails.vtt",
"transcoded/88/thumbnails/sprite_",
1, 10, 1, 160, 0, 120, 120, 0L,
MediaThumbnail.STATUS_FAILED, "tile 滤镜失败");
MediaThumbnail.STATUS_FAILED, thumbnailError);
worker.processOne();
@@ -128,6 +131,8 @@ class TranscodeWorkerTest {
assertThat(thumbnail.getValue().getStatus())
.isEqualTo(MediaThumbnail.STATUS_FAILED);
assertThat(thumbnail.getValue().getErrorMsg()).contains("tile");
assertThat(thumbnail.getValue().getErrorMsg()).contains("truncated");
assertThat(thumbnail.getValue().getErrorMsg().length()).isLessThanOrEqualTo(500);
verify(mediaService).markReady(eq(ASSET_ID), anyString(), anyString(), eq(120));
verify(transcodeService, never()).fail(any(), anyString());
}
@@ -190,12 +195,16 @@ class TranscodeWorkerTest {
private TranscodeRequest request;
private RuntimeException failure;
private ProducedThumbnail thumbnail;
private Integer progress;
private boolean called;
@Override
public TranscodeResult run(TranscodeRequest request) {
this.request = request;
this.called = true;
if (progress != null) {
request.progressListener().accept(progress);
}
if (failure != null) {
throw failure;
}