Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,6 +18,7 @@
* 토론 기록 Soniox 모드에서 타이머가 끝난 발언 구간의 오디오를 받아 문장 단위로 전사한다.
* Soniox 전사가 끝날 때까지 요청 스레드에서 기다린다.
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class SpeechTranscribeService {
Expand All @@ -27,10 +30,21 @@ public SpeechTranscribeResponse transcribeWithSoniox(MultipartFile audio, @Nulla
if (audio == null || audio.isEmpty()) {
throw new DTClientErrorException(ClientErrorCode.EMPTY_AUDIO);
}
List<SpeechToken> tokens = sonioxClient.transcribe(audio.getResource(), toLanguageHints(language));
List<SpeechToken> tokens = transcribe(audio, language);
return new SpeechTranscribeResponse(utteranceAssembler.assemble(tokens));
}

// Soniox가 오디오를 거절하면 어떤 파일이었는지 알 수 있도록 업로드한 오디오 정보를 함께 남긴다.
private List<SpeechToken> 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<String> toLanguageHints(@Nullable String language) {
if (language != null && language.toLowerCase().startsWith("en")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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");
}
}
}
Loading