-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4 from goormthon-Univ/develope
Develope
- Loading branch information
Showing
10 changed files
with
316 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
--- | ||
name: "✅ FEATURE" | ||
about: Feature 작업 사항을 입력해주세요. | ||
title: '' | ||
labels: '' | ||
assignees: '' | ||
|
||
--- | ||
|
||
### Description | ||
> 설명을 작성해주세요. | ||
### In Progress | ||
> 작업사항들을 작성해주세요. | ||
- [ ] todo1 | ||
- [ ] todo2 | ||
- [ ] todo3 | ||
|
||
### ETC | ||
> 기타사항 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
--- | ||
name: "\U0001F41B BUG" | ||
about: bug 발생 시 작성해주세요. | ||
title: '' | ||
labels: '' | ||
assignees: '' | ||
|
||
--- | ||
|
||
### Describe the bug | ||
> 버그에 대해 설명해주세요. | ||
### To Reproduce | ||
> 버그 발생 과정을 기술하세요. | ||
1. Go to '...' | ||
2. Click on '....' | ||
3. Scroll down to '....' | ||
4. See error | ||
|
||
### Expected behavior | ||
> 본래 작동할 것이라 예상했던 구현 결과에 대해 설명하세요. | ||
### Screenshots | ||
> 화면 첨부파일 | ||
### Additional context(Optional) | ||
>발생한 문제에 대한 추가사항 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
## ☑️ Describe your changes | ||
- 작업 내용을 적어주세요 | ||
> ex. - CORS 허용 범위를 (/**) URL 전체로 변경한다. | ||
## 📷 Screenshot | ||
- 관련 스크린샷 | ||
|
||
## 🔗 Issue number and link | ||
- 이슈 번호를 등록해주세요 | ||
> closed {#이슈번호} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
97 changes: 97 additions & 0 deletions
97
src/main/java/site/balpyo/ai/controller/PollyController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
package site.balpyo.ai.controller; | ||
|
||
import com.amazonaws.util.IOUtils; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.HttpHeaders; | ||
import org.springframework.http.MediaType; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import site.balpyo.ai.dto.PollyDTO; | ||
import site.balpyo.ai.service.PollyService; | ||
import site.balpyo.common.dto.CommonResponse; | ||
import site.balpyo.common.dto.ErrorEnum; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
|
||
/** | ||
* @author dongheonlee | ||
* AWS polly를 활용한 tts 구현 컨트롤러 | ||
*/ | ||
@RestController | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
@RequestMapping("/api/polly") | ||
public class PollyController { | ||
|
||
@Value("${secrets.BALPYO_API_KEY}") //TODO :: 임시 api 시크릿 키 구현 (차후 로그인 연동시 삭제예정) | ||
public String BALPYO_API_KEY; | ||
|
||
private final PollyService pollyService; | ||
|
||
/** | ||
* @param pollyDTO | ||
* @return 호출 시, 요청정보에 따른 mp3 음성파일을 반환(audioBytes)한다. | ||
*/ | ||
@PostMapping("/generateAudio") | ||
public ResponseEntity<?> synthesizeText(@RequestBody PollyDTO pollyDTO) { | ||
|
||
if (!BALPYO_API_KEY.equals(pollyDTO.getBalpyoAPIKey())) { | ||
return CommonResponse.error(ErrorEnum.BALPYO_API_KEY_ERROR); | ||
} | ||
|
||
try { | ||
// Amazon Polly와 통합하여 텍스트를 음성으로 변환 | ||
InputStream audioStream = pollyService.synthesizeSpeech(pollyDTO); | ||
|
||
// InputStream을 byte 배열로 변환 | ||
byte[] audioBytes = IOUtils.toByteArray(audioStream); | ||
|
||
// MP3 파일을 클라이언트에게 반환 | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); | ||
headers.setContentDispositionFormData("testAudio", "speech.mp3"); | ||
|
||
return ResponseEntity.ok() | ||
.headers(headers) | ||
.body(audioBytes); | ||
|
||
} catch (IOException e) { | ||
e.printStackTrace(); | ||
return CommonResponse.error(ErrorEnum.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
|
||
|
||
/** | ||
* 입력한 대본을 음성 파일 변환 시, 음절당 기준값에 따라 계산된 소요시간을 반환한다. | ||
* @param pollyDTO | ||
* @return | ||
*/ | ||
@PostMapping("/estimateDuration") | ||
public ResponseEntity<CommonResponse> estimateSpeechDuration(@RequestBody PollyDTO pollyDTO) { | ||
|
||
if (!BALPYO_API_KEY.equals(pollyDTO.getBalpyoAPIKey())) { | ||
return CommonResponse.error(ErrorEnum.BALPYO_API_KEY_ERROR); | ||
} | ||
|
||
String inputText = pollyDTO.getText(); | ||
|
||
try { | ||
int durationInSeconds = pollyService.estimateSpeechDuration(inputText); | ||
|
||
|
||
return CommonResponse.success(durationInSeconds); | ||
|
||
} catch (Exception e) { | ||
e.printStackTrace(); | ||
return CommonResponse.error(ErrorEnum.INTERNAL_SERVER_ERROR); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package site.balpyo.ai.dto; | ||
|
||
import lombok.Data; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
|
||
/** | ||
* @author dongheonlee | ||
*/ | ||
@Data | ||
public class EstimateRequestDTO { | ||
|
||
// 소요시간을 계산할 입력 텍스트 | ||
@NotBlank(message = "text는 비어 있을 수 없습니다.") | ||
private String text; | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
package site.balpyo.ai.dto; | ||
|
||
import lombok.Data; | ||
|
||
import javax.validation.constraints.NotBlank; | ||
|
||
/** | ||
* @author dongheonlee | ||
*/ | ||
@Data | ||
public class PollyDTO { | ||
|
||
// 음성으로 변환할 텍스트 | ||
@NotBlank(message = "text는 비어 있을 수 없습니다.") | ||
private String text; | ||
|
||
// 빠르기 조절 [-2, -1, 0, 1, 2] | ||
@NotBlank(message = "speed는 비어 있을 수 없습니다.") | ||
private int speed; | ||
|
||
@NotBlank(message = "balpyoSecretKey는 비어 있을 수 없습니다.") | ||
private String balpyoAPIKey; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
package site.balpyo.ai.service; | ||
|
||
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; | ||
import com.amazonaws.regions.Regions; | ||
import com.amazonaws.services.polly.AmazonPolly; | ||
import com.amazonaws.services.polly.AmazonPollyClient; | ||
import com.amazonaws.services.polly.model.*; | ||
import org.springframework.stereotype.Service; | ||
import site.balpyo.ai.dto.PollyDTO; | ||
|
||
import java.io.InputStream; | ||
|
||
/** | ||
* @author dongheonlee | ||
*/ | ||
@Service | ||
public class PollyService { | ||
|
||
/** | ||
* 입력된 텍스트의 예상 음성 파일 재생 시간을 계산하여 반환한다. | ||
* | ||
* @param text 입력 텍스트 | ||
* @return 예상 음성 파일 재생 시간 (초) | ||
*/ | ||
public int estimateSpeechDuration(String text) { | ||
// 예상 소요 시간 계산을 위한 변수 | ||
int totalDuration = 0; | ||
|
||
// 입력 텍스트에서 "\n"과 "."을 제외한 문자열 추출 | ||
String cleanedText = text.replaceAll("[\\n.]", ""); | ||
|
||
// 입력 텍스트를 한 음절씩 분리하여 처리 | ||
for (char c : cleanedText.toCharArray()) { | ||
// 띄어쓰기나 쉼표가 있을 때마다 한 번씩 숨을 쉬는 시간 추가 | ||
if (c == ' ' || c == ',') { | ||
totalDuration += 1000; // 1초의 숨쉬는 시간으로 가정 | ||
} else { | ||
int durationPerSyllable = 150; // 음절당 150ms로 설정 | ||
totalDuration += durationPerSyllable; | ||
} | ||
} | ||
|
||
// 소요 시간을 초로 변환하여 반환 | ||
return totalDuration / 1000; // ms를 초로 변환 | ||
} | ||
|
||
|
||
/** | ||
* 입력된 텍스트와 선택된 빠르기에 따라 음성파일으로 변환하여 반환한다. | ||
* | ||
* @param pollyDTO | ||
* @return mp3 오디오 파일 | ||
*/ | ||
public InputStream synthesizeSpeech(PollyDTO pollyDTO) { | ||
|
||
String inputText = pollyDTO.getText(); | ||
int speed = pollyDTO.getSpeed(); | ||
|
||
// Amazon Polly 클라이언트 생성 | ||
AmazonPolly amazonPolly = AmazonPollyClient.builder() | ||
.withRegion(Regions.AP_NORTHEAST_2) // 서울 리전 | ||
.withCredentials(new DefaultAWSCredentialsProviderChain()) | ||
.build(); | ||
|
||
// 빠르기 계산 | ||
float relativeSpeed = calculateRelativeSpeed(speed); | ||
|
||
// SynthesizeSpeechRequest 생성 | ||
SynthesizeSpeechRequest synthesizeSpeechRequest = new SynthesizeSpeechRequest() | ||
.withText(inputText) | ||
.withOutputFormat(OutputFormat.Mp3) // MP3 형식 | ||
.withVoiceId(VoiceId.Seoyeon) // 한국어 음성 변환 보이스 | ||
.withTextType("ssml") // SSML 형식 사용 -> <prosody> 태그와 rate로 설정 가능 | ||
.withText("<speak><prosody rate=\"" + relativeSpeed + "\">" + inputText + "</prosody></speak>"); | ||
|
||
// 텍스트를 음성으로 변환하여 InputStream으로 반환 | ||
SynthesizeSpeechResult synthesizeSpeechResult = amazonPolly.synthesizeSpeech(synthesizeSpeechRequest); | ||
return synthesizeSpeechResult.getAudioStream(); | ||
} | ||
|
||
|
||
/** | ||
* mp3 audio 생성 시, 빠르기 설정 메소드 | ||
*/ | ||
private static float calculateRelativeSpeed(int speed) { | ||
// 기본 속도 | ||
float baseSpeed = 1.0f; | ||
|
||
switch (speed) { | ||
case -2: | ||
return baseSpeed * 0.5f; | ||
case -1: | ||
return baseSpeed * 0.8f; | ||
case 1: | ||
return baseSpeed * 1.2f; | ||
case 2: | ||
return baseSpeed * 1.5f; | ||
default: | ||
return baseSpeed; | ||
} | ||
} | ||
|
||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters