1+ package com .ureca .unity .domain .stt .service ;
2+
3+ import com .google .api .gax .core .FixedCredentialsProvider ;
4+ import com .google .auth .oauth2 .GoogleCredentials ;
5+ import com .google .cloud .speech .v1 .*;
6+ import com .google .protobuf .ByteString ;
7+ import com .ureca .unity .domain .stt .mapper .SttMapper ;
8+ import com .ureca .unity .domain .stt .model .SttJob ;
9+ import lombok .RequiredArgsConstructor ;
10+ import lombok .extern .slf4j .Slf4j ;
11+ import org .springframework .beans .factory .annotation .Value ;
12+ import org .springframework .core .io .DefaultResourceLoader ;
13+ import org .springframework .core .io .Resource ;
14+ import org .springframework .stereotype .Service ;
15+ import ws .schild .jave .Encoder ;
16+ import ws .schild .jave .MultimediaObject ;
17+ import ws .schild .jave .encode .AudioAttributes ;
18+ import ws .schild .jave .encode .EncodingAttributes ;
19+
20+ import java .io .File ;
21+ import java .io .IOException ;
22+ import java .io .InputStream ;
23+ import java .nio .file .Files ;
24+ import java .nio .file .Path ;
25+ import java .nio .file .Paths ;
26+ import java .util .stream .Collectors ;
27+
28+ @ Service
29+ @ RequiredArgsConstructor
30+ @ Slf4j
31+ public class SttServiceImpl implements SttService {
32+
33+ private final SttMapper sttMapper ;
34+
35+ @ Value ("${google.cloud.credentials.location}" )
36+ private String keyPath ;
37+
38+ @ Override
39+ public SttJob startStt (Long counselingId ) {
40+ Long userId = 1L ; // 임시
41+
42+ SttJob job = sttMapper .findByCounselingId (counselingId );
43+
44+ if (job == null ) {
45+ job = SttJob .builder ()
46+ .counselingId (counselingId )
47+ .userId (userId )
48+ .summaryType ("CALL" )
49+ .status ("LOADING" )
50+ .build ();
51+ sttMapper .insert (job );
52+ }
53+
54+ // 1. 오디오 파일 읽기
55+ // String audioPath = "C:/Users/food0/Desktop/realsample.wav";
56+ Path m4aPath = Paths .get ("C:/Users/food0/Desktop/테헤란로.m4a" );
57+ File tempWavFile = null ;
58+
59+ try {
60+ tempWavFile = convertM4aToWav (m4aPath .toFile ());
61+
62+ byte [] data = Files .readAllBytes (tempWavFile .toPath ());
63+ ByteString audioBytes = ByteString .copyFrom (data );
64+
65+ RecognitionAudio audio = RecognitionAudio .newBuilder ()
66+ .setContent (audioBytes )
67+ .build ();
68+
69+ RecognitionConfig config = RecognitionConfig .newBuilder ()
70+ .setEncoding (RecognitionConfig .AudioEncoding .LINEAR16 )
71+ .setLanguageCode ("ko-KR" )
72+ .setSampleRateHertz (16000 )
73+ .setAudioChannelCount (1 )
74+ .build ();
75+
76+ // 2. Google STT 인증 및 실행
77+ Resource resource = new DefaultResourceLoader ().getResource (keyPath );
78+ try (InputStream is = resource .getInputStream ()) {
79+ GoogleCredentials credentials = GoogleCredentials .fromStream (is );
80+ SpeechSettings settings = SpeechSettings .newBuilder ()
81+ .setCredentialsProvider (FixedCredentialsProvider .create (credentials ))
82+ .build ();
83+
84+ try (SpeechClient speechClient = SpeechClient .create (settings )) {
85+ RecognizeResponse response = speechClient .recognize (config , audio );
86+
87+ String text = response .getResultsList ().stream ()
88+ .map (r -> r .getAlternatives (0 ).getTranscript ())
89+ .collect (Collectors .joining (" " ));
90+
91+ job .setStatus ("SUCCESS" );
92+ job .setSummaryText (text );
93+ }
94+ } // InputStream close
95+
96+ } catch (Exception e ) {
97+ log .error ("STT 처리 중 오류 발생: " , e );
98+ job .setStatus ("FAIL" ); // 에러 시 상태 업데이트
99+ job .setSummaryText ("Error: " + e .getMessage ());
100+ } finally {
101+ // 4. 사용 후 임시 wav 파일 삭제 (서버 용량 관리)
102+ if (tempWavFile != null && tempWavFile .exists ()) {
103+ tempWavFile .delete ();
104+ log .info ("임시 변환 파일 삭제 완료" );
105+ }
106+ }
107+
108+ // 3. 결과 DB 업데이트
109+ sttMapper .updateResult (job );
110+ return job ;
111+ }
112+
113+ private File convertM4aToWav (File source ) throws Exception {
114+ File target = File .createTempFile ("stt_temp_" , ".wav" );
115+
116+ AudioAttributes audio = new AudioAttributes ();
117+ audio .setCodec ("pcm_s16le" ); // Google STT 권장 코덱
118+ audio .setSamplingRate (16000 );
119+ audio .setChannels (1 );
120+
121+ EncodingAttributes attrs = new EncodingAttributes ();
122+ attrs .setOutputFormat ("wav" );
123+ attrs .setAudioAttributes (audio );
124+
125+ Encoder encoder = new Encoder ();
126+ encoder .encode (new MultimediaObject (source ), target , attrs );
127+
128+ log .info ("변환 완료: {} -> {}" , source .getName (), target .getName ());
129+ return target ;
130+ }
131+
132+ @ Override
133+ public SttJob getStt (Long counselingId ) {
134+ return sttMapper .findByCounselingId (counselingId );
135+ }
136+ }
0 commit comments