diff --git a/src/main/java/com/debatetimer/service/transcript/SpeechTranscribeService.java b/src/main/java/com/debatetimer/service/transcript/SpeechTranscribeService.java index 6a7218b0..93a454d7 100644 --- a/src/main/java/com/debatetimer/service/transcript/SpeechTranscribeService.java +++ b/src/main/java/com/debatetimer/service/transcript/SpeechTranscribeService.java @@ -5,9 +5,11 @@ import com.debatetimer.domain.transcript.UtteranceAssembler; import com.debatetimer.dto.transcript.response.SpeechTranscribeResponse; import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.custom.DTServerErrorException; import com.debatetimer.exception.errorcode.ClientErrorCode; import java.util.List; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.lang.Nullable; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; @@ -16,6 +18,7 @@ * 토론 기록 Soniox 모드에서 타이머가 끝난 발언 구간의 오디오를 받아 문장 단위로 전사한다. * Soniox 전사가 끝날 때까지 요청 스레드에서 기다린다. */ +@Slf4j @Service @RequiredArgsConstructor public class SpeechTranscribeService { @@ -27,10 +30,21 @@ public SpeechTranscribeResponse transcribeWithSoniox(MultipartFile audio, @Nulla if (audio == null || audio.isEmpty()) { throw new DTClientErrorException(ClientErrorCode.EMPTY_AUDIO); } - List tokens = sonioxClient.transcribe(audio.getResource(), toLanguageHints(language)); + List tokens = transcribe(audio, language); return new SpeechTranscribeResponse(utteranceAssembler.assemble(tokens)); } + // Soniox가 오디오를 거절하면 어떤 파일이었는지 알 수 있도록 업로드한 오디오 정보를 함께 남긴다. + private List transcribe(MultipartFile audio, @Nullable String language) { + try { + return sonioxClient.transcribe(audio.getResource(), toLanguageHints(language)); + } catch (DTServerErrorException exception) { + log.warn("Soniox 전사에 실패한 오디오입니다. name={}, contentType={}, size={}", + audio.getOriginalFilename(), audio.getContentType(), audio.getSize()); + throw exception; + } + } + // 프론트 음성 인식 언어(ko-KR, en-US 등)를 Soniox 언어 힌트로 바꾼다. 기본은 한국어다. private List toLanguageHints(@Nullable String language) { if (language != null && language.toLowerCase().startsWith("en")) { diff --git a/src/test/java/com/debatetimer/service/transcript/SpeechTranscribeServiceTest.java b/src/test/java/com/debatetimer/service/transcript/SpeechTranscribeServiceTest.java index 9f0296f5..10a536ce 100644 --- a/src/test/java/com/debatetimer/service/transcript/SpeechTranscribeServiceTest.java +++ b/src/test/java/com/debatetimer/service/transcript/SpeechTranscribeServiceTest.java @@ -13,15 +13,21 @@ import com.debatetimer.domain.transcript.Utterance; import com.debatetimer.dto.transcript.response.SpeechTranscribeResponse; import com.debatetimer.exception.custom.DTClientErrorException; +import com.debatetimer.exception.custom.DTServerErrorException; +import com.debatetimer.exception.errorcode.ServerErrorCode; import com.debatetimer.exception.errorcode.ClientErrorCode; import com.debatetimer.service.BaseServiceTest; import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.context.bean.override.mockito.MockitoBean; +@ExtendWith(OutputCaptureExtension.class) class SpeechTranscribeServiceTest extends BaseServiceTest { private static final MockMultipartFile AUDIO = new MockMultipartFile("audio", "segment.webm", "audio/webm", @@ -70,5 +76,19 @@ class TranscribeWithSoniox { .hasMessage(ClientErrorCode.EMPTY_AUDIO.getMessage()); verifyNoInteractions(sonioxClient); } + + @Test + void 전사에_실패하면_오디오_이름_형식_크기를_로그에_남기고_예외를_그대로_던진다(CapturedOutput output) { + when(sonioxClient.transcribe(any(), any())) + .thenThrow(new DTServerErrorException(ServerErrorCode.STT_REQUEST_FAILED)); + + assertThatThrownBy(() -> speechTranscribeService.transcribeWithSoniox(AUDIO, "ko-KR")) + .isInstanceOf(DTServerErrorException.class) + .hasMessage(ServerErrorCode.STT_REQUEST_FAILED.getMessage()); + assertThat(output.getAll()) + .contains("segment.webm") + .contains("audio/webm") + .contains("size=3"); + } } }