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
18 changes: 18 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,24 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

//websocket
implementation 'org.springframework.boot:spring-boot-starter-websocket'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

//Java용 gRPC 클라이언트, Google STT Streaming
//implementation 'com.google.cloud:google-cloud-speech:4.38.0'

//implementation 'com.fasterxml.jackson.core:jackson-databind'

//BOM으로 Google Cloud 라이브러리 버전 일괄 관리
implementation platform('com.google.cloud:libraries-bom:26.60.0')
// gRPC 스트리밍 번역
implementation 'com.google.api.grpc:grpc-google-cloud-translate-v3:2.63.0'
// --- STT, Translation, TTS 클라이언트 ---
implementation 'com.google.cloud:google-cloud-speech' // Speech-to-Text
implementation 'com.google.cloud:google-cloud-translate' // Translation API (REST)
implementation 'com.google.cloud:google-cloud-texttospeech' // Cloud Text-to-Speech
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
// package com.onebridge.ouch.ai.translate.googleSttPlusGpt;
//
// import java.io.IOException;
// import java.io.InputStream;
// import java.util.List;
// import java.util.concurrent.ConcurrentHashMap;
//
// import jakarta.annotation.PostConstruct;
// import org.springframework.beans.factory.annotation.Value;
// import org.springframework.core.io.Resource;
// import org.springframework.stereotype.Component;
// import org.springframework.web.socket.BinaryMessage;
// import org.springframework.web.socket.CloseStatus;
// import org.springframework.web.socket.TextMessage;
// import org.springframework.web.socket.WebSocketSession;
// import org.springframework.web.socket.handler.BinaryWebSocketHandler;
//
// import com.google.api.gax.core.FixedCredentialsProvider;
// import com.google.auth.oauth2.GoogleCredentials;
// import com.google.api.gax.rpc.ClientStream;
// import com.google.api.gax.rpc.ResponseObserver;
// import com.google.api.gax.rpc.StreamController;
// import com.google.cloud.speech.v1.RecognitionConfig;
// import com.google.cloud.speech.v1.StreamingRecognitionConfig;
// import com.google.cloud.speech.v1.StreamingRecognizeRequest;
// import com.google.cloud.speech.v1.StreamingRecognizeResponse;
// import com.google.cloud.speech.v1.StreamingRecognitionResult;
// import com.google.cloud.speech.v1.SpeechClient;
// import com.google.cloud.speech.v1.SpeechSettings;
// import com.google.protobuf.ByteString;
//
// @Component
// public class GoogleSttSocketHandler extends BinaryWebSocketHandler {
//
// @Value("${google.application.credentials}")
// private Resource googleCredentialResource;
//
// private SpeechClient speechClient;
// private final ConcurrentHashMap<String, ClientStream<StreamingRecognizeRequest>> clientStreams = new ConcurrentHashMap<>();
// private final GptTtsService gptTtsService;
//
// public GoogleSttSocketHandler(GptTtsService gptTtsService) {
// this.gptTtsService = gptTtsService;
// }
//
// @PostConstruct
// public void init() throws IOException {
// // 서비스 계정 JSON을 직접 로드해 명시적 자격 증명 설정
// try (InputStream is = googleCredentialResource.getInputStream()) {
// GoogleCredentials creds = GoogleCredentials.fromStream(is)
// .createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));
//
// SpeechSettings settings = SpeechSettings.newBuilder()
// .setCredentialsProvider(FixedCredentialsProvider.create(creds))
// .build();
//
// speechClient = SpeechClient.create(settings);
// }
// }
//
// @Override
// public void afterConnectionEstablished(WebSocketSession session) {
// System.out.println("[서버] WebSocket 연결됨: " + session.getId());
// try {
// // STT 설정
// RecognitionConfig recogConfig = RecognitionConfig.newBuilder()
// .setEncoding(RecognitionConfig.AudioEncoding.WEBM_OPUS)
// .setSampleRateHertz(48000)
// .setLanguageCode("ko-KR")
// .build();
//
// StreamingRecognitionConfig streamingConfig = StreamingRecognitionConfig.newBuilder()
// .setConfig(recogConfig)
// .setInterimResults(true)
// .build();
//
// // STT 스트림 시작
// ClientStream<StreamingRecognizeRequest> clientStream =
// speechClient.streamingRecognizeCallable().splitCall(new ResponseObserver<StreamingRecognizeResponse>() {
// @Override
// public void onStart(StreamController controller) {
// System.out.println("[서버] STT Streaming 시작 (세션: " + session.getId() + ")");
// }
//
// @Override
// public void onResponse(StreamingRecognizeResponse response) {
// for (StreamingRecognitionResult result : response.getResultsList()) {
// if (!result.getIsFinal()) continue;
//
// String transcript = result.getAlternatives(0).getTranscript().trim();
// if (transcript.isEmpty()) continue;
//
// try {
// byte[] audioBytes = gptTtsService.translateAndSynthesize(transcript);
// session.sendMessage(new BinaryMessage(audioBytes));
// } catch (Exception e) {
// try {
// session.sendMessage(new TextMessage("TTS 처리 중 오류가 발생했습니다."));
// } catch (IOException ioe) {
// System.err.println("[서버] 클라이언트 알림 전송 오류: " + ioe.getMessage());
// }
// }
// }
// }
//
// @Override
// public void onError(Throwable t) {
// System.err.println("[서버] STT 오류 (세션: " + session.getId() + "): " + t.getMessage());
// try {
// session.sendMessage(new TextMessage("STT 처리 중 오류가 발생했습니다."));
// } catch (IOException ioe) {
// System.err.println("[서버] 클라이언트 알림 전송 오류: " + ioe.getMessage());
// }
// }
//
// @Override
// public void onComplete() {
// System.out.println("[서버] STT Streaming 종료 (세션: " + session.getId() + ")");
// // 세션은 클라이언트가 stop할 때까지 유지
// }
// });
//
// // 초기 설정 전송
// clientStream.send(StreamingRecognizeRequest.newBuilder()
// .setStreamingConfig(streamingConfig)
// .build());
//
// clientStreams.put(session.getId(), clientStream);
//
// } catch (Exception e) {
// e.printStackTrace();
// try {
// session.close(CloseStatus.SERVER_ERROR);
// } catch (IOException ioe) {
// System.err.println("[서버] 세션 종료 오류: " + ioe.getMessage());
// }
// }
// }
//
// @Override
// protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) {
// ClientStream<StreamingRecognizeRequest> clientStream = clientStreams.get(session.getId());
// if (clientStream != null) {
// try {
// clientStream.send(StreamingRecognizeRequest.newBuilder()
// .setAudioContent(ByteString.copyFrom(message.getPayload().array()))
// .build());
// } catch (Exception e) {
// System.err.println("[서버] 오디오 청크 전송 오류 (세션: " + session.getId() + "): " + e.getMessage());
// }
// }
// }
//
// @Override
// public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
// System.out.println("[서버] WebSocket 종료: " + session.getId() + " (" + status + ")");
// ClientStream<StreamingRecognizeRequest> stream = clientStreams.remove(session.getId());
// if (stream != null) stream.closeSend();
// }
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// package com.onebridge.ouch.ai.translate.googleSttPlusGpt;
//
// import org.springframework.beans.factory.annotation.Value;
// import org.springframework.http.HttpEntity;
// import org.springframework.http.HttpHeaders;
// import org.springframework.http.HttpMethod;
// import org.springframework.http.MediaType;
// import org.springframework.http.ResponseEntity;
// import org.springframework.stereotype.Service;
// import org.springframework.web.client.RestTemplate;
//
// import com.fasterxml.jackson.databind.JsonNode;
// import com.fasterxml.jackson.databind.ObjectMapper;
//
// @Service
// public class GptTtsService {
// @Value("${openai.api-key}")
// private String openAiApiKey;
//
// private final RestTemplate restTemplate = new RestTemplate();
//
// public byte[] translateAndSynthesize(String text) {
// String translatedText = translateText(text);
// return textToSpeech(translatedText);
// }
//
// private String translateText(String text) {
// String url = "https://api.openai.com/v1/chat/completions";
// HttpHeaders headers = new HttpHeaders();
// headers.setBearerAuth(openAiApiKey);
// headers.setContentType(MediaType.APPLICATION_JSON);
//
// String systemPrompt = "Detect the language and translate the following text to Korean if it is not Korean, or to English if it is Korean. Output only the translation.";
// String reqBody = String.format("""
// {
// "model": "gpt-4o",
// "messages": [
// {"role": "system", "content": "%s"},
// {"role": "user", "content": "%s"}
// ],
// "max_tokens": 512
// }
// """, systemPrompt, text.replace("\"", "\\\""));
//
// HttpEntity<String> requestEntity = new HttpEntity<>(reqBody, headers);
// ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
// if (!response.getStatusCode().is2xxSuccessful()) {
// throw new RuntimeException("번역 API 실패: " + response.getBody());
// }
//
// // GPT 응답 JSON 파싱 (Jackson 등 사용)
// try {
// ObjectMapper mapper = new ObjectMapper();
// JsonNode root = mapper.readTree(response.getBody());
// return root.path("choices").get(0).path("message").path("content").asText().trim();
// } catch (Exception e) {
// throw new RuntimeException("GPT 응답 파싱 오류", e);
// }
// }
//
// private byte[] textToSpeech(String text) {
// String url = "https://api.openai.com/v1/audio/speech";
// HttpHeaders headers = new HttpHeaders();
// headers.setBearerAuth(openAiApiKey);
// headers.setContentType(MediaType.APPLICATION_JSON);
//
// String reqBody = String.format("""
// {
// "model": "tts-1",
// "input": "%s",
// "voice": "nova",
// "response_format": "opus"
// }
// """, text.replace("\"", "\\\""));
//
// HttpEntity<String> requestEntity = new HttpEntity<>(reqBody, headers);
// ResponseEntity<byte[]> response = restTemplate.exchange(
// url, HttpMethod.POST, requestEntity, byte[].class
// );
//
// if (!response.getStatusCode().is2xxSuccessful()) {
// throw new RuntimeException("TTS API 실패: " + response.getBody());
// }
// return response.getBody();
// }
// }
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// package com.onebridge.ouch.ai.translate.googleSttPlusGpt;
//
// import org.springframework.context.annotation.Configuration;
// import org.springframework.beans.factory.annotation.Autowired;
// import org.springframework.web.socket.config.annotation.*;
//
// @Configuration
// @EnableWebSocket
// public class WebSocketConfig implements WebSocketConfigurer {
// @Autowired
// private GoogleSttSocketHandler googleSttSocketHandler;
//
// @Override
// public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// registry.addHandler(googleSttSocketHandler, "/ws/stt").setAllowedOrigins("*");
// }
// }
Loading