Last update: @6/12/2023
Whiper API
•
OpenAI의 AI 음성 인식 및 번역 API로, 유튜브 자동 자막 또는 그 이상의 성능을 보여줌
◦
음성 인식 API - 예제에서 사용
https://api.openai.com/v1/audio/transcriptions
Plain Text
복사
▪
특별히 언어를 지정하지 않아도 자동으로 언어를 인식해서 텍스트로 바꿔줌
▪
특정 언어를 지정하면 다른 언어로 말을 해도 지정 언어로 바꿔줌(번역 API와 똑같이 동작함)
◦
번역 API - 모든 언어를 영어로 번역해줌
https://api.openai.com/v1/audio/translations
Plain Text
복사
Java
•
스프링 MVC 사용
•
•
컨트롤러 - 음성파일을 수신해서 openAI API로 넘겨줌
@ResponseBody
@PostMapping("/whisper")
public String transcript(
@RequestParam MultipartFile audio
) {
return sttClient.transcript(audio);
}
Java
복사
•
OpenAi 클래스 (HttpURLConnection 이용)
@Component
@Slf4j
public class WhisperClient implements SttClient {
@Value("${api-key.open-ai}")
private String API_KEY;
private final String API_URL = "https://api.openai.com/v1/audio/transcriptions";
@Override
public String transcript(MultipartFile audio) {
return transcript(audio, "en");
}
@Override
public String transcript(MultipartFile audio, String language) {
// Speech To Text(whisper API)
String script = "";
try {
// HTTP 통신 설정
URL url = new URL(API_URL);
HttpURLConnection con = (HttpURLConnection) url.openConnection();
con.setUseCaches(false);
con.setDoOutput(true);
con.setDoInput(true);
String LINE_FEED = "\r\n";
String boundary = "----" + UUID.randomUUID();
con.setRequestProperty("Authorization", "Bearer " + API_KEY);
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
OutputStream os = con.getOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(os, StandardCharsets.UTF_8), true);
// request body
writer.append("--" + boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"file\"; filename=\"" + "audio.webm" + "\"").append(LINE_FEED);
writer.append(LINE_FEED);
writer.flush();
InputStream is = audio.getInputStream();
BufferedInputStream bis = new BufferedInputStream(is);
byte[] buffer = new byte[4096];
int bytesRead = -1;
while ((bytesRead = bis.read(buffer)) != -1) {
os.write(buffer, 0, bytesRead);
}
os.flush();
bis.close();
writer.append(LINE_FEED);
writer.append("--" + boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"model\"").append(LINE_FEED);
writer.append(LINE_FEED);
writer.append("whisper-1").append(LINE_FEED);
writer.append("--" + boundary).append(LINE_FEED);
writer.append("Content-Disposition: form-data; name=\"language\"").append(LINE_FEED);
writer.append(LINE_FEED);
writer.append("en").append(LINE_FEED);
writer.append("--" + boundary + "--").append(LINE_FEED);
writer.close();
// 결과 수신
String output = new BufferedReader(new InputStreamReader(con.getInputStream()))
.lines()
.reduce((a, b) -> a + b)
.orElse("");
if (output.equals("")) {
return "";
}
log.info("transcript output = {}", output);
JSONObject jsonObject = new JSONObject(output);
script = jsonObject.getString("text");
} catch (Exception e) {
throw new ApiFailException(e);
}
return script;
}
}
Java
복사
•
Authorization 헤더는 Bearer가 포함되어야 하고, 띄어쓰기 후 API 키가 들어감
•
파라미터는 file, model 두 가지가 필수이고 나머지 prompt, response_format, temperature, language 파라미터가 옵션으로 들어갈 수 있음. 자세한 것은 API 문서 참고
전송되는 HTTP 전문은 아래와 같음
POST /v1/audio/transcriptions HTTP/1.1
Authorization: Bearer my_api_key
User-Agent: Java/17.0.6
Accept: */*
Host: api.openai.com
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Type: multipart/form-data; boundary=----2174e5e9-b18e-49f4-9a39-4024757ef25c
Content-Length: 12365
------2174e5e9-b18e-49f4-9a39-4024757ef25c
Content-Disposition: form-data; name="file"; filename="c9781b97-8cd6-4134-a91b-1ad43d100902.webm"
<binary data>
------2174e5e9-b18e-49f4-9a39-4024757ef25c
Content-Disposition: form-data; name="model"
whisper-1
------2174e5e9-b18e-49f4-9a39-4024757ef25c--
Plain Text
복사
View (HTML, Javascript)
•
대충 아래와 같은 화면에 녹음 및 출력을 해주는 페이지
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>OpenAI Whisper</title>
</head>
<body onload="mediaStart()">
<h1>whisper</h1>
<input type="button" id="record" value="녹음 시작">
<input type="button" id="stop" value="녹음 중지">
<textarea rows="3" id="textarea"></textarea>
<script>
// 음성 녹음
function mediaStart() {
let recordButton = document.querySelector("#record");
let stopButton = document.querySelector("#stop");
if (navigator.mediaDevices) {
const constraints = {audio:true};
let chunks = [] // 녹음된 내용을 저장할 버퍼
navigator.mediaDevices.getUserMedia({audio:true}).then(stream => {
// 녹음을 시작하고 종료하는 객체
const mediaRecorder = new MediaRecorder(stream, {mimeType:'audio/webm'});
// 녹음 시작
recordButton.onclick = () => {
chunks = []; // 이전 녹음 초기화
mediaRecorder.start();
recordButton.style.backgroundColor = "red";
recordButton.style.color = "white";
recordButton.disabled = true;
}
// 오디오 저장
mediaRecorder.ondataavailable = e => {
chunks.push(e.data);
}
// 녹음 종료
stopButton.onclick = () => {
mediaRecorder.stop();
recordButton.style.backgroundColor = "";
recordButton.style.color = "";
recordButton.disabled = false;
}
// 녹음이 종료되면 서버로 녹음 내용을 보내고 결과 수신
mediaRecorder.onstop = () => {
// chunks에 저장된 데이터를 바이너리코드로 변환
const blob = new Blob(chunks, {'type':'audio/webm'});
// 서버로 전송
let formData = new FormData();
formData.append("audio", blob);
ajax(formData);
}
}).catch(err => {
console.log(err);
});
} else {
console.log("미디어 장치 없음");
}
}
function ajax(formData) {
let request = new XMLHttpRequest();
request.onload = () => {
let responseText = request.responseText;
console.log(responseText);
document.querySelector('#textarea').innerText = responseText;
}
request.open("POST", "/whisper");
request.send(formData);
}
</script>
</body>
</html>
JavaScript
복사
테스트
•
중간 중간 영어를 섞어도 알아서 바꿔줌
•
마침표도 찍어주기 때문에 문장별로 파싱하기도 좋음.
Spring WebClient 이용 (@6/12/2023 추가)
•
스프링 5부터 지원하는 WebClient를 이용하면 코드를 간결하게 작성할 수 있음
◦
스프링 RestTemplate는 향후 deprecated될 예정
@Component
@Slf4j
public class WhisperClient implements SttClient {
@Value("${api-key.open-ai}")
private String API_KEY;
private final String API_URL = "https://api.openai.com/v1/audio/transcriptions";
private WebClient webClient;
@PostConstruct
private void init() {
webClient = WebClient.builder()
.baseUrl(API_URL)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + API_KEY)
.build();
}
@Override
public String transcript(MultipartFile audio) {
return transcript(audio, "en");
}
@Override
public String transcript(MultipartFile audio, String language) {
try {
MultipartBodyBuilder bodyBuilder = new MultipartBodyBuilder();
bodyBuilder.part("file", new InputStreamResource(audio.getInputStream()))
.filename("audio.webm");
bodyBuilder.part("model", "whisper-1");
bodyBuilder.part("language", language);
String result = webClient
.post()
.body(BodyInserters.fromMultipartData(bodyBuilder.build()))
.retrieve()
.bodyToMono(String.class)
.block();
return new JSONObject(result).getString("text");
} catch (Exception e) {
log.error(e.getMessage());
throw new ApiFailException(e);
}
}
}
Java
복사